44 KiB
Language-Agnostic Support Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Make Understand-Anything language-agnostic by introducing a config-driven language framework, replacing the TS-only tree-sitter plugin, and creating language-aware prompts for 12 languages.
Architecture: Config-first hybrid approach — each language defined by a LanguageConfig object (tree-sitter node mappings, concepts, extensions) plus a prompt snippet markdown file. A single GenericTreeSitterPlugin replaces the hardcoded TS-only plugin, driven by whichever config matches the file extension.
Tech Stack: TypeScript, web-tree-sitter (WASM), Zod v4, Vitest
Task 1: Create LanguageConfig types and Zod schema
Files:
- Create:
understand-anything-plugin/packages/core/src/languages/types.ts
Step 1: Write the failing test
Create: understand-anything-plugin/packages/core/src/languages/__tests__/types.test.ts
import { describe, it, expect } from "vitest";
import { LanguageConfigSchema } from "../types.js";
describe("LanguageConfigSchema", () => {
it("validates a complete language config", () => {
const config = {
id: "python",
displayName: "Python",
extensions: [".py", ".pyi"],
treeSitter: {
grammarPackage: "tree-sitter-python",
wasmFile: "tree-sitter-python.wasm",
nodeTypes: {
function: ["function_definition"],
class: ["class_definition"],
import: ["import_statement", "import_from_statement"],
export: [],
typeAnnotation: ["type"],
},
},
concepts: ["decorators", "list comprehensions", "generators"],
};
const result = LanguageConfigSchema.safeParse(config);
expect(result.success).toBe(true);
});
it("rejects config missing required fields", () => {
const result = LanguageConfigSchema.safeParse({ id: "python" });
expect(result.success).toBe(false);
});
it("accepts optional filePatterns", () => {
const config = {
id: "python",
displayName: "Python",
extensions: [".py"],
treeSitter: {
grammarPackage: "tree-sitter-python",
wasmFile: "tree-sitter-python.wasm",
nodeTypes: {
function: ["function_definition"],
class: ["class_definition"],
import: ["import_statement"],
export: [],
typeAnnotation: [],
},
},
concepts: ["decorators"],
filePatterns: { config: "pyproject.toml" },
};
const result = LanguageConfigSchema.safeParse(config);
expect(result.success).toBe(true);
});
});
Step 2: Run test to verify it fails
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/languages/__tests__/types.test.ts
Expected: FAIL — module ../types.js not found
Step 3: Write minimal implementation
Create: understand-anything-plugin/packages/core/src/languages/types.ts
import { z } from "zod/v4";
export const TreeSitterConfigSchema = z.object({
grammarPackage: z.string(),
wasmFile: z.string(),
nodeTypes: z.object({
function: z.array(z.string()),
class: z.array(z.string()),
import: z.array(z.string()),
export: z.array(z.string()),
typeAnnotation: z.array(z.string()),
}),
});
export const LanguageConfigSchema = z.object({
id: z.string(),
displayName: z.string(),
extensions: z.array(z.string()),
treeSitter: TreeSitterConfigSchema,
concepts: z.array(z.string()),
filePatterns: z.record(z.string(), z.string()).optional(),
});
export type LanguageConfig = z.infer<typeof LanguageConfigSchema>;
export type TreeSitterConfig = z.infer<typeof TreeSitterConfigSchema>;
Step 4: Run test to verify it passes
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/languages/__tests__/types.test.ts
Expected: PASS
Step 5: Commit
git add understand-anything-plugin/packages/core/src/languages/
git commit -m "feat: add LanguageConfig types and Zod schema"
Task 2: Create LanguageRegistry
Files:
- Create:
understand-anything-plugin/packages/core/src/languages/registry.ts
Step 1: Write the failing test
Create: understand-anything-plugin/packages/core/src/languages/__tests__/registry.test.ts
import { describe, it, expect } from "vitest";
import { LanguageRegistry } from "../registry.js";
import type { LanguageConfig } from "../types.js";
const pythonConfig: LanguageConfig = {
id: "python",
displayName: "Python",
extensions: [".py", ".pyi"],
treeSitter: {
grammarPackage: "tree-sitter-python",
wasmFile: "tree-sitter-python.wasm",
nodeTypes: {
function: ["function_definition"],
class: ["class_definition"],
import: ["import_statement", "import_from_statement"],
export: [],
typeAnnotation: ["type"],
},
},
concepts: ["decorators", "generators"],
};
const tsConfig: LanguageConfig = {
id: "typescript",
displayName: "TypeScript",
extensions: [".ts", ".tsx"],
treeSitter: {
grammarPackage: "tree-sitter-typescript",
wasmFile: "tree-sitter-typescript.wasm",
nodeTypes: {
function: ["function_declaration"],
class: ["class_declaration"],
import: ["import_statement"],
export: ["export_statement"],
typeAnnotation: ["type_annotation"],
},
},
concepts: ["generics", "type guards", "decorators"],
};
describe("LanguageRegistry", () => {
it("registers and retrieves a config by id", () => {
const registry = new LanguageRegistry();
registry.register(pythonConfig);
expect(registry.getById("python")).toBe(pythonConfig);
});
it("retrieves config by file extension", () => {
const registry = new LanguageRegistry();
registry.register(pythonConfig);
expect(registry.getByExtension(".py")).toBe(pythonConfig);
expect(registry.getByExtension(".pyi")).toBe(pythonConfig);
});
it("returns null for unknown extension", () => {
const registry = new LanguageRegistry();
registry.register(pythonConfig);
expect(registry.getByExtension(".rs")).toBeNull();
});
it("returns all registered configs", () => {
const registry = new LanguageRegistry();
registry.register(pythonConfig);
registry.register(tsConfig);
expect(registry.getAll()).toHaveLength(2);
});
it("later registration overrides same id", () => {
const registry = new LanguageRegistry();
const updated = { ...pythonConfig, displayName: "Python 3" };
registry.register(pythonConfig);
registry.register(updated);
expect(registry.getById("python")?.displayName).toBe("Python 3");
});
it("throws on invalid config", () => {
const registry = new LanguageRegistry();
expect(() => registry.register({ id: "bad" } as LanguageConfig)).toThrow();
});
});
Step 2: Run test to verify it fails
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/languages/__tests__/registry.test.ts
Expected: FAIL — module ../registry.js not found
Step 3: Write minimal implementation
// understand-anything-plugin/packages/core/src/languages/registry.ts
import { LanguageConfigSchema } from "./types.js";
import type { LanguageConfig } from "./types.js";
export class LanguageRegistry {
private configs = new Map<string, LanguageConfig>();
private extensionMap = new Map<string, string>();
register(config: LanguageConfig): void {
const result = LanguageConfigSchema.safeParse(config);
if (!result.success) {
throw new Error(`Invalid LanguageConfig for "${config.id}": ${result.error.message}`);
}
this.configs.set(config.id, config);
for (const ext of config.extensions) {
this.extensionMap.set(ext, config.id);
}
}
getById(id: string): LanguageConfig | null {
return this.configs.get(id) ?? null;
}
getByExtension(ext: string): LanguageConfig | null {
const id = this.extensionMap.get(ext);
if (!id) return null;
return this.configs.get(id) ?? null;
}
getAll(): LanguageConfig[] {
return [...this.configs.values()];
}
}
Step 4: Run test to verify it passes
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/languages/__tests__/registry.test.ts
Expected: PASS
Step 5: Commit
git add understand-anything-plugin/packages/core/src/languages/
git commit -m "feat: add LanguageRegistry with Zod validation"
Task 3: Create all 12 language configs
Files:
- Create:
understand-anything-plugin/packages/core/src/languages/configs/typescript.ts - Create:
understand-anything-plugin/packages/core/src/languages/configs/javascript.ts - Create:
understand-anything-plugin/packages/core/src/languages/configs/python.ts - Create:
understand-anything-plugin/packages/core/src/languages/configs/go.ts - Create:
understand-anything-plugin/packages/core/src/languages/configs/java.ts - Create:
understand-anything-plugin/packages/core/src/languages/configs/rust.ts - Create:
understand-anything-plugin/packages/core/src/languages/configs/cpp.ts - Create:
understand-anything-plugin/packages/core/src/languages/configs/csharp.ts - Create:
understand-anything-plugin/packages/core/src/languages/configs/ruby.ts - Create:
understand-anything-plugin/packages/core/src/languages/configs/php.ts - Create:
understand-anything-plugin/packages/core/src/languages/configs/swift.ts - Create:
understand-anything-plugin/packages/core/src/languages/configs/kotlin.ts - Create:
understand-anything-plugin/packages/core/src/languages/configs/index.ts
Step 1: Write the failing test
Create: understand-anything-plugin/packages/core/src/languages/__tests__/configs.test.ts
import { describe, it, expect } from "vitest";
import { LanguageConfigSchema } from "../types.js";
import { builtinConfigs } from "../configs/index.js";
describe("builtin language configs", () => {
it("has 12 language configs", () => {
expect(builtinConfigs).toHaveLength(12);
});
it("all configs pass Zod validation", () => {
for (const config of builtinConfigs) {
const result = LanguageConfigSchema.safeParse(config);
expect(result.success, `${config.id} failed validation: ${result.error?.message}`).toBe(true);
}
});
it("all configs have unique ids", () => {
const ids = builtinConfigs.map((c) => c.id);
expect(new Set(ids).size).toBe(ids.length);
});
it("no duplicate extensions across configs", () => {
const allExts: string[] = [];
for (const config of builtinConfigs) {
allExts.push(...config.extensions);
}
expect(new Set(allExts).size).toBe(allExts.length);
});
it("all configs have non-empty function and class node types", () => {
for (const config of builtinConfigs) {
expect(config.treeSitter.nodeTypes.function.length, `${config.id} missing function types`).toBeGreaterThan(0);
expect(config.treeSitter.nodeTypes.class.length, `${config.id} missing class types`).toBeGreaterThanOrEqual(0);
}
});
it("all configs have at least one concept", () => {
for (const config of builtinConfigs) {
expect(config.concepts.length, `${config.id} has no concepts`).toBeGreaterThan(0);
}
});
});
Step 2: Run test to verify it fails
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/languages/__tests__/configs.test.ts
Expected: FAIL — module not found
Step 3: Write all config files
Each config file exports a LanguageConfig. Here are the key ones (the rest follow the same pattern):
typescript.ts:
import type { LanguageConfig } from "../types.js";
export const typescriptConfig: LanguageConfig = {
id: "typescript",
displayName: "TypeScript",
extensions: [".ts", ".tsx"],
treeSitter: {
grammarPackage: "tree-sitter-typescript",
wasmFile: "tree-sitter-typescript.wasm",
nodeTypes: {
function: ["function_declaration"],
class: ["class_declaration"],
import: ["import_statement"],
export: ["export_statement"],
typeAnnotation: ["type_annotation"],
},
},
concepts: [
"generics", "type guards", "discriminated unions", "utility types",
"decorators", "enums", "interfaces", "type inference",
"mapped types", "conditional types", "template literal types",
],
filePatterns: { config: "tsconfig.json", manifest: "package.json" },
};
python.ts:
import type { LanguageConfig } from "../types.js";
export const pythonConfig: LanguageConfig = {
id: "python",
displayName: "Python",
extensions: [".py", ".pyi"],
treeSitter: {
grammarPackage: "tree-sitter-python",
wasmFile: "tree-sitter-python.wasm",
nodeTypes: {
function: ["function_definition"],
class: ["class_definition"],
import: ["import_statement", "import_from_statement"],
export: [],
typeAnnotation: ["type"],
},
},
concepts: [
"decorators", "list comprehensions", "generators", "context managers",
"type hints", "dunder methods", "metaclasses", "dataclasses",
"async/await", "descriptors",
],
filePatterns: { config: "pyproject.toml", manifest: "setup.py" },
};
go.ts:
import type { LanguageConfig } from "../types.js";
export const goConfig: LanguageConfig = {
id: "go",
displayName: "Go",
extensions: [".go"],
treeSitter: {
grammarPackage: "tree-sitter-go",
wasmFile: "tree-sitter-go.wasm",
nodeTypes: {
function: ["function_declaration", "method_declaration"],
class: ["type_declaration"],
import: ["import_declaration"],
export: [],
typeAnnotation: [],
},
},
concepts: [
"goroutines", "channels", "interfaces", "struct embedding",
"error handling patterns", "defer/panic/recover", "slices",
"pointers", "concurrency patterns",
],
filePatterns: { config: "go.mod" },
};
java.ts:
import type { LanguageConfig } from "../types.js";
export const javaConfig: LanguageConfig = {
id: "java",
displayName: "Java",
extensions: [".java"],
treeSitter: {
grammarPackage: "tree-sitter-java",
wasmFile: "tree-sitter-java.wasm",
nodeTypes: {
function: ["method_declaration", "constructor_declaration"],
class: ["class_declaration", "interface_declaration", "enum_declaration"],
import: ["import_declaration"],
export: [],
typeAnnotation: ["type_identifier"],
},
},
concepts: [
"generics", "annotations", "interfaces", "abstract classes",
"streams API", "lambdas", "sealed classes", "records",
"dependency injection", "checked exceptions",
],
filePatterns: { config: "pom.xml", manifest: "build.gradle" },
};
rust.ts:
import type { LanguageConfig } from "../types.js";
export const rustConfig: LanguageConfig = {
id: "rust",
displayName: "Rust",
extensions: [".rs"],
treeSitter: {
grammarPackage: "tree-sitter-rust",
wasmFile: "tree-sitter-rust.wasm",
nodeTypes: {
function: ["function_item"],
class: ["struct_item", "enum_item", "impl_item", "trait_item"],
import: ["use_declaration"],
export: [],
typeAnnotation: ["type_identifier"],
},
},
concepts: [
"ownership", "borrowing", "lifetimes", "traits", "pattern matching",
"enums with data", "error handling (Result/Option)", "macros",
"async/await", "unsafe blocks", "generics", "closures",
],
filePatterns: { config: "Cargo.toml" },
};
cpp.ts:
import type { LanguageConfig } from "../types.js";
export const cppConfig: LanguageConfig = {
id: "cpp",
displayName: "C/C++",
extensions: [".cpp", ".cc", ".cxx", ".c", ".h", ".hpp", ".hxx"],
treeSitter: {
grammarPackage: "tree-sitter-cpp",
wasmFile: "tree-sitter-cpp.wasm",
nodeTypes: {
function: ["function_definition"],
class: ["class_specifier", "struct_specifier"],
import: ["preproc_include"],
export: [],
typeAnnotation: [],
},
},
concepts: [
"templates", "RAII", "smart pointers", "move semantics",
"operator overloading", "virtual functions", "namespaces",
"constexpr", "lambda expressions", "STL containers",
],
filePatterns: { config: "CMakeLists.txt", manifest: "Makefile" },
};
csharp.ts:
import type { LanguageConfig } from "../types.js";
export const csharpConfig: LanguageConfig = {
id: "csharp",
displayName: "C#",
extensions: [".cs"],
treeSitter: {
grammarPackage: "tree-sitter-c-sharp",
wasmFile: "tree-sitter-c_sharp.wasm",
nodeTypes: {
function: ["method_declaration", "constructor_declaration"],
class: ["class_declaration", "interface_declaration", "struct_declaration", "enum_declaration", "record_declaration"],
import: ["using_directive"],
export: [],
typeAnnotation: ["type_identifier"],
},
},
concepts: [
"LINQ", "async/await", "generics", "properties",
"delegates and events", "attributes", "nullable reference types",
"pattern matching", "records", "dependency injection",
],
filePatterns: { config: "*.csproj" },
};
ruby.ts:
import type { LanguageConfig } from "../types.js";
export const rubyConfig: LanguageConfig = {
id: "ruby",
displayName: "Ruby",
extensions: [".rb", ".rake"],
treeSitter: {
grammarPackage: "tree-sitter-ruby",
wasmFile: "tree-sitter-ruby.wasm",
nodeTypes: {
function: ["method"],
class: ["class", "module"],
import: ["call"],
export: [],
typeAnnotation: [],
},
},
concepts: [
"blocks and procs", "mixins", "metaprogramming", "duck typing",
"DSLs", "monkey patching", "gems", "symbols",
"method_missing", "open classes",
],
filePatterns: { config: "Gemfile" },
};
php.ts:
import type { LanguageConfig } from "../types.js";
export const phpConfig: LanguageConfig = {
id: "php",
displayName: "PHP",
extensions: [".php"],
treeSitter: {
grammarPackage: "tree-sitter-php",
wasmFile: "tree-sitter-php.wasm",
nodeTypes: {
function: ["function_definition", "method_declaration"],
class: ["class_declaration", "interface_declaration", "trait_declaration"],
import: ["namespace_use_declaration"],
export: [],
typeAnnotation: ["type_list", "named_type"],
},
},
concepts: [
"namespaces", "traits", "type declarations", "attributes",
"enums", "fibers", "closures", "magic methods",
"dependency injection", "middleware",
],
filePatterns: { config: "composer.json" },
};
swift.ts:
import type { LanguageConfig } from "../types.js";
export const swiftConfig: LanguageConfig = {
id: "swift",
displayName: "Swift",
extensions: [".swift"],
treeSitter: {
grammarPackage: "tree-sitter-swift",
wasmFile: "tree-sitter-swift.wasm",
nodeTypes: {
function: ["function_declaration", "init_declaration"],
class: ["class_declaration", "struct_declaration", "protocol_declaration", "enum_declaration"],
import: ["import_declaration"],
export: [],
typeAnnotation: ["type_annotation"],
},
},
concepts: [
"optionals", "protocols", "extensions", "generics",
"closures", "property wrappers", "result builders",
"actors", "structured concurrency", "value types vs reference types",
],
filePatterns: { config: "Package.swift" },
};
kotlin.ts:
import type { LanguageConfig } from "../types.js";
export const kotlinConfig: LanguageConfig = {
id: "kotlin",
displayName: "Kotlin",
extensions: [".kt", ".kts"],
treeSitter: {
grammarPackage: "tree-sitter-kotlin",
wasmFile: "tree-sitter-kotlin.wasm",
nodeTypes: {
function: ["function_declaration"],
class: ["class_declaration", "object_declaration", "interface_declaration"],
import: ["import_header"],
export: [],
typeAnnotation: ["type_identifier"],
},
},
concepts: [
"coroutines", "data classes", "sealed classes", "extension functions",
"null safety", "delegation", "DSL builders", "inline functions",
"companion objects", "flow",
],
filePatterns: { config: "build.gradle.kts" },
};
javascript.ts:
import type { LanguageConfig } from "../types.js";
export const javascriptConfig: LanguageConfig = {
id: "javascript",
displayName: "JavaScript",
extensions: [".js", ".mjs", ".cjs", ".jsx"],
treeSitter: {
grammarPackage: "tree-sitter-javascript",
wasmFile: "tree-sitter-javascript.wasm",
nodeTypes: {
function: ["function_declaration"],
class: ["class_declaration"],
import: ["import_statement"],
export: ["export_statement"],
typeAnnotation: [],
},
},
concepts: [
"closures", "prototypes", "promises", "async/await",
"event loop", "destructuring", "spread operator",
"proxies", "generators", "modules (ESM/CJS)",
],
filePatterns: { config: "package.json" },
};
configs/index.ts:
import { typescriptConfig } from "./typescript.js";
import { javascriptConfig } from "./javascript.js";
import { pythonConfig } from "./python.js";
import { goConfig } from "./go.js";
import { javaConfig } from "./java.js";
import { rustConfig } from "./rust.js";
import { cppConfig } from "./cpp.js";
import { csharpConfig } from "./csharp.js";
import { rubyConfig } from "./ruby.js";
import { phpConfig } from "./php.js";
import { swiftConfig } from "./swift.js";
import { kotlinConfig } from "./kotlin.js";
import type { LanguageConfig } from "../types.js";
export const builtinConfigs: LanguageConfig[] = [
typescriptConfig,
javascriptConfig,
pythonConfig,
goConfig,
javaConfig,
rustConfig,
cppConfig,
csharpConfig,
rubyConfig,
phpConfig,
swiftConfig,
kotlinConfig,
];
Step 4: Run test to verify it passes
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/languages/__tests__/configs.test.ts
Expected: PASS
Step 5: Commit
git add understand-anything-plugin/packages/core/src/languages/configs/
git commit -m "feat: add 12 builtin language configs"
Task 4: Create languages/index.ts barrel and export from core
Files:
- Create:
understand-anything-plugin/packages/core/src/languages/index.ts - Modify:
understand-anything-plugin/packages/core/src/index.ts
Step 1: Create barrel export
// understand-anything-plugin/packages/core/src/languages/index.ts
export { LanguageRegistry } from "./registry.js";
export { LanguageConfigSchema } from "./types.js";
export type { LanguageConfig, TreeSitterConfig } from "./types.js";
export { builtinConfigs } from "./configs/index.js";
Step 2: Add export to core index.ts
Add to understand-anything-plugin/packages/core/src/index.ts:
// Languages
export { LanguageRegistry, builtinConfigs, LanguageConfigSchema } from "./languages/index.js";
export type { LanguageConfig, TreeSitterConfig } from "./languages/index.js";
Step 3: Build and verify
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core build
Expected: Build succeeds with no errors
Step 4: Commit
git add understand-anything-plugin/packages/core/src/languages/index.ts understand-anything-plugin/packages/core/src/index.ts
git commit -m "feat: export language types and registry from core"
Task 5: Install tree-sitter WASM grammar packages
Files:
- Modify:
understand-anything-plugin/packages/core/package.json
Step 1: Install new grammar packages
Run:
cd understand-anything-plugin && pnpm --filter @understand-anything/core add \
tree-sitter-python \
tree-sitter-go \
tree-sitter-java \
tree-sitter-rust \
tree-sitter-cpp \
tree-sitter-c-sharp \
tree-sitter-ruby \
tree-sitter-php \
tree-sitter-swift \
tree-sitter-kotlin
Note: Some grammar packages may not ship .wasm files. For those, we need to check availability and potentially build from source or use the tree-sitter CLI to generate WASM. Verify each package after install:
cd understand-anything-plugin && for lang in python go java rust cpp c-sharp ruby php swift kotlin; do
echo "=== tree-sitter-$lang ==="
ls node_modules/tree-sitter-$lang/*.wasm 2>/dev/null || echo "NO WASM FOUND"
done
For packages without pre-built WASM, use tree-sitter build --wasm to compile them, or find alternative npm packages that ship WASM builds. Document which packages needed manual WASM generation.
Step 2: Verify build still passes
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core build
Expected: PASS
Step 3: Commit
git add understand-anything-plugin/packages/core/package.json understand-anything-plugin/pnpm-lock.yaml
git commit -m "feat: add tree-sitter grammar packages for 10 new languages"
Task 6: Build GenericTreeSitterPlugin
Files:
- Create:
understand-anything-plugin/packages/core/src/plugins/generic-tree-sitter-plugin.ts
Step 1: Write the failing test
Create: understand-anything-plugin/packages/core/src/plugins/generic-tree-sitter-plugin.test.ts
import { describe, it, expect, beforeAll } from "vitest";
import { GenericTreeSitterPlugin } from "./generic-tree-sitter-plugin.js";
import { LanguageRegistry } from "../languages/registry.js";
import { typescriptConfig } from "../languages/configs/typescript.js";
import { javascriptConfig } from "../languages/configs/javascript.js";
import { pythonConfig } from "../languages/configs/python.js";
describe("GenericTreeSitterPlugin", () => {
let plugin: GenericTreeSitterPlugin;
beforeAll(async () => {
const registry = new LanguageRegistry();
registry.register(typescriptConfig);
registry.register(javascriptConfig);
registry.register(pythonConfig);
plugin = new GenericTreeSitterPlugin(registry);
await plugin.init();
});
describe("TypeScript (migration parity)", () => {
it("extracts function declarations", () => {
const code = `
function greet(name: string): string {
return "Hello " + name;
}
`;
const result = plugin.analyzeFile("test.ts", code);
expect(result.functions).toHaveLength(1);
expect(result.functions[0].name).toBe("greet");
});
it("extracts class declarations", () => {
const code = `
class UserService {
getName(): string { return "test"; }
}
`;
const result = plugin.analyzeFile("test.ts", code);
expect(result.classes).toHaveLength(1);
expect(result.classes[0].name).toBe("UserService");
});
it("extracts imports", () => {
const code = `import { readFile } from "fs";`;
const result = plugin.analyzeFile("test.ts", code);
expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("fs");
});
it("extracts exports", () => {
const code = `export function hello() {}`;
const result = plugin.analyzeFile("test.ts", code);
expect(result.exports.length).toBeGreaterThanOrEqual(1);
});
it("extracts arrow functions", () => {
const code = `const add = (a: number, b: number): number => a + b;`;
const result = plugin.analyzeFile("test.ts", code);
expect(result.functions).toHaveLength(1);
expect(result.functions[0].name).toBe("add");
});
});
describe("Python", () => {
it("extracts function definitions", () => {
const code = `
def greet(name):
return f"Hello {name}"
def add(a, b):
return a + b
`;
const result = plugin.analyzeFile("test.py", code);
expect(result.functions).toHaveLength(2);
expect(result.functions[0].name).toBe("greet");
expect(result.functions[1].name).toBe("add");
});
it("extracts class definitions", () => {
const code = `
class UserService:
def get_name(self):
return "test"
`;
const result = plugin.analyzeFile("test.py", code);
expect(result.classes).toHaveLength(1);
expect(result.classes[0].name).toBe("UserService");
});
it("extracts import statements", () => {
const code = `
import os
from pathlib import Path
from typing import Optional
`;
const result = plugin.analyzeFile("test.py", code);
expect(result.imports).toHaveLength(3);
});
});
it("returns null for unsupported file extension", () => {
expect(plugin.canAnalyze("test.unknown")).toBe(false);
});
it("reports all registered languages", () => {
const langs = plugin.supportedLanguages();
expect(langs).toContain("typescript");
expect(langs).toContain("python");
});
});
Step 2: Run test to verify it fails
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/plugins/generic-tree-sitter-plugin.test.ts
Expected: FAIL — module not found
Step 3: Write implementation
Create understand-anything-plugin/packages/core/src/plugins/generic-tree-sitter-plugin.ts:
This file implements a GenericTreeSitterPlugin that:
- Takes a
LanguageRegistryin the constructor - In
init(), lazily loads WASM grammars per language usingrequire.resolve(config.treeSitter.grammarPackage + '/' + config.treeSitter.wasmFile) - In
analyzeFile(), determines language from extension via registry, then walks the AST usingconfig.treeSitter.nodeTypesto extract functions/classes/imports/exports - Reuses the same helper patterns from the old
TreeSitterPlugin(traverse, getStringValue, extractParams) but driven by config instead of hardcoded node types - Implements
resolveImports()andextractCallGraph()with the same logic as before
Key implementation notes:
- The
extractNodes()method walks the AST and matches nodes againstnodeTypes.function,nodeTypes.class, etc. - For TS/JS, also handle
lexical_declaration/variable_declarationwith arrow function values (existing behavior) - For import extraction, use the same
getStringValue()approach but match against language-specific import node types - For export extraction, same pattern matching against export node types
- Grammar loading: try
require.resolve()first; if WASM not found, log warning and skip that language
Step 4: Run test to verify it passes
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/plugins/generic-tree-sitter-plugin.test.ts
Expected: PASS
Step 5: Run old TreeSitterPlugin tests with new plugin to verify migration parity
Ensure the existing tree-sitter-plugin.test.ts test cases also pass with GenericTreeSitterPlugin + TS/JS configs.
Step 6: Commit
git add understand-anything-plugin/packages/core/src/plugins/generic-tree-sitter-plugin.ts
git add understand-anything-plugin/packages/core/src/plugins/generic-tree-sitter-plugin.test.ts
git commit -m "feat: add GenericTreeSitterPlugin driven by LanguageConfig"
Task 7: Add per-language test fixtures for remaining languages
Files:
- Modify:
understand-anything-plugin/packages/core/src/plugins/generic-tree-sitter-plugin.test.ts
Step 1: Add test cases for Go, Java, Rust, C++, C#, Ruby, PHP, Swift, Kotlin
For each language, add a describe block with a small fixture testing function/class/import extraction. Example for Go:
describe("Go", () => {
it("extracts function declarations", () => {
const code = `
package main
func greet(name string) string {
return "Hello " + name
}
`;
const result = plugin.analyzeFile("test.go", code);
expect(result.functions).toHaveLength(1);
expect(result.functions[0].name).toBe("greet");
});
it("extracts type declarations", () => {
const code = `
package main
type UserService struct {
Name string
}
`;
const result = plugin.analyzeFile("test.go", code);
expect(result.classes).toHaveLength(1);
});
it("extracts imports", () => {
const code = `
package main
import (
"fmt"
"os"
)
`;
const result = plugin.analyzeFile("test.go", code);
expect(result.imports).toHaveLength(2);
});
});
Follow same pattern for each language with appropriate syntax. Each test uses ~10-20 lines of idiomatic code.
Note: Some WASM grammars may not be available. For languages where the grammar fails to load, register them in the beforeAll with a try/catch and use it.skipIf() to conditionally skip tests. This prevents CI failures while still testing what's available.
Step 2: Run all tests
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/plugins/generic-tree-sitter-plugin.test.ts
Expected: PASS for all languages with available grammars
Step 3: Commit
git add understand-anything-plugin/packages/core/src/plugins/generic-tree-sitter-plugin.test.ts
git commit -m "test: add per-language fixtures for GenericTreeSitterPlugin"
Task 8: Replace TreeSitterPlugin with GenericTreeSitterPlugin
Files:
- Modify:
understand-anything-plugin/packages/core/src/index.ts - Modify:
understand-anything-plugin/packages/core/src/plugins/registry.ts - Delete:
understand-anything-plugin/packages/core/src/plugins/tree-sitter-plugin.ts(after confirming no other imports)
Step 1: Update core exports
In understand-anything-plugin/packages/core/src/index.ts:
- Replace
export { TreeSitterPlugin }withexport { GenericTreeSitterPlugin } - Also export
GenericTreeSitterPluginasTreeSitterPluginfor backward compat if needed (check consumers)
Step 2: Update PluginRegistry extension map
In understand-anything-plugin/packages/core/src/plugins/registry.ts:
- The
EXTENSION_TO_LANGUAGEmap is already comprehensive (has py, go, rs, etc.) - No changes needed here — the registry just dispatches to whatever plugin is registered
Step 3: Update all imports in skill source
Search for all imports of TreeSitterPlugin across the codebase:
Run: grep -r "TreeSitterPlugin" understand-anything-plugin/
Update each import to use GenericTreeSitterPlugin. The main consumers are:
understand-anything-plugin/packages/core/src/index.ts- Any skill source files that instantiate the plugin
Step 4: Delete old TreeSitterPlugin
Once all imports are updated and tests pass:
Run: rm understand-anything-plugin/packages/core/src/plugins/tree-sitter-plugin.ts
Keep the old test file temporarily — rename it to verify parity.
Step 5: Run full test suite
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test
Expected: ALL PASS
Step 6: Commit
git add -A
git commit -m "refactor: replace TreeSitterPlugin with GenericTreeSitterPlugin"
Task 9: Update language-lesson.ts to use LanguageRegistry
Files:
- Modify:
understand-anything-plugin/packages/core/src/analyzer/language-lesson.ts - Modify:
understand-anything-plugin/packages/core/src/__tests__/language-lesson.test.ts
Step 1: Update the test
Update language-lesson.test.ts to verify concepts come from the registry:
it("detects concepts from language config", () => {
const node = {
...sampleNode,
summary: "Uses decorators and async/await with generators",
tags: ["decorators"],
};
const concepts = detectLanguageConcepts(node, "python");
expect(concepts).toContain("decorators");
expect(concepts).toContain("async/await");
});
Step 2: Run test to verify it fails (or passes with old behavior)
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/__tests__/language-lesson.test.ts
Step 3: Update implementation
In language-lesson.ts:
- Import
LanguageRegistryandbuiltinConfigs - Create a module-level registry instance, pre-populated with builtinConfigs
- Replace
LANGUAGE_DISPLAY_NAMESlookups withregistry.getById(lang)?.displayName - Replace hardcoded
CONCEPT_PATTERNSwithregistry.getById(lang)?.conceptsmerged with generic patterns (async/await, error handling, etc. that apply to all languages) - Keep the detection logic (search tags/summary for concept keywords) but source keywords from the config
Step 4: Run test to verify it passes
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/__tests__/language-lesson.test.ts
Expected: PASS
Step 5: Run full test suite
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test
Expected: ALL PASS
Step 6: Commit
git add understand-anything-plugin/packages/core/src/analyzer/language-lesson.ts
git add understand-anything-plugin/packages/core/src/__tests__/language-lesson.test.ts
git commit -m "refactor: source language concepts from LanguageRegistry"
Task 10: Create language prompt snippet files
Files:
- Create:
understand-anything-plugin/skills/understand/languages/typescript.md - Create:
understand-anything-plugin/skills/understand/languages/javascript.md - Create:
understand-anything-plugin/skills/understand/languages/python.md - Create:
understand-anything-plugin/skills/understand/languages/go.md - Create:
understand-anything-plugin/skills/understand/languages/java.md - Create:
understand-anything-plugin/skills/understand/languages/rust.md - Create:
understand-anything-plugin/skills/understand/languages/cpp.md - Create:
understand-anything-plugin/skills/understand/languages/csharp.md - Create:
understand-anything-plugin/skills/understand/languages/ruby.md - Create:
understand-anything-plugin/skills/understand/languages/php.md - Create:
understand-anything-plugin/skills/understand/languages/swift.md - Create:
understand-anything-plugin/skills/understand/languages/kotlin.md
Step 1: Create all 12 language markdown files
Each file follows this structure:
# [Language Name]
## Key Concepts
- [5-10 language-specific concepts with brief explanations]
## Import Patterns
- [All common import syntax patterns for this language]
## Notable File Patterns
- [Special files like __init__.py, go.mod, Cargo.toml, etc.]
## Common Frameworks
- [Top 3-5 frameworks/libraries in this ecosystem]
## Example Summary Style
> "[Example of how to summarize a function/class in this language's idiom]"
Each file should be 30-50 lines, with content specific to that language's ecosystem and idioms. The content should help the LLM produce better analysis by understanding language-specific patterns.
Step 2: Verify files are well-formed
Manually review each file for accuracy and completeness.
Step 3: Commit
git add understand-anything-plugin/skills/understand/languages/
git commit -m "feat: add language-specific prompt snippet files for 12 languages"
Task 11: Make base prompts language-neutral with injection points
Files:
- Modify:
understand-anything-plugin/skills/understand/file-analyzer-prompt.md - Modify:
understand-anything-plugin/skills/understand/tour-builder-prompt.md - Modify:
understand-anything-plugin/skills/understand/project-scanner-prompt.md
Step 1: Update file-analyzer-prompt.md
- Remove all TypeScript-specific examples (e.g., "TypeScript barrel file", type guard references)
- Replace TS-specific concept lists with generic placeholders
- Add injection point:
## Language-Specific Guidance
{{LANGUAGE_CONTEXT}}
- Make the Phase 1 script detection language-aware (not just "Node.js recommended")
Step 2: Update tour-builder-prompt.md
- Remove TS-specific language lesson examples ("generics, discriminated unions, utility types")
- Replace with injection point for detected languages:
## Language-Specific Concepts
{{LANGUAGE_CONTEXT}}
Step 3: Update project-scanner-prompt.md
- Remove
tsconfig.jsonhardcoded check - Make framework detection generic (inject detected languages' framework lists)
- Add multi-language section:
## Detected Languages
{{LANGUAGE_CONTEXT}}
Step 4: Verify prompts are well-formed
Read each modified prompt to ensure it's coherent with injection points and no residual TS bias.
Step 5: Commit
git add understand-anything-plugin/skills/understand/file-analyzer-prompt.md
git add understand-anything-plugin/skills/understand/tour-builder-prompt.md
git add understand-anything-plugin/skills/understand/project-scanner-prompt.md
git commit -m "refactor: make agent prompts language-neutral with injection points"
Task 12: Implement prompt injection logic in skill source
Files:
- Modify:
understand-anything-plugin/skills/understand/SKILL.md(the/understandskill definition)
Step 1: Update the skill orchestration
In the /understand skill (SKILL.md), update the agent dispatch logic:
- Phase 0 (Pre-flight): After scanning files, detect languages present and load corresponding
languages/*.mdfiles - Phase 2 (File Analyzer dispatch): For each file batch, inject the matching language's
.mdcontent into the file-analyzer prompt's{{LANGUAGE_CONTEXT}}placeholder - Phase 4 (Architecture Analyzer): Inject all detected languages' concepts
- Phase 5 (Tour Builder): Inject all detected languages'
.mdcontent into the{{LANGUAGE_CONTEXT}}placeholder - Phase 1 (Project Scanner): Inject all detected languages'
.mdcontent
The injection logic:
- Map file extensions to language IDs (reuse
LanguageRegistry.getByExtension()) - Read the corresponding
languages/<id>.mdfile - Replace
{{LANGUAGE_CONTEXT}}in the base prompt with the file contents
For multi-language projects, concatenate all detected language files.
Step 2: Verify by reading modified SKILL.md
Ensure the orchestration flow includes language detection and prompt injection steps.
Step 3: Commit
git add understand-anything-plugin/skills/understand/SKILL.md
git commit -m "feat: add language detection and prompt injection to /understand skill"
Task 13: Update old tree-sitter-plugin test to use GenericTreeSitterPlugin
Files:
- Modify or Delete:
understand-anything-plugin/packages/core/src/plugins/tree-sitter-plugin.test.ts
Step 1: Migrate or delete
If the old tree-sitter-plugin.test.ts still exists:
- Either update it to import
GenericTreeSitterPluginand instantiate with aLanguageRegistrycontaining TS/JS configs - Or delete it if all its test cases are covered in
generic-tree-sitter-plugin.test.ts
Prefer deleting to avoid duplication.
Step 2: Run full test suite
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test
Expected: ALL PASS
Step 3: Commit
git add -A
git commit -m "test: migrate old tree-sitter-plugin tests to generic plugin"
Task 14: Build and lint verification
Step 1: Build core
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core build
Expected: PASS
Step 2: Build skill package
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/skill build
Expected: PASS
Step 3: Build dashboard
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard build
Expected: PASS (dashboard doesn't import language modules directly)
Step 4: Run lint
Run: cd understand-anything-plugin && pnpm lint
Expected: PASS (or fix any lint issues)
Step 5: Run all tests
Run: cd understand-anything-plugin && pnpm --filter @understand-anything/core test && pnpm --filter @understand-anything/skill test
Expected: ALL PASS
Step 6: Commit any fixes
git add -A
git commit -m "fix: resolve build and lint issues from language-agnostic refactor"
Task 15: Update CLAUDE.md and documentation
Files:
- Modify:
CLAUDE.md - Modify:
README.md(if it exists and mentions TS-only support)
Step 1: Update CLAUDE.md
Add to the Architecture section:
- Mention the
languages/directories (both in core and skills) - Document how to add a new language (create config + prompt snippet)
- List supported languages
Step 2: Commit
git add CLAUDE.md
git commit -m "docs: update CLAUDE.md with language-agnostic architecture"