Nano Banana Pro
Agent skill for nano-banana-pro
**Project.md** is a VSCode extension that transforms Markdown files into interactive project navigation hubs. It provides intelligent file path detection, clickable navigation, and automatic file creation - specifically designed to enhance workflow for code assistants like Claude Code, Gemini CLI, a
Sign in to like and favorite skills
Project.md is a VSCode extension that transforms Markdown files into interactive project navigation hubs. It provides intelligent file path detection, clickable navigation, and automatic file creation - specifically designed to enhance workflow for code assistants like Claude Code, Gemini CLI, and Codex CLI.
When working with code assistants, developers often reference project files in Markdown documentation. This extension makes those references actionable:
project-md/ ├── src/ │ ├── extension.ts # Main extension logic │ └── test/ │ └── extension.test.ts # Test suite (placeholder) ├── dist/ # Compiled output (esbuild) ├── package.json # Extension manifest └── tsconfig.json # TypeScript configuration
"activationEvents": ["onLanguage:markdown"]
a) Document Link Provider
b) Definition Provider
vscode.Location pointing to target filec) Command Handler
markdownLinks.openThree regex patterns capture different reference styles:
const MD_LINK_RE = /\[[^\]]+\]\(\s*(@?(?:\.{1,2}\/|\/)[^)\s]+)\s*\)/g;
Matches:
[text](./path/file.md)[config](@./config/settings.ts)[root](/absolute/path.ts)Captures: Path inside parentheses, including optional
@ prefix
const BARE_REF_RE = /(^|[\s(`])(@?(?:\.{1,2}\/|\/)[^\s`)\]]+)/g;
Matches:
./src/index.ts (standalone)Check ./config/app.ts for detailsCaptures: Leading whitespace/delimiter + path
Conflict Resolution: Only adds if not already captured by MD_LINK_RE
const INLINE_CODE_RE = /`(@?(?:\.{1,2}\/|\/)[^\s`)\]]+)`/g;
Matches:
`./src/utils.ts``@./config/database.ts`Captures: Path inside backticks
Conflict Resolution: Checks for range intersection before adding
const abs = path.resolve(baseDir, rel.replace(/^@/, ""));
Steps:
path.dirname(doc.uri.fsPath))@ prefix if present@ Prefix Behavior:
@./ for project root contexts)./Implementation:
const linkProvider: vscode.DocumentLinkProvider = { provideDocumentLinks(doc) { return getRefRanges(doc).map(({ range, targetFsPath }) => { const cmdUri = vscode.Uri.parse( `command:${commandId}?${encodeURIComponent(JSON.stringify({ path: targetFsPath }))}` ); const link = new vscode.DocumentLink(range, cmdUri); link.tooltip = "Open path"; return link; }); } };
Flow:
markdownLinks.open) executesopenPathLike() processes the pathImplementation:
async function openPathLike(targetFsPath: string) { const stat = await fs.promises.stat(targetFsPath).catch(() => undefined); if (!stat) { await fs.promises.mkdir(path.dirname(targetFsPath), { recursive: true }); await fs.promises.writeFile(targetFsPath, "", "utf8"); } const td = await vscode.workspace.openTextDocument(vscode.Uri.file(targetFsPath)); await vscode.window.showTextDocument(td); }
Behavior:
Safety:
recursive: true for mkdir (safe if directories exist)Implementation:
const defProvider: vscode.DefinitionProvider = { provideDefinition(doc, pos) { const hit = getRefRanges(doc).find(h => h.range.contains(pos)); if (!hit) return; const stat = fs.existsSync(hit.targetFsPath) ? fs.statSync(hit.targetFsPath) : undefined; if (stat?.isFile()) { return new vscode.Location(vscode.Uri.file(hit.targetFsPath), new vscode.Position(0, 0)); } } };
Behavior:
Design Decision:
openPathLike to avoid auto-creation on F12Implementation:
if (stat?.isDirectory()) { try { await vscode.commands.executeCommand("revealInExplorer", vscode.Uri.file(targetFsPath)); } catch { await vscode.commands.executeCommand("revealFileInOS", vscode.Uri.file(targetFsPath)); } return; }
Fallback Strategy:
revealInExplorer (VSCode Explorer sidebar)revealFileInOS (system file manager)Type Safety:
vscode types from @types/vscodeError Handling:
.catch(() => undefined) for graceful degradationNaming Conventions:
getRefRanges: Pure function, no side effectsopenPathLike: Async action with side effectsHit: Type represents detected path + rangeBuild Commands:
npm run compile # Type check + lint + build npm run watch # Watch mode (esbuild + tsc) npm run package # Production build
Development Flow:
npm run watch in terminalUnit Tests (Recommended):
// Test regex patterns describe('getRefRanges', () => { it('should detect markdown links', () => { const doc = createMockDocument('[test](./file.md)'); const ranges = getRefRanges(doc); expect(ranges).toHaveLength(1); expect(ranges[0].targetFsPath).toContain('file.md'); }); it('should detect bare references', () => { /* ... */ }); it('should detect inline code paths', () => { /* ... */ }); it('should handle @ prefix', () => { /* ... */ }); it('should prevent duplicate ranges', () => { /* ... */ }); });
Integration Tests (Recommended):
// Test providers describe('DocumentLinkProvider', () => { it('should provide clickable links', async () => { /* ... */ }); }); describe('DefinitionProvider', () => { it('should navigate to existing files', async () => { /* ... */ }); it('should return undefined for non-existent files', async () => { /* ... */ }); });
Manual Testing Checklist:
@ prefixGoal: Visual differentiation for file paths in Markdown Implementation approach: - TextMate grammar in package.json contributes - Scope: source.markdown meta.path.projectmd - Color themes can customize highlighting - Distinguish existing vs non-existent files
Goal: Warn about broken references Implementation approach: - Diagnostic provider for Markdown files - Check file existence on document change - Warning severity for non-existent paths - Quick fix: "Create file" code action
Claude Code Integration:
- Detect claude.md references - Auto-link to project context files - Validate @-references against project structure
Gemini CLI Integration:
- Support for .geminirc path conventions - Validate tool configurations
Codex CLI Integration:
- Support for .codex paths - Validate context file references
{ "project-md.autoCreate": true, "project-md.atPrefixAlias": "./", "project-md.highlightPaths": true, "project-md.validatePaths": "warning" }
Goal: Multi-root workspace handling Implementation approach: - Resolve paths relative to workspace root - Support workspace-relative paths (e.g., ${workspaceFolder}/...) - Handle monorepo structures
Decision: Use 3 patterns instead of one complex regex
Rationale:
Trade-off: Potential overlapping matches (mitigated by intersection checks)
Decision: Automatically create referenced files when clicked
Rationale:
Trade-off: Accidental file creation (mitigated by requiring explicit click)
Decision: Definition provider only navigates to existing files
Rationale:
Trade-off: Different behavior for same path (click vs F12)
Decision: Strip
@ but don't resolve to special directory
Rationale:
@ for future aliasing featureTrade-off:
@./ behaves identically to ./ (no benefit yet)
{ "name": "project-md", "displayName": "Project.md", "version": "0.0.1", "engines": { "vscode": "^1.104.0" }, "activationEvents": ["onLanguage:markdown"], "main": "./dist/extension.js", "contributes": { "commands": [ { "command": "project-md.helloWorld", "title": "Hello World" } ] } }
Note:
project-md.helloWorld is template command - not used in v0.0.1
Runtime: None (uses only VSCode API + Node.js built-ins)
Development:
esbuild: Fast bundling for productiontypescript: Type checkingeslint: Code linting@types/vscode: VSCode API typesDocument Scanning:
provideDocumentLinks()Optimization Opportunities:
Current Impact: Negligible for typical Markdown files (<10k lines)
Per Document:
Hit objects (range + path)Extension Footprint: <500KB (bundled)
Vulnerability: Path traversal attacks
Mitigation:
const abs = path.resolve(baseDir, rel.replace(/^@/, ""));
path.resolve() normalizes paths (prevents ../../../etc/passwd)fs directly)Vulnerability: Unintended file creation
Mitigation:
utf8 encoding (prevents binary corruption)Remaining Risk: User could click malicious path in untrusted Markdown
npm run lint passesnpm run compile succeedsany types without justification// Add to extension.ts const outputChannel = vscode.window.createOutputChannel("Project.md"); outputChannel.appendLine(`Detected ${hits.length} paths`);
Cmd+Shift+P)Use online regex tester with JavaScript flavor:
Document Link Provider:
vscode.languages.registerDocumentLinkProvider()vscode.DocumentLink(range, target)command: URI scheme for custom commandsDefinition Provider:
vscode.languages.registerDefinitionProvider()vscode.Location(uri, position)Commands:
vscode.commands.registerCommand()vscode.commands.executeCommand()File System:
vscode.workspace.openTextDocument()vscode.window.showTextDocument()Node.js APIs:
fs.promises.stat() / fs.existsSync() / fs.statSync()fs.promises.mkdir() / fs.promises.writeFile()path.resolve() / path.dirname()Q: Why aren't my paths being detected? A: Check if path starts with
./, ../, or /. Paths without these prefixes are not detected.
Q: Can I use workspace-relative paths? A: Not yet in v0.0.1. This is planned for future release.
Q: Why does F12 create files but Definition provider doesn't? A: They use different code paths. Click uses
openPathLike() (auto-creates), F12 uses definition provider (existing files only).
Q: How do I configure the extension? A: No configuration options in v0.0.1. Coming in future releases.
For technical questions or contributions:
Last Updated: 2025-10-01 Extension Version: 0.0.1 VSCode Engine: ^1.104.0