From 99a632433026ee3fcfe2316905dbbc6385347196 Mon Sep 17 00:00:00 2001 From: antianqi <75944423+antianqi@users.noreply.github.com> Date: Fri, 14 Aug 2026 23:21:19 +0800 Subject: [PATCH 1/5] Add skill-bridge plugin (antianqi/skill-bridge) Convert openclaw (and similar) skills into mavis/mcode-compatible skills or plugins. Detects encoding, parameterizes hardcoded paths, enriches frontmatter, runs the official lint, and produces a portable Skill-only Agent Plugin. - 1 Skill (skill-bridge) - 6 lib modules - 3 working demo conversions (task-tracker, investor-brand-kit, self-improving-agent) - 29 unit + integration tests - MIT license - Validates clean against the official plugin-compatibility.md schema --- plugins/antianqi/skill-bridge/.gitignore | 2 + plugins/antianqi/skill-bridge/LICENSE | 21 + plugins/antianqi/skill-bridge/README.md | 199 ++++++ .../input/investor-brand-kit/SKILL.md | 308 +++++++++ .../input/self-improving-agent/SKILL.md | 651 ++++++++++++++++++ .../examples/input/task-tracker/SKILL.md | 89 +++ .../output/investor-brand-kit/SKILL.md | 326 +++++++++ .../investor-brand-kit/conversion-report.md | 21 + .../output/self-improving-agent/SKILL.md | 51 ++ .../self-improving-agent/conversion-report.md | 45 ++ .../references/after-api-changes.md | 4 + .../references/area-tags.md | 12 + .../references/automatic-skill-extraction.md | 64 ++ .../references/best-practices.md | 10 + .../references/build-dependencies.md | 10 + .../references/detection-triggers.md | 26 + .../err-yyyymmdd-xxx-skill_or_command_name.md | 36 + .../feat-yyyymmdd-xxx-capability_name.md | 25 + .../references/generic-setup-other-agents.md | 20 + .../references/gitignore-options.md | 15 + .../references/hook-integration.md | 55 ++ .../references/id-generation.md | 8 + .../references/logging-format.md | 7 + .../references/lrn-yyyymmdd-xxx-category.md | 34 + .../references/multi-agent-support.md | 22 + .../references/openclaw-setup-recommended.md | 81 +++ .../references/periodic-review.md | 27 + .../references/priority-guidelines.md | 8 + .../references/promoting-to-project-memory.md | 37 + .../references/recurring-pattern-detection.md | 11 + .../references/resolving-entries.md | 18 + .../references/self-improvement.md | 38 + .../references/simplify-harden-feed.md | 36 + .../examples/output/task-tracker/SKILL.md | 105 +++ .../output/task-tracker/conversion-report.md | 21 + plugins/antianqi/skill-bridge/index.js | 215 ++++++ plugins/antianqi/skill-bridge/lib/analyze.js | 114 +++ plugins/antianqi/skill-bridge/lib/classify.js | 94 +++ plugins/antianqi/skill-bridge/lib/detect.js | 111 +++ plugins/antianqi/skill-bridge/lib/lint.js | 71 ++ plugins/antianqi/skill-bridge/lib/paths.js | 131 ++++ .../skill-bridge/lib/transform-skill.js | 251 +++++++ .../antianqi/skill-bridge/package-lock.json | 63 ++ plugins/antianqi/skill-bridge/package.json | 42 ++ plugins/antianqi/skill-bridge/plugin.json | 14 + .../references/compatibility-matrix.md | 70 ++ .../references/encoding-tables.md | 56 ++ .../skill-bridge/references/path-patterns.md | 60 ++ .../skill-bridge/skills/skill-bridge/SKILL.md | 107 +++ .../skill-bridge/tests/classify.test.mjs | 70 ++ .../antianqi/skill-bridge/tests/cli.test.mjs | 67 ++ .../skill-bridge/tests/detect.test.mjs | 50 ++ .../skill-bridge/tests/paths.test.mjs | 60 ++ .../tests/transform-skill.test.mjs | 105 +++ 54 files changed, 4194 insertions(+) create mode 100644 plugins/antianqi/skill-bridge/.gitignore create mode 100644 plugins/antianqi/skill-bridge/LICENSE create mode 100644 plugins/antianqi/skill-bridge/README.md create mode 100644 plugins/antianqi/skill-bridge/examples/input/investor-brand-kit/SKILL.md create mode 100644 plugins/antianqi/skill-bridge/examples/input/self-improving-agent/SKILL.md create mode 100644 plugins/antianqi/skill-bridge/examples/input/task-tracker/SKILL.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/SKILL.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/conversion-report.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/SKILL.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/conversion-report.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/after-api-changes.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/area-tags.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/automatic-skill-extraction.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/best-practices.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/build-dependencies.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/detection-triggers.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/err-yyyymmdd-xxx-skill_or_command_name.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/feat-yyyymmdd-xxx-capability_name.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/generic-setup-other-agents.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/gitignore-options.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/hook-integration.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/id-generation.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/logging-format.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/lrn-yyyymmdd-xxx-category.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/multi-agent-support.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/openclaw-setup-recommended.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/periodic-review.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/priority-guidelines.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/promoting-to-project-memory.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/recurring-pattern-detection.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/resolving-entries.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/self-improvement.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/simplify-harden-feed.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/task-tracker/SKILL.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md create mode 100644 plugins/antianqi/skill-bridge/index.js create mode 100644 plugins/antianqi/skill-bridge/lib/analyze.js create mode 100644 plugins/antianqi/skill-bridge/lib/classify.js create mode 100644 plugins/antianqi/skill-bridge/lib/detect.js create mode 100644 plugins/antianqi/skill-bridge/lib/lint.js create mode 100644 plugins/antianqi/skill-bridge/lib/paths.js create mode 100644 plugins/antianqi/skill-bridge/lib/transform-skill.js create mode 100644 plugins/antianqi/skill-bridge/package-lock.json create mode 100644 plugins/antianqi/skill-bridge/package.json create mode 100644 plugins/antianqi/skill-bridge/plugin.json create mode 100644 plugins/antianqi/skill-bridge/references/compatibility-matrix.md create mode 100644 plugins/antianqi/skill-bridge/references/encoding-tables.md create mode 100644 plugins/antianqi/skill-bridge/references/path-patterns.md create mode 100644 plugins/antianqi/skill-bridge/skills/skill-bridge/SKILL.md create mode 100644 plugins/antianqi/skill-bridge/tests/classify.test.mjs create mode 100644 plugins/antianqi/skill-bridge/tests/cli.test.mjs create mode 100644 plugins/antianqi/skill-bridge/tests/detect.test.mjs create mode 100644 plugins/antianqi/skill-bridge/tests/paths.test.mjs create mode 100644 plugins/antianqi/skill-bridge/tests/transform-skill.test.mjs diff --git a/plugins/antianqi/skill-bridge/.gitignore b/plugins/antianqi/skill-bridge/.gitignore new file mode 100644 index 0000000..21542b7 --- /dev/null +++ b/plugins/antianqi/skill-bridge/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +tests/last-run.log \ No newline at end of file diff --git a/plugins/antianqi/skill-bridge/LICENSE b/plugins/antianqi/skill-bridge/LICENSE new file mode 100644 index 0000000..4bea20a --- /dev/null +++ b/plugins/antianqi/skill-bridge/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 antianqi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/antianqi/skill-bridge/README.md b/plugins/antianqi/skill-bridge/README.md new file mode 100644 index 0000000..652e890 --- /dev/null +++ b/plugins/antianqi/skill-bridge/README.md @@ -0,0 +1,199 @@ +# skill-bridge + +> Convert openclaw (and similar) skills into mavis/mcode-compatible skills or plugins. + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![Node](https://img.shields.io/badge/node-%3E%3D22.19-brightgreen)](package.json) + +## Why + +`openclaw` (and other agent frameworks) and `mavis` / `mcode` don't share a skill format. The hard parts are: + +1. **Schema gap** — openclaw skills are 2-field frontmatter; mavis needs `descriptions.zh-Hans`, `displayNames`, `metadata`, locale keys. +2. **Encoding gap** — openclaw wrote Chinese as GBK and filenames as mojibake. mavis requires UTF-8. +3. **Path gap** — openclaw skills hardcode `C:\Users\Administrator\.openclaw\workspace\...` and `/tmp/CLI-Anything/...`. mavis needs parameterized paths. +4. **Platform gap** — openclaw assumes `bash` / `pip install -e .` / `python3` in PATH. mavis (especially on Windows) needs PowerShell equivalents. +5. **Discovery gap** — openclaw's staging directory is not in mavis's skill scan path. Copying files there does nothing. + +**skill-bridge** turns "copy the folder and pray" into a deterministic pipeline: detect → analyze → classify → transform → lint. + +## Install + +```bash +# from a clone of this repo +npm install +npm link # so `mcode-skill-bridge` is on PATH +# OR via mcode plugin install (after this is published): +# mcode plugin add https://github.com/antianqi/skill-bridge +``` + +Requires **Node.js 22.19+ or 24+** (matches the mcode engine). + +## Quick start + +```bash +# 1. Look at one openclaw skill +mcode-skill-bridge analyze /path/to/openclaw/skills/task-tracker + +# 2. See what tier it falls into +mcode-skill-bridge classify /path/to/openclaw/skills/task-tracker + +# 3. Convert to a mavis-compatible skill +mcode-skill-bridge convert /path/to/openclaw/skills/task-tracker \ + --out ~/.minimax/agents/mavis/skills/task-tracker +``` + +After step 3, restart mavis (or start a new session) and the converted skill shows up in ``. + +## How it works + +``` +input SKILL.md + │ + ▼ +[detect] GBK vs UTF-8; restore mojibake if needed + │ + ▼ +[analyze] parse frontmatter, scan hardcoded paths, scan external commands + │ + ▼ +[classify] pure-translate | pure-wrapped-fix | wrapped-* | abandon + │ + ▼ +[transform] write new SKILL.md (+ optional references/) to mavis schema + │ + ▼ +[lint] run the official skill-creator lint on the output + │ + ▼ +output: mavis-compatible skill +``` + +### The three tiers + +| Tier | What it is | Output in v0.1 | +|---|---|---| +| `pure-translate` | Pure instruction, ASCII-clean, no hardcoded paths | A `SKILL.md` with enriched frontmatter only | +| `pure-wrapped-fix` | Pure instruction but with hardcoded paths or GBK | A `SKILL.md` with paths parameterized + encoding fixed + Windows notes added | +| `wrapped-*` | Needs an external CLI/API (Python, ComfyUI, Douyin, …) | **Not supported in v0.1.** v0.2 will emit a plugin skeleton. | + +## What's in v0.1 + +- ✅ `lib/detect.js` — UTF-8 / GBK detection via `iconv-lite` + heuristic mojibake detection +- ✅ `lib/paths.js` — 6 hardcoded path patterns → `${OPENCLAW_HOME}`, `${OPENCLAW_WORKSPACE}`, `${SCRATCH}`, `${DATA_DIR}` +- ✅ `lib/analyze.js` — YAML frontmatter parse, hardcoded-path scan, external-command scan +- ✅ `lib/classify.js` — 4-question decision tree +- ✅ `lib/transform-skill.js` — frontmatter enrichment, body path rewriting, 500-line body splitter, Windows notes injection +- ✅ `lib/lint.js` — wraps the official `~/.minimax/.builtin-skills/skill-creator/scripts/lint-skill.js` (handles the `.js`-as-ESM quirk) +- ✅ `index.js` — CLI with `detect` / `analyze` / `classify` / `convert` / `lint` +- ✅ `skills/SKILL.md` — discoverable LLM entry (so a Mavis session can use it without remembering the CLI) +- ✅ 29 unit + integration tests +- ✅ 3 working demos (see `examples/output/`) + +## What's NOT in v0.1 + +- ❌ `wrapped-*` → plugin skeleton generation (planned for v0.2) +- ❌ GBK **filename** restoration (we warn, we don't rename) +- ❌ npm publish (planned for v0.2) +- ❌ Reverse tool (mavis → openclaw) +- ❌ Auto-registration into mavis's scan path (you have to restart the session) + +## Try the demos + +```bash +git clone https://github.com/antianqi/skill-bridge +cd skill-bridge +npm install +npm run demo:all +# inspect the output +ls examples/output/task-tracker +cat examples/output/task-tracker/SKILL.md +cat examples/output/task-tracker/conversion-report.md +``` + +The three demos cover the main pure-tier shapes: + +| Demo | What it stresses | +|---|---| +| `task-tracker` | Chinese name in source, hardcoded `${OPENCLAW_WORKSPACE}` path, no external deps | +| `investor-brand-kit` | CJK body with rich content, no path/encoding issues (pure-translate) | +| `self-improving-agent` | 600+ line body → automatically split into `references/` | + +## CLI reference + +``` +mcode-skill-bridge detect Detect encoding of a SKILL.md +mcode-skill-bridge analyze Analyze (frontmatter, paths, external cmds) +mcode-skill-bridge classify Classify into pure / wrapped / abandon +mcode-skill-bridge convert Convert and write to --out +mcode-skill-bridge lint Lint a converted skill + +Options: + --out Output directory (default: ./out/) + --force Overwrite existing output + --no-lint Skip lint after convert + --scope user | agent | project (informational) + --json Machine-readable output +``` + +## Project layout + +``` +skill-bridge/ +├── plugin.json # mcode plugin manifest +├── index.js # CLI entry +├── package.json +├── lib/ # pure ESM modules +│ ├── detect.js +│ ├── paths.js +│ ├── analyze.js +│ ├── classify.js +│ ├── transform-skill.js +│ └── lint.js +├── skills/ +│ └── SKILL.md # discoverable LLM entry +├── references/ # human docs +│ ├── compatibility-matrix.md +│ ├── path-patterns.md +│ └── encoding-tables.md +├── examples/ +│ ├── input/ # original openclaw skills (CC0 from openclaw) +│ └── output/ # converted mavis skills +└── tests/ + ├── detect.test.mjs + ├── paths.test.mjs + ├── classify.test.mjs + ├── transform-skill.test.mjs + └── cli.test.mjs +``` + +## Method — how we decided what's a "compatible skill" + +See [`references/compatibility-matrix.md`](references/compatibility-matrix.md) for the full mapping of all 36 openclaw skills into the three tiers. + +The high-level rule: + +> If the skill is a self-contained instruction (you can read it and act on it without installing anything else), it is `pure`. Otherwise, it is `wrapped`. If it depends on openclaw-specific runtime (e.g. the openclaw TUI, a specific Python venv, a non-replicable hard-coded directory), it is `abandon`. + +## Roadmap + +- **v0.2** — `wrapped-*` tier: generate a real mavis plugin (`plugin.json` + `index.js`) for skills that need external CLIs/APIs +- **v0.3** — Web UI via the `visual-page` skill, history-aware incremental conversion +- **v0.4** — Reverse tool: mavis skill → openclaw-compatible bundle + +## Contributing + +1. Fork the repo. +2. Add a fixture under `tests/fixtures/` for the new edge case. +3. Add a test under `tests/`. +4. Open a PR. CI will run `npm test`. + +## License + +MIT — see [LICENSE](LICENSE). + +## Credits + +- The mavis skill schema and lint rules are owned by MiniMax. +- The three demo skills (`task-tracker`, `investor-brand-kit`, `self-improving-agent`) are adapted from the openclaw workspace with the author's permission. +- Built by [antianqi](https://github.com/antianqi). diff --git a/plugins/antianqi/skill-bridge/examples/input/investor-brand-kit/SKILL.md b/plugins/antianqi/skill-bridge/examples/input/investor-brand-kit/SKILL.md new file mode 100644 index 0000000..e6bd2b2 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/input/investor-brand-kit/SKILL.md @@ -0,0 +1,308 @@ +--- +name: 绿川椒品牌招商核心资料库 +description: 整合品牌信息、差异化卖点、招商角度、合规规则的完整知识库。写脚本前必读。 +--- + +# 绿川椒品牌招商核心资料库 + +> 写脚本前必读。整合了品牌规划书原文 + PPT截图 + 7个脚本 + TASKS.md 实战积累。 + +--- + +## 一、品牌基础信息 + +| 项目 | 内容 | +|------|------| +| 品牌全称 | 绿川椒清水麻辣烫(曾用名:清水绿川椒麻辣烫) | +| 公司全称 | 齐齐哈尔清水绿川椒餐饮管理有限公司 | +| 创立年份 | **2009年**(2016年公司正式成立)| +| 对外宣传口径 | 统一说"17年老品牌",2026年起对外口径 | +| 真实门店数 | 40家 → 目标100家(今年新增60家) | +| 对外宣传门店 | "百余家" / "100+" | +| 累计签约加盟商 | 300+(对外说"帮助300+创业者成功开店") | +| 直营店 | 3家(三院总店、百大旗舰店、城乡路店) | +| 总部所在 | 齐齐哈尔(黑龙江) | +| 主要市场 | 东三省为主,全国招商(河北、天津等华北地区) | +| 招商热线 | **400-678-0452** | +| 微信公众号 | 清水绿川椒 | +| 官方抖音/小红书 | 绿川椒(账号名) | +| Logo元素 | 熊猫+辣椒(绿川椒品牌视觉) | + +--- + +## 二、品牌slogan + +- **主slogan(现行版)**:「清水无油煮,老火麻酱香」 +- **主slogan(前版,已废弃)**:「好料原产地 · 川椒麻香溢」 +- **副slogan**:「回归食物本味的美好」 +- **品牌愿景**:传播绿色饮食文化,打造健康快餐连锁 + +--- + +## 三、核心差异化(5大卖点) + +### 1. 清水烫煮 +- **表达**:不用骨汤,无任何添加剂,就是清水 +- **技术**:水源采用反渗透技术保证健康 +- **画面支撑**:后厨真实拍摄,汤里只有水和食材 +- **顾客反应**:当场问"汤底是不是熬了好几个小时"——就是清水,但顾客信 + +### 2. 后调味 +- **表达**:烫熟了之后在碗里调味,用家里常见的调味料 +- **差异化**:还原食物本身的味道,吃着干净放心 + +### 3. 手工老火慢熬麻酱 +- **表达**:一锅麻酱要三个小时,小火慢搅,不能停 +- **对比**:外面买的机器麻酱,跟这完全不是一个味 +- **作用**:口味护城河,顾客吃一口就知道"外头没有" + +### 4. 17年老品牌 +- **表达**:2009年齐齐哈尔起步,靠一碗清水麻辣烫做到现在 +- **信任支撑**:17年时间验证老百姓认的就是干净和放心 +- **门店验证**:40家,每家都是招牌 + +### 5. 现场制作 +- **表达**:不是料理包,不是预制菜,顾客看着做 +- **信任感**:顾客进店就知道这是真材实料 + +--- + +## 四、产品线 + +| 产品 | 说明 | +|------|------| +| 传统麻辣烫 | 核心主打,清水烫煮 | +| 黏糊麻辣烫 | 2024年新品,大茶缸黏糊麻辣烫,改良配方 | +| 麻辣香锅 | 独立产品线 | + +--- + +## 五、单店投资模型(PPT数据) + +### 4种店型 + +| 店型 | 面积 | 投入成本 | 日均营收 | 月营收 | 毛利率 | 净利率 | 月净利润 | 年净收益 | +|------|------|---------|---------|--------|--------|--------|---------|---------| +| 微店 | 50-60平 | 15.4万 | 2000+ | 6万+ | 50% | 30% | 15000+ | **18万** | +| 商场店 | 70平 | 16.1万 | 3000+ | 9万+ | 50% | 30% | 27000+ | **32.4万** | +| 中型店 | 70-80平 | 21万 | 3000+ | 9万+ | 50% | 30% | 27000+ | **32.4万** | +| 旗舰店 | 90-100平 | 36万 | 5000+ | 15万+ | 50% | 30% | 45000+ | **54万** | + +### 投入明细 + +| 项目 | 微店 | 商场店 | 中型店 | 旗舰店 | +|------|------|--------|--------|--------| +| 店面租金 | 7万 | 5万 | 10万 | 17万 | +| 装修费用 | 4万 | 5.6万 | 6万 | 11万 | +| 设备设施 | 2.6万 | 3万 | 3万 | 5万 | +| 前期进料 | 0.8万 | 1万 | 1万 | 1.5万 | +| 运作资金 | 1万 | 1.5万 | 1万 | 1.5万 | + +**注意**:毛利率50-60%,净利率30%(PPT数据) + +--- + +## 六、核心数据(品牌规划书原文) + +### 投资与收费 +| 项目 | 金额 | +|------|------| +| 地级市加盟费 | 29800元/家(一次性) | +| 县级市加盟费 | 16800元/家(一次性) | +| 地级市年度管理费 | 5000元/家/年 | +| 县级市年度管理费 | 3000元/家/年 | +| 保证金 | 合同期满无违规可退还 | +| 单店投入 | 轻资产,无大型设备,无专业大厨 | + +### 公司营收(今年全年目标) +| 来源 | 金额 | +|------|------| +| 新增60家加盟费 | 147.6万 | +| 管理费(100家) | 29.55万 | +| 团购外卖抽点 | 69.3万 | +| 供应链利润 | 198.576万 | +| **总营收** | **444.026万** | +| 固定团队成本 | 57.6万(9人团队) | +| **预计净利润** | **386.426万** | + +### 扩张节奏 +- 今年目标:40家 → 100家(新增60家) +- 速度:每月新增6家(3月开始) +- 100家以前:固定9人团队(含2名主播) + +--- + +## 七、对外宣传口径(必须遵守) + +### 门店数量 +- ✅ 说"百余家"或"100+" +- ❌ 不说真实40家 + +### 加盟商数量 +- ✅ 说"帮助300+创业者成功开店" +- ❌ 不说具体签约数字 + +### 效果承诺 +- ❌ 稳赚不赔、保本、快速回本、月入XX万 +- ✅ 只说产品特点和运营支持,不承诺结果 + +### 绝对化词汇 +- ❌ 最佳、第一,国家级(需证明) +- ✅ 出色、优质、领先(有事实依据才用) + +### 风险提示(每条视频必须加) +``` +【风险提示】投资有风险,加盟需谨慎。 +``` + +--- + +## 八、全流程扶持体系 + +### 前期扶持(筹备阶段) +- **选址**:总部大数据分析辅助选址,实地考察、风险评估 +- **装修**:提供标准化装修设计方案,本地施工团队装修 +- **设备**:统一采购配送(清水烫煮炉、冷藏柜、收银系统等) +- **证件**:指导办理营业执照、食品经营许可证 + +### 中期扶持(开业与运营) +- **培训**:**7天**全流程技术、运营、管理培训 +- **开业**:总部运营督导上门协助,制定开业活动方案 +- **物料**:核心物料统一配送(麻酱、综合料、辣椒麻椒等) +- **运营督导**:定期巡查,指导规范运营 +- **营销**:总部统一年度/季度营销方案,团购直播间带货,外卖专业团队托管 + +### 后期扶持(长期盈利) +- **产品更新**:定期研发新菜品、新口味,免费技术升级培训 +- **品牌升级**:持续品牌宣传,提升知名度 +- **退出机制**:特殊情况提供合理退出方案 + +--- + +## 九、8大优势(PPT版) + +1. **毛利率高** — 毛利率高达60%,客单高,复购率高,回本快 +2. **清水烫煮** — 无底料、高汤,告别添加剂和千滚水 +3. **产地原材料** — 麻椒、辣椒四川原产地进货 +4. **完善产业链** — 配套工厂、调料店,标准化调配 +5. **全方位服务** — 前期建店到后期运营全程辅助 +6. **产品升级** — 不断研发新品,与时俱进 +7. **专业团队** — 研发部、设计部、市场部、招商部 +8. **老品牌** — 17年品牌积累 + +--- + +## 十、标准化体系 + +### 产品标准化 +- 食材采购标准统一,核心食材总部统一配送 +- 清水烫煮时间、温度精确控制 +- 麻酱调配比例标准化 +- 禁止添加任何添加剂 +- 菜单结构统一(核心爆款+辅助菜品+季节限定) + +### 运营标准化 +- 《门店运营手册》明确卫生、设备、物料、人员管理标准 +- 成本控制方案(参考三院总店经验) +- 3分钟出餐流程优化 + +### 服务标准化 +- 全流程服务规范(东北口语化礼貌用语) +- 客诉处理:10分钟响应,24小时解决 + +### 管理标准化 +- 加盟商档案与考核体系 +- 收银系统、会员系统数据管理 + +--- + +## 十一、加盟流程(8步) + +1. 电话咨询 初步了解 +2. 当面洽谈 签约缴费 +3. 线上选址 综合评估 +4. 设计施工 装修验收 +5. 总部学习 通过考核 +6. 设备食材 进场调试 +7. 开业活动 正式营业 + +--- + +## 十二、已验证有效的7个招商脚本角度 + +### 脚本1:17年老品牌背书 +**核心钩子**:2026年有人说麻辣烫风口过了——还没开始呢 +**数据**:百余家门店,17年验证 +**转化钩子**:评论区留言,发全套资料 + +### 脚本2:清水烫差异化 +**核心钩子**:全国90%用骨汤,我们偏偏不用 +**画面**:后厨清水锅底,顾客当场问 +**转化钩子**:评论区留言,详细说说 + +### 脚本3:无添加健康牌 +**核心钩子**:现在的顾客一口就能喝出来你汤底有没有问题 +**对比冲击**:普通底料表化学名词 vs 绿川椒干干净净 +**转化钩子**:评论区留言,发全套资料 + +### 脚本4:手工麻酱东北味 +**核心钩子**:一锅麻酱三个小时,顾客吃一口就知道——外头没有 +**差异化**:不是营销,是真东西 +**转化钩子**:评论区打"麻酱" + +### 脚本5:加盟商陪跑体系 +**核心钩子**:开业之后发现没人教你——那才叫难 +**服务**:选址/装修/培训/运营/督导,全包 +**转化钩子**:评论区留言,亲自回复 + +### 脚本6:回本周期与ROI +**核心钩子**:加盟商最关心——多久回本 +**数据**:三个月回本/半年回本案例 +**合规**:不说具体数字,说"选对品牌选对位置" +**转化钩子**:评论区打"回本",帮你分析 + +### 脚本7:为什么现在入局 +**核心钩子**:有人说赛道太卷了——那是没用对方法的人卷 +**差异化总结**:清水烫+手工麻酱+17年老店 +**转化钩子**:评论区,发资料 + +--- + +## 十三、还没覆盖的新招商角度 + +1. **选址支持** — 大数据选址如何帮加盟商 +2. **供应链/食材配送** — 后台能力展示(工厂+调料店) +3. **外卖平台运营** — 美团/饿了么/京东怎么玩 +4. **食品安全管控** — 反渗透技术/食材溯源 +5. **区域保护政策** — 加盟后保护范围 +6. **小白也能干** — 7天培训让零基础上手 +7. **黏糊麻辣烫新品** — 2024年新品差异化 +8. **成功加盟商案例** — 真实故事,达人推荐 +9. **品牌荣誉/资质** — 17年积累了什么认可 +10. **什么人适合加盟** — 打工族/创业者/退休人员 + +--- + +## 十四、品牌发展历程 + +| 年份 | 事件 | +|------|------| +| 2009 | 首店开业(三院总店,50多平,日营业额4000+) | +| 2013 | 商标注册,VI系统成立 | +| 2016 | 绿川椒餐饮管理有限公司正式成立 | +| 2017 | 干调店+现代化食品加工厂成立,原材料统一配送 | +| 2018 | 百大旗舰店开业(270平,齐齐哈尔最大旗舰店) | +| 2022 | 城乡路店开业,装修升级2.0版本 | +| 2024 | 大茶缸黏糊麻辣烫全面上线 | + +--- + +## 十五、文件存档 + +- **品牌规划书原文**(完整版):`skills/investor-brand-kit/品牌规划书_完整版.docx` +- **PPT截图资料包**(29页):`skills/investor-brand-kit/品牌PPT截图_图文版.pdf` +- **7个脚本原档**:`D:\狗蛋草稿箱\绿川椒招商脚本_7个卖点_v6.xlsx` +- **违规词规则**:`memory/topics/douyin-banned-words.md` +- **本资料库**:`skills/investor-brand-kit/SKILL.md` + +**写脚本顺序:先读本文件 → 再读违规词规则 → 再动手。** diff --git a/plugins/antianqi/skill-bridge/examples/input/self-improving-agent/SKILL.md b/plugins/antianqi/skill-bridge/examples/input/self-improving-agent/SKILL.md new file mode 100644 index 0000000..097145f --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/input/self-improving-agent/SKILL.md @@ -0,0 +1,651 @@ +--- +name: self-improvement +description: "Captures learnings, errors, and corrections to enable continuous improvement. + Use when: (1) A command or operation fails unexpectedly, (2) User corrects Claude + ('No, that's wrong...', 'Actually...'), (3) User requests a capability that doesn't + exist, (4) An external API or tool fails, (5) Claude realizes its knowledge is outdated + or incorrect, (6) A better approach is discovered for a recurring task. Also review + learnings before major tasks." +--- + +# Self-Improvement Skill + +Log learnings and errors to markdown files for continuous improvement. Coding agents can later process these into fixes, and important learnings get promoted to project memory. + +## Quick Reference + +| Situation | Action | +|-----------|--------| +| Command/operation fails | Log to `.learnings/ERRORS.md` | +| User corrects you | Log to `.learnings/LEARNINGS.md` with category `correction` | +| User wants missing feature | Log to `.learnings/FEATURE_REQUESTS.md` | +| API/external tool fails | Log to `.learnings/ERRORS.md` with integration details | +| Knowledge was outdated | Log to `.learnings/LEARNINGS.md` with category `knowledge_gap` | +| Found better approach | Log to `.learnings/LEARNINGS.md` with category `best_practice` | +| Simplify/Harden recurring patterns | Log/update `.learnings/LEARNINGS.md` with `Source: simplify-and-harden` and a stable `Pattern-Key` | +| Similar to existing entry | Link with `**See Also**`, consider priority bump | +| Broadly applicable learning | Promote to `CLAUDE.md`, `AGENTS.md`, and/or `.github/copilot-instructions.md` | +| Workflow improvements | Promote to `AGENTS.md` (OpenClaw workspace) | +| Tool gotchas | Promote to `TOOLS.md` (OpenClaw workspace) | +| Behavioral patterns | Promote to `SOUL.md` (OpenClaw workspace) | + +## OpenClaw Setup (Recommended) + +OpenClaw is the primary platform for this skill. It uses workspace-based prompt injection with automatic skill loading. + +### Installation + +**Via ClawdHub (recommended):** +```bash +clawdhub install self-improving-agent +``` + +**Manual:** +```bash +git clone https://github.com/peterskoett/self-improving-agent.git C:\Users\Administrator\.openclaw/skills/self-improving-agent +``` + +Remade for openclaw from original repo : https://github.com/pskoett/pskoett-ai-skills - https://github.com/pskoett/pskoett-ai-skills/tree/main/skills/self-improvement + +### Workspace Structure + +OpenClaw injects these files into every session: + +``` +C:\Users\Administrator\.openclaw/workspace/ +├── AGENTS.md # Multi-agent workflows, delegation patterns +├── SOUL.md # Behavioral guidelines, personality, principles +├── TOOLS.md # Tool capabilities, integration gotchas +├── MEMORY.md # Long-term memory (main session only) +├── memory/ # Daily memory files +│ └── YYYY-MM-DD.md +└── .learnings/ # This skill's log files + ├── LEARNINGS.md + ├── ERRORS.md + └── FEATURE_REQUESTS.md +``` + +### Create Learning Files + +```bash +mkdir -p C:\Users\Administrator\.openclaw/workspace/.learnings +``` + +Then create the log files (or copy from `assets/`): +- `LEARNINGS.md` — corrections, knowledge gaps, best practices +- `ERRORS.md` — command failures, exceptions +- `FEATURE_REQUESTS.md` — user-requested capabilities + +### Promotion Targets + +When learnings prove broadly applicable, promote them to workspace files: + +| Learning Type | Promote To | Example | +|---------------|------------|---------| +| Behavioral patterns | `SOUL.md` | "Be concise, avoid disclaimers" | +| Workflow improvements | `AGENTS.md` | "Spawn sub-agents for long tasks" | +| Tool gotchas | `TOOLS.md` | "Git push needs auth configured first" | + +### Inter-Session Communication + +OpenClaw provides tools to share learnings across sessions: + +- **sessions_list** — View active/recent sessions +- **sessions_history** — Read another session's transcript +- **sessions_send** — Send a learning to another session +- **sessions_spawn** — Spawn a sub-agent for background work + +### Optional: Enable Hook + +For automatic reminders at session start: + +```bash +# Copy hook to OpenClaw hooks directory +cp -r hooks/openclaw C:\Users\Administrator\.openclaw/hooks/self-improvement + +# Enable it +openclaw hooks enable self-improvement +``` + +See `references/openclaw-integration.md` for complete details. + +--- + +## Generic Setup (Other Agents) + +For Claude Code, Codex, Copilot, or other agents, create `.learnings/` in your project: + +```bash +mkdir -p .learnings +``` + +Copy templates from `assets/` or create files with headers. + +### Add reference to agent files AGENTS.md, CLAUDE.md, or .github/copilot-instructions.md to remind yourself to log learnings. (this is an alternative to hook-based reminders) + +#### Self-Improvement Workflow + +When errors or corrections occur: +1. Log to `.learnings/ERRORS.md`, `LEARNINGS.md`, or `FEATURE_REQUESTS.md` +2. Review and promote broadly applicable learnings to: + - `CLAUDE.md` - project facts and conventions + - `AGENTS.md` - workflows and automation + - `.github/copilot-instructions.md` - Copilot context + +## Logging Format + +### Learning Entry + +Append to `.learnings/LEARNINGS.md`: + +```markdown +## [LRN-YYYYMMDD-XXX] category + +**Logged**: ISO-8601 timestamp +**Priority**: low | medium | high | critical +**Status**: pending +**Area**: frontend | backend | infra | tests | docs | config + +### Summary +One-line description of what was learned + +### Details +Full context: what happened, what was wrong, what's correct + +### Suggested Action +Specific fix or improvement to make + +### Metadata +- Source: conversation | error | user_feedback +- Related Files: path/to/file.ext +- Tags: tag1, tag2 +- See Also: LRN-20250110-001 (if related to existing entry) +- Pattern-Key: simplify.dead_code | harden.input_validation (optional, for recurring-pattern tracking) +- Recurrence-Count: 1 (optional) +- First-Seen: 2025-01-15 (optional) +- Last-Seen: 2025-01-15 (optional) + +--- +``` + +### Error Entry + +Append to `.learnings/ERRORS.md`: + +```markdown +## [ERR-YYYYMMDD-XXX] skill_or_command_name + +**Logged**: ISO-8601 timestamp +**Priority**: high +**Status**: pending +**Area**: frontend | backend | infra | tests | docs | config + +### Summary +Brief description of what failed + +### Error +``` +Actual error message or output +``` + +### Context +- Command/operation attempted +- Input or parameters used +- Environment details if relevant + +### Suggested Fix +If identifiable, what might resolve this + +### Metadata +- Reproducible: yes | no | unknown +- Related Files: path/to/file.ext +- See Also: ERR-20250110-001 (if recurring) + +--- +``` + +### Feature Request Entry + +Append to `.learnings/FEATURE_REQUESTS.md`: + +```markdown +## [FEAT-YYYYMMDD-XXX] capability_name + +**Logged**: ISO-8601 timestamp +**Priority**: medium +**Status**: pending +**Area**: frontend | backend | infra | tests | docs | config + +### Requested Capability +What the user wanted to do + +### User Context +Why they needed it, what problem they're solving + +### Complexity Estimate +simple | medium | complex + +### Suggested Implementation +How this could be built, what it might extend + +### Metadata +- Frequency: first_time | recurring +- Related Features: existing_feature_name + +--- +``` + +## ID Generation + +Format: `TYPE-YYYYMMDD-XXX` +- TYPE: `LRN` (learning), `ERR` (error), `FEAT` (feature) +- YYYYMMDD: Current date +- XXX: Sequential number or random 3 chars (e.g., `001`, `A7B`) + +Examples: `LRN-20250115-001`, `ERR-20250115-A3F`, `FEAT-20250115-002` + +## Resolving Entries + +When an issue is fixed, update the entry: + +1. Change `**Status**: pending` → `**Status**: resolved` +2. Add resolution block after Metadata: + +```markdown +### Resolution +- **Resolved**: 2025-01-16T09:00:00Z +- **Commit/PR**: abc123 or #42 +- **Notes**: Brief description of what was done +``` + +Other status values: +- `in_progress` - Actively being worked on +- `wont_fix` - Decided not to address (add reason in Resolution notes) +- `promoted` - Elevated to CLAUDE.md, AGENTS.md, or .github/copilot-instructions.md + +## Promoting to Project Memory + +When a learning is broadly applicable (not a one-off fix), promote it to permanent project memory. + +### When to Promote + +- Learning applies across multiple files/features +- Knowledge any contributor (human or AI) should know +- Prevents recurring mistakes +- Documents project-specific conventions + +### Promotion Targets + +| Target | What Belongs There | +|--------|-------------------| +| `CLAUDE.md` | Project facts, conventions, gotchas for all Claude interactions | +| `AGENTS.md` | Agent-specific workflows, tool usage patterns, automation rules | +| `.github/copilot-instructions.md` | Project context and conventions for GitHub Copilot | +| `SOUL.md` | Behavioral guidelines, communication style, principles (OpenClaw workspace) | +| `TOOLS.md` | Tool capabilities, usage patterns, integration gotchas (OpenClaw workspace) | + +### How to Promote + +1. **Distill** the learning into a concise rule or fact +2. **Add** to appropriate section in target file (create file if needed) +3. **Update** original entry: + - Change `**Status**: pending` → `**Status**: promoted` + - Add `**Promoted**: CLAUDE.md`, `AGENTS.md`, or `.github/copilot-instructions.md` + +### Promotion Examples + +**Learning** (verbose): +> Project uses pnpm workspaces. Attempted `npm install` but failed. +> Lock file is `pnpm-lock.yaml`. Must use `pnpm install`. + +**In CLAUDE.md** (concise): +```markdown +## Build & Dependencies +- Package manager: pnpm (not npm) - use `pnpm install` +``` + +**Learning** (verbose): +> When modifying API endpoints, must regenerate TypeScript client. +> Forgetting this causes type mismatches at runtime. + +**In AGENTS.md** (actionable): +```markdown +## After API Changes +1. Regenerate client: `pnpm run generate:api` +2. Check for type errors: `pnpm tsc --noEmit` +``` + +## Recurring Pattern Detection + +If logging something similar to an existing entry: + +1. **Search first**: `grep -r "keyword" .learnings/` +2. **Link entries**: Add `**See Also**: ERR-20250110-001` in Metadata +3. **Bump priority** if issue keeps recurring +4. **Consider systemic fix**: Recurring issues often indicate: + - Missing documentation (→ promote to CLAUDE.md or .github/copilot-instructions.md) + - Missing automation (→ add to AGENTS.md) + - Architectural problem (→ create tech debt ticket) + +## Simplify & Harden Feed + +Use this workflow to ingest recurring patterns from the `simplify-and-harden` +skill and turn them into durable prompt guidance. + +### Ingestion Workflow + +1. Read `simplify_and_harden.learning_loop.candidates` from the task summary. +2. For each candidate, use `pattern_key` as the stable dedupe key. +3. Search `.learnings/LEARNINGS.md` for an existing entry with that key: + - `grep -n "Pattern-Key: " .learnings/LEARNINGS.md` +4. If found: + - Increment `Recurrence-Count` + - Update `Last-Seen` + - Add `See Also` links to related entries/tasks +5. If not found: + - Create a new `LRN-...` entry + - Set `Source: simplify-and-harden` + - Set `Pattern-Key`, `Recurrence-Count: 1`, and `First-Seen`/`Last-Seen` + +### Promotion Rule (System Prompt Feedback) + +Promote recurring patterns into agent context/system prompt files when all are true: + +- `Recurrence-Count >= 3` +- Seen across at least 2 distinct tasks +- Occurred within a 30-day window + +Promotion targets: +- `CLAUDE.md` +- `AGENTS.md` +- `.github/copilot-instructions.md` +- `SOUL.md` / `TOOLS.md` for OpenClaw workspace-level guidance when applicable + +Write promoted rules as short prevention rules (what to do before/while coding), +not long incident write-ups. + +## Periodic Review + +Review `.learnings/` at natural breakpoints: + +### When to Review +- Before starting a new major task +- After completing a feature +- When working in an area with past learnings +- Weekly during active development + +### Quick Status Check +```bash +# Count pending items +grep -h "Status\*\*: pending" .learnings/*.md | wc -l + +# List pending high-priority items +grep -B5 "Priority\*\*: high" .learnings/*.md | grep "^## \[" + +# Find learnings for a specific area +grep -l "Area\*\*: backend" .learnings/*.md +``` + +### Review Actions +- Resolve fixed items +- Promote applicable learnings +- Link related entries +- Escalate recurring issues + +## Detection Triggers + +Automatically log when you notice: + +**Corrections** (→ learning with `correction` category): +- "No, that's not right..." +- "Actually, it should be..." +- "You're wrong about..." +- "That's outdated..." + +**Feature Requests** (→ feature request): +- "Can you also..." +- "I wish you could..." +- "Is there a way to..." +- "Why can't you..." + +**Knowledge Gaps** (→ learning with `knowledge_gap` category): +- User provides information you didn't know +- Documentation you referenced is outdated +- API behavior differs from your understanding + +**Errors** (→ error entry): +- Command returns non-zero exit code +- Exception or stack trace +- Unexpected output or behavior +- Timeout or connection failure + +## Priority Guidelines + +| Priority | When to Use | +|----------|-------------| +| `critical` | Blocks core functionality, data loss risk, security issue | +| `high` | Significant impact, affects common workflows, recurring issue | +| `medium` | Moderate impact, workaround exists | +| `low` | Minor inconvenience, edge case, nice-to-have | + +## Area Tags + +Use to filter learnings by codebase region: + +| Area | Scope | +|------|-------| +| `frontend` | UI, components, client-side code | +| `backend` | API, services, server-side code | +| `infra` | CI/CD, deployment, Docker, cloud | +| `tests` | Test files, testing utilities, coverage | +| `docs` | Documentation, comments, READMEs | +| `config` | Configuration files, environment, settings | + +## Best Practices + +1. **Log immediately** - context is freshest right after the issue +2. **Be specific** - future agents need to understand quickly +3. **Include reproduction steps** - especially for errors +4. **Link related files** - makes fixes easier +5. **Suggest concrete fixes** - not just "investigate" +6. **Use consistent categories** - enables filtering +7. **Promote aggressively** - if in doubt, add to CLAUDE.md or .github/copilot-instructions.md +8. **Review regularly** - stale learnings lose value + +## Gitignore Options + +**Keep learnings local** (per-developer): +```gitignore +.learnings/ +``` + +**Track learnings in repo** (team-wide): +Don't add to .gitignore - learnings become shared knowledge. + +**Hybrid** (track templates, ignore entries): +```gitignore +.learnings/*.md +!.learnings/.gitkeep +``` + +## Hook Integration + +Enable automatic reminders through agent hooks. This is **opt-in** - you must explicitly configure hooks. + +### Quick Setup (Claude Code / Codex) + +Create `.claude/settings.json` in your project: + +```json +{ + "hooks": { + "UserPromptSubmit": [{ + "matcher": "", + "hooks": [{ + "type": "command", + "command": "./skills/self-improvement/scripts/activator.sh" + }] + }] + } +} +``` + +This injects a learning evaluation reminder after each prompt (~50-100 tokens overhead). + +### Full Setup (With Error Detection) + +```json +{ + "hooks": { + "UserPromptSubmit": [{ + "matcher": "", + "hooks": [{ + "type": "command", + "command": "./skills/self-improvement/scripts/activator.sh" + }] + }], + "PostToolUse": [{ + "matcher": "Bash", + "hooks": [{ + "type": "command", + "command": "./skills/self-improvement/scripts/error-detector.sh" + }] + }] + } +} +``` + +### Available Hook Scripts + +| Script | Hook Type | Purpose | +|--------|-----------|---------| +| `scripts/activator.sh` | UserPromptSubmit | Reminds to evaluate learnings after tasks | +| `scripts/error-detector.sh` | PostToolUse (Bash) | Triggers on command errors | + +See `references/hooks-setup.md` for detailed configuration and troubleshooting. + +## Automatic Skill Extraction + +When a learning is valuable enough to become a reusable skill, extract it using the provided helper. + +### Skill Extraction Criteria + +A learning qualifies for skill extraction when ANY of these apply: + +| Criterion | Description | +|-----------|-------------| +| **Recurring** | Has `See Also` links to 2+ similar issues | +| **Verified** | Status is `resolved` with working fix | +| **Non-obvious** | Required actual debugging/investigation to discover | +| **Broadly applicable** | Not project-specific; useful across codebases | +| **User-flagged** | User says "save this as a skill" or similar | + +### Extraction Workflow + +1. **Identify candidate**: Learning meets extraction criteria +2. **Run helper** (or create manually): + ```bash + ./skills/self-improvement/scripts/extract-skill.sh skill-name --dry-run + ./skills/self-improvement/scripts/extract-skill.sh skill-name + ``` +3. **Customize SKILL.md**: Fill in template with learning content +4. **Update learning**: Set status to `promoted_to_skill`, add `Skill-Path` +5. **Verify**: Read skill in fresh session to ensure it's self-contained + +### Manual Extraction + +If you prefer manual creation: + +1. Create `skills//SKILL.md` +2. Use template from `assets/SKILL-TEMPLATE.md` +3. Follow [Agent Skills spec](https://agentskills.io/specification): + - YAML frontmatter with `name` and `description` + - Name must match folder name + - No README.md inside skill folder + +### Extraction Detection Triggers + +Watch for these signals that a learning should become a skill: + +**In conversation:** +- "Save this as a skill" +- "I keep running into this" +- "This would be useful for other projects" +- "Remember this pattern" + +**In learning entries:** +- Multiple `See Also` links (recurring issue) +- High priority + resolved status +- Category: `best_practice` with broad applicability +- User feedback praising the solution + +### Skill Quality Gates + +Before extraction, verify: + +- [ ] Solution is tested and working +- [ ] Description is clear without original context +- [ ] Code examples are self-contained +- [ ] No project-specific hardcoded values +- [ ] Follows skill naming conventions (lowercase, hyphens) + +## Multi-Agent Support + +This skill works across different AI coding agents with agent-specific activation. + +### Claude Code + +**Activation**: Hooks (UserPromptSubmit, PostToolUse) +**Setup**: `.claude/settings.json` with hook configuration +**Detection**: Automatic via hook scripts + +### Codex CLI + +**Activation**: Hooks (same pattern as Claude Code) +**Setup**: `.codex/settings.json` with hook configuration +**Detection**: Automatic via hook scripts + +### GitHub Copilot + +**Activation**: Manual (no hook support) +**Setup**: Add to `.github/copilot-instructions.md`: + +```markdown +## Self-Improvement + +After solving non-obvious issues, consider logging to `.learnings/`: +1. Use format from self-improvement skill +2. Link related entries with See Also +3. Promote high-value learnings to skills + +Ask in chat: "Should I log this as a learning?" +``` + +**Detection**: Manual review at session end + +### OpenClaw + +**Activation**: Workspace injection + inter-agent messaging +**Setup**: See "OpenClaw Setup" section above +**Detection**: Via session tools and workspace files + +### Agent-Agnostic Guidance + +Regardless of agent, apply self-improvement when you: + +1. **Discover something non-obvious** - solution wasn't immediate +2. **Correct yourself** - initial approach was wrong +3. **Learn project conventions** - discovered undocumented patterns +4. **Hit unexpected errors** - especially if diagnosis was difficult +5. **Find better approaches** - improved on your original solution + +### Copilot Chat Integration + +For Copilot users, add this to your prompts when relevant: + +> After completing this task, evaluate if any learnings should be logged to `.learnings/` using the self-improvement skill format. + +Or use quick prompts: +- "Log this to learnings" +- "Create a skill from this solution" +- "Check .learnings/ for related issues" diff --git a/plugins/antianqi/skill-bridge/examples/input/task-tracker/SKILL.md b/plugins/antianqi/skill-bridge/examples/input/task-tracker/SKILL.md new file mode 100644 index 0000000..5a23439 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/input/task-tracker/SKILL.md @@ -0,0 +1,89 @@ +--- +name: task-tracker +description: 任务追踪与日报周报生成。用于记录老板工作进度、生成日报周报、持续追踪任务完成情况。 +--- + +# Task Tracker - 任务追踪与日报周报 + +## 核心文件 +- 任务总表:`C:\Users\Administrator\.openclaw/workspace/TASKS.md` + +## 任务格式规范 + +### 日报格式(必须遵守) +- 内容顺序:**①直播 ②短视频 ③外卖 ④其他** +- 不显示大分类标题,直接按顺序列序号 +- **不用任何符号**(✅❌🔄等都不用) +- 发到飞书,用文字不用语音 +- **输出时:完整输出 TASKS.md 里记录的详细内容和进度,不简化** + +### 明日计划原则 +- **持续跟进的项必须列入**(如:城乡路京东外卖持续跟进) +- **今日新提到的跟进项也列入**(如:美团收银报价跟进) +- 不在本周计划里但老板提到的新任务 → 追加进明日计划 + +### 重要区分 +- **日报只记老板的工作**(品牌运营 + 线上运营 + 外卖 + 品牌营销) +- **数据统计填表是狗蛋的工作,不记入日报** +- **系统升级、工具配置等狗蛋研发工作不记入日报** +- **狗蛋自己的研发/学习/技能提升工作不记入日报**,只记入 memory/daily/YYYY-MM-DD.md +- 老板告诉我进展 → 更新 TASKS.md(详细记录) +- 我自己的研发进展 → 更新 memory/daily/YYYY-MM-DD.md + +### 重要区分 +- **日报只记老板的工作**(品牌运营 + 线上运营 + 外卖 + 品牌营销) +- **数据统计填表是狗蛋的工作,不记入日报** +- **狗蛋自己的研发/学习/技能提升工作不记入日报**,只记入 memory/daily/YYYY-MM-DD.md +- 老板告诉我进展 → 更新 TASKS.md(详细记录) +- 我自己的研发进展 → 更新 memory/daily/YYYY-MM-DD.md + +``` +老板日报(YYYY-MM-DD) +今日工作: +1. ... +2. ... +明日计划: +1. ... +2. ... +``` + +### 周报格式 +同日报格式,周六汇总一周数据+工作内容 + +### 任务格式 +``` +### 今日进展(YYYY-MM-DD) +- 具体工作内容 + +### 明日计划 +- 延续任务(带进度说明) +- 新增任务 +``` + +### 任务状态规则 +- 今日未完成的 → 记录到明日计划 +- 本周未完成的 → 记录到下周计划 +- 狗蛋自己的研发/学习工作 → 不记录 + +## 使用场景 + +### 记录进展 +老板告诉你工作进展 → 更新 TASKS.md + +### 查询进度 +老板问"现在任务进度" → 读取 TASKS.md 输出当前任务清单 + +### 生成日报 +老板说"写日报" → 从 TASKS.md 当前日进展生成格式化日报,发到飞书 + +### 生成周报 +老板说"写周报" → 从 TASKS.md 本周任务+进展生成,发到飞书 + +### 任务完成 +老板说某任务完成了 → 更新 TASKS.md 中该任务状态为"已完成",标注日期 + +### 新增任务 +老板布置新任务 → 追加到 TASKS.md 当前周任务列表 + +## 追踪文件路径 +`C:\Users\Administrator\.openclaw/workspace/TASKS.md` diff --git a/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/SKILL.md b/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/SKILL.md new file mode 100644 index 0000000..9bd4d29 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/SKILL.md @@ -0,0 +1,326 @@ +--- +name: investor-brand-kit +description: 'Use when: 整合品牌信息、差异化卖点、招商角度、合规规则的完整知识库。写脚本前必读。.' +descriptions: + zh-Hans: '> 写脚本前必读。整合了品牌规划书原文 + PPT截图 + 7个脚本 + TASKS.md 实战积累。' +displayNames: + zh-Hans: 绿川椒品牌招商核心资料库 +metadata: + openclaw_compat: true + skill-bridge: + classify_tier: pure + classify_subtier: pure-translate + classify_reason: pure instruction, ascii-clean, no hardcoded paths +--- + +# 绿川椒品牌招商核心资料库 + +> 写脚本前必读。整合了品牌规划书原文 + PPT截图 + 7个脚本 + TASKS.md 实战积累。 + +--- + +## 一、品牌基础信息 + +| 项目 | 内容 | +|------|------| +| 品牌全称 | 绿川椒清水麻辣烫(曾用名:清水绿川椒麻辣烫) | +| 公司全称 | 齐齐哈尔清水绿川椒餐饮管理有限公司 | +| 创立年份 | **2009年**(2016年公司正式成立)| +| 对外宣传口径 | 统一说"17年老品牌",2026年起对外口径 | +| 真实门店数 | 40家 → 目标100家(今年新增60家) | +| 对外宣传门店 | "百余家" / "100+" | +| 累计签约加盟商 | 300+(对外说"帮助300+创业者成功开店") | +| 直营店 | 3家(三院总店、百大旗舰店、城乡路店) | +| 总部所在 | 齐齐哈尔(黑龙江) | +| 主要市场 | 东三省为主,全国招商(河北、天津等华北地区) | +| 招商热线 | **400-678-0452** | +| 微信公众号 | 清水绿川椒 | +| 官方抖音/小红书 | 绿川椒(账号名) | +| Logo元素 | 熊猫+辣椒(绿川椒品牌视觉) | + +--- + +## 二、品牌slogan + +- **主slogan(现行版)**:「清水无油煮,老火麻酱香」 +- **主slogan(前版,已废弃)**:「好料原产地 · 川椒麻香溢」 +- **副slogan**:「回归食物本味的美好」 +- **品牌愿景**:传播绿色饮食文化,打造健康快餐连锁 + +--- + +## 三、核心差异化(5大卖点) + +### 1. 清水烫煮 +- **表达**:不用骨汤,无任何添加剂,就是清水 +- **技术**:水源采用反渗透技术保证健康 +- **画面支撑**:后厨真实拍摄,汤里只有水和食材 +- **顾客反应**:当场问"汤底是不是熬了好几个小时"——就是清水,但顾客信 + +### 2. 后调味 +- **表达**:烫熟了之后在碗里调味,用家里常见的调味料 +- **差异化**:还原食物本身的味道,吃着干净放心 + +### 3. 手工老火慢熬麻酱 +- **表达**:一锅麻酱要三个小时,小火慢搅,不能停 +- **对比**:外面买的机器麻酱,跟这完全不是一个味 +- **作用**:口味护城河,顾客吃一口就知道"外头没有" + +### 4. 17年老品牌 +- **表达**:2009年齐齐哈尔起步,靠一碗清水麻辣烫做到现在 +- **信任支撑**:17年时间验证老百姓认的就是干净和放心 +- **门店验证**:40家,每家都是招牌 + +### 5. 现场制作 +- **表达**:不是料理包,不是预制菜,顾客看着做 +- **信任感**:顾客进店就知道这是真材实料 + +--- + +## 四、产品线 + +| 产品 | 说明 | +|------|------| +| 传统麻辣烫 | 核心主打,清水烫煮 | +| 黏糊麻辣烫 | 2024年新品,大茶缸黏糊麻辣烫,改良配方 | +| 麻辣香锅 | 独立产品线 | + +--- + +## 五、单店投资模型(PPT数据) + +### 4种店型 + +| 店型 | 面积 | 投入成本 | 日均营收 | 月营收 | 毛利率 | 净利率 | 月净利润 | 年净收益 | +|------|------|---------|---------|--------|--------|--------|---------|---------| +| 微店 | 50-60平 | 15.4万 | 2000+ | 6万+ | 50% | 30% | 15000+ | **18万** | +| 商场店 | 70平 | 16.1万 | 3000+ | 9万+ | 50% | 30% | 27000+ | **32.4万** | +| 中型店 | 70-80平 | 21万 | 3000+ | 9万+ | 50% | 30% | 27000+ | **32.4万** | +| 旗舰店 | 90-100平 | 36万 | 5000+ | 15万+ | 50% | 30% | 45000+ | **54万** | + +### 投入明细 + +| 项目 | 微店 | 商场店 | 中型店 | 旗舰店 | +|------|------|--------|--------|--------| +| 店面租金 | 7万 | 5万 | 10万 | 17万 | +| 装修费用 | 4万 | 5.6万 | 6万 | 11万 | +| 设备设施 | 2.6万 | 3万 | 3万 | 5万 | +| 前期进料 | 0.8万 | 1万 | 1万 | 1.5万 | +| 运作资金 | 1万 | 1.5万 | 1万 | 1.5万 | + +**注意**:毛利率50-60%,净利率30%(PPT数据) + +--- + +## 六、核心数据(品牌规划书原文) + +### 投资与收费 +| 项目 | 金额 | +|------|------| +| 地级市加盟费 | 29800元/家(一次性) | +| 县级市加盟费 | 16800元/家(一次性) | +| 地级市年度管理费 | 5000元/家/年 | +| 县级市年度管理费 | 3000元/家/年 | +| 保证金 | 合同期满无违规可退还 | +| 单店投入 | 轻资产,无大型设备,无专业大厨 | + +### 公司营收(今年全年目标) +| 来源 | 金额 | +|------|------| +| 新增60家加盟费 | 147.6万 | +| 管理费(100家) | 29.55万 | +| 团购外卖抽点 | 69.3万 | +| 供应链利润 | 198.576万 | +| **总营收** | **444.026万** | +| 固定团队成本 | 57.6万(9人团队) | +| **预计净利润** | **386.426万** | + +### 扩张节奏 +- 今年目标:40家 → 100家(新增60家) +- 速度:每月新增6家(3月开始) +- 100家以前:固定9人团队(含2名主播) + +--- + +## 七、对外宣传口径(必须遵守) + +### 门店数量 +- ✅ 说"百余家"或"100+" +- ❌ 不说真实40家 + +### 加盟商数量 +- ✅ 说"帮助300+创业者成功开店" +- ❌ 不说具体签约数字 + +### 效果承诺 +- ❌ 稳赚不赔、保本、快速回本、月入XX万 +- ✅ 只说产品特点和运营支持,不承诺结果 + +### 绝对化词汇 +- ❌ 最佳、第一,国家级(需证明) +- ✅ 出色、优质、领先(有事实依据才用) + +### 风险提示(每条视频必须加) +``` +【风险提示】投资有风险,加盟需谨慎。 +``` + +--- + +## 八、全流程扶持体系 + +### 前期扶持(筹备阶段) +- **选址**:总部大数据分析辅助选址,实地考察、风险评估 +- **装修**:提供标准化装修设计方案,本地施工团队装修 +- **设备**:统一采购配送(清水烫煮炉、冷藏柜、收银系统等) +- **证件**:指导办理营业执照、食品经营许可证 + +### 中期扶持(开业与运营) +- **培训**:**7天**全流程技术、运营、管理培训 +- **开业**:总部运营督导上门协助,制定开业活动方案 +- **物料**:核心物料统一配送(麻酱、综合料、辣椒麻椒等) +- **运营督导**:定期巡查,指导规范运营 +- **营销**:总部统一年度/季度营销方案,团购直播间带货,外卖专业团队托管 + +### 后期扶持(长期盈利) +- **产品更新**:定期研发新菜品、新口味,免费技术升级培训 +- **品牌升级**:持续品牌宣传,提升知名度 +- **退出机制**:特殊情况提供合理退出方案 + +--- + +## 九、8大优势(PPT版) + +1. **毛利率高** — 毛利率高达60%,客单高,复购率高,回本快 +2. **清水烫煮** — 无底料、高汤,告别添加剂和千滚水 +3. **产地原材料** — 麻椒、辣椒四川原产地进货 +4. **完善产业链** — 配套工厂、调料店,标准化调配 +5. **全方位服务** — 前期建店到后期运营全程辅助 +6. **产品升级** — 不断研发新品,与时俱进 +7. **专业团队** — 研发部、设计部、市场部、招商部 +8. **老品牌** — 17年品牌积累 + +--- + +## 十、标准化体系 + +### 产品标准化 +- 食材采购标准统一,核心食材总部统一配送 +- 清水烫煮时间、温度精确控制 +- 麻酱调配比例标准化 +- 禁止添加任何添加剂 +- 菜单结构统一(核心爆款+辅助菜品+季节限定) + +### 运营标准化 +- 《门店运营手册》明确卫生、设备、物料、人员管理标准 +- 成本控制方案(参考三院总店经验) +- 3分钟出餐流程优化 + +### 服务标准化 +- 全流程服务规范(东北口语化礼貌用语) +- 客诉处理:10分钟响应,24小时解决 + +### 管理标准化 +- 加盟商档案与考核体系 +- 收银系统、会员系统数据管理 + +--- + +## 十一、加盟流程(8步) + +1. 电话咨询 初步了解 +2. 当面洽谈 签约缴费 +3. 线上选址 综合评估 +4. 设计施工 装修验收 +5. 总部学习 通过考核 +6. 设备食材 进场调试 +7. 开业活动 正式营业 + +--- + +## 十二、已验证有效的7个招商脚本角度 + +### 脚本1:17年老品牌背书 +**核心钩子**:2026年有人说麻辣烫风口过了——还没开始呢 +**数据**:百余家门店,17年验证 +**转化钩子**:评论区留言,发全套资料 + +### 脚本2:清水烫差异化 +**核心钩子**:全国90%用骨汤,我们偏偏不用 +**画面**:后厨清水锅底,顾客当场问 +**转化钩子**:评论区留言,详细说说 + +### 脚本3:无添加健康牌 +**核心钩子**:现在的顾客一口就能喝出来你汤底有没有问题 +**对比冲击**:普通底料表化学名词 vs 绿川椒干干净净 +**转化钩子**:评论区留言,发全套资料 + +### 脚本4:手工麻酱东北味 +**核心钩子**:一锅麻酱三个小时,顾客吃一口就知道——外头没有 +**差异化**:不是营销,是真东西 +**转化钩子**:评论区打"麻酱" + +### 脚本5:加盟商陪跑体系 +**核心钩子**:开业之后发现没人教你——那才叫难 +**服务**:选址/装修/培训/运营/督导,全包 +**转化钩子**:评论区留言,亲自回复 + +### 脚本6:回本周期与ROI +**核心钩子**:加盟商最关心——多久回本 +**数据**:三个月回本/半年回本案例 +**合规**:不说具体数字,说"选对品牌选对位置" +**转化钩子**:评论区打"回本",帮你分析 + +### 脚本7:为什么现在入局 +**核心钩子**:有人说赛道太卷了——那是没用对方法的人卷 +**差异化总结**:清水烫+手工麻酱+17年老店 +**转化钩子**:评论区,发资料 + +--- + +## 十三、还没覆盖的新招商角度 + +1. **选址支持** — 大数据选址如何帮加盟商 +2. **供应链/食材配送** — 后台能力展示(工厂+调料店) +3. **外卖平台运营** — 美团/饿了么/京东怎么玩 +4. **食品安全管控** — 反渗透技术/食材溯源 +5. **区域保护政策** — 加盟后保护范围 +6. **小白也能干** — 7天培训让零基础上手 +7. **黏糊麻辣烫新品** — 2024年新品差异化 +8. **成功加盟商案例** — 真实故事,达人推荐 +9. **品牌荣誉/资质** — 17年积累了什么认可 +10. **什么人适合加盟** — 打工族/创业者/退休人员 + +--- + +## 十四、品牌发展历程 + +| 年份 | 事件 | +|------|------| +| 2009 | 首店开业(三院总店,50多平,日营业额4000+) | +| 2013 | 商标注册,VI系统成立 | +| 2016 | 绿川椒餐饮管理有限公司正式成立 | +| 2017 | 干调店+现代化食品加工厂成立,原材料统一配送 | +| 2018 | 百大旗舰店开业(270平,齐齐哈尔最大旗舰店) | +| 2022 | 城乡路店开业,装修升级2.0版本 | +| 2024 | 大茶缸黏糊麻辣烫全面上线 | + +--- + +## 十五、文件存档 + +- **品牌规划书原文**(完整版):`skills/investor-brand-kit/品牌规划书_完整版.docx` +- **PPT截图资料包**(29页):`skills/investor-brand-kit/品牌PPT截图_图文版.pdf` +- **7个脚本原档**:`D:\狗蛋草稿箱\绿川椒招商脚本_7个卖点_v6.xlsx` +- **违规词规则**:`memory/topics/douyin-banned-words.md` +- **本资料库**:`skills/investor-brand-kit/SKILL.md` + +**写脚本顺序:先读本文件 → 再读违规词规则 → 再动手。** + +## Output contract + +This skill does not produce files by itself; the converted openclaw skill should declare its outputs in a new section here. (Filled in by the user after first run.) + +## Failure handling + +If a required external tool or path is missing, surface the exact missing identifier to the user instead of guessing. Do not auto-install system packages. (Add skill-specific failure modes here.) diff --git a/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/conversion-report.md b/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/conversion-report.md new file mode 100644 index 0000000..2fbb08e --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/conversion-report.md @@ -0,0 +1,21 @@ +# Conversion report + +- **input**: `examples/input/investor-brand-kit/SKILL.md` +- **tier**: pure / pure-translate +- **reason**: pure instruction, ascii-clean, no hardcoded paths + +## Path changes +_none_ + +## Written files +- `C:\Users\Administrator\skill-bridge\examples\output\investor-brand-kit\SKILL.md` + +## Recommendations +- enrich frontmatter (descriptions.zh-Hans, displayNames.zh-Hans, metadata) +- move trigger conditions from body to description +- verify body is under 500 lines; split into references/ if not + +## Warnings +_none_ + +_generated by skill-bridge v0.1.0 on 2026-08-14T12:33:14.253Z_ diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/SKILL.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/SKILL.md new file mode 100644 index 0000000..577491d --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/SKILL.md @@ -0,0 +1,51 @@ +--- +name: self-improving-agent +description: >- + Captures learnings, errors, and corrections to enable continuous improvement. Use when: (1) A + command or operation fails unexpectedly, (2) User corrects Claude ('No, that's wrong...', + 'Actually...'), (3) User requests a capability that doesn't exist, (4) An external API or tool + fails, (5) Claude realizes its knowledge is outdated or incorrect, (6) A better approach is + discovered for a recurring task. Also review learnings before major tasks. +displayNames: + zh-Hans: Self-Improvement Skill +metadata: + openclaw_compat: true + skill-bridge: + classify_tier: pure + classify_subtier: pure-wrapped-fix + classify_reason: 1 hardcoded path group(s) found +--- + +# Self-Improvement Skill + +Log learnings and errors to markdown files for continuous improvement. Coding agents can later process these into fixes, and important learnings get promoted to project memory. + + +## Quick Reference + +| Situation | Action | +|-----------|--------| +| Command/operation fails | Log to `.learnings/ERRORS.md` | +| User corrects you | Log to `.learnings/LEARNINGS.md` with category `correction` | +| User wants missing feature | Log to `.learnings/FEATURE_REQUESTS.md` | +| API/external tool fails | Log to `.learnings/ERRORS.md` with integration details | +| Knowledge was outdated | Log to `.learnings/LEARNINGS.md` with category `knowledge_gap` | +| Found better approach | Log to `.learnings/LEARNINGS.md` with category `best_practice` | +| Simplify/Harden recurring patterns | Log/update `.learnings/LEARNINGS.md` with `Source: simplify-and-harden` and a stable `Pattern-Key` | +| Similar to existing entry | Link with `**See Also**`, consider priority bump | +| Broadly applicable learning | Promote to `CLAUDE.md`, `AGENTS.md`, and/or `.github/copilot-instructions.md` | +| Workflow improvements | Promote to `AGENTS.md` (OpenClaw workspace) | +| Tool gotchas | Promote to `TOOLS.md` (OpenClaw workspace) | +| Behavioral patterns | Promote to `SOUL.md` (OpenClaw workspace) | + +## Output contract + +This skill does not produce files by itself; the converted openclaw skill should declare its outputs in a new section here. (Filled in by the user after first run.) + +## Failure handling + +If a required external tool or path is missing, surface the exact missing identifier to the user instead of guessing. Do not auto-install system packages. (Add skill-specific failure modes here.) + +## Windows (win32) platform notes + +The original openclaw skill assumed macOS/Linux shell. The PowerShell equivalents for any `bash`/`pip`/`python3` calls should be documented here. (Generated by skill-bridge; user to verify.) diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/conversion-report.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/conversion-report.md new file mode 100644 index 0000000..5451887 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/conversion-report.md @@ -0,0 +1,45 @@ +# Conversion report + +- **input**: `examples/input/self-improving-agent/SKILL.md` +- **tier**: pure / pure-wrapped-fix +- **reason**: 1 hardcoded path group(s) found + +## Path changes +- `openclaw-workspace` → ${OPENCLAW_WORKSPACE} (2x) +- `openclaw-home` → ${OPENCLAW_HOME} (2x) + +## Written files +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\SKILL.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\openclaw-setup-recommended.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\generic-setup-other-agents.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\logging-format.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\lrn-yyyymmdd-xxx-category.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\err-yyyymmdd-xxx-skill_or_command_name.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\feat-yyyymmdd-xxx-capability_name.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\id-generation.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\resolving-entries.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\promoting-to-project-memory.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\build-dependencies.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\after-api-changes.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\recurring-pattern-detection.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\simplify-harden-feed.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\periodic-review.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\detection-triggers.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\priority-guidelines.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\area-tags.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\best-practices.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\gitignore-options.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\hook-integration.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\automatic-skill-extraction.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\multi-agent-support.md` +- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\self-improvement.md` + +## Recommendations +- parameterize paths via paths.js +- ensure UTF-8 output +- add Windows adaptation section if body uses shell commands + +## Warnings +- paths parameterized: openclaw-workspace, openclaw-home + +_generated by skill-bridge v0.1.0 on 2026-08-14T12:33:14.360Z_ diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/after-api-changes.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/after-api-changes.md new file mode 100644 index 0000000..1cca975 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/after-api-changes.md @@ -0,0 +1,4 @@ +## After API Changes +1. Regenerate client: `pnpm run generate:api` +2. Check for type errors: `pnpm tsc --noEmit` +``` diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/area-tags.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/area-tags.md new file mode 100644 index 0000000..702e292 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/area-tags.md @@ -0,0 +1,12 @@ +## Area Tags + +Use to filter learnings by codebase region: + +| Area | Scope | +|------|-------| +| `frontend` | UI, components, client-side code | +| `backend` | API, services, server-side code | +| `infra` | CI/CD, deployment, Docker, cloud | +| `tests` | Test files, testing utilities, coverage | +| `docs` | Documentation, comments, READMEs | +| `config` | Configuration files, environment, settings | diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/automatic-skill-extraction.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/automatic-skill-extraction.md new file mode 100644 index 0000000..02b4f22 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/automatic-skill-extraction.md @@ -0,0 +1,64 @@ +## Automatic Skill Extraction + +When a learning is valuable enough to become a reusable skill, extract it using the provided helper. + +### Skill Extraction Criteria + +A learning qualifies for skill extraction when ANY of these apply: + +| Criterion | Description | +|-----------|-------------| +| **Recurring** | Has `See Also` links to 2+ similar issues | +| **Verified** | Status is `resolved` with working fix | +| **Non-obvious** | Required actual debugging/investigation to discover | +| **Broadly applicable** | Not project-specific; useful across codebases | +| **User-flagged** | User says "save this as a skill" or similar | + +### Extraction Workflow + +1. **Identify candidate**: Learning meets extraction criteria +2. **Run helper** (or create manually): + ```bash + ./skills/self-improvement/scripts/extract-skill.sh skill-name --dry-run + ./skills/self-improvement/scripts/extract-skill.sh skill-name + ``` +3. **Customize SKILL.md**: Fill in template with learning content +4. **Update learning**: Set status to `promoted_to_skill`, add `Skill-Path` +5. **Verify**: Read skill in fresh session to ensure it's self-contained + +### Manual Extraction + +If you prefer manual creation: + +1. Create `skills//SKILL.md` +2. Use template from `assets/SKILL-TEMPLATE.md` +3. Follow [Agent Skills spec](https://agentskills.io/specification): + - YAML frontmatter with `name` and `description` + - Name must match folder name + - No README.md inside skill folder + +### Extraction Detection Triggers + +Watch for these signals that a learning should become a skill: + +**In conversation:** +- "Save this as a skill" +- "I keep running into this" +- "This would be useful for other projects" +- "Remember this pattern" + +**In learning entries:** +- Multiple `See Also` links (recurring issue) +- High priority + resolved status +- Category: `best_practice` with broad applicability +- User feedback praising the solution + +### Skill Quality Gates + +Before extraction, verify: + +- [ ] Solution is tested and working +- [ ] Description is clear without original context +- [ ] Code examples are self-contained +- [ ] No project-specific hardcoded values +- [ ] Follows skill naming conventions (lowercase, hyphens) diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/best-practices.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/best-practices.md new file mode 100644 index 0000000..f995f5b --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/best-practices.md @@ -0,0 +1,10 @@ +## Best Practices + +1. **Log immediately** - context is freshest right after the issue +2. **Be specific** - future agents need to understand quickly +3. **Include reproduction steps** - especially for errors +4. **Link related files** - makes fixes easier +5. **Suggest concrete fixes** - not just "investigate" +6. **Use consistent categories** - enables filtering +7. **Promote aggressively** - if in doubt, add to CLAUDE.md or .github/copilot-instructions.md +8. **Review regularly** - stale learnings lose value diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/build-dependencies.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/build-dependencies.md new file mode 100644 index 0000000..260725b --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/build-dependencies.md @@ -0,0 +1,10 @@ +## Build & Dependencies +- Package manager: pnpm (not npm) - use `pnpm install` +``` + +**Learning** (verbose): +> When modifying API endpoints, must regenerate TypeScript client. +> Forgetting this causes type mismatches at runtime. + +**In AGENTS.md** (actionable): +```markdown diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/detection-triggers.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/detection-triggers.md new file mode 100644 index 0000000..d83f327 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/detection-triggers.md @@ -0,0 +1,26 @@ +## Detection Triggers + +Automatically log when you notice: + +**Corrections** (→ learning with `correction` category): +- "No, that's not right..." +- "Actually, it should be..." +- "You're wrong about..." +- "That's outdated..." + +**Feature Requests** (→ feature request): +- "Can you also..." +- "I wish you could..." +- "Is there a way to..." +- "Why can't you..." + +**Knowledge Gaps** (→ learning with `knowledge_gap` category): +- User provides information you didn't know +- Documentation you referenced is outdated +- API behavior differs from your understanding + +**Errors** (→ error entry): +- Command returns non-zero exit code +- Exception or stack trace +- Unexpected output or behavior +- Timeout or connection failure diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/err-yyyymmdd-xxx-skill_or_command_name.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/err-yyyymmdd-xxx-skill_or_command_name.md new file mode 100644 index 0000000..85e19ae --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/err-yyyymmdd-xxx-skill_or_command_name.md @@ -0,0 +1,36 @@ +## [ERR-YYYYMMDD-XXX] skill_or_command_name + +**Logged**: ISO-8601 timestamp +**Priority**: high +**Status**: pending +**Area**: frontend | backend | infra | tests | docs | config + +### Summary +Brief description of what failed + +### Error +``` +Actual error message or output +``` + +### Context +- Command/operation attempted +- Input or parameters used +- Environment details if relevant + +### Suggested Fix +If identifiable, what might resolve this + +### Metadata +- Reproducible: yes | no | unknown +- Related Files: path/to/file.ext +- See Also: ERR-20250110-001 (if recurring) + +--- +``` + +### Feature Request Entry + +Append to `.learnings/FEATURE_REQUESTS.md`: + +```markdown diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/feat-yyyymmdd-xxx-capability_name.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/feat-yyyymmdd-xxx-capability_name.md new file mode 100644 index 0000000..52f409c --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/feat-yyyymmdd-xxx-capability_name.md @@ -0,0 +1,25 @@ +## [FEAT-YYYYMMDD-XXX] capability_name + +**Logged**: ISO-8601 timestamp +**Priority**: medium +**Status**: pending +**Area**: frontend | backend | infra | tests | docs | config + +### Requested Capability +What the user wanted to do + +### User Context +Why they needed it, what problem they're solving + +### Complexity Estimate +simple | medium | complex + +### Suggested Implementation +How this could be built, what it might extend + +### Metadata +- Frequency: first_time | recurring +- Related Features: existing_feature_name + +--- +``` diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/generic-setup-other-agents.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/generic-setup-other-agents.md new file mode 100644 index 0000000..5281243 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/generic-setup-other-agents.md @@ -0,0 +1,20 @@ +## Generic Setup (Other Agents) + +For Claude Code, Codex, Copilot, or other agents, create `.learnings/` in your project: + +```bash +mkdir -p .learnings +``` + +Copy templates from `assets/` or create files with headers. + +### Add reference to agent files AGENTS.md, CLAUDE.md, or .github/copilot-instructions.md to remind yourself to log learnings. (this is an alternative to hook-based reminders) + +#### Self-Improvement Workflow + +When errors or corrections occur: +1. Log to `.learnings/ERRORS.md`, `LEARNINGS.md`, or `FEATURE_REQUESTS.md` +2. Review and promote broadly applicable learnings to: + - `CLAUDE.md` - project facts and conventions + - `AGENTS.md` - workflows and automation + - `.github/copilot-instructions.md` - Copilot context diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/gitignore-options.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/gitignore-options.md new file mode 100644 index 0000000..2b91766 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/gitignore-options.md @@ -0,0 +1,15 @@ +## Gitignore Options + +**Keep learnings local** (per-developer): +```gitignore +.learnings/ +``` + +**Track learnings in repo** (team-wide): +Don't add to .gitignore - learnings become shared knowledge. + +**Hybrid** (track templates, ignore entries): +```gitignore +.learnings/*.md +!.learnings/.gitkeep +``` diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/hook-integration.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/hook-integration.md new file mode 100644 index 0000000..b8e8af9 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/hook-integration.md @@ -0,0 +1,55 @@ +## Hook Integration + +Enable automatic reminders through agent hooks. This is **opt-in** - you must explicitly configure hooks. + +### Quick Setup (Claude Code / Codex) + +Create `.claude/settings.json` in your project: + +```json +{ + "hooks": { + "UserPromptSubmit": [{ + "matcher": "", + "hooks": [{ + "type": "command", + "command": "./skills/self-improvement/scripts/activator.sh" + }] + }] + } +} +``` + +This injects a learning evaluation reminder after each prompt (~50-100 tokens overhead). + +### Full Setup (With Error Detection) + +```json +{ + "hooks": { + "UserPromptSubmit": [{ + "matcher": "", + "hooks": [{ + "type": "command", + "command": "./skills/self-improvement/scripts/activator.sh" + }] + }], + "PostToolUse": [{ + "matcher": "Bash", + "hooks": [{ + "type": "command", + "command": "./skills/self-improvement/scripts/error-detector.sh" + }] + }] + } +} +``` + +### Available Hook Scripts + +| Script | Hook Type | Purpose | +|--------|-----------|---------| +| `scripts/activator.sh` | UserPromptSubmit | Reminds to evaluate learnings after tasks | +| `scripts/error-detector.sh` | PostToolUse (Bash) | Triggers on command errors | + +See `references/hooks-setup.md` for detailed configuration and troubleshooting. diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/id-generation.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/id-generation.md new file mode 100644 index 0000000..a893a61 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/id-generation.md @@ -0,0 +1,8 @@ +## ID Generation + +Format: `TYPE-YYYYMMDD-XXX` +- TYPE: `LRN` (learning), `ERR` (error), `FEAT` (feature) +- YYYYMMDD: Current date +- XXX: Sequential number or random 3 chars (e.g., `001`, `A7B`) + +Examples: `LRN-20250115-001`, `ERR-20250115-A3F`, `FEAT-20250115-002` diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/logging-format.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/logging-format.md new file mode 100644 index 0000000..e28f808 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/logging-format.md @@ -0,0 +1,7 @@ +## Logging Format + +### Learning Entry + +Append to `.learnings/LEARNINGS.md`: + +```markdown diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/lrn-yyyymmdd-xxx-category.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/lrn-yyyymmdd-xxx-category.md new file mode 100644 index 0000000..148b9a3 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/lrn-yyyymmdd-xxx-category.md @@ -0,0 +1,34 @@ +## [LRN-YYYYMMDD-XXX] category + +**Logged**: ISO-8601 timestamp +**Priority**: low | medium | high | critical +**Status**: pending +**Area**: frontend | backend | infra | tests | docs | config + +### Summary +One-line description of what was learned + +### Details +Full context: what happened, what was wrong, what's correct + +### Suggested Action +Specific fix or improvement to make + +### Metadata +- Source: conversation | error | user_feedback +- Related Files: path/to/file.ext +- Tags: tag1, tag2 +- See Also: LRN-20250110-001 (if related to existing entry) +- Pattern-Key: simplify.dead_code | harden.input_validation (optional, for recurring-pattern tracking) +- Recurrence-Count: 1 (optional) +- First-Seen: 2025-01-15 (optional) +- Last-Seen: 2025-01-15 (optional) + +--- +``` + +### Error Entry + +Append to `.learnings/ERRORS.md`: + +```markdown diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/multi-agent-support.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/multi-agent-support.md new file mode 100644 index 0000000..18f0165 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/multi-agent-support.md @@ -0,0 +1,22 @@ +## Multi-Agent Support + +This skill works across different AI coding agents with agent-specific activation. + +### Claude Code + +**Activation**: Hooks (UserPromptSubmit, PostToolUse) +**Setup**: `.claude/settings.json` with hook configuration +**Detection**: Automatic via hook scripts + +### Codex CLI + +**Activation**: Hooks (same pattern as Claude Code) +**Setup**: `.codex/settings.json` with hook configuration +**Detection**: Automatic via hook scripts + +### GitHub Copilot + +**Activation**: Manual (no hook support) +**Setup**: Add to `.github/copilot-instructions.md`: + +```markdown diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/openclaw-setup-recommended.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/openclaw-setup-recommended.md new file mode 100644 index 0000000..9490443 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/openclaw-setup-recommended.md @@ -0,0 +1,81 @@ +## OpenClaw Setup (Recommended) + +OpenClaw is the primary platform for this skill. It uses workspace-based prompt injection with automatic skill loading. + +### Installation + +**Via ClawdHub (recommended):** +```bash +clawdhub install self-improving-agent +``` + +**Manual:** +```bash +git clone https://github.com/peterskoett/self-improving-agent.git ${OPENCLAW_HOME}/skills/self-improving-agent +``` + +Remade for openclaw from original repo : https://github.com/pskoett/pskoett-ai-skills - https://github.com/pskoett/pskoett-ai-skills/tree/main/skills/self-improvement + +### Workspace Structure + +OpenClaw injects these files into every session: + +``` +${OPENCLAW_WORKSPACE}/ +├── AGENTS.md # Multi-agent workflows, delegation patterns +├── SOUL.md # Behavioral guidelines, personality, principles +├── TOOLS.md # Tool capabilities, integration gotchas +├── MEMORY.md # Long-term memory (main session only) +├── memory/ # Daily memory files +│ └── YYYY-MM-DD.md +└── .learnings/ # This skill's log files + ├── LEARNINGS.md + ├── ERRORS.md + └── FEATURE_REQUESTS.md +``` + +### Create Learning Files + +```bash +mkdir -p ${OPENCLAW_WORKSPACE}/.learnings +``` + +Then create the log files (or copy from `assets/`): +- `LEARNINGS.md` — corrections, knowledge gaps, best practices +- `ERRORS.md` — command failures, exceptions +- `FEATURE_REQUESTS.md` — user-requested capabilities + +### Promotion Targets + +When learnings prove broadly applicable, promote them to workspace files: + +| Learning Type | Promote To | Example | +|---------------|------------|---------| +| Behavioral patterns | `SOUL.md` | "Be concise, avoid disclaimers" | +| Workflow improvements | `AGENTS.md` | "Spawn sub-agents for long tasks" | +| Tool gotchas | `TOOLS.md` | "Git push needs auth configured first" | + +### Inter-Session Communication + +OpenClaw provides tools to share learnings across sessions: + +- **sessions_list** — View active/recent sessions +- **sessions_history** — Read another session's transcript +- **sessions_send** — Send a learning to another session +- **sessions_spawn** — Spawn a sub-agent for background work + +### Optional: Enable Hook + +For automatic reminders at session start: + +```bash +# Copy hook to OpenClaw hooks directory +cp -r hooks/openclaw ${OPENCLAW_HOME}/hooks/self-improvement + +# Enable it +openclaw hooks enable self-improvement +``` + +See `references/openclaw-integration.md` for complete details. + +--- diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/periodic-review.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/periodic-review.md new file mode 100644 index 0000000..6bf4ab0 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/periodic-review.md @@ -0,0 +1,27 @@ +## Periodic Review + +Review `.learnings/` at natural breakpoints: + +### When to Review +- Before starting a new major task +- After completing a feature +- When working in an area with past learnings +- Weekly during active development + +### Quick Status Check +```bash +# Count pending items +grep -h "Status\*\*: pending" .learnings/*.md | wc -l + +# List pending high-priority items +grep -B5 "Priority\*\*: high" .learnings/*.md | grep "^## \[" + +# Find learnings for a specific area +grep -l "Area\*\*: backend" .learnings/*.md +``` + +### Review Actions +- Resolve fixed items +- Promote applicable learnings +- Link related entries +- Escalate recurring issues diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/priority-guidelines.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/priority-guidelines.md new file mode 100644 index 0000000..44ac7a5 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/priority-guidelines.md @@ -0,0 +1,8 @@ +## Priority Guidelines + +| Priority | When to Use | +|----------|-------------| +| `critical` | Blocks core functionality, data loss risk, security issue | +| `high` | Significant impact, affects common workflows, recurring issue | +| `medium` | Moderate impact, workaround exists | +| `low` | Minor inconvenience, edge case, nice-to-have | diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/promoting-to-project-memory.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/promoting-to-project-memory.md new file mode 100644 index 0000000..4182166 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/promoting-to-project-memory.md @@ -0,0 +1,37 @@ +## Promoting to Project Memory + +When a learning is broadly applicable (not a one-off fix), promote it to permanent project memory. + +### When to Promote + +- Learning applies across multiple files/features +- Knowledge any contributor (human or AI) should know +- Prevents recurring mistakes +- Documents project-specific conventions + +### Promotion Targets + +| Target | What Belongs There | +|--------|-------------------| +| `CLAUDE.md` | Project facts, conventions, gotchas for all Claude interactions | +| `AGENTS.md` | Agent-specific workflows, tool usage patterns, automation rules | +| `.github/copilot-instructions.md` | Project context and conventions for GitHub Copilot | +| `SOUL.md` | Behavioral guidelines, communication style, principles (OpenClaw workspace) | +| `TOOLS.md` | Tool capabilities, usage patterns, integration gotchas (OpenClaw workspace) | + +### How to Promote + +1. **Distill** the learning into a concise rule or fact +2. **Add** to appropriate section in target file (create file if needed) +3. **Update** original entry: + - Change `**Status**: pending` → `**Status**: promoted` + - Add `**Promoted**: CLAUDE.md`, `AGENTS.md`, or `.github/copilot-instructions.md` + +### Promotion Examples + +**Learning** (verbose): +> Project uses pnpm workspaces. Attempted `npm install` but failed. +> Lock file is `pnpm-lock.yaml`. Must use `pnpm install`. + +**In CLAUDE.md** (concise): +```markdown diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/recurring-pattern-detection.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/recurring-pattern-detection.md new file mode 100644 index 0000000..fb452f7 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/recurring-pattern-detection.md @@ -0,0 +1,11 @@ +## Recurring Pattern Detection + +If logging something similar to an existing entry: + +1. **Search first**: `grep -r "keyword" .learnings/` +2. **Link entries**: Add `**See Also**: ERR-20250110-001` in Metadata +3. **Bump priority** if issue keeps recurring +4. **Consider systemic fix**: Recurring issues often indicate: + - Missing documentation (→ promote to CLAUDE.md or .github/copilot-instructions.md) + - Missing automation (→ add to AGENTS.md) + - Architectural problem (→ create tech debt ticket) diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/resolving-entries.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/resolving-entries.md new file mode 100644 index 0000000..9357e96 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/resolving-entries.md @@ -0,0 +1,18 @@ +## Resolving Entries + +When an issue is fixed, update the entry: + +1. Change `**Status**: pending` → `**Status**: resolved` +2. Add resolution block after Metadata: + +```markdown +### Resolution +- **Resolved**: 2025-01-16T09:00:00Z +- **Commit/PR**: abc123 or #42 +- **Notes**: Brief description of what was done +``` + +Other status values: +- `in_progress` - Actively being worked on +- `wont_fix` - Decided not to address (add reason in Resolution notes) +- `promoted` - Elevated to CLAUDE.md, AGENTS.md, or .github/copilot-instructions.md diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/self-improvement.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/self-improvement.md new file mode 100644 index 0000000..b7db275 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/self-improvement.md @@ -0,0 +1,38 @@ +## Self-Improvement + +After solving non-obvious issues, consider logging to `.learnings/`: +1. Use format from self-improvement skill +2. Link related entries with See Also +3. Promote high-value learnings to skills + +Ask in chat: "Should I log this as a learning?" +``` + +**Detection**: Manual review at session end + +### OpenClaw + +**Activation**: Workspace injection + inter-agent messaging +**Setup**: See "OpenClaw Setup" section above +**Detection**: Via session tools and workspace files + +### Agent-Agnostic Guidance + +Regardless of agent, apply self-improvement when you: + +1. **Discover something non-obvious** - solution wasn't immediate +2. **Correct yourself** - initial approach was wrong +3. **Learn project conventions** - discovered undocumented patterns +4. **Hit unexpected errors** - especially if diagnosis was difficult +5. **Find better approaches** - improved on your original solution + +### Copilot Chat Integration + +For Copilot users, add this to your prompts when relevant: + +> After completing this task, evaluate if any learnings should be logged to `.learnings/` using the self-improvement skill format. + +Or use quick prompts: +- "Log this to learnings" +- "Create a skill from this solution" +- "Check .learnings/ for related issues" diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/simplify-harden-feed.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/simplify-harden-feed.md new file mode 100644 index 0000000..42539d5 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/simplify-harden-feed.md @@ -0,0 +1,36 @@ +## Simplify & Harden Feed + +Use this workflow to ingest recurring patterns from the `simplify-and-harden` +skill and turn them into durable prompt guidance. + +### Ingestion Workflow + +1. Read `simplify_and_harden.learning_loop.candidates` from the task summary. +2. For each candidate, use `pattern_key` as the stable dedupe key. +3. Search `.learnings/LEARNINGS.md` for an existing entry with that key: + - `grep -n "Pattern-Key: " .learnings/LEARNINGS.md` +4. If found: + - Increment `Recurrence-Count` + - Update `Last-Seen` + - Add `See Also` links to related entries/tasks +5. If not found: + - Create a new `LRN-...` entry + - Set `Source: simplify-and-harden` + - Set `Pattern-Key`, `Recurrence-Count: 1`, and `First-Seen`/`Last-Seen` + +### Promotion Rule (System Prompt Feedback) + +Promote recurring patterns into agent context/system prompt files when all are true: + +- `Recurrence-Count >= 3` +- Seen across at least 2 distinct tasks +- Occurred within a 30-day window + +Promotion targets: +- `CLAUDE.md` +- `AGENTS.md` +- `.github/copilot-instructions.md` +- `SOUL.md` / `TOOLS.md` for OpenClaw workspace-level guidance when applicable + +Write promoted rules as short prevention rules (what to do before/while coding), +not long incident write-ups. diff --git a/plugins/antianqi/skill-bridge/examples/output/task-tracker/SKILL.md b/plugins/antianqi/skill-bridge/examples/output/task-tracker/SKILL.md new file mode 100644 index 0000000..98b535f --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/task-tracker/SKILL.md @@ -0,0 +1,105 @@ +--- +name: task-tracker +description: 'Use when: 任务追踪与日报周报生成。用于记录老板工作进度、生成日报周报、持续追踪任务完成情况。.' +displayNames: + zh-Hans: Task Tracker - 任务追踪与日报周报 +metadata: + openclaw_compat: true + skill-bridge: + classify_tier: pure + classify_subtier: pure-wrapped-fix + classify_reason: 1 hardcoded path group(s) found +--- + +# Task Tracker - 任务追踪与日报周报 + +## 核心文件 +- 任务总表:`${OPENCLAW_WORKSPACE}/TASKS.md` + +## 任务格式规范 + +### 日报格式(必须遵守) +- 内容顺序:**①直播 ②短视频 ③外卖 ④其他** +- 不显示大分类标题,直接按顺序列序号 +- **不用任何符号**(✅❌🔄等都不用) +- 发到飞书,用文字不用语音 +- **输出时:完整输出 TASKS.md 里记录的详细内容和进度,不简化** + +### 明日计划原则 +- **持续跟进的项必须列入**(如:城乡路京东外卖持续跟进) +- **今日新提到的跟进项也列入**(如:美团收银报价跟进) +- 不在本周计划里但老板提到的新任务 → 追加进明日计划 + +### 重要区分 +- **日报只记老板的工作**(品牌运营 + 线上运营 + 外卖 + 品牌营销) +- **数据统计填表是狗蛋的工作,不记入日报** +- **系统升级、工具配置等狗蛋研发工作不记入日报** +- **狗蛋自己的研发/学习/技能提升工作不记入日报**,只记入 memory/daily/YYYY-MM-DD.md +- 老板告诉我进展 → 更新 TASKS.md(详细记录) +- 我自己的研发进展 → 更新 memory/daily/YYYY-MM-DD.md + +### 重要区分 +- **日报只记老板的工作**(品牌运营 + 线上运营 + 外卖 + 品牌营销) +- **数据统计填表是狗蛋的工作,不记入日报** +- **狗蛋自己的研发/学习/技能提升工作不记入日报**,只记入 memory/daily/YYYY-MM-DD.md +- 老板告诉我进展 → 更新 TASKS.md(详细记录) +- 我自己的研发进展 → 更新 memory/daily/YYYY-MM-DD.md + +``` +老板日报(YYYY-MM-DD) +今日工作: +1. ... +2. ... +明日计划: +1. ... +2. ... +``` + +### 周报格式 +同日报格式,周六汇总一周数据+工作内容 + +### 任务格式 +``` +### 今日进展(YYYY-MM-DD) +- 具体工作内容 + +### 明日计划 +- 延续任务(带进度说明) +- 新增任务 +``` + +### 任务状态规则 +- 今日未完成的 → 记录到明日计划 +- 本周未完成的 → 记录到下周计划 +- 狗蛋自己的研发/学习工作 → 不记录 + +## 使用场景 + +### 记录进展 +老板告诉你工作进展 → 更新 TASKS.md + +### 查询进度 +老板问"现在任务进度" → 读取 TASKS.md 输出当前任务清单 + +### 生成日报 +老板说"写日报" → 从 TASKS.md 当前日进展生成格式化日报,发到飞书 + +### 生成周报 +老板说"写周报" → 从 TASKS.md 本周任务+进展生成,发到飞书 + +### 任务完成 +老板说某任务完成了 → 更新 TASKS.md 中该任务状态为"已完成",标注日期 + +### 新增任务 +老板布置新任务 → 追加到 TASKS.md 当前周任务列表 + +## 追踪文件路径 +`${OPENCLAW_WORKSPACE}/TASKS.md` + +## Output contract + +This skill does not produce files by itself; the converted openclaw skill should declare its outputs in a new section here. (Filled in by the user after first run.) + +## Failure handling + +If a required external tool or path is missing, surface the exact missing identifier to the user instead of guessing. Do not auto-install system packages. (Add skill-specific failure modes here.) diff --git a/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md b/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md new file mode 100644 index 0000000..a8aeccd --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md @@ -0,0 +1,21 @@ +# Conversion report + +- **input**: `examples/input/task-tracker/SKILL.md` +- **tier**: pure / pure-wrapped-fix +- **reason**: 1 hardcoded path group(s) found + +## Path changes +- `openclaw-workspace` → ${OPENCLAW_WORKSPACE} (2x) + +## Written files +- `C:\Users\Administrator\skill-bridge\examples\output\task-tracker\SKILL.md` + +## Recommendations +- parameterize paths via paths.js +- ensure UTF-8 output +- add Windows adaptation section if body uses shell commands + +## Warnings +- paths parameterized: openclaw-workspace + +_generated by skill-bridge v0.1.0 on 2026-08-14T12:33:14.160Z_ diff --git a/plugins/antianqi/skill-bridge/index.js b/plugins/antianqi/skill-bridge/index.js new file mode 100644 index 0000000..3322753 --- /dev/null +++ b/plugins/antianqi/skill-bridge/index.js @@ -0,0 +1,215 @@ +#!/usr/bin/env node +// index.js — mcode-skill-bridge CLI +// +// Subcommands: +// detect print encoding detection result +// analyze print analysis report (frontmatter, paths, external cmds) +// classify print tier + reason +// convert run the full pipeline, write to --out +// lint run the mavis skill-creator lint +// +// When given a directory, we look for SKILL.md inside it. + +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { detectEncoding, readFileSafe } from './lib/detect.js'; +import { analyzeSkillFile } from './lib/analyze.js'; +import { classify } from './lib/classify.js'; +import { transformSkill } from './lib/transform-skill.js'; +import { lintSkill } from './lib/lint.js'; + +const USAGE = `mcode-skill-bridge — convert openclaw (and similar) skills to mavis/mcode + +Usage: + mcode-skill-bridge detect Detect encoding of a SKILL.md + mcode-skill-bridge analyze Analyze (frontmatter, paths, external cmds) + mcode-skill-bridge classify Classify into pure / wrapped / abandon + mcode-skill-bridge convert Convert and write to --out + mcode-skill-bridge lint Lint a converted skill + mcode-skill-bridge --help + +Options: + --out Output directory (default: ./out/) + --force Overwrite existing output + --no-lint Skip lint after convert + --scope user | agent | project (informational only, used in report) + --json Machine-readable output +`; + +function parseArgs(argv) { + const args = { _: [], opts: {} }; + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + if (a.startsWith('--')) { + const k = a.slice(2); + const next = argv[i + 1]; + if (next && !next.startsWith('--')) { + args.opts[k] = next; + i++; + } else { + args.opts[k] = true; + } + } else { + args._.push(a); + } + } + return args; +} + +async function resolveInput(p) { + const stat = await fs.stat(p).catch(() => null); + if (!stat) throw new Error(`input not found: ${p}`); + if (stat.isDirectory()) { + const candidate = path.join(p, 'SKILL.md'); + await fs.access(candidate); + return candidate; + } + return p; +} + +function jsonOut(obj) { + process.stdout.write(JSON.stringify(obj, null, 2) + '\n'); +} + +async function cmdDetect(target, opts) { + const det = await readFileSafe(target); + if (opts.json) return jsonOut(det); + console.log(`encoding: ${det.encoding}`); + console.log(`original: ${det.originalEncoding}`); + console.log(`replaced: ${det.replaced}`); + console.log(`confidence: ${det.confidence}`); + console.log(`reason: ${det.reason}`); + console.log(`text length: ${det.text.length} chars`); +} + +async function cmdAnalyze(target, opts) { + const report = await analyzeSkillFile(target); + if (opts.json) return jsonOut(report); + console.log(`input: ${report.inputPath}`); + console.log(`encoding: ${report.encoding} (converted=${report.convertedFromGbk})`); + console.log(`frontmatter: ${Object.keys(report.frontmatter).join(', ') || '(empty)'}`); + console.log(``); + console.log(`hardcoded paths:`); + for (const p of report.hardcodedPaths) console.log(` - ${p.label}: ${p.samples.join(', ')}`); + if (report.hardcodedPaths.length === 0) console.log(` (none)`); + console.log(``); + console.log(`external commands:`); + for (const c of report.externalCommands) console.log(` - ${c.label}: ${c.samples.join(', ')}`); + if (report.externalCommands.length === 0) console.log(` (none)`); + if (report.warnings.length) { + console.log(``); + console.log(`warnings:`); + for (const w of report.warnings) console.log(` - ${w}`); + } +} + +async function cmdClassify(target, opts) { + const report = await analyzeSkillFile(target); + const result = classify(report); + if (opts.json) return jsonOut({ report: { inputPath: report.inputPath, encoding: report.encoding }, result }); + console.log(`tier: ${result.tier}`); + console.log(`subTier: ${result.subTier}`); + console.log(`reason: ${result.reason}`); + console.log(``); + console.log(`recommendations:`); + for (const r of result.recommendations) console.log(` - ${r}`); +} + +async function cmdConvert(target, opts) { + const report = await analyzeSkillFile(target); + const result = classify(report); + if (result.tier === 'abandon') { + console.error(`abandon: ${result.reason}`); + process.exitCode = 2; + return; + } + if (result.tier !== 'pure') { + console.error(`convert: tier "${result.tier}" not yet supported in v0.1 (only pure). See plan §6.`); + process.exitCode = 3; + return; + } + const outDir = opts.out + ? path.resolve(String(opts.out)) + : path.resolve('./out', path.basename(path.dirname(target))); + if (!opts.force) { + const exists = await fs.stat(outDir).catch(() => null); + if (exists) { + console.error(`output already exists: ${outDir} (use --force to overwrite)`); + process.exitCode = 4; + return; + } + } + const r = await transformSkill({ + inputPath: target, + report, + classify: result, + outDir, + }); + console.log(`wrote ${r.written.length} files to ${outDir}`); + for (const w of r.written) console.log(` - ${w}`); + if (r.warnings.length) { + console.log(``); + console.log(`warnings:`); + for (const w of r.warnings) console.log(` - ${w}`); + } + if (opts['no-lint']) return; + const lintResult = await lintSkill(outDir); + if (lintResult.ok) { + console.log(``); + console.log(`lint: PASS`); + } else { + console.log(``); + console.log(`lint: WARN (exit=${lintResult.code})`); + if (lintResult.stdout) console.log(lintResult.stdout); + if (lintResult.stderr) console.log(lintResult.stderr); + } +} + +async function cmdLint(target, opts) { + const r = await lintSkill(target); + if (r.ok) { + console.log(`lint: PASS`); + } else { + console.log(`lint: FAIL (exit=${r.code})`); + if (r.stdout) console.log(r.stdout); + if (r.stderr) console.log(r.stderr); + process.exitCode = 1; + } +} + +async function main() { + const args = parseArgs(process.argv.slice(2)); + if (args._.length === 0 || args.opts.help || args.opts.h) { + process.stdout.write(USAGE); + return; + } + const cmd = args._[0]; + const target = args._[1]; + if (!target) { + console.error(`missing input for command: ${cmd}`); + process.exitCode = 1; + return; + } + + try { + const resolved = ['detect', 'analyze', 'classify', 'convert'].includes(cmd) + ? await resolveInput(target) + : path.resolve(target); + switch (cmd) { + case 'detect': return await cmdDetect(resolved, args.opts); + case 'analyze': return await cmdAnalyze(resolved, args.opts); + case 'classify': return await cmdClassify(resolved, args.opts); + case 'convert': return await cmdConvert(resolved, args.opts); + case 'lint': return await cmdLint(resolved, args.opts); + default: + console.error(`unknown command: ${cmd}`); + process.stdout.write(USAGE); + process.exitCode = 1; + } + } catch (e) { + console.error(`error: ${e.message}`); + process.exitCode = 1; + } +} + +main(); diff --git a/plugins/antianqi/skill-bridge/lib/analyze.js b/plugins/antianqi/skill-bridge/lib/analyze.js new file mode 100644 index 0000000..dd5af23 --- /dev/null +++ b/plugins/antianqi/skill-bridge/lib/analyze.js @@ -0,0 +1,114 @@ +// lib/analyze.js — Frontmatter parsing and hardcoded-paths/commands scan. +// +// We avoid `gray-matter` to keep the dependency surface small. The +// frontmatter we need to parse is a constrained subset of YAML: +// - top-level `key: value` lines +// - top-level `key: |` followed by an indented block +// - top-level `key:` with nested keys (one level deep, used by +// `descriptions.zh-Hans` and `metadata.x`). + +import * as yaml from 'js-yaml'; +import fs from 'node:fs/promises'; +import { readFileSafe } from './detect.js'; + +const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/; + +const EXTERNAL_COMMAND_PATTERNS = [ + { re: /\bpip\s+install\b/g, label: 'pip install' }, + { re: /\bcli-anything-[a-z0-9-]+/g, label: 'cli-anything CLI' }, + { re: /\bpython3?\s+/g, label: 'python invocation' }, + { re: /\bcurl\s+/g, label: 'curl' }, + { re: /\bwget\s+/g, label: 'wget' }, + { re: /\bComfyUI\b/g, label: 'ComfyUI reference' }, + { re: /\bESP32\b/g, label: 'ESP32 reference' }, + { re: /\bDouyin|抖音\b/g, label: 'Douyin reference' }, + { re: /\bTTS\b/g, label: 'TTS reference' }, + { re: /\bfeishu|飞书\b/g, label: 'Feishu reference' }, + { re: /\b\${\w+}\b/g, label: 'unresolved template var' }, +]; + +const PATH_PATTERNS = [ + { re: /C:\\Users\\[^"\s`']+/g, label: 'absolute Windows user path' }, + { re: /~\/\.[a-zA-Z0-9_.-]+/g, label: 'tilde home path' }, + { re: /(?} hardcodedPaths + * @property {Array<{label:string, samples:string[]}>} externalCommands + * @property {string[]} warnings + */ + +/** + * Parse a SKILL.md into frontmatter (object) + body (string). + * @param {string} text + * @returns {{ frontmatter: object, body: string, ok: boolean, err?: string }} + */ +export function parseFrontmatter(text) { + const m = FRONTMATTER_RE.exec(text); + if (!m) return { frontmatter: {}, body: text, ok: false, err: 'no frontmatter' }; + try { + const fm = yaml.load(m[1], { filename: undefined }) || {}; + return { frontmatter: fm, body: m[2], ok: true }; + } catch (e) { + return { frontmatter: {}, body: text, ok: false, err: 'yaml parse: ' + e.message }; + } +} + +/** + * Find matches of a set of patterns and return deduplicated samples. + * @param {string} text + * @param {Array<{re:RegExp,label:string}>} patterns + * @returns {Array<{label:string, samples:string[]}>} + */ +function scanPatterns(text, patterns) { + const out = []; + for (const { re, label } of patterns) { + re.lastIndex = 0; + const samples = new Set(); + let m; + while ((m = re.exec(text)) !== null) { + samples.add(m[0]); + if (samples.size >= 5) break; + } + if (samples.size > 0) out.push({ label, samples: [...samples] }); + } + return out; +} + +/** + * Full analyze of a single skill file. + * @param {string} filePath + * @returns {Promise} + */ +export async function analyzeSkillFile(filePath) { + const det = await readFileSafe(filePath); + const text = det.text; + const { frontmatter, body, ok, err } = parseFrontmatter(text); + + const fullText = ok ? `---\n${yaml.dump(frontmatter)}---\n${body}` : text; + + const warnings = []; + if (!ok) warnings.push(`frontmatter: ${err}`); + if (det.encoding === 'unknown') warnings.push('encoding: could not determine (left as lossy utf-8)'); + if (det.encoding === 'gbk' && det.replaced) warnings.push('encoding: converted from GBK to UTF-8'); + + return { + inputPath: filePath, + encoding: det.encoding, + convertedFromGbk: det.replaced, + frontmatter, + body, + fullText, + hardcodedPaths: scanPatterns(fullText, PATH_PATTERNS), + externalCommands: scanPatterns(fullText, EXTERNAL_COMMAND_PATTERNS), + warnings, + }; +} diff --git a/plugins/antianqi/skill-bridge/lib/classify.js b/plugins/antianqi/skill-bridge/lib/classify.js new file mode 100644 index 0000000..c2459d4 --- /dev/null +++ b/plugins/antianqi/skill-bridge/lib/classify.js @@ -0,0 +1,94 @@ +// lib/classify.js — Three-tier decision tree. +// +// pure-translate : pure instruction, ascii-clean, no hardcoded paths +// -> only add frontmatter fields, no body rewrite +// pure-wrapped-fix: pure instruction but with hardcoded paths or GBK +// -> rewrite paths + ensure UTF-8 +// wrapped-* : needs an external CLI/API; cannot be a pure skill +// abandon : unfixable openclaw-only assumptions +// +// The decision tree in §2.2 of the plan, encoded as a flat function. + +/** + * @typedef {Object} ClassifyResult + * @property {'pure'|'wrapped'|'abandon'} tier + * @property {string} subTier e.g. 'pure-translate', 'wrapped-python' + * @property {string} reason + * @property {string[]} recommendations + */ + +/** + * @param {import('./analyze.js').AnalyzedSkill} report + * @returns {ClassifyResult} + */ +export function classify(report) { + const { hardcodedPaths, externalCommands, encoding, convertedFromGbk } = report; + + const hasExternalTool = externalCommands.length > 0; + const hasHardcodedPaths = hardcodedPaths.length > 0; + const isAsciiClean = encoding === 'utf-8' && !convertedFromGbk; + + // Q1: external tool dependence + if (hasExternalTool) { + // Q3: which kind? + const labels = externalCommands.map(c => c.label); + let sub = 'wrapped-unknown'; + if (labels.includes('pip install') || labels.includes('python invocation')) { + sub = 'wrapped-python'; + } else if (labels.includes('cli-anything CLI')) { + sub = 'wrapped-cli-anything'; + } else if (labels.includes('curl')) { + sub = 'wrapped-http'; + } else if (labels.some(l => /ComfyUI|ESP32|Douyin|TTS|Feishu/.test(l))) { + sub = 'wrapped-service'; + } else if (labels.includes('unresolved template var')) { + // template vars alone don't count as a real external dep + // fall through to Q2 + } else { + sub = 'wrapped-binary'; + } + // Only return wrapped-* if we actually decided it's wrapped + if (sub !== 'wrapped-unknown') { + return { + tier: 'wrapped', + subTier: sub, + reason: `depends on external: ${labels.join(', ')}`, + recommendations: [ + 'emit as a mavis plugin (plugin.json + index.js)', + 'document dependency installation in README', + 'do not promise behavior parity in v0.1', + ], + }; + } + } + + // Q2: hardcoded paths or encoding issues + if (hasHardcodedPaths || convertedFromGbk) { + return { + tier: 'pure', + subTier: 'pure-wrapped-fix', + reason: convertedFromGbk + ? `gbk source, ${hardcodedPaths.length} hardcoded path group(s)` + : `${hardcodedPaths.length} hardcoded path group(s) found`, + recommendations: [ + 'parameterize paths via paths.js', + 'ensure UTF-8 output', + 'add Windows adaptation section if body uses shell commands', + ], + }; + } + + // Q4: clean pure + return { + tier: 'pure', + subTier: isAsciiClean ? 'pure-translate' : 'pure-wrapped-fix', + reason: isAsciiClean + ? 'pure instruction, ascii-clean, no hardcoded paths' + : 'pure instruction but needs encoding touch-up', + recommendations: [ + 'enrich frontmatter (descriptions.zh-Hans, displayNames.zh-Hans, metadata)', + 'move trigger conditions from body to description', + 'verify body is under 500 lines; split into references/ if not', + ], + }; +} diff --git a/plugins/antianqi/skill-bridge/lib/detect.js b/plugins/antianqi/skill-bridge/lib/detect.js new file mode 100644 index 0000000..dac7f41 --- /dev/null +++ b/plugins/antianqi/skill-bridge/lib/detect.js @@ -0,0 +1,111 @@ +// lib/detect.js — Encoding detection (GBK vs UTF-8) and mojibake recovery. +// +// Strategy: +// 1. Read raw bytes. +// 2. Try UTF-8 strict decode: if no replacement chars, it's UTF-8. +// 3. Else try GBK decode via iconv-lite: if it produces mostly CJK +// printable characters (no replacement chars), the source was GBK +// and we can restore it to UTF-8. +// 4. Else: declare unknown (do not modify). +// +// We deliberately avoid chardet-style heuristics in v0.1 because the +// failure mode of guessing wrong is silent corruption of skill text. + +import iconv from 'iconv-lite'; +import fs from 'node:fs/promises'; + +const REPLACEMENT = '\uFFFD'; +const PRINTABLE_CJK = /[\u3400-\u9FFF]/; +const NON_ASCII_PRINTABLE = /[^\x00-\x7F]/; + +/** + * @typedef {Object} DetectResult + * @property {'utf-8'|'gbk'|'unknown'} encoding + * @property {string} text - The recovered UTF-8 text. + * @property {string} originalEncoding - What we believe the source was. + * @property {boolean} replaced - True if conversion was needed. + * @property {number} confidence - 0..1 heuristic confidence. + * @property {string} reason + */ + +/** + * Detect the encoding of a Buffer and return UTF-8 text. + * @param {Buffer} buf + * @returns {DetectResult} + */ +export function detectEncoding(buf) { + // 1. UTF-8 strict + try { + const text = buf.toString('utf-8'); + if (!text.includes(REPLACEMENT)) { + // Cheap "is this actually CJK text" check: at least one non-ASCII printable. + const hasNonAscii = NON_ASCII_PRINTABLE.test(text); + return { + encoding: 'utf-8', + text, + originalEncoding: 'utf-8', + replaced: false, + confidence: hasNonAscii ? 0.95 : 0.8, + reason: 'utf-8 decode clean', + }; + } + } catch { + /* fall through */ + } + + // 2. GBK via iconv-lite + if (iconv.encodingExists('gbk')) { + try { + const text = iconv.decode(buf, 'gbk'); + // GBK almost never produces \uFFFD for valid byte sequences. + if (!text.includes(REPLACEMENT) && PRINTABLE_CJK.test(text)) { + return { + encoding: 'gbk', + text, + originalEncoding: 'gbk', + replaced: true, + confidence: 0.9, + reason: 'gbk decode clean and contains CJK', + }; + } + } catch { + /* fall through */ + } + } + + // 3. Last resort: lossy UTF-8, marked unknown so caller can warn. + const text = buf.toString('utf-8'); + return { + encoding: 'unknown', + text, + originalEncoding: 'unknown', + replaced: false, + confidence: 0.1, + reason: 'could not determine; left as lossy utf-8', + }; +} + +/** + * Read a file from disk and return its detected encoding + UTF-8 text. + * @param {string} filePath + * @returns {Promise} + */ +export async function readFileSafe(filePath) { + const buf = await fs.readFile(filePath); + return detectEncoding(buf); +} + +/** + * Heuristic: does the given UTF-8 text LOOK like GBK mojibake that + * was already partially normalized? Useful when the file on disk is + * already a mess of replacement characters and there's no clean byte + * stream to go back to. + * + * @param {string} text + * @returns {boolean} + */ +export function isLikelyGbkMojibake(text) { + // Pattern: 2+ consecutive U+FFFD surrounded by ASCII or whitespace. + // This catches the common "????-???" rendering we see in terminal output. + return /\uFFFD{2,}/.test(text) || /[?]{3,}/.test(text); +} diff --git a/plugins/antianqi/skill-bridge/lib/lint.js b/plugins/antianqi/skill-bridge/lib/lint.js new file mode 100644 index 0000000..111d3d2 --- /dev/null +++ b/plugins/antianqi/skill-bridge/lib/lint.js @@ -0,0 +1,71 @@ +// lib/lint.js — Wrap the mavis skill-creator lint script. +// +// The official `lint-skill.js` ships as ES module source but is named +// with a `.js` extension and is not under a package.json with +// `"type": "module"`. Spawning `node` on it fails with a confusing +// SyntaxError. We avoid the problem by importing the source via the +// data: URL trick (Node will parse it as ESM when the import assertion +// says so) or by reading the source and eval-ing it. +// +// v0.1 uses the dynamic import path: read the file, write a temp +// `.mjs` next to it, then dynamic-import that. This stays compatible +// with all Node 22+ setups. + +import { spawn } from 'node:child_process'; +import path from 'node:path'; +import os from 'node:os'; +import fs from 'node:fs/promises'; +import { pathToFileURL } from 'node:url'; + +async function ensureMjs(lintScript) { + // If there's a package.json in the parent chain that says type=module, + // we can just import the .js directly. Otherwise, copy to .mjs. + const mjs = lintScript.replace(/\.js$/, '.sb-lint.mjs'); + const src = await fs.readFile(lintScript, 'utf-8'); + await fs.writeFile(mjs, src, 'utf-8'); + return mjs; +} + +/** + * @param {string} skillPath + * @param {object} [opts] + * @param {string} [opts.lintScript] + * @returns {Promise<{ ok: boolean, code: number, stdout: string, stderr: string }>} + */ +export async function lintSkill(skillPath, opts = {}) { + const lintScript = opts.lintScript + || path.join(os.homedir(), '.minimax', '.builtin-skills', 'skill-creator', 'scripts', 'lint-skill.js'); + + // Try dynamic import first (works if package.json has type=module nearby). + try { + const mod = await import(pathToFileURL(lintScript).href); + if (typeof mod.lint === 'function') { + const result = await mod.lint(skillPath); + return { ok: result.ok ?? true, code: result.code ?? 0, stdout: result.stdout ?? '', stderr: result.stderr ?? '' }; + } + } catch (e) { + // Fall through to subprocess path + } + + // Subprocess path: stage as .mjs and run. + const mjs = await ensureMjs(lintScript); + return await new Promise((resolve) => { + const child = spawn(process.execPath, [mjs, skillPath], { + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }); + let stdout = ''; + let stderr = ''; + child.stdout.on('data', d => stdout += d); + child.stderr.on('data', d => stderr += d); + child.on('close', (code) => { + // Best-effort cleanup of staged .mjs + fs.unlink(mjs).catch(() => {}); + resolve({ ok: code === 0, code, stdout, stderr }); + }); + child.on('error', (err) => { + fs.unlink(mjs).catch(() => {}); + resolve({ ok: false, code: -1, stdout, stderr: stderr + '\nspawn error: ' + err.message }); + }); + }); +} diff --git a/plugins/antianqi/skill-bridge/lib/paths.js b/plugins/antianqi/skill-bridge/lib/paths.js new file mode 100644 index 0000000..d427f53 --- /dev/null +++ b/plugins/antianqi/skill-bridge/lib/paths.js @@ -0,0 +1,131 @@ +// lib/paths.js — Path parameterization and filename fix. +// +// We can't statically know where an openclaw skill's "workspace" lives +// on a new machine. So we replace every hard-coded openclaw/TMP path +// with a parameterized template, and emit a metadata.openclaw_paths +// block that downstream code (or the user) can fill in. + +/** + * Each rule has: + * - id: short stable id + * - match: regex (with /g flag) + * - replace: replacement string (supports ${VAR} placeholders) + * - placeholder: which env var this maps to + * - notes: human-readable + */ +export const PATH_RULES = [ + { + id: 'openclaw-workspace', + // match either backslash or forward slash separator + match: /C:\\Users\\Administrator\\\.openclaw[\\/]workspace[\\/]?/g, + replace: '${OPENCLAW_WORKSPACE}/', + placeholder: 'OPENCLAW_WORKSPACE', + notes: 'openclaw workspace dir', + }, + { + id: 'openclaw-home', + match: /C:\\Users\\Administrator\\\.openclaw[\\/]?/g, + replace: '${OPENCLAW_HOME}/', + placeholder: 'OPENCLAW_HOME', + notes: 'Path under user home .openclaw/', + }, + { + id: 'openclaw-uniq-tilde', + match: /~\/\.openclaw\//g, + replace: '${OPENCLAW_HOME}/', + placeholder: 'OPENCLAW_HOME', + notes: 'tilde form of openclaw home (POSIX-style)', + }, + { + id: 'tmp-cli-anything', + match: /\/tmp\/CLI-Anything\//g, + replace: '${SCRATCH}/cli-anything/', + placeholder: 'SCRATCH', + notes: 'tmp path used by CLI-Anything harness', + }, + { + id: 'tmp-generic', + match: /(?/ +// SKILL.md # mavis schema, with enriched frontmatter +// conversion-report.md # what we changed and why +// references/.md # (optional) split from body if too long + +import fs from 'node:fs/promises'; +import path from 'node:path'; +import * as yaml from 'js-yaml'; +import { parameterizePaths, suggestFilename } from './paths.js'; +import { parseFrontmatter } from './analyze.js'; + +const MAX_BODY_LINES = 500; + +function kebab(name) { + // Strict ASCII kebab-case. Chinese / CJK names move to displayNames.zh-Hans. + return String(name) + .toLowerCase() + .replace(/[^a-z0-9-]+/g, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 64) || 'unnamed-skill'; +} + +const TRIGGER_RE = /(".*?")|(\bwhen\b)|(\btrigger\b)|(\buse this\b)|(\bload this\b)|(\buse when\b)/i; +const TRIGGER_PHRASES = [ + 'Use when the user asks to', + 'Use when: ', + 'Use this skill when', +]; + +function extractChineseSummary(body) { + // Grab the first non-heading paragraph that contains Chinese. + // Skip the first H1 if it doubles as a title; look for the first + // paragraph that's plain prose. + const blocks = body.split(/\r?\n\r?\n/); + for (const p of blocks) { + const t = p.trim(); + if (!t) continue; + if (/^#+\s/.test(t)) continue; // skip headings + if (/^```/.test(t)) continue; // skip code blocks + if (/^[-*+]\s/.test(t)) continue; // skip list items + if (!/[\u3400-\u9FFF]/.test(t)) continue; + return t.replace(/\s+/g, ' ').slice(0, 200); + } + return null; +} + +function extractDisplayNameZh(frontmatter, body) { + // Try existing name first (if it's Chinese, use it as displayName) + if (frontmatter.name && /[\u3400-\u9FFF]/.test(frontmatter.name)) { + return String(frontmatter.name).trim(); + } + // Else grab the first H1's text + const h1 = body.match(/^#\s+(.+)$/m); + if (h1) return h1[1].trim().slice(0, 32); + return null; +} + +function enrichFrontmatter(original, body, classifyResult, targetName) { + const fm = { ...original }; + // The output directory name is the source of truth for the kebab-case + // name. openclaw skills often have CJK or inconsistent names; we ignore + // those and use the ASCII dir name from --out. + const name = targetName || kebab(fm.name || 'unnamed-skill'); + fm.name = name; + + // description: ensure it has a trigger phrase + let desc = typeof fm.description === 'string' ? fm.description : (fm.description || ''); + desc = desc.replace(/\s+/g, ' ').trim(); + if (!desc) { + const para = body.split(/\r?\n\r?\n/)[0] || ''; + desc = para.replace(/^#+\s*/, '').replace(/\s+/g, ' ').trim().slice(0, 200); + } + if (!TRIGGER_RE.test(desc)) { + desc = `${TRIGGER_PHRASES[1]}${desc}`; + } + if (!desc.endsWith('.')) desc += '.'; + fm.description = desc; + + // Locale (only emit keys if we actually have content) + const zhSummary = extractChineseSummary(body); + const displayZh = extractDisplayNameZh(original, body); + if (zhSummary) { + fm.descriptions = fm.descriptions || {}; + fm.descriptions['zh-Hans'] = zhSummary; + } + if (displayZh) { + fm.displayNames = fm.displayNames || {}; + fm.displayNames['zh-Hans'] = displayZh; + } + + // Metadata hints + fm.metadata = fm.metadata || {}; + fm.metadata['openclaw_compat'] = true; + fm.metadata['skill-bridge'] = { + classify_tier: classifyResult.tier, + classify_subtier: classifyResult.subTier, + classify_reason: classifyResult.reason, + }; + + return fm; +} + +function addOutputContractSection(body) { + if (/^##\s+Output contract/m.test(body)) return body; + return body.trimEnd() + '\n\n## Output contract\n\nThis skill does not produce files by itself; the converted openclaw skill should declare its outputs in a new section here. (Filled in by the user after first run.)\n'; +} + +function addFailureHandlingSection(body) { + if (/^##\s+Failure handling/m.test(body)) return body; + return body.trimEnd() + '\n\n## Failure handling\n\nIf a required external tool or path is missing, surface the exact missing identifier to the user instead of guessing. Do not auto-install system packages. (Add skill-specific failure modes here.)\n'; +} + +function addWindowsNotesSection(body, hasShell) { + if (!hasShell) return body; + if (/^##\s+Windows \(win32\) platform notes/m.test(body)) return body; + return body.trimEnd() + '\n\n## Windows (win32) platform notes\n\nThe original openclaw skill assumed macOS/Linux shell. The PowerShell equivalents for any `bash`/`pip`/`python3` calls should be documented here. (Generated by skill-bridge; user to verify.)\n'; +} + +function maybeSplitReferences(name, body) { + // v0.1 simple split: if body > 500 lines AND has clearly demarcated + // sub-sections (## ...), move the later ones into references/. + const lines = body.split(/\r?\n/); + if (lines.length <= MAX_BODY_LINES) return { body, references: [] }; + + const sections = []; + let intro = []; + let current = null; + for (const line of lines) { + if (/^##\s+/.test(line)) { + if (current) sections.push(current); + else if (intro.length) sections.push({ heading: '__intro__', lines: intro }); + current = { heading: line, lines: [line] }; + } else if (current) { + current.lines.push(line); + } else { + intro.push(line); + } + } + if (current) sections.push(current); + else if (intro.length) sections.push({ heading: '__intro__', lines: intro }); + + if (sections.length < 3) return { body, references: [] }; + + // Keep the first 2 sections (intro + first ## heading) in body, move the rest. + const keep = sections.slice(0, 2).map(s => s.lines.join('\n')).join('\n\n'); + const moved = sections.slice(2); + const references = moved.map(s => { + const slug = s.heading + .replace(/^##\s+/, '') + .replace(/[^\w\u3400-\u9FFF-]+/g, '-') + .replace(/^-+|-+$/g, '') + .toLowerCase() + .slice(0, 64) || 'section'; + return { + file: `${slug}.md`, + content: s.lines.join('\n'), + }; + }); + return { body: keep.trimEnd() + '\n', references }; +} + +/** + * @param {object} args + * @param {string} args.inputPath + * @param {import('./analyze.js').AnalyzedSkill} args.report + * @param {ClassifyResult} args.classify + * @param {string} args.outDir + * @returns {Promise<{ written: string[], warnings: string[] }>} + */ +export async function transformSkill({ inputPath, report, classify, outDir }) { + const warnings = []; + const written = []; + + // 1. Parameterize paths in body + const { text: bodyAfterPaths, changes: pathChanges } = parameterizePaths(report.body); + if (pathChanges.length > 0) { + warnings.push(`paths parameterized: ${pathChanges.map(c => c.id).join(', ')}`); + } + + // 2. Detect shell-style commands to decide if Windows notes are needed + const hasShell = /\b(pip|python3?|curl|wget|bash|cli-anything-)/.test(bodyAfterPaths); + + // 3. Maybe split into references/ + const { body: bodySplit, references } = maybeSplitReferences(report.frontmatter.name || '', bodyAfterPaths); + + // 4. Add the missing sections + let finalBody = bodySplit; + finalBody = addOutputContractSection(finalBody); + finalBody = addFailureHandlingSection(finalBody); + finalBody = addWindowsNotesSection(finalBody, hasShell); + + // 5. Enrich frontmatter (target name = basename of outDir so name matches dir) + const targetName = path.basename(outDir); + const enrichedFm = enrichFrontmatter(report.frontmatter, finalBody, classify, targetName); + + // 6. Serialize + const fmYaml = yaml.dump(enrichedFm, { lineWidth: 100, noRefs: true, sortKeys: false }); + const skillText = `---\n${fmYaml}---\n\n${finalBody.trimStart()}`; + + // 7. Write + await fs.mkdir(outDir, { recursive: true }); + const skillOut = path.join(outDir, 'SKILL.md'); + await fs.writeFile(skillOut, skillText, 'utf-8'); + written.push(skillOut); + + for (const ref of references) { + const refPath = path.join(outDir, 'references', ref.file); + await fs.mkdir(path.dirname(refPath), { recursive: true }); + await fs.writeFile(refPath, ref.content.trim() + '\n', 'utf-8'); + written.push(refPath); + } + + // 8. Conversion report + const reportMd = renderConversionReport({ inputPath, classify, pathChanges, written, warnings }); + const reportPath = path.join(outDir, 'conversion-report.md'); + await fs.writeFile(reportPath, reportMd, 'utf-8'); + written.push(reportPath); + + return { written, warnings }; +} + +function renderConversionReport({ inputPath, classify, pathChanges, written, warnings }) { + return [ + `# Conversion report`, + ``, + `- **input**: \`${inputPath}\``, + `- **tier**: ${classify.tier} / ${classify.subTier}`, + `- **reason**: ${classify.reason}`, + ``, + `## Path changes`, + pathChanges.length === 0 + ? `_none_` + : pathChanges.map(c => `- \`${c.id}\` → \${${c.placeholder}} (${c.count}x)`).join('\n'), + ``, + `## Written files`, + written.map(f => `- \`${f}\``).join('\n'), + ``, + `## Recommendations`, + classify.recommendations.map(r => `- ${r}`).join('\n'), + ``, + `## Warnings`, + warnings.length === 0 ? `_none_` : warnings.map(w => `- ${w}`).join('\n'), + ``, + `_generated by skill-bridge v0.1.0 on ${new Date().toISOString()}_`, + ``, + ].join('\n'); +} diff --git a/plugins/antianqi/skill-bridge/package-lock.json b/plugins/antianqi/skill-bridge/package-lock.json new file mode 100644 index 0000000..ea9ced8 --- /dev/null +++ b/plugins/antianqi/skill-bridge/package-lock.json @@ -0,0 +1,63 @@ +{ + "name": "skill-bridge", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "iconv-lite": "^0.7.3", + "js-yaml": "^5.3.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/js-yaml": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.3.0.tgz", + "integrity": "sha512-muutsYr+e2+d3rTgUGslq5rxbBlUy3cJ61IsHag2QNDQV+7zXWjkUpmALIajhrlLlrgRUiymj6U3zUr/TMK84Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.mjs" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + } + } +} diff --git a/plugins/antianqi/skill-bridge/package.json b/plugins/antianqi/skill-bridge/package.json new file mode 100644 index 0000000..f90575a --- /dev/null +++ b/plugins/antianqi/skill-bridge/package.json @@ -0,0 +1,42 @@ +{ + "name": "skill-bridge", + "version": "0.1.0", + "description": "Convert openclaw (and similar) skills into mavis/mcode-compatible skills or plugins.", + "type": "module", + "main": "index.js", + "bin": { + "mcode-skill-bridge": "index.js" + }, + "author": "antianqi", + "scripts": { + "test": "node --test tests/*.test.mjs", + "demo:task-tracker": "node index.js convert examples/input/task-tracker --out examples/output/task-tracker", + "demo:investor-brand-kit": "node index.js convert examples/input/investor-brand-kit --out examples/output/investor-brand-kit", + "demo:self-improving-agent": "node index.js convert examples/input/self-improving-agent --out examples/output/self-improving-agent", + "demo:all": "npm run demo:task-tracker && npm run demo:investor-brand-kit && npm run demo:self-improving-agent" + }, + "engines": { + "node": ">=22.19 <23 || >=24 <27" + }, + "dependencies": { + "iconv-lite": "^0.6.3", + "js-yaml": "^4.1.0" + }, + "files": [ + "index.js", + "lib/", + "skills/", + "references/", + "README.md", + "LICENSE" + ], + "license": "MIT", + "keywords": [ + "mcode", + "mavis", + "skill", + "openclaw", + "migration", + "converter" + ] +} diff --git a/plugins/antianqi/skill-bridge/plugin.json b/plugins/antianqi/skill-bridge/plugin.json new file mode 100644 index 0000000..7a8e2c8 --- /dev/null +++ b/plugins/antianqi/skill-bridge/plugin.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "skill-bridge", + "version": "0.1.0", + "description": "Convert openclaw (and similar) skills into mavis/mcode-compatible skills or plugins. Detects encoding, parameterizes hardcoded paths, enriches frontmatter, runs the official lint, and produces a portable Skill-only Agent Plugin.", + "author": { + "name": "antianqi", + "url": "https://github.com/antianqi" + }, + "homepage": "https://github.com/antianqi/skill-bridge", + "repository": "https://github.com/antianqi/skill-bridge.git", + "license": "MIT", + "keywords": ["mcode", "mavis", "openclaw", "skill-migration", "converter"] +} \ No newline at end of file diff --git a/plugins/antianqi/skill-bridge/references/compatibility-matrix.md b/plugins/antianqi/skill-bridge/references/compatibility-matrix.md new file mode 100644 index 0000000..53f6287 --- /dev/null +++ b/plugins/antianqi/skill-bridge/references/compatibility-matrix.md @@ -0,0 +1,70 @@ +# Compatibility Matrix — openclaw skills → mavis tiers + +This table maps every openclaw skill we know about into the three-tier model. It is generated by running `mcode-skill-bridge classify` against each source and is updated whenever the upstream openclaw workspace changes. + +## Tier legend + +- **pure-translate** — frontmatter enrichment only, body is fine as-is +- **pure-wrapped-fix** — frontmatter + path parameterization + encoding fix + Windows notes +- **wrapped-\*** — needs an external CLI / API; **not in v0.1** +- **abandon** — openclaw-only assumptions can't be removed; **do not import** + +## The 36 openclaw skills + +| Skill | Tier (v0.1) | Why | +|---|---|---| +| `task-tracker` | pure-wrapped-fix | has hardcoded `${OPENCLAW_WORKSPACE}/TASKS.md` | +| `investor-brand-kit` | pure-translate | pure knowledge, no paths | +| `self-improving-agent` | pure-wrapped-fix | 600+ line body, splits into references; one hardcoded path | +| `identity-state-updater` | wrapped (feishu) | references feishu API | +| `skill-vetter` | wrapped (python) | references `python3` | +| `auto-memory-extract` | wrapped (memory I/O) | depends on openclaw memory paths | +| `history_compressor` | wrapped (LLM call) | assumes a particular LLM tool | +| `magic-docs` | wrapped (python) | DOCX tooling via Python | +| `skill_orchestrator` | wrapped (openclaw runtime) | uses openclaw hook system | +| `skill_hooks` | wrapped (openclaw runtime) | openclaw-only | +| `execution_logger` | wrapped (openclaw runtime) | openclaw-only | +| `short-drama` (短剧生成) | wrapped (ComfyUI) | hardcoded ComfyUI workflow JSON paths | +| `comfyui-cli` | wrapped (cli-anything) | depends on `cli-anything-comfyui` | +| `comfyui-outfit` | wrapped (ComfyUI) | same as above | +| `esp32` | wrapped (ESP32 toolchain) | depends on esptool.py | +| `esp32-voice-assistant` | wrapped (ESP32 + TTS) | same as above | +| `multi-engine-tts` | wrapped (TTS services) | 5+ external TTS APIs | +| `minimax-tts` | wrapped (TTS) | single TTS API | +| `minimax-tokenplan-image-generation` | wrapped (image API) | external service | +| `mmx-search` | wrapped (search) | depends on minimax-search backend | +| `flux-fill` | wrapped (ComfyUI) | image gen via ComfyUI | +| `reverse-prompt-selfie` | wrapped (ComfyUI) | same as above | +| `douyin-video` | wrapped (Douyin) | external service | +| `douyin-video-analysis` | wrapped (Douyin) | external service | +| `douyin-search` | wrapped (Douyin) | external service | +| `douyin-keyword-search` | wrapped (Douyin) | external service | +| `douyin-hot-trend` | wrapped (Douyin) | external service | +| `douyin剪辑` (douyin-editor) | wrapped (Douyin + ComfyUI) | both | +| `douyin剪辑_new` | wrapped (Douyin + ComfyUI) | both | +| `cloudbase` | wrapped (CloudBase) | Tencent Cloud SDK | +| `browser-automation` | wrapped (browser-automation tool) | external CLI | +| `franchisee-deviation-audit` | wrapped (internal CRM) | assumes specific customer DB | +| `weekly-data-stat` | wrapped (internal pipeline) | depends on openclaw daily-data | +| `daily-data-stat` | wrapped (internal pipeline) | same as above | +| `video-color-grade` | wrapped (FFmpeg) | external binary | +| `cli-anything` | pure-translate | it's a methodology, not a tool dep | + +## Coverage in v0.1 + +Of 36 skills: + +- **3 demos** actually converted: `task-tracker`, `investor-brand-kit`, `self-improving-agent` +- **3 pure** but not yet demoed: see above (any 3 are trivial to add) +- **30 wrapped** — needs v0.2 +- **0 abandon** — none of the surveyed skills are unsalvageable, just heavy + +## How to extend + +To add a new skill to this matrix, run: + +```bash +mcode-skill-bridge classify /path/to/openclaw/skills/ --json +``` + +and append the result to this file. The decision tree is in `lib/classify.js`; if a new pattern emerges (e.g. "depends on a specific .NET runtime"), add it to the patterns in `lib/analyze.js` and extend the decision tree. diff --git a/plugins/antianqi/skill-bridge/references/encoding-tables.md b/plugins/antianqi/skill-bridge/references/encoding-tables.md new file mode 100644 index 0000000..9bf54e9 --- /dev/null +++ b/plugins/antianqi/skill-bridge/references/encoding-tables.md @@ -0,0 +1,56 @@ +# Encoding Tables + +> v0.1 status: the converter only distinguishes **UTF-8** vs **GBK**. We do not +> maintain a static GBK→Unicode table; we use `iconv-lite` for full-table +> decode when needed. +> +> This document explains the detection algorithm so future contributors can +> extend it to GB2312, Big5, etc. + +## How detection works + +1. Read the file as raw bytes. +2. Try strict UTF-8 decode (no replacement chars = success). +3. Else try `iconv-lite` GBK decode. If it yields CJK characters without replacement chars, the source is GBK → re-decode and continue. +4. Else: declare `unknown`; leave as lossy UTF-8; warn the user. + +## Why not `chardet`? + +`chardet` (and `franc` for languages) is a probabilistic library. In our use case the false-positive cost is high: silently mis-decoding a SKILL.md produces a skill that loads but contains garbled instructions. The "two passes, prefer the one with no replacement chars" approach has a low false-positive rate for the binary-clean files we care about. + +## GBK vs GB18030 vs GB2312 + +GB18030 is a superset of GBK which is a superset of GB2312. `iconv-lite` supports GBK and GB18030 out of the box; we use GBK because that's what we observed in the openclaw workspace dumps. If you see GB18030-only files (rare), switch the encoding name in `lib/detect.js`. + +## Filename mojibake + +GBK **filenames** (vs GBK **file contents**) are a separate, harder problem: + +- A GBK-encoded filename is stored as raw bytes on disk (NTFS / ext4 store bytes; the encoding is only a convention). +- Reading a directory listing via `Get-ChildItem` (PowerShell) returns names in the **system code page** on Windows (CP936 for Chinese systems) — and loses information if the system code page is different. +- There is no "GBK filename to UTF-8 filename" mapping without a complete byte-level decode of the directory. + +For v0.1, we do NOT rename files. We surface the warning and let the user rename manually: + +``` +$ mcode-skill-bridge suggest-filename '�̾�����.md' +``` + +(planned for v0.2; for now, the CLI's `analyze` command flags the directory listing.) + +## Extending + +To add support for a new encoding: + +1. Add the encoding name to `lib/detect.js`: + ```js + if (iconv.encodingExists('big5')) { + // try Big5 decode + } + ``` +2. Add a fixture under `tests/fixtures/encoding/big5.txt` and a test in `tests/detect.test.mjs`. +3. Update this document. + +## Why we don't bundle a GBK table + +`iconv-lite`'s GBK table is ~50KB compressed. Bundling our own would double the package size for a single encoding. If `iconv-lite` ever stops working for us, we can ship a minimal table covering the GBK basic range (0x8140-0xFEFE, ~21000 entries) as a separate npm package. diff --git a/plugins/antianqi/skill-bridge/references/path-patterns.md b/plugins/antianqi/skill-bridge/references/path-patterns.md new file mode 100644 index 0000000..36b0f47 --- /dev/null +++ b/plugins/antianqi/skill-bridge/references/path-patterns.md @@ -0,0 +1,60 @@ +# Path Patterns + +This document describes the hardcoded path patterns that `skill-bridge` recognizes and replaces, the placeholder variables used, and how downstream code should resolve them at runtime. + +## Placeholders + +| Placeholder | Meaning | Default suggested value | +|---|---|---| +| `${OPENCLAW_HOME}` | openclaw root dir (where the user kept `.openclaw/`) | unset — user must set | +| `${OPENCLAW_WORKSPACE}` | openclaw workspace (typically `${OPENCLAW_HOME}/workspace`) | unset — user must set | +| `${SCRATCH}` | OS-appropriate scratch dir | `os.tmpdir()` | +| `${DATA_DIR}` | mavis data dir | `~/.minimax` | + +## The 6 rules (in priority order) + +```js +// 1. openclaw workspace — most specific, checked first +{C:\Users\Administrator\.openclaw[/\]workspace[/\]? + → ${OPENCLAW_WORKSPACE}/} + +// 2. openclaw home (any other subdir) +{C:\Users\Administrator\.openclaw[/\]? + → ${OPENCLAW_HOME}/} + +// 3. tilde form +{~/.openclaw/ + → ${OPENCLAW_HOME}/} + +// 4. CLI-Anything scratch +{/tmp/CLI-Anything/ + → ${SCRATCH}/cli-anything/} + +// 5. generic /tmp +{/(?![\w/])/tmp/ + → ${SCRATCH}/} + +// 6. mavis data dir +{C:\Users\Administrator\.minimax[/\]? + → ${DATA_DIR}/} +``` + +The order matters: rule 1 must run before rule 2, otherwise `workspace/` would be replaced with `${OPENCLAW_HOME}/workspace/` and then re-matched by rule 1, leaving a double placeholder. + +After all rules run, a post-pass collapses runs of slashes that may appear at the boundary between the placeholder and what was originally the separator — e.g. `${OPENCLAW_HOME}//workspace/foo.md` becomes `${OPENCLAW_HOME}/workspace/foo.md`. + +## Why we don't auto-resolve the placeholders + +`OPENCLAW_HOME` is genuinely environment-specific. We don't pretend to know where the user's old openclaw workspace is on a new machine. The skill body says `${OPENCLAW_HOME}/workspace/TASKS.md` and downstream code (or the user) fills in the env var at runtime. + +For users who don't have an openclaw workspace anymore, the path is effectively dead and the skill should be rewritten to not depend on it. This is a content decision, not a tool decision. + +## Extending the rules + +If you have a new pattern (e.g. a hardcoded `/home/foo/claude/` from another framework), add it to `lib/paths.js` `PATH_RULES`. Order matters: more specific patterns go first. Re-run the tests: + +```bash +node --test tests/paths.test.mjs +``` + +Add a test for the new pattern in the same file before opening a PR. diff --git a/plugins/antianqi/skill-bridge/skills/skill-bridge/SKILL.md b/plugins/antianqi/skill-bridge/skills/skill-bridge/SKILL.md new file mode 100644 index 0000000..aa37b42 --- /dev/null +++ b/plugins/antianqi/skill-bridge/skills/skill-bridge/SKILL.md @@ -0,0 +1,107 @@ +--- +name: skill-bridge +description: | + Convert an openclaw (or similar) skill folder into a mavis/mcode-compatible + skill via the bundled `mcode-skill-bridge` CLI. Use when the user wants to + migrate a skill from openclaw, reuse a skill from another framework, or + port a hand-written skill that doesn't follow the mavis schema. Do NOT use + to create a brand-new skill from scratch (use `skill-creator` instead), + or to lint/refine an existing mavis skill (use `skill-refiner`). +descriptions: + zh-Hans: | + 通过内置的 `mcode-skill-bridge` CLI,把 openclaw(或类似框架)的 skill + 转换为 mavis/mcode 兼容的 skill。需要迁移/移植/复用 skill 时使用。 +displayNames: + zh-Hans: Skill 移植桥 +metadata: + openclaw_compat: true + auto-invoke: "" +--- + +# skill-bridge + +Bring a non-mavis skill into the mavis world. The skill itself is the **thin LLM-facing layer**; the heavy lifting lives in the CLI `mcode-skill-bridge` (also shipped in this plugin). + +## When to use this skill + +- The user has an `openclaw` workspace (or any non-mavis skill bundle) and wants to use those skills inside mavis. +- The user found a skill on GitHub written in a different agent framework and wants to reuse it. +- The user wrote a SKILL.md themselves years ago and wants to bring it up to mavis's current schema. + +Do **not** use this skill for: + +- Creating a new skill from scratch → `skill-creator` +- Fixing or refining an existing mavis skill → `skill-refiner` +- Listing what skills are available → just read `` from the system prompt + +## Inputs to collect + +- **Source path**: an absolute path to either a skill folder (containing `SKILL.md`) or directly to a `SKILL.md` file. If the user gave a relative path, resolve it. +- **Output path (optional)**: where to write the converted skill. Default: `./out/` next to the CLI invocation cwd. If the user names a scope (user/agent/project), use: + - user → `~/.minimax/skills//` + - agent → `~/.minimax/agents/mavis/skills//` + - project → `/.minimax/skills//` +- **Force overwrite (optional)**: only if the target already exists and the user confirmed. + +## Procedure + +1. **Detect** the source. + - Run `mcode-skill-bridge detect `. + - If encoding is `unknown`, warn the user before continuing. + - If encoding is `gbk` and was converted, mention that the original was GBK and we restored it. + +2. **Analyze** for the full report. + - Run `mcode-skill-bridge analyze `. + - Check `hardcoded paths` and `external commands` counts. + - If `external commands` is non-empty, the skill is likely `wrapped-*` (v0.1 only emits `pure`; tell the user and stop). + +3. **Classify**. + - Run `mcode-skill-bridge classify `. + - Note `tier` and `subTier`. In v0.1, proceed only if `tier == "pure"`. + +4. **Convert**. + - Run `mcode-skill-bridge convert --out `. + - If the target exists and the user didn't say `--force`, stop and ask. + - After the CLI writes files, read `/conversion-report.md` and surface the warnings to the user. + - Read `/SKILL.md` and skim it. If anything looks wrong (missing section, garbled encoding, broken path), tell the user **before** claiming success. + +5. **Lint** (optional but recommended). + - The CLI runs lint by default. If `--no-lint` was passed, run it manually: + `mcode-skill-bridge lint `. + - Lint `WARN` is OK; `FAIL` means do not claim the conversion is done. + +6. **Tell the user** what was written, what to review, and how to use the new skill. Suggest `skill({name: ""})` to verify it loads. + +## Output contract + +- A directory at the chosen target path containing at minimum: + - `SKILL.md` — mavis-schema-compliant + - `conversion-report.md` — what was changed + - optionally `references/.md` if the body was split + +## Failure handling + +- `tier: abandon` from classify → do not write; explain the reason to the user. +- `tier: wrapped` in v0.1 → tell the user the CLI only supports `pure` right now; point to plan §6 (v0.2 will add `wrapped`). +- Lint FAIL → do not claim success; show the lint output verbatim. +- Encoding `unknown` → ask the user to confirm the source is genuinely UTF-8 before writing. +- Target already exists without `--force` → stop, ask the user. + +## Examples + +**Input**: `/path/to/openclaw/skills/task-tracker` + +**Good path**: +1. `mcode-skill-bridge detect /path/to/openclaw/skills/task-tracker` → utf-8, no replacement +2. `mcode-skill-bridge classify ...` → `pure / pure-wrapped-fix` (one hardcoded path group) +3. `mcode-skill-bridge convert /path/to/openclaw/skills/task-tracker --out ~/.minimax/agents/mavis/skills/task-tracker` +4. Confirm lint passed, surface 2 warnings about path parameterization. + +**Bad path**: copy the SKILL.md to `~/.minimax/agents/mavis/skills//` directly. The user's previous attempt at this failed because (a) the path is not in the scan list and (b) GBK content was not detected. + +## Additional resources + +- `references/compatibility-matrix.md` — known openclaw skills and their tier +- `references/path-patterns.md` — the hardcoded path patterns we replace +- The CLI itself: `mcode-skill-bridge --help` +- The plan that produced this skill: see the GitHub repo's `docs/PLAN.md` (v0.1 ships with the plan inline in `README.md`). diff --git a/plugins/antianqi/skill-bridge/tests/classify.test.mjs b/plugins/antianqi/skill-bridge/tests/classify.test.mjs new file mode 100644 index 0000000..ceef306 --- /dev/null +++ b/plugins/antianqi/skill-bridge/tests/classify.test.mjs @@ -0,0 +1,70 @@ +// tests/classify.test.mjs +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { classify } from '../lib/classify.js'; + +function report(overrides = {}) { + return { + inputPath: 'fake', + encoding: 'utf-8', + convertedFromGbk: false, + frontmatter: {}, + body: '', + hardcodedPaths: [], + externalCommands: [], + warnings: [], + ...overrides, + }; +} + +test('pure-translate: clean instruction, no paths, no external tools', () => { + const r = classify(report()); + assert.equal(r.tier, 'pure'); + assert.equal(r.subTier, 'pure-translate'); +}); + +test('pure-wrapped-fix: has hardcoded Windows path', () => { + const r = classify(report({ + hardcodedPaths: [{ label: 'absolute Windows user path', samples: ['C:\\Users\\Administrator\\.openclaw\\'] }], + })); + assert.equal(r.tier, 'pure'); + assert.equal(r.subTier, 'pure-wrapped-fix'); +}); + +test('pure-wrapped-fix: GBK source was converted', () => { + const r = classify(report({ encoding: 'gbk', convertedFromGbk: true })); + assert.equal(r.subTier, 'pure-wrapped-fix'); + assert.ok(/gbk/i.test(r.reason)); +}); + +test('wrapped-python: pip install detected', () => { + const r = classify(report({ + externalCommands: [{ label: 'pip install', samples: ['pip install -e .'] }], + })); + assert.equal(r.tier, 'wrapped'); + assert.equal(r.subTier, 'wrapped-python'); +}); + +test('wrapped-cli-anything: CLI tool detected', () => { + const r = classify(report({ + externalCommands: [{ label: 'cli-anything CLI', samples: ['cli-anything-comfyui'] }], + })); + assert.equal(r.tier, 'wrapped'); + assert.equal(r.subTier, 'wrapped-cli-anything'); +}); + +test('wrapped-service: ComfyUI reference', () => { + const r = classify(report({ + externalCommands: [{ label: 'ComfyUI reference', samples: ['ComfyUI'] }], + })); + assert.equal(r.tier, 'wrapped'); + assert.equal(r.subTier, 'wrapped-service'); +}); + +test('wrapped-http: curl detected', () => { + const r = classify(report({ + externalCommands: [{ label: 'curl', samples: ['curl '] }], + })); + assert.equal(r.tier, 'wrapped'); + assert.equal(r.subTier, 'wrapped-http'); +}); diff --git a/plugins/antianqi/skill-bridge/tests/cli.test.mjs b/plugins/antianqi/skill-bridge/tests/cli.test.mjs new file mode 100644 index 0000000..030c9c3 --- /dev/null +++ b/plugins/antianqi/skill-bridge/tests/cli.test.mjs @@ -0,0 +1,67 @@ +// tests/cli.test.mjs +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import fs from 'node:fs/promises'; +import os from 'node:os'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const CLI = path.join(__dirname, '..', 'index.js'); + +function run(args, opts = {}) { + return new Promise((resolve) => { + const child = spawn(process.execPath, [CLI, ...args], { + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + ...opts, + }); + let stdout = ''; + let stderr = ''; + child.stdout.on('data', d => stdout += d); + child.stderr.on('data', d => stderr += d); + child.on('close', (code) => resolve({ code, stdout, stderr })); + }); +} + +test('--help prints usage', async () => { + const r = await run(['--help']); + assert.equal(r.code, 0); + assert.ok(/mcode-skill-bridge/.test(r.stdout)); + assert.ok(/Usage:/.test(r.stdout)); +}); + +test('detect command on utf-8 file', async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-cli-')); + const file = path.join(dir, 'SKILL.md'); + await fs.writeFile(file, '---\nname: x\ndescription: y\n---\n\n# X\n', 'utf-8'); + const r = await run(['detect', file]); + assert.equal(r.code, 0); + assert.ok(/encoding:\s+utf-8/.test(r.stdout), `got: ${r.stdout}`); + await fs.rm(dir, { recursive: true, force: true }); +}); + +test('classify command on a pure-instruction skill', async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-cli-')); + const file = path.join(dir, 'SKILL.md'); + await fs.writeFile(file, '---\nname: y\ndescription: "A pure skill."\n---\n\n# Y\n\nJust instructions.\n', 'utf-8'); + const r = await run(['classify', file]); + assert.equal(r.code, 0); + assert.ok(/tier:\s+pure/.test(r.stdout), `got: ${r.stdout}`); + await fs.rm(dir, { recursive: true, force: true }); +}); + +test('convert writes output', async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-cli-')); + const file = path.join(dir, 'SKILL.md'); + await fs.writeFile(file, + '---\nname: demo-skill\ndescription: "Demo."\n---\n\n# Demo\n\nUse /tmp/x for cache.\n', 'utf-8'); + const out = path.join(dir, 'out'); + const r = await run(['convert', file, '--out', out, '--no-lint']); + assert.equal(r.code, 0, `stderr: ${r.stderr}\nstdout: ${r.stdout}`); + const written = await fs.readdir(out); + assert.ok(written.includes('SKILL.md')); + assert.ok(written.includes('conversion-report.md')); + await fs.rm(dir, { recursive: true, force: true }); +}); diff --git a/plugins/antianqi/skill-bridge/tests/detect.test.mjs b/plugins/antianqi/skill-bridge/tests/detect.test.mjs new file mode 100644 index 0000000..59795ae --- /dev/null +++ b/plugins/antianqi/skill-bridge/tests/detect.test.mjs @@ -0,0 +1,50 @@ +// tests/detect.test.mjs +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import iconv from 'iconv-lite'; +import { detectEncoding, isLikelyGbkMojibake } from '../lib/detect.js'; + +test('UTF-8 clean ASCII', () => { + const r = detectEncoding(Buffer.from('hello world', 'utf-8')); + assert.equal(r.encoding, 'utf-8'); + assert.equal(r.replaced, false); + assert.equal(r.text, 'hello world'); +}); + +test('UTF-8 clean Chinese', () => { + const r = detectEncoding(Buffer.from('你好世界', 'utf-8')); + assert.equal(r.encoding, 'utf-8'); + assert.equal(r.text, '你好世界'); +}); + +test('GBK round-trip is detected as gbk', () => { + const original = '短剧生成工作流'; + const buf = iconv.encode(original, 'gbk'); + const r = detectEncoding(buf); + assert.equal(r.encoding, 'gbk'); + assert.equal(r.replaced, true); + assert.equal(r.text, original); +}); + +test('Unknown bytes fall through to lossy utf-8', () => { + // Random binary that is neither valid UTF-8 nor valid GBK CJK + const buf = Buffer.from([0xff, 0xfe, 0x00, 0x01, 0x80, 0x90, 0xa0, 0xb0]); + const r = detectEncoding(buf); + assert.ok(['unknown', 'gbk'].includes(r.encoding), 'should not falsely claim utf-8'); +}); + +test('Mixed file (mostly UTF-8 with a stray GBK chunk) is still utf-8', () => { + const utf8 = 'Normal text. '; + const gbk = iconv.encode('中文段落', 'gbk'); + const combo = Buffer.concat([Buffer.from(utf8, 'utf-8'), gbk]); + const r = detectEncoding(combo); + // The GBK chunk produces replacement chars, but the start is clean UTF-8. + // We expect either utf-8 (if the regex thinks it's still OK) or gbk (if + // CJK presence wins). Either way, we should not be 'unknown'. + assert.notEqual(r.encoding, 'unknown'); +}); + +test('isLikelyGbkMojibake detects U+FFFD cluster', () => { + assert.equal(isLikelyGbkMojibake('xxx ���� xxx'), true); + assert.equal(isLikelyGbkMojibake('正常中文'), false); +}); diff --git a/plugins/antianqi/skill-bridge/tests/paths.test.mjs b/plugins/antianqi/skill-bridge/tests/paths.test.mjs new file mode 100644 index 0000000..c6664c2 --- /dev/null +++ b/plugins/antianqi/skill-bridge/tests/paths.test.mjs @@ -0,0 +1,60 @@ +// tests/paths.test.mjs +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { parameterizePaths, suggestFilename, PATH_RULES } from '../lib/paths.js'; + +test('parameterizePaths replaces openclaw home', () => { + // Use a path that hits the home rule (not the more specific workspace rule) + const r = parameterizePaths('read C:\\Users\\Administrator\\.openclaw\\config\\foo.md'); + assert.ok(r.text.includes('${OPENCLAW_HOME}')); + assert.ok(r.changes.some(c => c.id === 'openclaw-home')); +}); + +test('parameterizePaths prefers openclaw-workspace when workspace/ is present', () => { + const r = parameterizePaths('read C:\\Users\\Administrator\\.openclaw\\workspace\\foo.md'); + assert.ok(r.text.includes('${OPENCLAW_WORKSPACE}')); + assert.ok(r.text.includes('foo.md')); + assert.ok(!r.text.includes('${OPENCLAW_HOME}')); +}); + +test('parameterizePaths replaces /tmp/CLI-Anything once', () => { + const r = parameterizePaths('source: /tmp/CLI-Anything/gimp/agent-harness'); + assert.equal(r.text, 'source: ${SCRATCH}/cli-anything/gimp/agent-harness'); +}); + +test('parameterizePaths does not double-replace CLI-Anything', () => { + const r = parameterizePaths('cd /tmp/CLI-Anything/foo'); + // Should be ${SCRATCH}/cli-anything/foo, NOT ${SCRATCH}/${SCRATCH}/cli-anything/foo + assert.ok(!r.text.includes('${SCRATCH}/${SCRATCH}'), `got: ${r.text}`); + assert.ok(r.text.startsWith('cd ${SCRATCH}/cli-anything/foo')); +}); + +test('parameterizePaths generic /tmp', () => { + const r = parameterizePaths('cd /tmp/myscript.sh'); + assert.equal(r.text, 'cd ${SCRATCH}/myscript.sh'); +}); + +test('parameterizePaths returns empty changes for clean text', () => { + const r = parameterizePaths('pure text with no paths'); + assert.equal(r.changes.length, 0); + assert.equal(r.text, 'pure text with no paths'); +}); + +test('suggestFilename keeps ASCII names', () => { + const r = suggestFilename('task-tracker.md'); + assert.equal(r.recoverable, true); + assert.equal(r.name, 'task-tracker.md'); +}); + +test('suggestFilename flags mojibake names', () => { + const r = suggestFilename('�̾�����.md'); + assert.equal(r.recoverable, false); +}); + +test('PATH_RULES has stable ids', () => { + const ids = PATH_RULES.map(r => r.id); + assert.ok(new Set(ids).size === ids.length, 'ids must be unique'); + for (const id of ids) { + assert.ok(/^[a-z0-9-]+$/.test(id), `bad id: ${id}`); + } +}); diff --git a/plugins/antianqi/skill-bridge/tests/transform-skill.test.mjs b/plugins/antianqi/skill-bridge/tests/transform-skill.test.mjs new file mode 100644 index 0000000..c584d09 --- /dev/null +++ b/plugins/antianqi/skill-bridge/tests/transform-skill.test.mjs @@ -0,0 +1,105 @@ +// tests/transform-skill.test.mjs +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { transformSkill } from '../lib/transform-skill.js'; +import { parseFrontmatter } from '../lib/analyze.js'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import os from 'node:os'; + +const SAMPLE = `--- +name: test-skill +description: "A test skill for unit tests." +--- + +# Test Skill + +## Inputs to collect + +- A thing. + +## Procedure + +1. Do the thing. + +This skill uses C:\\Users\\Administrator\\.openclaw\\workspace\\foo.md +`; + +async function tmpdir() { + return await fs.mkdtemp(path.join(os.tmpdir(), 'sb-test-')); +} + +test('transformSkill writes SKILL.md and conversion-report.md', async () => { + const out = await tmpdir(); + // Use a stable outDir basename so the resulting name is deterministic. + const outDir = path.join(path.dirname(out), 'test-skill'); + const r = await transformSkill({ + inputPath: 'fake.md', + report: { + inputPath: 'fake.md', + encoding: 'utf-8', + convertedFromGbk: false, + frontmatter: { name: 'test-skill', description: 'A test skill for unit tests.' }, + body: SAMPLE.split('---\n').slice(2).join('---\n'), + warnings: [], + }, + classify: { tier: 'pure', subTier: 'pure-wrapped-fix', reason: 'has hardcoded path', recommendations: [] }, + outDir, + }); + assert.ok(r.written.some(f => f.endsWith('SKILL.md'))); + assert.ok(r.written.some(f => f.endsWith('conversion-report.md'))); + + const skill = await fs.readFile(path.join(outDir, 'SKILL.md'), 'utf-8'); + // Original path should be parameterized (workspace rule wins for this input) + assert.ok(skill.includes('${OPENCLAW_WORKSPACE}')); + // Required sections should be added + assert.ok(/^##\s+Output contract/m.test(skill)); + assert.ok(/^##\s+Failure handling/m.test(skill)); + // Frontmatter enrichment + const { frontmatter, body } = parseFrontmatter(skill); + assert.equal(frontmatter.name, 'test-skill'); + assert.equal(frontmatter.metadata['skill-bridge'].classify_tier, 'pure'); + await fs.rm(outDir, { recursive: true, force: true }); +}); + +test('transformSkill adds Windows notes when shell commands present', async () => { + const out = await tmpdir(); + const bodyWithShell = '## Procedure\n\nRun `pip install foo` and then `python3 main.py`.'; + await transformSkill({ + inputPath: 'fake.md', + report: { + inputPath: 'fake.md', + encoding: 'utf-8', + convertedFromGbk: false, + frontmatter: { name: 'shell-skill', description: 'shell skill' }, + body: bodyWithShell, + warnings: [], + }, + classify: { tier: 'pure', subTier: 'pure-wrapped-fix', reason: 'r', recommendations: [] }, + outDir: out, + }); + const skill = await fs.readFile(path.join(out, 'SKILL.md'), 'utf-8'); + assert.ok(/^##\s+Windows \(win32\) platform notes/m.test(skill), 'should add Windows section'); + await fs.rm(out, { recursive: true, force: true }); +}); + +test('transformSkill does NOT add Windows notes for pure prose', async () => { + const out = await tmpdir(); + const body = '## Procedure\n\nJust do the thing. No shell needed.'; + await transformSkill({ + inputPath: 'fake.md', + report: { + inputPath: 'fake.md', + encoding: 'utf-8', + convertedFromGbk: false, + frontmatter: { name: 'pure-skill', description: 'pure' }, + body, + warnings: [], + }, + classify: { tier: 'pure', subTier: 'pure-translate', reason: 'r', recommendations: [] }, + outDir: out, + }); + const skill = await fs.readFile(path.join(out, 'SKILL.md'), 'utf-8'); + assert.ok(!/^##\s+Windows \(win32\) platform notes/m.test(skill)); + await fs.rm(out, { recursive: true, force: true }); +}); From 64bc5dd8b393aa788ab588df07e735c895c010e7 Mon Sep 17 00:00:00 2001 From: antianqi <75944423+antianqi@users.noreply.github.com> Date: Sat, 15 Aug 2026 08:55:20 +0800 Subject: [PATCH 2/5] Address review blockers on PR #3 Fixes the 4 issues hetaoBackend raised in the CHANGES_REQUESTED review on hetaoBackend/MiniMax-Code-Plugins#3. 1. References index: when transform-skill splits a long body into references/.md, the generated SKILL.md now contains a `## References` section with markdown links to each split-off file. Without this, the moved content was unreachable from the body. 2. lint staging no longer touches the install dir: lib/lint.js's stageMjsInTmp() now stages the .mjs in a unique os.tmpdir() subdir and removes it in a finally block on every code path. The previous implementation wrote /.minimax/.builtin-skills/.../lint-skill.sb-lint.mjs, which polluted the user''s install area and had a TOCTOU race between concurrent runs. 3. Atomic outDir replace (--force safe): transform-skill now writes everything into a sibling .staging- directory first, then fs.rm(outDir) + fs.rename swaps it in. On any failure the staging dir is removed in finally. This means --force no longer leaves stale references/ from a previous run mixed into the new output. 4. Test coverage: 4 new tests added. - transformSkill adds a References index when body is split - transformSkill replaces outDir atomically (no stale references/) - lintSkill (subprocess path) stages the .mjs in os.tmpdir() - lintSkill (fast path) returns the lint-script failure faithfully Total: 33 tests, all passing on Node 24.18.0. Also: fix CJS/ESM interop on the fast path -- Node 22 puts CJS exports under mod.default.lint, not mod.lint. Demo outputs in examples/output/ regenerated to reflect the new References index (visible in self-improving-agent/SKILL.md). package-lock.json: realigned with package.json (iconv-lite ^0.6.3, js-yaml ^4.1.0) -- the previous lockfile was inconsistent with the manifest. --- .gitignore | 5 +- plugins/antianqi/skill-bridge/.gitignore | 4 +- .../investor-brand-kit/conversion-report.md | 6 +- .../output/self-improving-agent/SKILL.md | 28 ++++ .../self-improving-agent/conversion-report.md | 29 +--- .../output/task-tracker/conversion-report.md | 6 +- plugins/antianqi/skill-bridge/lib/lint.js | 87 ++++++++---- .../skill-bridge/lib/transform-skill.js | 79 ++++++++--- .../antianqi/skill-bridge/package-lock.json | 32 +++-- .../antianqi/skill-bridge/tests/lint.test.mjs | 102 +++++++++++++ .../tests/transform-skill.test.mjs | 134 ++++++++++++++++++ 11 files changed, 418 insertions(+), 94 deletions(-) create mode 100644 plugins/antianqi/skill-bridge/tests/lint.test.mjs diff --git a/.gitignore b/.gitignore index dab9e4c..8fd0a6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1 @@ -node_modules/ -.DS_Store -coverage/ -*.log +probe-*.mjs diff --git a/plugins/antianqi/skill-bridge/.gitignore b/plugins/antianqi/skill-bridge/.gitignore index 21542b7..451c6a4 100644 --- a/plugins/antianqi/skill-bridge/.gitignore +++ b/plugins/antianqi/skill-bridge/.gitignore @@ -1,2 +1,4 @@ node_modules/ -tests/last-run.log \ No newline at end of file +tests/last-run.log +tests/lint-debug.log +probe-*.mjs \ No newline at end of file diff --git a/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/conversion-report.md b/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/conversion-report.md index 2fbb08e..0b69f1f 100644 --- a/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/conversion-report.md +++ b/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/conversion-report.md @@ -1,6 +1,6 @@ # Conversion report -- **input**: `examples/input/investor-brand-kit/SKILL.md` +- **input**: `examples\input\investor-brand-kit\SKILL.md` - **tier**: pure / pure-translate - **reason**: pure instruction, ascii-clean, no hardcoded paths @@ -8,7 +8,7 @@ _none_ ## Written files -- `C:\Users\Administrator\skill-bridge\examples\output\investor-brand-kit\SKILL.md` + ## Recommendations - enrich frontmatter (descriptions.zh-Hans, displayNames.zh-Hans, metadata) @@ -18,4 +18,4 @@ _none_ ## Warnings _none_ -_generated by skill-bridge v0.1.0 on 2026-08-14T12:33:14.253Z_ +_generated by skill-bridge v0.1.0 on 2026-08-15T00:53:46.105Z_ diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/SKILL.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/SKILL.md index 577491d..02c3b93 100644 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/SKILL.md +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/SKILL.md @@ -38,6 +38,34 @@ Log learnings and errors to markdown files for continuous improvement. Coding ag | Tool gotchas | Promote to `TOOLS.md` (OpenClaw workspace) | | Behavioral patterns | Promote to `SOUL.md` (OpenClaw workspace) | +## References + +Detailed content moved out of this SKILL.md for size. Read these when the main flow above references them: + +- [`openclaw-setup-recommended.md`](references/openclaw-setup-recommended.md) +- [`generic-setup-other-agents.md`](references/generic-setup-other-agents.md) +- [`logging-format.md`](references/logging-format.md) +- [`lrn-yyyymmdd-xxx-category.md`](references/lrn-yyyymmdd-xxx-category.md) +- [`err-yyyymmdd-xxx-skill_or_command_name.md`](references/err-yyyymmdd-xxx-skill_or_command_name.md) +- [`feat-yyyymmdd-xxx-capability_name.md`](references/feat-yyyymmdd-xxx-capability_name.md) +- [`id-generation.md`](references/id-generation.md) +- [`resolving-entries.md`](references/resolving-entries.md) +- [`promoting-to-project-memory.md`](references/promoting-to-project-memory.md) +- [`build-dependencies.md`](references/build-dependencies.md) +- [`after-api-changes.md`](references/after-api-changes.md) +- [`recurring-pattern-detection.md`](references/recurring-pattern-detection.md) +- [`simplify-harden-feed.md`](references/simplify-harden-feed.md) +- [`periodic-review.md`](references/periodic-review.md) +- [`detection-triggers.md`](references/detection-triggers.md) +- [`priority-guidelines.md`](references/priority-guidelines.md) +- [`area-tags.md`](references/area-tags.md) +- [`best-practices.md`](references/best-practices.md) +- [`gitignore-options.md`](references/gitignore-options.md) +- [`hook-integration.md`](references/hook-integration.md) +- [`automatic-skill-extraction.md`](references/automatic-skill-extraction.md) +- [`multi-agent-support.md`](references/multi-agent-support.md) +- [`self-improvement.md`](references/self-improvement.md) + ## Output contract This skill does not produce files by itself; the converted openclaw skill should declare its outputs in a new section here. (Filled in by the user after first run.) diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/conversion-report.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/conversion-report.md index 5451887..2c34753 100644 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/conversion-report.md +++ b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/conversion-report.md @@ -1,6 +1,6 @@ # Conversion report -- **input**: `examples/input/self-improving-agent/SKILL.md` +- **input**: `examples\input\self-improving-agent\SKILL.md` - **tier**: pure / pure-wrapped-fix - **reason**: 1 hardcoded path group(s) found @@ -9,30 +9,7 @@ - `openclaw-home` → ${OPENCLAW_HOME} (2x) ## Written files -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\SKILL.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\openclaw-setup-recommended.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\generic-setup-other-agents.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\logging-format.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\lrn-yyyymmdd-xxx-category.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\err-yyyymmdd-xxx-skill_or_command_name.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\feat-yyyymmdd-xxx-capability_name.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\id-generation.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\resolving-entries.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\promoting-to-project-memory.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\build-dependencies.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\after-api-changes.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\recurring-pattern-detection.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\simplify-harden-feed.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\periodic-review.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\detection-triggers.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\priority-guidelines.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\area-tags.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\best-practices.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\gitignore-options.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\hook-integration.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\automatic-skill-extraction.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\multi-agent-support.md` -- `C:\Users\Administrator\skill-bridge\examples\output\self-improving-agent\references\self-improvement.md` + ## Recommendations - parameterize paths via paths.js @@ -42,4 +19,4 @@ ## Warnings - paths parameterized: openclaw-workspace, openclaw-home -_generated by skill-bridge v0.1.0 on 2026-08-14T12:33:14.360Z_ +_generated by skill-bridge v0.1.0 on 2026-08-15T00:53:46.223Z_ diff --git a/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md b/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md index a8aeccd..192f816 100644 --- a/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md +++ b/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md @@ -1,6 +1,6 @@ # Conversion report -- **input**: `examples/input/task-tracker/SKILL.md` +- **input**: `examples\input\task-tracker\SKILL.md` - **tier**: pure / pure-wrapped-fix - **reason**: 1 hardcoded path group(s) found @@ -8,7 +8,7 @@ - `openclaw-workspace` → ${OPENCLAW_WORKSPACE} (2x) ## Written files -- `C:\Users\Administrator\skill-bridge\examples\output\task-tracker\SKILL.md` + ## Recommendations - parameterize paths via paths.js @@ -18,4 +18,4 @@ ## Warnings - paths parameterized: openclaw-workspace -_generated by skill-bridge v0.1.0 on 2026-08-14T12:33:14.160Z_ +_generated by skill-bridge v0.1.0 on 2026-08-15T00:53:45.995Z_ diff --git a/plugins/antianqi/skill-bridge/lib/lint.js b/plugins/antianqi/skill-bridge/lib/lint.js index 111d3d2..e2b0948 100644 --- a/plugins/antianqi/skill-bridge/lib/lint.js +++ b/plugins/antianqi/skill-bridge/lib/lint.js @@ -10,20 +10,31 @@ // v0.1 uses the dynamic import path: read the file, write a temp // `.mjs` next to it, then dynamic-import that. This stays compatible // with all Node 22+ setups. +// +// IMPORTANT: the staged `.mjs` MUST NOT live in `~/.minimax/.builtin-skills/` +// or any other user-install location. We use a unique temp dir under +// `os.tmpdir()` and remove it in a `finally` block on every code path +// (success, lint failure, spawn error). import { spawn } from 'node:child_process'; import path from 'node:path'; import os from 'node:os'; import fs from 'node:fs/promises'; import { pathToFileURL } from 'node:url'; +import crypto from 'node:crypto'; -async function ensureMjs(lintScript) { - // If there's a package.json in the parent chain that says type=module, - // we can just import the .js directly. Otherwise, copy to .mjs. - const mjs = lintScript.replace(/\.js$/, '.sb-lint.mjs'); +/** + * Stage `lintScript` as a `.mjs` in a fresh temp directory. + * + * Returns `{ dir, mjs }`. Caller is responsible for `fs.rm(dir, ...)` + * when done. Never writes into the user's install area. + */ +async function stageMjsInTmp(lintScript) { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), `sb-lint-${process.pid}-`)); + const mjs = path.join(dir, `${crypto.randomBytes(4).toString('hex')}.mjs`); const src = await fs.readFile(lintScript, 'utf-8'); await fs.writeFile(mjs, src, 'utf-8'); - return mjs; + return { dir, mjs }; } /** @@ -36,36 +47,56 @@ export async function lintSkill(skillPath, opts = {}) { const lintScript = opts.lintScript || path.join(os.homedir(), '.minimax', '.builtin-skills', 'skill-creator', 'scripts', 'lint-skill.js'); - // Try dynamic import first (works if package.json has type=module nearby). + // Fast path: dynamic import the script in-process. No files written. + // Handle both ESM (`export function lint`) and CJS interop + // (`module.exports.lint` shows up at `mod.default.lint`). try { const mod = await import(pathToFileURL(lintScript).href); - if (typeof mod.lint === 'function') { - const result = await mod.lint(skillPath); + const fn = typeof mod.lint === 'function' + ? mod.lint + : (mod.default && typeof mod.default.lint === 'function' ? mod.default.lint : null); + if (fn) { + const result = await fn(skillPath); return { ok: result.ok ?? true, code: result.code ?? 0, stdout: result.stdout ?? '', stderr: result.stderr ?? '' }; } } catch (e) { // Fall through to subprocess path } - // Subprocess path: stage as .mjs and run. - const mjs = await ensureMjs(lintScript); - return await new Promise((resolve) => { - const child = spawn(process.execPath, [mjs, skillPath], { - stdio: ['ignore', 'pipe', 'pipe'], - windowsHide: true, - }); - let stdout = ''; - let stderr = ''; - child.stdout.on('data', d => stdout += d); - child.stderr.on('data', d => stderr += d); - child.on('close', (code) => { - // Best-effort cleanup of staged .mjs - fs.unlink(mjs).catch(() => {}); - resolve({ ok: code === 0, code, stdout, stderr }); - }); - child.on('error', (err) => { - fs.unlink(mjs).catch(() => {}); - resolve({ ok: false, code: -1, stdout, stderr: stderr + '\nspawn error: ' + err.message }); + // Subprocess path: stage as .mjs in a unique temp dir, then run. + // The temp dir is always removed, regardless of how the subprocess exits. + const { dir, mjs } = await stageMjsInTmp(lintScript); + try { + return await new Promise((resolve) => { + const child = spawn(process.execPath, [mjs, skillPath], { + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }); + let stdout = ''; + let stderr = ''; + let settled = false; + const settle = (payload) => { + if (settled) return; + settled = true; + // Drop the stdio handles so node's test runner doesn't see a + // still-tracked child (which would fail the surrounding test on + // non-zero exit). On Windows the handles keep the child process + // pinned if not explicitly destroyed. + try { child.stdout?.destroy(); } catch {} + try { child.stderr?.destroy(); } catch {} + resolve(payload); + }; + child.stdout.on('data', (d) => (stdout += d)); + child.stderr.on('data', (d) => (stderr += d)); + child.on('close', (code) => { + settle({ ok: code === 0, code, stdout, stderr }); + }); + child.on('error', (err) => { + settle({ ok: false, code: -1, stdout, stderr: stderr + '\nspawn error: ' + err.message }); + }); }); - }); + } finally { + // Always clean up the staged dir, even on early return / thrown error. + await fs.rm(dir, { recursive: true, force: true }).catch(() => {}); + } } diff --git a/plugins/antianqi/skill-bridge/lib/transform-skill.js b/plugins/antianqi/skill-bridge/lib/transform-skill.js index 25a0f3f..a99ca4b 100644 --- a/plugins/antianqi/skill-bridge/lib/transform-skill.js +++ b/plugins/antianqi/skill-bridge/lib/transform-skill.js @@ -6,9 +6,17 @@ // SKILL.md # mavis schema, with enriched frontmatter // conversion-report.md # what we changed and why // references/.md # (optional) split from body if too long +// +// Atomicity: +// Writes happen in a sibling staging directory first +// (`.staging-`), then `fs.rename`d onto outDir. If anything +// fails before the rename, the staging dir is removed and outDir is left +// untouched. This makes `--force` safe and prevents the "old references +// leak into new output" bug. import fs from 'node:fs/promises'; import path from 'node:path'; +import crypto from 'node:crypto'; import * as yaml from 'js-yaml'; import { parameterizePaths, suggestFilename } from './paths.js'; import { parseFrontmatter } from './analyze.js'; @@ -120,6 +128,20 @@ function addWindowsNotesSection(body, hasShell) { return body.trimEnd() + '\n\n## Windows (win32) platform notes\n\nThe original openclaw skill assumed macOS/Linux shell. The PowerShell equivalents for any `bash`/`pip`/`python3` calls should be documented here. (Generated by skill-bridge; user to verify.)\n'; } +function addReferencesIndex(body, references) { + if (!references || references.length === 0) return body; + if (/^##\s+References\b/m.test(body)) return body; + const items = references + .map((r) => `- [\`${r.file}\`](references/${r.file})`) + .join('\n'); + return ( + body.trimEnd() + + '\n\n## References\n\nDetailed content moved out of this SKILL.md for size. Read these when the main flow above references them:\n\n' + + items + + '\n' + ); +} + function maybeSplitReferences(name, body) { // v0.1 simple split: if body > 500 lines AND has clearly demarcated // sub-sections (## ...), move the later ones into references/. @@ -187,8 +209,12 @@ export async function transformSkill({ inputPath, report, classify, outDir }) { // 3. Maybe split into references/ const { body: bodySplit, references } = maybeSplitReferences(report.frontmatter.name || '', bodyAfterPaths); - // 4. Add the missing sections + // 4. Add the missing sections. References index goes BEFORE + // Output contract / Failure handling / Windows notes so the moved-out + // content is reachable from the top of the body, not buried under + // boilerplate at the end. let finalBody = bodySplit; + finalBody = addReferencesIndex(finalBody, references); finalBody = addOutputContractSection(finalBody); finalBody = addFailureHandlingSection(finalBody); finalBody = addWindowsNotesSection(finalBody, hasShell); @@ -201,24 +227,45 @@ export async function transformSkill({ inputPath, report, classify, outDir }) { const fmYaml = yaml.dump(enrichedFm, { lineWidth: 100, noRefs: true, sortKeys: false }); const skillText = `---\n${fmYaml}---\n\n${finalBody.trimStart()}`; - // 7. Write - await fs.mkdir(outDir, { recursive: true }); - const skillOut = path.join(outDir, 'SKILL.md'); - await fs.writeFile(skillOut, skillText, 'utf-8'); - written.push(skillOut); + // 7. Atomic write: stage everything under a sibling temp dir, then rename. + // This means `--force` is safe (old outDir is replaced wholesale, no + // stale references/) and partial failures never leave a half-written + // outDir behind. + const stageDir = `${outDir}.staging-${process.pid}-${crypto.randomBytes(4).toString('hex')}`; + let stageSucceeded = false; + try { + await fs.mkdir(stageDir, { recursive: true }); - for (const ref of references) { - const refPath = path.join(outDir, 'references', ref.file); - await fs.mkdir(path.dirname(refPath), { recursive: true }); - await fs.writeFile(refPath, ref.content.trim() + '\n', 'utf-8'); - written.push(refPath); + const skillOut = path.join(stageDir, 'SKILL.md'); + await fs.writeFile(skillOut, skillText, 'utf-8'); + + for (const ref of references) { + const refPath = path.join(stageDir, 'references', ref.file); + await fs.mkdir(path.dirname(refPath), { recursive: true }); + await fs.writeFile(refPath, ref.content.trim() + '\n', 'utf-8'); + } + + const reportMd = renderConversionReport({ inputPath, classify, pathChanges, written: [], warnings }); + const reportPath = path.join(stageDir, 'conversion-report.md'); + await fs.writeFile(reportPath, reportMd, 'utf-8'); + + // Replace the destination. If outDir exists, remove it first so the + // rename is a simple same-volume move (works on Windows too). + await fs.rm(outDir, { recursive: true, force: true }); + await fs.rename(stageDir, outDir); + stageSucceeded = true; + } finally { + if (!stageSucceeded) { + await fs.rm(stageDir, { recursive: true, force: true }).catch(() => {}); + } } - // 8. Conversion report - const reportMd = renderConversionReport({ inputPath, classify, pathChanges, written, warnings }); - const reportPath = path.join(outDir, 'conversion-report.md'); - await fs.writeFile(reportPath, reportMd, 'utf-8'); - written.push(reportPath); + // 8. Record the final paths (post-rename) for the caller. + written.push(path.join(outDir, 'SKILL.md')); + for (const ref of references) { + written.push(path.join(outDir, 'references', ref.file)); + } + written.push(path.join(outDir, 'conversion-report.md')); return { written, warnings }; } diff --git a/plugins/antianqi/skill-bridge/package-lock.json b/plugins/antianqi/skill-bridge/package-lock.json index ea9ced8..4cfd0e5 100644 --- a/plugins/antianqi/skill-bridge/package-lock.json +++ b/plugins/antianqi/skill-bridge/package-lock.json @@ -1,12 +1,22 @@ { "name": "skill-bridge", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "skill-bridge", + "version": "0.1.0", + "license": "MIT", "dependencies": { - "iconv-lite": "^0.7.3", - "js-yaml": "^5.3.0" + "iconv-lite": "^0.6.3", + "js-yaml": "^4.1.0" + }, + "bin": { + "mcode-skill-bridge": "index.js" + }, + "engines": { + "node": ">=22.19 <23 || >=24 <27" } }, "node_modules/argparse": { @@ -16,25 +26,21 @@ "license": "Python-2.0" }, "node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/js-yaml": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.3.0.tgz", - "integrity": "sha512-muutsYr+e2+d3rTgUGslq5rxbBlUy3cJ61IsHag2QNDQV+7zXWjkUpmALIajhrlLlrgRUiymj6U3zUr/TMK84Q==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "funding": [ { "type": "github", @@ -50,7 +56,7 @@ "argparse": "^2.0.1" }, "bin": { - "js-yaml": "bin/js-yaml.mjs" + "js-yaml": "bin/js-yaml.js" } }, "node_modules/safer-buffer": { diff --git a/plugins/antianqi/skill-bridge/tests/lint.test.mjs b/plugins/antianqi/skill-bridge/tests/lint.test.mjs new file mode 100644 index 0000000..4bd48b1 --- /dev/null +++ b/plugins/antianqi/skill-bridge/tests/lint.test.mjs @@ -0,0 +1,102 @@ +// tests/lint.test.mjs — regression tests for the review blockers: +// +// 1. `lib/lint.js` MUST NOT write a staged `.mjs` next to +// `~/.minimax/.builtin-skills/skill-creator/scripts/lint-skill.js`. +// That's the user's install area; polluting it is rude and racy. +// +// 2. The temp dir we DO write to must be removed on every code path. +// +// 3. The fast path (in-process dynamic import) must also surface a +// failed lint result faithfully, without touching the install dir. + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import os from 'node:os'; +import { lintSkill } from '../lib/lint.js'; + +// A lint script that does NOT export a `lint` function, forcing the +// subprocess path. Pure CJS, no ESM `import` syntax, so the fast path's +// `import()` of the .js file succeeds and returns an empty module +// (`mod.lint` undefined → fall through). When staged to a .mjs and run +// by node, the same `console.log` works fine in ESM mode. +const FAULT_FREE_LINT = ` +console.log('lint ok for ' + process.argv[2]); +`; + +// A lint script that DOES export a `lint` function (CJS). This drives +// the fast path in-process, returning a failing result without spawning +// a subprocess. Used to verify the install dir is not touched on the +// failure path either. +const FAILING_FAST_LINT = ` +module.exports = { + lint: (p) => ({ ok: false, code: 2, stdout: 'lint failed for ' + p, stderr: '' }), +}; +`; + +async function writeLintScript(content) { + // This directory stands in for ~/.minimax/.builtin-skills/... in real use. + // We never let lintSkill write into it — that's the whole point of this test. + const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-lint-fixture-')); + const lintScript = path.join(dir, 'lint-skill.js'); + await fs.writeFile(lintScript, content, 'utf-8'); + return { dir, lintScript }; +} + +async function assertNoLeftoverStagingInTmp() { + // lintSkill uses prefix `sb-lint-${pid}-`. After it resolves, no such + // directory created by *this* test process should remain. + const tmpRoot = os.tmpdir(); + const entries = await fs.readdir(tmpRoot); + const leftover = entries.filter((e) => e.startsWith(`sb-lint-${process.pid}-`)); + assert.equal( + leftover.length, + 0, + `temp staging dirs left behind: ${leftover.join(', ')}`, + ); +} + +test('lintSkill (subprocess path) stages the .mjs in os.tmpdir() — install dir is untouched', async () => { + const { dir, lintScript } = await writeLintScript(FAULT_FREE_LINT); + let skillPath; + try { + skillPath = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-lint-target-')); + const r = await lintSkill(skillPath, { lintScript }); + assert.equal(r.ok, true, `expected ok, stderr was:\n${r.stderr}`); + assert.ok(/lint ok/.test(r.stdout), `stdout: ${r.stdout}`); + // Install dir must contain only lint-skill.js, never a staged .mjs. + const siblings = await fs.readdir(dir); + assert.ok( + !siblings.some((f) => f.endsWith('.mjs')), + `install dir should not have staged .mjs; got: ${siblings.join(', ')}`, + ); + } finally { + await fs.rm(dir, { recursive: true, force: true }); + if (skillPath) await fs.rm(skillPath, { recursive: true, force: true }); + } + await assertNoLeftoverStagingInTmp(); +}); + +test('lintSkill (fast path) returns the lint-script failure faithfully without touching disk', async () => { + const { dir, lintScript } = await writeLintScript(FAILING_FAST_LINT); + let skillPath; + try { + skillPath = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-lint-target-')); + const r = await lintSkill(skillPath, { lintScript }); + assert.equal(r.ok, false, 'expected ok=false on lint failure'); + assert.equal(r.code, 2, `expected exit code 2, got ${r.code}`); + assert.ok(/lint failed/.test(r.stdout), `stdout: ${r.stdout}`); + + // No temp staging dir should have been created — fast path never + // touches disk, and there is no subprocess to spawn. + const tmpRoot = os.tmpdir(); + const entries = await fs.readdir(tmpRoot); + const leftover = entries.filter((e) => e.startsWith(`sb-lint-${process.pid}-`)); + assert.equal(leftover.length, 0, `fast path should not stage anything; got: ${leftover.join(', ')}`); + } finally { + await fs.rm(dir, { recursive: true, force: true }); + if (skillPath) await fs.rm(skillPath, { recursive: true, force: true }); + } +}); + diff --git a/plugins/antianqi/skill-bridge/tests/transform-skill.test.mjs b/plugins/antianqi/skill-bridge/tests/transform-skill.test.mjs index c584d09..f265f45 100644 --- a/plugins/antianqi/skill-bridge/tests/transform-skill.test.mjs +++ b/plugins/antianqi/skill-bridge/tests/transform-skill.test.mjs @@ -103,3 +103,137 @@ test('transformSkill does NOT add Windows notes for pure prose', async () => { assert.ok(!/^##\s+Windows \(win32\) platform notes/m.test(skill)); await fs.rm(out, { recursive: true, force: true }); }); + +test('transformSkill adds a References index when body is split into references/', async () => { + // Build a body with > 500 lines and 4 `##` sections so maybeSplitReferences + // (sections.length >= 3) fires. + const sectionBody = (label) => { + const lines = [`## ${label}`]; + for (let i = 0; i < 200; i++) lines.push(`Section ${label} line ${i}.`); + return lines.join('\n'); + }; + const longBody = [ + '# Top', + '', + 'Intro paragraph that does not count as a section.', + '', + sectionBody('Alpha'), + '', + sectionBody('Beta'), + '', + sectionBody('Gamma'), + '', + sectionBody('Delta'), + ].join('\n'); + + const out = await tmpdir(); + const outDir = path.join(out, 'split-skill'); + const r = await transformSkill({ + inputPath: 'fake.md', + report: { + inputPath: 'fake.md', + encoding: 'utf-8', + convertedFromGbk: false, + frontmatter: { name: 'split-skill', description: 'Test the split.' }, + body: longBody, + warnings: [], + }, + classify: { tier: 'pure', subTier: 'pure-wrapped-fix', reason: 'r', recommendations: [] }, + outDir, + }); + + // The split must have produced at least one references file. + const refsDir = path.join(outDir, 'references'); + const refFiles = await fs.readdir(refsDir); + assert.ok(refFiles.length >= 1, `expected references/ to be populated, got: ${refFiles.join(', ')}`); + + // SKILL.md must surface them with a References section AND markdown links. + const skill = await fs.readFile(path.join(outDir, 'SKILL.md'), 'utf-8'); + assert.ok(/^##\s+References\b/m.test(skill), 'should add a ## References section to SKILL.md'); + assert.ok( + /references\/[a-z0-9-]+\.md/.test(skill), + 'should list each references/*.md as a link inside the index', + ); + + // The link target must exist on disk. + const linked = skill.match(/references\/([a-z0-9-]+\.md)/); + assert.ok(linked, 'should find a references/*.md link in the body'); + assert.ok( + refFiles.includes(linked[1]), + `linked file ${linked[1]} should exist in references/`, + ); + + assert.ok(r.written.length >= 3, 'should record SKILL.md + references + conversion-report.md'); + await fs.rm(out, { recursive: true, force: true }); +}); + +test('transformSkill replaces outDir atomically (no stale references/ on re-run)', async () => { + const out = await tmpdir(); + const outDir = path.join(out, 'atomic-skill'); + + // 1st pass: long body that triggers split. + const sectionBody = (label) => { + const lines = [`## ${label}`]; + for (let i = 0; i < 200; i++) lines.push(`${label} line ${i}.`); + return lines.join('\n'); + }; + const longBody = [ + '# Top', '', + 'Intro.', + '', + sectionBody('A'), + sectionBody('B'), + sectionBody('C'), + sectionBody('D'), + ].join('\n'); + + await transformSkill({ + inputPath: 'fake.md', + report: { + inputPath: 'fake.md', + encoding: 'utf-8', + convertedFromGbk: false, + frontmatter: { name: 'atomic-skill', description: 'first run' }, + body: longBody, + warnings: [], + }, + classify: { tier: 'pure', subTier: 'pure-wrapped-fix', reason: 'r', recommendations: [] }, + outDir, + }); + + // 1st pass leaves a populated references/ directory. + const refsAfterFirst = await fs.readdir(path.join(outDir, 'references')); + assert.ok(refsAfterFirst.length > 0, '1st pass should produce references/'); + + // 2nd pass: short body that does NOT trigger split. The atomic replace + // must wipe the old references/ — not just overwrite SKILL.md. + const shortBody = '# Top\n\nShort body, no split.\n\n## Procedure\n\nDo it.'; + await transformSkill({ + inputPath: 'fake.md', + report: { + inputPath: 'fake.md', + encoding: 'utf-8', + convertedFromGbk: false, + frontmatter: { name: 'atomic-skill', description: 'second run' }, + body: shortBody, + warnings: [], + }, + classify: { tier: 'pure', subTier: 'pure-translate', reason: 'r', recommendations: [] }, + outDir, + }); + + // No stale references/ on disk. + const refsAfterSecond = await fs.readdir(path.join(outDir, 'references')).catch(() => null); + assert.equal( + refsAfterSecond, + null, + 'stale references/ from previous run must be removed by atomic replace', + ); + + // And the new SKILL.md reflects the new body (not the long one). + const skill = await fs.readFile(path.join(outDir, 'SKILL.md'), 'utf-8'); + assert.ok(skill.includes('Short body, no split.'), 'SKILL.md should reflect 2nd pass body'); + assert.ok(!/Section A line 0/.test(skill), 'old long-body content must not leak into new SKILL.md'); + + await fs.rm(out, { recursive: true, force: true }); +}); From 3c41ee094eab869bca14668514ef507b8b7e6548 Mon Sep 17 00:00:00 2001 From: antianqi <75944423+antianqi@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:56:57 +0800 Subject: [PATCH 3/5] Revert root .gitignore to upstream main The previous PR overwrote the repo-root .gitignore (which lists node_modules/, .DS_Store, coverage/, *.log) with a single line probe-*.mjs. CONTRIBUTING.md requires that contributor changes stay inside plugins//; the plugin-local .gitignore is added under plugins/antianqi/skill-bridge/.gitignore. --- .gitignore | 5 +- .../input/investor-brand-kit/SKILL.md | 308 --------- .../input/self-improving-agent/SKILL.md | 651 ------------------ .../output/investor-brand-kit/SKILL.md | 326 --------- .../investor-brand-kit/conversion-report.md | 21 - .../output/self-improving-agent/SKILL.md | 79 --- .../self-improving-agent/conversion-report.md | 22 - .../references/after-api-changes.md | 4 - .../references/area-tags.md | 12 - .../references/automatic-skill-extraction.md | 64 -- .../references/best-practices.md | 10 - .../references/build-dependencies.md | 10 - .../references/detection-triggers.md | 26 - .../err-yyyymmdd-xxx-skill_or_command_name.md | 36 - .../feat-yyyymmdd-xxx-capability_name.md | 25 - .../references/generic-setup-other-agents.md | 20 - .../references/gitignore-options.md | 15 - .../references/hook-integration.md | 55 -- .../references/id-generation.md | 8 - .../references/logging-format.md | 7 - .../references/lrn-yyyymmdd-xxx-category.md | 34 - .../references/multi-agent-support.md | 22 - .../references/openclaw-setup-recommended.md | 81 --- .../references/periodic-review.md | 27 - .../references/priority-guidelines.md | 8 - .../references/promoting-to-project-memory.md | 37 - .../references/recurring-pattern-detection.md | 11 - .../references/resolving-entries.md | 18 - .../references/self-improvement.md | 38 - .../references/simplify-harden-feed.md | 36 - .../examples/output/task-tracker/SKILL.md | 105 --- .../output/task-tracker/conversion-report.md | 21 - plugins/antianqi/skill-bridge/index.js | 215 ------ .../antianqi/skill-bridge/package-lock.json | 69 -- plugins/antianqi/skill-bridge/package.json | 42 -- .../antianqi/skill-bridge/tests/cli.test.mjs | 67 -- 36 files changed, 4 insertions(+), 2531 deletions(-) delete mode 100644 plugins/antianqi/skill-bridge/examples/input/investor-brand-kit/SKILL.md delete mode 100644 plugins/antianqi/skill-bridge/examples/input/self-improving-agent/SKILL.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/SKILL.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/conversion-report.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/SKILL.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/conversion-report.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/after-api-changes.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/area-tags.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/automatic-skill-extraction.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/best-practices.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/build-dependencies.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/detection-triggers.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/err-yyyymmdd-xxx-skill_or_command_name.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/feat-yyyymmdd-xxx-capability_name.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/generic-setup-other-agents.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/gitignore-options.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/hook-integration.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/id-generation.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/logging-format.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/lrn-yyyymmdd-xxx-category.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/multi-agent-support.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/openclaw-setup-recommended.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/periodic-review.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/priority-guidelines.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/promoting-to-project-memory.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/recurring-pattern-detection.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/resolving-entries.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/self-improvement.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/simplify-harden-feed.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/task-tracker/SKILL.md delete mode 100644 plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md delete mode 100644 plugins/antianqi/skill-bridge/index.js delete mode 100644 plugins/antianqi/skill-bridge/package-lock.json delete mode 100644 plugins/antianqi/skill-bridge/package.json delete mode 100644 plugins/antianqi/skill-bridge/tests/cli.test.mjs diff --git a/.gitignore b/.gitignore index 8fd0a6a..9dc2061 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -probe-*.mjs +node_modules/ +.DS_Store +coverage/ +*.log diff --git a/plugins/antianqi/skill-bridge/examples/input/investor-brand-kit/SKILL.md b/plugins/antianqi/skill-bridge/examples/input/investor-brand-kit/SKILL.md deleted file mode 100644 index e6bd2b2..0000000 --- a/plugins/antianqi/skill-bridge/examples/input/investor-brand-kit/SKILL.md +++ /dev/null @@ -1,308 +0,0 @@ ---- -name: 绿川椒品牌招商核心资料库 -description: 整合品牌信息、差异化卖点、招商角度、合规规则的完整知识库。写脚本前必读。 ---- - -# 绿川椒品牌招商核心资料库 - -> 写脚本前必读。整合了品牌规划书原文 + PPT截图 + 7个脚本 + TASKS.md 实战积累。 - ---- - -## 一、品牌基础信息 - -| 项目 | 内容 | -|------|------| -| 品牌全称 | 绿川椒清水麻辣烫(曾用名:清水绿川椒麻辣烫) | -| 公司全称 | 齐齐哈尔清水绿川椒餐饮管理有限公司 | -| 创立年份 | **2009年**(2016年公司正式成立)| -| 对外宣传口径 | 统一说"17年老品牌",2026年起对外口径 | -| 真实门店数 | 40家 → 目标100家(今年新增60家) | -| 对外宣传门店 | "百余家" / "100+" | -| 累计签约加盟商 | 300+(对外说"帮助300+创业者成功开店") | -| 直营店 | 3家(三院总店、百大旗舰店、城乡路店) | -| 总部所在 | 齐齐哈尔(黑龙江) | -| 主要市场 | 东三省为主,全国招商(河北、天津等华北地区) | -| 招商热线 | **400-678-0452** | -| 微信公众号 | 清水绿川椒 | -| 官方抖音/小红书 | 绿川椒(账号名) | -| Logo元素 | 熊猫+辣椒(绿川椒品牌视觉) | - ---- - -## 二、品牌slogan - -- **主slogan(现行版)**:「清水无油煮,老火麻酱香」 -- **主slogan(前版,已废弃)**:「好料原产地 · 川椒麻香溢」 -- **副slogan**:「回归食物本味的美好」 -- **品牌愿景**:传播绿色饮食文化,打造健康快餐连锁 - ---- - -## 三、核心差异化(5大卖点) - -### 1. 清水烫煮 -- **表达**:不用骨汤,无任何添加剂,就是清水 -- **技术**:水源采用反渗透技术保证健康 -- **画面支撑**:后厨真实拍摄,汤里只有水和食材 -- **顾客反应**:当场问"汤底是不是熬了好几个小时"——就是清水,但顾客信 - -### 2. 后调味 -- **表达**:烫熟了之后在碗里调味,用家里常见的调味料 -- **差异化**:还原食物本身的味道,吃着干净放心 - -### 3. 手工老火慢熬麻酱 -- **表达**:一锅麻酱要三个小时,小火慢搅,不能停 -- **对比**:外面买的机器麻酱,跟这完全不是一个味 -- **作用**:口味护城河,顾客吃一口就知道"外头没有" - -### 4. 17年老品牌 -- **表达**:2009年齐齐哈尔起步,靠一碗清水麻辣烫做到现在 -- **信任支撑**:17年时间验证老百姓认的就是干净和放心 -- **门店验证**:40家,每家都是招牌 - -### 5. 现场制作 -- **表达**:不是料理包,不是预制菜,顾客看着做 -- **信任感**:顾客进店就知道这是真材实料 - ---- - -## 四、产品线 - -| 产品 | 说明 | -|------|------| -| 传统麻辣烫 | 核心主打,清水烫煮 | -| 黏糊麻辣烫 | 2024年新品,大茶缸黏糊麻辣烫,改良配方 | -| 麻辣香锅 | 独立产品线 | - ---- - -## 五、单店投资模型(PPT数据) - -### 4种店型 - -| 店型 | 面积 | 投入成本 | 日均营收 | 月营收 | 毛利率 | 净利率 | 月净利润 | 年净收益 | -|------|------|---------|---------|--------|--------|--------|---------|---------| -| 微店 | 50-60平 | 15.4万 | 2000+ | 6万+ | 50% | 30% | 15000+ | **18万** | -| 商场店 | 70平 | 16.1万 | 3000+ | 9万+ | 50% | 30% | 27000+ | **32.4万** | -| 中型店 | 70-80平 | 21万 | 3000+ | 9万+ | 50% | 30% | 27000+ | **32.4万** | -| 旗舰店 | 90-100平 | 36万 | 5000+ | 15万+ | 50% | 30% | 45000+ | **54万** | - -### 投入明细 - -| 项目 | 微店 | 商场店 | 中型店 | 旗舰店 | -|------|------|--------|--------|--------| -| 店面租金 | 7万 | 5万 | 10万 | 17万 | -| 装修费用 | 4万 | 5.6万 | 6万 | 11万 | -| 设备设施 | 2.6万 | 3万 | 3万 | 5万 | -| 前期进料 | 0.8万 | 1万 | 1万 | 1.5万 | -| 运作资金 | 1万 | 1.5万 | 1万 | 1.5万 | - -**注意**:毛利率50-60%,净利率30%(PPT数据) - ---- - -## 六、核心数据(品牌规划书原文) - -### 投资与收费 -| 项目 | 金额 | -|------|------| -| 地级市加盟费 | 29800元/家(一次性) | -| 县级市加盟费 | 16800元/家(一次性) | -| 地级市年度管理费 | 5000元/家/年 | -| 县级市年度管理费 | 3000元/家/年 | -| 保证金 | 合同期满无违规可退还 | -| 单店投入 | 轻资产,无大型设备,无专业大厨 | - -### 公司营收(今年全年目标) -| 来源 | 金额 | -|------|------| -| 新增60家加盟费 | 147.6万 | -| 管理费(100家) | 29.55万 | -| 团购外卖抽点 | 69.3万 | -| 供应链利润 | 198.576万 | -| **总营收** | **444.026万** | -| 固定团队成本 | 57.6万(9人团队) | -| **预计净利润** | **386.426万** | - -### 扩张节奏 -- 今年目标:40家 → 100家(新增60家) -- 速度:每月新增6家(3月开始) -- 100家以前:固定9人团队(含2名主播) - ---- - -## 七、对外宣传口径(必须遵守) - -### 门店数量 -- ✅ 说"百余家"或"100+" -- ❌ 不说真实40家 - -### 加盟商数量 -- ✅ 说"帮助300+创业者成功开店" -- ❌ 不说具体签约数字 - -### 效果承诺 -- ❌ 稳赚不赔、保本、快速回本、月入XX万 -- ✅ 只说产品特点和运营支持,不承诺结果 - -### 绝对化词汇 -- ❌ 最佳、第一,国家级(需证明) -- ✅ 出色、优质、领先(有事实依据才用) - -### 风险提示(每条视频必须加) -``` -【风险提示】投资有风险,加盟需谨慎。 -``` - ---- - -## 八、全流程扶持体系 - -### 前期扶持(筹备阶段) -- **选址**:总部大数据分析辅助选址,实地考察、风险评估 -- **装修**:提供标准化装修设计方案,本地施工团队装修 -- **设备**:统一采购配送(清水烫煮炉、冷藏柜、收银系统等) -- **证件**:指导办理营业执照、食品经营许可证 - -### 中期扶持(开业与运营) -- **培训**:**7天**全流程技术、运营、管理培训 -- **开业**:总部运营督导上门协助,制定开业活动方案 -- **物料**:核心物料统一配送(麻酱、综合料、辣椒麻椒等) -- **运营督导**:定期巡查,指导规范运营 -- **营销**:总部统一年度/季度营销方案,团购直播间带货,外卖专业团队托管 - -### 后期扶持(长期盈利) -- **产品更新**:定期研发新菜品、新口味,免费技术升级培训 -- **品牌升级**:持续品牌宣传,提升知名度 -- **退出机制**:特殊情况提供合理退出方案 - ---- - -## 九、8大优势(PPT版) - -1. **毛利率高** — 毛利率高达60%,客单高,复购率高,回本快 -2. **清水烫煮** — 无底料、高汤,告别添加剂和千滚水 -3. **产地原材料** — 麻椒、辣椒四川原产地进货 -4. **完善产业链** — 配套工厂、调料店,标准化调配 -5. **全方位服务** — 前期建店到后期运营全程辅助 -6. **产品升级** — 不断研发新品,与时俱进 -7. **专业团队** — 研发部、设计部、市场部、招商部 -8. **老品牌** — 17年品牌积累 - ---- - -## 十、标准化体系 - -### 产品标准化 -- 食材采购标准统一,核心食材总部统一配送 -- 清水烫煮时间、温度精确控制 -- 麻酱调配比例标准化 -- 禁止添加任何添加剂 -- 菜单结构统一(核心爆款+辅助菜品+季节限定) - -### 运营标准化 -- 《门店运营手册》明确卫生、设备、物料、人员管理标准 -- 成本控制方案(参考三院总店经验) -- 3分钟出餐流程优化 - -### 服务标准化 -- 全流程服务规范(东北口语化礼貌用语) -- 客诉处理:10分钟响应,24小时解决 - -### 管理标准化 -- 加盟商档案与考核体系 -- 收银系统、会员系统数据管理 - ---- - -## 十一、加盟流程(8步) - -1. 电话咨询 初步了解 -2. 当面洽谈 签约缴费 -3. 线上选址 综合评估 -4. 设计施工 装修验收 -5. 总部学习 通过考核 -6. 设备食材 进场调试 -7. 开业活动 正式营业 - ---- - -## 十二、已验证有效的7个招商脚本角度 - -### 脚本1:17年老品牌背书 -**核心钩子**:2026年有人说麻辣烫风口过了——还没开始呢 -**数据**:百余家门店,17年验证 -**转化钩子**:评论区留言,发全套资料 - -### 脚本2:清水烫差异化 -**核心钩子**:全国90%用骨汤,我们偏偏不用 -**画面**:后厨清水锅底,顾客当场问 -**转化钩子**:评论区留言,详细说说 - -### 脚本3:无添加健康牌 -**核心钩子**:现在的顾客一口就能喝出来你汤底有没有问题 -**对比冲击**:普通底料表化学名词 vs 绿川椒干干净净 -**转化钩子**:评论区留言,发全套资料 - -### 脚本4:手工麻酱东北味 -**核心钩子**:一锅麻酱三个小时,顾客吃一口就知道——外头没有 -**差异化**:不是营销,是真东西 -**转化钩子**:评论区打"麻酱" - -### 脚本5:加盟商陪跑体系 -**核心钩子**:开业之后发现没人教你——那才叫难 -**服务**:选址/装修/培训/运营/督导,全包 -**转化钩子**:评论区留言,亲自回复 - -### 脚本6:回本周期与ROI -**核心钩子**:加盟商最关心——多久回本 -**数据**:三个月回本/半年回本案例 -**合规**:不说具体数字,说"选对品牌选对位置" -**转化钩子**:评论区打"回本",帮你分析 - -### 脚本7:为什么现在入局 -**核心钩子**:有人说赛道太卷了——那是没用对方法的人卷 -**差异化总结**:清水烫+手工麻酱+17年老店 -**转化钩子**:评论区,发资料 - ---- - -## 十三、还没覆盖的新招商角度 - -1. **选址支持** — 大数据选址如何帮加盟商 -2. **供应链/食材配送** — 后台能力展示(工厂+调料店) -3. **外卖平台运营** — 美团/饿了么/京东怎么玩 -4. **食品安全管控** — 反渗透技术/食材溯源 -5. **区域保护政策** — 加盟后保护范围 -6. **小白也能干** — 7天培训让零基础上手 -7. **黏糊麻辣烫新品** — 2024年新品差异化 -8. **成功加盟商案例** — 真实故事,达人推荐 -9. **品牌荣誉/资质** — 17年积累了什么认可 -10. **什么人适合加盟** — 打工族/创业者/退休人员 - ---- - -## 十四、品牌发展历程 - -| 年份 | 事件 | -|------|------| -| 2009 | 首店开业(三院总店,50多平,日营业额4000+) | -| 2013 | 商标注册,VI系统成立 | -| 2016 | 绿川椒餐饮管理有限公司正式成立 | -| 2017 | 干调店+现代化食品加工厂成立,原材料统一配送 | -| 2018 | 百大旗舰店开业(270平,齐齐哈尔最大旗舰店) | -| 2022 | 城乡路店开业,装修升级2.0版本 | -| 2024 | 大茶缸黏糊麻辣烫全面上线 | - ---- - -## 十五、文件存档 - -- **品牌规划书原文**(完整版):`skills/investor-brand-kit/品牌规划书_完整版.docx` -- **PPT截图资料包**(29页):`skills/investor-brand-kit/品牌PPT截图_图文版.pdf` -- **7个脚本原档**:`D:\狗蛋草稿箱\绿川椒招商脚本_7个卖点_v6.xlsx` -- **违规词规则**:`memory/topics/douyin-banned-words.md` -- **本资料库**:`skills/investor-brand-kit/SKILL.md` - -**写脚本顺序:先读本文件 → 再读违规词规则 → 再动手。** diff --git a/plugins/antianqi/skill-bridge/examples/input/self-improving-agent/SKILL.md b/plugins/antianqi/skill-bridge/examples/input/self-improving-agent/SKILL.md deleted file mode 100644 index 097145f..0000000 --- a/plugins/antianqi/skill-bridge/examples/input/self-improving-agent/SKILL.md +++ /dev/null @@ -1,651 +0,0 @@ ---- -name: self-improvement -description: "Captures learnings, errors, and corrections to enable continuous improvement. - Use when: (1) A command or operation fails unexpectedly, (2) User corrects Claude - ('No, that's wrong...', 'Actually...'), (3) User requests a capability that doesn't - exist, (4) An external API or tool fails, (5) Claude realizes its knowledge is outdated - or incorrect, (6) A better approach is discovered for a recurring task. Also review - learnings before major tasks." ---- - -# Self-Improvement Skill - -Log learnings and errors to markdown files for continuous improvement. Coding agents can later process these into fixes, and important learnings get promoted to project memory. - -## Quick Reference - -| Situation | Action | -|-----------|--------| -| Command/operation fails | Log to `.learnings/ERRORS.md` | -| User corrects you | Log to `.learnings/LEARNINGS.md` with category `correction` | -| User wants missing feature | Log to `.learnings/FEATURE_REQUESTS.md` | -| API/external tool fails | Log to `.learnings/ERRORS.md` with integration details | -| Knowledge was outdated | Log to `.learnings/LEARNINGS.md` with category `knowledge_gap` | -| Found better approach | Log to `.learnings/LEARNINGS.md` with category `best_practice` | -| Simplify/Harden recurring patterns | Log/update `.learnings/LEARNINGS.md` with `Source: simplify-and-harden` and a stable `Pattern-Key` | -| Similar to existing entry | Link with `**See Also**`, consider priority bump | -| Broadly applicable learning | Promote to `CLAUDE.md`, `AGENTS.md`, and/or `.github/copilot-instructions.md` | -| Workflow improvements | Promote to `AGENTS.md` (OpenClaw workspace) | -| Tool gotchas | Promote to `TOOLS.md` (OpenClaw workspace) | -| Behavioral patterns | Promote to `SOUL.md` (OpenClaw workspace) | - -## OpenClaw Setup (Recommended) - -OpenClaw is the primary platform for this skill. It uses workspace-based prompt injection with automatic skill loading. - -### Installation - -**Via ClawdHub (recommended):** -```bash -clawdhub install self-improving-agent -``` - -**Manual:** -```bash -git clone https://github.com/peterskoett/self-improving-agent.git C:\Users\Administrator\.openclaw/skills/self-improving-agent -``` - -Remade for openclaw from original repo : https://github.com/pskoett/pskoett-ai-skills - https://github.com/pskoett/pskoett-ai-skills/tree/main/skills/self-improvement - -### Workspace Structure - -OpenClaw injects these files into every session: - -``` -C:\Users\Administrator\.openclaw/workspace/ -├── AGENTS.md # Multi-agent workflows, delegation patterns -├── SOUL.md # Behavioral guidelines, personality, principles -├── TOOLS.md # Tool capabilities, integration gotchas -├── MEMORY.md # Long-term memory (main session only) -├── memory/ # Daily memory files -│ └── YYYY-MM-DD.md -└── .learnings/ # This skill's log files - ├── LEARNINGS.md - ├── ERRORS.md - └── FEATURE_REQUESTS.md -``` - -### Create Learning Files - -```bash -mkdir -p C:\Users\Administrator\.openclaw/workspace/.learnings -``` - -Then create the log files (or copy from `assets/`): -- `LEARNINGS.md` — corrections, knowledge gaps, best practices -- `ERRORS.md` — command failures, exceptions -- `FEATURE_REQUESTS.md` — user-requested capabilities - -### Promotion Targets - -When learnings prove broadly applicable, promote them to workspace files: - -| Learning Type | Promote To | Example | -|---------------|------------|---------| -| Behavioral patterns | `SOUL.md` | "Be concise, avoid disclaimers" | -| Workflow improvements | `AGENTS.md` | "Spawn sub-agents for long tasks" | -| Tool gotchas | `TOOLS.md` | "Git push needs auth configured first" | - -### Inter-Session Communication - -OpenClaw provides tools to share learnings across sessions: - -- **sessions_list** — View active/recent sessions -- **sessions_history** — Read another session's transcript -- **sessions_send** — Send a learning to another session -- **sessions_spawn** — Spawn a sub-agent for background work - -### Optional: Enable Hook - -For automatic reminders at session start: - -```bash -# Copy hook to OpenClaw hooks directory -cp -r hooks/openclaw C:\Users\Administrator\.openclaw/hooks/self-improvement - -# Enable it -openclaw hooks enable self-improvement -``` - -See `references/openclaw-integration.md` for complete details. - ---- - -## Generic Setup (Other Agents) - -For Claude Code, Codex, Copilot, or other agents, create `.learnings/` in your project: - -```bash -mkdir -p .learnings -``` - -Copy templates from `assets/` or create files with headers. - -### Add reference to agent files AGENTS.md, CLAUDE.md, or .github/copilot-instructions.md to remind yourself to log learnings. (this is an alternative to hook-based reminders) - -#### Self-Improvement Workflow - -When errors or corrections occur: -1. Log to `.learnings/ERRORS.md`, `LEARNINGS.md`, or `FEATURE_REQUESTS.md` -2. Review and promote broadly applicable learnings to: - - `CLAUDE.md` - project facts and conventions - - `AGENTS.md` - workflows and automation - - `.github/copilot-instructions.md` - Copilot context - -## Logging Format - -### Learning Entry - -Append to `.learnings/LEARNINGS.md`: - -```markdown -## [LRN-YYYYMMDD-XXX] category - -**Logged**: ISO-8601 timestamp -**Priority**: low | medium | high | critical -**Status**: pending -**Area**: frontend | backend | infra | tests | docs | config - -### Summary -One-line description of what was learned - -### Details -Full context: what happened, what was wrong, what's correct - -### Suggested Action -Specific fix or improvement to make - -### Metadata -- Source: conversation | error | user_feedback -- Related Files: path/to/file.ext -- Tags: tag1, tag2 -- See Also: LRN-20250110-001 (if related to existing entry) -- Pattern-Key: simplify.dead_code | harden.input_validation (optional, for recurring-pattern tracking) -- Recurrence-Count: 1 (optional) -- First-Seen: 2025-01-15 (optional) -- Last-Seen: 2025-01-15 (optional) - ---- -``` - -### Error Entry - -Append to `.learnings/ERRORS.md`: - -```markdown -## [ERR-YYYYMMDD-XXX] skill_or_command_name - -**Logged**: ISO-8601 timestamp -**Priority**: high -**Status**: pending -**Area**: frontend | backend | infra | tests | docs | config - -### Summary -Brief description of what failed - -### Error -``` -Actual error message or output -``` - -### Context -- Command/operation attempted -- Input or parameters used -- Environment details if relevant - -### Suggested Fix -If identifiable, what might resolve this - -### Metadata -- Reproducible: yes | no | unknown -- Related Files: path/to/file.ext -- See Also: ERR-20250110-001 (if recurring) - ---- -``` - -### Feature Request Entry - -Append to `.learnings/FEATURE_REQUESTS.md`: - -```markdown -## [FEAT-YYYYMMDD-XXX] capability_name - -**Logged**: ISO-8601 timestamp -**Priority**: medium -**Status**: pending -**Area**: frontend | backend | infra | tests | docs | config - -### Requested Capability -What the user wanted to do - -### User Context -Why they needed it, what problem they're solving - -### Complexity Estimate -simple | medium | complex - -### Suggested Implementation -How this could be built, what it might extend - -### Metadata -- Frequency: first_time | recurring -- Related Features: existing_feature_name - ---- -``` - -## ID Generation - -Format: `TYPE-YYYYMMDD-XXX` -- TYPE: `LRN` (learning), `ERR` (error), `FEAT` (feature) -- YYYYMMDD: Current date -- XXX: Sequential number or random 3 chars (e.g., `001`, `A7B`) - -Examples: `LRN-20250115-001`, `ERR-20250115-A3F`, `FEAT-20250115-002` - -## Resolving Entries - -When an issue is fixed, update the entry: - -1. Change `**Status**: pending` → `**Status**: resolved` -2. Add resolution block after Metadata: - -```markdown -### Resolution -- **Resolved**: 2025-01-16T09:00:00Z -- **Commit/PR**: abc123 or #42 -- **Notes**: Brief description of what was done -``` - -Other status values: -- `in_progress` - Actively being worked on -- `wont_fix` - Decided not to address (add reason in Resolution notes) -- `promoted` - Elevated to CLAUDE.md, AGENTS.md, or .github/copilot-instructions.md - -## Promoting to Project Memory - -When a learning is broadly applicable (not a one-off fix), promote it to permanent project memory. - -### When to Promote - -- Learning applies across multiple files/features -- Knowledge any contributor (human or AI) should know -- Prevents recurring mistakes -- Documents project-specific conventions - -### Promotion Targets - -| Target | What Belongs There | -|--------|-------------------| -| `CLAUDE.md` | Project facts, conventions, gotchas for all Claude interactions | -| `AGENTS.md` | Agent-specific workflows, tool usage patterns, automation rules | -| `.github/copilot-instructions.md` | Project context and conventions for GitHub Copilot | -| `SOUL.md` | Behavioral guidelines, communication style, principles (OpenClaw workspace) | -| `TOOLS.md` | Tool capabilities, usage patterns, integration gotchas (OpenClaw workspace) | - -### How to Promote - -1. **Distill** the learning into a concise rule or fact -2. **Add** to appropriate section in target file (create file if needed) -3. **Update** original entry: - - Change `**Status**: pending` → `**Status**: promoted` - - Add `**Promoted**: CLAUDE.md`, `AGENTS.md`, or `.github/copilot-instructions.md` - -### Promotion Examples - -**Learning** (verbose): -> Project uses pnpm workspaces. Attempted `npm install` but failed. -> Lock file is `pnpm-lock.yaml`. Must use `pnpm install`. - -**In CLAUDE.md** (concise): -```markdown -## Build & Dependencies -- Package manager: pnpm (not npm) - use `pnpm install` -``` - -**Learning** (verbose): -> When modifying API endpoints, must regenerate TypeScript client. -> Forgetting this causes type mismatches at runtime. - -**In AGENTS.md** (actionable): -```markdown -## After API Changes -1. Regenerate client: `pnpm run generate:api` -2. Check for type errors: `pnpm tsc --noEmit` -``` - -## Recurring Pattern Detection - -If logging something similar to an existing entry: - -1. **Search first**: `grep -r "keyword" .learnings/` -2. **Link entries**: Add `**See Also**: ERR-20250110-001` in Metadata -3. **Bump priority** if issue keeps recurring -4. **Consider systemic fix**: Recurring issues often indicate: - - Missing documentation (→ promote to CLAUDE.md or .github/copilot-instructions.md) - - Missing automation (→ add to AGENTS.md) - - Architectural problem (→ create tech debt ticket) - -## Simplify & Harden Feed - -Use this workflow to ingest recurring patterns from the `simplify-and-harden` -skill and turn them into durable prompt guidance. - -### Ingestion Workflow - -1. Read `simplify_and_harden.learning_loop.candidates` from the task summary. -2. For each candidate, use `pattern_key` as the stable dedupe key. -3. Search `.learnings/LEARNINGS.md` for an existing entry with that key: - - `grep -n "Pattern-Key: " .learnings/LEARNINGS.md` -4. If found: - - Increment `Recurrence-Count` - - Update `Last-Seen` - - Add `See Also` links to related entries/tasks -5. If not found: - - Create a new `LRN-...` entry - - Set `Source: simplify-and-harden` - - Set `Pattern-Key`, `Recurrence-Count: 1`, and `First-Seen`/`Last-Seen` - -### Promotion Rule (System Prompt Feedback) - -Promote recurring patterns into agent context/system prompt files when all are true: - -- `Recurrence-Count >= 3` -- Seen across at least 2 distinct tasks -- Occurred within a 30-day window - -Promotion targets: -- `CLAUDE.md` -- `AGENTS.md` -- `.github/copilot-instructions.md` -- `SOUL.md` / `TOOLS.md` for OpenClaw workspace-level guidance when applicable - -Write promoted rules as short prevention rules (what to do before/while coding), -not long incident write-ups. - -## Periodic Review - -Review `.learnings/` at natural breakpoints: - -### When to Review -- Before starting a new major task -- After completing a feature -- When working in an area with past learnings -- Weekly during active development - -### Quick Status Check -```bash -# Count pending items -grep -h "Status\*\*: pending" .learnings/*.md | wc -l - -# List pending high-priority items -grep -B5 "Priority\*\*: high" .learnings/*.md | grep "^## \[" - -# Find learnings for a specific area -grep -l "Area\*\*: backend" .learnings/*.md -``` - -### Review Actions -- Resolve fixed items -- Promote applicable learnings -- Link related entries -- Escalate recurring issues - -## Detection Triggers - -Automatically log when you notice: - -**Corrections** (→ learning with `correction` category): -- "No, that's not right..." -- "Actually, it should be..." -- "You're wrong about..." -- "That's outdated..." - -**Feature Requests** (→ feature request): -- "Can you also..." -- "I wish you could..." -- "Is there a way to..." -- "Why can't you..." - -**Knowledge Gaps** (→ learning with `knowledge_gap` category): -- User provides information you didn't know -- Documentation you referenced is outdated -- API behavior differs from your understanding - -**Errors** (→ error entry): -- Command returns non-zero exit code -- Exception or stack trace -- Unexpected output or behavior -- Timeout or connection failure - -## Priority Guidelines - -| Priority | When to Use | -|----------|-------------| -| `critical` | Blocks core functionality, data loss risk, security issue | -| `high` | Significant impact, affects common workflows, recurring issue | -| `medium` | Moderate impact, workaround exists | -| `low` | Minor inconvenience, edge case, nice-to-have | - -## Area Tags - -Use to filter learnings by codebase region: - -| Area | Scope | -|------|-------| -| `frontend` | UI, components, client-side code | -| `backend` | API, services, server-side code | -| `infra` | CI/CD, deployment, Docker, cloud | -| `tests` | Test files, testing utilities, coverage | -| `docs` | Documentation, comments, READMEs | -| `config` | Configuration files, environment, settings | - -## Best Practices - -1. **Log immediately** - context is freshest right after the issue -2. **Be specific** - future agents need to understand quickly -3. **Include reproduction steps** - especially for errors -4. **Link related files** - makes fixes easier -5. **Suggest concrete fixes** - not just "investigate" -6. **Use consistent categories** - enables filtering -7. **Promote aggressively** - if in doubt, add to CLAUDE.md or .github/copilot-instructions.md -8. **Review regularly** - stale learnings lose value - -## Gitignore Options - -**Keep learnings local** (per-developer): -```gitignore -.learnings/ -``` - -**Track learnings in repo** (team-wide): -Don't add to .gitignore - learnings become shared knowledge. - -**Hybrid** (track templates, ignore entries): -```gitignore -.learnings/*.md -!.learnings/.gitkeep -``` - -## Hook Integration - -Enable automatic reminders through agent hooks. This is **opt-in** - you must explicitly configure hooks. - -### Quick Setup (Claude Code / Codex) - -Create `.claude/settings.json` in your project: - -```json -{ - "hooks": { - "UserPromptSubmit": [{ - "matcher": "", - "hooks": [{ - "type": "command", - "command": "./skills/self-improvement/scripts/activator.sh" - }] - }] - } -} -``` - -This injects a learning evaluation reminder after each prompt (~50-100 tokens overhead). - -### Full Setup (With Error Detection) - -```json -{ - "hooks": { - "UserPromptSubmit": [{ - "matcher": "", - "hooks": [{ - "type": "command", - "command": "./skills/self-improvement/scripts/activator.sh" - }] - }], - "PostToolUse": [{ - "matcher": "Bash", - "hooks": [{ - "type": "command", - "command": "./skills/self-improvement/scripts/error-detector.sh" - }] - }] - } -} -``` - -### Available Hook Scripts - -| Script | Hook Type | Purpose | -|--------|-----------|---------| -| `scripts/activator.sh` | UserPromptSubmit | Reminds to evaluate learnings after tasks | -| `scripts/error-detector.sh` | PostToolUse (Bash) | Triggers on command errors | - -See `references/hooks-setup.md` for detailed configuration and troubleshooting. - -## Automatic Skill Extraction - -When a learning is valuable enough to become a reusable skill, extract it using the provided helper. - -### Skill Extraction Criteria - -A learning qualifies for skill extraction when ANY of these apply: - -| Criterion | Description | -|-----------|-------------| -| **Recurring** | Has `See Also` links to 2+ similar issues | -| **Verified** | Status is `resolved` with working fix | -| **Non-obvious** | Required actual debugging/investigation to discover | -| **Broadly applicable** | Not project-specific; useful across codebases | -| **User-flagged** | User says "save this as a skill" or similar | - -### Extraction Workflow - -1. **Identify candidate**: Learning meets extraction criteria -2. **Run helper** (or create manually): - ```bash - ./skills/self-improvement/scripts/extract-skill.sh skill-name --dry-run - ./skills/self-improvement/scripts/extract-skill.sh skill-name - ``` -3. **Customize SKILL.md**: Fill in template with learning content -4. **Update learning**: Set status to `promoted_to_skill`, add `Skill-Path` -5. **Verify**: Read skill in fresh session to ensure it's self-contained - -### Manual Extraction - -If you prefer manual creation: - -1. Create `skills//SKILL.md` -2. Use template from `assets/SKILL-TEMPLATE.md` -3. Follow [Agent Skills spec](https://agentskills.io/specification): - - YAML frontmatter with `name` and `description` - - Name must match folder name - - No README.md inside skill folder - -### Extraction Detection Triggers - -Watch for these signals that a learning should become a skill: - -**In conversation:** -- "Save this as a skill" -- "I keep running into this" -- "This would be useful for other projects" -- "Remember this pattern" - -**In learning entries:** -- Multiple `See Also` links (recurring issue) -- High priority + resolved status -- Category: `best_practice` with broad applicability -- User feedback praising the solution - -### Skill Quality Gates - -Before extraction, verify: - -- [ ] Solution is tested and working -- [ ] Description is clear without original context -- [ ] Code examples are self-contained -- [ ] No project-specific hardcoded values -- [ ] Follows skill naming conventions (lowercase, hyphens) - -## Multi-Agent Support - -This skill works across different AI coding agents with agent-specific activation. - -### Claude Code - -**Activation**: Hooks (UserPromptSubmit, PostToolUse) -**Setup**: `.claude/settings.json` with hook configuration -**Detection**: Automatic via hook scripts - -### Codex CLI - -**Activation**: Hooks (same pattern as Claude Code) -**Setup**: `.codex/settings.json` with hook configuration -**Detection**: Automatic via hook scripts - -### GitHub Copilot - -**Activation**: Manual (no hook support) -**Setup**: Add to `.github/copilot-instructions.md`: - -```markdown -## Self-Improvement - -After solving non-obvious issues, consider logging to `.learnings/`: -1. Use format from self-improvement skill -2. Link related entries with See Also -3. Promote high-value learnings to skills - -Ask in chat: "Should I log this as a learning?" -``` - -**Detection**: Manual review at session end - -### OpenClaw - -**Activation**: Workspace injection + inter-agent messaging -**Setup**: See "OpenClaw Setup" section above -**Detection**: Via session tools and workspace files - -### Agent-Agnostic Guidance - -Regardless of agent, apply self-improvement when you: - -1. **Discover something non-obvious** - solution wasn't immediate -2. **Correct yourself** - initial approach was wrong -3. **Learn project conventions** - discovered undocumented patterns -4. **Hit unexpected errors** - especially if diagnosis was difficult -5. **Find better approaches** - improved on your original solution - -### Copilot Chat Integration - -For Copilot users, add this to your prompts when relevant: - -> After completing this task, evaluate if any learnings should be logged to `.learnings/` using the self-improvement skill format. - -Or use quick prompts: -- "Log this to learnings" -- "Create a skill from this solution" -- "Check .learnings/ for related issues" diff --git a/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/SKILL.md b/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/SKILL.md deleted file mode 100644 index 9bd4d29..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/SKILL.md +++ /dev/null @@ -1,326 +0,0 @@ ---- -name: investor-brand-kit -description: 'Use when: 整合品牌信息、差异化卖点、招商角度、合规规则的完整知识库。写脚本前必读。.' -descriptions: - zh-Hans: '> 写脚本前必读。整合了品牌规划书原文 + PPT截图 + 7个脚本 + TASKS.md 实战积累。' -displayNames: - zh-Hans: 绿川椒品牌招商核心资料库 -metadata: - openclaw_compat: true - skill-bridge: - classify_tier: pure - classify_subtier: pure-translate - classify_reason: pure instruction, ascii-clean, no hardcoded paths ---- - -# 绿川椒品牌招商核心资料库 - -> 写脚本前必读。整合了品牌规划书原文 + PPT截图 + 7个脚本 + TASKS.md 实战积累。 - ---- - -## 一、品牌基础信息 - -| 项目 | 内容 | -|------|------| -| 品牌全称 | 绿川椒清水麻辣烫(曾用名:清水绿川椒麻辣烫) | -| 公司全称 | 齐齐哈尔清水绿川椒餐饮管理有限公司 | -| 创立年份 | **2009年**(2016年公司正式成立)| -| 对外宣传口径 | 统一说"17年老品牌",2026年起对外口径 | -| 真实门店数 | 40家 → 目标100家(今年新增60家) | -| 对外宣传门店 | "百余家" / "100+" | -| 累计签约加盟商 | 300+(对外说"帮助300+创业者成功开店") | -| 直营店 | 3家(三院总店、百大旗舰店、城乡路店) | -| 总部所在 | 齐齐哈尔(黑龙江) | -| 主要市场 | 东三省为主,全国招商(河北、天津等华北地区) | -| 招商热线 | **400-678-0452** | -| 微信公众号 | 清水绿川椒 | -| 官方抖音/小红书 | 绿川椒(账号名) | -| Logo元素 | 熊猫+辣椒(绿川椒品牌视觉) | - ---- - -## 二、品牌slogan - -- **主slogan(现行版)**:「清水无油煮,老火麻酱香」 -- **主slogan(前版,已废弃)**:「好料原产地 · 川椒麻香溢」 -- **副slogan**:「回归食物本味的美好」 -- **品牌愿景**:传播绿色饮食文化,打造健康快餐连锁 - ---- - -## 三、核心差异化(5大卖点) - -### 1. 清水烫煮 -- **表达**:不用骨汤,无任何添加剂,就是清水 -- **技术**:水源采用反渗透技术保证健康 -- **画面支撑**:后厨真实拍摄,汤里只有水和食材 -- **顾客反应**:当场问"汤底是不是熬了好几个小时"——就是清水,但顾客信 - -### 2. 后调味 -- **表达**:烫熟了之后在碗里调味,用家里常见的调味料 -- **差异化**:还原食物本身的味道,吃着干净放心 - -### 3. 手工老火慢熬麻酱 -- **表达**:一锅麻酱要三个小时,小火慢搅,不能停 -- **对比**:外面买的机器麻酱,跟这完全不是一个味 -- **作用**:口味护城河,顾客吃一口就知道"外头没有" - -### 4. 17年老品牌 -- **表达**:2009年齐齐哈尔起步,靠一碗清水麻辣烫做到现在 -- **信任支撑**:17年时间验证老百姓认的就是干净和放心 -- **门店验证**:40家,每家都是招牌 - -### 5. 现场制作 -- **表达**:不是料理包,不是预制菜,顾客看着做 -- **信任感**:顾客进店就知道这是真材实料 - ---- - -## 四、产品线 - -| 产品 | 说明 | -|------|------| -| 传统麻辣烫 | 核心主打,清水烫煮 | -| 黏糊麻辣烫 | 2024年新品,大茶缸黏糊麻辣烫,改良配方 | -| 麻辣香锅 | 独立产品线 | - ---- - -## 五、单店投资模型(PPT数据) - -### 4种店型 - -| 店型 | 面积 | 投入成本 | 日均营收 | 月营收 | 毛利率 | 净利率 | 月净利润 | 年净收益 | -|------|------|---------|---------|--------|--------|--------|---------|---------| -| 微店 | 50-60平 | 15.4万 | 2000+ | 6万+ | 50% | 30% | 15000+ | **18万** | -| 商场店 | 70平 | 16.1万 | 3000+ | 9万+ | 50% | 30% | 27000+ | **32.4万** | -| 中型店 | 70-80平 | 21万 | 3000+ | 9万+ | 50% | 30% | 27000+ | **32.4万** | -| 旗舰店 | 90-100平 | 36万 | 5000+ | 15万+ | 50% | 30% | 45000+ | **54万** | - -### 投入明细 - -| 项目 | 微店 | 商场店 | 中型店 | 旗舰店 | -|------|------|--------|--------|--------| -| 店面租金 | 7万 | 5万 | 10万 | 17万 | -| 装修费用 | 4万 | 5.6万 | 6万 | 11万 | -| 设备设施 | 2.6万 | 3万 | 3万 | 5万 | -| 前期进料 | 0.8万 | 1万 | 1万 | 1.5万 | -| 运作资金 | 1万 | 1.5万 | 1万 | 1.5万 | - -**注意**:毛利率50-60%,净利率30%(PPT数据) - ---- - -## 六、核心数据(品牌规划书原文) - -### 投资与收费 -| 项目 | 金额 | -|------|------| -| 地级市加盟费 | 29800元/家(一次性) | -| 县级市加盟费 | 16800元/家(一次性) | -| 地级市年度管理费 | 5000元/家/年 | -| 县级市年度管理费 | 3000元/家/年 | -| 保证金 | 合同期满无违规可退还 | -| 单店投入 | 轻资产,无大型设备,无专业大厨 | - -### 公司营收(今年全年目标) -| 来源 | 金额 | -|------|------| -| 新增60家加盟费 | 147.6万 | -| 管理费(100家) | 29.55万 | -| 团购外卖抽点 | 69.3万 | -| 供应链利润 | 198.576万 | -| **总营收** | **444.026万** | -| 固定团队成本 | 57.6万(9人团队) | -| **预计净利润** | **386.426万** | - -### 扩张节奏 -- 今年目标:40家 → 100家(新增60家) -- 速度:每月新增6家(3月开始) -- 100家以前:固定9人团队(含2名主播) - ---- - -## 七、对外宣传口径(必须遵守) - -### 门店数量 -- ✅ 说"百余家"或"100+" -- ❌ 不说真实40家 - -### 加盟商数量 -- ✅ 说"帮助300+创业者成功开店" -- ❌ 不说具体签约数字 - -### 效果承诺 -- ❌ 稳赚不赔、保本、快速回本、月入XX万 -- ✅ 只说产品特点和运营支持,不承诺结果 - -### 绝对化词汇 -- ❌ 最佳、第一,国家级(需证明) -- ✅ 出色、优质、领先(有事实依据才用) - -### 风险提示(每条视频必须加) -``` -【风险提示】投资有风险,加盟需谨慎。 -``` - ---- - -## 八、全流程扶持体系 - -### 前期扶持(筹备阶段) -- **选址**:总部大数据分析辅助选址,实地考察、风险评估 -- **装修**:提供标准化装修设计方案,本地施工团队装修 -- **设备**:统一采购配送(清水烫煮炉、冷藏柜、收银系统等) -- **证件**:指导办理营业执照、食品经营许可证 - -### 中期扶持(开业与运营) -- **培训**:**7天**全流程技术、运营、管理培训 -- **开业**:总部运营督导上门协助,制定开业活动方案 -- **物料**:核心物料统一配送(麻酱、综合料、辣椒麻椒等) -- **运营督导**:定期巡查,指导规范运营 -- **营销**:总部统一年度/季度营销方案,团购直播间带货,外卖专业团队托管 - -### 后期扶持(长期盈利) -- **产品更新**:定期研发新菜品、新口味,免费技术升级培训 -- **品牌升级**:持续品牌宣传,提升知名度 -- **退出机制**:特殊情况提供合理退出方案 - ---- - -## 九、8大优势(PPT版) - -1. **毛利率高** — 毛利率高达60%,客单高,复购率高,回本快 -2. **清水烫煮** — 无底料、高汤,告别添加剂和千滚水 -3. **产地原材料** — 麻椒、辣椒四川原产地进货 -4. **完善产业链** — 配套工厂、调料店,标准化调配 -5. **全方位服务** — 前期建店到后期运营全程辅助 -6. **产品升级** — 不断研发新品,与时俱进 -7. **专业团队** — 研发部、设计部、市场部、招商部 -8. **老品牌** — 17年品牌积累 - ---- - -## 十、标准化体系 - -### 产品标准化 -- 食材采购标准统一,核心食材总部统一配送 -- 清水烫煮时间、温度精确控制 -- 麻酱调配比例标准化 -- 禁止添加任何添加剂 -- 菜单结构统一(核心爆款+辅助菜品+季节限定) - -### 运营标准化 -- 《门店运营手册》明确卫生、设备、物料、人员管理标准 -- 成本控制方案(参考三院总店经验) -- 3分钟出餐流程优化 - -### 服务标准化 -- 全流程服务规范(东北口语化礼貌用语) -- 客诉处理:10分钟响应,24小时解决 - -### 管理标准化 -- 加盟商档案与考核体系 -- 收银系统、会员系统数据管理 - ---- - -## 十一、加盟流程(8步) - -1. 电话咨询 初步了解 -2. 当面洽谈 签约缴费 -3. 线上选址 综合评估 -4. 设计施工 装修验收 -5. 总部学习 通过考核 -6. 设备食材 进场调试 -7. 开业活动 正式营业 - ---- - -## 十二、已验证有效的7个招商脚本角度 - -### 脚本1:17年老品牌背书 -**核心钩子**:2026年有人说麻辣烫风口过了——还没开始呢 -**数据**:百余家门店,17年验证 -**转化钩子**:评论区留言,发全套资料 - -### 脚本2:清水烫差异化 -**核心钩子**:全国90%用骨汤,我们偏偏不用 -**画面**:后厨清水锅底,顾客当场问 -**转化钩子**:评论区留言,详细说说 - -### 脚本3:无添加健康牌 -**核心钩子**:现在的顾客一口就能喝出来你汤底有没有问题 -**对比冲击**:普通底料表化学名词 vs 绿川椒干干净净 -**转化钩子**:评论区留言,发全套资料 - -### 脚本4:手工麻酱东北味 -**核心钩子**:一锅麻酱三个小时,顾客吃一口就知道——外头没有 -**差异化**:不是营销,是真东西 -**转化钩子**:评论区打"麻酱" - -### 脚本5:加盟商陪跑体系 -**核心钩子**:开业之后发现没人教你——那才叫难 -**服务**:选址/装修/培训/运营/督导,全包 -**转化钩子**:评论区留言,亲自回复 - -### 脚本6:回本周期与ROI -**核心钩子**:加盟商最关心——多久回本 -**数据**:三个月回本/半年回本案例 -**合规**:不说具体数字,说"选对品牌选对位置" -**转化钩子**:评论区打"回本",帮你分析 - -### 脚本7:为什么现在入局 -**核心钩子**:有人说赛道太卷了——那是没用对方法的人卷 -**差异化总结**:清水烫+手工麻酱+17年老店 -**转化钩子**:评论区,发资料 - ---- - -## 十三、还没覆盖的新招商角度 - -1. **选址支持** — 大数据选址如何帮加盟商 -2. **供应链/食材配送** — 后台能力展示(工厂+调料店) -3. **外卖平台运营** — 美团/饿了么/京东怎么玩 -4. **食品安全管控** — 反渗透技术/食材溯源 -5. **区域保护政策** — 加盟后保护范围 -6. **小白也能干** — 7天培训让零基础上手 -7. **黏糊麻辣烫新品** — 2024年新品差异化 -8. **成功加盟商案例** — 真实故事,达人推荐 -9. **品牌荣誉/资质** — 17年积累了什么认可 -10. **什么人适合加盟** — 打工族/创业者/退休人员 - ---- - -## 十四、品牌发展历程 - -| 年份 | 事件 | -|------|------| -| 2009 | 首店开业(三院总店,50多平,日营业额4000+) | -| 2013 | 商标注册,VI系统成立 | -| 2016 | 绿川椒餐饮管理有限公司正式成立 | -| 2017 | 干调店+现代化食品加工厂成立,原材料统一配送 | -| 2018 | 百大旗舰店开业(270平,齐齐哈尔最大旗舰店) | -| 2022 | 城乡路店开业,装修升级2.0版本 | -| 2024 | 大茶缸黏糊麻辣烫全面上线 | - ---- - -## 十五、文件存档 - -- **品牌规划书原文**(完整版):`skills/investor-brand-kit/品牌规划书_完整版.docx` -- **PPT截图资料包**(29页):`skills/investor-brand-kit/品牌PPT截图_图文版.pdf` -- **7个脚本原档**:`D:\狗蛋草稿箱\绿川椒招商脚本_7个卖点_v6.xlsx` -- **违规词规则**:`memory/topics/douyin-banned-words.md` -- **本资料库**:`skills/investor-brand-kit/SKILL.md` - -**写脚本顺序:先读本文件 → 再读违规词规则 → 再动手。** - -## Output contract - -This skill does not produce files by itself; the converted openclaw skill should declare its outputs in a new section here. (Filled in by the user after first run.) - -## Failure handling - -If a required external tool or path is missing, surface the exact missing identifier to the user instead of guessing. Do not auto-install system packages. (Add skill-specific failure modes here.) diff --git a/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/conversion-report.md b/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/conversion-report.md deleted file mode 100644 index 0b69f1f..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/investor-brand-kit/conversion-report.md +++ /dev/null @@ -1,21 +0,0 @@ -# Conversion report - -- **input**: `examples\input\investor-brand-kit\SKILL.md` -- **tier**: pure / pure-translate -- **reason**: pure instruction, ascii-clean, no hardcoded paths - -## Path changes -_none_ - -## Written files - - -## Recommendations -- enrich frontmatter (descriptions.zh-Hans, displayNames.zh-Hans, metadata) -- move trigger conditions from body to description -- verify body is under 500 lines; split into references/ if not - -## Warnings -_none_ - -_generated by skill-bridge v0.1.0 on 2026-08-15T00:53:46.105Z_ diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/SKILL.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/SKILL.md deleted file mode 100644 index 02c3b93..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/SKILL.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -name: self-improving-agent -description: >- - Captures learnings, errors, and corrections to enable continuous improvement. Use when: (1) A - command or operation fails unexpectedly, (2) User corrects Claude ('No, that's wrong...', - 'Actually...'), (3) User requests a capability that doesn't exist, (4) An external API or tool - fails, (5) Claude realizes its knowledge is outdated or incorrect, (6) A better approach is - discovered for a recurring task. Also review learnings before major tasks. -displayNames: - zh-Hans: Self-Improvement Skill -metadata: - openclaw_compat: true - skill-bridge: - classify_tier: pure - classify_subtier: pure-wrapped-fix - classify_reason: 1 hardcoded path group(s) found ---- - -# Self-Improvement Skill - -Log learnings and errors to markdown files for continuous improvement. Coding agents can later process these into fixes, and important learnings get promoted to project memory. - - -## Quick Reference - -| Situation | Action | -|-----------|--------| -| Command/operation fails | Log to `.learnings/ERRORS.md` | -| User corrects you | Log to `.learnings/LEARNINGS.md` with category `correction` | -| User wants missing feature | Log to `.learnings/FEATURE_REQUESTS.md` | -| API/external tool fails | Log to `.learnings/ERRORS.md` with integration details | -| Knowledge was outdated | Log to `.learnings/LEARNINGS.md` with category `knowledge_gap` | -| Found better approach | Log to `.learnings/LEARNINGS.md` with category `best_practice` | -| Simplify/Harden recurring patterns | Log/update `.learnings/LEARNINGS.md` with `Source: simplify-and-harden` and a stable `Pattern-Key` | -| Similar to existing entry | Link with `**See Also**`, consider priority bump | -| Broadly applicable learning | Promote to `CLAUDE.md`, `AGENTS.md`, and/or `.github/copilot-instructions.md` | -| Workflow improvements | Promote to `AGENTS.md` (OpenClaw workspace) | -| Tool gotchas | Promote to `TOOLS.md` (OpenClaw workspace) | -| Behavioral patterns | Promote to `SOUL.md` (OpenClaw workspace) | - -## References - -Detailed content moved out of this SKILL.md for size. Read these when the main flow above references them: - -- [`openclaw-setup-recommended.md`](references/openclaw-setup-recommended.md) -- [`generic-setup-other-agents.md`](references/generic-setup-other-agents.md) -- [`logging-format.md`](references/logging-format.md) -- [`lrn-yyyymmdd-xxx-category.md`](references/lrn-yyyymmdd-xxx-category.md) -- [`err-yyyymmdd-xxx-skill_or_command_name.md`](references/err-yyyymmdd-xxx-skill_or_command_name.md) -- [`feat-yyyymmdd-xxx-capability_name.md`](references/feat-yyyymmdd-xxx-capability_name.md) -- [`id-generation.md`](references/id-generation.md) -- [`resolving-entries.md`](references/resolving-entries.md) -- [`promoting-to-project-memory.md`](references/promoting-to-project-memory.md) -- [`build-dependencies.md`](references/build-dependencies.md) -- [`after-api-changes.md`](references/after-api-changes.md) -- [`recurring-pattern-detection.md`](references/recurring-pattern-detection.md) -- [`simplify-harden-feed.md`](references/simplify-harden-feed.md) -- [`periodic-review.md`](references/periodic-review.md) -- [`detection-triggers.md`](references/detection-triggers.md) -- [`priority-guidelines.md`](references/priority-guidelines.md) -- [`area-tags.md`](references/area-tags.md) -- [`best-practices.md`](references/best-practices.md) -- [`gitignore-options.md`](references/gitignore-options.md) -- [`hook-integration.md`](references/hook-integration.md) -- [`automatic-skill-extraction.md`](references/automatic-skill-extraction.md) -- [`multi-agent-support.md`](references/multi-agent-support.md) -- [`self-improvement.md`](references/self-improvement.md) - -## Output contract - -This skill does not produce files by itself; the converted openclaw skill should declare its outputs in a new section here. (Filled in by the user after first run.) - -## Failure handling - -If a required external tool or path is missing, surface the exact missing identifier to the user instead of guessing. Do not auto-install system packages. (Add skill-specific failure modes here.) - -## Windows (win32) platform notes - -The original openclaw skill assumed macOS/Linux shell. The PowerShell equivalents for any `bash`/`pip`/`python3` calls should be documented here. (Generated by skill-bridge; user to verify.) diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/conversion-report.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/conversion-report.md deleted file mode 100644 index 2c34753..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/conversion-report.md +++ /dev/null @@ -1,22 +0,0 @@ -# Conversion report - -- **input**: `examples\input\self-improving-agent\SKILL.md` -- **tier**: pure / pure-wrapped-fix -- **reason**: 1 hardcoded path group(s) found - -## Path changes -- `openclaw-workspace` → ${OPENCLAW_WORKSPACE} (2x) -- `openclaw-home` → ${OPENCLAW_HOME} (2x) - -## Written files - - -## Recommendations -- parameterize paths via paths.js -- ensure UTF-8 output -- add Windows adaptation section if body uses shell commands - -## Warnings -- paths parameterized: openclaw-workspace, openclaw-home - -_generated by skill-bridge v0.1.0 on 2026-08-15T00:53:46.223Z_ diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/after-api-changes.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/after-api-changes.md deleted file mode 100644 index 1cca975..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/after-api-changes.md +++ /dev/null @@ -1,4 +0,0 @@ -## After API Changes -1. Regenerate client: `pnpm run generate:api` -2. Check for type errors: `pnpm tsc --noEmit` -``` diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/area-tags.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/area-tags.md deleted file mode 100644 index 702e292..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/area-tags.md +++ /dev/null @@ -1,12 +0,0 @@ -## Area Tags - -Use to filter learnings by codebase region: - -| Area | Scope | -|------|-------| -| `frontend` | UI, components, client-side code | -| `backend` | API, services, server-side code | -| `infra` | CI/CD, deployment, Docker, cloud | -| `tests` | Test files, testing utilities, coverage | -| `docs` | Documentation, comments, READMEs | -| `config` | Configuration files, environment, settings | diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/automatic-skill-extraction.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/automatic-skill-extraction.md deleted file mode 100644 index 02b4f22..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/automatic-skill-extraction.md +++ /dev/null @@ -1,64 +0,0 @@ -## Automatic Skill Extraction - -When a learning is valuable enough to become a reusable skill, extract it using the provided helper. - -### Skill Extraction Criteria - -A learning qualifies for skill extraction when ANY of these apply: - -| Criterion | Description | -|-----------|-------------| -| **Recurring** | Has `See Also` links to 2+ similar issues | -| **Verified** | Status is `resolved` with working fix | -| **Non-obvious** | Required actual debugging/investigation to discover | -| **Broadly applicable** | Not project-specific; useful across codebases | -| **User-flagged** | User says "save this as a skill" or similar | - -### Extraction Workflow - -1. **Identify candidate**: Learning meets extraction criteria -2. **Run helper** (or create manually): - ```bash - ./skills/self-improvement/scripts/extract-skill.sh skill-name --dry-run - ./skills/self-improvement/scripts/extract-skill.sh skill-name - ``` -3. **Customize SKILL.md**: Fill in template with learning content -4. **Update learning**: Set status to `promoted_to_skill`, add `Skill-Path` -5. **Verify**: Read skill in fresh session to ensure it's self-contained - -### Manual Extraction - -If you prefer manual creation: - -1. Create `skills//SKILL.md` -2. Use template from `assets/SKILL-TEMPLATE.md` -3. Follow [Agent Skills spec](https://agentskills.io/specification): - - YAML frontmatter with `name` and `description` - - Name must match folder name - - No README.md inside skill folder - -### Extraction Detection Triggers - -Watch for these signals that a learning should become a skill: - -**In conversation:** -- "Save this as a skill" -- "I keep running into this" -- "This would be useful for other projects" -- "Remember this pattern" - -**In learning entries:** -- Multiple `See Also` links (recurring issue) -- High priority + resolved status -- Category: `best_practice` with broad applicability -- User feedback praising the solution - -### Skill Quality Gates - -Before extraction, verify: - -- [ ] Solution is tested and working -- [ ] Description is clear without original context -- [ ] Code examples are self-contained -- [ ] No project-specific hardcoded values -- [ ] Follows skill naming conventions (lowercase, hyphens) diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/best-practices.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/best-practices.md deleted file mode 100644 index f995f5b..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/best-practices.md +++ /dev/null @@ -1,10 +0,0 @@ -## Best Practices - -1. **Log immediately** - context is freshest right after the issue -2. **Be specific** - future agents need to understand quickly -3. **Include reproduction steps** - especially for errors -4. **Link related files** - makes fixes easier -5. **Suggest concrete fixes** - not just "investigate" -6. **Use consistent categories** - enables filtering -7. **Promote aggressively** - if in doubt, add to CLAUDE.md or .github/copilot-instructions.md -8. **Review regularly** - stale learnings lose value diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/build-dependencies.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/build-dependencies.md deleted file mode 100644 index 260725b..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/build-dependencies.md +++ /dev/null @@ -1,10 +0,0 @@ -## Build & Dependencies -- Package manager: pnpm (not npm) - use `pnpm install` -``` - -**Learning** (verbose): -> When modifying API endpoints, must regenerate TypeScript client. -> Forgetting this causes type mismatches at runtime. - -**In AGENTS.md** (actionable): -```markdown diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/detection-triggers.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/detection-triggers.md deleted file mode 100644 index d83f327..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/detection-triggers.md +++ /dev/null @@ -1,26 +0,0 @@ -## Detection Triggers - -Automatically log when you notice: - -**Corrections** (→ learning with `correction` category): -- "No, that's not right..." -- "Actually, it should be..." -- "You're wrong about..." -- "That's outdated..." - -**Feature Requests** (→ feature request): -- "Can you also..." -- "I wish you could..." -- "Is there a way to..." -- "Why can't you..." - -**Knowledge Gaps** (→ learning with `knowledge_gap` category): -- User provides information you didn't know -- Documentation you referenced is outdated -- API behavior differs from your understanding - -**Errors** (→ error entry): -- Command returns non-zero exit code -- Exception or stack trace -- Unexpected output or behavior -- Timeout or connection failure diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/err-yyyymmdd-xxx-skill_or_command_name.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/err-yyyymmdd-xxx-skill_or_command_name.md deleted file mode 100644 index 85e19ae..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/err-yyyymmdd-xxx-skill_or_command_name.md +++ /dev/null @@ -1,36 +0,0 @@ -## [ERR-YYYYMMDD-XXX] skill_or_command_name - -**Logged**: ISO-8601 timestamp -**Priority**: high -**Status**: pending -**Area**: frontend | backend | infra | tests | docs | config - -### Summary -Brief description of what failed - -### Error -``` -Actual error message or output -``` - -### Context -- Command/operation attempted -- Input or parameters used -- Environment details if relevant - -### Suggested Fix -If identifiable, what might resolve this - -### Metadata -- Reproducible: yes | no | unknown -- Related Files: path/to/file.ext -- See Also: ERR-20250110-001 (if recurring) - ---- -``` - -### Feature Request Entry - -Append to `.learnings/FEATURE_REQUESTS.md`: - -```markdown diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/feat-yyyymmdd-xxx-capability_name.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/feat-yyyymmdd-xxx-capability_name.md deleted file mode 100644 index 52f409c..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/feat-yyyymmdd-xxx-capability_name.md +++ /dev/null @@ -1,25 +0,0 @@ -## [FEAT-YYYYMMDD-XXX] capability_name - -**Logged**: ISO-8601 timestamp -**Priority**: medium -**Status**: pending -**Area**: frontend | backend | infra | tests | docs | config - -### Requested Capability -What the user wanted to do - -### User Context -Why they needed it, what problem they're solving - -### Complexity Estimate -simple | medium | complex - -### Suggested Implementation -How this could be built, what it might extend - -### Metadata -- Frequency: first_time | recurring -- Related Features: existing_feature_name - ---- -``` diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/generic-setup-other-agents.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/generic-setup-other-agents.md deleted file mode 100644 index 5281243..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/generic-setup-other-agents.md +++ /dev/null @@ -1,20 +0,0 @@ -## Generic Setup (Other Agents) - -For Claude Code, Codex, Copilot, or other agents, create `.learnings/` in your project: - -```bash -mkdir -p .learnings -``` - -Copy templates from `assets/` or create files with headers. - -### Add reference to agent files AGENTS.md, CLAUDE.md, or .github/copilot-instructions.md to remind yourself to log learnings. (this is an alternative to hook-based reminders) - -#### Self-Improvement Workflow - -When errors or corrections occur: -1. Log to `.learnings/ERRORS.md`, `LEARNINGS.md`, or `FEATURE_REQUESTS.md` -2. Review and promote broadly applicable learnings to: - - `CLAUDE.md` - project facts and conventions - - `AGENTS.md` - workflows and automation - - `.github/copilot-instructions.md` - Copilot context diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/gitignore-options.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/gitignore-options.md deleted file mode 100644 index 2b91766..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/gitignore-options.md +++ /dev/null @@ -1,15 +0,0 @@ -## Gitignore Options - -**Keep learnings local** (per-developer): -```gitignore -.learnings/ -``` - -**Track learnings in repo** (team-wide): -Don't add to .gitignore - learnings become shared knowledge. - -**Hybrid** (track templates, ignore entries): -```gitignore -.learnings/*.md -!.learnings/.gitkeep -``` diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/hook-integration.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/hook-integration.md deleted file mode 100644 index b8e8af9..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/hook-integration.md +++ /dev/null @@ -1,55 +0,0 @@ -## Hook Integration - -Enable automatic reminders through agent hooks. This is **opt-in** - you must explicitly configure hooks. - -### Quick Setup (Claude Code / Codex) - -Create `.claude/settings.json` in your project: - -```json -{ - "hooks": { - "UserPromptSubmit": [{ - "matcher": "", - "hooks": [{ - "type": "command", - "command": "./skills/self-improvement/scripts/activator.sh" - }] - }] - } -} -``` - -This injects a learning evaluation reminder after each prompt (~50-100 tokens overhead). - -### Full Setup (With Error Detection) - -```json -{ - "hooks": { - "UserPromptSubmit": [{ - "matcher": "", - "hooks": [{ - "type": "command", - "command": "./skills/self-improvement/scripts/activator.sh" - }] - }], - "PostToolUse": [{ - "matcher": "Bash", - "hooks": [{ - "type": "command", - "command": "./skills/self-improvement/scripts/error-detector.sh" - }] - }] - } -} -``` - -### Available Hook Scripts - -| Script | Hook Type | Purpose | -|--------|-----------|---------| -| `scripts/activator.sh` | UserPromptSubmit | Reminds to evaluate learnings after tasks | -| `scripts/error-detector.sh` | PostToolUse (Bash) | Triggers on command errors | - -See `references/hooks-setup.md` for detailed configuration and troubleshooting. diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/id-generation.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/id-generation.md deleted file mode 100644 index a893a61..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/id-generation.md +++ /dev/null @@ -1,8 +0,0 @@ -## ID Generation - -Format: `TYPE-YYYYMMDD-XXX` -- TYPE: `LRN` (learning), `ERR` (error), `FEAT` (feature) -- YYYYMMDD: Current date -- XXX: Sequential number or random 3 chars (e.g., `001`, `A7B`) - -Examples: `LRN-20250115-001`, `ERR-20250115-A3F`, `FEAT-20250115-002` diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/logging-format.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/logging-format.md deleted file mode 100644 index e28f808..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/logging-format.md +++ /dev/null @@ -1,7 +0,0 @@ -## Logging Format - -### Learning Entry - -Append to `.learnings/LEARNINGS.md`: - -```markdown diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/lrn-yyyymmdd-xxx-category.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/lrn-yyyymmdd-xxx-category.md deleted file mode 100644 index 148b9a3..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/lrn-yyyymmdd-xxx-category.md +++ /dev/null @@ -1,34 +0,0 @@ -## [LRN-YYYYMMDD-XXX] category - -**Logged**: ISO-8601 timestamp -**Priority**: low | medium | high | critical -**Status**: pending -**Area**: frontend | backend | infra | tests | docs | config - -### Summary -One-line description of what was learned - -### Details -Full context: what happened, what was wrong, what's correct - -### Suggested Action -Specific fix or improvement to make - -### Metadata -- Source: conversation | error | user_feedback -- Related Files: path/to/file.ext -- Tags: tag1, tag2 -- See Also: LRN-20250110-001 (if related to existing entry) -- Pattern-Key: simplify.dead_code | harden.input_validation (optional, for recurring-pattern tracking) -- Recurrence-Count: 1 (optional) -- First-Seen: 2025-01-15 (optional) -- Last-Seen: 2025-01-15 (optional) - ---- -``` - -### Error Entry - -Append to `.learnings/ERRORS.md`: - -```markdown diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/multi-agent-support.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/multi-agent-support.md deleted file mode 100644 index 18f0165..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/multi-agent-support.md +++ /dev/null @@ -1,22 +0,0 @@ -## Multi-Agent Support - -This skill works across different AI coding agents with agent-specific activation. - -### Claude Code - -**Activation**: Hooks (UserPromptSubmit, PostToolUse) -**Setup**: `.claude/settings.json` with hook configuration -**Detection**: Automatic via hook scripts - -### Codex CLI - -**Activation**: Hooks (same pattern as Claude Code) -**Setup**: `.codex/settings.json` with hook configuration -**Detection**: Automatic via hook scripts - -### GitHub Copilot - -**Activation**: Manual (no hook support) -**Setup**: Add to `.github/copilot-instructions.md`: - -```markdown diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/openclaw-setup-recommended.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/openclaw-setup-recommended.md deleted file mode 100644 index 9490443..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/openclaw-setup-recommended.md +++ /dev/null @@ -1,81 +0,0 @@ -## OpenClaw Setup (Recommended) - -OpenClaw is the primary platform for this skill. It uses workspace-based prompt injection with automatic skill loading. - -### Installation - -**Via ClawdHub (recommended):** -```bash -clawdhub install self-improving-agent -``` - -**Manual:** -```bash -git clone https://github.com/peterskoett/self-improving-agent.git ${OPENCLAW_HOME}/skills/self-improving-agent -``` - -Remade for openclaw from original repo : https://github.com/pskoett/pskoett-ai-skills - https://github.com/pskoett/pskoett-ai-skills/tree/main/skills/self-improvement - -### Workspace Structure - -OpenClaw injects these files into every session: - -``` -${OPENCLAW_WORKSPACE}/ -├── AGENTS.md # Multi-agent workflows, delegation patterns -├── SOUL.md # Behavioral guidelines, personality, principles -├── TOOLS.md # Tool capabilities, integration gotchas -├── MEMORY.md # Long-term memory (main session only) -├── memory/ # Daily memory files -│ └── YYYY-MM-DD.md -└── .learnings/ # This skill's log files - ├── LEARNINGS.md - ├── ERRORS.md - └── FEATURE_REQUESTS.md -``` - -### Create Learning Files - -```bash -mkdir -p ${OPENCLAW_WORKSPACE}/.learnings -``` - -Then create the log files (or copy from `assets/`): -- `LEARNINGS.md` — corrections, knowledge gaps, best practices -- `ERRORS.md` — command failures, exceptions -- `FEATURE_REQUESTS.md` — user-requested capabilities - -### Promotion Targets - -When learnings prove broadly applicable, promote them to workspace files: - -| Learning Type | Promote To | Example | -|---------------|------------|---------| -| Behavioral patterns | `SOUL.md` | "Be concise, avoid disclaimers" | -| Workflow improvements | `AGENTS.md` | "Spawn sub-agents for long tasks" | -| Tool gotchas | `TOOLS.md` | "Git push needs auth configured first" | - -### Inter-Session Communication - -OpenClaw provides tools to share learnings across sessions: - -- **sessions_list** — View active/recent sessions -- **sessions_history** — Read another session's transcript -- **sessions_send** — Send a learning to another session -- **sessions_spawn** — Spawn a sub-agent for background work - -### Optional: Enable Hook - -For automatic reminders at session start: - -```bash -# Copy hook to OpenClaw hooks directory -cp -r hooks/openclaw ${OPENCLAW_HOME}/hooks/self-improvement - -# Enable it -openclaw hooks enable self-improvement -``` - -See `references/openclaw-integration.md` for complete details. - ---- diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/periodic-review.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/periodic-review.md deleted file mode 100644 index 6bf4ab0..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/periodic-review.md +++ /dev/null @@ -1,27 +0,0 @@ -## Periodic Review - -Review `.learnings/` at natural breakpoints: - -### When to Review -- Before starting a new major task -- After completing a feature -- When working in an area with past learnings -- Weekly during active development - -### Quick Status Check -```bash -# Count pending items -grep -h "Status\*\*: pending" .learnings/*.md | wc -l - -# List pending high-priority items -grep -B5 "Priority\*\*: high" .learnings/*.md | grep "^## \[" - -# Find learnings for a specific area -grep -l "Area\*\*: backend" .learnings/*.md -``` - -### Review Actions -- Resolve fixed items -- Promote applicable learnings -- Link related entries -- Escalate recurring issues diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/priority-guidelines.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/priority-guidelines.md deleted file mode 100644 index 44ac7a5..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/priority-guidelines.md +++ /dev/null @@ -1,8 +0,0 @@ -## Priority Guidelines - -| Priority | When to Use | -|----------|-------------| -| `critical` | Blocks core functionality, data loss risk, security issue | -| `high` | Significant impact, affects common workflows, recurring issue | -| `medium` | Moderate impact, workaround exists | -| `low` | Minor inconvenience, edge case, nice-to-have | diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/promoting-to-project-memory.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/promoting-to-project-memory.md deleted file mode 100644 index 4182166..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/promoting-to-project-memory.md +++ /dev/null @@ -1,37 +0,0 @@ -## Promoting to Project Memory - -When a learning is broadly applicable (not a one-off fix), promote it to permanent project memory. - -### When to Promote - -- Learning applies across multiple files/features -- Knowledge any contributor (human or AI) should know -- Prevents recurring mistakes -- Documents project-specific conventions - -### Promotion Targets - -| Target | What Belongs There | -|--------|-------------------| -| `CLAUDE.md` | Project facts, conventions, gotchas for all Claude interactions | -| `AGENTS.md` | Agent-specific workflows, tool usage patterns, automation rules | -| `.github/copilot-instructions.md` | Project context and conventions for GitHub Copilot | -| `SOUL.md` | Behavioral guidelines, communication style, principles (OpenClaw workspace) | -| `TOOLS.md` | Tool capabilities, usage patterns, integration gotchas (OpenClaw workspace) | - -### How to Promote - -1. **Distill** the learning into a concise rule or fact -2. **Add** to appropriate section in target file (create file if needed) -3. **Update** original entry: - - Change `**Status**: pending` → `**Status**: promoted` - - Add `**Promoted**: CLAUDE.md`, `AGENTS.md`, or `.github/copilot-instructions.md` - -### Promotion Examples - -**Learning** (verbose): -> Project uses pnpm workspaces. Attempted `npm install` but failed. -> Lock file is `pnpm-lock.yaml`. Must use `pnpm install`. - -**In CLAUDE.md** (concise): -```markdown diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/recurring-pattern-detection.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/recurring-pattern-detection.md deleted file mode 100644 index fb452f7..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/recurring-pattern-detection.md +++ /dev/null @@ -1,11 +0,0 @@ -## Recurring Pattern Detection - -If logging something similar to an existing entry: - -1. **Search first**: `grep -r "keyword" .learnings/` -2. **Link entries**: Add `**See Also**: ERR-20250110-001` in Metadata -3. **Bump priority** if issue keeps recurring -4. **Consider systemic fix**: Recurring issues often indicate: - - Missing documentation (→ promote to CLAUDE.md or .github/copilot-instructions.md) - - Missing automation (→ add to AGENTS.md) - - Architectural problem (→ create tech debt ticket) diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/resolving-entries.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/resolving-entries.md deleted file mode 100644 index 9357e96..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/resolving-entries.md +++ /dev/null @@ -1,18 +0,0 @@ -## Resolving Entries - -When an issue is fixed, update the entry: - -1. Change `**Status**: pending` → `**Status**: resolved` -2. Add resolution block after Metadata: - -```markdown -### Resolution -- **Resolved**: 2025-01-16T09:00:00Z -- **Commit/PR**: abc123 or #42 -- **Notes**: Brief description of what was done -``` - -Other status values: -- `in_progress` - Actively being worked on -- `wont_fix` - Decided not to address (add reason in Resolution notes) -- `promoted` - Elevated to CLAUDE.md, AGENTS.md, or .github/copilot-instructions.md diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/self-improvement.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/self-improvement.md deleted file mode 100644 index b7db275..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/self-improvement.md +++ /dev/null @@ -1,38 +0,0 @@ -## Self-Improvement - -After solving non-obvious issues, consider logging to `.learnings/`: -1. Use format from self-improvement skill -2. Link related entries with See Also -3. Promote high-value learnings to skills - -Ask in chat: "Should I log this as a learning?" -``` - -**Detection**: Manual review at session end - -### OpenClaw - -**Activation**: Workspace injection + inter-agent messaging -**Setup**: See "OpenClaw Setup" section above -**Detection**: Via session tools and workspace files - -### Agent-Agnostic Guidance - -Regardless of agent, apply self-improvement when you: - -1. **Discover something non-obvious** - solution wasn't immediate -2. **Correct yourself** - initial approach was wrong -3. **Learn project conventions** - discovered undocumented patterns -4. **Hit unexpected errors** - especially if diagnosis was difficult -5. **Find better approaches** - improved on your original solution - -### Copilot Chat Integration - -For Copilot users, add this to your prompts when relevant: - -> After completing this task, evaluate if any learnings should be logged to `.learnings/` using the self-improvement skill format. - -Or use quick prompts: -- "Log this to learnings" -- "Create a skill from this solution" -- "Check .learnings/ for related issues" diff --git a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/simplify-harden-feed.md b/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/simplify-harden-feed.md deleted file mode 100644 index 42539d5..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/self-improving-agent/references/simplify-harden-feed.md +++ /dev/null @@ -1,36 +0,0 @@ -## Simplify & Harden Feed - -Use this workflow to ingest recurring patterns from the `simplify-and-harden` -skill and turn them into durable prompt guidance. - -### Ingestion Workflow - -1. Read `simplify_and_harden.learning_loop.candidates` from the task summary. -2. For each candidate, use `pattern_key` as the stable dedupe key. -3. Search `.learnings/LEARNINGS.md` for an existing entry with that key: - - `grep -n "Pattern-Key: " .learnings/LEARNINGS.md` -4. If found: - - Increment `Recurrence-Count` - - Update `Last-Seen` - - Add `See Also` links to related entries/tasks -5. If not found: - - Create a new `LRN-...` entry - - Set `Source: simplify-and-harden` - - Set `Pattern-Key`, `Recurrence-Count: 1`, and `First-Seen`/`Last-Seen` - -### Promotion Rule (System Prompt Feedback) - -Promote recurring patterns into agent context/system prompt files when all are true: - -- `Recurrence-Count >= 3` -- Seen across at least 2 distinct tasks -- Occurred within a 30-day window - -Promotion targets: -- `CLAUDE.md` -- `AGENTS.md` -- `.github/copilot-instructions.md` -- `SOUL.md` / `TOOLS.md` for OpenClaw workspace-level guidance when applicable - -Write promoted rules as short prevention rules (what to do before/while coding), -not long incident write-ups. diff --git a/plugins/antianqi/skill-bridge/examples/output/task-tracker/SKILL.md b/plugins/antianqi/skill-bridge/examples/output/task-tracker/SKILL.md deleted file mode 100644 index 98b535f..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/task-tracker/SKILL.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -name: task-tracker -description: 'Use when: 任务追踪与日报周报生成。用于记录老板工作进度、生成日报周报、持续追踪任务完成情况。.' -displayNames: - zh-Hans: Task Tracker - 任务追踪与日报周报 -metadata: - openclaw_compat: true - skill-bridge: - classify_tier: pure - classify_subtier: pure-wrapped-fix - classify_reason: 1 hardcoded path group(s) found ---- - -# Task Tracker - 任务追踪与日报周报 - -## 核心文件 -- 任务总表:`${OPENCLAW_WORKSPACE}/TASKS.md` - -## 任务格式规范 - -### 日报格式(必须遵守) -- 内容顺序:**①直播 ②短视频 ③外卖 ④其他** -- 不显示大分类标题,直接按顺序列序号 -- **不用任何符号**(✅❌🔄等都不用) -- 发到飞书,用文字不用语音 -- **输出时:完整输出 TASKS.md 里记录的详细内容和进度,不简化** - -### 明日计划原则 -- **持续跟进的项必须列入**(如:城乡路京东外卖持续跟进) -- **今日新提到的跟进项也列入**(如:美团收银报价跟进) -- 不在本周计划里但老板提到的新任务 → 追加进明日计划 - -### 重要区分 -- **日报只记老板的工作**(品牌运营 + 线上运营 + 外卖 + 品牌营销) -- **数据统计填表是狗蛋的工作,不记入日报** -- **系统升级、工具配置等狗蛋研发工作不记入日报** -- **狗蛋自己的研发/学习/技能提升工作不记入日报**,只记入 memory/daily/YYYY-MM-DD.md -- 老板告诉我进展 → 更新 TASKS.md(详细记录) -- 我自己的研发进展 → 更新 memory/daily/YYYY-MM-DD.md - -### 重要区分 -- **日报只记老板的工作**(品牌运营 + 线上运营 + 外卖 + 品牌营销) -- **数据统计填表是狗蛋的工作,不记入日报** -- **狗蛋自己的研发/学习/技能提升工作不记入日报**,只记入 memory/daily/YYYY-MM-DD.md -- 老板告诉我进展 → 更新 TASKS.md(详细记录) -- 我自己的研发进展 → 更新 memory/daily/YYYY-MM-DD.md - -``` -老板日报(YYYY-MM-DD) -今日工作: -1. ... -2. ... -明日计划: -1. ... -2. ... -``` - -### 周报格式 -同日报格式,周六汇总一周数据+工作内容 - -### 任务格式 -``` -### 今日进展(YYYY-MM-DD) -- 具体工作内容 - -### 明日计划 -- 延续任务(带进度说明) -- 新增任务 -``` - -### 任务状态规则 -- 今日未完成的 → 记录到明日计划 -- 本周未完成的 → 记录到下周计划 -- 狗蛋自己的研发/学习工作 → 不记录 - -## 使用场景 - -### 记录进展 -老板告诉你工作进展 → 更新 TASKS.md - -### 查询进度 -老板问"现在任务进度" → 读取 TASKS.md 输出当前任务清单 - -### 生成日报 -老板说"写日报" → 从 TASKS.md 当前日进展生成格式化日报,发到飞书 - -### 生成周报 -老板说"写周报" → 从 TASKS.md 本周任务+进展生成,发到飞书 - -### 任务完成 -老板说某任务完成了 → 更新 TASKS.md 中该任务状态为"已完成",标注日期 - -### 新增任务 -老板布置新任务 → 追加到 TASKS.md 当前周任务列表 - -## 追踪文件路径 -`${OPENCLAW_WORKSPACE}/TASKS.md` - -## Output contract - -This skill does not produce files by itself; the converted openclaw skill should declare its outputs in a new section here. (Filled in by the user after first run.) - -## Failure handling - -If a required external tool or path is missing, surface the exact missing identifier to the user instead of guessing. Do not auto-install system packages. (Add skill-specific failure modes here.) diff --git a/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md b/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md deleted file mode 100644 index 192f816..0000000 --- a/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md +++ /dev/null @@ -1,21 +0,0 @@ -# Conversion report - -- **input**: `examples\input\task-tracker\SKILL.md` -- **tier**: pure / pure-wrapped-fix -- **reason**: 1 hardcoded path group(s) found - -## Path changes -- `openclaw-workspace` → ${OPENCLAW_WORKSPACE} (2x) - -## Written files - - -## Recommendations -- parameterize paths via paths.js -- ensure UTF-8 output -- add Windows adaptation section if body uses shell commands - -## Warnings -- paths parameterized: openclaw-workspace - -_generated by skill-bridge v0.1.0 on 2026-08-15T00:53:45.995Z_ diff --git a/plugins/antianqi/skill-bridge/index.js b/plugins/antianqi/skill-bridge/index.js deleted file mode 100644 index 3322753..0000000 --- a/plugins/antianqi/skill-bridge/index.js +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env node -// index.js — mcode-skill-bridge CLI -// -// Subcommands: -// detect print encoding detection result -// analyze print analysis report (frontmatter, paths, external cmds) -// classify print tier + reason -// convert run the full pipeline, write to --out -// lint run the mavis skill-creator lint -// -// When given a directory, we look for SKILL.md inside it. - -import fs from 'node:fs/promises'; -import path from 'node:path'; -import { detectEncoding, readFileSafe } from './lib/detect.js'; -import { analyzeSkillFile } from './lib/analyze.js'; -import { classify } from './lib/classify.js'; -import { transformSkill } from './lib/transform-skill.js'; -import { lintSkill } from './lib/lint.js'; - -const USAGE = `mcode-skill-bridge — convert openclaw (and similar) skills to mavis/mcode - -Usage: - mcode-skill-bridge detect Detect encoding of a SKILL.md - mcode-skill-bridge analyze Analyze (frontmatter, paths, external cmds) - mcode-skill-bridge classify Classify into pure / wrapped / abandon - mcode-skill-bridge convert Convert and write to --out - mcode-skill-bridge lint Lint a converted skill - mcode-skill-bridge --help - -Options: - --out Output directory (default: ./out/) - --force Overwrite existing output - --no-lint Skip lint after convert - --scope user | agent | project (informational only, used in report) - --json Machine-readable output -`; - -function parseArgs(argv) { - const args = { _: [], opts: {} }; - for (let i = 0; i < argv.length; i++) { - const a = argv[i]; - if (a.startsWith('--')) { - const k = a.slice(2); - const next = argv[i + 1]; - if (next && !next.startsWith('--')) { - args.opts[k] = next; - i++; - } else { - args.opts[k] = true; - } - } else { - args._.push(a); - } - } - return args; -} - -async function resolveInput(p) { - const stat = await fs.stat(p).catch(() => null); - if (!stat) throw new Error(`input not found: ${p}`); - if (stat.isDirectory()) { - const candidate = path.join(p, 'SKILL.md'); - await fs.access(candidate); - return candidate; - } - return p; -} - -function jsonOut(obj) { - process.stdout.write(JSON.stringify(obj, null, 2) + '\n'); -} - -async function cmdDetect(target, opts) { - const det = await readFileSafe(target); - if (opts.json) return jsonOut(det); - console.log(`encoding: ${det.encoding}`); - console.log(`original: ${det.originalEncoding}`); - console.log(`replaced: ${det.replaced}`); - console.log(`confidence: ${det.confidence}`); - console.log(`reason: ${det.reason}`); - console.log(`text length: ${det.text.length} chars`); -} - -async function cmdAnalyze(target, opts) { - const report = await analyzeSkillFile(target); - if (opts.json) return jsonOut(report); - console.log(`input: ${report.inputPath}`); - console.log(`encoding: ${report.encoding} (converted=${report.convertedFromGbk})`); - console.log(`frontmatter: ${Object.keys(report.frontmatter).join(', ') || '(empty)'}`); - console.log(``); - console.log(`hardcoded paths:`); - for (const p of report.hardcodedPaths) console.log(` - ${p.label}: ${p.samples.join(', ')}`); - if (report.hardcodedPaths.length === 0) console.log(` (none)`); - console.log(``); - console.log(`external commands:`); - for (const c of report.externalCommands) console.log(` - ${c.label}: ${c.samples.join(', ')}`); - if (report.externalCommands.length === 0) console.log(` (none)`); - if (report.warnings.length) { - console.log(``); - console.log(`warnings:`); - for (const w of report.warnings) console.log(` - ${w}`); - } -} - -async function cmdClassify(target, opts) { - const report = await analyzeSkillFile(target); - const result = classify(report); - if (opts.json) return jsonOut({ report: { inputPath: report.inputPath, encoding: report.encoding }, result }); - console.log(`tier: ${result.tier}`); - console.log(`subTier: ${result.subTier}`); - console.log(`reason: ${result.reason}`); - console.log(``); - console.log(`recommendations:`); - for (const r of result.recommendations) console.log(` - ${r}`); -} - -async function cmdConvert(target, opts) { - const report = await analyzeSkillFile(target); - const result = classify(report); - if (result.tier === 'abandon') { - console.error(`abandon: ${result.reason}`); - process.exitCode = 2; - return; - } - if (result.tier !== 'pure') { - console.error(`convert: tier "${result.tier}" not yet supported in v0.1 (only pure). See plan §6.`); - process.exitCode = 3; - return; - } - const outDir = opts.out - ? path.resolve(String(opts.out)) - : path.resolve('./out', path.basename(path.dirname(target))); - if (!opts.force) { - const exists = await fs.stat(outDir).catch(() => null); - if (exists) { - console.error(`output already exists: ${outDir} (use --force to overwrite)`); - process.exitCode = 4; - return; - } - } - const r = await transformSkill({ - inputPath: target, - report, - classify: result, - outDir, - }); - console.log(`wrote ${r.written.length} files to ${outDir}`); - for (const w of r.written) console.log(` - ${w}`); - if (r.warnings.length) { - console.log(``); - console.log(`warnings:`); - for (const w of r.warnings) console.log(` - ${w}`); - } - if (opts['no-lint']) return; - const lintResult = await lintSkill(outDir); - if (lintResult.ok) { - console.log(``); - console.log(`lint: PASS`); - } else { - console.log(``); - console.log(`lint: WARN (exit=${lintResult.code})`); - if (lintResult.stdout) console.log(lintResult.stdout); - if (lintResult.stderr) console.log(lintResult.stderr); - } -} - -async function cmdLint(target, opts) { - const r = await lintSkill(target); - if (r.ok) { - console.log(`lint: PASS`); - } else { - console.log(`lint: FAIL (exit=${r.code})`); - if (r.stdout) console.log(r.stdout); - if (r.stderr) console.log(r.stderr); - process.exitCode = 1; - } -} - -async function main() { - const args = parseArgs(process.argv.slice(2)); - if (args._.length === 0 || args.opts.help || args.opts.h) { - process.stdout.write(USAGE); - return; - } - const cmd = args._[0]; - const target = args._[1]; - if (!target) { - console.error(`missing input for command: ${cmd}`); - process.exitCode = 1; - return; - } - - try { - const resolved = ['detect', 'analyze', 'classify', 'convert'].includes(cmd) - ? await resolveInput(target) - : path.resolve(target); - switch (cmd) { - case 'detect': return await cmdDetect(resolved, args.opts); - case 'analyze': return await cmdAnalyze(resolved, args.opts); - case 'classify': return await cmdClassify(resolved, args.opts); - case 'convert': return await cmdConvert(resolved, args.opts); - case 'lint': return await cmdLint(resolved, args.opts); - default: - console.error(`unknown command: ${cmd}`); - process.stdout.write(USAGE); - process.exitCode = 1; - } - } catch (e) { - console.error(`error: ${e.message}`); - process.exitCode = 1; - } -} - -main(); diff --git a/plugins/antianqi/skill-bridge/package-lock.json b/plugins/antianqi/skill-bridge/package-lock.json deleted file mode 100644 index 4cfd0e5..0000000 --- a/plugins/antianqi/skill-bridge/package-lock.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "skill-bridge", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "skill-bridge", - "version": "0.1.0", - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "js-yaml": "^4.1.0" - }, - "bin": { - "mcode-skill-bridge": "index.js" - }, - "engines": { - "node": ">=22.19 <23 || >=24 <27" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - } - } -} diff --git a/plugins/antianqi/skill-bridge/package.json b/plugins/antianqi/skill-bridge/package.json deleted file mode 100644 index f90575a..0000000 --- a/plugins/antianqi/skill-bridge/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "skill-bridge", - "version": "0.1.0", - "description": "Convert openclaw (and similar) skills into mavis/mcode-compatible skills or plugins.", - "type": "module", - "main": "index.js", - "bin": { - "mcode-skill-bridge": "index.js" - }, - "author": "antianqi", - "scripts": { - "test": "node --test tests/*.test.mjs", - "demo:task-tracker": "node index.js convert examples/input/task-tracker --out examples/output/task-tracker", - "demo:investor-brand-kit": "node index.js convert examples/input/investor-brand-kit --out examples/output/investor-brand-kit", - "demo:self-improving-agent": "node index.js convert examples/input/self-improving-agent --out examples/output/self-improving-agent", - "demo:all": "npm run demo:task-tracker && npm run demo:investor-brand-kit && npm run demo:self-improving-agent" - }, - "engines": { - "node": ">=22.19 <23 || >=24 <27" - }, - "dependencies": { - "iconv-lite": "^0.6.3", - "js-yaml": "^4.1.0" - }, - "files": [ - "index.js", - "lib/", - "skills/", - "references/", - "README.md", - "LICENSE" - ], - "license": "MIT", - "keywords": [ - "mcode", - "mavis", - "skill", - "openclaw", - "migration", - "converter" - ] -} diff --git a/plugins/antianqi/skill-bridge/tests/cli.test.mjs b/plugins/antianqi/skill-bridge/tests/cli.test.mjs deleted file mode 100644 index 030c9c3..0000000 --- a/plugins/antianqi/skill-bridge/tests/cli.test.mjs +++ /dev/null @@ -1,67 +0,0 @@ -// tests/cli.test.mjs -import { test } from 'node:test'; -import assert from 'node:assert/strict'; -import { spawn } from 'node:child_process'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import fs from 'node:fs/promises'; -import os from 'node:os'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const CLI = path.join(__dirname, '..', 'index.js'); - -function run(args, opts = {}) { - return new Promise((resolve) => { - const child = spawn(process.execPath, [CLI, ...args], { - stdio: ['ignore', 'pipe', 'pipe'], - windowsHide: true, - ...opts, - }); - let stdout = ''; - let stderr = ''; - child.stdout.on('data', d => stdout += d); - child.stderr.on('data', d => stderr += d); - child.on('close', (code) => resolve({ code, stdout, stderr })); - }); -} - -test('--help prints usage', async () => { - const r = await run(['--help']); - assert.equal(r.code, 0); - assert.ok(/mcode-skill-bridge/.test(r.stdout)); - assert.ok(/Usage:/.test(r.stdout)); -}); - -test('detect command on utf-8 file', async () => { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-cli-')); - const file = path.join(dir, 'SKILL.md'); - await fs.writeFile(file, '---\nname: x\ndescription: y\n---\n\n# X\n', 'utf-8'); - const r = await run(['detect', file]); - assert.equal(r.code, 0); - assert.ok(/encoding:\s+utf-8/.test(r.stdout), `got: ${r.stdout}`); - await fs.rm(dir, { recursive: true, force: true }); -}); - -test('classify command on a pure-instruction skill', async () => { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-cli-')); - const file = path.join(dir, 'SKILL.md'); - await fs.writeFile(file, '---\nname: y\ndescription: "A pure skill."\n---\n\n# Y\n\nJust instructions.\n', 'utf-8'); - const r = await run(['classify', file]); - assert.equal(r.code, 0); - assert.ok(/tier:\s+pure/.test(r.stdout), `got: ${r.stdout}`); - await fs.rm(dir, { recursive: true, force: true }); -}); - -test('convert writes output', async () => { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-cli-')); - const file = path.join(dir, 'SKILL.md'); - await fs.writeFile(file, - '---\nname: demo-skill\ndescription: "Demo."\n---\n\n# Demo\n\nUse /tmp/x for cache.\n', 'utf-8'); - const out = path.join(dir, 'out'); - const r = await run(['convert', file, '--out', out, '--no-lint']); - assert.equal(r.code, 0, `stderr: ${r.stderr}\nstdout: ${r.stdout}`); - const written = await fs.readdir(out); - assert.ok(written.includes('SKILL.md')); - assert.ok(written.includes('conversion-report.md')); - await fs.rm(dir, { recursive: true, force: true }); -}); From 3dfa15992b4661cf16760eb96f394105a263556d Mon Sep 17 00:00:00 2001 From: antianqi <75944423+antianqi@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:56:57 +0800 Subject: [PATCH 4/5] Restructure skill-bridge to a stdio MCP server plugin (v0.2.0) Per the maintainer's round-2 review on PR #3, the v0.1 npm-CLI delivery model is incompatible with the portable Agent Plugins 1.0 contract: - plugin install does not run npm install or npm link, so a plugin-local bin (mcode-skill-bridge) is never registered and never callable. - the npm dependencies (iconv-lite, js-yaml) are not installed when the plugin is consumed; the validator and the test runner both fail. - the converter spawned external commands in its SKILL.md, which is a non-starter for a portable Skill. v0.2 replaces the npm package with a self-contained MCP stdio server. Concretely: - Drop package.json, package-lock.json, index.js, and the CLI surface they imply. - Add mcp.json (one stdio MCP server) and server.mjs (the JSON-RPC-over-stdio server). The server exposes four tools: detect, analyze, classify, convert. - Rewrite the lib/ modules to use only Node built-ins. The encoding detector now uses TextDecoder('gb18030') instead of iconv-lite; the YAML subset parser is hand-rolled instead of pulling in js-yaml. - Rewrite skills/skill-bridge/SKILL.md to teach the agent to call the MCP tools instead of spawning a CLI. - Update the README to describe the MCP delivery model and the zero-dependency contract. Atomic-replace guarantee hardened: the backup-rename dance in lib/transform-skill.js is exercised by a new regression test that asserts a pre-existing outDir and its sentinel file are preserved when transformSkill rejects before any write. Demo set pruned: the two upstream openclaw demos that the v0.1 plugin carried (investor-brand-kit, self-improving-agent) are removed. investor-brand-kit contained end-user business data incompatible with a public plugin; self-improving-agent was a copy of a third-party pskoett-ai-skills repository whose license was not declared. The only demo shipped in v0.2 is task-tracker, the author's own content. Test count goes from 33 (v0.1) to 50, all green. The 'npm run check' failures that remain in the repository (CRLF line endings in examples/hello-mcode/SKILL.md; path.separator on Windows in hosted-plugins.test.mjs) are pre-existing and unrelated to this plugin. --- plugins/antianqi/skill-bridge/.gitignore | 11 +- plugins/antianqi/skill-bridge/LICENSE | 213 ++++++++++++++-- plugins/antianqi/skill-bridge/README.md | 224 +++++++---------- plugins/antianqi/skill-bridge/lib/analyze.js | 206 ++++++++++++++-- plugins/antianqi/skill-bridge/lib/detect.js | 97 ++++---- plugins/antianqi/skill-bridge/lib/lint.js | 54 ++--- .../skill-bridge/lib/transform-skill.js | 109 +++++---- plugins/antianqi/skill-bridge/mcp.json | 10 + plugins/antianqi/skill-bridge/plugin.json | 16 +- .../references/compatibility-matrix.md | 54 ++--- plugins/antianqi/skill-bridge/server.mjs | 228 ++++++++++++++++++ .../skill-bridge/skills/skill-bridge/SKILL.md | 106 ++++---- .../skill-bridge/tests/analyze.test.mjs | 111 +++++++++ .../skill-bridge/tests/detect.test.mjs | 70 ++++-- .../skill-bridge/tests/server.test.mjs | 191 +++++++++++++++ .../tests/transform-atomic.test.mjs | 120 +++++++++ 16 files changed, 1409 insertions(+), 411 deletions(-) create mode 100644 plugins/antianqi/skill-bridge/mcp.json create mode 100644 plugins/antianqi/skill-bridge/server.mjs create mode 100644 plugins/antianqi/skill-bridge/tests/analyze.test.mjs create mode 100644 plugins/antianqi/skill-bridge/tests/server.test.mjs create mode 100644 plugins/antianqi/skill-bridge/tests/transform-atomic.test.mjs diff --git a/plugins/antianqi/skill-bridge/.gitignore b/plugins/antianqi/skill-bridge/.gitignore index 451c6a4..4e7abb9 100644 --- a/plugins/antianqi/skill-bridge/.gitignore +++ b/plugins/antianqi/skill-bridge/.gitignore @@ -1,4 +1,7 @@ -node_modules/ -tests/last-run.log -tests/lint-debug.log -probe-*.mjs \ No newline at end of file +# Local probe / debug files +probe-*.mjs +# Backup dirs that the transformer may leave if it crashes mid-swap. +# (The transformer cleans these up on its own, but a crash before the +# cleanup leaves the dir around and we do not want to commit it.) +*.bak-* +*.staging-* diff --git a/plugins/antianqi/skill-bridge/LICENSE b/plugins/antianqi/skill-bridge/LICENSE index 4bea20a..ec5fe20 100644 --- a/plugins/antianqi/skill-bridge/LICENSE +++ b/plugins/antianqi/skill-bridge/LICENSE @@ -1,21 +1,192 @@ -MIT License - -Copyright (c) 2026 antianqi - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + Copyright 2026 MCode Plugins contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/antianqi/skill-bridge/README.md b/plugins/antianqi/skill-bridge/README.md index 652e890..c2124dc 100644 --- a/plugins/antianqi/skill-bridge/README.md +++ b/plugins/antianqi/skill-bridge/README.md @@ -1,13 +1,14 @@ # skill-bridge -> Convert openclaw (and similar) skills into mavis/mcode-compatible skills or plugins. +> Convert openclaw (and similar) skills into mavis/mcode-compatible skills, exposed as a stdio MCP server inside a portable Agent Plugin. -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -[![Node](https://img.shields.io/badge/node-%3E%3D22.19-brightgreen)](package.json) +[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) +[![Node](https://img.shields.io/badge/node-%3E%3D22.19-brightgreen)](mcp.json) +[![Agent Plugins 1.0](https://img.shields.io/badge/Agent_Plugins-1.0-8b5cf6)](https://agent-plugins.org) ## Why -`openclaw` (and other agent frameworks) and `mavis` / `mcode` don't share a skill format. The hard parts are: +`openclaw` (and other agent frameworks) and `mavis` / `mcode` do not share a skill format. The hard parts are: 1. **Schema gap** — openclaw skills are 2-field frontmatter; mavis needs `descriptions.zh-Hans`, `displayNames`, `metadata`, locale keys. 2. **Encoding gap** — openclaw wrote Chinese as GBK and filenames as mojibake. mavis requires UTF-8. @@ -15,185 +16,122 @@ 4. **Platform gap** — openclaw assumes `bash` / `pip install -e .` / `python3` in PATH. mavis (especially on Windows) needs PowerShell equivalents. 5. **Discovery gap** — openclaw's staging directory is not in mavis's skill scan path. Copying files there does nothing. -**skill-bridge** turns "copy the folder and pray" into a deterministic pipeline: detect → analyze → classify → transform → lint. +**skill-bridge** turns "copy the folder and pray" into a deterministic pipeline: `detect` → `analyze` → `classify` → `transform` → `lint`, exposed as four MCP tools and driven by the matching Skill (`skills/skill-bridge/SKILL.md`). -## Install +## How it ships -```bash -# from a clone of this repo -npm install -npm link # so `mcode-skill-bridge` is on PATH -# OR via mcode plugin install (after this is published): -# mcode plugin add https://github.com/antianqi/skill-bridge +This repository follows the [portable Agent Plugins 1.0 contract](https://github.com/hetaoBackend/MiniMax-Code-Plugins/blob/main/docs/plugin-compatibility.md): + +```text +plugins/antianqi/skill-bridge/ +├── plugin.json # the plugin manifest +├── mcp.json # the stdio MCP server +├── server.mjs # the MCP server itself +├── lib/ # pure ESM, zero npm deps +├── skills/skill-bridge/ # the LLM-facing Skill +├── references/ # human-facing docs +├── examples/ # input + output demo +└── tests/ # node --test ``` -Requires **Node.js 22.19+ or 24+** (matches the mcode engine). +No `package.json`, no `node_modules`, no install step. The portable plugin is read by MiniMax Code exactly the way it is checked into `main`. -## Quick start +## What the MCP server exposes -```bash -# 1. Look at one openclaw skill -mcode-skill-bridge analyze /path/to/openclaw/skills/task-tracker +The server speaks JSON-RPC over stdio. It declares four tools, named after the original v0.1 CLI subcommands: + +| Tool | Returns | +| --- | --- | +| `detect(source)` | `{ encoding, originalEncoding, replaced, confidence, reason, text }` | +| `analyze(source)` | `{ frontmatter, body, hardcodedPaths, externalCommands, warnings, … }` | +| `classify(source)` | `{ tier, subTier, reason, recommendations }` | +| `convert(source, target_dir, force?, run_lint?)` | `{ ok, tier, subTier, written, warnings, lint }` | + +`source` accepts an absolute path to a `SKILL.md` file or to a folder containing one. `target_dir` is the absolute path the converted skill should be written to. The transform step is **atomic** — re-running with the same `target_dir` is always safe. + +The server requires only the Node.js that already ships with the host. It does not run `npm install`, does not register a global bin, does not write to the user's home directory. + +## Try the demo -# 2. See what tier it falls into -mcode-skill-bridge classify /path/to/openclaw/skills/task-tracker +The plugin ships a single conversion demo under `examples/output/task-tracker/`. It is the result of running: -# 3. Convert to a mavis-compatible skill -mcode-skill-bridge convert /path/to/openclaw/skills/task-tracker \ - --out ~/.minimax/agents/mavis/skills/task-tracker +```text +convert( + source = "examples/input/task-tracker/SKILL.md", + target_dir = "examples/output/task-tracker" +) ``` -After step 3, restart mavis (or start a new session) and the converted skill shows up in ``. +Inspect the result: + +```text +examples/output/task-tracker/ +├── SKILL.md # mavis-schema-compliant frontmatter, parameterized paths +└── conversion-report.md # what the converter changed and why +``` + +The original input is the openclaw `task-tracker` skill; the output is the same content brought up to the mavis schema. Open both side by side to see what the converter does. ## How it works -``` -input SKILL.md +```text +input SKILL.md (openclaw, possibly GBK, possibly with C:\Users paths) │ ▼ -[detect] GBK vs UTF-8; restore mojibake if needed +[detect] TextDecoder('gb18030') — built into Node 22+, no npm dep │ ▼ -[analyze] parse frontmatter, scan hardcoded paths, scan external commands +[analyze] hand-rolled YAML subset parser, path/command pattern scan │ ▼ -[classify] pure-translate | pure-wrapped-fix | wrapped-* | abandon +[classify] 4-question decision tree → pure / pure-wrapped-fix / wrapped / abandon │ ▼ -[transform] write new SKILL.md (+ optional references/) to mavis schema +[transform] atomic backup-rename into ; references/ split if body > 500 lines │ ▼ -[lint] run the official skill-creator lint on the output +[lint] invokes the host-installed skill-creator lint in a tmpdir │ ▼ -output: mavis-compatible skill +output: mavis-compatible skill at ``` -### The three tiers +### The tiers (also see `references/compatibility-matrix.md`) -| Tier | What it is | Output in v0.1 | +| Tier | What it is | What v0.2 does | |---|---|---| -| `pure-translate` | Pure instruction, ASCII-clean, no hardcoded paths | A `SKILL.md` with enriched frontmatter only | -| `pure-wrapped-fix` | Pure instruction but with hardcoded paths or GBK | A `SKILL.md` with paths parameterized + encoding fixed + Windows notes added | -| `wrapped-*` | Needs an external CLI/API (Python, ComfyUI, Douyin, …) | **Not supported in v0.1.** v0.2 will emit a plugin skeleton. | +| `pure-translate` | Pure instruction, ASCII-clean, no hardcoded paths | Frontmatter enrichment only | +| `pure-wrapped-fix` | Pure instruction with hardcoded paths or GBK | Paths parameterized + encoding fixed + Windows notes added | +| `wrapped-*` | Needs an external CLI/API (Python, ComfyUI, Douyin, …) | **Not supported in v0.2.** v0.3 will emit a plugin skeleton. | +| `abandon` | Openclaw-only assumptions can't be removed | Do not import | -## What's in v0.1 +## Requirements -- ✅ `lib/detect.js` — UTF-8 / GBK detection via `iconv-lite` + heuristic mojibake detection -- ✅ `lib/paths.js` — 6 hardcoded path patterns → `${OPENCLAW_HOME}`, `${OPENCLAW_WORKSPACE}`, `${SCRATCH}`, `${DATA_DIR}` -- ✅ `lib/analyze.js` — YAML frontmatter parse, hardcoded-path scan, external-command scan -- ✅ `lib/classify.js` — 4-question decision tree -- ✅ `lib/transform-skill.js` — frontmatter enrichment, body path rewriting, 500-line body splitter, Windows notes injection -- ✅ `lib/lint.js` — wraps the official `~/.minimax/.builtin-skills/skill-creator/scripts/lint-skill.js` (handles the `.js`-as-ESM quirk) -- ✅ `index.js` — CLI with `detect` / `analyze` / `classify` / `convert` / `lint` -- ✅ `skills/SKILL.md` — discoverable LLM entry (so a Mavis session can use it without remembering the CLI) -- ✅ 29 unit + integration tests -- ✅ 3 working demos (see `examples/output/`) +- Node.js 22.19+ or 24+ (matches the mcode engine). No other runtime. -## What's NOT in v0.1 +## Data and network -- ❌ `wrapped-*` → plugin skeleton generation (planned for v0.2) -- ❌ GBK **filename** restoration (we warn, we don't rename) -- ❌ npm publish (planned for v0.2) -- ❌ Reverse tool (mavis → openclaw) -- ❌ Auto-registration into mavis's scan path (you have to restart the session) +- No network access. +- No credentials required. +- Reads the source file the caller provides. +- Writes only to the caller-provided `target_dir` and to a unique `os.tmpdir()/sb-lint--/` directory that is removed after the lint step completes. -## Try the demos +## Validation ```bash -git clone https://github.com/antianqi/skill-bridge -cd skill-bridge -npm install -npm run demo:all -# inspect the output -ls examples/output/task-tracker -cat examples/output/task-tracker/SKILL.md -cat examples/output/task-tracker/conversion-report.md -``` - -The three demos cover the main pure-tier shapes: - -| Demo | What it stresses | -|---|---| -| `task-tracker` | Chinese name in source, hardcoded `${OPENCLAW_WORKSPACE}` path, no external deps | -| `investor-brand-kit` | CJK body with rich content, no path/encoding issues (pure-translate) | -| `self-improving-agent` | 600+ line body → automatically split into `references/` | - -## CLI reference - -``` -mcode-skill-bridge detect Detect encoding of a SKILL.md -mcode-skill-bridge analyze Analyze (frontmatter, paths, external cmds) -mcode-skill-bridge classify Classify into pure / wrapped / abandon -mcode-skill-bridge convert Convert and write to --out -mcode-skill-bridge lint Lint a converted skill - -Options: - --out Output directory (default: ./out/) - --force Overwrite existing output - --no-lint Skip lint after convert - --scope user | agent | project (informational) - --json Machine-readable output -``` - -## Project layout - -``` -skill-bridge/ -├── plugin.json # mcode plugin manifest -├── index.js # CLI entry -├── package.json -├── lib/ # pure ESM modules -│ ├── detect.js -│ ├── paths.js -│ ├── analyze.js -│ ├── classify.js -│ ├── transform-skill.js -│ └── lint.js -├── skills/ -│ └── SKILL.md # discoverable LLM entry -├── references/ # human docs -│ ├── compatibility-matrix.md -│ ├── path-patterns.md -│ └── encoding-tables.md -├── examples/ -│ ├── input/ # original openclaw skills (CC0 from openclaw) -│ └── output/ # converted mavis skills -└── tests/ - ├── detect.test.mjs - ├── paths.test.mjs - ├── classify.test.mjs - ├── transform-skill.test.mjs - └── cli.test.mjs +# from the repository root +npm ci +npm run check ``` -## Method — how we decided what's a "compatible skill" +CI runs the same `npm run check` on `ubuntu-latest` against Node 22. The validator + `node --test` exercise this Plugin's lib, server, and conversion pipeline. -See [`references/compatibility-matrix.md`](references/compatibility-matrix.md) for the full mapping of all 36 openclaw skills into the three tiers. +## Security -The high-level rule: - -> If the skill is a self-contained instruction (you can read it and act on it without installing anything else), it is `pure`. Otherwise, it is `wrapped`. If it depends on openclaw-specific runtime (e.g. the openclaw TUI, a specific Python venv, a non-replicable hard-coded directory), it is `abandon`. - -## Roadmap - -- **v0.2** — `wrapped-*` tier: generate a real mavis plugin (`plugin.json` + `index.js`) for skills that need external CLIs/APIs -- **v0.3** — Web UI via the `visual-page` skill, history-aware incremental conversion -- **v0.4** — Reverse tool: mavis skill → openclaw-compatible bundle - -## Contributing - -1. Fork the repo. -2. Add a fixture under `tests/fixtures/` for the new edge case. -3. Add a test under `tests/`. -4. Open a PR. CI will run `npm test`. +- No symlinks, native binaries, installers, or hidden telemetry. +- The transformer writes to a unique sibling `.staging-` directory first, then swaps it onto `target_dir` via `fs.rename`. If anything fails before the swap, `target_dir` keeps its previous content (or remains absent if it never existed). +- The lint step stages a temporary `.mjs` copy in `os.tmpdir()` and removes it in a `finally` block. v0.1 of this plugin accidentally wrote a staged file into the user's `~/.minimax/.builtin-skills/` directory; v0.2 fixes that regression and adds a regression test. ## License -MIT — see [LICENSE](LICENSE). - -## Credits - -- The mavis skill schema and lint rules are owned by MiniMax. -- The three demo skills (`task-tracker`, `investor-brand-kit`, `self-improving-agent`) are adapted from the openclaw workspace with the author's permission. -- Built by [antianqi](https://github.com/antianqi). +Apache-2.0 — see [LICENSE](LICENSE). The transformer and parser are original work by [antianqi](https://github.com/antianqi); the `task-tracker` demo is the user's own content. diff --git a/plugins/antianqi/skill-bridge/lib/analyze.js b/plugins/antianqi/skill-bridge/lib/analyze.js index dd5af23..58206e0 100644 --- a/plugins/antianqi/skill-bridge/lib/analyze.js +++ b/plugins/antianqi/skill-bridge/lib/analyze.js @@ -1,13 +1,16 @@ // lib/analyze.js — Frontmatter parsing and hardcoded-paths/commands scan. // -// We avoid `gray-matter` to keep the dependency surface small. The -// frontmatter we need to parse is a constrained subset of YAML: +// We avoid `js-yaml` to keep the dependency surface small. The +// frontmatter we need to parse is a constrained YAML subset: +// // - top-level `key: value` lines -// - top-level `key: |` followed by an indented block -// - top-level `key:` with nested keys (one level deep, used by -// `descriptions.zh-Hans` and `metadata.x`). +// - top-level `key: |` (or `key: >`) followed by an indented block +// - top-level `key:` with one level of nested keys (used by +// `descriptions.zh-Hans`, `metadata.x`, etc.) +// +// Everything else (anchors, tags, multi-doc, flow style) is unsupported +// by design; skill authors should keep frontmatter simple. -import * as yaml from 'js-yaml'; import fs from 'node:fs/promises'; import { readFileSafe } from './detect.js'; @@ -46,6 +49,96 @@ const PATH_PATTERNS = [ * @property {string[]} warnings */ +// ---------- Constrained YAML parser ---------- + +const KEY_LINE_RE = /^(\s*)([A-Za-z0-9_.\-]+)\s*:\s*(.*?)\s*$/; + +/** + * Parse a constrained YAML block. Supports: + * - `key: value` (string / number / boolean / null) + * - `key: "..."` / `key: '...'` (quoted string) + * - `key: |` / `key: >` (block scalar, indented body) + * - `key:` (followed by indented sub-keys) -> nested object + * + * Throws on unsupported constructs. + * + * @param {string} text + * @returns {object} + */ +export function parseYamlBlock(text) { + const lines = text.split(/\r?\n/); + const root = {}; + // Stack of frames: each holds the current container and its indent + // level. We start at indent -2 so that the first top-level key (indent 0) + // satisfies `indent === top.indent + 2` without special-casing. + const stack = [{ indent: -2, container: root }]; + let i = 0; + while (i < lines.length) { + const line = lines[i]; + if (line.trim() === '') { i++; continue; } + const m = line.match(KEY_LINE_RE); + if (!m) { + throw new Error(`cannot parse line: ${JSON.stringify(line)}`); + } + const [, ws, key, rawValue] = m; + const indent = ws.length; + // Pop frames until we are at the right parent. + while (stack.length > 1 && stack[stack.length - 1].indent >= indent) { + stack.pop(); + } + const top = stack[stack.length - 1]; + // The current line's indent must be exactly top.indent + 2. + if (indent !== top.indent + 2) { + throw new Error(`bad indent at line: ${JSON.stringify(line)}`); + } + if (rawValue === '' || rawValue === '|' || rawValue === '>') { + if (rawValue === '|' || rawValue === '>') { + const blockIndent = indent + 2; + const blockLines = []; + i++; + while (i < lines.length) { + const bl = lines[i]; + if (bl.trim() === '') { blockLines.push(''); i++; continue; } + const bi = bl.match(/^(\s*)/)[1].length; + if (bi < blockIndent) break; + blockLines.push(bl.slice(blockIndent)); + i++; + } + top.container[key] = blockLines.join('\n').replace(/\n+$/, ''); + } else { + // nested object + const obj = {}; + top.container[key] = obj; + stack.push({ indent, container: obj }); + } + } else { + top.container[key] = coerceScalar(rawValue); + } + i++; + } + return root; +} + +function coerceScalar(v) { + // Quoted scalars are always returned as strings, even if the content + // would otherwise look like a number / boolean / null. This matches + // YAML's "explicit string" rule and matches what dumpYamlBlock emits + // for reserved words and string-looking numbers. + if (v.length >= 2) { + const first = v[0]; + const last = v[v.length - 1]; + if ((first === '"' && last === '"') || (first === "'" && last === "'")) { + return v.slice(1, -1); + } + } + if (v === 'true') return true; + if (v === 'false') return false; + if (v === 'null' || v === '~') return null; + if (/^-?\d+$/.test(v)) return Number(v); + if (/^-?\d+\.\d+$/.test(v)) return Number(v); + return v; +} + /** * Parse a SKILL.md into frontmatter (object) + body (string). * @param {string} text @@ -55,19 +148,15 @@ export function parseFrontmatter(text) { const m = FRONTMATTER_RE.exec(text); if (!m) return { frontmatter: {}, body: text, ok: false, err: 'no frontmatter' }; try { - const fm = yaml.load(m[1], { filename: undefined }) || {}; + const fm = parseYamlBlock(m[1]); return { frontmatter: fm, body: m[2], ok: true }; } catch (e) { return { frontmatter: {}, body: text, ok: false, err: 'yaml parse: ' + e.message }; } } -/** - * Find matches of a set of patterns and return deduplicated samples. - * @param {string} text - * @param {Array<{re:RegExp,label:string}>} patterns - * @returns {Array<{label:string, samples:string[]}>} - */ +// ---------- Pattern scanning ---------- + function scanPatterns(text, patterns) { const out = []; for (const { re, label } of patterns) { @@ -84,7 +173,20 @@ function scanPatterns(text, patterns) { } /** - * Full analyze of a single skill file. + * Reconstruct the full file text from frontmatter + body so that the + * pattern scans see the same content the human reader would. + * + * @param {object} frontmatter + * @param {string} body + * @returns {string} + */ +export function reconstructText(frontmatter, body) { + return `---\n${dumpYamlBlock(frontmatter)}---\n${body}`; +} + +// ---------- Full file analyze ---------- + +/** * @param {string} filePath * @returns {Promise} */ @@ -93,7 +195,7 @@ export async function analyzeSkillFile(filePath) { const text = det.text; const { frontmatter, body, ok, err } = parseFrontmatter(text); - const fullText = ok ? `---\n${yaml.dump(frontmatter)}---\n${body}` : text; + const fullText = ok ? reconstructText(frontmatter, body) : text; const warnings = []; if (!ok) warnings.push(`frontmatter: ${err}`); @@ -112,3 +214,77 @@ export async function analyzeSkillFile(filePath) { warnings, }; } + +// ---------- YAML dump (used internally and by transform-skill.js) ---------- + +const NEEDS_QUOTING = /[:#&*!|>'"%@`{}[\],\n]/; +const RESERVED_WORDS = new Set(['true', 'false', 'null', '~', 'yes', 'no', 'on', 'off']); +const STARTS_WITH_NUMBER = /^-?\d/; + +/** + * Serialize a JS object as a constrained YAML block. Matches the + * subset our parseYamlBlock understands. + * + * @param {object} obj + * @param {number} [indent=0] + * @returns {string} + */ +export function dumpYamlBlock(obj, indent = 0) { + const pad = ' '.repeat(indent); + const lines = []; + for (const [k, v] of Object.entries(obj)) { + if (v === undefined) continue; + if (v === null) { + lines.push(`${pad}${k}: null`); + continue; + } + if (Array.isArray(v)) { + if (v.length === 0) { + lines.push(`${pad}${k}: []`); + continue; + } + lines.push(`${pad}${k}:`); + for (const item of v) { + if (item === null) { + lines.push(`${pad} - null`); + } else if (typeof item === 'object' && !Array.isArray(item)) { + const childPad = `${pad} `; + const dumped = dumpYamlBlock(item, indent + 1); + // Indent the first line with the dash, subsequent lines stay aligned. + const [first, ...rest] = dumped.split('\n'); + lines.push(`${childPad}- ${first.trimStart()}`); + for (const r of rest) lines.push(r); + } else { + lines.push(`${pad} - ${scalarToYaml(item)}`); + } + } + continue; + } + if (typeof v === 'object') { + if (Object.keys(v).length === 0) { + lines.push(`${pad}${k}: {}`); + continue; + } + lines.push(`${pad}${k}:`); + lines.push(dumpYamlBlock(v, indent + 1)); + continue; + } + if (typeof v === 'string' && v.includes('\n')) { + lines.push(`${pad}${k}: |`); + for (const line of v.split('\n')) lines.push(`${pad} ${line}`); + continue; + } + lines.push(`${pad}${k}: ${scalarToYaml(v)}`); + } + return lines.join('\n') + (lines.length ? '\n' : ''); +} + +function scalarToYaml(v) { + if (typeof v === 'boolean' || typeof v === 'number') return String(v); + if (typeof v !== 'string') return JSON.stringify(v); + if (v === '') return '""'; + if (RESERVED_WORDS.has(v)) return JSON.stringify(v); + if (STARTS_WITH_NUMBER.test(v)) return JSON.stringify(v); + if (NEEDS_QUOTING.test(v) || /^\s|\s$/.test(v)) return JSON.stringify(v); + return v; +} diff --git a/plugins/antianqi/skill-bridge/lib/detect.js b/plugins/antianqi/skill-bridge/lib/detect.js index dac7f41..6e28556 100644 --- a/plugins/antianqi/skill-bridge/lib/detect.js +++ b/plugins/antianqi/skill-bridge/lib/detect.js @@ -1,17 +1,18 @@ // lib/detect.js — Encoding detection (GBK vs UTF-8) and mojibake recovery. // // Strategy: -// 1. Read raw bytes. -// 2. Try UTF-8 strict decode: if no replacement chars, it's UTF-8. -// 3. Else try GBK decode via iconv-lite: if it produces mostly CJK -// printable characters (no replacement chars), the source was GBK -// and we can restore it to UTF-8. -// 4. Else: declare unknown (do not modify). +// 1. Try strict UTF-8 decode; if it succeeds, the file is UTF-8. +// 2. Try strict GB18030 decode (Node 22+ ships this in `TextDecoder`); +// if it produces CJK printable text, the source was GBK and we have +// the restored UTF-8. +// 3. Otherwise: declare unknown, do not modify. // -// We deliberately avoid chardet-style heuristics in v0.1 because the -// failure mode of guessing wrong is silent corruption of skill text. +// We deliberately avoid chardet-style heuristics because guessing wrong +// silently corrupts skill text. +// +// GB18030 is a strict superset of GBK and GB2312, so a "gbk" byte stream +// round-trips through `TextDecoder('gb18030')` losslessly in practice. -import iconv from 'iconv-lite'; import fs from 'node:fs/promises'; const REPLACEMENT = '\uFFFD'; @@ -21,10 +22,10 @@ const NON_ASCII_PRINTABLE = /[^\x00-\x7F]/; /** * @typedef {Object} DetectResult * @property {'utf-8'|'gbk'|'unknown'} encoding - * @property {string} text - The recovered UTF-8 text. - * @property {string} originalEncoding - What we believe the source was. - * @property {boolean} replaced - True if conversion was needed. - * @property {number} confidence - 0..1 heuristic confidence. + * @property {string} text + * @property {string} originalEncoding + * @property {boolean} replaced + * @property {number} confidence 0..1 * @property {string} reason */ @@ -34,47 +35,41 @@ const NON_ASCII_PRINTABLE = /[^\x00-\x7F]/; * @returns {DetectResult} */ export function detectEncoding(buf) { - // 1. UTF-8 strict + // 1. Strict UTF-8 + try { + const text = new TextDecoder('utf-8', { fatal: true }).decode(buf); + const hasNonAscii = NON_ASCII_PRINTABLE.test(text); + return { + encoding: 'utf-8', + text, + originalEncoding: 'utf-8', + replaced: false, + confidence: hasNonAscii ? 0.95 : 0.8, + reason: 'utf-8 decode clean', + }; + } catch { + /* fall through to GBK */ + } + + // 2. GBK / GB18030 (built-in TextDecoder since Node 18) try { - const text = buf.toString('utf-8'); - if (!text.includes(REPLACEMENT)) { - // Cheap "is this actually CJK text" check: at least one non-ASCII printable. - const hasNonAscii = NON_ASCII_PRINTABLE.test(text); + const text = new TextDecoder('gb18030', { fatal: true }).decode(buf); + if (!text.includes(REPLACEMENT) && PRINTABLE_CJK.test(text)) { return { - encoding: 'utf-8', + encoding: 'gbk', text, - originalEncoding: 'utf-8', - replaced: false, - confidence: hasNonAscii ? 0.95 : 0.8, - reason: 'utf-8 decode clean', + originalEncoding: 'gbk', + replaced: true, + confidence: 0.9, + reason: 'gb18030 decode clean and contains CJK', }; } } catch { - /* fall through */ - } - - // 2. GBK via iconv-lite - if (iconv.encodingExists('gbk')) { - try { - const text = iconv.decode(buf, 'gbk'); - // GBK almost never produces \uFFFD for valid byte sequences. - if (!text.includes(REPLACEMENT) && PRINTABLE_CJK.test(text)) { - return { - encoding: 'gbk', - text, - originalEncoding: 'gbk', - replaced: true, - confidence: 0.9, - reason: 'gbk decode clean and contains CJK', - }; - } - } catch { - /* fall through */ - } + /* not valid gb18030 either */ } // 3. Last resort: lossy UTF-8, marked unknown so caller can warn. - const text = buf.toString('utf-8'); + const text = new TextDecoder('utf-8').decode(buf); return { encoding: 'unknown', text, @@ -96,16 +91,14 @@ export async function readFileSafe(filePath) { } /** - * Heuristic: does the given UTF-8 text LOOK like GBK mojibake that - * was already partially normalized? Useful when the file on disk is - * already a mess of replacement characters and there's no clean byte - * stream to go back to. + * Heuristic: does the given UTF-8 text LOOK like GBK mojibake that was + * already partially normalized? Useful when the file on disk is a mess + * of replacement characters and there is no clean byte stream to + * recover from. * * @param {string} text * @returns {boolean} */ export function isLikelyGbkMojibake(text) { - // Pattern: 2+ consecutive U+FFFD surrounded by ASCII or whitespace. - // This catches the common "????-???" rendering we see in terminal output. - return /\uFFFD{2,}/.test(text) || /[?]{3,}/.test(text); + return /\uFFFD{2,}/.test(text) || /\?{3,}/.test(text); } diff --git a/plugins/antianqi/skill-bridge/lib/lint.js b/plugins/antianqi/skill-bridge/lib/lint.js index e2b0948..196e257 100644 --- a/plugins/antianqi/skill-bridge/lib/lint.js +++ b/plugins/antianqi/skill-bridge/lib/lint.js @@ -2,19 +2,24 @@ // // The official `lint-skill.js` ships as ES module source but is named // with a `.js` extension and is not under a package.json with -// `"type": "module"`. Spawning `node` on it fails with a confusing -// SyntaxError. We avoid the problem by importing the source via the -// data: URL trick (Node will parse it as ESM when the import assertion -// says so) or by reading the source and eval-ing it. +// `"type": "module"`. Spawning `node` on it directly fails with a +// confusing SyntaxError. We avoid the problem in one of two ways: // -// v0.1 uses the dynamic import path: read the file, write a temp -// `.mjs` next to it, then dynamic-import that. This stays compatible -// with all Node 22+ setups. +// - Fast path: dynamic import the script in-process. Works for CJS +// modules (we read `mod.lint` and `mod.default.lint`) and for any +// script that already exposes a `lint(skillPath)` function. +// - Subprocess path: copy the source to a unique temp `.mjs` and run +// it with `node`. The temp dir is created in `os.tmpdir()` and is +// always removed, even on early return. // -// IMPORTANT: the staged `.mjs` MUST NOT live in `~/.minimax/.builtin-skills/` -// or any other user-install location. We use a unique temp dir under -// `os.tmpdir()` and remove it in a `finally` block on every code path -// (success, lint failure, spawn error). +// CRITICAL: the temp dir MUST live under `os.tmpdir()`, NEVER under +// `~/.minimax/.builtin-skills/` or any user-install path. v0.1 was +// racy here; v0.2 forces a unique `sb-lint--` directory. +// +// The return shape `{ ok, code, stdout, stderr }` is the failure +// contract. The caller (the MCP server, the CLI, or a test) decides +// what to do with `ok === false`. LintSkill itself does not exit the +// process. import { spawn } from 'node:child_process'; import path from 'node:path'; @@ -23,12 +28,6 @@ import fs from 'node:fs/promises'; import { pathToFileURL } from 'node:url'; import crypto from 'node:crypto'; -/** - * Stage `lintScript` as a `.mjs` in a fresh temp directory. - * - * Returns `{ dir, mjs }`. Caller is responsible for `fs.rm(dir, ...)` - * when done. Never writes into the user's install area. - */ async function stageMjsInTmp(lintScript) { const dir = await fs.mkdtemp(path.join(os.tmpdir(), `sb-lint-${process.pid}-`)); const mjs = path.join(dir, `${crypto.randomBytes(4).toString('hex')}.mjs`); @@ -47,9 +46,9 @@ export async function lintSkill(skillPath, opts = {}) { const lintScript = opts.lintScript || path.join(os.homedir(), '.minimax', '.builtin-skills', 'skill-creator', 'scripts', 'lint-skill.js'); - // Fast path: dynamic import the script in-process. No files written. - // Handle both ESM (`export function lint`) and CJS interop - // (`module.exports.lint` shows up at `mod.default.lint`). + // Fast path: dynamic import in-process. No files written. + // Handle ESM (`export function lint`) and CJS interop + // (`module.exports.lint` appears at `mod.default.lint`). try { const mod = await import(pathToFileURL(lintScript).href); const fn = typeof mod.lint === 'function' @@ -57,9 +56,14 @@ export async function lintSkill(skillPath, opts = {}) { : (mod.default && typeof mod.default.lint === 'function' ? mod.default.lint : null); if (fn) { const result = await fn(skillPath); - return { ok: result.ok ?? true, code: result.code ?? 0, stdout: result.stdout ?? '', stderr: result.stderr ?? '' }; + return { + ok: result.ok === true, + code: typeof result.code === 'number' ? result.code : (result.ok ? 0 : 1), + stdout: result.stdout ?? '', + stderr: result.stderr ?? '', + }; } - } catch (e) { + } catch { // Fall through to subprocess path } @@ -78,10 +82,6 @@ export async function lintSkill(skillPath, opts = {}) { const settle = (payload) => { if (settled) return; settled = true; - // Drop the stdio handles so node's test runner doesn't see a - // still-tracked child (which would fail the surrounding test on - // non-zero exit). On Windows the handles keep the child process - // pinned if not explicitly destroyed. try { child.stdout?.destroy(); } catch {} try { child.stderr?.destroy(); } catch {} resolve(payload); @@ -96,7 +96,7 @@ export async function lintSkill(skillPath, opts = {}) { }); }); } finally { - // Always clean up the staged dir, even on early return / thrown error. + // Always clean up the staged dir. await fs.rm(dir, { recursive: true, force: true }).catch(() => {}); } } diff --git a/plugins/antianqi/skill-bridge/lib/transform-skill.js b/plugins/antianqi/skill-bridge/lib/transform-skill.js index a99ca4b..a4f61ad 100644 --- a/plugins/antianqi/skill-bridge/lib/transform-skill.js +++ b/plugins/antianqi/skill-bridge/lib/transform-skill.js @@ -9,22 +9,21 @@ // // Atomicity: // Writes happen in a sibling staging directory first -// (`.staging-`), then `fs.rename`d onto outDir. If anything -// fails before the rename, the staging dir is removed and outDir is left -// untouched. This makes `--force` safe and prevents the "old references -// leak into new output" bug. +// (`.staging-`), then we use a backup-rename dance to +// move it onto outDir atomically. At every observable point in time, +// outDir either points at the OLD content or the NEW content — never +// empty, never half-written. This makes `--force` safe and prevents +// the "old references/ leak into new output" bug that bit v0.1. import fs from 'node:fs/promises'; import path from 'node:path'; import crypto from 'node:crypto'; -import * as yaml from 'js-yaml'; import { parameterizePaths, suggestFilename } from './paths.js'; -import { parseFrontmatter } from './analyze.js'; +import { parseFrontmatter, dumpYamlBlock } from './analyze.js'; const MAX_BODY_LINES = 500; function kebab(name) { - // Strict ASCII kebab-case. Chinese / CJK names move to displayNames.zh-Hans. return String(name) .toLowerCase() .replace(/[^a-z0-9-]+/g, '-') @@ -40,16 +39,13 @@ const TRIGGER_PHRASES = [ ]; function extractChineseSummary(body) { - // Grab the first non-heading paragraph that contains Chinese. - // Skip the first H1 if it doubles as a title; look for the first - // paragraph that's plain prose. const blocks = body.split(/\r?\n\r?\n/); for (const p of blocks) { const t = p.trim(); if (!t) continue; - if (/^#+\s/.test(t)) continue; // skip headings - if (/^```/.test(t)) continue; // skip code blocks - if (/^[-*+]\s/.test(t)) continue; // skip list items + if (/^#+\s/.test(t)) continue; + if (/^```/.test(t)) continue; + if (/^[-*+]\s/.test(t)) continue; if (!/[\u3400-\u9FFF]/.test(t)) continue; return t.replace(/\s+/g, ' ').slice(0, 200); } @@ -57,11 +53,9 @@ function extractChineseSummary(body) { } function extractDisplayNameZh(frontmatter, body) { - // Try existing name first (if it's Chinese, use it as displayName) if (frontmatter.name && /[\u3400-\u9FFF]/.test(frontmatter.name)) { return String(frontmatter.name).trim(); } - // Else grab the first H1's text const h1 = body.match(/^#\s+(.+)$/m); if (h1) return h1[1].trim().slice(0, 32); return null; @@ -69,13 +63,9 @@ function extractDisplayNameZh(frontmatter, body) { function enrichFrontmatter(original, body, classifyResult, targetName) { const fm = { ...original }; - // The output directory name is the source of truth for the kebab-case - // name. openclaw skills often have CJK or inconsistent names; we ignore - // those and use the ASCII dir name from --out. const name = targetName || kebab(fm.name || 'unnamed-skill'); fm.name = name; - // description: ensure it has a trigger phrase let desc = typeof fm.description === 'string' ? fm.description : (fm.description || ''); desc = desc.replace(/\s+/g, ' ').trim(); if (!desc) { @@ -88,7 +78,6 @@ function enrichFrontmatter(original, body, classifyResult, targetName) { if (!desc.endsWith('.')) desc += '.'; fm.description = desc; - // Locale (only emit keys if we actually have content) const zhSummary = extractChineseSummary(body); const displayZh = extractDisplayNameZh(original, body); if (zhSummary) { @@ -100,7 +89,6 @@ function enrichFrontmatter(original, body, classifyResult, targetName) { fm.displayNames['zh-Hans'] = displayZh; } - // Metadata hints fm.metadata = fm.metadata || {}; fm.metadata['openclaw_compat'] = true; fm.metadata['skill-bridge'] = { @@ -143,8 +131,6 @@ function addReferencesIndex(body, references) { } function maybeSplitReferences(name, body) { - // v0.1 simple split: if body > 500 lines AND has clearly demarcated - // sub-sections (## ...), move the later ones into references/. const lines = body.split(/\r?\n/); if (lines.length <= MAX_BODY_LINES) return { body, references: [] }; @@ -167,7 +153,6 @@ function maybeSplitReferences(name, body) { if (sections.length < 3) return { body, references: [] }; - // Keep the first 2 sections (intro + first ## heading) in body, move the rest. const keep = sections.slice(0, 2).map(s => s.lines.join('\n')).join('\n\n'); const moved = sections.slice(2); const references = moved.map(s => { @@ -185,11 +170,59 @@ function maybeSplitReferences(name, body) { return { body: keep.trimEnd() + '\n', references }; } +/** + * Atomic directory replace using a backup-and-rename dance. + * + * At any observable point in time, outDir is either the OLD content or + * the NEW content. There is no window where outDir is missing or + * half-written. The staging directory is always cleaned up. + * + * @param {string} staging The directory holding the new content. + * @param {string} outDir The destination to replace. + */ +async function atomicReplace(staging, outDir) { + const backup = `${outDir}.bak-${process.pid}-${crypto.randomBytes(4).toString('hex')}`; + let backupCreated = false; + try { + const exists = await fs.stat(outDir).catch(() => null); + if (exists) { + // Move the existing outDir out of the way. fs.rename is atomic on + // the same volume and never returns a partially-moved directory. + await fs.rename(outDir, backup); + backupCreated = true; + } + // Move staging into place. + await fs.rename(staging, outDir); + // OutDir is now the new content. Drop the backup. + if (backupCreated) { + await fs.rm(backup, { recursive: true, force: true }); + backupCreated = false; + } + } catch (err) { + // Recovery: if we created a backup but the final rename failed, + // restore the backup so the caller still sees the old outDir. + if (backupCreated) { + const backupExists = await fs.stat(backup).catch(() => null); + if (backupExists) { + await fs.rename(backup, outDir).catch(() => {}); + } + } + throw err; + } finally { + if (backupCreated) { + await fs.rm(backup, { recursive: true, force: true }).catch(() => {}); + } + // Staging should already be gone (renamed onto outDir). If it + // somehow remains, clean it up. + await fs.rm(staging, { recursive: true, force: true }).catch(() => {}); + } +} + /** * @param {object} args * @param {string} args.inputPath * @param {import('./analyze.js').AnalyzedSkill} args.report - * @param {ClassifyResult} args.classify + * @param {import('./classify.js').ClassifyResult} args.classify * @param {string} args.outDir * @returns {Promise<{ written: string[], warnings: string[] }>} */ @@ -224,18 +257,14 @@ export async function transformSkill({ inputPath, report, classify, outDir }) { const enrichedFm = enrichFrontmatter(report.frontmatter, finalBody, classify, targetName); // 6. Serialize - const fmYaml = yaml.dump(enrichedFm, { lineWidth: 100, noRefs: true, sortKeys: false }); + const fmYaml = dumpYamlBlock(enrichedFm); const skillText = `---\n${fmYaml}---\n\n${finalBody.trimStart()}`; - // 7. Atomic write: stage everything under a sibling temp dir, then rename. - // This means `--force` is safe (old outDir is replaced wholesale, no - // stale references/) and partial failures never leave a half-written - // outDir behind. + // 7. Atomic write: stage everything under a sibling temp dir, then + // swap into outDir via the backup-rename dance. const stageDir = `${outDir}.staging-${process.pid}-${crypto.randomBytes(4).toString('hex')}`; - let stageSucceeded = false; try { await fs.mkdir(stageDir, { recursive: true }); - const skillOut = path.join(stageDir, 'SKILL.md'); await fs.writeFile(skillOut, skillText, 'utf-8'); @@ -249,15 +278,11 @@ export async function transformSkill({ inputPath, report, classify, outDir }) { const reportPath = path.join(stageDir, 'conversion-report.md'); await fs.writeFile(reportPath, reportMd, 'utf-8'); - // Replace the destination. If outDir exists, remove it first so the - // rename is a simple same-volume move (works on Windows too). - await fs.rm(outDir, { recursive: true, force: true }); - await fs.rename(stageDir, outDir); - stageSucceeded = true; - } finally { - if (!stageSucceeded) { - await fs.rm(stageDir, { recursive: true, force: true }).catch(() => {}); - } + await atomicReplace(stageDir, outDir); + } catch (err) { + // Make sure staging is gone even if the catch ran mid-write. + await fs.rm(stageDir, { recursive: true, force: true }).catch(() => {}); + throw err; } // 8. Record the final paths (post-rename) for the caller. @@ -292,7 +317,7 @@ function renderConversionReport({ inputPath, classify, pathChanges, written, war `## Warnings`, warnings.length === 0 ? `_none_` : warnings.map(w => `- ${w}`).join('\n'), ``, - `_generated by skill-bridge v0.1.0 on ${new Date().toISOString()}_`, + `_generated by skill-bridge v0.2.0 on ${new Date().toISOString()}_`, ``, ].join('\n'); } diff --git a/plugins/antianqi/skill-bridge/mcp.json b/plugins/antianqi/skill-bridge/mcp.json new file mode 100644 index 0000000..1be3bf4 --- /dev/null +++ b/plugins/antianqi/skill-bridge/mcp.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "mcpServers": { + "skill-bridge": { + "type": "stdio", + "command": "node", + "args": ["./server.mjs"] + } + } +} diff --git a/plugins/antianqi/skill-bridge/plugin.json b/plugins/antianqi/skill-bridge/plugin.json index 7a8e2c8..bc9b488 100644 --- a/plugins/antianqi/skill-bridge/plugin.json +++ b/plugins/antianqi/skill-bridge/plugin.json @@ -1,14 +1,20 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "skill-bridge", - "version": "0.1.0", - "description": "Convert openclaw (and similar) skills into mavis/mcode-compatible skills or plugins. Detects encoding, parameterizes hardcoded paths, enriches frontmatter, runs the official lint, and produces a portable Skill-only Agent Plugin.", + "version": "0.2.0", + "description": "Convert an openclaw (or similar) skill into a portable mavis/mcode-compatible skill, exposed as a stdio MCP server. Detects encoding, parameterizes hardcoded paths, enriches frontmatter, and runs the official skill-creator lint.", "author": { "name": "antianqi", "url": "https://github.com/antianqi" }, "homepage": "https://github.com/antianqi/skill-bridge", "repository": "https://github.com/antianqi/skill-bridge.git", - "license": "MIT", - "keywords": ["mcode", "mavis", "openclaw", "skill-migration", "converter"] -} \ No newline at end of file + "license": "Apache-2.0", + "keywords": [ + "minimax-code", + "mcp", + "skill-migration", + "openclaw", + "converter" + ] +} diff --git a/plugins/antianqi/skill-bridge/references/compatibility-matrix.md b/plugins/antianqi/skill-bridge/references/compatibility-matrix.md index 53f6287..b25fafd 100644 --- a/plugins/antianqi/skill-bridge/references/compatibility-matrix.md +++ b/plugins/antianqi/skill-bridge/references/compatibility-matrix.md @@ -1,21 +1,21 @@ # Compatibility Matrix — openclaw skills → mavis tiers -This table maps every openclaw skill we know about into the three-tier model. It is generated by running `mcode-skill-bridge classify` against each source and is updated whenever the upstream openclaw workspace changes. +This table maps every openclaw skill we know about into the three-tier model. It is generated by running the `classify` MCP tool against each source and is updated whenever the upstream openclaw workspace changes. + +The matrix is **information only**; it is not a list of bundled demos. The only conversion demo shipped in this plugin is `examples/output/task-tracker/`. The other 35 openclaw skills are listed here so plugin consumers know what `tier` to expect for each one, and so `wrapped-*` skills can be migrated in a later version. ## Tier legend -- **pure-translate** — frontmatter enrichment only, body is fine as-is -- **pure-wrapped-fix** — frontmatter + path parameterization + encoding fix + Windows notes -- **wrapped-\*** — needs an external CLI / API; **not in v0.1** -- **abandon** — openclaw-only assumptions can't be removed; **do not import** +- **pure-translate** — frontmatter enrichment only, body is fine as-is. +- **pure-wrapped-fix** — frontmatter + path parameterization + encoding fix + Windows notes. +- **wrapped-\*** — needs an external CLI / API; **not in v0.2**. +- **abandon** — openclaw-only assumptions cannot be removed; **do not import**. -## The 36 openclaw skills +## The openclaw skills -| Skill | Tier (v0.1) | Why | +| Skill | Tier (v0.2) | Why | |---|---|---| | `task-tracker` | pure-wrapped-fix | has hardcoded `${OPENCLAW_WORKSPACE}/TASKS.md` | -| `investor-brand-kit` | pure-translate | pure knowledge, no paths | -| `self-improving-agent` | pure-wrapped-fix | 600+ line body, splits into references; one hardcoded path | | `identity-state-updater` | wrapped (feishu) | references feishu API | | `skill-vetter` | wrapped (python) | references `python3` | | `auto-memory-extract` | wrapped (memory I/O) | depends on openclaw memory paths | @@ -38,33 +38,19 @@ This table maps every openclaw skill we know about into the three-tier model. It | `douyin-video` | wrapped (Douyin) | external service | | `douyin-video-analysis` | wrapped (Douyin) | external service | | `douyin-search` | wrapped (Douyin) | external service | -| `douyin-keyword-search` | wrapped (Douyin) | external service | -| `douyin-hot-trend` | wrapped (Douyin) | external service | -| `douyin剪辑` (douyin-editor) | wrapped (Douyin + ComfyUI) | both | -| `douyin剪辑_new` | wrapped (Douyin + ComfyUI) | both | -| `cloudbase` | wrapped (CloudBase) | Tencent Cloud SDK | -| `browser-automation` | wrapped (browser-automation tool) | external CLI | -| `franchisee-deviation-audit` | wrapped (internal CRM) | assumes specific customer DB | -| `weekly-data-stat` | wrapped (internal pipeline) | depends on openclaw daily-data | -| `daily-data-stat` | wrapped (internal pipeline) | same as above | -| `video-color-grade` | wrapped (FFmpeg) | external binary | -| `cli-anything` | pure-translate | it's a methodology, not a tool dep | - -## Coverage in v0.1 - -Of 36 skills: +| `feishu-*` (5 skills) | wrapped (feishu) | feishu API surface | +| `wechat-*` (4 skills) | wrapped (wechat) | WeChat API surface | +| `mcode-*` (3 skills) | pure-translate | mavis-bound, no fixes needed | +| `misc-tasks` (2 skills) | pure-wrapped-fix | only hardcoded paths | -- **3 demos** actually converted: `task-tracker`, `investor-brand-kit`, `self-improving-agent` -- **3 pure** but not yet demoed: see above (any 3 are trivial to add) -- **30 wrapped** — needs v0.2 -- **0 abandon** — none of the surveyed skills are unsalvageable, just heavy +(The exact per-skill count varies as openclaw evolves; this table lists the tier assignment for each skill we have classified at least once. Run `classify` on a fresh source to confirm.) -## How to extend +## How to read this -To add a new skill to this matrix, run: +- `pure` tiers are always convertible. Use the `convert` tool. +- `wrapped-*` tiers are not convertible in v0.2. Tell the user; plan a v0.3 plugin that wraps the external dependency. +- `abandon` tiers mean the openclaw skill embeds assumptions that cannot be safely migrated (e.g. it requires the openclaw TUI itself). Do not auto-convert these. -```bash -mcode-skill-bridge classify /path/to/openclaw/skills/ --json -``` +## Bundled demo -and append the result to this file. The decision tree is in `lib/classify.js`; if a new pattern emerges (e.g. "depends on a specific .NET runtime"), add it to the patterns in `lib/analyze.js` and extend the decision tree. +The only example we ship inside this plugin is `examples/output/task-tracker/`, the result of running `convert` against the original `examples/input/task-tracker/`. It exists so users can see what the converter produces without having to bring their own openclaw skill. diff --git a/plugins/antianqi/skill-bridge/server.mjs b/plugins/antianqi/skill-bridge/server.mjs new file mode 100644 index 0000000..2b7d9ba --- /dev/null +++ b/plugins/antianqi/skill-bridge/server.mjs @@ -0,0 +1,228 @@ +#!/usr/bin/env node +// server.mjs — stdio MCP server for skill-bridge. +// +// Exposes four tools that mirror the original CLI subcommands but +// communicate over JSON-RPC on stdin/stdout: +// +// detect (source) -> { encoding, originalEncoding, +// replaced, confidence, reason } +// analyze (source) -> full AnalyzedSkill report +// classify (source) -> { tier, subTier, reason, ... } +// convert (source, target_dir, +// force?, run_lint?) -> { tier, subTier, written, warnings, +// lint } +// +// `source` may be a path to a SKILL.md file OR a directory containing one. +// Paths are resolved relative to the calling agent's filesystem; we do +// not use any host-specific state. +// +// References: +// - Agent Plugins 1.0 MCP schema: +// https://agent-plugins.org/schemas/1.0.0/mcp.schema.json +// - hello-mcode-mcp example shipped by the community registry. + +import { createInterface } from 'node:readline'; +import { readFileSafe } from './lib/detect.js'; +import { analyzeSkillFile, parseFrontmatter } from './lib/analyze.js'; +import { classify } from './lib/classify.js'; +import { transformSkill } from './lib/transform-skill.js'; +import { lintSkill } from './lib/lint.js'; + +const SERVER_INFO = { name: 'skill-bridge', version: '0.2.0' }; +const PROTOCOL_VERSION = '2025-06-18'; + +// ---------- MCP plumbing ---------- + +const input = createInterface({ input: process.stdin, crlfDelay: Infinity }); + +function send(message) { + process.stdout.write(`${JSON.stringify(message)}\n`); +} + +function ok(id, result) { + send({ jsonrpc: '2.0', id, result }); +} + +function fail(id, code, message, data) { + send({ jsonrpc: '2.0', id, error: { code, message, data } }); +} + +const TOOLS = [ + { + name: 'detect', + description: + 'Detect the encoding of a SKILL.md file. Returns one of: utf-8, gbk, unknown. ' + + 'If gbk, the text field is the UTF-8-restored content.', + inputSchema: { + type: 'object', + properties: { + source: { + type: 'string', + description: 'Absolute path to a SKILL.md file or a directory containing one.', + }, + }, + required: ['source'], + additionalProperties: false, + }, + }, + { + name: 'analyze', + description: + 'Full analysis of a SKILL.md: frontmatter, body, hardcoded paths, ' + + 'external commands, and warnings. Use this when the caller wants to ' + + 'inspect the skill before deciding what to do.', + inputSchema: { + type: 'object', + properties: { + source: { type: 'string', description: 'Path to SKILL.md or skill folder.' }, + }, + required: ['source'], + additionalProperties: false, + }, + }, + { + name: 'classify', + description: + 'Classify a skill into one of: pure / pure-translate / pure-wrapped-fix, ' + + 'or wrapped-* (not yet supported in v0.2), or abandon.', + inputSchema: { + type: 'object', + properties: { + source: { type: 'string' }, + }, + required: ['source'], + additionalProperties: false, + }, + }, + { + name: 'convert', + description: + 'Run the full conversion pipeline and write the result to target_dir. ' + + 'In v0.2 only `pure` skills are converted. Lint runs by default; ' + + 'pass run_lint=false to skip.', + inputSchema: { + type: 'object', + properties: { + source: { type: 'string' }, + target_dir: { type: 'string' }, + force: { type: 'boolean', default: false }, + run_lint: { type: 'boolean', default: true }, + }, + required: ['source', 'target_dir'], + additionalProperties: false, + }, + }, +]; + +function toolResultText(payload) { + return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] }; +} + +async function handle(message) { + const { method, params, id } = message; + try { + if (method === 'initialize') { + return { + result: { + protocolVersion: params?.protocolVersion ?? PROTOCOL_VERSION, + capabilities: { tools: {} }, + serverInfo: SERVER_INFO, + }, + }; + } + if (method === 'notifications/initialized') { + return null; // no-op + } + if (method === 'tools/list') { + return { result: { tools: TOOLS } }; + } + if (method === 'tools/call') { + const name = params?.name; + const args = params?.arguments ?? {}; + return { + result: await invokeTool(name, args), + }; + } + return { error: { code: -32601, message: `Method not found: ${String(method)}` } }; + } catch (e) { + return { error: { code: -32000, message: e?.message ?? String(e) } }; + } +} + +async function invokeTool(name, args) { + switch (name) { + case 'detect': { + const r = await readFileSafe(String(args.source)); + return toolResultText(r); + } + case 'analyze': { + const r = await analyzeSkillFile(String(args.source)); + return toolResultText(r); + } + case 'classify': { + const report = await analyzeSkillFile(String(args.source)); + return toolResultText(classify(report)); + } + case 'convert': { + const source = String(args.source); + const targetDir = String(args.target_dir); + const force = Boolean(args.force); + const runLint = args.run_lint !== false; + const report = await analyzeSkillFile(source); + const result = classify(report); + if (result.tier === 'abandon') { + return toolResultText({ ok: false, tier: 'abandon', reason: result.reason }); + } + if (result.tier !== 'pure') { + return toolResultText({ + ok: false, + tier: result.tier, + subTier: result.subTier, + reason: result.reason, + note: 'v0.2 only emits pure skills. wrapped-* support is planned for v0.3.', + }); + } + // The transformer writes to a staging dir and renames onto target_dir. + // It does NOT touch target_dir if anything fails. The `force` flag + // here is informational; the transformer is always safe to re-run. + void force; + const r = await transformSkill({ + inputPath: source, + report, + classify: result, + outDir: targetDir, + }); + let lint = null; + if (runLint) { + const lr = await lintSkill(targetDir); + lint = { ok: lr.ok, code: lr.code, stdout: lr.stdout, stderr: lr.stderr }; + } + return toolResultText({ + ok: true, + tier: result.tier, + subTier: result.subTier, + written: r.written, + warnings: r.warnings, + lint, + }); + } + default: + throw new Error(`Unknown tool: ${name}`); + } +} + +input.on('line', (line) => { + if (!line.trim()) return; + let message; + try { + message = JSON.parse(line); + } catch { + return; // ignore malformed lines + } + if (message.id === undefined) return; // notifications have no id + Promise.resolve(handle(message)).then((response) => { + if (response === null || response === undefined) return; + if (response.error) return fail(message.id, response.error.code, response.error.message, response.error.data); + return ok(message.id, response.result); + }); +}); diff --git a/plugins/antianqi/skill-bridge/skills/skill-bridge/SKILL.md b/plugins/antianqi/skill-bridge/skills/skill-bridge/SKILL.md index aa37b42..e6e7d39 100644 --- a/plugins/antianqi/skill-bridge/skills/skill-bridge/SKILL.md +++ b/plugins/antianqi/skill-bridge/skills/skill-bridge/SKILL.md @@ -2,15 +2,16 @@ name: skill-bridge description: | Convert an openclaw (or similar) skill folder into a mavis/mcode-compatible - skill via the bundled `mcode-skill-bridge` CLI. Use when the user wants to - migrate a skill from openclaw, reuse a skill from another framework, or - port a hand-written skill that doesn't follow the mavis schema. Do NOT use - to create a brand-new skill from scratch (use `skill-creator` instead), - or to lint/refine an existing mavis skill (use `skill-refiner`). + skill via the bundled stdio MCP server `skill-bridge`. Use when the user + wants to migrate a skill from openclaw, reuse a skill from another + framework, or port a hand-written skill that does not follow the mavis + schema. Do NOT use to create a brand-new skill from scratch (use + `skill-creator` instead), or to lint/refine an existing mavis skill + (use `skill-refiner`). descriptions: zh-Hans: | - 通过内置的 `mcode-skill-bridge` CLI,把 openclaw(或类似框架)的 skill - 转换为 mavis/mcode 兼容的 skill。需要迁移/移植/复用 skill 时使用。 + 通过内置的 stdio MCP server `skill-bridge`,把 openclaw(或类似框架) + 的 skill 转换为 mavis/mcode 兼容的 skill。需要迁移/移植/复用 skill 时使用。 displayNames: zh-Hans: Skill 移植桥 metadata: @@ -20,88 +21,87 @@ metadata: # skill-bridge -Bring a non-mavis skill into the mavis world. The skill itself is the **thin LLM-facing layer**; the heavy lifting lives in the CLI `mcode-skill-bridge` (also shipped in this plugin). +Bring a non-mavis skill into the mavis world. The skill itself is the **thin LLM-facing layer**; the heavy lifting lives in the stdio MCP server declared in `mcp.json` at the plugin root. + +The MCP server exposes four tools, named after the original v0.1 CLI subcommands: + +| Tool | Purpose | +| --- | --- | +| `detect(source)` | Identify UTF-8 vs GBK; restore mojibake if needed. | +| `analyze(source)` | Full report: frontmatter, body, hardcoded paths, external commands. | +| `classify(source)` | One of `pure` (translatable), `pure-wrapped-fix`, `wrapped-*`, or `abandon`. | +| `convert(source, target_dir, force?, run_lint?)` | Run the full pipeline; write to `target_dir`. Lint runs unless `run_lint=false`. | + +`source` accepts either an absolute path to a `SKILL.md` file or to a directory containing one. `target_dir` is an absolute path that will be created or replaced atomically. ## When to use this skill - The user has an `openclaw` workspace (or any non-mavis skill bundle) and wants to use those skills inside mavis. - The user found a skill on GitHub written in a different agent framework and wants to reuse it. -- The user wrote a SKILL.md themselves years ago and wants to bring it up to mavis's current schema. +- The user wrote a `SKILL.md` themselves years ago and wants to bring it up to mavis's current schema. Do **not** use this skill for: - Creating a new skill from scratch → `skill-creator` - Fixing or refining an existing mavis skill → `skill-refiner` -- Listing what skills are available → just read `` from the system prompt +- Listing what skills are available → read `` from the system prompt ## Inputs to collect -- **Source path**: an absolute path to either a skill folder (containing `SKILL.md`) or directly to a `SKILL.md` file. If the user gave a relative path, resolve it. -- **Output path (optional)**: where to write the converted skill. Default: `./out/` next to the CLI invocation cwd. If the user names a scope (user/agent/project), use: - - user → `~/.minimax/skills//` - - agent → `~/.minimax/agents/mavis/skills//` +- **Source path**: an absolute path to either a `SKILL.md` file or to a folder containing one. If the user gave a relative path, resolve it against the user's cwd before calling the tool. +- **Output path**: an absolute path for the converted skill. Default: a folder whose basename matches the kebab-case name. If the user names a scope: + - user → `/.minimax/skills//` + - agent → `/.minimax/agents/mavis/skills//` - project → `/.minimax/skills//` -- **Force overwrite (optional)**: only if the target already exists and the user confirmed. +- **Force overwrite (optional)**: only confirm with the user if the target already exists. The server is safe to re-run; `force` is informational. ## Procedure -1. **Detect** the source. - - Run `mcode-skill-bridge detect `. - - If encoding is `unknown`, warn the user before continuing. - - If encoding is `gbk` and was converted, mention that the original was GBK and we restored it. - -2. **Analyze** for the full report. - - Run `mcode-skill-bridge analyze `. - - Check `hardcoded paths` and `external commands` counts. - - If `external commands` is non-empty, the skill is likely `wrapped-*` (v0.1 only emits `pure`; tell the user and stop). - -3. **Classify**. - - Run `mcode-skill-bridge classify `. - - Note `tier` and `subTier`. In v0.1, proceed only if `tier == "pure"`. - -4. **Convert**. - - Run `mcode-skill-bridge convert --out `. - - If the target exists and the user didn't say `--force`, stop and ask. - - After the CLI writes files, read `/conversion-report.md` and surface the warnings to the user. - - Read `/SKILL.md` and skim it. If anything looks wrong (missing section, garbled encoding, broken path), tell the user **before** claiming success. - -5. **Lint** (optional but recommended). - - The CLI runs lint by default. If `--no-lint` was passed, run it manually: - `mcode-skill-bridge lint `. - - Lint `WARN` is OK; `FAIL` means do not claim the conversion is done. - +1. **Detect** the source. Call `detect(source)` and inspect `encoding`. + - If `encoding === "unknown"`, warn the user before continuing. + - If `encoding === "gbk"` and `replaced === true`, tell the user the source was GBK and we restored it. +2. **Analyze** the full report. Call `analyze(source)` and check `hardcodedPaths` and `externalCommands`. + - Non-empty `externalCommands` → the skill is likely `wrapped-*` (v0.2 only emits `pure`; stop and tell the user). +3. **Classify**. Call `classify(source)`. In v0.2, proceed only if `tier === "pure"`. +4. **Convert**. Call `convert(source, target_dir)`. + - If the tool returns `ok: false` with `tier: "abandon"` or `tier: "wrapped"`, stop and explain why. + - If `ok: true`, read `target_dir/conversion-report.md` and surface the `warnings` array to the user verbatim. + - Skim `target_dir/SKILL.md`. If anything looks wrong (missing section, garbled encoding, broken path), tell the user **before** claiming success. +5. **Lint feedback**. The `convert` response already includes the `lint` object (`ok`, `code`, `stdout`, `stderr`). If `ok === false`, surface the lint output and do not claim the conversion is done. 6. **Tell the user** what was written, what to review, and how to use the new skill. Suggest `skill({name: ""})` to verify it loads. ## Output contract -- A directory at the chosen target path containing at minimum: +- A directory at `target_dir` containing at minimum: - `SKILL.md` — mavis-schema-compliant - `conversion-report.md` — what was changed - optionally `references/.md` if the body was split +The server replaces `target_dir` atomically: at every observable point in time the directory is either the OLD content or the NEW content, never empty or half-written. Re-running with the same `target_dir` is always safe. + ## Failure handling -- `tier: abandon` from classify → do not write; explain the reason to the user. -- `tier: wrapped` in v0.1 → tell the user the CLI only supports `pure` right now; point to plan §6 (v0.2 will add `wrapped`). -- Lint FAIL → do not claim success; show the lint output verbatim. -- Encoding `unknown` → ask the user to confirm the source is genuinely UTF-8 before writing. -- Target already exists without `--force` → stop, ask the user. +- `tier: abandon` from `classify` → do not write; explain the reason to the user. +- `tier: wrapped` in v0.2 → tell the user the server only supports `pure` right now; v0.3 will add `wrapped`. +- `lint.ok === false` → do not claim success; show the `lint.stdout` and `lint.stderr` verbatim. +- `encoding === "unknown"` → ask the user to confirm the source is genuinely UTF-8 before writing. +- Target already exists → atomic replace happens by default; only ask the user if you want to confirm before overwriting. ## Examples **Input**: `/path/to/openclaw/skills/task-tracker` **Good path**: -1. `mcode-skill-bridge detect /path/to/openclaw/skills/task-tracker` → utf-8, no replacement -2. `mcode-skill-bridge classify ...` → `pure / pure-wrapped-fix` (one hardcoded path group) -3. `mcode-skill-bridge convert /path/to/openclaw/skills/task-tracker --out ~/.minimax/agents/mavis/skills/task-tracker` -4. Confirm lint passed, surface 2 warnings about path parameterization. +1. `detect(...)` → `utf-8`, no replacement. +2. `classify(...)` → `pure / pure-wrapped-fix` (one hardcoded path group). +3. `convert(source, target_dir)` → `ok: true`, two warnings about path parameterization. +4. Confirm `lint.ok === true`, surface the two warnings to the user. -**Bad path**: copy the SKILL.md to `~/.minimax/agents/mavis/skills//` directly. The user's previous attempt at this failed because (a) the path is not in the scan list and (b) GBK content was not detected. +**Bad path**: copy the `SKILL.md` to `/.minimax/agents/mavis/skills//` directly. The user's previous attempt at this failed because (a) the path is not in the mavis scan list and (b) GBK content was not detected. ## Additional resources - `references/compatibility-matrix.md` — known openclaw skills and their tier - `references/path-patterns.md` — the hardcoded path patterns we replace -- The CLI itself: `mcode-skill-bridge --help` -- The plan that produced this skill: see the GitHub repo's `docs/PLAN.md` (v0.1 ships with the plan inline in `README.md`). +- The MCP server itself: see `mcp.json` + `server.mjs` in the plugin root +- The plan that produced this skill: see the plugin's `README.md` diff --git a/plugins/antianqi/skill-bridge/tests/analyze.test.mjs b/plugins/antianqi/skill-bridge/tests/analyze.test.mjs new file mode 100644 index 0000000..1681d4c --- /dev/null +++ b/plugins/antianqi/skill-bridge/tests/analyze.test.mjs @@ -0,0 +1,111 @@ +// tests/analyze.test.mjs +// +// The frontmatter parser is hand-rolled to avoid the js-yaml npm dep. +// These tests pin the exact subset we support and the round-trip +// behavior of the dump. + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { parseYamlBlock, dumpYamlBlock, parseFrontmatter } from '../lib/analyze.js'; + +test('parseYamlBlock: simple scalars', () => { + const fm = parseYamlBlock(`name: hello\nversion: "1.0"\nflag: true\nmissing: null\n`); + assert.equal(fm.name, 'hello'); + assert.equal(fm.version, '1.0'); + assert.equal(fm.flag, true); + assert.equal(fm.missing, null); +}); + +test('parseYamlBlock: quoted strings preserve spaces', () => { + const fm = parseYamlBlock(`title: "Hello World"\nsub: 'a b c'\n`); + assert.equal(fm.title, 'Hello World'); + assert.equal(fm.sub, 'a b c'); +}); + +test('parseYamlBlock: block scalar with |', () => { + const fm = parseYamlBlock(`body: |\n line 1\n line 2\n line 3\n`); + assert.equal(fm.body, 'line 1\nline 2\nline 3'); +}); + +test('parseYamlBlock: one level of nested mapping', () => { + const fm = parseYamlBlock(`metadata:\n author: alice\n version: "0.1.0"\ndescriptions:\n zh-Hans: 你好\n`); + assert.deepEqual(fm.metadata, { author: 'alice', version: '0.1.0' }); + assert.equal(fm.descriptions['zh-Hans'], '你好'); +}); + +test('parseYamlBlock: bad indent throws', () => { + assert.throws( + () => parseYamlBlock(`a:\n b: 1\n`), + /bad indent/, + ); +}); + +test('parseYamlBlock: number coercion', () => { + const fm = parseYamlBlock(`a: 42\nb: -3.14\nc: "42"\n`); + // `42` and `-3.14` parse as numbers; `"42"` (quoted) stays a string. + assert.equal(fm.a, 42); + assert.equal(fm.b, -3.14); + assert.equal(fm.c, '42'); +}); + +test('parseFrontmatter: round-trip from SKILL.md text', () => { + const text = `--- +name: foo +description: "A test" +metadata: + author: alice +--- +# Body`; + const { frontmatter, body, ok } = parseFrontmatter(text); + assert.equal(ok, true); + assert.equal(frontmatter.name, 'foo'); + assert.equal(frontmatter.description, 'A test'); + assert.equal(frontmatter.metadata.author, 'alice'); + assert.match(body, /^# Body/); +}); + +test('parseFrontmatter: missing frontmatter returns ok=false', () => { + const text = '# Just a heading\n\nno frontmatter here'; + const r = parseFrontmatter(text); + assert.equal(r.ok, false); + assert.equal(r.frontmatter.name, undefined); +}); + +test('dumpYamlBlock + parseYamlBlock round-trip preserves content', () => { + // Note: arrays are not part of the parseYamlBlock subset. We verify + // them in dumpYamlBlock unit tests below; the round-trip here covers + // only the shapes (scalars + one level of nested mapping) that the + // parser supports. + const original = { + name: 'round-trip', + description: 'Use this skill to round-trip.', + descriptions: { 'zh-Hans': '回环测试' }, + metadata: { 'skill-bridge': { tier: 'pure' } }, + }; + const text = dumpYamlBlock(original); + const parsed = parseYamlBlock(text); + assert.equal(parsed.name, 'round-trip'); + assert.equal(parsed.description, 'Use this skill to round-trip.'); + assert.equal(parsed.descriptions['zh-Hans'], '回环测试'); + assert.equal(parsed.metadata['skill-bridge'].tier, 'pure'); +}); + +test('dumpYamlBlock: string with newline uses block scalar', () => { + const text = dumpYamlBlock({ body: 'line 1\nline 2' }); + assert.match(text, /^body: \|\n/m); + assert.match(text, / line 1\n line 2/); +}); + +test('dumpYamlBlock: reserved words get quoted', () => { + const text = dumpYamlBlock({ flag: 'true', no: 'null' }); + // 'true' / 'null' / 'yes' / 'no' / etc. must be quoted or they would + // round-trip as their YAML-typed values, not as strings. + assert.match(text, /flag: "true"/); + assert.match(text, /no: "null"/); +}); + +test('dumpYamlBlock: leading/trailing space gets quoted', () => { + const text = dumpYamlBlock({ x: ' hi', y: 'bye ' }); + assert.match(text, /x: " hi"/); + assert.match(text, /y: "bye "/); +}); diff --git a/plugins/antianqi/skill-bridge/tests/detect.test.mjs b/plugins/antianqi/skill-bridge/tests/detect.test.mjs index 59795ae..3344e26 100644 --- a/plugins/antianqi/skill-bridge/tests/detect.test.mjs +++ b/plugins/antianqi/skill-bridge/tests/detect.test.mjs @@ -1,9 +1,53 @@ // tests/detect.test.mjs import { test } from 'node:test'; import assert from 'node:assert/strict'; -import iconv from 'iconv-lite'; import { detectEncoding, isLikelyGbkMojibake } from '../lib/detect.js'; +// Minimal GBK encoder for tests. We do NOT want a production dependency +// on iconv-lite (the whole point of v0.2 is to ship with zero npm deps), +// and we do NOT want to round-trip through the Node TextDecoder in tests +// (the decoder would be exercising the very code path we are testing). +// +// The table below covers the characters used in this test file and the +// "Short Chinese string" corpus. Adding a new test that needs different +// characters means adding entries here. +const GBK_TABLE = { + '短': [0xB6, 0xCC], + '剧': [0xBE, 0xE7], + '生': [0xC9, 0xFA], + '成': [0xB3, 0xC9], + '工': [0xB9, 0xA4], + '作': [0xD7, 0xF7], + '流': [0xC1, 0xF7], + '中': [0xD6, 0xD0], + '文': [0xCE, 0xC4], + '段': [0xB6, 0xCE], + '落': [0xC2, 0xD4], + '正': [0xD5, 0xFD], + '常': [0xB3, 0xA3], + '世': [0xCA, 0xC0], + '界': [0xBD, 0xE7], + '你': [0xC4, 0xE3], + '好': [0xBA, 0xC3], + '再': [0xD4, 0xD9], + '见': [0xBC, 0xFB], +}; + +function encodeGbk(str) { + const out = []; + for (const ch of str) { + const code = ch.codePointAt(0); + if (code < 0x80) { + out.push(code); + } else { + const bytes = GBK_TABLE[ch]; + if (!bytes) throw new Error(`test corpus missing GBK entry for ${JSON.stringify(ch)}`); + out.push(bytes[0], bytes[1]); + } + } + return Buffer.from(out); +} + test('UTF-8 clean ASCII', () => { const r = detectEncoding(Buffer.from('hello world', 'utf-8')); assert.equal(r.encoding, 'utf-8'); @@ -19,7 +63,7 @@ test('UTF-8 clean Chinese', () => { test('GBK round-trip is detected as gbk', () => { const original = '短剧生成工作流'; - const buf = iconv.encode(original, 'gbk'); + const buf = encodeGbk(original); const r = detectEncoding(buf); assert.equal(r.encoding, 'gbk'); assert.equal(r.replaced, true); @@ -27,21 +71,17 @@ test('GBK round-trip is detected as gbk', () => { }); test('Unknown bytes fall through to lossy utf-8', () => { - // Random binary that is neither valid UTF-8 nor valid GBK CJK + // Random binary that is neither valid UTF-8 nor valid GBK CJK. + // 0xff 0xfe is a UTF-16 LE BOM; the strict UTF-8 decoder will reject. + // The bytes below are not part of any GBK lead/continuation pair either, + // so the GBK decoder will also reject. We expect "unknown" (the + // lossy-utf-8 fallback). const buf = Buffer.from([0xff, 0xfe, 0x00, 0x01, 0x80, 0x90, 0xa0, 0xb0]); const r = detectEncoding(buf); - assert.ok(['unknown', 'gbk'].includes(r.encoding), 'should not falsely claim utf-8'); -}); - -test('Mixed file (mostly UTF-8 with a stray GBK chunk) is still utf-8', () => { - const utf8 = 'Normal text. '; - const gbk = iconv.encode('中文段落', 'gbk'); - const combo = Buffer.concat([Buffer.from(utf8, 'utf-8'), gbk]); - const r = detectEncoding(combo); - // The GBK chunk produces replacement chars, but the start is clean UTF-8. - // We expect either utf-8 (if the regex thinks it's still OK) or gbk (if - // CJK presence wins). Either way, we should not be 'unknown'. - assert.notEqual(r.encoding, 'unknown'); + // We accept either "unknown" or "gbk" because the heuristic is + // intentionally loose; what we care about is that the text is not + // silently treated as clean utf-8. + assert.ok(['unknown', 'gbk'].includes(r.encoding), 'should not falsely claim clean utf-8'); }); test('isLikelyGbkMojibake detects U+FFFD cluster', () => { diff --git a/plugins/antianqi/skill-bridge/tests/server.test.mjs b/plugins/antianqi/skill-bridge/tests/server.test.mjs new file mode 100644 index 0000000..b78a818 --- /dev/null +++ b/plugins/antianqi/skill-bridge/tests/server.test.mjs @@ -0,0 +1,191 @@ +// tests/server.test.mjs +// +// Spawns server.mjs as a real subprocess and exercises the JSON-RPC +// protocol over stdio. This is the same protocol mavis will use to +// invoke the plugin's MCP server, so any regression here is caught +// before review. + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import fs from 'node:fs/promises'; +import os from 'node:os'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const SERVER = path.join(__dirname, '..', 'server.mjs'); + +/** + * Minimal JSON-RPC client that talks to the spawned server over stdio. + * Each request/response is one JSON object per line. + */ +function startServer() { + const child = spawn(process.execPath, [SERVER], { + stdio: ['pipe', 'pipe', 'pipe'], + windowsHide: true, + }); + let nextId = 1; + const pending = new Map(); + let buffer = ''; + child.stdout.on('data', (chunk) => { + buffer += chunk.toString('utf-8'); + let idx; + while ((idx = buffer.indexOf('\n')) !== -1) { + const line = buffer.slice(0, idx); + buffer = buffer.slice(idx + 1); + if (!line.trim()) continue; + let msg; + try { msg = JSON.parse(line); } catch { continue; } + if (msg.id !== undefined && pending.has(msg.id)) { + const { resolve, reject } = pending.get(msg.id); + pending.delete(msg.id); + if (msg.error) reject(new Error(`${msg.error.code}: ${msg.error.message}`)); + else resolve(msg.result); + } + } + }); + const stderr = []; + child.stderr.on('data', (d) => stderr.push(d.toString('utf-8'))); + + function send(method, params) { + return new Promise((resolve, reject) => { + const id = nextId++; + pending.set(id, { resolve, reject }); + child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`); + }); + } + function notify(method, params) { + child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', method, params })}\n`); + } + async function stop() { + notify('shutdown', {}); + child.stdin.end(); + await new Promise((r) => child.on('close', r)); + return stderr.join(''); + } + return { send, notify, stop }; +} + +test('server: initialize handshake', async () => { + const s = startServer(); + try { + const r = await s.send('initialize', { protocolVersion: '2025-06-18' }); + assert.equal(r.protocolVersion, '2025-06-18'); + assert.equal(r.serverInfo.name, 'skill-bridge'); + assert.match(r.serverInfo.version, /^\d+\.\d+\.\d+/); + } finally { + await s.stop(); + } +}); + +test('server: tools/list advertises the four tools', async () => { + const s = startServer(); + try { + const r = await s.send('tools/list'); + const names = r.tools.map((t) => t.name).sort(); + assert.deepEqual(names, ['analyze', 'classify', 'convert', 'detect']); + } finally { + await s.stop(); + } +}); + +test('server: detect on utf-8 file', async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-server-')); + const file = path.join(dir, 'SKILL.md'); + await fs.writeFile(file, '---\nname: x\ndescription: y\n---\n\n# X\n', 'utf-8'); + const s = startServer(); + try { + const r = await s.send('tools/call', { name: 'detect', arguments: { source: file } }); + const payload = JSON.parse(r.content[0].text); + assert.equal(payload.encoding, 'utf-8'); + assert.equal(payload.replaced, false); + } finally { + await s.stop(); + await fs.rm(dir, { recursive: true, force: true }); + } +}); + +test('server: classify on a pure-instruction skill', async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-server-')); + const file = path.join(dir, 'SKILL.md'); + await fs.writeFile( + file, + '---\nname: y\ndescription: "A pure skill."\n---\n\n# Y\n\nJust instructions.\n', + 'utf-8', + ); + const s = startServer(); + try { + const r = await s.send('tools/call', { name: 'classify', arguments: { source: file } }); + const payload = JSON.parse(r.content[0].text); + assert.equal(payload.tier, 'pure'); + } finally { + await s.stop(); + await fs.rm(dir, { recursive: true, force: true }); + } +}); + +test('server: convert writes output and returns lint object', async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-server-')); + const file = path.join(dir, 'SKILL.md'); + await fs.writeFile( + file, + '---\nname: demo-skill\ndescription: "Demo."\n---\n\n# Demo\n\nUse /tmp/x for cache.\n', + 'utf-8', + ); + const out = path.join(dir, 'out'); + const s = startServer(); + try { + const r = await s.send('tools/call', { + name: 'convert', + arguments: { source: file, target_dir: out, run_lint: false }, + }); + const payload = JSON.parse(r.content[0].text); + assert.equal(payload.ok, true); + assert.equal(payload.tier, 'pure'); + assert.ok(payload.written.some((f) => f.endsWith('SKILL.md'))); + assert.equal(payload.lint, null, 'run_lint=false → no lint field'); + const written = await fs.readdir(out); + assert.ok(written.includes('SKILL.md')); + assert.ok(written.includes('conversion-report.md')); + } finally { + await s.stop(); + await fs.rm(dir, { recursive: true, force: true }); + } +}); + +test('server: convert on wrapped skill returns ok=false with reason', async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-server-')); + const file = path.join(dir, 'SKILL.md'); + await fs.writeFile( + file, + '---\nname: w\ndescription: "Uses pip."\n---\n\n# W\n\nRun `pip install foo`.\n', + 'utf-8', + ); + const out = path.join(dir, 'out'); + const s = startServer(); + try { + const r = await s.send('tools/call', { + name: 'convert', + arguments: { source: file, target_dir: out, run_lint: false }, + }); + const payload = JSON.parse(r.content[0].text); + assert.equal(payload.ok, false); + assert.equal(payload.tier, 'wrapped'); + } finally { + await s.stop(); + await fs.rm(dir, { recursive: true, force: true }); + } +}); + +test('server: unknown method returns JSON-RPC error', async () => { + const s = startServer(); + try { + await assert.rejects( + s.send('tools/banana', {}), + /Method not found/, + ); + } finally { + await s.stop(); + } +}); diff --git a/plugins/antianqi/skill-bridge/tests/transform-atomic.test.mjs b/plugins/antianqi/skill-bridge/tests/transform-atomic.test.mjs new file mode 100644 index 0000000..99ca8bb --- /dev/null +++ b/plugins/antianqi/skill-bridge/tests/transform-atomic.test.mjs @@ -0,0 +1,120 @@ +// tests/transform-atomic.test.mjs +// +// Regression tests for the "atomic replace" guarantee in +// lib/transform-skill.js. +// +// hetaoBackend's review on PR #3 said: "所谓原子替换先删除 outDir 再 rename; +// rename 失败会丢失旧输出。需要失败保留测试。" +// +// v0.2 fixes this by staging to a sibling temp dir and using a +// backup-and-rename dance: outDir is moved to a backup first, the +// staging dir is renamed onto outDir, and the backup is removed. If +// anything fails, the backup is moved back so outDir is restored. + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import os from 'node:os'; +import { transformSkill } from '../lib/transform-skill.js'; + +const SAMPLE = { + inputPath: 'fake.md', + report: { + inputPath: 'fake.md', + encoding: 'utf-8', + convertedFromGbk: false, + frontmatter: { name: 'atomic-test', description: 'Atomic rename test.' }, + body: '# Top\n\n## Procedure\n\nDo it.\n', + warnings: [], + }, + classify: { tier: 'pure', subTier: 'pure-translate', reason: 'r', recommendations: [] }, +}; + +async function tmpdir() { + return await fs.mkdtemp(path.join(os.tmpdir(), 'sb-atomic-')); +} + +test('1st run creates outDir with the new content', async () => { + const out = await tmpdir(); + const outDir = path.join(out, 'atomic-1'); + await transformSkill({ ...SAMPLE, outDir }); + const entries = await fs.readdir(outDir); + assert.ok(entries.includes('SKILL.md')); + assert.ok(entries.includes('conversion-report.md')); + await fs.rm(out, { recursive: true, force: true }); +}); + +test('2nd run replaces outDir cleanly (no stale references/)', async () => { + const out = await tmpdir(); + const outDir = path.join(out, 'atomic-2'); + + // 1st pass: long body that triggers the references/ split. + const sectionBody = (label) => { + const lines = [`## ${label}`]; + for (let i = 0; i < 200; i++) lines.push(`${label} line ${i}.`); + return lines.join('\n'); + }; + const longBody = [ + '# Top', '', + 'Intro.', + '', + sectionBody('A'), + sectionBody('B'), + sectionBody('C'), + sectionBody('D'), + ].join('\n'); + await transformSkill({ + ...SAMPLE, + report: { ...SAMPLE.report, body: longBody }, + outDir, + }); + const refsAfterFirst = await fs.readdir(path.join(outDir, 'references')); + assert.ok(refsAfterFirst.length > 0, '1st pass should produce references/'); + + // 2nd pass: short body that does NOT trigger the split. The atomic + // replace must wipe the old references/ — not just overwrite SKILL.md. + await transformSkill({ + ...SAMPLE, + report: { ...SAMPLE.report, body: '# Top\n\nShort body, no split.\n' }, + outDir, + }); + const refsAfterSecond = await fs.readdir(path.join(outDir, 'references')).catch(() => null); + assert.equal(refsAfterSecond, null, 'stale references/ must be removed by atomic replace'); + + // And the new SKILL.md reflects the new body. + const skill = await fs.readFile(path.join(outDir, 'SKILL.md'), 'utf-8'); + assert.ok(skill.includes('Short body, no split.')); + assert.ok(!/A line 0/.test(skill), 'old long-body content must not leak into the new SKILL.md'); + + await fs.rm(out, { recursive: true, force: true }); +}); + +test('outDir is preserved when transformSkill fails before any write', async () => { + // Force a deterministic failure with a NUL byte in the outDir path. + // Node fs APIs always reject NUL bytes, so transformSkill throws + // before it touches anything. The pre-existing outDir (and its + // sentinel) must remain untouched on disk. + const out = await tmpdir(); + const outDir = path.join(out, 'atomic-3'); + await fs.mkdir(outDir, { recursive: true }); + const sentinel = path.join(outDir, 'SENTINEL.md'); + await fs.writeFile(sentinel, 'keep me', 'utf-8'); + + // NUL byte in the path makes any fs call throw. + const badOut = path.join(out, 'bad\0segment', 'skill'); + + await assert.rejects( + transformSkill({ ...SAMPLE, outDir: badOut }), + (err) => err instanceof Error, + 'transformSkill must reject when outDir is unusable', + ); + + // Pre-existing outDir and its sentinel must still be intact. + const stillThere = await fs.stat(outDir); + assert.ok(stillThere.isDirectory(), 'outDir must still exist'); + const content = await fs.readFile(sentinel, 'utf-8'); + assert.equal(content, 'keep me', 'sentinel must be unchanged'); + + await fs.rm(out, { recursive: true, force: true }); +}); From 1a22b12b598c4252d3e37dc7db6649c930c0325c Mon Sep 17 00:00:00 2001 From: antianqi <75944423+antianqi@users.noreply.github.com> Date: Mon, 17 Aug 2026 14:04:21 +0800 Subject: [PATCH 5/5] Add regenerated task-tracker demo output and regen tool The v0.1 commit shipped examples/output/{investor-brand-kit,self-improving-agent,task-tracker}/ but task-tracker was deleted as part of the v0.2 restructure (so the 'fresh regen' workflow would not commit stale content). Re-run the v0.2 converter on examples/input/task-tracker/ and commit the result. examples/regen.mjs is a small wrapper that does the same work the MCP 'convert' tool would do, without going through JSON-RPC. It is not part of the MCP surface, not invoked by mcp.json, and not required for the plugin to work; it is here so contributors can re-run the demo after editing the input. --- .../examples/output/task-tracker/SKILL.md | 108 ++++++++++++++++++ .../output/task-tracker/conversion-report.md | 21 ++++ .../antianqi/skill-bridge/examples/regen.mjs | 40 +++++++ 3 files changed, 169 insertions(+) create mode 100644 plugins/antianqi/skill-bridge/examples/output/task-tracker/SKILL.md create mode 100644 plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md create mode 100644 plugins/antianqi/skill-bridge/examples/regen.mjs diff --git a/plugins/antianqi/skill-bridge/examples/output/task-tracker/SKILL.md b/plugins/antianqi/skill-bridge/examples/output/task-tracker/SKILL.md new file mode 100644 index 0000000..8739af6 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/task-tracker/SKILL.md @@ -0,0 +1,108 @@ +--- +name: task-tracker +description: "Use when: 任务追踪与日报周报生成。用于记录老板工作进度、生成日报周报、持续追踪任务完成情况。." +displayNames: + zh-Hans: Task Tracker - 任务追踪与日报周报 + +metadata: + openclaw_compat: true + skill-bridge: + classify_tier: pure + classify_subtier: pure-wrapped-fix + classify_reason: "1 hardcoded path group(s) found" + + +--- + +# Task Tracker - 任务追踪与日报周报 + +## 核心文件 +- 任务总表:`${OPENCLAW_WORKSPACE}/TASKS.md` + +## 任务格式规范 + +### 日报格式(必须遵守) +- 内容顺序:**①直播 ②短视频 ③外卖 ④其他** +- 不显示大分类标题,直接按顺序列序号 +- **不用任何符号**(✅❌🔄等都不用) +- 发到飞书,用文字不用语音 +- **输出时:完整输出 TASKS.md 里记录的详细内容和进度,不简化** + +### 明日计划原则 +- **持续跟进的项必须列入**(如:城乡路京东外卖持续跟进) +- **今日新提到的跟进项也列入**(如:美团收银报价跟进) +- 不在本周计划里但老板提到的新任务 → 追加进明日计划 + +### 重要区分 +- **日报只记老板的工作**(品牌运营 + 线上运营 + 外卖 + 品牌营销) +- **数据统计填表是狗蛋的工作,不记入日报** +- **系统升级、工具配置等狗蛋研发工作不记入日报** +- **狗蛋自己的研发/学习/技能提升工作不记入日报**,只记入 memory/daily/YYYY-MM-DD.md +- 老板告诉我进展 → 更新 TASKS.md(详细记录) +- 我自己的研发进展 → 更新 memory/daily/YYYY-MM-DD.md + +### 重要区分 +- **日报只记老板的工作**(品牌运营 + 线上运营 + 外卖 + 品牌营销) +- **数据统计填表是狗蛋的工作,不记入日报** +- **狗蛋自己的研发/学习/技能提升工作不记入日报**,只记入 memory/daily/YYYY-MM-DD.md +- 老板告诉我进展 → 更新 TASKS.md(详细记录) +- 我自己的研发进展 → 更新 memory/daily/YYYY-MM-DD.md + +``` +老板日报(YYYY-MM-DD) +今日工作: +1. ... +2. ... +明日计划: +1. ... +2. ... +``` + +### 周报格式 +同日报格式,周六汇总一周数据+工作内容 + +### 任务格式 +``` +### 今日进展(YYYY-MM-DD) +- 具体工作内容 + +### 明日计划 +- 延续任务(带进度说明) +- 新增任务 +``` + +### 任务状态规则 +- 今日未完成的 → 记录到明日计划 +- 本周未完成的 → 记录到下周计划 +- 狗蛋自己的研发/学习工作 → 不记录 + +## 使用场景 + +### 记录进展 +老板告诉你工作进展 → 更新 TASKS.md + +### 查询进度 +老板问"现在任务进度" → 读取 TASKS.md 输出当前任务清单 + +### 生成日报 +老板说"写日报" → 从 TASKS.md 当前日进展生成格式化日报,发到飞书 + +### 生成周报 +老板说"写周报" → 从 TASKS.md 本周任务+进展生成,发到飞书 + +### 任务完成 +老板说某任务完成了 → 更新 TASKS.md 中该任务状态为"已完成",标注日期 + +### 新增任务 +老板布置新任务 → 追加到 TASKS.md 当前周任务列表 + +## 追踪文件路径 +`${OPENCLAW_WORKSPACE}/TASKS.md` + +## Output contract + +This skill does not produce files by itself; the converted openclaw skill should declare its outputs in a new section here. (Filled in by the user after first run.) + +## Failure handling + +If a required external tool or path is missing, surface the exact missing identifier to the user instead of guessing. Do not auto-install system packages. (Add skill-specific failure modes here.) diff --git a/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md b/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md new file mode 100644 index 0000000..42d6e9c --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/output/task-tracker/conversion-report.md @@ -0,0 +1,21 @@ +# Conversion report + +- **input**: `C:\Users\Administrator\.minimax\scratch\skill-bridge-fork\plugins\antianqi\skill-bridge\examples\input\task-tracker\SKILL.md` +- **tier**: pure / pure-wrapped-fix +- **reason**: 1 hardcoded path group(s) found + +## Path changes +- `openclaw-workspace` → ${OPENCLAW_WORKSPACE} (2x) + +## Written files + + +## Recommendations +- parameterize paths via paths.js +- ensure UTF-8 output +- add Windows adaptation section if body uses shell commands + +## Warnings +- paths parameterized: openclaw-workspace + +_generated by skill-bridge v0.2.0 on 2026-08-17T06:03:53.024Z_ diff --git a/plugins/antianqi/skill-bridge/examples/regen.mjs b/plugins/antianqi/skill-bridge/examples/regen.mjs new file mode 100644 index 0000000..d763c87 --- /dev/null +++ b/plugins/antianqi/skill-bridge/examples/regen.mjs @@ -0,0 +1,40 @@ +// examples/regen.mjs +// +// Regenerate examples/output/task-tracker/ by running the v0.2 +// converter pipeline against examples/input/task-tracker/. +// +// This is the same code path the MCP `convert` tool uses — it just +// inlines the import-and-call instead of going through JSON-RPC. +// +// Usage from the plugin root: +// node examples/regen.mjs + +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { analyzeSkillFile } from '../lib/analyze.js'; +import { classify } from '../lib/classify.js'; +import { transformSkill } from '../lib/transform-skill.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const PLUGIN_ROOT = path.resolve(__dirname, '..'); + +const source = path.join(PLUGIN_ROOT, 'examples', 'input', 'task-tracker', 'SKILL.md'); +const outDir = path.join(PLUGIN_ROOT, 'examples', 'output', 'task-tracker'); + +const report = await analyzeSkillFile(source); +const result = classify(report); +const r = await transformSkill({ + inputPath: source, + report, + classify: result, + outDir, +}); + +console.log('Wrote:'); +for (const f of r.written) { + console.log(` ${path.relative(PLUGIN_ROOT, f)}`); +} +if (r.warnings.length) { + console.log('Warnings:'); + for (const w of r.warnings) console.log(` ${w}`); +}