feat(qoder): add Qoder MCP and commands support (2/4)#1601
Conversation
Add Qoder (https://qoder.com) as a new tool target with rules support. Qoder is an AI coding tool developed by Alibaba Group, designed for enterprise-grade intelligent code generation and development assistance. - Add "qoder" to ALL_TOOL_TARGETS - Add qoder-specific fields to RulesyncRuleFrontmatterSchema - Implement QoderRule adapter for .qoder/rules/*.md with YAML frontmatter - Smart trigger inference maps rulesync canonical fields to Qoder's four native trigger modes: always_on, glob, model_decision, manual - Register QoderRule in rules-processor - Add .qoder/rules/ gitignore entry Qoder documentation: https://docs.qoder.com/user-guide/rules Co-authored-by: Cursor <cursoragent@cursor.com> AI-Contributed/Feature: 0/285 AI-Contributed/UT: 0/501
Add MCP and commands support for the Qoder tool target. - Implement QoderMcp adapter for .qoder/mcp.json - Implement QoderCommand adapter for .qoder/commands/*.md - Register both in their respective processors - Add gitignore entries for .qoder/mcp.json and .qoder/commands/ Co-authored-by: Cursor <cursoragent@cursor.com> AI-Contributed/Feature: 0/304 AI-Contributed/UT: 0/457
|
Thank you for your contribution! Unfortunately, this PR has 1547 added lines, which exceeds the limit of 1000 lines for external contributors. Please split your changes into smaller PRs. See CONTRIBUTING.md for details. |
|
@zxhdaniel Please work on the ci failure. |
dyoshikawa-claw
left a comment
There was a problem hiding this comment.
Overall this is a solid implementation — it follows the existing patterns in the codebase consistently, the tests are thorough, and the trigger inference logic in QoderRule.fromRulesyncRule is well thought out.
The CI failure in oxlint is the main blocker to address (the != usage in qoder-rule.ts). A couple of other minor things: the command adapter uses outputRoot: "." where process.cwd() would be more consistent with other adapters, and the MCP adapter's fromRulesyncMcp fully overwrites the target file — consider merging with existing content like CursorMcp does to preserve user-added MCP servers.
| const description = fm.qoder?.description ?? fm.description; | ||
|
|
||
| const isCatchAll = | ||
| globs != null && globs.length > 0 && globs.every((g) => g === "**/*" || g === "**"); |
There was a problem hiding this comment.
Use !== instead of != here — this is what's causing the oxlint CI failure. TypeScript's strict null checks make != unnecessary, and the linter enforces !==.
|
|
||
| const isCatchAll = | ||
| globs != null && globs.length > 0 && globs.every((g) => g === "**/*" || g === "**"); | ||
| const hasSpecificGlobs = globs != null && globs.length > 0 && !isCatchAll; |
There was a problem hiding this comment.
Same != → !== issue as above.
| const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter); | ||
|
|
||
| return new RulesyncCommand({ | ||
| outputRoot: ".", |
There was a problem hiding this comment.
This should be outputRoot: process.cwd() — using "." breaks when cwd differs from the mocked test directory. Other command adapters (e.g. junie-command.ts, geminicli-command.ts) use process.cwd().
|
|
||
| constructor(params: ToolMcpParams) { | ||
| super(params); | ||
| this.json = this.fileContent !== undefined ? JSON.parse(this.fileContent) : {}; |
There was a problem hiding this comment.
JSON.parse can throw on malformed input — consider wrapping in try/catch with a descriptive error message that includes the file path, like CursorMcp does. Helps users debug their .qoder/mcp.json.
| }); | ||
| } | ||
|
|
||
| static fromRulesyncMcp({ |
There was a problem hiding this comment.
fromRulesyncMcp replaces the entire file content without reading the existing .qoder/mcp.json first. If users have manually added MCP servers outside of rulesync, those get lost on generate. CursorMcp.fromRulesyncMcp shows the pattern for reading the existing file and merging. If this overwrite is intentional, a comment would help future readers.
| if (alwaysApply === true || isCatchAll) { | ||
| qoderFrontmatter = { trigger: "always_on", alwaysApply: true }; | ||
| } else if (hasSpecificGlobs) { | ||
| qoderFrontmatter = { trigger: "glob", glob: globs!.join(",") }; |
There was a problem hiding this comment.
The non-null assertion globs! here is safe due to the hasSpecificGlobs guard above, but TypeScript can't narrow through that variable. Consider extracting globs to a local const after a null check to avoid the assertion entirely.
Summary
Add MCP and commands support for the Qoder tool target — the second PR in a series of 4 for full Qoder integration.
About Qoder: Qoder is an AI coding tool developed by Alibaba Group. Documentation: https://docs.qoder.com
Changes
QoderMcpadapter for.qoder/mcp.jsonQoderCommandadapter for.qoder/commands/*.md.qoder/mcp.jsonand.qoder/commands/PR Series
This is PR 2 of 4 for full Qoder support:
Test plan
oxfmt --check .)Made with Cursor