| name | vibemap |
| description | Use when the user wants to visualize, diagram, or explore the architecture of a repository. Triggers on requests like "show me the architecture", "diagram this repo", "visualize the codebase", or direct invocation via /vibemap. |
vibemap: Interactive Architecture Diagram
Generate an interactive architecture diagram of the current repository and open it in the browser.
All shell commands below use node -e so they work in any shell (bash, PowerShell, cmd).
Before starting — check for existing session
Read the file .vibemap/state.json in the project root. If it exists and contains tmpDir, port, and repoRoot:
- Verify the repo matches: compare
repoRoot in the state file to the current working directory. If they don't match, delete the state file and continue with "Fresh setup" below.
- Check if the temp dir and data still exist:
node -e "const fs=require('fs');console.log(fs.existsSync('<tmpDir>/data.json')&&fs.existsSync('<tmpDir>/index.html')?'DATA_EXISTS':'NO_DATA')"
- If NO_DATA: delete the state file and continue with "Fresh setup" below.
- Check if the server is still running:
node -e "const r=require('http').get('http://127.0.0.1:<port>/',()=>{console.log('RUNNING');r.destroy()}).on('error',()=>console.log('STOPPED'))"
- If RUNNING: skip to "Check for changes" below.
- If STOPPED but data exists: restart the server, then check for changes:
node -e "const{spawn:s}=require('child_process'),fs=require('fs'),f='<tmpDir>/.port';try{fs.unlinkSync(f)}catch{};const p=s(process.execPath,['<tmpDir>/server.mjs','<tmpDir>','<repo_root>','<port>'],{detached:true,stdio:'ignore'});p.unref();(function w(){fs.existsSync(f)?console.log(fs.readFileSync(f,'utf-8').trim()):setTimeout(w,200)})()"
Check for changes
Compare the data.json timestamp against the latest source file change:
node -e "
const fs=require('fs'),path=require('path');
const skip=new Set(['node_modules','.git','dist','build','__pycache__','.vibemap','.claude','.worktrees']);
const dm=fs.statSync('<tmpDir>/data.json').mtimeMs;
let newest=0;
(function walk(d){try{for(const e of fs.readdirSync(d,{withFileTypes:true})){
if(skip.has(e.name))continue;const p=path.join(d,e.name);
if(e.isDirectory())walk(p);else{const t=fs.statSync(p).mtimeMs;if(t>newest)newest=t}
}}catch{}})('<repo_root>');
console.log(newest>dm?'STALE':'FRESH');
"
- If FRESH: just open browser and tell the user Diagram is open in your browser. Stop here.
- If STALE: continue to "Update existing diagram" below.
Update existing diagram
- Run Step 1 and Step 2 below to gather structure and produce new JSON.
- Use the Write tool to overwrite
<tmpDir>/data.json with the new JSON.
- Open browser (refresh will pick up the new data):
node -e "require('child_process').exec(process.platform==='win32'?'start http://127.0.0.1:<port>':process.platform==='darwin'?'open http://127.0.0.1:<port>':'xdg-open http://127.0.0.1:<port>')"
- Tell the user: Diagram updated and opened in your browser.
- Stop here — do not continue to Fresh setup.
Fresh setup
Step 1 — Gather repo structure
Explore the project thoroughly — do not stop at top-level directories:
- Use Glob with
**/* to get the full file tree (ignore node_modules/, .git/, dist/, build/, __pycache__/, lock files)
- Read key config files (
package.json, tsconfig.json, README.md, etc.)
- For component-based projects (React, Vue, Angular, etc.): list ALL subdirectories inside the components folder, including nested children. Every leaf component directory should be a candidate node. Use Glob
src/components/** to ensure you see the full tree.
- Read the entry point and router files to understand the component hierarchy
Step 2 — Produce graph JSON
Analyze the architecture and produce a JSON object (DO NOT output it to the user):
{
"title": "repo-name",
"groups": [{ "id": "g1", "label": "Group Label" }],
"nodes": [{ "id": "n1", "label": "Name", "shape": "box", "path": "relative/path", "group": "g1", "description": "What it does" }],
"edges": [{ "from": "n1", "to": "n2", "label": "relationship" }]
}
Rules:
- Max 10 groups, 30 nodes, 40 edges
id must match [a-zA-Z][a-zA-Z0-9_]*
shape: one of box, database, queue, document, circle, hexagon
path: must be a real file/directory — verify with Glob
- Every node should have a
group
- No double quotes in labels
description: 1-2 sentence summary
renderable (optional boolean): Set true to force-show the Render tab for this node, or false to hide it. When omitted, renderability is auto-detected: nodes with a route, or with a frontend file extension (.tsx, .jsx, .vue, .svelte, .html, .astro, .mdx) are renderable. Nodes with database or queue shape are not. Backend services, configs, and non-visual items should NOT be marked renderable.
- Include child components as nodes — if a parent component contains subcomponents (e.g.
BarSide/PlayerCard, BarSide/TeamEconomy), each child gets its own node with an edge from the parent. Do NOT flatten a parent with children into a single node.
- Use groups to organize by feature area (e.g. "Sidebar", "Top Bar", "Overlays")
- Prefer more nodes over fewer — use the full budget of 30 nodes
- Dev server detection: Check if the project has a dev server configured:
- Read
vite.config.js or webpack.config.js for port settings
- Check
package.json scripts for dev or start commands with port numbers
- If a dev server port is found, add
"devServerUrl": "http://localhost:<port>" to the top-level JSON object
- If no dev server is detected, omit
devServerUrl
- Route detection: For nodes that correspond to routes/pages, add
"route": "/path" to the node. Check the router config (React Router, Vue Router, etc.) to find route paths. For example, if HudMain maps to /hudmain, set "route": "/hudmain" on that node. This enables the Render tab to show the correct page when clicking that node or its children.
Step 3 — Build and launch
- Create a temp directory and copy files:
node -e "const fs=require('fs'),os=require('os'),p=require('path');const d=fs.mkdtempSync(p.join(os.tmpdir(),'vibemap-'));[['template.html','index.html'],['server.mjs','server.mjs'],['bridge.js','bridge.js']].forEach(([s,t])=>fs.copyFileSync(p.join('__VIBEMAP_SKILL_ROOT__',s),p.join(d,t)));console.log(d)"
Use the printed path as <temp_dir> for all subsequent steps.
- Use the Write tool to write your JSON object to
<temp_dir>/data.json — the template loads it automatically via fetch, no replacement needed. Use the exact path printed above (e.g. C:\Users\...\vibemap-xxxxx\data.json on Windows).
- Start the server (port 0 = auto-pick). This spawns it as a detached background process, polls for the
.port file, and prints the port:
node -e "const{spawn:s}=require('child_process'),fs=require('fs'),f='<temp_dir>/.port';const p=s(process.execPath,['<temp_dir>/server.mjs','<temp_dir>','<repo_root>','0'],{detached:true,stdio:'ignore'});p.unref();(function w(){fs.existsSync(f)?console.log(fs.readFileSync(f,'utf-8').trim()):setTimeout(w,200)})()"
Use the printed number as <port>.
- Save state — use the Write tool to write
.vibemap/state.json:
{ "tmpDir": "<temp_dir>", "port": <port>, "repoRoot": "<repo_root>" }
- Open browser:
node -e "require('child_process').exec(process.platform==='win32'?'start http://127.0.0.1:<port>':process.platform==='darwin'?'open http://127.0.0.1:<port>':'xdg-open http://127.0.0.1:<port>')"
- Tell the user: Diagram is open in your browser. Select components by clicking them, then click "Send to Agent".
Phase 2: Receive Selections
When the user says "read my selections", "go", etc.:
node -e "const http=require('http');http.get('http://127.0.0.1:<port>/selections',r=>{let d='';r.on('data',c=>d+=c);r.on('end',()=>console.log(d))})"
Parse the JSON response. Extract the paths array. For each path, use the Read tool to load the file contents. Present organized context.
If subSelections exists in the response, present each sub-selection as targeted context:
- Code type: "The user selected lines N-M of
path/to/file:" followed by the selected text
- Element type: "The user pointed at
<tag> element matching selector with text: text"
Ask what the user would like to do with this context.