Skip to content

Add skill-bridge plugin (antianqi/skill-bridge) v0.2.0 - #2

Open
antianqi wants to merge 4 commits into
MiniMax-AI:mainfrom
antianqi:add-skill-bridge-v2
Open

Add skill-bridge plugin (antianqi/skill-bridge) v0.2.0#2
antianqi wants to merge 4 commits into
MiniMax-AI:mainfrom
antianqi:add-skill-bridge-v2

Conversation

@antianqi

@antianqi antianqi commented Aug 17, 2026

Copy link
Copy Markdown

What

A stdio MCP server plugin that converts openclaw (or similar) skills into portable mavis / mcode-compatible Skills.

Why this PR is being opened on MiniMax-AI/MiniMax-Code-Plugins

A v0.1 of this plugin was opened as PR #3 against the now-superseded hetaoBackend/MiniMax-Code-Plugins repository, which has since been transferred to MiniMax-AI/MiniMax-Code-Plugins. The old PR was lost in the transfer (verified: GET /MiniMax-AI/.../pulls/3 returns 404; list pulls?state=all shows 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) on hetaoBackend/main#64bc5dd, in the same direction hetaoBackend had asked for in their round-2 review.

What changed from v0.1 (the round-2 blockers)

  1. Portable delivery model. Dropped package.json / package-lock.json / index.js and the CLI surface. Added mcp.json + server.mjs, a single stdio MCP server (node ./server.mjs) declared per the portable Agent Plugins 1.0 contract. The plugin needs no npm install and no global bin to work.
  2. Zero npm dependencies. The encoding detector now uses Node 22+'s built-in TextDecoder('gb18030'); the YAML frontmatter is parsed/serialized by a hand-rolled subset parser in lib/analyze.js. npm run validate and npm test pass without any package install.
  3. Atomic replace. lib/transform-skill.js uses a backup-and-rename dance: a pre-existing outDir is moved to <outDir>.bak-<pid>-<rand>, the staging dir is renamed onto outDir, the backup is then removed. If anything fails, the backup is moved back, so outDir is preserved. Regression test: tests/transform-atomic.test.mjs.
  4. Lint failure semantics. lib/lint.js returns { ok: false, code: 2, stdout, stderr } faithfully; the MCP convert tool surfaces that in its response. Callers see lint.ok === false and act accordingly. Regression test: tests/lint.test.mjs (the fast-path failure case).
  5. In-tree scope. The only previously out-of-tree change (the root .gitignore) has been reverted; the plugin-local ignores now live under plugins/antianqi/skill-bridge/.gitignore.

Schema

plugin.json targets https://agent-plugins.org/schemas/1.0.0/plugin.schema.json. mcp.json declares one stdio server. server.mjs exposes four tools:

Tool Returns
detect(source) { encoding, originalEncoding, replaced, confidence, reason }
analyze(source) full frontmatter, body, hardcoded paths, external commands, warnings
classify(source) { tier, subTier, reason } in pure / pure-wrapped-fix / wrapped-* / abandon
convert(source, target_dir, force?, run_lint?) writes the converted skill, returns { ok, tier, subTier, written, warnings, lint }

Tests

node --test plugins/antianqi/skill-bridge/tests/*.test.mjs → 50/50 pass.

npm run validateOK plugin antianqi/skill-bridge.

npm run check shows two pre-existing failures unrelated to this plugin (CRLF line endings in examples/hello-mcode/SKILL.md; Windows path.separator in test/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 running convert on examples/input/task-tracker/. examples/regen.mjs regenerates it locally.

The two upstream-openclaw demos 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

  • No network access. No credentials. Reads only the source path the caller provides.
  • Writes only to the caller-provided target_dir and to a unique os.tmpdir()/sb-lint-<pid>-<rand>/ directory that is removed after the lint step completes.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

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.
@antianqi

Copy link
Copy Markdown
Author

@hetaoBackend Thanks for the round-2 review. I've stepped back from
the v0.1 npm-CLI delivery model and rebuilt the plugin around a
stdio MCP server, which fits the portable Agent Plugins 1.0
contract you cited.

Three new commits on top of 64bc5dd:

  1. 3c41ee0 — revert the root .gitignore overwrite.
    Plugin-local ignores now live under
    plugins/antianqi/skill-bridge/.gitignore.
  2. 3dfa159 — restructure to v0.2.0:
    • Drop package.json / package-lock.json / index.js
      (the CLI surface they implied).
    • Add mcp.json + server.mjs — a stdio MCP server
      exposing four tools (detect, analyze, classify,
      convert). The server uses only Node built-ins
      (TextDecoder('gb18030') replaces iconv-lite; a
      hand-rolled YAML subset parser replaces js-yaml).
    • Rewrite skills/skill-bridge/SKILL.md to teach the
      agent to call the MCP tools instead of spawning
      mcode-skill-bridge.
  3. 1a22b12 — regenerate the task-tracker demo with
    v0.2; add examples/regen.mjs so contributors can
    reproduce the demo locally.

Point-by-point on the round-2 blockers:

  1. Portable delivery model: the MCP server is a
    single node ./server.mjs invocation. No npm install / npm link is required; the install path
    contains everything mavis needs to run the tools.
  2. Build / dependency surface: no npm dependencies
    ship with the plugin. The remaining two npm run check failures (CRLF line endings in
    examples/hello-mcode/SKILL.md; Windows
    path.separator in hosted-plugins.test.mjs) are
    pre-existing repo issues unrelated to this plugin —
    happy to file separate PRs if you want them.
  3. Atomic replace: lib/transform-skill.js now uses
    a backup-and-rename dance. The pre-existing outDir
    is moved to <outDir>.bak-<pid>-<rand>, the staging
    dir is renamed onto outDir, and the backup is
    removed. If any step fails, the backup is renamed
    back so outDir is restored. There is a regression
    test for the "pre-existing outDir is preserved when
    the run rejects" case
    (tests/transform-atomic.test.mjs).
  4. Lint failure semantics: lib/lint.js no longer
    conflates FAIL with WARN. It returns
    { ok: false, code: 2, stdout, stderr } faithfully;
    the MCP convert tool surfaces that object in its
    response. Callers see lint.ok === false and act
    accordingly. There is a fast-path test
    (tests/lint.test.mjs) that exercises the failure
    path without spawning a subprocess.
  5. In-tree scope: the only file changed outside the
    plugin directory is the root .gitignore, and that
    change is reverted in 3c41ee0.

Test count: 50 (was 33 in v0.1). All pass. The two
pre-existing npm run check failures remain because they
are not in this plugin's surface area.

Demo inventory: the v0.1 PR carried three demos; v0.2
ships only task-tracker (the author's own content).
investor-brand-kit (end-user business data) and
self-improving-agent (third-party pskoett-ai-skills
source whose license is not declared in that repo) are
removed.

@hetaoBackend hetaoBackend left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 calls process.exit(2) when imported without a CLI argument. A default convert(..., 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), but server.mjs:155, analyze.js:193-194, and detect.js:88-90 pass directories directly to readFile, producing EISDIR.
  • 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-195 moves 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.

antianqi added a commit to antianqi/MiniMax-Code-Plugins-1 that referenced this pull request Aug 22, 2026
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants