ilmik
v0.3.0
Published
ilmik — Code analysis and transformations for AI agents.
Maintainers
Readme
ilmik
ilmik — Code analysis and transformations for AI agents.
ilmik is a deterministic, local code analysis and controlled batch transformation Model Context Protocol (MCP) server for JavaScript and TypeScript codebases powered by jscodeshift.
Unlike generic file-editing tools that rely on fragile regular expressions or loose string replacements, ilmik provides structured AST querying, in-memory fixture verification, whole-project preview planning with content hashes, and safe preflight application.
Key Guarantees
Zero Target Workspace Pollution
ilmik strictly respects the integrity of your project:
- No dependencies added: Never modifies
package.jsonor lockfiles in the target project. - No configuration files: Never drops
.codemods,.ilmik, or temporary cache files inside your workspace. - No Git requirement: Operates reliably on any local folder, whether git-tracked or not.
- Isolated external storage: Plans, temporary caches, snapshots, and backups are saved exclusively in the operating system's standard cache directory (
~/.cache/ilmikor~/Library/Caches/ilmik), strictly outside the target workspace. - Controlled writes: Only
apply_transformmodifies source files, and only after hash-based preflight validation.
Installation & Running
Run directly with npx (No Global Installation Required)
# Dynamic multi-project mode (default):
npx -y ilmik
# Fixed-root mode locked to a specific workspace:
npx -y ilmik --root /absolute/path/to/projectEnable Custom Code Execution (--trusted-code)
To allow AI agents to execute custom transforms and custom AST queries:
npx -y ilmik --trusted-codeGlobal Installation (Optional)
If you prefer having the ilmik command available globally in your PATH:
npm install -g ilmik
# Start MCP server over stdio
ilmik
# Or explicitly with the mcp subcommand
ilmik mcp --trusted-codeCLI Subcommands & Options
Usage:
ilmik [options]
ilmik mcp [options]
ilmik rollback <planId> [options]
ilmik history [options]
ilmik install-skill [options]
Subcommands:
mcp Start the MCP server over stdio (default)
rollback <planId> Roll back an applied transformation plan using external backup
--root <path> Target project workspace root (default: plan root or current directory)
--json Output complete result as raw JSON to stdout
history, stats View project transformation history & statistics
--root <path> Target project workspace root (default: current working directory)
--limit <n> Maximum number of past operations to list (default: 20)
--json Output complete report as raw JSON to stdout
--export <path> Export complete JSON report to specified file
install-skill Install the code-transform agent skill
--global Install to global agent config directory (~/.gemini/config/skills) [default]
--project Install to local project directory (.agents/skills)
--to <path> Install to a custom directory path
--symlink Create a symbolic link instead of copying files
--force, -f Overwrite existing skill directory
Options:
--root <path> Lock server to target workspace root (disallows targeting other roots)
--trusted-code Enable execution of custom transforms and AST analysis
--cache-dir <path> Directory outside target project for storing plans and backups (default: ~/.cache/ilmik)
--timeout <ms> Timeout in ms for transform executions (default: 10000)
--max-file-size <bytes> Maximum file size in bytes to process (default: 2097152 / 2MB)
-v, --version Display version information
-h, --help Display help messageWorkspace Root Contract
ilmik provides clear, deterministic workspace root semantics:
- Fixed-Root Mode (
--root <path>):- The server is strictly locked to the canonical path specified at startup.
- Tool invocations cannot target or expand to a different root. Any tool argument specifying a different
rootwill fail immediately with aSecurityError.
- Dynamic Multi-Workspace Mode (Default, started without
--root):- One running server instance can serve multiple local projects.
- Every workspace inspection and preview request (
inspect,preview_transform) must explicitly passroot: "/path/to/project". - Fallback Contract: If
rootis omitted when running in dynamic mode, the server throws an explicitSecurityError. It does not silently guess or write to an arbitrary directory.
- Plan & Snapshot Root Isolation:
- Transformation plans (
planId) and cached analysis snapshots (snapshotId) remain permanently bound to the canonical root where they were created. apply_transformtakes the target root strictly from the stored plan (plan.root). Requests attempting to cross-apply plans or read snapshots from other workspaces are rejected.
- Transformation plans (
- Cache Directory Separation:
- Both at startup and on dynamic tool calls,
ilmikverifies that the cache directory (cacheDir) is strictly located outside the target project directory.
- Both at startup and on dynamic tool calls,
Security Model & Code Execution Boundaries
Please understand ilmik's security guarantees and boundaries:
[!WARNING] Child Process is NOT a Security Sandbox: When custom transforms or custom analysis scripts are executed via
--trusted-code, they run inside a dedicated child process. This child process provides operational and stability isolation (preventing CPU infinite loops, unhandled syntax errors, or process crashes from killing the main MCP server). It does NOT enforce OS sandbox restrictions.
- User Privileges: Custom JavaScript code executed via
--trusted-coderuns with the permissions of the current user. It can make network requests or read accessible filesystem paths. Enable--trusted-codeonly when you trust the prompts and generated code. - Normal Workflow Zero Writes: In
ilmik's normal design,inspect,test_transform, andpreview_transformperform zero writes to source files. Onlyapply_transformmodifies workspace files after hash preflight checks. However, arbitrary transform code executed via--trusted-codecould theoretically have native side effects if authored maliciously. - Opt-in Only: Custom code execution requires explicit
--trusted-codeat server launch; tool calls cannot dynamically enable execution if the server was started without this flag.
MCP Client Configuration
ilmik communicates over stdio using standard JSON-RPC 2.0. Below are example configurations for popular MCP clients.
Claude Desktop (claude_desktop_config.json)
{
"mcpServers": {
"ilmik": {
"command": "npx",
"args": [
"-y",
"ilmik",
"--trusted-code"
]
}
}
}Generic MCP Client Entry (Dynamic Multi-Project)
{
"mcpServers": {
"ilmik": {
"command": "npx",
"args": [
"-y",
"ilmik",
"--trusted-code"
]
}
}
}Generic MCP Client Entry (Fixed-Root Mode)
{
"mcpServers": {
"ilmik": {
"command": "npx",
"args": [
"-y",
"ilmik",
"--root",
"/absolute/path/to/project",
"--trusted-code"
]
}
}
}Note: If ilmik is installed globally via npm install -g ilmik, you can replace "command": "npx" and "args": ["-y", "ilmik", ...] with "command": "ilmik" and "args": ["mcp", ...].
The Core MCP Tools
[1. inspect] ──> [2. test_transform] ──> [3. preview_transform] ──> [4. apply_transform]
│ │
│ [5. rollback]
▼ ▼
[paths & globs] ──> [Declarative Recipes] [Post-format & 6. history]1. inspect
Structural AST analysis without modifying source files.
- File outline (
query: { type: "file-outline" }): Discovers top-level functions, arrow functions, classes, methods, TS interfaces, and type aliases with start/end lines and truncated signatures without leaking bodies. Allows agents to discover structure without reading the full file into context. - Summary presentation mode (
output: "summary"): Groups findings by natural attributes (e.g. module source, callee, tag name, declaration kind) returning total scanned files, total matches, group counts, and limited representative samples (samplesPerGroup: 2) without accumulating massive finding arrays in memory. - Declarative module-references: Single-pass traversal extracting
import,reexport,require, anddynamic-importwithexactandprefix(path-boundary aware) modes, safely reporting dynamic expressions (unresolved-dynamic). - Declarative AST queries: Query imports, exports, function calls, JSX elements, and declarations without code execution.
- Granular scope: Use
paths,files, orinclude(e.g.["src/components/**"]) to narrow the inspection scope. - Early exit (
earlyExit: true): Halts file scanning immediately once matches reachlimitfor ultra-fast existence checks (isComplete: false). - Custom queries: Execute AST visitors (requires
--trusted-code). - Scope awareness: Differentiates imported identifiers from local variables and shadowed parameters.
- Pagination: Uses cached snapshots to paginate large result sets without re-scanning files.
File Outline Example
{
"query": {
"type": "file-outline"
},
"include": ["src/server.ts"],
"limit": 100
}Summary Mode Example
{
"query": {
"type": "imports",
"source": "@legacy/ui"
},
"output": "summary",
"samplesPerGroup": 2,
"limit": 50
}2. test_transform
Tests a jscodeshift transform or built-in recipe against in-memory input/output fixtures before touching workspace files.
- Built-in recipes: Test declarative recipes (
rename_import,rename_jsx_prop,rename_function_call,rename_identifier) safely without requiring--trusted-code. - Fixture assertions: Validates positive transformations and negative examples (
unchanged: true). - Syntax verification: Automatically re-parses transformed code to catch syntax breakages.
- Idempotency check: Applies the transform a second time to ensure stability.
3. preview_transform
Generates an immutable transformation plan and unified diffs across the project.
- Zero file writes: Source files on disk remain untouched.
- Declarative recipes: Execute built-in recipes safely even when
--trusted-codeis disabled. - Granular paths: Restrict scanning to specific subtrees via
paths(e.g.["packages/ui/**"]). - Content hashing: Computes SHA-256 hashes of original files.
- Applicability check: Blocks plan application if any transformed file produces a syntax or execution error.
- Paginated review: Review diffs page-by-page using the returned
planId.
4. apply_transform
Applies an immutable preview plan to target project files after strict preflight validation.
- Preflight validation: Verifies every target file exists, is not a symlink, and matches its preview SHA-256 hash. If any file has been modified concurrently, preflight aborts with zero writes.
- External backup: Backs up all target files to OS cache before writing.
- Format preservation: Preserves original file permissions (mode), UTF-8 BOM, and newline style (
\r\nvs\n). - Optional post-formatting: Parameterized via
formatter: "auto" | "prettier" | "biome" | "none". If local formatter is not found or fails, apply completes gracefully without throwing errors. - Best-effort rollback: In case of a mid-write I/O error, attempts rollback from backup while verifying
currentHash === expectedChangedHashto guarantee subsequent user edits are never overwritten.
5. rollback
Rolls back an applied transformation plan using the external backup created before modification.
- Drift protection: Compares current file hashes against the applied output before restoring. If a file was concurrently modified by the user after apply, rollback skips that file to preserve edits.
- Audit tracking: Automatically logs rollback operations in project history.
- CLI support: Can also be executed directly via
ilmik rollback <planId>.
6. history
Inspects past transformation operations, diff line metrics, and cumulative file modification statistics for the target project.
- External storage: Records are stored strictly in the external cache directory (
~/.cache/ilmik/projects/<projectId>/), never polluting the target workspace. - Cumulative tracking: Aggregates total operations, applied transformations, lines added/deleted, and per-file change counters.
- JSON Export: Supports exporting complete audit reports to a specified file via
exportJsonPath.
Step-by-Step Walkthrough with Agent Prompts
Below is an end-to-end example showing how a developer interacts with an AI agent (Claude, Antigravity, Cursor, etc.) to safely refactor code using ilmik across all 5 stages.
Scenario: Migrating Component Imports & JSX Props
A React/TypeScript project is deprecating @legacy/ui in favor of @modern/design-system.
The goal:
- Migrate
Buttonimports from@legacy/uito@modern/design-system. - Leave other imported specifiers (such as
Card) in@legacy/ui. - Preserve aliases (such as
import { Button as PrimaryButton } from '@legacy/ui'). - Merge into existing
@modern/design-systemimport declarations if one is already present. - Rename the JSX prop
varianttotone(for all variant values, e.g.variant="primary"→tone="primary"), strictly on JSX elements bound to the importedButtonbinding. - Guard against regressions:
- Do NOT touch unrelated components (such as
<Card variant="flat" />). - Do NOT modify locally declared or shadowed
Buttoncomponents. - Do NOT rename
variantiftonealready exists or if spread attributes ({...props}) are present. - Note: Namespace imports (
import * as UI from '@legacy/ui') are explicitly not supported and left untouched.
- Do NOT touch unrelated components (such as
The verified transform script and all fixtures are maintained as a single source of truth in skills/code-transform/examples/migrate-button/transform.js.
Stage 1: Discovery & Analysis (inspect)
User Prompt:
"Find all files in our project that import
Buttonfrom@legacy/ui."
Agent Action:
The agent calls inspect using a declarative query (safe, fast, read-only):
{
"query": {
"imports": {
"source": "@legacy/ui",
"importedName": "Button"
}
},
"files": ["src/**/*.{ts,tsx}"],
"limit": 50
}ilmik Response:
{
"scannedFiles": 42,
"matchedFiles": 2,
"skippedFiles": 0,
"unparseableFiles": 0,
"totalMatches": 2,
"matches": [
{
"file": "src/components/Header.tsx",
"line": 1,
"column": 9,
"nodeType": "ImportDeclaration",
"matchText": "import { Button } from '@legacy/ui';",
"details": {
"source": "@legacy/ui",
"importedName": "Button",
"localName": "Button"
}
},
{
"file": "src/pages/Dashboard.tsx",
"line": 2,
"column": 9,
"nodeType": "ImportDeclaration",
"matchText": "import { Button } from '@legacy/ui';",
"details": {
"source": "@legacy/ui",
"importedName": "Button",
"localName": "Button"
}
}
],
"skipped": [],
"unparseable": [],
"hasMore": false,
"snapshotId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Stage 2: Transform Authoring & Fixture Testing (test_transform)
User Prompt:
"Write a codemod that moves
Buttonto@modern/design-systemand renames itsvariantprop totone. Before touching any files, test it on fixtures including unaffected cases."
Agent Action:
The agent provides the transform (see migrate-button/transform.js) and tests it against in-memory fixtures using test_transform:
{
"parser": "tsx",
"transformCode": "export default function(fileInfo, api) {\n const j = api.jscodeshift;\n const root = j(fileInfo.source);\n let migratedBinding = null;\n root.find(j.ImportDeclaration, { source: { value: '@legacy/ui' } }).forEach(p => {\n const specs = p.node.specifiers || [];\n const idx = specs.findIndex(s => s.type === 'ImportSpecifier' && s.imported?.name === 'Button');\n if (idx !== -1) {\n const [spec] = specs.splice(idx, 1);\n migratedBinding = spec.local?.name || spec.imported.name;\n p.insertAfter(j.importDeclaration([spec], j.literal('@modern/design-system')));\n if (specs.length === 0) p.prune();\n }\n });\n if (!migratedBinding) return undefined;\n root.find(j.JSXOpeningElement).forEach(p => {\n if (p.node.name.name !== migratedBinding) return;\n const scope = p.scope.lookup(migratedBinding);\n if (!scope || scope.node.type !== 'Program') return;\n const attrs = p.node.attributes || [];\n if (attrs.some(a => a.type === 'JSXSpreadAttribute' || a.name?.name === 'tone')) return;\n attrs.forEach(a => { if (a.name?.name === 'variant') a.name.name = 'tone'; });\n });\n return root.toSource();\n}",
"fixtures": [
{
"name": "migrates button import and prop",
"input": "import { Button } from '@legacy/ui';\nexport const Header = () => <Button variant=\"primary\">Save</Button>;",
"expectedOutput": "import { Button } from \"@modern/design-system\";\nexport const Header = () => <Button tone=\"primary\">Save</Button>;"
},
{
"name": "leaves unrelated Card untouched",
"input": "import { Card } from '@legacy/ui';\nexport const App = () => <Card variant=\"flat\" />;",
"unchanged": true
}
],
"checkIdempotency": true
}ilmik Response:
{
"allPassed": true,
"passedCount": 2,
"failedCount": 0,
"results": [
{
"name": "migrates button import and prop",
"passed": true,
"syntaxValid": true,
"idempotent": true
},
{
"name": "leaves unrelated Card untouched",
"passed": true,
"syntaxValid": true,
"idempotent": true,
"isUnchanged": true
}
]
}Stage 3: Workspace Preview & Diff Review (preview_transform)
User Prompt:
"Now run this transform on
src/as a preview and show me the unified diffs before modifying anything."
Agent Action:
The agent calls preview_transform. Files on disk remain untouched. An immutable plan is stored outside the workspace in OS cache (~/.cache/ilmik/plans/<planId>.json):
{
"transformCode": "... verified transform code ...",
"parser": "tsx",
"files": ["src/**/*.{ts,tsx}"]
}ilmik Response:
{
"planId": "3b8a1c9e-5f2d-4e7a-9011-8a6e7b8c9d01",
"root": "/absolute/path/to/project",
"fingerprint": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"createdAt": "2026-10-23T08:00:00.000Z",
"expiresAt": "2026-10-23T09:00:00.000Z",
"isApplicable": true,
"applicableBlockers": [],
"totalFilesScanned": 42,
"changedCount": 2,
"unchangedCount": 40,
"skippedCount": 0,
"errorCount": 0,
"changedFiles": [
{
"file": "src/components/Header.tsx",
"diff": "--- src/components/Header.tsx\n+++ src/components/Header.tsx\n@@ -1,2 +1,2 @@\n-import { Button } from '@legacy/ui';\n+import { Button } from \"@modern/design-system\";\n-export const Header = () => <Button variant=\"primary\">Save</Button>;\n+export const Header = () => <Button tone=\"primary\">Save</Button>;",
"originalHash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"transformedHash": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
}
],
"skippedFiles": [],
"errorFiles": [],
"hasMore": false
}Stage 4: Preflight Verification & Controlled Write (apply_transform)
User Prompt:
"The diff looks great. Apply the plan to the codebase."
Agent Action:
The agent invokes apply_transform using the verified planId:
{
"planId": "3b8a1c9e-5f2d-4e7a-9011-8a6e7b8c9d01"
}ilmik Response:
{
"success": true,
"planId": "3b8a1c9e-5f2d-4e7a-9011-8a6e7b8c9d01",
"status": "applied",
"appliedCount": 2,
"appliedFiles": [
"src/components/Header.tsx",
"src/pages/Dashboard.tsx"
],
"failedFiles": [],
"rollbackStatus": "not_needed",
"message": "Successfully applied transformation plan 3b8a1c9e-5f2d-4e7a-9011-8a6e7b8c9d01 to 2 file(s)."
}Guarantees Applied During Execution:
- Preflight Check: Prior to modifying any file, SHA-256 hashes of all target files were compared to the preview state. If any file had drifted,
apply_transformwould have aborted with zero modifications. - External Backup: Backed up original copies in OS cache (
~/.cache/ilmik/backups/<planId>) before modifications. - Format Preservation: File permissions (
mode), UTF-8 BOM, and line endings (\r\nvs\n) were preserved. - Best-Effort Rollback Protection: If an error occurs during writing, ilmik attempts to restore written files from backup, ensuring that any file concurrently modified by the user after writing is not clobbered.
Stage 5: Auditing & Transformation History (history)
User Prompt:
"What transformations have we executed on this project so far and how many lines changed?"
Agent Action:
The agent queries project history and cumulative diff statistics using history:
{
"limit": 5,
"format": "json"
}ilmik Response:
{
"project": {
"id": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"name": "my-app",
"root": "/absolute/path/to/project"
},
"stats": {
"projectId": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"projectName": "my-app",
"root": "/absolute/path/to/project",
"firstOperationAt": "2026-10-23T08:15:00.000Z",
"lastOperationAt": "2026-10-23T08:20:12.000Z",
"totalOperations": 2,
"appliedTransforms": 1,
"previewedTransforms": 1,
"totalFilesModified": 2,
"totalLinesAdded": 2,
"totalLinesDeleted": 2,
"files": {
"src/components/Header.tsx": {
"modifiedCount": 1,
"linesAdded": 1,
"linesDeleted": 1,
"lastModifiedAt": "2026-10-23T08:20:12.000Z"
}
}
},
"recentOperations": [
{
"id": "op_1729671612000_apply",
"timestamp": "2026-10-23T08:20:12.000Z",
"type": "apply",
"planId": "3b8a1c9e-5f2d-4e7a-9011-8a6e7b8c9d01",
"status": "applied",
"filesChanged": [
"src/components/Header.tsx",
"src/pages/Dashboard.tsx"
],
"stats": {
"filesModified": 2,
"linesAdded": 2,
"linesDeleted": 2
}
}
],
"historyFile": "/Users/ahmet/.cache/ilmik/projects/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/history.json",
"statsFile": "/Users/ahmet/.cache/ilmik/projects/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/stats.json"
}Stage 6: Reverting Changes Safely (rollback)
User Prompt:
"Wait, we noticed an issue with the new design system. Revert the last transformation plan."
Agent Action:
The agent invokes rollback using the planId:
{
"planId": "3b8a1c9e-5f2d-4e7a-9011-8a6e7b8c9d01"
}ilmik Response:
{
"success": true,
"planId": "3b8a1c9e-5f2d-4e7a-9011-8a6e7b8c9d01",
"status": "rolled_back",
"revertedCount": 2,
"revertedFiles": [
"src/components/Header.tsx",
"src/pages/Dashboard.tsx"
],
"skippedFiles": [],
"failedFiles": [],
"message": "Successfully rolled back plan 3b8a1c9e-5f2d-4e7a-9011-8a6e7b8c9d01 for 2 file(s)."
}[!NOTE] If a developer manually edited any file after the transform was applied,
rollbackautomatically detects the hash difference and skips reverting that file to avoid clobbering concurrent user edits.
Built-in Declarative Recipes (Safe Mode, No --trusted-code Needed)
For standard codebase migrations, ilmik includes built-in declarative recipes. When using recipes in test_transform or preview_transform, AI agents can execute transformations safely without requiring --trusted-code:
| Recipe | Description | Parameters |
| :--- | :--- | :--- |
| rename_import | Renames an imported specifier or moves it to a new package | source, specifier, newSource?, newSpecifier? |
| rename_jsx_prop | Renames a JSX prop on a specific component (spread-guarded) | element, from, to, skipSpread? |
| rename_function_call | Renames function invocations across the project | from, to |
| rename_identifier | Renames variables, functions, and bindings cleanly | from, to |
Example recipe invocation via preview_transform with granular paths:
{
"recipe": "rename_jsx_prop",
"recipeParams": {
"element": "Button",
"from": "variant",
"to": "tone"
},
"paths": ["src/components/**"]
}Skill Package (skills/code-transform/)
ilmik includes a comprehensive agent skill package with guidelines, recipes, and verified examples:
skills/code-transform/SKILL.md: Main workflow instructions.skills/code-transform/references/: Tool contracts, analysis guides, and limitations.skills/code-transform/recipes/: Recipes for imports, function calls, JSX props, and exports.skills/code-transform/examples/: Tested examples:move-import: Moving imports between packages.rename-jsx-prop: Renaming JSX element attributes.migrate-button: Full component migration with scope checks and collision guards.
Install Skill Globally or to Project
# Install globally to ~/.gemini/config/skills/code-transform (default)
npx -y ilmik install-skill
# Install into current project (.agents/skills/code-transform)
npx -y ilmik install-skill --project
# Install to a custom directory path
npx -y ilmik install-skill --to /path/to/custom/skills
# Create a symlink instead of copying (ideal for local development)
npx -y ilmik install-skill --symlink --forceDevelopment & Testing
# Install dependencies
npm install
# Build the project (tsup + declaration generation)
npm run build
# Run type check
npm run typecheck
# Run full test suite (Vitest)
npm testLicense
MIT © ilmik contributors
