Add skill-bridge plugin (antianqi/skill-bridge) v0.2.0 - #2
Conversation
A stdio MCP server plugin that converts openclaw (or similar) skills
into mavis/mcode-compatible Skills. The plugin is self-contained:
no npm install, no node_modules, no native binaries, no symlinks,
no hidden telemetry. It declares one stdio MCP server via mcp.json
(node ./server.mjs) and exposes four tools:
detect (source) -> encoding + mojibake status
analyze (source) -> full frontmatter / paths / commands
classify (source) -> pure | pure-wrapped-fix | wrapped-* | abandon
convert (source, target_dir,
force?, run_lint?) -> writes converted skill to target_dir
What changed from v0.1 of this plugin (PR MiniMax-AI#3 on the old
hetaoBackend/MiniMax-Code-Plugins repo, which was lost in the
transfer to MiniMax-AI/MiniMax-Code-Plugins):
- Drop package.json, package-lock.json, and the CLI entry point.
The plugin no longer relies on npm install or a global bin.
- Add mcp.json + server.mjs, a JSON-RPC-over-stdio MCP server
declared as a portable Agent Plugin.
- Drop the iconv-lite and js-yaml dependencies. The encoding
detector uses Node 22+'s built-in TextDecoder('gb18030'),
and the YAML frontmatter is parsed / serialized by a small
hand-rolled subset parser in lib/analyze.js.
- Rewrite skills/skill-bridge/SKILL.md to teach the agent to
call the MCP tools instead of spawning a CLI.
- Atomic-replace: lib/transform-skill.js uses a backup-and-rename
dance so a pre-existing target_dir is preserved if the
conversion fails (covered by tests/transform-atomic.test.mjs).
- Lint failure: lib/lint.js returns ok=false, code!=0 on a
failing lint. The MCP convert tool surfaces that to the caller.
- Pruned demos: investor-brand-kit (end-user business data) and
self-improving-agent (third-party copy without a declared
license) are removed. The only demo shipped is task-tracker,
the author's own content.
Test count: 50 (was 33 in v0.1). All pass. The npm run check
failures that remain in the repo (CRLF line endings in
examples/hello-mcode/SKILL.md; Windows path.separator in
hosted-plugins.test.mjs) are pre-existing and unrelated to this
plugin.
|
hetaoBackend
left a comment
There was a problem hiding this comment.
Review result: do not approve / do not merge yet.
The repository check passes (77 tests), but the default product path has blocking defects:
plugins/antianqi/skill-bridge/lib/lint.js:46-53: the default host linter is a CLI-only module that callsprocess.exit(2)when imported without a CLI argument. A defaultconvert(..., run_lint=true)therefore terminates the MCP server before it can return the documented response.- The docs advertise both a SKILL.md path and a directory source (
README.md:50,skills/skill-bridge/SKILL.md:35,51), butserver.mjs:155,analyze.js:193-194, anddetect.js:88-90pass directories directly toreadFile, producingEISDIR. - Unsupported YAML lists are treated as parse failure (
analyze.js:79-82,147-154), then conversion proceeds with empty frontmatter (transform-skill.js:64-100), silently discarding metadata and embedding the original frontmatter in the body. This should either be supported or fail closed. transform-skill.js:187-195moves the existing output away and only then moves staging into place; there is a missing-target window and a crash can leave the output absent despite the atomicity claim.
Please fix the default lint lifecycle first, add directory/list-frontmatter regression tests, and narrow the atomic replacement guarantee before requesting another review.
Fixes for review comments from hetaoBackend (commit fce7c5f): MiniMax-AI#1 detector hard-coded path: resolve the [userprofile]/.minimax-code directory at runtime via the mcode node process cmdline (regex on @minimax-ai/code/cli.js), with fallbacks to $env:USERPROFILE/.minimax-code, $env:APPDATA/minimax-code, and the current working directory. Override with -Root [path]. MiniMax-AI#2 idle fallback unreachable: mtime cache now returns the last inferred message instead of null, so the 60s stale -> idle branch fires every poll. Verified locally: idle :: already idle 195s after 65s of inactivity. #2b session log: prefer ledger.jsonl (mcode v2 event stream) and fall back to messages.jsonl when ledger is missing. Both formats are handled in Infer-State (kind/phase for ledger, message.role for messages). MiniMax-AI#3 PID reuse safety: start/stop-{island,detect-island}.ps1 now verify the target PID command line contains the expected script path before acting. Stale PIDs and PID-reused processes are refused with a REFUSED log line instead of being killed. MiniMax-AI#4 wrap-tool.ps1 shell-injection: removed Invoke-Expression entirely. The wrapper is now status-only; the agent runs the command via mcode's own bash tool and passes -ExitCode to publish the outcome. Documented in README + SKILL.md. MiniMax-AI#5 README: -Enable -> -Action Enable to match autostart.ps1 parameter set. MiniMax-AI#6 start-island.ps1 readiness: dropped the 'about to ShowDialog' log wait (which was never emitted). Now polls MainWindowHandle != 0 every 500ms for up to 8s. Tests: validator reports OK plugin antianqi/mcode-island. wrap-tool 6-state matrix verified locally (working / done / waiting / error).
) The README and SKILL.md promise that `source` may be either a SKILL.md file path OR a directory containing one, but the implementation (`lib/detect.js:88-91` and `lib/analyze.js:193-194`) called `fs.readFile` directly. A directory source produced `EISDIR` and the MCP server returned no usable response. - `lib/detect.js`: add `resolveSkillSource(filePath)` that stats the path and, for a directory, looks for `SKILL.md` inside. `readFileSafe` now resolves first, then reads the resolved file. - `lib/analyze.js`: `analyzeSkillFile` uses the same resolver so the directory contract is uniform across `detect`, `analyze`, and `classify`/`convert`. `AnalyzedSkill.inputPath` now reports the resolved file, not the directory. - `tests/detect.test.mjs`: three new tests - directory with SKILL.md reads cleanly - directory without SKILL.md throws a descriptive error - file path is returned unchanged by `resolveSkillSource` `node --test plugins/antianqi/skill-bridge/tests/*.test.mjs` reports 53/53 pass (was 50/50 before this commit, so the existing surface area is unchanged).
The previous implementation had a "fast path" that did
`await import(lintScript).then(mod => mod.lint(skillPath))` in-process.
The default host linter at
`~/.minimax/.builtin-skills/skill-creator/scripts/lint-skill.js` calls
`process.exit(2)` when invoked without CLI arguments, and `process.exit`
is not catchable from JS — so a default invocation (no `run_lint=false`
override) terminated the entire MCP server before it could return a
JSON-RPC response.
- `lib/lint.js`: drop the in-process fast path; always run the
linter as a child process. Cost: one extra `node` spawn + a
staged `.mjs` in `os.tmpdir()` per `convert` call (~100 ms). The
trade is worth it: the MCP server is now guaranteed to survive a
misbehaving linter.
- `lib/lint.js`: pre-flight `fs.stat(lintScript)` so a missing host
linter surfaces as `{ ok: false, code: -1, stderr: 'lint script
not available: ...' }` instead of an uncaught ENOENT from
`fs.readFile` inside `stageMjsInTmp`.
- `tests/lint.test.mjs`: rewrite around the subprocess-only model.
Replace the fast-path test with three cases:
- subprocess path stages in `os.tmpdir()`, install dir untouched
- linter calls `process.exit(2)` and the MCP server still
returns `{ ok: false, code: 2 }`
- missing lintScript returns `{ ok: false, code: -1, stderr }`
`node --test plugins/antianqi/skill-bridge/tests/*.test.mjs` reports
54/54 pass (was 53/53; +1 new case for missing linter).
…s (review MiniMax-AI#4) The review called out a missing-target window in `atomicReplace`: between the `outDir -> backup` rename and the `staging -> outDir` rename, outDir is absent. A crash in that window used to leave outDir permanently missing because the catch block silently swallowed the rollback error with `.catch(() => {})`. - `lib/transform-skill.js`: export `atomicReplace` and add two test-only hooks (`opts.rename`, `opts.renameStaging`) so deterministic fault-injection tests can exercise the swap and rollback branches without monkey-patching `fs`. In the catch block, attach `err.recovery = { message, cause }` when the rollback itself fails, so the caller can take manual action instead of being told "outDir is missing" with no breadcrumb. - `tests/transform-atomic.test.mjs`: two new cases. - "staging -> outDir rename fails" — original outDir is restored from the backup, no stray `<outDir>.bak-*` is left behind. - "swap fails AND rollback fails" — the thrown error has a `.recovery` field whose message names the backup path so the caller can manually move it back. `node --test plugins/antianqi/skill-bridge/tests/*.test.mjs` reports 56/56 pass (was 54/54; +2 new atomic-replace cases).
What
A stdio MCP server plugin that converts
openclaw(or similar) skills into portablemavis/mcode-compatible Skills.Why this PR is being opened on
MiniMax-AI/MiniMax-Code-PluginsA v0.1 of this plugin was opened as PR #3 against the now-superseded
hetaoBackend/MiniMax-Code-Pluginsrepository, which has since been transferred toMiniMax-AI/MiniMax-Code-Plugins. The old PR was lost in the transfer (verified:GET /MiniMax-AI/.../pulls/3returns 404;list pulls?state=allshows only #1).This PR reopens the same plugin against the new official repo. The single commit (
64ede9f) is a clean replacement of the old three-commit series (3c41ee0+3dfa159+1a22b12) onhetaoBackend/main#64bc5dd, in the same direction hetaoBackend had asked for in their round-2 review.What changed from v0.1 (the round-2 blockers)
package.json/package-lock.json/index.jsand the CLI surface. Addedmcp.json+server.mjs, a single stdio MCP server (node ./server.mjs) declared per the portable Agent Plugins 1.0 contract. The plugin needs nonpm installand no global bin to work.TextDecoder('gb18030'); the YAML frontmatter is parsed/serialized by a hand-rolled subset parser inlib/analyze.js.npm run validateandnpm testpass without any package install.lib/transform-skill.jsuses a backup-and-rename dance: a pre-existingoutDiris moved to<outDir>.bak-<pid>-<rand>, the staging dir is renamed ontooutDir, the backup is then removed. If anything fails, the backup is moved back, sooutDiris preserved. Regression test:tests/transform-atomic.test.mjs.lib/lint.jsreturns{ ok: false, code: 2, stdout, stderr }faithfully; the MCPconverttool surfaces that in its response. Callers seelint.ok === falseand act accordingly. Regression test:tests/lint.test.mjs(the fast-path failure case)..gitignore) has been reverted; the plugin-local ignores now live underplugins/antianqi/skill-bridge/.gitignore.Schema
plugin.jsontargetshttps://agent-plugins.org/schemas/1.0.0/plugin.schema.json.mcp.jsondeclares one stdio server.server.mjsexposes four tools:detect(source){ encoding, originalEncoding, replaced, confidence, reason }analyze(source)classify(source){ tier, subTier, reason }inpure/pure-wrapped-fix/wrapped-*/abandonconvert(source, target_dir, force?, run_lint?){ ok, tier, subTier, written, warnings, lint }Tests
node --test plugins/antianqi/skill-bridge/tests/*.test.mjs→ 50/50 pass.npm run validate→OK plugin antianqi/skill-bridge.npm run checkshows two pre-existing failures unrelated to this plugin (CRLF line endings inexamples/hello-mcode/SKILL.md; Windowspath.separatorintest/hosted-plugins.test.mjs). Happy to open a follow-up PR to address either if you want them.Demo
The only demo is
examples/output/task-tracker/, the result of runningconvertonexamples/input/task-tracker/.examples/regen.mjsregenerates it locally.The two upstream-
openclawdemos from v0.1 (investor-brand-kit,self-improving-agent) are removed: the first contained end-user business data; the second was a copy of a third-party repo whose license is not declared in that repo.Data and network
target_dirand to a uniqueos.tmpdir()/sb-lint-<pid>-<rand>/directory that is removed after the lint step completes.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.