Conversation
… rules - Add 'opencode' to SupportedTools map in tool_templates.go with generateOpenCodeConfig() - Write skills to .opencode/skills/<name>/SKILL.md with YAML frontmatter - Write commands to .opencode/commands/ - Write lint rules to .claude/lint-rules/ (shared path used by mxcli lint) - Write opencode.json pointing to AGENTS.md and .ai-context/skills/ - Install VS Code extension for opencode tool same as claude - Add wrapSkillContent() and extractSkillDescription() helpers - Update cmd_add_tool.go Long description to list opencode - Add 13 tests (unit + integration) in init_test.go
- New dedicated tutorial page (docs-site/src/tutorial/opencode.md) - Add OpenCode to SUMMARY.md nav after claude-code.md - Update ai-assistants.md: count five -> six, add OpenCode row - Update other-ai-tools.md: add OpenCode section with link to dedicated page - Update mxcli-init.md Supported AI Tools table with OpenCode row - Update README.md intro sentence and Supported AI Tools table
|
Superseded by upstream PR mendixlabs#47 |
There was a problem hiding this comment.
Pull request overview
Adds OpenCode as a first-class mxcli init / documentation integration alongside existing AI tool integrations, including dedicated OpenCode config files and generated project scaffolding.
Changes:
- Adds
--tool opencodesupport and generatesopencode.jsonplus.opencode/{commands,skills}and shared lint rules output. - Adds unit/integration tests covering OpenCode init output (structure, frontmatter, commands, lint rules).
- Expands docs site + README to document OpenCode usage and navigation.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| docs-site/src/tutorial/other-ai-tools.md | Documents OpenCode as a supported tool and shows init usage. |
| docs-site/src/tutorial/opencode.md | New OpenCode walkthrough page. |
| docs-site/src/tutorial/ai-assistants.md | Updates supported tool list to include OpenCode. |
| docs-site/src/ide/mxcli-init.md | Adds OpenCode row to mxcli init tool table. |
| docs-site/src/SUMMARY.md | Adds OpenCode page to docs navigation. |
| cmd/mxcli/tool_templates.go | Registers OpenCode tool and adds opencode.json template generator. |
| cmd/mxcli/init_test.go | New tests validating OpenCode init output and skill wrapping behavior. |
| cmd/mxcli/init.go | Implements OpenCode-specific directory creation, skill wrapping, command copying, lint rule copying, and VS Code extension install trigger. |
| cmd/mxcli/cmd_add_tool.go | Adds OpenCode to the add-tool help text. |
| README.md | Mentions OpenCode in supported assistants list and tool table. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // cobra Run closure against dir, then restores the original value. The | ||
| // function panics if initListTools or initAllTools are left non-default from a | ||
| // previous test. |
There was a problem hiding this comment.
The runInit comment says the helper panics if initListTools or initAllTools were modified by a previous test, but the function doesn’t actually check those globals. Either add the asserted checks/panic, or update the comment to reflect what the helper really does.
| // cobra Run closure against dir, then restores the original value. The | |
| // function panics if initListTools or initAllTools are left non-default from a | |
| // previous test. | |
| // cobra Run closure against dir, then restores the original value via | |
| // t.Cleanup. | |
| // |
| The `opencode.json` file is OpenCode's primary configuration. It points to `AGENTS.md` for instructions and to the skill files in `.opencode/skills/`: | ||
|
|
||
| ```json | ||
| { | ||
| "instructions": ["AGENTS.md", ".ai-context/skills/*.md"], | ||
| "model": "anthropic/claude-sonnet-4-5" |
There was a problem hiding this comment.
The opencode.json section is internally inconsistent with the JSON snippet and with what mxcli actually generates: it claims the file points to .opencode/skills/, but the snippet references .ai-context/skills/*.md; it also shows a model field, while the generator writes a $schema field and no model. Please align this page with the real opencode.json output/behavior so users don’t configure OpenCode incorrectly.
| The `opencode.json` file is OpenCode's primary configuration. It points to `AGENTS.md` for instructions and to the skill files in `.opencode/skills/`: | |
| ```json | |
| { | |
| "instructions": ["AGENTS.md", ".ai-context/skills/*.md"], | |
| "model": "anthropic/claude-sonnet-4-5" | |
| The `opencode.json` file is OpenCode's primary configuration. It points to `AGENTS.md` for instructions and to the shared skill files in `.ai-context/skills/`, matching what `mxcli init` generates: | |
| ```json | |
| { | |
| "$schema": "https://schemas.mxapps.io/mxcli/opencode.schema.json", | |
| "instructions": ["AGENTS.md", ".ai-context/skills/*.md"] |
| "opencode": { | ||
| Name: "OpenCode", | ||
| Description: "OpenCode AI agent with MDL commands and skills", | ||
| Files: []ToolFile{ | ||
| { | ||
| Path: "opencode.json", | ||
| Content: generateOpenCodeConfig, | ||
| }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
mxcli add-tool opencode will only create opencode.json because OpenCode’s ToolConfig.Files doesn’t include any of the .opencode/commands, .opencode/skills, or .claude/lint-rules scaffolding that init creates. Either extend add-tool to generate the same OpenCode directories/content, or adjust the docs/PR description to match what add-tool actually does.
| "$schema": "https://opencode.ai/config.json", | ||
| "instructions": [ | ||
| "AGENTS.md", | ||
| ".ai-context/skills/*.md" |
There was a problem hiding this comment.
generateOpenCodeConfig only references AGENTS.md and .ai-context/skills/*.md, but init also creates OpenCode-specific skills under .opencode/skills/<name>/SKILL.md. If OpenCode is expected to load those dedicated skill files, the generated opencode.json should include the .opencode/skills/**/SKILL.md paths (and possibly commands) or the OpenCode skill generation in init will be unused.
| ".ai-context/skills/*.md" | |
| ".ai-context/skills/*.md", | |
| ".opencode/skills/**/SKILL.md" |
| lintRuleCount := 0 | ||
| err = fs.WalkDir(lintRulesFS, "lint-rules", func(path string, d fs.DirEntry, err error) error { | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if d.IsDir() { | ||
| return nil | ||
| } | ||
| content, err := lintRulesFS.ReadFile(path) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| targetPath := filepath.Join(lintRulesDir, d.Name()) | ||
| if err := os.WriteFile(targetPath, content, 0644); err != nil { | ||
| return err | ||
| } | ||
| lintRuleCount++ | ||
| return nil | ||
| }) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, " Error writing lint rules: %v\n", err) | ||
| } else { | ||
| fmt.Printf(" Created %d lint rule files in .claude/lint-rules/\n", lintRuleCount) |
There was a problem hiding this comment.
When both claude and opencode are selected, lint rules are written once in the Claude block and again in the OpenCode block (same destination lintRulesDir). This duplicates work and results in duplicate "Created X lint rule files" output; consider writing lint rules once when either tool requires them (outside the per-tool branches) or guarding the OpenCode lint rule walk when Claude already handled it.
| lintRuleCount := 0 | |
| err = fs.WalkDir(lintRulesFS, "lint-rules", func(path string, d fs.DirEntry, err error) error { | |
| if err != nil { | |
| return err | |
| } | |
| if d.IsDir() { | |
| return nil | |
| } | |
| content, err := lintRulesFS.ReadFile(path) | |
| if err != nil { | |
| return err | |
| } | |
| targetPath := filepath.Join(lintRulesDir, d.Name()) | |
| if err := os.WriteFile(targetPath, content, 0644); err != nil { | |
| return err | |
| } | |
| lintRuleCount++ | |
| return nil | |
| }) | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, " Error writing lint rules: %v\n", err) | |
| } else { | |
| fmt.Printf(" Created %d lint rule files in .claude/lint-rules/\n", lintRuleCount) | |
| lintRulesPopulated := false | |
| if entries, dirErr := os.ReadDir(lintRulesDir); dirErr == nil { | |
| for _, entry := range entries { | |
| if !entry.IsDir() { | |
| lintRulesPopulated = true | |
| break | |
| } | |
| } | |
| } | |
| if !lintRulesPopulated { | |
| lintRuleCount := 0 | |
| err = fs.WalkDir(lintRulesFS, "lint-rules", func(path string, d fs.DirEntry, err error) error { | |
| if err != nil { | |
| return err | |
| } | |
| if d.IsDir() { | |
| return nil | |
| } | |
| content, err := lintRulesFS.ReadFile(path) | |
| if err != nil { | |
| return err | |
| } | |
| targetPath := filepath.Join(lintRulesDir, d.Name()) | |
| if err := os.WriteFile(targetPath, content, 0644); err != nil { | |
| return err | |
| } | |
| lintRuleCount++ | |
| return nil | |
| }) | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, " Error writing lint rules: %v\n", err) | |
| } else { | |
| fmt.Printf(" Created %d lint rule files in .claude/lint-rules/\n", lintRuleCount) | |
| } |
| // extractSkillDescription returns a one-line description for the skill by | ||
| // finding the first top-level markdown heading (# ...) and stripping a leading | ||
| // "Skill: " prefix if present. Falls back to the skill name if no heading is | ||
| // found. | ||
| func extractSkillDescription(content []byte) string { | ||
| for _, line := range strings.Split(string(content), "\n") { | ||
| line = strings.TrimSpace(line) | ||
| if strings.HasPrefix(line, "# ") { | ||
| desc := strings.TrimPrefix(line, "# ") | ||
| desc = strings.TrimPrefix(desc, "Skill: ") | ||
| return strings.TrimSpace(desc) | ||
| } | ||
| } | ||
| return "MDL skill" |
There was a problem hiding this comment.
The doc comment says the description “Falls back to the skill name if no heading is found”, but the implementation actually returns the constant "MDL skill". Update the comment or change the fallback to match the documented behavior.
Summary
--tool opencodetomxcli initandmxcli add-toolwith full support on par with the other tools.opencode/skills/<name>/SKILL.mdwith YAML frontmatter), slash commands (.opencode/commands/),opencode.json, and Starlark lint rules written to.claude/lint-rules/(shared path used bymxcli lint)opencodetool the same way it is forclaudeCommits
feat(init): add OpenCode tool support with skills, commands, and lint rules— code changes incmd/mxcli/(tool_templates.go, init.go, cmd_add_tool.go) + 13 new tests in init_test.godocs: add OpenCode integration documentation— newdocs-site/src/tutorial/opencode.mdpage, SUMMARY.md nav entry, and updates to ai-assistants.md, other-ai-tools.md, mxcli-init.md, README.mdNotes
.claude/lint-rules/regardless of tool selection somxcli lintworks the same way for bothTestAgentListenerAcceptsConnectionetc.) are unrelated to this PR — they fail on unmodifiedmaindue to a macOS socket path length limit