feat: add Codemod skill documentation and command structure#2226
Conversation
| Some(language.to_string()) | ||
| } | ||
|
|
||
| fn resolve_tool<'a>(tools: &'a [CliToolInfo], name: &str) -> Option<&'a CliToolInfo> { |
There was a problem hiding this comment.
[LOW] ai tool node-types alias is not resolved, unlike ai call node-types
handle_tool uses resolve_tool, which normalizes only - to _ (node-types -> node_types), but the advertised tool name in metadata is get_node_types. Meanwhile call_tool_text explicitly aliases node-types to get_node_types. This creates inconsistent behavior across sibling commands: ai call node-types works, but ai tool node-types returns unknown tool. Reuse the same normalization/alias mapping for metadata lookup to keep command behavior consistent.
| @@ -0,0 +1,72 @@ | |||
| use std::io::Write; | |||
There was a problem hiding this comment.
[LOW] New CLI AI command branches are only partially covered by integration tests
The new integration test file validates dump-ast, node-types, docs (list mode), and tools JSON, but does not cover newly added tool, call, resources, resource, or docs query/read modes. Given the new parsing/dispatch logic and alias handling, this leaves key regression paths untested. Adding end-to-end coverage for at least one success and one failure case per new command would materially reduce risk.
Codex ReviewThe PR adds substantial new Findings
|
There was a problem hiding this comment.
Pull request overview
This PR expands the Codemod “skill” and CLI to support an MCP-equivalent, CLI-first “Codemod AI tools” workflow (docs lookup, AST dumping, node-type introspection, and tool/resource discovery/calls), and updates the MCP server to bundle local documentation as an offline fallback.
Changes:
- Updated the Codemod skill + command routing docs to prefer
npx codemod ai ...as a reproducible fallback when MCP isn’t available. - Added
codemod aisubcommands (dump-ast,node-types,docs,tools/tool,resources/resource,call) with integration tests. - Refactored
codemod-mcpdocs resources to build bundles from localdocs/content (and optionally fetch public docs), plus exposed “CLI tool/resource info” APIs.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| skills/codemod/SKILL.md | Updates core skill guidance to include CLI AI tooling and MCP-optional authoring flow. |
| skills/codemod/commands/codemod.md | Updates intent routing to prefer codemod ai CLI fallbacks and documents ai call validate_codemod_package. |
| skills/codemod/agents/openai.yaml | Adds an agent interface descriptor for the Codemod skill. |
| crates/mcp/src/lib.rs | Adds local-doc bundling, CLI tool/resource metadata, CLI-style tool calling, and adjusts docs fetch behavior. |
| crates/mcp/src/handlers/node_types.rs | Extracts node-types output formatting into a helper and adds a text-returning API. |
| crates/mcp/src/handlers/ast_dump.rs | Adds a text-returning API and reuses it from the MCP tool handler. |
| crates/mcp/src/data/prompts/sharding-instructions.md | Deletes old prompt fallback (now replaced by local docs/ bundling). |
| crates/mcp/src/data/prompts/jssg-utils-instructions.md | Deletes old prompt fallback (now replaced by local docs/ bundling). |
| crates/mcp/src/data/prompts/jssg-runtime-capabilities.md | Deletes old prompt fallback (now replaced by local docs/ bundling). |
| crates/mcp/src/data/prompts/jssg-instructions.md | Deletes old prompt fallback (now replaced by local docs/ bundling). |
| crates/mcp/src/data/prompts/jssg-gotchas.md | Deletes old prompt fallback (now replaced by local docs/ bundling). |
| crates/mcp/src/data/prompts/codemod-troubleshooting.md | Deletes old prompt fallback (now replaced by local docs/ bundling). |
| crates/mcp/src/data/prompts/codemod-maintainer-monorepo.md | Deletes old prompt fallback (now replaced by local docs/ bundling). |
| crates/mcp/src/data/prompts/codemod-creation-workflow.md | Deletes old prompt fallback (now replaced by local docs/ bundling). |
| crates/mcp/src/data/prompts/codemod-cli-instructions.md | Deletes old prompt fallback (now replaced by local docs/ bundling). |
| crates/mcp/src/data/prompts/ast-grep-gotchas.md | Deletes old prompt fallback (now replaced by local docs/ bundling). |
| crates/cli/tests/ai_cli_tools.rs | Adds integration tests for the new codemod ai CLI tools. |
| crates/cli/src/commands/harness_adapter.rs | Bumps skill version and embeds the updated skill/command markdown from skills/. |
| crates/cli/src/commands/ai/mod.rs | Wires new codemod ai subcommands into the CLI. |
| crates/cli/src/commands/ai/cli_tools.rs | Implements the new codemod ai tool/resource/docs commands backed by codemod-mcp. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "/../../skills/codemod/SKILL.md" | ||
| )); | ||
| const MCS_COMMAND_MD: &str = include_str!(concat!( | ||
| env!("CARGO_MANIFEST_DIR"), | ||
| "/../../skills/codemod/commands/codemod.md" |
| reqwest::Client::builder() | ||
| .timeout(Duration::from_secs(PUBLIC_DOCS_TIMEOUT_SECS)) | ||
| .no_proxy() | ||
| .user_agent(format!("codemod-mcp/{}", env!("CARGO_PKG_VERSION"))) | ||
| .build() | ||
| .ok() |
| fallback.clone(), | ||
| initial_wait, | ||
| move || async move { |
| format!( | ||
| r#"<TREE_SITTER_NODE_TYPES> | ||
| {node_types} | ||
| </TREE_SITTER_NODE_TYPES> | ||
|
|
TUI Perf Comparison
Focused Perf Regression Tests
Timed Perf Benchmarks
Fails only when candidate median is more than 25% slower and exceeds the benchmark-specific absolute floor. TUI Perf Comparison
Completed
Awaiting Trigger
Active
Awaiting Trigger Resume
Active Busy
Terminal Activity
|
23452a2 to
51a4283
Compare
51a4283 to
3bda6dc
Compare
| fn format_node_types_response(node_types: &str) -> String { | ||
| format!( | ||
| r#"<TREE_SITTER_NODE_TYPES> | ||
| {node_types} | ||
| </TREE_SITTER_NODE_TYPES> | ||
|
|
||
| <LEGEND> | ||
| Legends for field notation: | ||
| - \`?\` - optional field (may not be present in all instances) | ||
| - \`*\` - multiple values allowed (array/list of values) | ||
|
|
||
| In tree-sitter grammar: | ||
| - Fields are named children with specific roles in the syntax tree | ||
| - Format: \`fieldName=nodeType\` (e.g., "body=block") | ||
| - When a field is not named, it's represented as \`children=nodeType\` | ||
| - Multiple possible types are comma-separated (e.g., "value=string,number") | ||
| </LEGEND> | ||
| "# | ||
| ) | ||
| } |
| const DOCS: &[&str] = &[ | ||
| "README.md", | ||
| "cli.mdx", | ||
| "model-context-protocol.mdx", | ||
| "oss.mdx", | ||
| "oss-quickstart.mdx", | ||
| "package-structure.mdx", | ||
| "workflows/introduction.mdx", | ||
| "workflows/reference.mdx", | ||
| "workflows/sharding.mdx", | ||
| "jssg/intro.mdx", | ||
| "jssg/reference.mdx", | ||
| "jssg/security.mdx", | ||
| "jssg/advanced.mdx", | ||
| "jssg/testing.mdx", | ||
| "jssg/metrics.mdx", | ||
| "jssg/utils.mdx", | ||
| "jssg/semantic-analysis.mdx", | ||
| ]; |
|
|
||
| ### Publishing Changes | ||
|
|
||
| Install our Github App to autopropagate changes from youre repo to your deployment. Changes will be deployed to production automatically after pushing to the default branch. Find the link to install on your dashboard. |
| for resource in resources { | ||
| if resource.name.to_lowercase().contains(&query_lower) | ||
| || resource | ||
| .description | ||
| .unwrap_or_default() | ||
| .to_lowercase() | ||
| .contains(&query_lower) | ||
| { | ||
| matches.push(DocsSearchMatch { | ||
| name: resource.name.to_string(), | ||
| uri: resource.uri.to_string(), | ||
| line: 0, | ||
| snippet: resource.description.unwrap_or_default().to_string(), | ||
| }); | ||
| continue; | ||
| } | ||
|
|
||
| let content = server.read_resource_text(resource.uri).await?; | ||
| if let Some((index, line)) = content | ||
| .lines() | ||
| .enumerate() | ||
| .find(|(_, line)| line.to_lowercase().contains(&query_lower)) | ||
| { |
| fn format_node_types_response(node_types: &str) -> String { | ||
| format!( | ||
| r#"<TREE_SITTER_NODE_TYPES> | ||
| {node_types} | ||
| </TREE_SITTER_NODE_TYPES> | ||
|
|
| const DOCS: &[&str] = &[ | ||
| "README.md", | ||
| "cli.mdx", | ||
| "model-context-protocol.mdx", | ||
| "oss.mdx", | ||
| "oss-quickstart.mdx", | ||
| "package-structure.mdx", | ||
| "workflows/introduction.mdx", | ||
| "workflows/reference.mdx", | ||
| "workflows/sharding.mdx", | ||
| "jssg/intro.mdx", | ||
| "jssg/reference.mdx", | ||
| "jssg/security.mdx", | ||
| "jssg/advanced.mdx", | ||
| "jssg/testing.mdx", | ||
| "jssg/metrics.mdx", | ||
| "jssg/utils.mdx", | ||
| "jssg/semantic-analysis.mdx", | ||
| ]; |
|
|
||
| ### Publishing Changes | ||
|
|
||
| Install our Github App to autopropagate changes from youre repo to your deployment. Changes will be deployed to production automatically after pushing to the default branch. Find the link to install on your dashboard. |
| async fn search_docs( | ||
| server: &CodemodMcpServer, | ||
| resources: &[CliResourceInfo], | ||
| query: &str, | ||
| ) -> Result<Vec<DocsSearchMatch>> { | ||
| let query_lower = query.to_lowercase(); | ||
| let mut matches = Vec::new(); | ||
|
|
||
| for resource in resources { | ||
| if resource.name.to_lowercase().contains(&query_lower) | ||
| || resource | ||
| .description | ||
| .unwrap_or_default() | ||
| .to_lowercase() | ||
| .contains(&query_lower) | ||
| { | ||
| matches.push(DocsSearchMatch { | ||
| name: resource.name.to_string(), | ||
| uri: resource.uri.to_string(), | ||
| line: 0, | ||
| snippet: resource.description.unwrap_or_default().to_string(), | ||
| }); | ||
| continue; | ||
| } | ||
|
|
||
| let content = server.read_resource_text(resource.uri).await?; | ||
| if let Some((index, line)) = content | ||
| .lines() | ||
| .enumerate() | ||
| .find(|(_, line)| line.to_lowercase().contains(&query_lower)) | ||
| { | ||
| matches.push(DocsSearchMatch { | ||
| name: resource.name.to_string(), | ||
| uri: resource.uri.to_string(), | ||
| line: index + 1, | ||
| snippet: line.trim().to_string(), | ||
| }); | ||
| } |
- Introduced SKILL.md to outline the Codemod skill's purpose, usage, and CLI tools. - Created openai.yaml to define the interface for the Codemod agent. - Added codemod.md to specify command behavior for codemod tasks, including intent classification and routing for authoring and execution.
3bda6dc to
6046aca
Compare
| let normalized = query.to_lowercase(); | ||
|
|
||
| resources | ||
| .iter() | ||
| .find(|resource| { | ||
| resource.uri.eq_ignore_ascii_case(query) | ||
| || resource.name.eq_ignore_ascii_case(query) | ||
| || resource | ||
| .name | ||
| .strip_suffix("-instructions") | ||
| .is_some_and(|name| name.eq_ignore_ascii_case(query)) | ||
| || resource | ||
| .uri | ||
| .split_once("://") | ||
| .is_some_and(|(scheme, _)| scheme.eq_ignore_ascii_case(query)) | ||
| }) | ||
| .or_else(|| { | ||
| resources.iter().find(|resource| { | ||
| resource.name.to_lowercase().contains(&normalized) | ||
| || resource.uri.to_lowercase().contains(&normalized) | ||
| }) | ||
| }) |
| let content = server.read_resource_text_cached(resource.uri).await?; | ||
| if let Some((index, line)) = content | ||
| .lines() | ||
| .enumerate() | ||
| .find(|(_, line)| line.to_lowercase().contains(&query_lower)) | ||
| { | ||
| matches.push(DocsSearchMatch { | ||
| name: resource.name.to_string(), | ||
| uri: resource.uri.to_string(), | ||
| line: index + 1, | ||
| snippet: line.trim().to_string(), | ||
| }); | ||
| } |
📚 Description
🔗 Linked Issue
🧪 Test Plan
📄 Documentation to Update