From d7d39254632fee2d17ff8599347f8cf2c4e60454 Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Sat, 21 Mar 2026 23:30:47 +0800 Subject: [PATCH 01/52] docs: add language-agnostic support design document Design for making Understand-Anything language-agnostic instead of TypeScript-heavy. Covers LanguageConfig registry, GenericTreeSitterPlugin, language-aware prompts, and support for 12 languages. Co-Authored-By: Claude Opus 4.6 --- .../2026-03-21-language-agnostic-design.md | 249 ++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 docs/plans/2026-03-21-language-agnostic-design.md diff --git a/docs/plans/2026-03-21-language-agnostic-design.md b/docs/plans/2026-03-21-language-agnostic-design.md new file mode 100644 index 0000000..1d0ec36 --- /dev/null +++ b/docs/plans/2026-03-21-language-agnostic-design.md @@ -0,0 +1,249 @@ +# Language-Agnostic Support Design + +**Date:** 2026-03-21 +**Status:** Approved +**Issue:** Make Understand-Anything codebase-aware and language-agnostic instead of TypeScript-heavy + +## Problem + +The tool's agent prompts, tree-sitter plugin, and language lesson system are heavily biased toward TypeScript/JavaScript. Non-TS codebases get degraded analysis because: + +1. Agent prompts use TS-specific examples and concepts (e.g., "barrel files", "type guards", "generics") +2. Tree-sitter plugin only ships TS/JS grammar support — structural analysis silently fails for other languages +3. Language lesson detection hardcodes TS-specific concept patterns and display names + +The architecture (PluginRegistry, GraphBuilder, dashboard, search) is already language-neutral. The bias is in shipped content, not the framework. + +## Decisions + +- **Scope:** All three layers — prompts, tree-sitter plugins, language framework +- **Languages (v1):** TypeScript, JavaScript, Python, Go, Java, Rust, C/C++, C#, Ruby, PHP, Swift, Kotlin +- **Architecture:** Config-first with code escape hatch (hybrid) +- **Prompt strategy:** Base prompt + per-language markdown snippet files in a `languages/` folder +- **Config location:** Prompt snippets in `skills/understand/languages/`, tree-sitter configs in `packages/core/src/languages/` +- **Multi-language projects:** Per-file language analysis + project-level multi-language summary +- **Language detection:** Auto-detect from file extensions only (no manual override for v1) + +## Design + +### 1. LanguageConfig Type & Registry + +#### LanguageConfig Interface + +```typescript +// packages/core/src/languages/types.ts +interface LanguageConfig { + id: string; // e.g., "python" + displayName: string; // e.g., "Python" + extensions: string[]; // e.g., [".py", ".pyi"] + treeSitter: { + grammarPackage: string; // npm package name + nodeTypes: { + function: string[]; // e.g., ["function_definition"] + class: string[]; // e.g., ["class_definition"] + import: string[]; // e.g., ["import_statement", "import_from_statement"] + export: string[]; // e.g., ["export_statement"] or [] for languages without exports + typeAnnotation: string[]; // e.g., ["type"] for Python type hints + }; + }; + concepts: string[]; // e.g., ["decorators", "list comprehensions", "generators"] + filePatterns?: Record; // special files, e.g., {"config": "pyproject.toml"} + customAnalyzer?: (node: SyntaxNode) => AnalysisResult; // escape hatch for unusual AST shapes +} +``` + +#### Language Registry + +```typescript +// packages/core/src/languages/registry.ts +class LanguageRegistry { + private configs: Map; + + register(config: LanguageConfig): void; + getByExtension(ext: string): LanguageConfig | null; + getById(id: string): LanguageConfig; + getAll(): LanguageConfig[]; +} +``` + +#### File Structure + +``` +packages/core/src/languages/ +├── types.ts +├── registry.ts +├── index.ts +├── configs/ +│ ├── typescript.ts +│ ├── javascript.ts +│ ├── python.ts +│ ├── go.ts +│ ├── java.ts +│ ├── rust.ts +│ ├── cpp.ts +│ ├── csharp.ts +│ ├── ruby.ts +│ ├── php.ts +│ ├── swift.ts +│ └── kotlin.ts +``` + +All built-in configs auto-registered on import. + +### 2. GenericTreeSitterPlugin + +Replaces the current TS-only `TreeSitterPlugin` with a config-driven version. + +```typescript +// packages/core/src/plugins/generic-tree-sitter-plugin.ts +class GenericTreeSitterPlugin implements AnalyzerPlugin { + private registry: LanguageRegistry; + + canAnalyze(filePath: string): boolean { + return this.registry.getByExtension(path.extname(filePath)) !== null; + } + + async analyzeFile(filePath: string, content: string): Promise { + const config = this.registry.getByExtension(path.extname(filePath)); + + // Custom analyzer escape hatch + if (config.customAnalyzer) { + return config.customAnalyzer(tree.rootNode); + } + + // Generic extraction driven by config.treeSitter.nodeTypes + const functions = this.extractNodes(tree, config.treeSitter.nodeTypes.function); + const classes = this.extractNodes(tree, config.treeSitter.nodeTypes.class); + const imports = this.extractNodes(tree, config.treeSitter.nodeTypes.import); + const exports = this.extractNodes(tree, config.treeSitter.nodeTypes.export); + // ... + } + + private extractNodes(tree: Tree, nodeTypes: string[]): NodeInfo[] { + // Walk AST, collect all nodes matching any of the given types + } +} +``` + +#### Migration + +- Current `TreeSitterPlugin` deleted, replaced by `GenericTreeSitterPlugin` + TS/JS configs +- `PluginRegistry` unchanged +- Existing tests updated to use new plugin + +#### WASM Grammar Loading + +- Each grammar loaded lazily on first use and cached +- WASM files bundled in `packages/core/src/languages/grammars/` or fetched from tree-sitter's official WASM builds + +### 3. Language-Aware Prompts + +#### File Structure + +``` +skills/understand/ +├── file-analyzer-prompt.md # Base prompt (language-neutral) +├── tour-builder-prompt.md +├── project-scanner-prompt.md +├── languages/ +│ ├── typescript.md +│ ├── javascript.md +│ ├── python.md +│ ├── go.md +│ ├── java.md +│ ├── rust.md +│ ├── cpp.md +│ ├── csharp.md +│ ├── ruby.md +│ ├── php.md +│ ├── swift.md +│ └── kotlin.md +``` + +#### Base Prompt Changes + +All TS-specific examples removed from base prompts. Replaced with injection point: + +```markdown +## Language-Specific Guidance + +{{LANGUAGE_CONTEXT}} +``` + +#### Language Markdown Format + +Each language file contains: + +```markdown +# Python + +## Key Concepts +- Decorators, comprehensions, generators, context managers, type hints, dunder methods + +## Import Patterns +- `import module`, `from module import name`, relative imports + +## Notable File Patterns +- `__init__.py` (package initializer), `conftest.py` (pytest), `pyproject.toml` (config) + +## Example Summary Style +> "FastAPI route handler that accepts a Pydantic model, validates input..." +``` + +#### Injection Logic + +1. Project scanner detects languages present in the codebase +2. File-analyzer: inject matching language `.md` for that file's language +3. Tour-builder: inject all detected languages' `.md` files +4. Project-scanner: inject all detected languages' key concepts for project-level summary + +#### Multi-Language Projects + +Project-scanner prompt gets a combined section listing all detected languages with their key concepts. + +### 4. Language Lesson Updates + +- Delete `LANGUAGE_DISPLAY_NAMES` — use `LanguageRegistry.getById(id).displayName` +- Delete hardcoded concept patterns — use `LanguageConfig.concepts` from registry +- Language lesson generation becomes config-driven + +### 5. Testing Strategy + +#### Unit Tests + +1. **LanguageConfig validation** — Each config has all required fields, non-empty nodeTypes +2. **LanguageRegistry** — Registration, lookup by extension/id, duplicate handling +3. **GenericTreeSitterPlugin per language** — Small fixture file per language verifying function/class/import extraction +4. **Language lesson generation** — Concepts sourced from config + +#### Integration Tests + +5. **Multi-language project** — Mixed TS + Python fixture, verify graph contains nodes from both languages +6. **Prompt injection** — Correct language `.md` injected based on detected language + +#### Migration Tests + +- Current tree-sitter-plugin tests rewritten for GenericTreeSitterPlugin with TS config +- Must produce identical results to validate non-breaking migration + +### 6. Error Handling & Graceful Degradation + +#### Key Principle + +**Every file always gets analyzed.** Tree-sitter is an enhancement, not a gate. The LLM is the primary analyzer; structural analysis enriches it. + +#### Unknown Language + +- Tree-sitter skipped (returns `null`) +- LLM analysis still runs — file gets summary, tags, graph node +- Debug log: `"No language config for .xyz, skipping structural analysis"` + +#### Missing WASM Grammar + +- Warning logged, that language degrades to LLM-only +- Other languages unaffected + +#### Malformed Language Config + +- Validated at registration time via Zod schema +- Invalid config throws at startup — fail fast From 87c07a61d88afe9c9a9add2d74b1ae7420d1c0a6 Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Sat, 21 Mar 2026 23:37:38 +0800 Subject: [PATCH 02/52] docs: add language-agnostic implementation plan 15-task TDD plan covering LanguageConfig types, LanguageRegistry, 12 language configs, GenericTreeSitterPlugin, prompt snippets, and migration from TS-only TreeSitterPlugin. Co-Authored-By: Claude Opus 4.6 --- .../2026-03-21-language-agnostic-plan.md | 1392 +++++++++++++++++ 1 file changed, 1392 insertions(+) create mode 100644 docs/plans/2026-03-21-language-agnostic-plan.md diff --git a/docs/plans/2026-03-21-language-agnostic-plan.md b/docs/plans/2026-03-21-language-agnostic-plan.md new file mode 100644 index 0000000..16e14c4 --- /dev/null +++ b/docs/plans/2026-03-21-language-agnostic-plan.md @@ -0,0 +1,1392 @@ +# 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` + +```typescript +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` + +```typescript +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; +export type TreeSitterConfig = z.infer; +``` + +**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** + +```bash +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` + +```typescript +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** + +```typescript +// 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(); + private extensionMap = new Map(); + + 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** + +```bash +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` + +```typescript +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:** +```typescript +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:** +```typescript +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:** +```typescript +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:** +```typescript +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:** +```typescript +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:** +```typescript +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:** +```typescript +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:** +```typescript +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:** +```typescript +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:** +```typescript +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:** +```typescript +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:** +```typescript +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:** +```typescript +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** + +```bash +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** + +```typescript +// 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`: + +```typescript +// 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** + +```bash +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: +```bash +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: + +```bash +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** + +```bash +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` + +```typescript +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 `LanguageRegistry` in the constructor +- In `init()`, lazily loads WASM grammars per language using `require.resolve(config.treeSitter.grammarPackage + '/' + config.treeSitter.wasmFile)` +- In `analyzeFile()`, determines language from extension via registry, then walks the AST using `config.treeSitter.nodeTypes` to 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()` and `extractCallGraph()` with the same logic as before + +Key implementation notes: +- The `extractNodes()` method walks the AST and matches nodes against `nodeTypes.function`, `nodeTypes.class`, etc. +- For TS/JS, also handle `lexical_declaration`/`variable_declaration` with 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** + +```bash +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: + +```typescript +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** + +```bash +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 }` with `export { GenericTreeSitterPlugin }` +- Also export `GenericTreeSitterPlugin` as `TreeSitterPlugin` for 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_LANGUAGE` map 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** + +```bash +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: + +```typescript +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 `LanguageRegistry` and `builtinConfigs` +- Create a module-level registry instance, pre-populated with builtinConfigs +- Replace `LANGUAGE_DISPLAY_NAMES` lookups with `registry.getById(lang)?.displayName` +- Replace hardcoded `CONCEPT_PATTERNS` with `registry.getById(lang)?.concepts` merged 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** + +```bash +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: + +```markdown +# [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** + +```bash +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: + +```markdown +## 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: + +```markdown +## Language-Specific Concepts + +{{LANGUAGE_CONTEXT}} +``` + +**Step 3: Update project-scanner-prompt.md** + +- Remove `tsconfig.json` hardcoded check +- Make framework detection generic (inject detected languages' framework lists) +- Add multi-language section: + +```markdown +## 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** + +```bash +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 `/understand` skill 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/*.md` files +- **Phase 2 (File Analyzer dispatch):** For each file batch, inject the matching language's `.md` content 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' `.md` content into the `{{LANGUAGE_CONTEXT}}` placeholder +- **Phase 1 (Project Scanner):** Inject all detected languages' `.md` content + +The injection logic: +1. Map file extensions to language IDs (reuse `LanguageRegistry.getByExtension()`) +2. Read the corresponding `languages/.md` file +3. 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** + +```bash +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 `GenericTreeSitterPlugin` and instantiate with a `LanguageRegistry` containing 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** + +```bash +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** + +```bash +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** + +```bash +git add CLAUDE.md +git commit -m "docs: update CLAUDE.md with language-agnostic architecture" +``` From 46fb114b14cfc05702e1f87e12b77ce2f3470f90 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Mar 2026 16:00:03 +0000 Subject: [PATCH 03/52] feat: extend analysis pipeline for Python codebases and frameworks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - project-scanner: detect Python frameworks from requirements.txt, pyproject.toml, setup.py, Pipfile (django, fastapi, flask, sqlalchemy, celery, pydantic, etc.); add pyproject.toml to project name extraction - SKILL.md: expand Python entry points (manage.py, app.py, wsgi.py, asgi.py, run.py, __main__.py); add FastAPI and Flask framework guidance to Phase 2 and Phase 4 inline hints; wire addendum file injection for Django, FastAPI, and Flask when detected - architecture-analyzer: add __init__.py and manage.py as entry-point file-level patterns; add Python directory patterns (migrations, signals, serializers, management, templatetags); clarify *.d.ts is TS-only - tour-builder: add Python entry point filename patterns to +3 scoring (manage.py, app.py, wsgi.py, asgi.py, run.py, __main__.py) - file-analyzer: add __init__.py barrel/entry-point detection alongside index.ts; show Python script execution alternative in example - new: django-analyzer-addendum.md — canonical file roles, edge patterns (URL routing graph, signal wiring, ORM relationships), layer guide, and language lesson patterns for Django projects - new: fastapi-analyzer-addendum.md — canonical file roles, DI tree edge patterns, layer guide, and language lesson patterns for FastAPI and Flask - new: PYTHON-SUPPORT-CHANGES.md — reviewer doc covering every change, rationale, regression risk, and a testing checklist https://claude.ai/code/session_015ihoTRnr7yYx3TkcadKYbn --- .../understand/PYTHON-SUPPORT-CHANGES.md | 208 ++++++++++++++++++ .../skills/understand/SKILL.md | 14 +- .../architecture-analyzer-prompt.md | 13 +- .../understand/django-analyzer-addendum.md | 67 ++++++ .../understand/fastapi-analyzer-addendum.md | 109 +++++++++ .../skills/understand/file-analyzer-prompt.md | 7 +- .../understand/project-scanner-prompt.md | 7 +- .../skills/understand/tour-builder-prompt.md | 2 +- 8 files changed, 415 insertions(+), 12 deletions(-) create mode 100644 understand-anything-plugin/skills/understand/PYTHON-SUPPORT-CHANGES.md create mode 100644 understand-anything-plugin/skills/understand/django-analyzer-addendum.md create mode 100644 understand-anything-plugin/skills/understand/fastapi-analyzer-addendum.md diff --git a/understand-anything-plugin/skills/understand/PYTHON-SUPPORT-CHANGES.md b/understand-anything-plugin/skills/understand/PYTHON-SUPPORT-CHANGES.md new file mode 100644 index 0000000..39939a1 --- /dev/null +++ b/understand-anything-plugin/skills/understand/PYTHON-SUPPORT-CHANGES.md @@ -0,0 +1,208 @@ +# Python Codebase Support — Change Review Guide + +This document explains every change made to add Python and Python framework support. It is written for a reviewer who wants to verify correctness, spot regressions, and understand the rationale for each decision. + +--- + +## Background: What Was Wrong + +The tool worked well for TypeScript/JavaScript codebases. For Python codebases it would: +- Fail to detect frameworks (Django, FastAPI, Flask) — `frameworks: []` always +- Miss common Python entry points (`manage.py`, `app.py`, `wsgi.py`) — defaulting to no entry point +- Score Python entry points much lower than TS equivalents in the tour builder (4 Python patterns vs 8 JS/TS patterns) +- Miss Python's `__init__.py` as a barrel/entry-point equivalent (only `index.ts`/`index.js` were recognized) +- Have no layer guidance for FastAPI or Flask (only Django had a brief mention) +- Ignore `pyproject.toml` for project name extraction + +The bias was **only in agent prompts** (markdown files). The graph schema, dashboard, search engine, and core plugin architecture are language-agnostic and required no changes. + +--- + +## Files Changed + +### 1. `project-scanner-prompt.md` + +**What changed:** + +**Step 5 (Framework Detection)** — Extended the Python manifest reading from "confirms Python project" to actually detecting frameworks: +- `requirements.txt`: now reads line-by-line, strips version specifiers, and matches against a Python framework keyword list: `django`, `djangorestframework`, `fastapi`, `flask`, `sqlalchemy`, `alembic`, `celery`, `pydantic`, `uvicorn`, `gunicorn`, `aiohttp`, `tornado`, `starlette`, `pytest`, `hypothesis`, `channels` +- `pyproject.toml`: now parses `[project].dependencies` and `[tool.poetry.dependencies]`, applies the same keyword matching, and also checks for `[tool.pytest.ini_options]` (pytest) and `[tool.django]` (Django) +- `setup.py`, `setup.cfg`, `Pipfile`: now apply the same Python framework keyword matching + +**Step 7 (Project Name)** — Added `pyproject.toml` to the priority order between `go.mod` and directory name. Checks `[project].name` first, then `[tool.poetry].name`. + +**Why:** Without framework detection, `frameworks: []` is passed to every downstream agent. The framework-specific guidance injected in Phase 2 and Phase 4 of SKILL.md is only useful when `frameworks` is non-empty. + +**Regression risk:** None. The JS framework detection in `package.json` is unchanged. The Python additions are additive. + +**How to verify:** Run `/understand` on a Django project with `requirements.txt` containing `django`. Check that `scan-result.json` has `frameworks: ["Django"]`. + +--- + +### 2. `SKILL.md` + +**What changed:** + +**Phase 0 (entry point detection, line 57)** — Added Python entry points to the pattern list: +- Before: `src/index.ts`, `src/main.ts`, `src/App.tsx`, `main.py`, `main.go`, `src/main.rs`, `index.js` +- After: added `manage.py`, `app.py`, `wsgi.py`, `asgi.py`, `run.py`, `__main__.py` + +**Why:** A Django project's real entry point is `manage.py`. A FastAPI/Flask project uses `app.py` or `run.py`. Without these, `$ENTRY_POINT` is empty for most Python projects, and the tour builder gets no starting hint. + +**Phase 2 (file-analyzer framework guidance)** — Extended the inline framework hints: +- Django: added `serializers.py`, `signals.py`, `admin.py`, `migrations/` descriptions +- Added FastAPI: describes `@router` decorator files, Pydantic schemas, `Depends()` providers +- Added Flask: describes `@blueprint.route`, `blueprints/`, SQLAlchemy `models.py` +- Added addendum injection: if `Django` detected, reads `./django-analyzer-addendum.md` and appends to the file-analyzer prompt. If `FastAPI` or `Flask` detected, reads `./fastapi-analyzer-addendum.md` and appends. + +**Phase 4 (architecture-analyzer framework hints)** — Extended the inline layer hints: +- Django: added `serializers.py`, `signals.py`, `migrations/` → specific layers +- Added FastAPI: router files → API, Pydantic schemas → Types, `dependencies.py` → Service, DB files → Data +- Added Flask: blueprint route files → API, `models.py` → Data, `forms.py` → UI, `extensions.py` → Config +- Added addendum injection: same logic as Phase 2 + +**Regression risk:** Low. The addendum injection only triggers when those frameworks are in the detected list. The inline guidance additions are additive strings — they don't change the structure of the injected context. + +**How to verify:** +- Run on a FastAPI project: check `layers.json` has a `layer:types` or `layer:api` with Pydantic schema files assigned correctly +- Run on a TS project: check that no Django/FastAPI addendum content appears in the analysis (it shouldn't, since `frameworks` won't contain those values) + +--- + +### 3. `architecture-analyzer-prompt.md` + +**What changed:** + +**Directory pattern table** — Added Python-specific directory names: + +| Added | Pattern Label | Why | +|-------|---------------|-----| +| `migrations` | `data` | Django/Alembic migration directories hold schema history — Data Layer | +| `management`, `commands` | `config` | Django management command directories | +| `templatetags` | `utility` | Django custom template tag directories | +| `signals` | `service` | Signal handler modules — cross-cutting service logic | +| `serializers` | `api` | DRF serializer directories | + +**File-level pattern matching** — Three changes: +1. Added `test_*.py` to the test pattern (Python's `pytest` naming convention) +2. Added `__init__.py` at a directory root → `entry` pattern (Python package barrel equivalent of `index.ts`) +3. Added `manage.py` → `entry` and `wsgi.py`/`asgi.py` → `config` +4. Clarified `*.d.ts → types` with "(TypeScript declaration files only)" — making it explicit this is TS-specific so an LLM doesn't misapply it to Python + +**Why:** Without `__init__.py → entry`, the architecture analyzer would never recognize any Python file as an entry point via file-level patterns. The `hooks` pattern label was left as-is (it won't trigger on Python projects since they don't conventionally have a `hooks/` directory). + +**Note on Node.js script:** The architecture analyzer's structural analysis script is hardcoded to `node`. This is correct to leave as-is — the script processes the JSON graph structure (file nodes, import edges), not the source language of the codebase being analyzed. The script's input/output is always JSON regardless of whether the project is Python or TypeScript. + +**Regression risk:** Very low. Added rows to the directory pattern table and clarified file-level pattern descriptions. No existing patterns were removed or modified. + +**How to verify:** Run on a Django project. Check that `migrations/` directory files land in `layer:data` and that `manage.py` gets tagged `entry`. + +--- + +### 4. `tour-builder-prompt.md` + +**What changed:** + +**Entry point candidate scoring (Section C)** — Added Python entry points to the +3 filename list: +- Added: `manage.py`, `app.py`, `wsgi.py`, `asgi.py`, `run.py`, `__main__.py` + +Before this change, the entry point scoring had 8 TS/JS patterns vs 4 for all other languages. After: 8 TS/JS + 6 Python + 4 others. + +**Why:** The tour builder uses entry point scores to decide Step 1 of the tour. For a Django project where `manage.py` exists, it should score highly. Without this, the tour might start from a random high-fan-in utility file instead of the actual entry point. + +**The `languageLesson` example** was left as-is (TypeScript barrel files). This is just an illustrative example in the output format section — it does not affect how Python tours are generated. The language lessons section already lists Python-specific patterns (decorators, generators, context managers, metaclasses, protocols). + +**Regression risk:** None. The scoring list is additive. TS/JS entry points retain their +3 scores. + +**How to verify:** Run on a Django project. Check that `tour.json` starts from `manage.py` or `apps.py`/`wsgi.py` rather than a utility file. + +--- + +### 5. `file-analyzer-prompt.md` + +**What changed:** + +**Tags indicators — barrel/entry-point detection** — Extended the `index.ts` rule: +- Before: `Named index.ts at a directory root with re-exports = entry-point` +- After: Added `__init__.py` at a package root with imports or re-exports = `entry-point`, and `manage.py` = `entry-point` + +**Script execution example** — Added the Python equivalent command alongside the Node.js example. The base prompt already says "Choose the best language for this task — Node.js is recommended for TypeScript/JavaScript projects, Python for Python projects" (line 15), but the execution example only showed `node`. This created a contradiction. Now both are shown. + +**Regression risk:** None. Additive changes only. + +**How to verify:** Run on a Python project with a package structure. Check that `__init__.py` files at package roots get the `entry-point` or `barrel` tag rather than being treated as empty boilerplate files. + +--- + +## New Files Created + +### `django-analyzer-addendum.md` + +A detailed reference injected into the file-analyzer and architecture-analyzer when Django is detected. Contains: +- Canonical file roles table (15+ Django file types with appropriate tags) +- Edge patterns to look for (URL routing graph, signal wiring, ORM relationships, serializer→model binding) +- Layer assignment guide (7 layers: api, data, service, ui, middleware, config, test) +- Notable `languageLesson` patterns (fat models, ORM lazy evaluation, CBV mixins, signal anti-patterns, app isolation) + +**How it's injected:** SKILL.md reads this file and appends it to the base `file-analyzer-prompt.md` and `architecture-analyzer-prompt.md` content when `Django` appears in the detected frameworks list. + +### `fastapi-analyzer-addendum.md` + +A detailed reference for FastAPI and Flask projects, injected when either framework is detected. Contains two sections: + +**FastAPI section:** +- Canonical file roles (router files, Pydantic schemas, CRUD, dependencies, database session) +- Edge patterns (router inclusion chain, DI tree, Pydantic inheritance, CRUD→model binding) +- Layer assignment guide (7 layers) +- Notable `languageLesson` patterns (DI as composition, Pydantic validation, async vs sync, route order) + +**Flask section:** +- Canonical file roles (blueprints, application factory, WTForms, Marshmallow) +- Edge patterns (blueprint registration, extension coupling, before/after request hooks) +- Layer assignment guide +- Notable `languageLesson` patterns (factory pattern, blueprint modularity, extension `init_app` protocol) + +**How it's injected:** Same mechanism as the Django addendum — SKILL.md appends it when `FastAPI` or `Flask` is in the detected frameworks list. + +--- + +## What Was NOT Changed (And Why) + +| Component | Rationale | +|-----------|-----------| +| Graph schema (`types.ts`, `schema.ts`) | Already language-agnostic. All 18 edge types work for Python patterns. | +| `packages/core/src/plugins/tree-sitter-plugin.ts` | Still TS/JS only. Adding Python tree-sitter support is Phase 3 (separate PR). | +| `packages/core/src/plugins/registry.ts` | Extension map already has `.py → python`. A Python plugin will register here in Phase 3. | +| `packages/core/src/analyzer/language-lesson.ts` | Concept detection patterns. Phase 3 work. | +| Dashboard, search engine, skills | Already language-agnostic. | +| `tour-builder-prompt.md` language lessons example | The TypeScript barrel file example is illustrative only. Python tours will produce Python-specific `languageLesson` strings based on the language-lessons list (which already includes Python patterns). | +| Architecture analyzer `node` script execution | The script analyzes the graph JSON, not the source language. `node` is always correct here. | +| `hooks` directory pattern label | React-specific but harmless — Python projects don't conventionally have `hooks/` directories, so this label will never trigger on Python codebases. | + +--- + +## Testing Checklist for Reviewer + +For a Django project (e.g., a real Django app with `requirements.txt`): +- [ ] `scan-result.json` has `frameworks: ["Django"]` (or similar) +- [ ] `manage.py` is detected as `$ENTRY_POINT` in SKILL.md Phase 0 +- [ ] `manage.py` node gets tags including `entry-point` +- [ ] `urls.py` files get `api-handler`, `routing` tags +- [ ] `models.py` files get `data-model` tag +- [ ] `migrations/` directory files land in `layer:data` +- [ ] Tour Step 1 starts from `manage.py` or `wsgi.py` +- [ ] No TypeScript-specific guidance appears in the analysis output + +For a FastAPI project: +- [ ] `scan-result.json` has `frameworks: ["FastAPI"]` +- [ ] Router files get `api-handler`, `routing` tags +- [ ] Pydantic schema files get `type-definition`, `serialization` tags +- [ ] `dependencies.py` or `deps.py` gets `service` tag +- [ ] `depends_on` edges appear between router files and their dependencies +- [ ] `layer:types` exists with schema files + +For an existing TypeScript project (regression check): +- [ ] No Django/FastAPI addendum content appears in analysis +- [ ] `frameworks: ["React"]` (or whatever was there before) unchanged +- [ ] `src/index.ts` still detected as entry point +- [ ] All existing layer assignments and tour steps unchanged diff --git a/understand-anything-plugin/skills/understand/SKILL.md b/understand-anything-plugin/skills/understand/SKILL.md index 1327551..fd89b92 100644 --- a/understand-anything-plugin/skills/understand/SKILL.md +++ b/understand-anything-plugin/skills/understand/SKILL.md @@ -54,7 +54,7 @@ Determine whether to run a full analysis or incremental update. find $PROJECT_ROOT -maxdepth 2 -type f -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' | head -100 ``` Store as `$DIR_TREE`. - - Detect the project entry point by checking for common patterns: `src/index.ts`, `src/main.ts`, `src/App.tsx`, `main.py`, `main.go`, `src/main.rs`, `index.js`. Store first match as `$ENTRY_POINT`. + - Detect the project entry point by checking for common patterns (in order): `src/index.ts`, `src/main.ts`, `src/App.tsx`, `index.js`, `main.py`, `manage.py`, `app.py`, `wsgi.py`, `asgi.py`, `run.py`, `__main__.py`, `main.go`, `src/main.rs`. Store first match as `$ENTRY_POINT`. --- @@ -98,7 +98,7 @@ After the subagent completes, read `$PROJECT_ROOT/.understand-anything/intermedi Batch the file list from Phase 1 into groups of **5-10 files each** (aim for balanced batch sizes). -For each batch, dispatch a subagent using the prompt template at `./file-analyzer-prompt.md`. Run up to **3 subagents concurrently** using parallel dispatch. Read the template once, then for each batch pass the full template content as the subagent's prompt, appending the following additional context: +For each batch, dispatch a subagent using the prompt template at `./file-analyzer-prompt.md`. Run up to **3 subagents concurrently** using parallel dispatch. Read the template once. If any detected framework is `Django`, also read `./django-analyzer-addendum.md` and append its full content after the base template. If any detected framework is `FastAPI` or `Flask`, also read `./fastapi-analyzer-addendum.md` and append its full content after the base template. Then for each batch pass the combined template content as the subagent's prompt, appending the following additional context: > **Additional context from main session:** > @@ -109,7 +109,9 @@ For each batch, dispatch a subagent using the prompt template at `./file-analyze > Framework-specific guidance: > - If React/Next.js: files in `app/` or `pages/` are routes, `components/` are UI, `lib/` or `utils/` are utilities > - If Express/Fastify: files in `routes/` are API endpoints, `middleware/` is middleware, `models/` or `db/` is data -> - If Python Django: `views.py` are controllers, `models.py` is data, `urls.py` is routing, `templates/` is UI +> - If Python Django: `views.py` are controllers, `models.py` is data, `urls.py` is routing, `templates/` is UI, `serializers.py` is API serialization, `signals.py` is event wiring, `admin.py` is admin registration, `migrations/` is schema history +> - If Python FastAPI: files with `@router.get/post/...` decorators are endpoints, `schemas.py` or `models.py` with Pydantic classes are request/response types, `dependencies.py` or `deps.py` holds `Depends()` providers, `routers/` or `api/` groups route modules +> - If Python Flask: files with `@app.route` or `@blueprint.route` are endpoints, `blueprints/` or `views/` groups route modules, `models.py` with SQLAlchemy classes is data > - If Go: `cmd/` is entry points, `internal/` is private packages, `pkg/` is public packages > > Use this context to produce more accurate summaries and better classify file roles. @@ -158,7 +160,7 @@ Merge all file-analyzer results into a single set of nodes and edges. Then perfo ## Phase 4 — ARCHITECTURE -Dispatch a subagent using the prompt template at `./architecture-analyzer-prompt.md`. Read the template file and pass the full content as the subagent's prompt, appending the following additional context: +Dispatch a subagent using the prompt template at `./architecture-analyzer-prompt.md`. If any detected framework is `Django`, also read `./django-analyzer-addendum.md` and append its full content after the base template. If any detected framework is `FastAPI` or `Flask`, also read `./fastapi-analyzer-addendum.md` and append its full content after the base template. Pass the combined content as the subagent's prompt, appending the following additional context: > **Additional context from main session:** > @@ -172,7 +174,9 @@ Dispatch a subagent using the prompt template at `./architecture-analyzer-prompt > Framework-specific layer hints: > - If React/Next.js: `app/` or `pages/` → UI Layer, `api/` → API Layer, `lib/` → Service Layer, `components/` → UI Layer > - If Express: `routes/` → API Layer, `controllers/` → Service Layer, `models/` → Data Layer, `middleware/` → Middleware Layer -> - If Python Django: `views/` → API Layer, `models/` → Data Layer, `templates/` → UI Layer, `management/` → CLI Layer +> - If Python Django: `views/` or `views.py` → API Layer, `models/` or `models.py` → Data Layer, `templates/` → UI Layer, `management/` → CLI Layer, `serializers.py` → API Layer, `signals.py` → Event Layer, `migrations/` → Data Layer +> - If Python FastAPI: files with router decorators → API Layer, Pydantic schema files → Types Layer, `dependencies.py` or `deps.py` → Service Layer, `routers/` or `api/` → API Layer, database session/engine files → Data Layer +> - If Python Flask: files with `@blueprint.route` → API Layer, `models.py` → Data Layer, `forms.py` → UI Layer, `extensions.py` → Config Layer > - If Go: `cmd/` → Entry Points, `internal/` → Service Layer, `pkg/` → Shared Library, `api/` → API Layer > > Use the directory tree and framework hints to inform layer assignments. Directory structure is strong evidence for layer boundaries. diff --git a/understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md b/understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md index 2200252..9728db3 100644 --- a/understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md +++ b/understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md @@ -82,11 +82,18 @@ Classify each directory name against known architectural patterns: | `hooks` | `hooks` | | `store`, `state`, `reducers`, `actions`, `slices` | `state` | | `assets`, `static`, `public` | `assets` | +| `migrations` | `data` | +| `management`, `commands` | `config` | +| `templatetags` | `utility` | +| `signals` | `service` | +| `serializers` | `api` | Also check file-level patterns: -- Files matching `*.test.*` or `*.spec.*` -> `test` -- Files matching `*.d.ts` -> `types` -- Files named `index.ts`/`index.js` at a package root -> `entry` +- Files matching `*.test.*` or `*.spec.*` or `test_*.py` -> `test` +- Files matching `*.d.ts` -> `types` (TypeScript declaration files only) +- Files named `index.ts`, `index.js`, or `__init__.py` at a package/directory root -> `entry` +- Files named `manage.py` at the project root -> `entry` (Django management entry point) +- Files named `wsgi.py` or `asgi.py` -> `config` (Python WSGI/ASGI server config) **F. Dependency Direction** diff --git a/understand-anything-plugin/skills/understand/django-analyzer-addendum.md b/understand-anything-plugin/skills/understand/django-analyzer-addendum.md new file mode 100644 index 0000000..e4c0601 --- /dev/null +++ b/understand-anything-plugin/skills/understand/django-analyzer-addendum.md @@ -0,0 +1,67 @@ +# Django Framework Addendum + +> Injected into file-analyzer and architecture-analyzer prompts when Django is detected. +> Do NOT use as a standalone prompt — always appended to the base prompt template. + +## Django Project Structure + +When analyzing a Django project, apply these additional conventions on top of the base analysis rules. + +### Canonical File Roles + +| File / Pattern | Role | Tags | +|---|---|---| +| `manage.py` | CLI entry point for dev server, migrations, management commands | `entry-point`, `config` | +| `*/settings.py`, `*/settings/*.py` | Project-wide configuration (DB, installed apps, middleware) | `config` | +| `*/urls.py` | URL routing — maps URL patterns to views | `api-handler`, `routing` | +| `*/views.py`, `*/views/*.py` | Request handlers (function-based or class-based views) | `api-handler`, `controller` | +| `*/models.py`, `*/models/*.py` | ORM models — map to database tables | `data-model` | +| `*/serializers.py` | DRF serializers — convert models to/from JSON | `serialization`, `api-handler` | +| `*/forms.py` | Django forms — validation and rendering logic | `validation`, `ui` | +| `*/admin.py` | Admin site registrations — exposes models in Django admin | `config` | +| `*/signals.py` | Signal handlers — cross-cutting side effects on model events | `event-handler` | +| `*/tasks.py` | Celery async task definitions | `service`, `event-handler` | +| `*/middleware.py`, `*/middleware/*.py` | Request/response middleware classes | `middleware` | +| `*/permissions.py` | DRF permission classes | `middleware`, `validation` | +| `*/filters.py` | DRF filter backends | `utility` | +| `*/migrations/*.py` | Auto-generated schema migrations — do not summarize individually | `config` | +| `*/templates/**/*.html` | Django HTML templates | `ui` | +| `*/templatetags/*.py` | Custom template filters and tags | `utility` | +| `*/management/commands/*.py` | Custom management commands (`./manage.py mycommand`) | `config`, `entry-point` | +| `wsgi.py`, `asgi.py` | WSGI/ASGI server adapter — production entry point | `config`, `entry-point` | +| `*/apps.py` | App configuration and startup hooks (`AppConfig`) | `config` | +| `*/tests.py`, `*/tests/*.py` | Unit and integration tests | `test` | + +### Edge Patterns to Look For + +**URL routing graph** — Create `calls` edges from `urls.py` nodes to their corresponding view nodes when `path()` or `re_path()` maps a URL pattern to a view function or class. These edges represent the HTTP routing chain. + +**Signal wiring** — When `signals.py` uses `post_save.connect(handler, sender=Model)` or `@receiver(post_save, sender=Model)`, create `subscribes` edges from the signal handler function to the model class. Create `publishes` edges from the model to the signal handler to show the trigger direction. + +**ORM relationships** — When `models.py` defines `ForeignKey`, `OneToOneField`, or `ManyToManyField`, create `relates_to` edges (use `depends_on` edge type) between the model classes with a description indicating the relationship type and cardinality. + +**Serializer-to-model binding** — When a DRF serializer has `model = MyModel` in its `Meta` class, create a `depends_on` edge from the serializer to the model. + +**View-to-serializer binding** — When a DRF ViewSet or APIView references a serializer class, create a `depends_on` edge from the view to the serializer. + +### Architectural Layers for Django + +Assign nodes to these layers when detected: + +| Layer ID | Layer Name | What Goes Here | +|---|---|---| +| `layer:api` | API Layer | `views.py`, `serializers.py`, `urls.py`, DRF ViewSets and APIViews | +| `layer:data` | Data Layer | `models.py`, `migrations/`, database utility files | +| `layer:service` | Service Layer | `signals.py`, `tasks.py`, custom managers, service modules | +| `layer:ui` | UI Layer | `templates/`, `forms.py`, `templatetags/` | +| `layer:middleware` | Middleware Layer | `middleware.py`, `permissions.py`, authentication backends | +| `layer:config` | Config Layer | `settings.py`, `urls.py` (root), `wsgi.py`, `asgi.py`, `apps.py`, `manage.py` | +| `layer:test` | Test Layer | `tests.py`, `tests/` directory, `conftest.py` | + +### Notable Patterns to Capture in languageLesson + +- **Fat models vs. thin views**: Django encourages business logic in model methods, keeping views thin HTTP adapters +- **Django ORM lazy evaluation**: QuerySets are not evaluated until iterated — chain filters without DB hits +- **Class-based views (CBVs)**: Mixins like `LoginRequiredMixin`, `PermissionRequiredMixin` compose behavior through multiple inheritance +- **Signal anti-patterns**: Signals create invisible coupling; a signal in `signals.py` may be triggered by a `save()` call anywhere in the codebase +- **App isolation**: Each Django app (`INSTALLED_APPS`) should be self-contained with its own models, views, urls, and migrations diff --git a/understand-anything-plugin/skills/understand/fastapi-analyzer-addendum.md b/understand-anything-plugin/skills/understand/fastapi-analyzer-addendum.md new file mode 100644 index 0000000..d8a71c6 --- /dev/null +++ b/understand-anything-plugin/skills/understand/fastapi-analyzer-addendum.md @@ -0,0 +1,109 @@ +# FastAPI / Flask Framework Addendum + +> Injected into file-analyzer and architecture-analyzer prompts when FastAPI or Flask is detected. +> Do NOT use as a standalone prompt — always appended to the base prompt template. + +## FastAPI Project Structure + +When analyzing a FastAPI project, apply these additional conventions on top of the base analysis rules. + +### Canonical File Roles — FastAPI + +| File / Pattern | Role | Tags | +|---|---|---| +| `main.py`, `app.py` | Application factory — creates and configures the `FastAPI()` instance | `entry-point`, `config` | +| `*/routers/*.py`, `*/api/*.py` | `APIRouter` modules — group related endpoints by domain | `api-handler`, `routing` | +| `*/schemas.py`, `*/schemas/*.py` | Pydantic request/response models | `type-definition`, `serialization` | +| `*/models.py`, `*/models/*.py` | SQLAlchemy ORM models or other DB models | `data-model` | +| `*/dependencies.py`, `*/deps.py` | `Depends()` provider functions — shared logic injected into routes | `service`, `middleware` | +| `*/crud.py`, `*/repository.py` | Database access layer — CRUD operations | `data-model`, `service` | +| `*/database.py`, `*/db.py` | DB engine, session factory, connection management | `config`, `data-model` | +| `*/config.py`, `*/settings.py` | `pydantic-settings` / `BaseSettings` config classes | `config` | +| `*/middleware.py` | Starlette middleware classes | `middleware` | +| `*/exceptions.py` | Custom exception classes and exception handlers | `utility` | +| `*/security.py`, `*/auth.py` | Auth utilities — JWT decoding, password hashing, OAuth helpers | `service`, `middleware` | +| `*/tasks.py` | Background tasks or Celery task definitions | `service`, `event-handler` | +| `*/tests/*.py`, `test_*.py` | pytest test files | `test` | +| `conftest.py` | pytest fixtures and test configuration | `test`, `config` | + +### Edge Patterns to Look For — FastAPI + +**Router inclusion chain** — When `app.include_router(some_router, prefix="/api")` appears in `main.py` or a router aggregator, create `imports` + `depends_on` edges from the main app file to each router module. This builds the URL hierarchy graph. + +**Dependency injection tree** — When a route function or another `Depends()` provider imports and calls `Depends(some_function)`, create `depends_on` edges from the caller to the dependency provider. Trace these chains — they often span multiple files (e.g., route → auth dependency → DB session dependency). + +**Pydantic model inheritance** — When a schema class inherits from another (e.g., `class UserCreate(UserBase)`), create `inherits` edges between the schema class nodes. + +**ORM model relationships** — When SQLAlchemy models use `relationship()`, `ForeignKey`, create `depends_on` edges between the model classes. + +**CRUD-to-model binding** — When a `crud.py` function takes a model type as an argument or directly references a model class, create `depends_on` edges from the CRUD file to the model file. + +### Architectural Layers for FastAPI + +| Layer ID | Layer Name | What Goes Here | +|---|---|---| +| `layer:api` | API Layer | Router files, endpoint functions with `@router.get/post/...` decorators | +| `layer:types` | Types Layer | Pydantic schema files, request/response models | +| `layer:service` | Service Layer | `dependencies.py`, `crud.py`, business logic modules | +| `layer:data` | Data Layer | ORM models, `database.py`, migrations | +| `layer:config` | Config Layer | `main.py` / `app.py` factory, `settings.py`, `config.py` | +| `layer:middleware` | Middleware Layer | `middleware.py`, `security.py`, `auth.py`, exception handlers | +| `layer:test` | Test Layer | `tests/`, `conftest.py` | + +### Notable Patterns to Capture in languageLesson + +- **Dependency injection as composition**: FastAPI's `Depends()` is a first-class DI system — a route can declare any number of dependencies, each of which can have their own dependencies, forming a tree resolved at request time +- **Pydantic for validation**: Request bodies, query params, and path params are automatically validated by Pydantic — invalid input raises `422 Unprocessable Entity` before your code runs +- **Async endpoints**: `async def` routes run in the event loop; `def` routes run in a threadpool — mixing them incorrectly can cause performance issues +- **Path operation order**: FastAPI matches routes in declaration order; a catch-all route before a specific one will shadow it + +--- + +## Flask Project Structure + +When analyzing a Flask project, apply these additional conventions on top of the base analysis rules. + +### Canonical File Roles — Flask + +| File / Pattern | Role | Tags | +|---|---|---| +| `app.py`, `__init__.py` (in app package) | Application factory (`create_app()`) or direct `Flask(__name__)` instance | `entry-point`, `config` | +| `run.py`, `wsgi.py` | Production/dev server entry point | `entry-point`, `config` | +| `*/views.py`, `*/routes.py` | Route handler functions with `@app.route` or `@blueprint.route` | `api-handler`, `routing` | +| `*/blueprints/*.py`, `*/api/*.py` | Blueprint modules — group routes by feature | `api-handler`, `routing` | +| `*/models.py` | SQLAlchemy models or other ORM models | `data-model` | +| `*/forms.py` | WTForms form classes | `validation`, `ui` | +| `*/schemas.py` | Marshmallow serialization schemas | `serialization`, `type-definition` | +| `*/config.py` | Config classes (`DevelopmentConfig`, `ProductionConfig`) | `config` | +| `*/extensions.py` | Flask extension initialization (`db = SQLAlchemy()`, `login_manager = LoginManager()`) | `config`, `singleton` | +| `*/decorators.py` | Custom route decorators (auth guards, rate limiting) | `middleware`, `utility` | +| `*/utils.py`, `*/helpers.py` | Shared utility functions | `utility` | +| `*/templates/**/*.html` | Jinja2 templates | `ui` | +| `*/static/` | CSS, JS, and asset files | `assets` | +| `*/tests/*.py`, `test_*.py` | pytest or unittest test files | `test` | + +### Edge Patterns to Look For — Flask + +**Blueprint registration** — When `app.register_blueprint(bp, url_prefix='/api')` appears in the application factory, create `depends_on` edges from the app factory to each blueprint module. + +**Extension coupling** — When a view imports from `extensions.py` (e.g., `from .extensions import db, login_manager`), create `imports` edges to show which views depend on which extensions. + +**Before/after request hooks** — When `@app.before_request` or `@blueprint.before_request` decorates a function, create `middleware` edges from those functions to the app/blueprint they attach to. + +### Architectural Layers for Flask + +| Layer ID | Layer Name | What Goes Here | +|---|---|---| +| `layer:api` | API Layer | Blueprint route files, view functions | +| `layer:data` | Data Layer | `models.py`, database migration files | +| `layer:service` | Service Layer | Business logic modules, `schemas.py`, service classes | +| `layer:ui` | UI Layer | `templates/`, `forms.py`, `static/` | +| `layer:config` | Config Layer | `app.py` factory, `config.py`, `extensions.py` | +| `layer:middleware` | Middleware Layer | `decorators.py`, before/after request hooks | +| `layer:test` | Test Layer | Test files, `conftest.py` | + +### Notable Patterns to Capture in languageLesson + +- **Application factory pattern**: `create_app()` functions allow multiple app instances (e.g., for testing) and delay extension initialization — avoids circular imports +- **Blueprint modularity**: Blueprints group related routes, templates, and static files; they are registered on the app with a URL prefix, making them independently testable +- **Flask extension protocol**: Extensions follow `init_app(app)` for lazy initialization — the extension object is created globally but bound to an app instance later diff --git a/understand-anything-plugin/skills/understand/file-analyzer-prompt.md b/understand-anything-plugin/skills/understand/file-analyzer-prompt.md index 4f28e77..0ab0cf1 100644 --- a/understand-anything-plugin/skills/understand/file-analyzer-prompt.md +++ b/understand-anything-plugin/skills/understand/file-analyzer-prompt.md @@ -125,7 +125,10 @@ ENDJSON After writing the script, execute it. **Use the batch index in every temp file path** — multiple file-analyzer agents run in parallel and must not overwrite each other's files: ```bash +# For Node.js scripts: node /tmp/ua-file-extract-.js /tmp/ua-file-analyzer-input-.json /tmp/ua-file-extract-results-.json +# For Python scripts: +python3 /tmp/ua-file-extract-.py /tmp/ua-file-analyzer-input-.json /tmp/ua-file-extract-results-.json ``` If the script exits with a non-zero code, read stderr, diagnose the issue, fix the script, and re-run. You have up to 2 retry attempts. @@ -166,7 +169,9 @@ Indicators from script data: - Filename contains `.test.` or `.spec.` = `test` - Exports a class with `Handler` or `Controller` in the name = `api-handler` - Only type/interface exports = `type-definition` -- Named `index.ts` at a directory root with re-exports = `entry-point` +- Named `index.ts` or `index.js` at a directory root with re-exports = `entry-point` (JavaScript/TypeScript barrel) +- Named `__init__.py` at a package root with imports or re-exports = `entry-point` (Python package barrel) +- Named `manage.py` = `entry-point` (Django management script) **Language Notes** (optional, your expert judgment): If the structural data reveals notable language-specific patterns (e.g., many generic type parameters, decorator usage, complex trait bounds), add a brief `languageNotes` string. Only add this when genuinely educational. diff --git a/understand-anything-plugin/skills/understand/project-scanner-prompt.md b/understand-anything-plugin/skills/understand/project-scanner-prompt.md index 30112ab..2b88af1 100644 --- a/understand-anything-plugin/skills/understand/project-scanner-prompt.md +++ b/understand-anything-plugin/skills/understand/project-scanner-prompt.md @@ -81,7 +81,9 @@ Read config files (if they exist) and extract framework information: - `tsconfig.json` -- if present, confirms TypeScript usage - `Cargo.toml` -- if present, confirms Rust project; extract `[package].name` - `go.mod` -- if present, confirms Go project; extract module name -- `requirements.txt` / `pyproject.toml` / `setup.py` / `Pipfile` -- if present, confirms Python project +- `requirements.txt` -- if present, confirms Python project; read line by line and match package names (strip version specifiers) against known Python frameworks: `django`, `djangorestframework`, `fastapi`, `flask`, `sqlalchemy`, `alembic`, `celery`, `pydantic`, `uvicorn`, `gunicorn`, `aiohttp`, `tornado`, `starlette`, `pytest`, `hypothesis`, `channels` +- `pyproject.toml` -- if present, confirms Python project; parse the `[project].dependencies` or `[tool.poetry.dependencies]` section and apply the same Python framework keyword matching as above. Also check for `[tool.pytest.ini_options]` (confirms pytest) and `[tool.django]` (confirms Django). +- `setup.py` / `setup.cfg` / `Pipfile` -- if present, confirms Python project; read and apply Python framework keyword matching - `Gemfile` -- if present, confirms Ruby project - `pom.xml` / `build.gradle` -- if present, confirms Java project @@ -99,7 +101,8 @@ Extract from (in priority order): 1. `package.json` `name` field 2. `Cargo.toml` `[package].name` 3. `go.mod` module path (last segment) -4. Directory name of project root +4. `pyproject.toml` -- check `[project].name` first, then `[tool.poetry].name` +5. Directory name of project root ### Script Output Format diff --git a/understand-anything-plugin/skills/understand/tour-builder-prompt.md b/understand-anything-plugin/skills/understand/tour-builder-prompt.md index fbfb8c6..dca4353 100644 --- a/understand-anything-plugin/skills/understand/tour-builder-prompt.md +++ b/understand-anything-plugin/skills/understand/tour-builder-prompt.md @@ -46,7 +46,7 @@ For every node, count how many other nodes it has edges pointing TO (fan-out). H **C. Entry Point Candidates** Identify likely entry points using these signals (score each file node, sum the scores): -- Filename matches `index.ts`, `index.js`, `main.ts`, `main.js`, `app.ts`, `app.js`, `server.ts`, `server.js`, `mod.rs`, `main.go`, `main.py`, `main.rs` -> +3 points +- Filename matches `index.ts`, `index.js`, `main.ts`, `main.js`, `app.ts`, `app.js`, `server.ts`, `server.js`, `mod.rs`, `main.go`, `main.py`, `main.rs`, `manage.py`, `app.py`, `wsgi.py`, `asgi.py`, `run.py`, `__main__.py` -> +3 points - Node tags contain `entry-point` or `barrel` -> +2 points - File is at the project root or one level deep (e.g., `src/index.ts`) -> +1 point - High fan-out (top 10%) -> +1 point From b5eb2e60414495385e93539080ebbd6f61d2d7c6 Mon Sep 17 00:00:00 2001 From: Sreeram Date: Mon, 23 Mar 2026 12:35:09 +0530 Subject: [PATCH 04/52] feat: language-agnostic analysis with config-driven registry and framework detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the TypeScript/JavaScript-biased analysis pipeline with a truly language-agnostic system. The core architecture was already language-neutral (graph schema, dashboard, search) — the bias lived in agent prompts, tree-sitter plugin, and language-lesson system. Core changes: - LanguageConfig + FrameworkConfig types with Zod validation - LanguageRegistry (12 languages) and FrameworkRegistry (10 frameworks) - Config-driven TreeSitterPlugin replacing hardcoded TS/JS grammars - PluginRegistry now delegates to LanguageRegistry for extension mapping - Language-lesson system uses config for display names and concepts Prompt system: - SKILL.md generalized: dynamic injection of language snippets and framework addendums instead of hardcoded if/else conditionals - 12 language prompt snippets (languages/*.md) with concepts, patterns, frameworks per language - 10 framework addendums (frameworks/*.md) with canonical file roles, edge patterns, architectural layers — Django/FastAPI/Flask preserved and split, plus React/Next.js/Express/Vue/Spring/Rails/Gin added - Extended entry points, directory patterns, and test patterns across all 12 language ecosystems in base prompts --- CLAUDE.md | 33 +++ .../packages/core/package.json | 4 + .../src/__tests__/framework-registry.test.ts | 99 +++++++++ .../src/__tests__/language-lesson.test.ts | 2 + .../src/__tests__/language-registry.test.ts | 91 ++++++++ .../core/src/analyzer/language-lesson.ts | 58 ++++- .../packages/core/src/index.ts | 14 ++ .../core/src/languages/configs/cpp.ts | 25 +++ .../core/src/languages/configs/csharp.ts | 25 +++ .../packages/core/src/languages/configs/go.ts | 24 ++ .../core/src/languages/configs/index.ts | 43 ++++ .../core/src/languages/configs/java.ts | 29 +++ .../core/src/languages/configs/javascript.ts | 43 ++++ .../core/src/languages/configs/kotlin.ts | 25 +++ .../core/src/languages/configs/php.ts | 25 +++ .../core/src/languages/configs/python.ts | 40 ++++ .../core/src/languages/configs/ruby.ts | 24 ++ .../core/src/languages/configs/rust.ts | 27 +++ .../core/src/languages/configs/swift.ts | 25 +++ .../core/src/languages/configs/typescript.ts | 48 ++++ .../core/src/languages/framework-registry.ts | 80 +++++++ .../core/src/languages/frameworks/django.ts | 36 +++ .../core/src/languages/frameworks/express.ts | 26 +++ .../core/src/languages/frameworks/fastapi.ts | 25 +++ .../core/src/languages/frameworks/flask.ts | 31 +++ .../core/src/languages/frameworks/gin.ts | 19 ++ .../core/src/languages/frameworks/index.ts | 38 ++++ .../core/src/languages/frameworks/nextjs.ts | 23 ++ .../core/src/languages/frameworks/rails.ts | 28 +++ .../core/src/languages/frameworks/react.ts | 19 ++ .../core/src/languages/frameworks/spring.ts | 27 +++ .../core/src/languages/frameworks/vue.ts | 19 ++ .../packages/core/src/languages/index.ts | 22 ++ .../core/src/languages/language-registry.ts | 53 +++++ .../packages/core/src/languages/types.ts | 54 +++++ .../packages/core/src/plugins/registry.ts | 42 ++-- .../core/src/plugins/tree-sitter-plugin.ts | 175 +++++++++++---- .../understand/PYTHON-SUPPORT-CHANGES.md | 208 ------------------ .../skills/understand/SKILL.md | 38 ++-- .../architecture-analyzer-prompt.md | 20 +- .../skills/understand/file-analyzer-prompt.md | 8 +- .../django.md} | 0 .../skills/understand/frameworks/express.md | 57 +++++ .../fastapi.md} | 59 +---- .../skills/understand/frameworks/flask.md | 53 +++++ .../skills/understand/frameworks/gin.md | 59 +++++ .../skills/understand/frameworks/nextjs.md | 59 +++++ .../skills/understand/frameworks/rails.md | 65 ++++++ .../skills/understand/frameworks/react.md | 55 +++++ .../skills/understand/frameworks/spring.md | 59 +++++ .../skills/understand/frameworks/vue.md | 59 +++++ .../skills/understand/languages/cpp.md | 47 ++++ .../skills/understand/languages/csharp.md | 46 ++++ .../skills/understand/languages/go.md | 47 ++++ .../skills/understand/languages/java.md | 45 ++++ .../skills/understand/languages/javascript.md | 46 ++++ .../skills/understand/languages/kotlin.md | 45 ++++ .../skills/understand/languages/php.md | 46 ++++ .../skills/understand/languages/python.md | 48 ++++ .../skills/understand/languages/ruby.md | 46 ++++ .../skills/understand/languages/rust.md | 47 ++++ .../skills/understand/languages/swift.md | 46 ++++ .../skills/understand/languages/typescript.md | 46 ++++ .../understand/project-scanner-prompt.md | 6 +- .../skills/understand/tour-builder-prompt.md | 2 +- 65 files changed, 2421 insertions(+), 362 deletions(-) create mode 100644 understand-anything-plugin/packages/core/src/__tests__/framework-registry.test.ts create mode 100644 understand-anything-plugin/packages/core/src/__tests__/language-registry.test.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/cpp.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/csharp.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/go.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/index.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/java.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/javascript.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/kotlin.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/php.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/python.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/ruby.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/rust.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/swift.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/typescript.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/framework-registry.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/frameworks/django.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/frameworks/express.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/frameworks/fastapi.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/frameworks/flask.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/frameworks/gin.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/frameworks/index.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/frameworks/nextjs.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/frameworks/rails.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/frameworks/react.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/frameworks/spring.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/frameworks/vue.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/index.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/language-registry.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/types.ts delete mode 100644 understand-anything-plugin/skills/understand/PYTHON-SUPPORT-CHANGES.md rename understand-anything-plugin/skills/understand/{django-analyzer-addendum.md => frameworks/django.md} (100%) create mode 100644 understand-anything-plugin/skills/understand/frameworks/express.md rename understand-anything-plugin/skills/understand/{fastapi-analyzer-addendum.md => frameworks/fastapi.md} (55%) create mode 100644 understand-anything-plugin/skills/understand/frameworks/flask.md create mode 100644 understand-anything-plugin/skills/understand/frameworks/gin.md create mode 100644 understand-anything-plugin/skills/understand/frameworks/nextjs.md create mode 100644 understand-anything-plugin/skills/understand/frameworks/rails.md create mode 100644 understand-anything-plugin/skills/understand/frameworks/react.md create mode 100644 understand-anything-plugin/skills/understand/frameworks/spring.md create mode 100644 understand-anything-plugin/skills/understand/frameworks/vue.md create mode 100644 understand-anything-plugin/skills/understand/languages/cpp.md create mode 100644 understand-anything-plugin/skills/understand/languages/csharp.md create mode 100644 understand-anything-plugin/skills/understand/languages/go.md create mode 100644 understand-anything-plugin/skills/understand/languages/java.md create mode 100644 understand-anything-plugin/skills/understand/languages/javascript.md create mode 100644 understand-anything-plugin/skills/understand/languages/kotlin.md create mode 100644 understand-anything-plugin/skills/understand/languages/php.md create mode 100644 understand-anything-plugin/skills/understand/languages/python.md create mode 100644 understand-anything-plugin/skills/understand/languages/ruby.md create mode 100644 understand-anything-plugin/skills/understand/languages/rust.md create mode 100644 understand-anything-plugin/skills/understand/languages/swift.md create mode 100644 understand-anything-plugin/skills/understand/languages/typescript.md diff --git a/CLAUDE.md b/CLAUDE.md index 02ae299..443042e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,3 +46,36 @@ An open-source tool combining LLM intelligence + static analysis to produce inte When pushing to remote, bump the version in **both** of these files (keep them in sync): - `understand-anything-plugin/package.json` → `"version"` field - `.claude-plugin/marketplace.json` → `plugins[0].version` field + +## Testing Local Plugin Changes + +Claude Code caches installed plugins at `~/.claude/plugins/cache/understand-anything/understand-anything//`. Symlinks don't work because Claude's Search/Glob tools can't follow them. To test local changes: + +1. **Build the packages:** + ```bash + pnpm --filter @understand-anything/core build + pnpm --filter @understand-anything/skill build + ``` + +2. **Find the installed version** (must match what the marketplace currently serves): + ```bash + ls ~/.claude/plugins/cache/understand-anything/understand-anything/ + ``` + +3. **Copy your local plugin into the cache**, replacing `` with the version from step 2: + ```bash + rm -rf ~/.claude/plugins/cache/understand-anything/understand-anything/ + cp -R ./understand-anything-plugin ~/.claude/plugins/cache/understand-anything/understand-anything/ + ``` + +4. **Start a fresh Claude Code session** (existing sessions cache the old prompts in context). + +5. **Run `/understand --full`** in the target project to verify. + +**Re-sync after further changes:** +```bash +pnpm --filter @understand-anything/core build && \ +cp -R ./understand-anything-plugin/* ~/.claude/plugins/cache/understand-anything/understand-anything// +``` + +**To revert to upstream:** Uninstall and reinstall the plugin from the marketplace — it repopulates the cache from the upstream repo. diff --git a/understand-anything-plugin/packages/core/package.json b/understand-anything-plugin/packages/core/package.json index f26319b..1609792 100644 --- a/understand-anything-plugin/packages/core/package.json +++ b/understand-anything-plugin/packages/core/package.json @@ -20,6 +20,10 @@ "./schema": { "types": "./dist/schema.d.ts", "default": "./dist/schema.js" + }, + "./languages": { + "types": "./dist/languages/index.d.ts", + "default": "./dist/languages/index.js" } }, "scripts": { diff --git a/understand-anything-plugin/packages/core/src/__tests__/framework-registry.test.ts b/understand-anything-plugin/packages/core/src/__tests__/framework-registry.test.ts new file mode 100644 index 0000000..3fdc588 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/__tests__/framework-registry.test.ts @@ -0,0 +1,99 @@ +import { describe, it, expect } from "vitest"; +import { FrameworkRegistry } from "../languages/framework-registry.js"; +import { djangoConfig } from "../languages/frameworks/django.js"; +import { reactConfig } from "../languages/frameworks/react.js"; + +describe("FrameworkRegistry", () => { + it("registers and retrieves a framework config by id", () => { + const registry = new FrameworkRegistry(); + registry.register(djangoConfig); + expect(registry.getById("django")?.displayName).toBe("Django"); + }); + + it("retrieves frameworks for a language", () => { + const registry = new FrameworkRegistry(); + registry.register(djangoConfig); + registry.register(reactConfig); + const pythonFrameworks = registry.getForLanguage("python"); + expect(pythonFrameworks).toHaveLength(1); + expect(pythonFrameworks[0].id).toBe("django"); + }); + + it("returns empty array for unknown language", () => { + const registry = new FrameworkRegistry(); + registry.register(djangoConfig); + expect(registry.getForLanguage("haskell")).toEqual([]); + }); + + describe("detectFrameworks", () => { + it("detects Django from requirements.txt", () => { + const registry = new FrameworkRegistry(); + registry.register(djangoConfig); + const detected = registry.detectFrameworks({ + "requirements.txt": "django==4.2\ncelery==5.3\n", + }); + expect(detected).toHaveLength(1); + expect(detected[0].id).toBe("django"); + }); + + it("detects React from package.json", () => { + const registry = new FrameworkRegistry(); + registry.register(reactConfig); + const detected = registry.detectFrameworks({ + "package.json": '{"dependencies": {"react": "^18.2.0", "react-dom": "^18.2.0"}}', + }); + expect(detected).toHaveLength(1); + expect(detected[0].id).toBe("react"); + }); + + it("detection is case-insensitive", () => { + const registry = new FrameworkRegistry(); + registry.register(djangoConfig); + const detected = registry.detectFrameworks({ + "requirements.txt": "Django==4.2\n", + }); + expect(detected).toHaveLength(1); + }); + + it("returns empty array when no frameworks match", () => { + const registry = new FrameworkRegistry(); + registry.register(djangoConfig); + const detected = registry.detectFrameworks({ + "requirements.txt": "requests==2.31\n", + }); + expect(detected).toEqual([]); + }); + + it("returns empty array for empty manifests", () => { + const registry = new FrameworkRegistry(); + registry.register(djangoConfig); + expect(registry.detectFrameworks({})).toEqual([]); + }); + + it("does not duplicate detected frameworks", () => { + const registry = new FrameworkRegistry(); + registry.register(djangoConfig); + const detected = registry.detectFrameworks({ + "requirements.txt": "django==4.2\ndjango==4.2\n", + "pyproject.toml": '[project]\ndependencies = ["django>=4.0"]', + }); + expect(detected).toHaveLength(1); + }); + }); + + describe("createDefault", () => { + it("registers all 10 built-in framework configs", () => { + const registry = FrameworkRegistry.createDefault(); + expect(registry.getAllFrameworks()).toHaveLength(10); + }); + + it("includes frameworks for multiple languages", () => { + const registry = FrameworkRegistry.createDefault(); + expect(registry.getForLanguage("python").length).toBeGreaterThanOrEqual(3); + expect(registry.getForLanguage("typescript").length).toBeGreaterThanOrEqual(2); + expect(registry.getForLanguage("java").length).toBeGreaterThanOrEqual(1); + expect(registry.getForLanguage("ruby").length).toBeGreaterThanOrEqual(1); + expect(registry.getForLanguage("go").length).toBeGreaterThanOrEqual(1); + }); + }); +}); diff --git a/understand-anything-plugin/packages/core/src/__tests__/language-lesson.test.ts b/understand-anything-plugin/packages/core/src/__tests__/language-lesson.test.ts index 7a6d8e7..a8ed5c1 100644 --- a/understand-anything-plugin/packages/core/src/__tests__/language-lesson.test.ts +++ b/understand-anything-plugin/packages/core/src/__tests__/language-lesson.test.ts @@ -5,6 +5,7 @@ import { detectLanguageConcepts, } from "../analyzer/language-lesson.js"; import type { GraphNode, GraphEdge } from "../types.js"; +import { typescriptConfig } from "../languages/configs/typescript.js"; const sampleNode: GraphNode = { id: "func:auth:verifyToken", @@ -51,6 +52,7 @@ describe("language-lesson", () => { sampleNode, sampleEdges, "typescript", + typescriptConfig, ); expect(prompt).toContain("TypeScript"); }); diff --git a/understand-anything-plugin/packages/core/src/__tests__/language-registry.test.ts b/understand-anything-plugin/packages/core/src/__tests__/language-registry.test.ts new file mode 100644 index 0000000..c3a8b75 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/__tests__/language-registry.test.ts @@ -0,0 +1,91 @@ +import { describe, it, expect } from "vitest"; +import { LanguageRegistry } from "../languages/language-registry.js"; +import { typescriptConfig } from "../languages/configs/typescript.js"; +import { pythonConfig } from "../languages/configs/python.js"; + +describe("LanguageRegistry", () => { + it("registers and retrieves a language config by id", () => { + const registry = new LanguageRegistry(); + registry.register(typescriptConfig); + expect(registry.getById("typescript")).toEqual(typescriptConfig); + }); + + it("retrieves config by file extension", () => { + const registry = new LanguageRegistry(); + registry.register(typescriptConfig); + expect(registry.getByExtension(".ts")?.id).toBe("typescript"); + expect(registry.getByExtension(".tsx")?.id).toBe("typescript"); + }); + + it("retrieves config for a file path", () => { + const registry = new LanguageRegistry(); + registry.register(typescriptConfig); + registry.register(pythonConfig); + expect(registry.getForFile("src/index.ts")?.id).toBe("typescript"); + expect(registry.getForFile("app/models.py")?.id).toBe("python"); + }); + + it("returns null for unknown extensions", () => { + const registry = new LanguageRegistry(); + registry.register(typescriptConfig); + expect(registry.getByExtension(".xyz")).toBeNull(); + expect(registry.getForFile("file.unknown")).toBeNull(); + }); + + it("returns null for files without extensions", () => { + const registry = new LanguageRegistry(); + expect(registry.getForFile("Makefile")).toBeNull(); + }); + + it("lists all registered languages", () => { + const registry = new LanguageRegistry(); + registry.register(typescriptConfig); + registry.register(pythonConfig); + const all = registry.getAllLanguages(); + expect(all).toHaveLength(2); + expect(all.map(c => c.id)).toContain("typescript"); + expect(all.map(c => c.id)).toContain("python"); + }); + + describe("createDefault", () => { + it("registers all 12 built-in language configs", () => { + const registry = LanguageRegistry.createDefault(); + const all = registry.getAllLanguages(); + expect(all.length).toBe(12); + }); + + it("maps all expected extensions", () => { + const registry = LanguageRegistry.createDefault(); + expect(registry.getByExtension(".ts")?.id).toBe("typescript"); + expect(registry.getByExtension(".py")?.id).toBe("python"); + expect(registry.getByExtension(".go")?.id).toBe("go"); + expect(registry.getByExtension(".rs")?.id).toBe("rust"); + expect(registry.getByExtension(".java")?.id).toBe("java"); + expect(registry.getByExtension(".rb")?.id).toBe("ruby"); + expect(registry.getByExtension(".php")?.id).toBe("php"); + expect(registry.getByExtension(".swift")?.id).toBe("swift"); + expect(registry.getByExtension(".kt")?.id).toBe("kotlin"); + expect(registry.getByExtension(".cs")?.id).toBe("csharp"); + expect(registry.getByExtension(".cpp")?.id).toBe("cpp"); + expect(registry.getByExtension(".js")?.id).toBe("javascript"); + }); + + it("has no duplicate extension mappings across configs", () => { + const registry = LanguageRegistry.createDefault(); + const all = registry.getAllLanguages(); + const allExtensions: string[] = []; + for (const config of all) { + allExtensions.push(...config.extensions); + } + const unique = new Set(allExtensions); + expect(unique.size).toBe(allExtensions.length); + }); + + it("every config has at least one concept", () => { + const registry = LanguageRegistry.createDefault(); + for (const config of registry.getAllLanguages()) { + expect(config.concepts.length).toBeGreaterThan(0); + } + }); + }); +}); diff --git a/understand-anything-plugin/packages/core/src/analyzer/language-lesson.ts b/understand-anything-plugin/packages/core/src/analyzer/language-lesson.ts index cd1bc56..53fcc01 100644 --- a/understand-anything-plugin/packages/core/src/analyzer/language-lesson.ts +++ b/understand-anything-plugin/packages/core/src/analyzer/language-lesson.ts @@ -1,11 +1,16 @@ import type { GraphNode, GraphEdge } from "../types.js"; +import type { LanguageConfig } from "../languages/types.js"; export interface LanguageLessonResult { languageNotes: string; concepts: Array<{ name: string; explanation: string }>; } -const CONCEPT_PATTERNS: Record = { +/** + * Base concept patterns that apply across all languages. + * These are merged with language-specific concepts from LanguageConfig. + */ +const BASE_CONCEPT_PATTERNS: Record = { "async/await": ["async", "await", "promise", "asynchronous"], "middleware pattern": ["middleware", "interceptor", "pipe"], "generics": ["generic", "type parameter", "template"], @@ -36,12 +41,35 @@ const CONCEPT_PATTERNS: Record = { "concurrency": ["goroutine", "channel", "thread", "worker", "mutex"], }; +/** + * Build the full concept patterns map by merging base patterns with + * language-specific concepts from a LanguageConfig (if provided). + */ +function buildConceptPatterns( + langConfig?: LanguageConfig | null, +): Record { + const patterns = { ...BASE_CONCEPT_PATTERNS }; + + if (langConfig?.concepts) { + for (const concept of langConfig.concepts) { + if (!patterns[concept]) { + // Use the concept name itself as a keyword for detection + patterns[concept] = [concept.toLowerCase()]; + } + } + } + + return patterns; +} + /** * Detects language concepts present in a graph node based on its tags, summary, and languageNotes. + * When a LanguageConfig is provided, language-specific concepts are also detected. */ export function detectLanguageConcepts( node: GraphNode, language: string, + langConfig?: LanguageConfig | null, ): string[] { const text = [ ...node.tags, @@ -49,9 +77,10 @@ export function detectLanguageConcepts( node.languageNotes?.toLowerCase() ?? "", ].join(" "); + const patterns = buildConceptPatterns(langConfig); const detected: string[] = []; - for (const [concept, keywords] of Object.entries(CONCEPT_PATTERNS)) { + for (const [concept, keywords] of Object.entries(patterns)) { const found = keywords.some((keyword) => text.toLowerCase().includes(keyword.toLowerCase()), ); @@ -63,11 +92,19 @@ export function detectLanguageConcepts( return detected; } -const LANGUAGE_DISPLAY_NAMES: Record = { - typescript: "TypeScript", - javascript: "JavaScript", - coffeescript: "CoffeeScript", -}; +/** + * Get the display name for a language. + * Uses LanguageConfig if provided, otherwise falls back to capitalization. + */ +export function getLanguageDisplayName( + language: string, + langConfig?: LanguageConfig | null, +): string { + if (langConfig?.displayName) { + return langConfig.displayName; + } + return language.charAt(0).toUpperCase() + language.slice(1); +} /** * Builds a prompt that asks an LLM to produce a language-specific lesson for a given node. @@ -76,12 +113,11 @@ export function buildLanguageLessonPrompt( node: GraphNode, edges: GraphEdge[], language: string, + langConfig?: LanguageConfig | null, ): string { - const capitalizedLanguage = - LANGUAGE_DISPLAY_NAMES[language.toLowerCase()] ?? - language.charAt(0).toUpperCase() + language.slice(1); + const capitalizedLanguage = getLanguageDisplayName(language, langConfig); - const concepts = detectLanguageConcepts(node, language); + const concepts = detectLanguageConcepts(node, language, langConfig); const relationships = edges .map((edge) => { diff --git a/understand-anything-plugin/packages/core/src/index.ts b/understand-anything-plugin/packages/core/src/index.ts index 7615d6d..6438720 100644 --- a/understand-anything-plugin/packages/core/src/index.ts +++ b/understand-anything-plugin/packages/core/src/index.ts @@ -36,6 +36,20 @@ export { type LanguageLessonResult, } from "./analyzer/language-lesson.js"; export { PluginRegistry } from "./plugins/registry.js"; +export { + LanguageRegistry, + FrameworkRegistry, + builtinLanguageConfigs, + builtinFrameworkConfigs, + LanguageConfigSchema, + FrameworkConfigSchema, +} from "./languages/index.js"; +export type { + LanguageConfig, + FrameworkConfig, + TreeSitterConfig, + FilePatternConfig, +} from "./languages/index.js"; export { parsePluginConfig, serializePluginConfig, diff --git a/understand-anything-plugin/packages/core/src/languages/configs/cpp.ts b/understand-anything-plugin/packages/core/src/languages/configs/cpp.ts new file mode 100644 index 0000000..e4aed53 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/cpp.ts @@ -0,0 +1,25 @@ +import type { LanguageConfig } from "../types.js"; + +export const cppConfig = { + id: "cpp", + displayName: "C/C++", + extensions: [".cpp", ".cc", ".cxx", ".c", ".h", ".hpp", ".hxx"], + concepts: [ + "templates", + "RAII", + "smart pointers", + "move semantics", + "operator overloading", + "virtual functions", + "namespaces", + "constexpr", + "lambda expressions", + "STL containers", + ], + filePatterns: { + entryPoints: ["main.cpp", "main.c", "src/main.cpp"], + barrels: [], + tests: ["*_test.cpp", "*_test.cc", "test_*.cpp"], + config: ["CMakeLists.txt", "Makefile", "meson.build"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/csharp.ts b/understand-anything-plugin/packages/core/src/languages/configs/csharp.ts new file mode 100644 index 0000000..ba2a864 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/csharp.ts @@ -0,0 +1,25 @@ +import type { LanguageConfig } from "../types.js"; + +export const csharpConfig = { + id: "csharp", + displayName: "C#", + extensions: [".cs"], + concepts: [ + "LINQ", + "async/await", + "generics", + "properties", + "delegates and events", + "attributes", + "nullable reference types", + "pattern matching", + "records", + "dependency injection", + ], + filePatterns: { + entryPoints: ["Program.cs", "**/Program.cs"], + barrels: [], + tests: ["*Tests.cs", "*Test.cs"], + config: ["*.csproj", "*.sln"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/go.ts b/understand-anything-plugin/packages/core/src/languages/configs/go.ts new file mode 100644 index 0000000..3d0f2a6 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/go.ts @@ -0,0 +1,24 @@ +import type { LanguageConfig } from "../types.js"; + +export const goConfig = { + id: "go", + displayName: "Go", + extensions: [".go"], + concepts: [ + "goroutines", + "channels", + "interfaces", + "struct embedding", + "error handling patterns", + "defer/panic/recover", + "slices", + "pointers", + "concurrency patterns", + ], + filePatterns: { + entryPoints: ["main.go", "cmd/*/main.go"], + barrels: [], + tests: ["*_test.go"], + config: ["go.mod", "go.sum"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/index.ts b/understand-anything-plugin/packages/core/src/languages/configs/index.ts new file mode 100644 index 0000000..f0a7676 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/index.ts @@ -0,0 +1,43 @@ +import type { LanguageConfig } from "../types.js"; +import { typescriptConfig } from "./typescript.js"; +import { javascriptConfig } from "./javascript.js"; +import { pythonConfig } from "./python.js"; +import { goConfig } from "./go.js"; +import { rustConfig } from "./rust.js"; +import { javaConfig } from "./java.js"; +import { rubyConfig } from "./ruby.js"; +import { phpConfig } from "./php.js"; +import { swiftConfig } from "./swift.js"; +import { kotlinConfig } from "./kotlin.js"; +import { cppConfig } from "./cpp.js"; +import { csharpConfig } from "./csharp.js"; + +export const builtinLanguageConfigs: LanguageConfig[] = [ + typescriptConfig, + javascriptConfig, + pythonConfig, + goConfig, + rustConfig, + javaConfig, + rubyConfig, + phpConfig, + swiftConfig, + kotlinConfig, + cppConfig, + csharpConfig, +]; + +export { + typescriptConfig, + javascriptConfig, + pythonConfig, + goConfig, + rustConfig, + javaConfig, + rubyConfig, + phpConfig, + swiftConfig, + kotlinConfig, + cppConfig, + csharpConfig, +}; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/java.ts b/understand-anything-plugin/packages/core/src/languages/configs/java.ts new file mode 100644 index 0000000..cc62fa4 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/java.ts @@ -0,0 +1,29 @@ +import type { LanguageConfig } from "../types.js"; + +export const javaConfig = { + id: "java", + displayName: "Java", + extensions: [".java"], + concepts: [ + "generics", + "annotations", + "interfaces", + "abstract classes", + "streams API", + "lambdas", + "sealed classes", + "records", + "dependency injection", + "checked exceptions", + ], + filePatterns: { + entryPoints: [ + "**/Application.java", + "**/Main.java", + "src/main/java/**/App.java", + ], + barrels: [], + tests: ["*Test.java", "*Tests.java", "*IT.java"], + config: ["pom.xml", "build.gradle", "build.gradle.kts"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/javascript.ts b/understand-anything-plugin/packages/core/src/languages/configs/javascript.ts new file mode 100644 index 0000000..9d29682 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/javascript.ts @@ -0,0 +1,43 @@ +import type { LanguageConfig } from "../types.js"; + +export const javascriptConfig = { + id: "javascript", + displayName: "JavaScript", + extensions: [".js", ".jsx", ".mjs", ".cjs"], + treeSitter: { + wasmPackage: "tree-sitter-javascript", + wasmFile: "tree-sitter-javascript.wasm", + nodeTypes: { + function: [ + "function_declaration", + "arrow_function", + "function_expression", + "method_definition", + ], + class: ["class_declaration"], + import: ["import_statement"], + export: ["export_statement"], + call: ["call_expression"], + string: ["string", "string_fragment"], + parameter: ["formal_parameters"], + }, + }, + concepts: [ + "closures", + "prototypes", + "promises", + "async/await", + "event loop", + "destructuring", + "spread operator", + "proxies", + "generators", + "modules (ESM/CJS)", + ], + filePatterns: { + entryPoints: ["index.js", "src/index.js", "main.js"], + barrels: ["index.js"], + tests: ["*.test.js", "*.spec.js"], + config: ["package.json", "jsconfig.json"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/kotlin.ts b/understand-anything-plugin/packages/core/src/languages/configs/kotlin.ts new file mode 100644 index 0000000..f02dc79 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/kotlin.ts @@ -0,0 +1,25 @@ +import type { LanguageConfig } from "../types.js"; + +export const kotlinConfig = { + id: "kotlin", + displayName: "Kotlin", + extensions: [".kt", ".kts"], + concepts: [ + "coroutines", + "data classes", + "sealed classes", + "extension functions", + "null safety", + "delegation", + "DSL builders", + "inline functions", + "companion objects", + "flow", + ], + filePatterns: { + entryPoints: ["**/Application.kt", "**/Main.kt"], + barrels: [], + tests: ["*Test.kt", "*Tests.kt"], + config: ["build.gradle.kts", "build.gradle"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/php.ts b/understand-anything-plugin/packages/core/src/languages/configs/php.ts new file mode 100644 index 0000000..2dab065 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/php.ts @@ -0,0 +1,25 @@ +import type { LanguageConfig } from "../types.js"; + +export const phpConfig = { + id: "php", + displayName: "PHP", + extensions: [".php"], + concepts: [ + "namespaces", + "traits", + "type declarations", + "attributes", + "enums", + "fibers", + "closures", + "magic methods", + "dependency injection", + "middleware", + ], + filePatterns: { + entryPoints: ["index.php", "public/index.php", "artisan"], + barrels: [], + tests: ["*Test.php", "tests/**/*.php"], + config: ["composer.json", "php.ini"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/python.ts b/understand-anything-plugin/packages/core/src/languages/configs/python.ts new file mode 100644 index 0000000..f5fa0b0 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/python.ts @@ -0,0 +1,40 @@ +import type { LanguageConfig } from "../types.js"; + +export const pythonConfig = { + id: "python", + displayName: "Python", + extensions: [".py", ".pyi"], + concepts: [ + "decorators", + "list comprehensions", + "generators", + "context managers", + "type hints", + "dunder methods", + "metaclasses", + "dataclasses", + "async/await", + "descriptors", + "protocols", + ], + filePatterns: { + entryPoints: [ + "main.py", + "manage.py", + "app.py", + "wsgi.py", + "asgi.py", + "run.py", + "__main__.py", + ], + barrels: ["__init__.py"], + tests: ["test_*.py", "*_test.py", "conftest.py"], + config: [ + "pyproject.toml", + "setup.py", + "setup.cfg", + "requirements.txt", + "Pipfile", + ], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/ruby.ts b/understand-anything-plugin/packages/core/src/languages/configs/ruby.ts new file mode 100644 index 0000000..f3ed999 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/ruby.ts @@ -0,0 +1,24 @@ +import type { LanguageConfig } from "../types.js"; + +export const rubyConfig = { + id: "ruby", + displayName: "Ruby", + extensions: [".rb", ".rake"], + concepts: [ + "blocks and procs", + "mixins", + "metaprogramming", + "duck typing", + "DSLs", + "monkey patching", + "symbols", + "method_missing", + "open classes", + ], + filePatterns: { + entryPoints: ["config.ru", "app.rb"], + barrels: [], + tests: ["*_test.rb", "*_spec.rb", "spec_helper.rb"], + config: ["Gemfile", "Rakefile"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/rust.ts b/understand-anything-plugin/packages/core/src/languages/configs/rust.ts new file mode 100644 index 0000000..3a6a4bb --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/rust.ts @@ -0,0 +1,27 @@ +import type { LanguageConfig } from "../types.js"; + +export const rustConfig = { + id: "rust", + displayName: "Rust", + extensions: [".rs"], + concepts: [ + "ownership", + "borrowing", + "lifetimes", + "traits", + "pattern matching", + "enums with data", + "error handling (Result/Option)", + "macros", + "async/await", + "unsafe blocks", + "generics", + "closures", + ], + filePatterns: { + entryPoints: ["src/main.rs", "src/lib.rs"], + barrels: ["mod.rs", "lib.rs"], + tests: ["tests/*.rs"], + config: ["Cargo.toml"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/swift.ts b/understand-anything-plugin/packages/core/src/languages/configs/swift.ts new file mode 100644 index 0000000..af0977e --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/swift.ts @@ -0,0 +1,25 @@ +import type { LanguageConfig } from "../types.js"; + +export const swiftConfig = { + id: "swift", + displayName: "Swift", + extensions: [".swift"], + concepts: [ + "optionals", + "protocols", + "extensions", + "generics", + "closures", + "property wrappers", + "result builders", + "actors", + "structured concurrency", + "value types vs reference types", + ], + filePatterns: { + entryPoints: ["Sources/*/main.swift", "App.swift", "AppDelegate.swift"], + barrels: [], + tests: ["*Tests.swift", "Tests/**/*.swift"], + config: ["Package.swift"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/typescript.ts b/understand-anything-plugin/packages/core/src/languages/configs/typescript.ts new file mode 100644 index 0000000..ffb093d --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/typescript.ts @@ -0,0 +1,48 @@ +import type { LanguageConfig } from "../types.js"; + +export const typescriptConfig = { + id: "typescript", + displayName: "TypeScript", + extensions: [".ts", ".tsx"], + treeSitter: { + wasmPackage: "tree-sitter-typescript", + wasmFile: "tree-sitter-typescript.wasm", + nodeTypes: { + function: [ + "function_declaration", + "arrow_function", + "function_expression", + "method_definition", + ], + class: ["class_declaration"], + import: ["import_statement"], + export: ["export_statement"], + call: ["call_expression"], + string: ["string", "string_fragment"], + parameter: [ + "formal_parameters", + "required_parameter", + "optional_parameter", + ], + }, + }, + concepts: [ + "generics", + "type guards", + "discriminated unions", + "utility types", + "decorators", + "enums", + "interfaces", + "type inference", + "mapped types", + "conditional types", + "template literal types", + ], + filePatterns: { + entryPoints: ["src/index.ts", "src/main.ts", "src/App.tsx", "index.ts"], + barrels: ["index.ts"], + tests: ["*.test.ts", "*.spec.ts", "*.test.tsx"], + config: ["tsconfig.json"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/framework-registry.ts b/understand-anything-plugin/packages/core/src/languages/framework-registry.ts new file mode 100644 index 0000000..5ec6d1d --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/framework-registry.ts @@ -0,0 +1,80 @@ +import { FrameworkConfigSchema } from "./types.js"; +import type { FrameworkConfig } from "./types.js"; +import { builtinFrameworkConfigs } from "./frameworks/index.js"; + +/** + * Registry for framework configurations. Provides detection of frameworks + * from manifest file contents and lookup by id or language. + */ +export class FrameworkRegistry { + private byId = new Map(); + private byLanguage = new Map(); + + register(config: FrameworkConfig): void { + const parsed = FrameworkConfigSchema.parse(config); + this.byId.set(parsed.id, parsed); + + const existing = this.byLanguage.get(parsed.language) ?? []; + existing.push(parsed); + this.byLanguage.set(parsed.language, existing); + } + + getById(id: string): FrameworkConfig | null { + return this.byId.get(id) ?? null; + } + + getForLanguage(langId: string): FrameworkConfig[] { + return this.byLanguage.get(langId) ?? []; + } + + getAllFrameworks(): FrameworkConfig[] { + return [...this.byId.values()]; + } + + /** + * Detect frameworks from manifest file contents. + * @param manifests - Map of filename to file content (e.g., { "requirements.txt": "django==4.2\n..." }) + * @returns Array of detected FrameworkConfig objects + */ + detectFrameworks(manifests: Record): FrameworkConfig[] { + const detected = new Set(); + const results: FrameworkConfig[] = []; + + for (const config of this.byId.values()) { + if (detected.has(config.id)) continue; + + for (const manifestFile of config.manifestFiles) { + // Match manifest entries by filename (basename match) + const content = Object.entries(manifests).find( + ([key]) => key === manifestFile || key.endsWith(`/${manifestFile}`), + )?.[1]; + + if (!content) continue; + + const contentLower = content.toLowerCase(); + const found = config.detectionKeywords.some((keyword) => + contentLower.includes(keyword.toLowerCase()), + ); + + if (found) { + detected.add(config.id); + results.push(config); + break; + } + } + } + + return results; + } + + /** + * Create a registry pre-populated with all built-in framework configs. + */ + static createDefault(): FrameworkRegistry { + const registry = new FrameworkRegistry(); + for (const config of builtinFrameworkConfigs) { + registry.register(config); + } + return registry; + } +} diff --git a/understand-anything-plugin/packages/core/src/languages/frameworks/django.ts b/understand-anything-plugin/packages/core/src/languages/frameworks/django.ts new file mode 100644 index 0000000..824b02c --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/frameworks/django.ts @@ -0,0 +1,36 @@ +import type { FrameworkConfig } from "../types.js"; + +export const djangoConfig = { + id: "django", + displayName: "Django", + language: "python", + detectionKeywords: [ + "django", + "djangorestframework", + "django-rest-framework", + "django-cors-headers", + "django-filter", + ], + manifestFiles: [ + "requirements.txt", + "pyproject.toml", + "setup.py", + "setup.cfg", + "Pipfile", + ], + promptSnippetPath: "./frameworks/django.md", + entryPoints: ["manage.py", "wsgi.py", "asgi.py"], + layerHints: { + views: "api", + models: "data", + serializers: "api", + urls: "api", + templates: "ui", + migrations: "data", + management: "config", + signals: "service", + admin: "config", + forms: "ui", + templatetags: "utility", + }, +} satisfies FrameworkConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/frameworks/express.ts b/understand-anything-plugin/packages/core/src/languages/frameworks/express.ts new file mode 100644 index 0000000..af82dca --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/frameworks/express.ts @@ -0,0 +1,26 @@ +import type { FrameworkConfig } from "../types.js"; + +export const expressConfig = { + id: "express", + displayName: "Express", + language: "javascript", + detectionKeywords: ["express", "express-validator", "cors", "body-parser"], + manifestFiles: ["package.json"], + promptSnippetPath: "./frameworks/express.md", + entryPoints: [ + "src/index.js", + "src/app.js", + "server.js", + "app.js", + "src/index.ts", + "src/app.ts", + ], + layerHints: { + routes: "api", + controllers: "service", + models: "data", + middleware: "middleware", + services: "service", + db: "data", + }, +} satisfies FrameworkConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/frameworks/fastapi.ts b/understand-anything-plugin/packages/core/src/languages/frameworks/fastapi.ts new file mode 100644 index 0000000..b62c871 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/frameworks/fastapi.ts @@ -0,0 +1,25 @@ +import type { FrameworkConfig } from "../types.js"; + +export const fastapiConfig = { + id: "fastapi", + displayName: "FastAPI", + language: "python", + detectionKeywords: ["fastapi", "uvicorn", "starlette"], + manifestFiles: [ + "requirements.txt", + "pyproject.toml", + "setup.py", + "setup.cfg", + "Pipfile", + ], + promptSnippetPath: "./frameworks/fastapi.md", + entryPoints: ["main.py", "app.py"], + layerHints: { + routers: "api", + schemas: "types", + models: "data", + dependencies: "service", + crud: "service", + api: "api", + }, +} satisfies FrameworkConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/frameworks/flask.ts b/understand-anything-plugin/packages/core/src/languages/frameworks/flask.ts new file mode 100644 index 0000000..e68fcc6 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/frameworks/flask.ts @@ -0,0 +1,31 @@ +import type { FrameworkConfig } from "../types.js"; + +export const flaskConfig = { + id: "flask", + displayName: "Flask", + language: "python", + detectionKeywords: [ + "flask", + "flask-restful", + "flask-sqlalchemy", + "flask-marshmallow", + "flask-wtf", + ], + manifestFiles: [ + "requirements.txt", + "pyproject.toml", + "setup.py", + "setup.cfg", + "Pipfile", + ], + promptSnippetPath: "./frameworks/flask.md", + entryPoints: ["app.py", "run.py", "wsgi.py"], + layerHints: { + blueprints: "api", + views: "api", + models: "data", + forms: "ui", + templates: "ui", + extensions: "config", + }, +} satisfies FrameworkConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/frameworks/gin.ts b/understand-anything-plugin/packages/core/src/languages/frameworks/gin.ts new file mode 100644 index 0000000..eae684a --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/frameworks/gin.ts @@ -0,0 +1,19 @@ +import type { FrameworkConfig } from "../types.js"; + +export const ginConfig = { + id: "gin", + displayName: "Gin", + language: "go", + detectionKeywords: ["github.com/gin-gonic/gin"], + manifestFiles: ["go.mod"], + promptSnippetPath: "./frameworks/gin.md", + entryPoints: ["main.go", "cmd/server/main.go"], + layerHints: { + handlers: "api", + routes: "api", + models: "data", + middleware: "middleware", + services: "service", + repository: "data", + }, +} satisfies FrameworkConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/frameworks/index.ts b/understand-anything-plugin/packages/core/src/languages/frameworks/index.ts new file mode 100644 index 0000000..0b26b60 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/frameworks/index.ts @@ -0,0 +1,38 @@ +import type { FrameworkConfig } from "../types.js"; + +import { djangoConfig } from "./django.js"; +import { fastapiConfig } from "./fastapi.js"; +import { flaskConfig } from "./flask.js"; +import { reactConfig } from "./react.js"; +import { nextjsConfig } from "./nextjs.js"; +import { expressConfig } from "./express.js"; +import { vueConfig } from "./vue.js"; +import { springConfig } from "./spring.js"; +import { railsConfig } from "./rails.js"; +import { ginConfig } from "./gin.js"; + +export const builtinFrameworkConfigs: FrameworkConfig[] = [ + djangoConfig, + fastapiConfig, + flaskConfig, + reactConfig, + nextjsConfig, + expressConfig, + vueConfig, + springConfig, + railsConfig, + ginConfig, +]; + +export { + djangoConfig, + fastapiConfig, + flaskConfig, + reactConfig, + nextjsConfig, + expressConfig, + vueConfig, + springConfig, + railsConfig, + ginConfig, +}; diff --git a/understand-anything-plugin/packages/core/src/languages/frameworks/nextjs.ts b/understand-anything-plugin/packages/core/src/languages/frameworks/nextjs.ts new file mode 100644 index 0000000..97fefad --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/frameworks/nextjs.ts @@ -0,0 +1,23 @@ +import type { FrameworkConfig } from "../types.js"; + +export const nextjsConfig = { + id: "nextjs", + displayName: "Next.js", + language: "typescript", + detectionKeywords: ["next", "@next/font", "@next/image"], + manifestFiles: ["package.json"], + promptSnippetPath: "./frameworks/nextjs.md", + entryPoints: [ + "src/app/layout.tsx", + "pages/_app.tsx", + "src/pages/_app.tsx", + ], + layerHints: { + app: "ui", + pages: "ui", + api: "api", + components: "ui", + lib: "service", + middleware: "middleware", + }, +} satisfies FrameworkConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/frameworks/rails.ts b/understand-anything-plugin/packages/core/src/languages/frameworks/rails.ts new file mode 100644 index 0000000..6ba47ab --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/frameworks/rails.ts @@ -0,0 +1,28 @@ +import type { FrameworkConfig } from "../types.js"; + +export const railsConfig = { + id: "rails", + displayName: "Ruby on Rails", + language: "ruby", + detectionKeywords: [ + "rails", + "railties", + "actionpack", + "activerecord", + "actionview", + ], + manifestFiles: ["Gemfile"], + promptSnippetPath: "./frameworks/rails.md", + entryPoints: ["config.ru", "bin/rails"], + layerHints: { + controllers: "api", + models: "data", + views: "ui", + helpers: "utility", + mailers: "service", + jobs: "service", + channels: "service", + middleware: "middleware", + lib: "service", + }, +} satisfies FrameworkConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/frameworks/react.ts b/understand-anything-plugin/packages/core/src/languages/frameworks/react.ts new file mode 100644 index 0000000..7429fd3 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/frameworks/react.ts @@ -0,0 +1,19 @@ +import type { FrameworkConfig } from "../types.js"; + +export const reactConfig = { + id: "react", + displayName: "React", + language: "typescript", + detectionKeywords: ["react", "react-dom", "@types/react"], + manifestFiles: ["package.json"], + promptSnippetPath: "./frameworks/react.md", + entryPoints: ["src/App.tsx", "src/App.jsx", "src/index.tsx", "src/main.tsx"], + layerHints: { + components: "ui", + hooks: "service", + pages: "ui", + contexts: "service", + utils: "utility", + lib: "service", + }, +} satisfies FrameworkConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/frameworks/spring.ts b/understand-anything-plugin/packages/core/src/languages/frameworks/spring.ts new file mode 100644 index 0000000..7b7b56d --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/frameworks/spring.ts @@ -0,0 +1,27 @@ +import type { FrameworkConfig } from "../types.js"; + +export const springConfig = { + id: "spring", + displayName: "Spring Boot", + language: "java", + detectionKeywords: [ + "spring-boot", + "spring-boot-starter", + "spring-web", + "spring-data", + "org.springframework", + ], + manifestFiles: ["pom.xml", "build.gradle", "build.gradle.kts"], + promptSnippetPath: "./frameworks/spring.md", + entryPoints: ["**/Application.java", "**/App.java"], + layerHints: { + controller: "api", + service: "service", + repository: "data", + model: "data", + entity: "data", + config: "config", + dto: "types", + security: "middleware", + }, +} satisfies FrameworkConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/frameworks/vue.ts b/understand-anything-plugin/packages/core/src/languages/frameworks/vue.ts new file mode 100644 index 0000000..94a8f01 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/frameworks/vue.ts @@ -0,0 +1,19 @@ +import type { FrameworkConfig } from "../types.js"; + +export const vueConfig = { + id: "vue", + displayName: "Vue", + language: "typescript", + detectionKeywords: ["vue", "@vue/cli-service", "nuxt", "vite-plugin-vue"], + manifestFiles: ["package.json"], + promptSnippetPath: "./frameworks/vue.md", + entryPoints: ["src/main.ts", "src/App.vue", "src/main.js"], + layerHints: { + components: "ui", + views: "ui", + store: "service", + composables: "service", + router: "config", + plugins: "config", + }, +} satisfies FrameworkConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/index.ts b/understand-anything-plugin/packages/core/src/languages/index.ts new file mode 100644 index 0000000..7fab61b --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/index.ts @@ -0,0 +1,22 @@ +// Types +export type { + LanguageConfig, + TreeSitterConfig, + FilePatternConfig, + FrameworkConfig, +} from "./types.js"; + +export { + LanguageConfigSchema, + TreeSitterConfigSchema, + FilePatternConfigSchema, + FrameworkConfigSchema, +} from "./types.js"; + +// Registries +export { LanguageRegistry } from "./language-registry.js"; +export { FrameworkRegistry } from "./framework-registry.js"; + +// Built-in configs +export { builtinLanguageConfigs } from "./configs/index.js"; +export { builtinFrameworkConfigs } from "./frameworks/index.js"; diff --git a/understand-anything-plugin/packages/core/src/languages/language-registry.ts b/understand-anything-plugin/packages/core/src/languages/language-registry.ts new file mode 100644 index 0000000..0afef19 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/language-registry.ts @@ -0,0 +1,53 @@ +import { LanguageConfigSchema } from "./types.js"; +import type { LanguageConfig } from "./types.js"; +import { builtinLanguageConfigs } from "./configs/index.js"; + +/** + * Registry for language configurations. Maps language ids and file extensions + * to their corresponding LanguageConfig objects. + */ +export class LanguageRegistry { + private byId = new Map(); + private byExtension = new Map(); + + register(config: LanguageConfig): void { + const parsed = LanguageConfigSchema.parse(config); + this.byId.set(parsed.id, parsed); + for (const ext of parsed.extensions) { + // Normalize: strip leading dot if present for lookup consistency + const key = ext.startsWith(".") ? ext : `.${ext}`; + this.byExtension.set(key, parsed); + } + } + + getById(id: string): LanguageConfig | null { + return this.byId.get(id) ?? null; + } + + getByExtension(ext: string): LanguageConfig | null { + const key = ext.startsWith(".") ? ext : `.${ext}`; + return this.byExtension.get(key) ?? null; + } + + getForFile(filePath: string): LanguageConfig | null { + const lastDot = filePath.lastIndexOf("."); + if (lastDot === -1) return null; + const ext = filePath.slice(lastDot).toLowerCase(); + return this.getByExtension(ext); + } + + getAllLanguages(): LanguageConfig[] { + return [...this.byId.values()]; + } + + /** + * Create a registry pre-populated with all built-in language configs. + */ + static createDefault(): LanguageRegistry { + const registry = new LanguageRegistry(); + for (const config of builtinLanguageConfigs) { + registry.register(config); + } + return registry; + } +} diff --git a/understand-anything-plugin/packages/core/src/languages/types.ts b/understand-anything-plugin/packages/core/src/languages/types.ts new file mode 100644 index 0000000..03953a2 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/types.ts @@ -0,0 +1,54 @@ +import { z } from "zod"; + +// Tree-sitter node type mappings for a language +export const TreeSitterConfigSchema = z.object({ + wasmPackage: 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()), + call: z.array(z.string()), + string: z.array(z.string()), + parameter: z.array(z.string()), + }), +}); + +export type TreeSitterConfig = z.infer; + +// File pattern conventions for a language +export const FilePatternConfigSchema = z.object({ + entryPoints: z.array(z.string()), + barrels: z.array(z.string()), + tests: z.array(z.string()), + config: z.array(z.string()), +}); + +export type FilePatternConfig = z.infer; + +// Complete language configuration +export const LanguageConfigSchema = z.object({ + id: z.string().min(1), + displayName: z.string().min(1), + extensions: z.array(z.string()).min(1), + treeSitter: TreeSitterConfigSchema.optional(), + concepts: z.array(z.string()), + filePatterns: FilePatternConfigSchema, +}); + +export type LanguageConfig = z.infer; + +// Framework configuration +export const FrameworkConfigSchema = z.object({ + id: z.string().min(1), + displayName: z.string().min(1), + language: z.string().min(1), + detectionKeywords: z.array(z.string()).min(1), + manifestFiles: z.array(z.string()).min(1), + promptSnippetPath: z.string().min(1), + entryPoints: z.array(z.string()).optional(), + layerHints: z.record(z.string(), z.string()).optional(), +}); + +export type FrameworkConfig = z.infer; diff --git a/understand-anything-plugin/packages/core/src/plugins/registry.ts b/understand-anything-plugin/packages/core/src/plugins/registry.ts index 324a802..71233fe 100644 --- a/understand-anything-plugin/packages/core/src/plugins/registry.ts +++ b/understand-anything-plugin/packages/core/src/plugins/registry.ts @@ -1,30 +1,21 @@ import type { AnalyzerPlugin, StructuralAnalysis, ImportResolution } from "../types.js"; - -const EXTENSION_TO_LANGUAGE: Record = { - ts: "typescript", - tsx: "typescript", - js: "javascript", - jsx: "javascript", - py: "python", - go: "go", - rs: "rust", - rb: "ruby", - java: "java", - kt: "kotlin", - cs: "csharp", - cpp: "cpp", - c: "c", - swift: "swift", - php: "php", -}; +import { LanguageRegistry } from "../languages/language-registry.js"; /** * Registry for analyzer plugins. Maps languages to plugins and provides * a unified interface for analyzing files across languages. + * + * Uses LanguageRegistry for extension-to-language mapping instead of + * a hardcoded lookup table. */ export class PluginRegistry { private plugins: AnalyzerPlugin[] = []; private languageMap = new Map(); + private languageRegistry: LanguageRegistry; + + constructor(languageRegistry?: LanguageRegistry) { + this.languageRegistry = languageRegistry ?? LanguageRegistry.createDefault(); + } register(plugin: AnalyzerPlugin): void { this.plugins.push(plugin); @@ -50,11 +41,16 @@ export class PluginRegistry { } getPluginForFile(filePath: string): AnalyzerPlugin | null { - const ext = filePath.split(".").pop()?.toLowerCase(); - if (!ext) return null; - const language = EXTENSION_TO_LANGUAGE[ext]; - if (!language) return null; - return this.getPluginForLanguage(language); + const langConfig = this.languageRegistry.getForFile(filePath); + if (!langConfig) return null; + return this.getPluginForLanguage(langConfig.id); + } + + /** + * Get the language id for a file path using the language registry. + */ + getLanguageForFile(filePath: string): string | null { + return this.languageRegistry.getForFile(filePath)?.id ?? null; } analyzeFile(filePath: string, content: string): StructuralAnalysis | null { diff --git a/understand-anything-plugin/packages/core/src/plugins/tree-sitter-plugin.ts b/understand-anything-plugin/packages/core/src/plugins/tree-sitter-plugin.ts index 81897c7..cf774f9 100644 --- a/understand-anything-plugin/packages/core/src/plugins/tree-sitter-plugin.ts +++ b/understand-anything-plugin/packages/core/src/plugins/tree-sitter-plugin.ts @@ -6,6 +6,7 @@ import type { ImportResolution, CallGraphEntry, } from "../types.js"; +import type { LanguageConfig } from "../languages/types.js"; // web-tree-sitter uses CJS internally; we need createRequire for .wasm resolution const require = createRequire(import.meta.url); @@ -14,23 +15,6 @@ type TreeSitterParser = import("web-tree-sitter").Parser; type TreeSitterLanguage = import("web-tree-sitter").Language; type TreeSitterNode = import("web-tree-sitter").Node; -function languageKeyFromPath(filePath: string): string { - const ext = extname(filePath).toLowerCase(); - switch (ext) { - case ".ts": - return "typescript"; - case ".tsx": - return "tsx"; - case ".js": - case ".mjs": - case ".cjs": - case ".jsx": - return "javascript"; - default: - throw new Error(`Unsupported file extension: ${ext}`); - } -} - /** * Recursively traverse an AST tree, calling the visitor for each node. */ @@ -155,17 +139,81 @@ function extractImportSpecifiers( return specifiers; } +/** + * Config-driven tree-sitter plugin. + * + * Accepts LanguageConfig objects to determine which languages to support + * and how to load their WASM grammars. Currently provides deep structural + * analysis for TypeScript/JavaScript; other languages with tree-sitter configs + * get basic function/class/import extraction. + * + * Languages without tree-sitter configs are gracefully skipped (the LLM + * agent handles analysis for those). + */ export class TreeSitterPlugin implements AnalyzerPlugin { readonly name = "tree-sitter"; - readonly languages = ["typescript", "javascript"]; + readonly languages: string[]; + + private configs: LanguageConfig[]; // Pre-loaded parser constructor and languages (set by init()) private _ParserClass: | (new () => TreeSitterParser) | null = null; private _languages = new Map(); + private _extensionToLang = new Map(); private _initialized = false; + /** + * Create a TreeSitterPlugin with the given language configs. + * Only configs that have a `treeSitter` field will be loaded. + * If no configs are provided, defaults to TypeScript and JavaScript. + */ + constructor(configs?: LanguageConfig[]) { + if (configs) { + this.configs = configs.filter((c) => c.treeSitter); + } else { + // Default: TS/JS for backward compatibility + this.configs = []; + } + + // Derive supported languages and extension map from configs + const langs: string[] = []; + for (const config of this.configs) { + langs.push(config.id); + for (const ext of config.extensions) { + const key = ext.startsWith(".") ? ext : `.${ext}`; + this._extensionToLang.set(key, config.id); + } + } + + // Fallback for backward compat when no configs provided + if (langs.length === 0) { + langs.push("typescript", "javascript"); + this._extensionToLang.set(".ts", "typescript"); + this._extensionToLang.set(".tsx", "typescript"); + this._extensionToLang.set(".js", "javascript"); + this._extensionToLang.set(".mjs", "javascript"); + this._extensionToLang.set(".cjs", "javascript"); + this._extensionToLang.set(".jsx", "javascript"); + } + + this.languages = langs; + } + + private languageKeyFromPath(filePath: string): string { + const ext = extname(filePath).toLowerCase(); + + // Special case: .tsx needs its own grammar + if (ext === ".tsx") return "tsx"; + + const lang = this._extensionToLang.get(ext); + if (!lang) { + throw new Error(`Unsupported file extension: ${ext}`); + } + return lang; + } + /** * Initialize the plugin by loading the WASM module and all language grammars. * Must be called (and awaited) before any synchronous methods. @@ -180,26 +228,68 @@ export class TreeSitterPlugin implements AnalyzerPlugin { await ParserCls.init(); this._ParserClass = ParserCls as unknown as new () => TreeSitterParser; - // Pre-load all supported language grammars - const tsWasm = require.resolve( - "tree-sitter-typescript/tree-sitter-typescript.wasm", - ); - const tsxWasm = require.resolve( - "tree-sitter-typescript/tree-sitter-tsx.wasm", - ); - const jsWasm = require.resolve( - "tree-sitter-javascript/tree-sitter-javascript.wasm", - ); + if (this.configs.length > 0) { + // Load grammars from configs + const loadPromises: Promise[] = []; - const [tsLang, tsxLang, jsLang] = await Promise.all([ - LanguageCls.load(tsWasm), - LanguageCls.load(tsxWasm), - LanguageCls.load(jsWasm), - ]); + for (const config of this.configs) { + if (!config.treeSitter) continue; + + const loadGrammar = async () => { + try { + const wasmPath = require.resolve( + `${config.treeSitter!.wasmPackage}/${config.treeSitter!.wasmFile}`, + ); + const lang = await LanguageCls.load(wasmPath); + this._languages.set(config.id, lang); + + // Special handling for TypeScript: also load TSX grammar + if (config.id === "typescript") { + try { + const tsxWasm = require.resolve( + `${config.treeSitter!.wasmPackage}/tree-sitter-tsx.wasm`, + ); + const tsxLang = await LanguageCls.load(tsxWasm); + this._languages.set("tsx", tsxLang); + } catch { + // TSX grammar not available; .tsx files will fall back to TS grammar + } + } + } catch { + // Grammar not available — this language will be skipped gracefully + console.debug?.( + `tree-sitter: Could not load grammar for ${config.id}, skipping structural analysis`, + ); + } + }; + + loadPromises.push(loadGrammar()); + } + + await Promise.all(loadPromises); + } else { + // Legacy fallback: load TS/JS grammars directly + const tsWasm = require.resolve( + "tree-sitter-typescript/tree-sitter-typescript.wasm", + ); + const tsxWasm = require.resolve( + "tree-sitter-typescript/tree-sitter-tsx.wasm", + ); + const jsWasm = require.resolve( + "tree-sitter-javascript/tree-sitter-javascript.wasm", + ); + + const [tsLang, tsxLang, jsLang] = await Promise.all([ + LanguageCls.load(tsWasm), + LanguageCls.load(tsxWasm), + LanguageCls.load(jsWasm), + ]); + + this._languages.set("typescript", tsLang); + this._languages.set("tsx", tsxLang); + this._languages.set("javascript", jsLang); + } - this._languages.set("typescript", tsLang); - this._languages.set("tsx", tsxLang); - this._languages.set("javascript", jsLang); this._initialized = true; } @@ -207,16 +297,17 @@ export class TreeSitterPlugin implements AnalyzerPlugin { * Create a parser set to the appropriate language for the given file. * This is synchronous because all languages are pre-loaded during init(). */ - private getParser(filePath: string): TreeSitterParser { + private getParser(filePath: string): TreeSitterParser | null { if (!this._initialized || !this._ParserClass) { throw new Error( "TreeSitterPlugin.init() must be called before use", ); } - const langKey = languageKeyFromPath(filePath); + const langKey = this.languageKeyFromPath(filePath); const lang = this._languages.get(langKey); if (!lang) { - throw new Error(`Language not loaded: ${langKey}`); + // Language grammar not loaded — graceful degradation + return null; } const parser = new this._ParserClass(); parser.setLanguage(lang); @@ -228,6 +319,10 @@ export class TreeSitterPlugin implements AnalyzerPlugin { content: string, ): StructuralAnalysis { const parser = this.getParser(filePath); + if (!parser) { + return { functions: [], classes: [], imports: [], exports: [] }; + } + const tree = parser.parse(content); if (!tree) { parser.delete(); @@ -290,6 +385,8 @@ export class TreeSitterPlugin implements AnalyzerPlugin { content: string, ): CallGraphEntry[] { const parser = this.getParser(filePath); + if (!parser) return []; + const tree = parser.parse(content); if (!tree) { parser.delete(); diff --git a/understand-anything-plugin/skills/understand/PYTHON-SUPPORT-CHANGES.md b/understand-anything-plugin/skills/understand/PYTHON-SUPPORT-CHANGES.md deleted file mode 100644 index 39939a1..0000000 --- a/understand-anything-plugin/skills/understand/PYTHON-SUPPORT-CHANGES.md +++ /dev/null @@ -1,208 +0,0 @@ -# Python Codebase Support — Change Review Guide - -This document explains every change made to add Python and Python framework support. It is written for a reviewer who wants to verify correctness, spot regressions, and understand the rationale for each decision. - ---- - -## Background: What Was Wrong - -The tool worked well for TypeScript/JavaScript codebases. For Python codebases it would: -- Fail to detect frameworks (Django, FastAPI, Flask) — `frameworks: []` always -- Miss common Python entry points (`manage.py`, `app.py`, `wsgi.py`) — defaulting to no entry point -- Score Python entry points much lower than TS equivalents in the tour builder (4 Python patterns vs 8 JS/TS patterns) -- Miss Python's `__init__.py` as a barrel/entry-point equivalent (only `index.ts`/`index.js` were recognized) -- Have no layer guidance for FastAPI or Flask (only Django had a brief mention) -- Ignore `pyproject.toml` for project name extraction - -The bias was **only in agent prompts** (markdown files). The graph schema, dashboard, search engine, and core plugin architecture are language-agnostic and required no changes. - ---- - -## Files Changed - -### 1. `project-scanner-prompt.md` - -**What changed:** - -**Step 5 (Framework Detection)** — Extended the Python manifest reading from "confirms Python project" to actually detecting frameworks: -- `requirements.txt`: now reads line-by-line, strips version specifiers, and matches against a Python framework keyword list: `django`, `djangorestframework`, `fastapi`, `flask`, `sqlalchemy`, `alembic`, `celery`, `pydantic`, `uvicorn`, `gunicorn`, `aiohttp`, `tornado`, `starlette`, `pytest`, `hypothesis`, `channels` -- `pyproject.toml`: now parses `[project].dependencies` and `[tool.poetry.dependencies]`, applies the same keyword matching, and also checks for `[tool.pytest.ini_options]` (pytest) and `[tool.django]` (Django) -- `setup.py`, `setup.cfg`, `Pipfile`: now apply the same Python framework keyword matching - -**Step 7 (Project Name)** — Added `pyproject.toml` to the priority order between `go.mod` and directory name. Checks `[project].name` first, then `[tool.poetry].name`. - -**Why:** Without framework detection, `frameworks: []` is passed to every downstream agent. The framework-specific guidance injected in Phase 2 and Phase 4 of SKILL.md is only useful when `frameworks` is non-empty. - -**Regression risk:** None. The JS framework detection in `package.json` is unchanged. The Python additions are additive. - -**How to verify:** Run `/understand` on a Django project with `requirements.txt` containing `django`. Check that `scan-result.json` has `frameworks: ["Django"]`. - ---- - -### 2. `SKILL.md` - -**What changed:** - -**Phase 0 (entry point detection, line 57)** — Added Python entry points to the pattern list: -- Before: `src/index.ts`, `src/main.ts`, `src/App.tsx`, `main.py`, `main.go`, `src/main.rs`, `index.js` -- After: added `manage.py`, `app.py`, `wsgi.py`, `asgi.py`, `run.py`, `__main__.py` - -**Why:** A Django project's real entry point is `manage.py`. A FastAPI/Flask project uses `app.py` or `run.py`. Without these, `$ENTRY_POINT` is empty for most Python projects, and the tour builder gets no starting hint. - -**Phase 2 (file-analyzer framework guidance)** — Extended the inline framework hints: -- Django: added `serializers.py`, `signals.py`, `admin.py`, `migrations/` descriptions -- Added FastAPI: describes `@router` decorator files, Pydantic schemas, `Depends()` providers -- Added Flask: describes `@blueprint.route`, `blueprints/`, SQLAlchemy `models.py` -- Added addendum injection: if `Django` detected, reads `./django-analyzer-addendum.md` and appends to the file-analyzer prompt. If `FastAPI` or `Flask` detected, reads `./fastapi-analyzer-addendum.md` and appends. - -**Phase 4 (architecture-analyzer framework hints)** — Extended the inline layer hints: -- Django: added `serializers.py`, `signals.py`, `migrations/` → specific layers -- Added FastAPI: router files → API, Pydantic schemas → Types, `dependencies.py` → Service, DB files → Data -- Added Flask: blueprint route files → API, `models.py` → Data, `forms.py` → UI, `extensions.py` → Config -- Added addendum injection: same logic as Phase 2 - -**Regression risk:** Low. The addendum injection only triggers when those frameworks are in the detected list. The inline guidance additions are additive strings — they don't change the structure of the injected context. - -**How to verify:** -- Run on a FastAPI project: check `layers.json` has a `layer:types` or `layer:api` with Pydantic schema files assigned correctly -- Run on a TS project: check that no Django/FastAPI addendum content appears in the analysis (it shouldn't, since `frameworks` won't contain those values) - ---- - -### 3. `architecture-analyzer-prompt.md` - -**What changed:** - -**Directory pattern table** — Added Python-specific directory names: - -| Added | Pattern Label | Why | -|-------|---------------|-----| -| `migrations` | `data` | Django/Alembic migration directories hold schema history — Data Layer | -| `management`, `commands` | `config` | Django management command directories | -| `templatetags` | `utility` | Django custom template tag directories | -| `signals` | `service` | Signal handler modules — cross-cutting service logic | -| `serializers` | `api` | DRF serializer directories | - -**File-level pattern matching** — Three changes: -1. Added `test_*.py` to the test pattern (Python's `pytest` naming convention) -2. Added `__init__.py` at a directory root → `entry` pattern (Python package barrel equivalent of `index.ts`) -3. Added `manage.py` → `entry` and `wsgi.py`/`asgi.py` → `config` -4. Clarified `*.d.ts → types` with "(TypeScript declaration files only)" — making it explicit this is TS-specific so an LLM doesn't misapply it to Python - -**Why:** Without `__init__.py → entry`, the architecture analyzer would never recognize any Python file as an entry point via file-level patterns. The `hooks` pattern label was left as-is (it won't trigger on Python projects since they don't conventionally have a `hooks/` directory). - -**Note on Node.js script:** The architecture analyzer's structural analysis script is hardcoded to `node`. This is correct to leave as-is — the script processes the JSON graph structure (file nodes, import edges), not the source language of the codebase being analyzed. The script's input/output is always JSON regardless of whether the project is Python or TypeScript. - -**Regression risk:** Very low. Added rows to the directory pattern table and clarified file-level pattern descriptions. No existing patterns were removed or modified. - -**How to verify:** Run on a Django project. Check that `migrations/` directory files land in `layer:data` and that `manage.py` gets tagged `entry`. - ---- - -### 4. `tour-builder-prompt.md` - -**What changed:** - -**Entry point candidate scoring (Section C)** — Added Python entry points to the +3 filename list: -- Added: `manage.py`, `app.py`, `wsgi.py`, `asgi.py`, `run.py`, `__main__.py` - -Before this change, the entry point scoring had 8 TS/JS patterns vs 4 for all other languages. After: 8 TS/JS + 6 Python + 4 others. - -**Why:** The tour builder uses entry point scores to decide Step 1 of the tour. For a Django project where `manage.py` exists, it should score highly. Without this, the tour might start from a random high-fan-in utility file instead of the actual entry point. - -**The `languageLesson` example** was left as-is (TypeScript barrel files). This is just an illustrative example in the output format section — it does not affect how Python tours are generated. The language lessons section already lists Python-specific patterns (decorators, generators, context managers, metaclasses, protocols). - -**Regression risk:** None. The scoring list is additive. TS/JS entry points retain their +3 scores. - -**How to verify:** Run on a Django project. Check that `tour.json` starts from `manage.py` or `apps.py`/`wsgi.py` rather than a utility file. - ---- - -### 5. `file-analyzer-prompt.md` - -**What changed:** - -**Tags indicators — barrel/entry-point detection** — Extended the `index.ts` rule: -- Before: `Named index.ts at a directory root with re-exports = entry-point` -- After: Added `__init__.py` at a package root with imports or re-exports = `entry-point`, and `manage.py` = `entry-point` - -**Script execution example** — Added the Python equivalent command alongside the Node.js example. The base prompt already says "Choose the best language for this task — Node.js is recommended for TypeScript/JavaScript projects, Python for Python projects" (line 15), but the execution example only showed `node`. This created a contradiction. Now both are shown. - -**Regression risk:** None. Additive changes only. - -**How to verify:** Run on a Python project with a package structure. Check that `__init__.py` files at package roots get the `entry-point` or `barrel` tag rather than being treated as empty boilerplate files. - ---- - -## New Files Created - -### `django-analyzer-addendum.md` - -A detailed reference injected into the file-analyzer and architecture-analyzer when Django is detected. Contains: -- Canonical file roles table (15+ Django file types with appropriate tags) -- Edge patterns to look for (URL routing graph, signal wiring, ORM relationships, serializer→model binding) -- Layer assignment guide (7 layers: api, data, service, ui, middleware, config, test) -- Notable `languageLesson` patterns (fat models, ORM lazy evaluation, CBV mixins, signal anti-patterns, app isolation) - -**How it's injected:** SKILL.md reads this file and appends it to the base `file-analyzer-prompt.md` and `architecture-analyzer-prompt.md` content when `Django` appears in the detected frameworks list. - -### `fastapi-analyzer-addendum.md` - -A detailed reference for FastAPI and Flask projects, injected when either framework is detected. Contains two sections: - -**FastAPI section:** -- Canonical file roles (router files, Pydantic schemas, CRUD, dependencies, database session) -- Edge patterns (router inclusion chain, DI tree, Pydantic inheritance, CRUD→model binding) -- Layer assignment guide (7 layers) -- Notable `languageLesson` patterns (DI as composition, Pydantic validation, async vs sync, route order) - -**Flask section:** -- Canonical file roles (blueprints, application factory, WTForms, Marshmallow) -- Edge patterns (blueprint registration, extension coupling, before/after request hooks) -- Layer assignment guide -- Notable `languageLesson` patterns (factory pattern, blueprint modularity, extension `init_app` protocol) - -**How it's injected:** Same mechanism as the Django addendum — SKILL.md appends it when `FastAPI` or `Flask` is in the detected frameworks list. - ---- - -## What Was NOT Changed (And Why) - -| Component | Rationale | -|-----------|-----------| -| Graph schema (`types.ts`, `schema.ts`) | Already language-agnostic. All 18 edge types work for Python patterns. | -| `packages/core/src/plugins/tree-sitter-plugin.ts` | Still TS/JS only. Adding Python tree-sitter support is Phase 3 (separate PR). | -| `packages/core/src/plugins/registry.ts` | Extension map already has `.py → python`. A Python plugin will register here in Phase 3. | -| `packages/core/src/analyzer/language-lesson.ts` | Concept detection patterns. Phase 3 work. | -| Dashboard, search engine, skills | Already language-agnostic. | -| `tour-builder-prompt.md` language lessons example | The TypeScript barrel file example is illustrative only. Python tours will produce Python-specific `languageLesson` strings based on the language-lessons list (which already includes Python patterns). | -| Architecture analyzer `node` script execution | The script analyzes the graph JSON, not the source language. `node` is always correct here. | -| `hooks` directory pattern label | React-specific but harmless — Python projects don't conventionally have `hooks/` directories, so this label will never trigger on Python codebases. | - ---- - -## Testing Checklist for Reviewer - -For a Django project (e.g., a real Django app with `requirements.txt`): -- [ ] `scan-result.json` has `frameworks: ["Django"]` (or similar) -- [ ] `manage.py` is detected as `$ENTRY_POINT` in SKILL.md Phase 0 -- [ ] `manage.py` node gets tags including `entry-point` -- [ ] `urls.py` files get `api-handler`, `routing` tags -- [ ] `models.py` files get `data-model` tag -- [ ] `migrations/` directory files land in `layer:data` -- [ ] Tour Step 1 starts from `manage.py` or `wsgi.py` -- [ ] No TypeScript-specific guidance appears in the analysis output - -For a FastAPI project: -- [ ] `scan-result.json` has `frameworks: ["FastAPI"]` -- [ ] Router files get `api-handler`, `routing` tags -- [ ] Pydantic schema files get `type-definition`, `serialization` tags -- [ ] `dependencies.py` or `deps.py` gets `service` tag -- [ ] `depends_on` edges appear between router files and their dependencies -- [ ] `layer:types` exists with schema files - -For an existing TypeScript project (regression check): -- [ ] No Django/FastAPI addendum content appears in analysis -- [ ] `frameworks: ["React"]` (or whatever was there before) unchanged -- [ ] `src/index.ts` still detected as entry point -- [ ] All existing layer assignments and tour steps unchanged diff --git a/understand-anything-plugin/skills/understand/SKILL.md b/understand-anything-plugin/skills/understand/SKILL.md index fd89b92..35b28d3 100644 --- a/understand-anything-plugin/skills/understand/SKILL.md +++ b/understand-anything-plugin/skills/understand/SKILL.md @@ -54,7 +54,7 @@ Determine whether to run a full analysis or incremental update. find $PROJECT_ROOT -maxdepth 2 -type f -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' | head -100 ``` Store as `$DIR_TREE`. - - Detect the project entry point by checking for common patterns (in order): `src/index.ts`, `src/main.ts`, `src/App.tsx`, `index.js`, `main.py`, `manage.py`, `app.py`, `wsgi.py`, `asgi.py`, `run.py`, `__main__.py`, `main.go`, `src/main.rs`. Store first match as `$ENTRY_POINT`. + - Detect the project entry point by checking for common patterns (in order): `src/index.ts`, `src/main.ts`, `src/App.tsx`, `index.js`, `main.py`, `manage.py`, `app.py`, `wsgi.py`, `asgi.py`, `run.py`, `__main__.py`, `main.go`, `cmd/*/main.go`, `src/main.rs`, `src/lib.rs`, `src/main/java/**/Application.java`, `Program.cs`, `config.ru`, `index.php`. Store first match as `$ENTRY_POINT`. --- @@ -98,7 +98,14 @@ After the subagent completes, read `$PROJECT_ROOT/.understand-anything/intermedi Batch the file list from Phase 1 into groups of **5-10 files each** (aim for balanced batch sizes). -For each batch, dispatch a subagent using the prompt template at `./file-analyzer-prompt.md`. Run up to **3 subagents concurrently** using parallel dispatch. Read the template once. If any detected framework is `Django`, also read `./django-analyzer-addendum.md` and append its full content after the base template. If any detected framework is `FastAPI` or `Flask`, also read `./fastapi-analyzer-addendum.md` and append its full content after the base template. Then for each batch pass the combined template content as the subagent's prompt, appending the following additional context: +For each batch, dispatch a subagent using the prompt template at `./file-analyzer-prompt.md`. Run up to **3 subagents concurrently** using parallel dispatch. + +**Build the combined prompt template:** +1. Read the base template at `./file-analyzer-prompt.md`. +2. **Language context injection:** For each language detected in Phase 1, check if `./languages/.md` exists. If it does, read it and append its content after the base template under a `## Language Context` header. +3. **Framework addendum injection:** For each framework detected in Phase 1, check if `./frameworks/.md` exists. If it does, read it and append its full content after the language context. This replaces any hardcoded framework-specific conditionals. + +Then for each batch pass the combined template content as the subagent's prompt, appending the following additional context: > **Additional context from main session:** > @@ -106,15 +113,7 @@ For each batch, dispatch a subagent using the prompt template at `./file-analyze > Frameworks detected: `` > Languages: `` > -> Framework-specific guidance: -> - If React/Next.js: files in `app/` or `pages/` are routes, `components/` are UI, `lib/` or `utils/` are utilities -> - If Express/Fastify: files in `routes/` are API endpoints, `middleware/` is middleware, `models/` or `db/` is data -> - If Python Django: `views.py` are controllers, `models.py` is data, `urls.py` is routing, `templates/` is UI, `serializers.py` is API serialization, `signals.py` is event wiring, `admin.py` is admin registration, `migrations/` is schema history -> - If Python FastAPI: files with `@router.get/post/...` decorators are endpoints, `schemas.py` or `models.py` with Pydantic classes are request/response types, `dependencies.py` or `deps.py` holds `Depends()` providers, `routers/` or `api/` groups route modules -> - If Python Flask: files with `@app.route` or `@blueprint.route` are endpoints, `blueprints/` or `views/` groups route modules, `models.py` with SQLAlchemy classes is data -> - If Go: `cmd/` is entry points, `internal/` is private packages, `pkg/` is public packages -> -> Use this context to produce more accurate summaries and better classify file roles. +> Use the language context and framework addendums (appended above) to produce more accurate summaries and better classify file roles. Fill in batch-specific parameters below and dispatch: @@ -160,7 +159,12 @@ Merge all file-analyzer results into a single set of nodes and edges. Then perfo ## Phase 4 — ARCHITECTURE -Dispatch a subagent using the prompt template at `./architecture-analyzer-prompt.md`. If any detected framework is `Django`, also read `./django-analyzer-addendum.md` and append its full content after the base template. If any detected framework is `FastAPI` or `Flask`, also read `./fastapi-analyzer-addendum.md` and append its full content after the base template. Pass the combined content as the subagent's prompt, appending the following additional context: +**Build the combined prompt template:** +1. Read the base template at `./architecture-analyzer-prompt.md`. +2. **Language context injection:** For each language detected in Phase 1, check if `./languages/.md` exists. If it does, read it and append its content after the base template under a `## Language Context` header. +3. **Framework addendum injection:** For each framework detected in Phase 1, check if `./frameworks/.md` exists. If it does, read it and append its full content after the language context. + +Pass the combined content as the subagent's prompt, appending the following additional context: > **Additional context from main session:** > @@ -171,15 +175,7 @@ Dispatch a subagent using the prompt template at `./architecture-analyzer-prompt > $DIR_TREE > ``` > -> Framework-specific layer hints: -> - If React/Next.js: `app/` or `pages/` → UI Layer, `api/` → API Layer, `lib/` → Service Layer, `components/` → UI Layer -> - If Express: `routes/` → API Layer, `controllers/` → Service Layer, `models/` → Data Layer, `middleware/` → Middleware Layer -> - If Python Django: `views/` or `views.py` → API Layer, `models/` or `models.py` → Data Layer, `templates/` → UI Layer, `management/` → CLI Layer, `serializers.py` → API Layer, `signals.py` → Event Layer, `migrations/` → Data Layer -> - If Python FastAPI: files with router decorators → API Layer, Pydantic schema files → Types Layer, `dependencies.py` or `deps.py` → Service Layer, `routers/` or `api/` → API Layer, database session/engine files → Data Layer -> - If Python Flask: files with `@blueprint.route` → API Layer, `models.py` → Data Layer, `forms.py` → UI Layer, `extensions.py` → Config Layer -> - If Go: `cmd/` → Entry Points, `internal/` → Service Layer, `pkg/` → Shared Library, `api/` → API Layer -> -> Use the directory tree and framework hints to inform layer assignments. Directory structure is strong evidence for layer boundaries. +> Use the directory tree, language context, and framework addendums (appended above) to inform layer assignments. Directory structure is strong evidence for layer boundaries. Pass these parameters in the dispatch prompt: diff --git a/understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md b/understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md index 9728db3..e7b1158 100644 --- a/understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md +++ b/understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md @@ -87,13 +87,31 @@ Classify each directory name against known architectural patterns: | `templatetags` | `utility` | | `signals` | `service` | | `serializers` | `api` | +| `cmd` | `entry` | +| `internal` | `service` | +| `pkg` | `utility` | +| `src/main/java` | `service` | +| `src/test/java` | `test` | +| `dto`, `request`, `response` | `types` | +| `entity` | `data` | +| `controller` | `api` | +| `routers` | `api` | +| `composables` | `service` | +| `blueprints` | `api` | +| `mailers`, `jobs`, `channels` | `service` | +| `bin` | `entry` | Also check file-level patterns: -- Files matching `*.test.*` or `*.spec.*` or `test_*.py` -> `test` +- Files matching `*.test.*` or `*.spec.*` or `test_*.py` or `*_test.go` or `*Test.java` or `*_spec.rb` or `*Test.php` or `*Tests.cs` -> `test` - Files matching `*.d.ts` -> `types` (TypeScript declaration files only) - Files named `index.ts`, `index.js`, or `__init__.py` at a package/directory root -> `entry` - Files named `manage.py` at the project root -> `entry` (Django management entry point) - Files named `wsgi.py` or `asgi.py` -> `config` (Python WSGI/ASGI server config) +- Files named `main.go` at `cmd/*/` -> `entry` (Go binary entry points) +- Files named `main.rs` or `lib.rs` at `src/` -> `entry` (Rust crate roots) +- Files named `Application.java` or `Program.cs` -> `entry` (JVM / .NET entry points) +- Files named `config.ru` -> `entry` (Ruby Rack entry point) +- Files named `Cargo.toml`, `go.mod`, `Gemfile`, `pom.xml`, `build.gradle`, `composer.json` -> `config` (language-level project config) **F. Dependency Direction** diff --git a/understand-anything-plugin/skills/understand/file-analyzer-prompt.md b/understand-anything-plugin/skills/understand/file-analyzer-prompt.md index 0ab0cf1..c948b1f 100644 --- a/understand-anything-plugin/skills/understand/file-analyzer-prompt.md +++ b/understand-anything-plugin/skills/understand/file-analyzer-prompt.md @@ -12,7 +12,7 @@ For each file in the batch provided to you, extract structural data via a script ## Phase 1 -- Structural Extraction Script -Write a script that reads each source file in your batch and extracts deterministic structural information. Choose the best language for this task -- Node.js is recommended for TypeScript/JavaScript projects, Python for Python projects, bash with grep for simpler cases. +Write a script that reads each source file in your batch and extracts deterministic structural information. Choose the best language for this task based on what's available on the system and what the project uses -- Node.js, Python, or bash with grep are all valid choices. ### Script Requirements @@ -172,6 +172,12 @@ Indicators from script data: - Named `index.ts` or `index.js` at a directory root with re-exports = `entry-point` (JavaScript/TypeScript barrel) - Named `__init__.py` at a package root with imports or re-exports = `entry-point` (Python package barrel) - Named `manage.py` = `entry-point` (Django management script) +- Named `main.go` in `cmd/` directory = `entry-point` (Go binary) +- Named `main.rs` or `lib.rs` in `src/` = `entry-point` (Rust crate root) +- Named `Application.java` or `Main.java` = `entry-point` (Java application) +- Named `Program.cs` = `entry-point` (.NET application) +- Named `config.ru` = `entry-point` (Ruby Rack server) +- Named `mod.rs` in a directory = `barrel` (Rust module barrel) **Language Notes** (optional, your expert judgment): If the structural data reveals notable language-specific patterns (e.g., many generic type parameters, decorator usage, complex trait bounds), add a brief `languageNotes` string. Only add this when genuinely educational. diff --git a/understand-anything-plugin/skills/understand/django-analyzer-addendum.md b/understand-anything-plugin/skills/understand/frameworks/django.md similarity index 100% rename from understand-anything-plugin/skills/understand/django-analyzer-addendum.md rename to understand-anything-plugin/skills/understand/frameworks/django.md diff --git a/understand-anything-plugin/skills/understand/frameworks/express.md b/understand-anything-plugin/skills/understand/frameworks/express.md new file mode 100644 index 0000000..2970354 --- /dev/null +++ b/understand-anything-plugin/skills/understand/frameworks/express.md @@ -0,0 +1,57 @@ +# Express Framework Addendum + +> Injected into file-analyzer and architecture-analyzer prompts when Express is detected. +> Do NOT use as a standalone prompt — always appended to the base prompt template. + +## Express Project Structure + +When analyzing an Express project, apply these additional conventions on top of the base analysis rules. + +### Canonical File Roles + +| File / Pattern | Role | Tags | +|---|---|---| +| `app.js`, `app.ts` | Application entry point — creates Express app, mounts middleware and routes | `entry-point`, `config` | +| `server.js`, `server.ts`, `index.js`, `index.ts` | Server bootstrap — starts HTTP listener, may import app | `entry-point`, `config` | +| `routes/*.js`, `routes/*.ts` | Route definitions — map HTTP methods and paths to handlers | `api-handler`, `routing` | +| `controllers/*.js`, `controllers/*.ts` | Request handlers — process requests, orchestrate services, return responses | `api-handler`, `service` | +| `models/*.js`, `models/*.ts` | Data models — Mongoose schemas, Sequelize models, or plain data definitions | `data-model` | +| `middleware/*.js`, `middleware/*.ts` | Middleware functions — authentication, logging, validation, error handling | `middleware` | +| `services/*.js`, `services/*.ts` | Business logic — domain operations decoupled from HTTP layer | `service` | +| `db/*.js`, `db/*.ts`, `database/*.js` | Database connection and configuration | `data-model`, `config` | +| `config/*.js`, `config/*.ts` | Application configuration — environment variables, feature flags | `config` | +| `validators/*.js`, `validators/*.ts` | Request validation schemas (Joi, Zod, express-validator) | `validation`, `utility` | +| `utils/*.js`, `utils/*.ts` | Shared utility functions | `utility` | +| `tests/*.js`, `test/*.js`, `__tests__/*.js` | Unit and integration tests | `test` | + +### Edge Patterns to Look For + +**Route mounting** — When `app.use('/api/users', usersRouter)` mounts a router, create `depends_on` edges from the main app to the router module. These edges represent the HTTP routing tree. + +**Middleware chain** — When `app.use(cors())`, `app.use(authMiddleware)`, or `router.use(validate)` registers middleware, create middleware edges from the app or router to the middleware function. Order matters — middleware executes in registration order. + +**Controller-to-service calls** — When a controller imports and calls a service function, create `depends_on` edges from the controller to the service. This represents the separation between HTTP handling and business logic. + +**Model relationships** — When models reference each other (Mongoose `ref`, Sequelize associations), create `depends_on` edges between model files with descriptions indicating the relationship type. + +### Architectural Layers for Express + +Assign nodes to these layers when detected: + +| Layer ID | Layer Name | What Goes Here | +|---|---|---| +| `layer:api` | API Layer | `routes/`, `controllers/`, request validators | +| `layer:data` | Data Layer | `models/`, `db/`, migration files, seeders | +| `layer:service` | Service Layer | `services/`, business logic modules | +| `layer:middleware` | Middleware Layer | `middleware/`, error handlers, authentication, logging | +| `layer:config` | Config Layer | `app.js`, `config/`, environment setup, `server.js` | +| `layer:utility` | Utility Layer | `utils/`, `helpers/`, shared pure functions | +| `layer:test` | Test Layer | `tests/`, `__tests__/`, `*.test.js`, `*.spec.js` | + +### Notable Patterns to Capture in languageLesson + +- **Middleware chain (req, res, next)**: Express processes requests through a pipeline of middleware functions — each receives the request, response, and a `next()` callback to pass control forward +- **Error-handling middleware (4 params)**: Middleware with signature `(err, req, res, next)` catches errors — must be registered after all routes to act as a global error handler +- **Router modularity**: `express.Router()` creates modular, mountable route handlers that can be composed into the main app at different path prefixes +- **MVC pattern**: Express apps commonly separate concerns into Models (data), Views (response formatting), and Controllers (request handling) +- **Body parsing and validation**: Request body parsing (`express.json()`, `express.urlencoded()`) and validation (Joi, Zod, express-validator) are middleware concerns applied before route handlers diff --git a/understand-anything-plugin/skills/understand/fastapi-analyzer-addendum.md b/understand-anything-plugin/skills/understand/frameworks/fastapi.md similarity index 55% rename from understand-anything-plugin/skills/understand/fastapi-analyzer-addendum.md rename to understand-anything-plugin/skills/understand/frameworks/fastapi.md index d8a71c6..79431a2 100644 --- a/understand-anything-plugin/skills/understand/fastapi-analyzer-addendum.md +++ b/understand-anything-plugin/skills/understand/frameworks/fastapi.md @@ -1,13 +1,13 @@ -# FastAPI / Flask Framework Addendum +# FastAPI Framework Addendum -> Injected into file-analyzer and architecture-analyzer prompts when FastAPI or Flask is detected. +> Injected into file-analyzer and architecture-analyzer prompts when FastAPI is detected. > Do NOT use as a standalone prompt — always appended to the base prompt template. ## FastAPI Project Structure When analyzing a FastAPI project, apply these additional conventions on top of the base analysis rules. -### Canonical File Roles — FastAPI +### Canonical File Roles | File / Pattern | Role | Tags | |---|---|---| @@ -26,7 +26,7 @@ When analyzing a FastAPI project, apply these additional conventions on top of t | `*/tests/*.py`, `test_*.py` | pytest test files | `test` | | `conftest.py` | pytest fixtures and test configuration | `test`, `config` | -### Edge Patterns to Look For — FastAPI +### Edge Patterns to Look For **Router inclusion chain** — When `app.include_router(some_router, prefix="/api")` appears in `main.py` or a router aggregator, create `imports` + `depends_on` edges from the main app file to each router module. This builds the URL hierarchy graph. @@ -56,54 +56,3 @@ When analyzing a FastAPI project, apply these additional conventions on top of t - **Pydantic for validation**: Request bodies, query params, and path params are automatically validated by Pydantic — invalid input raises `422 Unprocessable Entity` before your code runs - **Async endpoints**: `async def` routes run in the event loop; `def` routes run in a threadpool — mixing them incorrectly can cause performance issues - **Path operation order**: FastAPI matches routes in declaration order; a catch-all route before a specific one will shadow it - ---- - -## Flask Project Structure - -When analyzing a Flask project, apply these additional conventions on top of the base analysis rules. - -### Canonical File Roles — Flask - -| File / Pattern | Role | Tags | -|---|---|---| -| `app.py`, `__init__.py` (in app package) | Application factory (`create_app()`) or direct `Flask(__name__)` instance | `entry-point`, `config` | -| `run.py`, `wsgi.py` | Production/dev server entry point | `entry-point`, `config` | -| `*/views.py`, `*/routes.py` | Route handler functions with `@app.route` or `@blueprint.route` | `api-handler`, `routing` | -| `*/blueprints/*.py`, `*/api/*.py` | Blueprint modules — group routes by feature | `api-handler`, `routing` | -| `*/models.py` | SQLAlchemy models or other ORM models | `data-model` | -| `*/forms.py` | WTForms form classes | `validation`, `ui` | -| `*/schemas.py` | Marshmallow serialization schemas | `serialization`, `type-definition` | -| `*/config.py` | Config classes (`DevelopmentConfig`, `ProductionConfig`) | `config` | -| `*/extensions.py` | Flask extension initialization (`db = SQLAlchemy()`, `login_manager = LoginManager()`) | `config`, `singleton` | -| `*/decorators.py` | Custom route decorators (auth guards, rate limiting) | `middleware`, `utility` | -| `*/utils.py`, `*/helpers.py` | Shared utility functions | `utility` | -| `*/templates/**/*.html` | Jinja2 templates | `ui` | -| `*/static/` | CSS, JS, and asset files | `assets` | -| `*/tests/*.py`, `test_*.py` | pytest or unittest test files | `test` | - -### Edge Patterns to Look For — Flask - -**Blueprint registration** — When `app.register_blueprint(bp, url_prefix='/api')` appears in the application factory, create `depends_on` edges from the app factory to each blueprint module. - -**Extension coupling** — When a view imports from `extensions.py` (e.g., `from .extensions import db, login_manager`), create `imports` edges to show which views depend on which extensions. - -**Before/after request hooks** — When `@app.before_request` or `@blueprint.before_request` decorates a function, create `middleware` edges from those functions to the app/blueprint they attach to. - -### Architectural Layers for Flask - -| Layer ID | Layer Name | What Goes Here | -|---|---|---| -| `layer:api` | API Layer | Blueprint route files, view functions | -| `layer:data` | Data Layer | `models.py`, database migration files | -| `layer:service` | Service Layer | Business logic modules, `schemas.py`, service classes | -| `layer:ui` | UI Layer | `templates/`, `forms.py`, `static/` | -| `layer:config` | Config Layer | `app.py` factory, `config.py`, `extensions.py` | -| `layer:middleware` | Middleware Layer | `decorators.py`, before/after request hooks | -| `layer:test` | Test Layer | Test files, `conftest.py` | - -### Notable Patterns to Capture in languageLesson - -- **Application factory pattern**: `create_app()` functions allow multiple app instances (e.g., for testing) and delay extension initialization — avoids circular imports -- **Blueprint modularity**: Blueprints group related routes, templates, and static files; they are registered on the app with a URL prefix, making them independently testable -- **Flask extension protocol**: Extensions follow `init_app(app)` for lazy initialization — the extension object is created globally but bound to an app instance later diff --git a/understand-anything-plugin/skills/understand/frameworks/flask.md b/understand-anything-plugin/skills/understand/frameworks/flask.md new file mode 100644 index 0000000..b1df89f --- /dev/null +++ b/understand-anything-plugin/skills/understand/frameworks/flask.md @@ -0,0 +1,53 @@ +# Flask Framework Addendum + +> Injected into file-analyzer and architecture-analyzer prompts when Flask is detected. +> Do NOT use as a standalone prompt — always appended to the base prompt template. + +## Flask Project Structure + +When analyzing a Flask project, apply these additional conventions on top of the base analysis rules. + +### Canonical File Roles + +| File / Pattern | Role | Tags | +|---|---|---| +| `app.py`, `__init__.py` (in app package) | Application factory (`create_app()`) or direct `Flask(__name__)` instance | `entry-point`, `config` | +| `run.py`, `wsgi.py` | Production/dev server entry point | `entry-point`, `config` | +| `*/views.py`, `*/routes.py` | Route handler functions with `@app.route` or `@blueprint.route` | `api-handler`, `routing` | +| `*/blueprints/*.py`, `*/api/*.py` | Blueprint modules — group routes by feature | `api-handler`, `routing` | +| `*/models.py` | SQLAlchemy models or other ORM models | `data-model` | +| `*/forms.py` | WTForms form classes | `validation`, `ui` | +| `*/schemas.py` | Marshmallow serialization schemas | `serialization`, `type-definition` | +| `*/config.py` | Config classes (`DevelopmentConfig`, `ProductionConfig`) | `config` | +| `*/extensions.py` | Flask extension initialization (`db = SQLAlchemy()`, `login_manager = LoginManager()`) | `config`, `singleton` | +| `*/decorators.py` | Custom route decorators (auth guards, rate limiting) | `middleware`, `utility` | +| `*/utils.py`, `*/helpers.py` | Shared utility functions | `utility` | +| `*/templates/**/*.html` | Jinja2 templates | `ui` | +| `*/static/` | CSS, JS, and asset files | `assets` | +| `*/tests/*.py`, `test_*.py` | pytest or unittest test files | `test` | + +### Edge Patterns to Look For + +**Blueprint registration** — When `app.register_blueprint(bp, url_prefix='/api')` appears in the application factory, create `depends_on` edges from the app factory to each blueprint module. + +**Extension coupling** — When a view imports from `extensions.py` (e.g., `from .extensions import db, login_manager`), create `imports` edges to show which views depend on which extensions. + +**Before/after request hooks** — When `@app.before_request` or `@blueprint.before_request` decorates a function, create `middleware` edges from those functions to the app/blueprint they attach to. + +### Architectural Layers for Flask + +| Layer ID | Layer Name | What Goes Here | +|---|---|---| +| `layer:api` | API Layer | Blueprint route files, view functions | +| `layer:data` | Data Layer | `models.py`, database migration files | +| `layer:service` | Service Layer | Business logic modules, `schemas.py`, service classes | +| `layer:ui` | UI Layer | `templates/`, `forms.py`, `static/` | +| `layer:config` | Config Layer | `app.py` factory, `config.py`, `extensions.py` | +| `layer:middleware` | Middleware Layer | `decorators.py`, before/after request hooks | +| `layer:test` | Test Layer | Test files, `conftest.py` | + +### Notable Patterns to Capture in languageLesson + +- **Application factory pattern**: `create_app()` functions allow multiple app instances (e.g., for testing) and delay extension initialization — avoids circular imports +- **Blueprint modularity**: Blueprints group related routes, templates, and static files; they are registered on the app with a URL prefix, making them independently testable +- **Flask extension protocol**: Extensions follow `init_app(app)` for lazy initialization — the extension object is created globally but bound to an app instance later diff --git a/understand-anything-plugin/skills/understand/frameworks/gin.md b/understand-anything-plugin/skills/understand/frameworks/gin.md new file mode 100644 index 0000000..494c27d --- /dev/null +++ b/understand-anything-plugin/skills/understand/frameworks/gin.md @@ -0,0 +1,59 @@ +# Gin (Go) Framework Addendum + +> Injected into file-analyzer and architecture-analyzer prompts when Gin is detected. +> Do NOT use as a standalone prompt — always appended to the base prompt template. + +## Gin Project Structure + +When analyzing a Gin project, apply these additional conventions on top of the base analysis rules. + +### Canonical File Roles + +| File / Pattern | Role | Tags | +|---|---|---| +| `main.go` | Application entry point — initializes the Gin engine, registers routes, starts the server | `entry-point`, `config` | +| `cmd/*.go`, `cmd/**/*.go` | CLI entry points — multiple binaries in a multi-command project | `entry-point`, `config` | +| `handlers/*.go`, `handler/*.go` | HTTP handlers — process requests with `gin.Context` | `api-handler` | +| `controllers/*.go`, `controller/*.go` | Controllers — alternative naming for HTTP handlers | `api-handler` | +| `routes/*.go`, `router/*.go` | Route definitions — register endpoints and route groups | `routing`, `config` | +| `models/*.go`, `model/*.go` | Data models — struct definitions mapped to database tables | `data-model` | +| `middleware/*.go` | Middleware functions — authentication, logging, CORS, rate limiting | `middleware` | +| `services/*.go`, `service/*.go` | Business logic — domain operations decoupled from HTTP layer | `service` | +| `repository/*.go`, `repo/*.go` | Data access layer — database queries and persistence logic | `data-model`, `service` | +| `config/*.go`, `config.go` | Application configuration — environment loading, struct-based config | `config` | +| `dto/*.go` | Data transfer objects — request and response structs | `type-definition` | +| `utils/*.go`, `pkg/*.go` | Shared utility packages | `utility` | +| `*_test.go` | Unit and integration tests | `test` | + +### Edge Patterns to Look For + +**Route group registration** — When `r.Group("/api")` creates a route group and registers handlers, create `configures` edges from the route definition file to each handler. Route groups organize endpoints by prefix and shared middleware. + +**Handler-to-service calls** — When a handler function calls a service method, create `depends_on` edges from the handler to the service. This represents the separation between HTTP handling and business logic. + +**Service-to-repository calls** — When a service calls a repository method for data access, create `depends_on` edges from the service to the repository. This represents the data access abstraction. + +**Middleware chaining** — When `r.Use(middleware)` or a route group applies middleware, create middleware edges from the router or group to the middleware function. Middleware executes in registration order. + +### Architectural Layers for Gin + +Assign nodes to these layers when detected: + +| Layer ID | Layer Name | What Goes Here | +|---|---|---| +| `layer:api` | API Layer | `handlers/`, `controllers/`, HTTP handler functions | +| `layer:data` | Data Layer | `models/`, `repository/`, database access, migrations | +| `layer:service` | Service Layer | `services/`, business logic | +| `layer:middleware` | Middleware Layer | `middleware/`, authentication, logging, rate limiting | +| `layer:config` | Config Layer | `main.go`, `routes/`, `config/`, environment setup | +| `layer:utility` | Utility Layer | `utils/`, `pkg/`, shared helper packages | +| `layer:test` | Test Layer | `*_test.go`, test fixtures, test helpers | + +### Notable Patterns to Capture in languageLesson + +- **Handler functions with gin.Context**: Every Gin handler receives a `*gin.Context` parameter — it provides request parsing (`c.Bind`, `c.Param`, `c.Query`), response writing (`c.JSON`, `c.HTML`), and control flow (`c.Abort`, `c.Next`) +- **Middleware chain with c.Next()**: Middleware calls `c.Next()` to pass control to the next handler in the chain — code before `c.Next()` runs pre-handler, code after runs post-handler +- **Route grouping for modular APIs**: `r.Group("/v1")` creates modular sub-routers that can have their own middleware stack — enables versioning and access control at the group level +- **Dependency injection via constructors (no framework DI)**: Go has no DI framework — dependencies are passed as constructor parameters (e.g., `NewUserHandler(userService)`) and stored as struct fields +- **Interface-driven design for testability**: Services and repositories are defined as interfaces — handlers depend on the interface, enabling mock implementations in tests +- **Error handling with gin.Error**: Gin collects errors via `c.Error(err)` — middleware can inspect `c.Errors` after handler execution to implement centralized error logging and response formatting diff --git a/understand-anything-plugin/skills/understand/frameworks/nextjs.md b/understand-anything-plugin/skills/understand/frameworks/nextjs.md new file mode 100644 index 0000000..6b9a93c --- /dev/null +++ b/understand-anything-plugin/skills/understand/frameworks/nextjs.md @@ -0,0 +1,59 @@ +# Next.js Framework Addendum + +> Injected into file-analyzer and architecture-analyzer prompts when Next.js is detected. +> Do NOT use as a standalone prompt — always appended to the base prompt template. + +## Next.js Project Structure + +When analyzing a Next.js project, apply these additional conventions on top of the base analysis rules. + +### Canonical File Roles + +| File / Pattern | Role | Tags | +|---|---|---| +| `app/layout.tsx` | Root layout — wraps all pages, defines HTML shell and global providers | `entry-point`, `config`, `ui` | +| `app/page.tsx` | Root page component — renders at `/` | `ui`, `routing` | +| `app/**/page.tsx` | Route page components — file path determines URL | `ui`, `routing` | +| `app/**/layout.tsx` | Nested layouts — wrap child routes with shared UI | `ui`, `config` | +| `app/**/loading.tsx` | Loading UI — shown as Suspense fallback during route transitions | `ui` | +| `app/**/error.tsx` | Error boundary — catches errors in the route segment | `ui` | +| `app/**/not-found.tsx` | 404 UI — shown when `notFound()` is called | `ui` | +| `app/api/**/route.ts` | API route handlers — serverless endpoint functions (GET, POST, etc.) | `api-handler` | +| `middleware.ts` | Edge middleware — intercepts requests before they reach routes | `middleware` | +| `lib/*.ts`, `lib/**/*.ts` | Shared server-side utilities, data access, and business logic | `service` | +| `components/*.tsx`, `components/**/*.tsx` | Reusable UI components | `ui` | +| `next.config.js`, `next.config.mjs`, `next.config.ts` | Next.js configuration — redirects, rewrites, env, webpack overrides | `config` | +| `actions/*.ts`, `app/**/actions.ts` | Server Actions — server-side mutation functions callable from client | `service`, `api-handler` | + +### Edge Patterns to Look For + +**Layout nesting** — When `app/foo/layout.tsx` wraps `app/foo/page.tsx` and `app/foo/bar/page.tsx`, create `contains` edges from the layout to the pages it wraps. Layouts compose via the file-system hierarchy. + +**API route handlers** — When a `route.ts` file exports named functions (GET, POST, PUT, DELETE), create edges from consuming components or server actions to the route handler based on fetch calls. + +**Server/Client component boundary** — Files with `"use client"` directive at the top are Client Components. All other components in the `app/` directory are Server Components by default. Create `depends_on` edges that cross this boundary and note the boundary in the edge description. + +**Parallel routes** — When `app/@slot/page.tsx` patterns appear, create `contains` edges from the parent layout to each parallel slot. These render simultaneously in the same layout. + +**Route groups** — Directories wrapped in parentheses `(group)` organize routes without affecting the URL path. Note these in node descriptions. + +### Architectural Layers for Next.js + +Assign nodes to these layers when detected: + +| Layer ID | Layer Name | What Goes Here | +|---|---|---| +| `layer:ui` | UI Layer | `app/**/page.tsx`, `app/**/layout.tsx`, `components/`, loading/error boundaries | +| `layer:api` | API Layer | `app/api/**/route.ts`, API route handlers | +| `layer:service` | Service Layer | `lib/`, server actions, data-fetching utilities | +| `layer:middleware` | Middleware Layer | `middleware.ts`, edge functions | +| `layer:config` | Config Layer | `next.config.*`, root layout, `tailwind.config.*`, environment setup | +| `layer:test` | Test Layer | `__tests__/`, `*.test.tsx`, `*.spec.tsx`, `e2e/` | + +### Notable Patterns to Capture in languageLesson + +- **Server Components by default**: Components in the `app/` directory are Server Components — no JavaScript is sent to the client unless `"use client"` is declared +- **Server Actions for mutations**: Functions marked with `"use server"` can be called directly from client components, replacing traditional API routes for form submissions and mutations +- **App Router file conventions**: Special files (`page`, `layout`, `loading`, `error`, `not-found`, `route`) define behavior by naming convention within the file-system router +- **ISR and static generation**: `generateStaticParams` pre-renders pages at build time; revalidation strategies control cache freshness +- **Parallel and intercepting routes**: `@slot` directories enable parallel rendering; `(.)` prefix directories enable route interception for modal patterns diff --git a/understand-anything-plugin/skills/understand/frameworks/rails.md b/understand-anything-plugin/skills/understand/frameworks/rails.md new file mode 100644 index 0000000..570ef10 --- /dev/null +++ b/understand-anything-plugin/skills/understand/frameworks/rails.md @@ -0,0 +1,65 @@ +# Ruby on Rails Framework Addendum + +> Injected into file-analyzer and architecture-analyzer prompts when Rails is detected. +> Do NOT use as a standalone prompt — always appended to the base prompt template. + +## Rails Project Structure + +When analyzing a Ruby on Rails project, apply these additional conventions on top of the base analysis rules. + +### Canonical File Roles + +| File / Pattern | Role | Tags | +|---|---|---| +| `config.ru` | Rack entry point — boots the Rails application for the web server | `entry-point` | +| `config/application.rb` | Application configuration — sets up Rails, loads gems, configures middleware | `entry-point`, `config` | +| `app/controllers/*_controller.rb` | Controllers — handle HTTP requests, orchestrate models, render responses | `api-handler` | +| `app/controllers/concerns/*.rb` | Controller concerns — shared controller behavior via mixins | `middleware`, `utility` | +| `app/models/*.rb` | ActiveRecord models — map to database tables, contain validations and associations | `data-model` | +| `app/models/concerns/*.rb` | Model concerns — shared model behavior via mixins | `utility` | +| `app/views/**/*.erb`, `app/views/**/*.haml` | View templates — HTML rendering with embedded Ruby | `ui` | +| `app/helpers/*_helper.rb` | View helpers — utility methods available in templates | `utility` | +| `app/mailers/*_mailer.rb` | Action Mailer classes — send email notifications | `service` | +| `app/jobs/*_job.rb` | Active Job classes — background job processing | `service` | +| `app/channels/*_channel.rb` | Action Cable channels — WebSocket communication | `service` | +| `app/serializers/*_serializer.rb` | API serializers — JSON response formatting (ActiveModelSerializers, Blueprinter) | `api-handler`, `utility` | +| `app/services/*.rb` | Service objects — encapsulate complex business logic | `service` | +| `db/migrate/*.rb` | Database migrations — schema changes versioned by timestamp | `config`, `data-model` | +| `db/schema.rb`, `db/structure.sql` | Generated schema snapshot — current database structure | `data-model`, `config` | +| `config/routes.rb` | Route definitions — maps URLs to controller actions | `routing`, `config` | +| `config/initializers/*.rb` | Initializers — run once at boot to configure gems and services | `config` | +| `lib/**/*.rb` | Library code — custom classes, Rake tasks, extensions | `utility`, `service` | +| `spec/**/*_spec.rb`, `test/**/*_test.rb` | RSpec or Minitest test files | `test` | + +### Edge Patterns to Look For + +**Route-to-controller mapping** — When `config/routes.rb` defines `resources :users` or `get '/foo', to: 'bar#baz'`, create `configures` edges from the routes file to the corresponding controller. RESTful resources generate a full set of action mappings. + +**ActiveRecord associations** — When models define `has_many`, `belongs_to`, `has_one`, or `has_and_belongs_to_many`, create `depends_on` edges between model files with descriptions indicating the association type and direction. + +**Controller-to-model** — When a controller calls model methods (`User.find`, `@post.save`), create `depends_on` edges from the controller to the model. Controllers are the primary consumers of model data. + +**Callbacks** — When models or controllers use `before_action`, `after_save`, `before_validation`, or similar callbacks, note these as middleware-like edges. Callbacks create implicit execution paths that are not visible from the call site. + +### Architectural Layers for Rails + +Assign nodes to these layers when detected: + +| Layer ID | Layer Name | What Goes Here | +|---|---|---| +| `layer:api` | API Layer | `app/controllers/`, `app/serializers/`, API-specific controllers | +| `layer:data` | Data Layer | `app/models/`, `db/migrate/`, `db/schema.rb` | +| `layer:ui` | UI Layer | `app/views/`, `app/helpers/`, `app/assets/`, `app/javascript/` | +| `layer:service` | Service Layer | `app/mailers/`, `app/jobs/`, `app/channels/`, `app/services/`, `lib/` | +| `layer:config` | Config Layer | `config/routes.rb`, `config/initializers/`, `config/application.rb`, `config.ru` | +| `layer:middleware` | Middleware Layer | `app/middleware/`, controller concerns, Rack middleware | +| `layer:test` | Test Layer | `spec/`, `test/`, `*.spec.rb`, `*_test.rb` | + +### Notable Patterns to Capture in languageLesson + +- **Convention over configuration**: Rails derives routing, table names, and file locations from naming conventions — `UsersController` maps to `users_controller.rb`, handles `/users`, and queries the `users` table +- **ActiveRecord pattern**: Models are database wrappers — each model class maps to a table, instances map to rows, and attributes map to columns with automatic type coercion +- **Concerns for shared behavior**: `ActiveSupport::Concern` modules are mixins included in models or controllers to share validations, scopes, callbacks, and methods across classes +- **Strong parameters for mass-assignment protection**: `params.require(:user).permit(:name, :email)` whitelists attributes — controllers must explicitly declare which fields can be set from user input +- **RESTful resource routing**: `resources :posts` generates seven standard CRUD routes — Rails strongly encourages RESTful design where each controller maps to a resource +- **Callbacks and observers**: `before_save`, `after_create`, and similar callbacks inject logic into the object lifecycle — they create invisible execution paths that can be difficult to trace diff --git a/understand-anything-plugin/skills/understand/frameworks/react.md b/understand-anything-plugin/skills/understand/frameworks/react.md new file mode 100644 index 0000000..d36eb39 --- /dev/null +++ b/understand-anything-plugin/skills/understand/frameworks/react.md @@ -0,0 +1,55 @@ +# React Framework Addendum + +> Injected into file-analyzer and architecture-analyzer prompts when React is detected. +> Do NOT use as a standalone prompt — always appended to the base prompt template. + +## React Project Structure + +When analyzing a React project, apply these additional conventions on top of the base analysis rules. + +### Canonical File Roles + +| File / Pattern | Role | Tags | +|---|---|---| +| `src/App.tsx` | Root application component — mounts providers, router, and top-level layout | `entry-point`, `ui` | +| `components/*.tsx`, `components/**/*.tsx` | Reusable UI components | `ui` | +| `hooks/*.ts`, `hooks/*.tsx` | Custom React hooks — encapsulate reusable stateful logic | `service`, `utility` | +| `contexts/*.tsx`, `context/*.tsx` | React Context providers and consumers — shared state across component tree | `service`, `state` | +| `pages/*.tsx`, `views/*.tsx` | Page-level components mapped to routes | `ui`, `routing` | +| `utils/*.ts`, `helpers/*.ts` | Pure utility functions — formatting, validation, transformations | `utility` | +| `types/*.ts`, `types/*.d.ts` | TypeScript type definitions and interfaces | `type-definition` | +| `services/*.ts`, `api/*.ts` | API client functions and data-fetching logic | `service` | +| `store/*.ts`, `slices/*.ts` | State management (Redux, Zustand, etc.) | `service`, `state` | +| `constants/*.ts` | Application-wide constants and enums | `config` | +| `__tests__/*.tsx`, `*.test.tsx`, `*.spec.tsx` | Unit and integration tests | `test` | + +### Edge Patterns to Look For + +**Component composition** — When a parent component renders a child component in its JSX return, create `contains` edges from the parent to the child. These edges represent the component tree hierarchy. + +**Hook usage** — When a component or hook imports and calls a custom hook (`useX`), create `depends_on` edges from the consumer to the hook module. Hooks are the primary mechanism for shared logic in React. + +**Context provider/consumer** — When a Context provider wraps components, create `publishes` edges from the provider to the context definition. When components call `useContext` or use a custom context hook, create `subscribes` edges from the consumer to the context. + +**Props drilling chains** — When props are passed through multiple component layers without being used, create `depends_on` edges along the chain to surface the coupling depth. + +### Architectural Layers for React + +Assign nodes to these layers when detected: + +| Layer ID | Layer Name | What Goes Here | +|---|---|---| +| `layer:ui` | UI Layer | `components/`, `pages/`, `views/`, layout components | +| `layer:service` | Service Layer | `hooks/`, `contexts/`, `services/`, `api/`, `store/` | +| `layer:types` | Types Layer | `types/`, shared TypeScript interfaces and type definitions | +| `layer:utility` | Utility Layer | `utils/`, `helpers/`, pure functions | +| `layer:config` | Config Layer | `App.tsx`, router configuration, provider setup, constants | +| `layer:test` | Test Layer | `__tests__/`, `*.test.tsx`, `*.spec.tsx` | + +### Notable Patterns to Capture in languageLesson + +- **Component composition over inheritance**: React favors composing components via props and children rather than class inheritance hierarchies +- **Custom hooks for reusable logic**: Hooks prefixed with `use` extract stateful logic into shareable modules without changing the component tree +- **React.memo for performance**: Components wrapped in `React.memo` skip re-renders when props are unchanged — indicates performance-sensitive paths +- **Controlled vs. uncontrolled components**: Controlled components derive state from props; uncontrolled components manage internal state via refs +- **Render props pattern**: Components that accept a function as children or a render prop to delegate rendering decisions to the consumer diff --git a/understand-anything-plugin/skills/understand/frameworks/spring.md b/understand-anything-plugin/skills/understand/frameworks/spring.md new file mode 100644 index 0000000..0c5bac4 --- /dev/null +++ b/understand-anything-plugin/skills/understand/frameworks/spring.md @@ -0,0 +1,59 @@ +# Spring Boot Framework Addendum + +> Injected into file-analyzer and architecture-analyzer prompts when Spring Boot is detected. +> Do NOT use as a standalone prompt — always appended to the base prompt template. + +## Spring Boot Project Structure + +When analyzing a Spring Boot project, apply these additional conventions on top of the base analysis rules. + +### Canonical File Roles + +| File / Pattern | Role | Tags | +|---|---|---| +| `*Application.java`, `*Application.kt` | Application entry point — `@SpringBootApplication` class with `main()` method | `entry-point`, `config` | +| `*Controller.java`, `*RestController.java` | REST controllers — handle HTTP requests, delegate to services | `api-handler` | +| `*Service.java` | Service interfaces — define business operation contracts | `service` | +| `*ServiceImpl.java` | Service implementations — contain business logic | `service` | +| `*Repository.java` | Spring Data repositories — data access interfaces extending JpaRepository/CrudRepository | `data-model` | +| `*Entity.java` | JPA entities — map to database tables via `@Entity` annotation | `data-model` | +| `*DTO.java`, `*Request.java`, `*Response.java` | Data transfer objects — request/response payloads | `type-definition` | +| `*Config.java`, `*Configuration.java` | Configuration classes — `@Configuration` beans, security config, web config | `config` | +| `*Filter.java` | Servlet filters — intercept requests before they reach controllers | `middleware` | +| `*Interceptor.java` | Handler interceptors — pre/post processing around controller methods | `middleware` | +| `*Advice.java`, `*ExceptionHandler.java` | Controller advice — global exception handling and response wrapping | `middleware` | +| `*Mapper.java` | Object mappers — convert between entities and DTOs (MapStruct, ModelMapper) | `utility` | +| `application.yml`, `application.properties` | Application configuration — profiles, datasource, server settings | `config` | +| `*Test.java`, `*Tests.java`, `*IT.java` | Unit tests, integration tests | `test` | + +### Edge Patterns to Look For + +**@Autowired injection** — When a class injects a dependency via `@Autowired`, constructor injection, or `@Inject`, create `depends_on` edges from the consumer to the injected bean. Constructor injection is preferred and most common in modern Spring. + +**Controller-Service-Repository chain** — The canonical call chain is `@RestController` -> `@Service` -> `@Repository`. Create `depends_on` edges along this chain to show the layered architecture. + +**@Entity relationships** — When entities define `@OneToMany`, `@ManyToOne`, `@OneToOne`, or `@ManyToMany` annotations, create `depends_on` edges between entity classes with descriptions indicating the relationship type and direction. + +**@Configuration bean definitions** — When a `@Configuration` class defines `@Bean` methods, create `configures` edges from the configuration class to the types it produces. These beans become available for injection throughout the application. + +### Architectural Layers for Spring Boot + +Assign nodes to these layers when detected: + +| Layer ID | Layer Name | What Goes Here | +|---|---|---| +| `layer:api` | API Layer | `*Controller.java`, REST endpoints, API documentation | +| `layer:service` | Service Layer | `*Service.java`, `*ServiceImpl.java`, business logic | +| `layer:data` | Data Layer | `*Repository.java`, `*Entity.java`, JPA mappings, database migrations | +| `layer:types` | Types Layer | `*DTO.java`, `*Request.java`, `*Response.java`, shared value objects | +| `layer:config` | Config Layer | `*Configuration.java`, `application.yml`, security config, `*Application.java` | +| `layer:middleware` | Middleware Layer | `*Filter.java`, `*Interceptor.java`, `*Advice.java`, security filters | +| `layer:test` | Test Layer | `*Test.java`, `*Tests.java`, `*IT.java`, test configuration | + +### Notable Patterns to Capture in languageLesson + +- **Dependency injection via constructor injection**: Spring favors constructor injection over field injection (`@Autowired` on fields) — it makes dependencies explicit, supports immutability, and simplifies testing +- **Layered architecture (Controller -> Service -> Repository)**: Spring Boot applications follow a strict layered pattern where controllers handle HTTP, services contain business logic, and repositories manage persistence +- **Spring Security filter chain**: Security is implemented as a chain of servlet filters — `SecurityFilterChain` beans configure authentication, authorization, CORS, and CSRF protection +- **JPA entity lifecycle**: Entities transition through states (transient, managed, detached, removed) — understanding this lifecycle is essential for tracing data flow through the persistence layer +- **AOP for cross-cutting concerns**: `@Aspect` classes with `@Before`, `@After`, and `@Around` advice inject behavior at join points — used for logging, transactions (`@Transactional`), and caching (`@Cacheable`) diff --git a/understand-anything-plugin/skills/understand/frameworks/vue.md b/understand-anything-plugin/skills/understand/frameworks/vue.md new file mode 100644 index 0000000..fdd3419 --- /dev/null +++ b/understand-anything-plugin/skills/understand/frameworks/vue.md @@ -0,0 +1,59 @@ +# Vue Framework Addendum + +> Injected into file-analyzer and architecture-analyzer prompts when Vue is detected. +> Do NOT use as a standalone prompt — always appended to the base prompt template. + +## Vue Project Structure + +When analyzing a Vue project, apply these additional conventions on top of the base analysis rules. + +### Canonical File Roles + +| File / Pattern | Role | Tags | +|---|---|---| +| `src/App.vue` | Root application component — mounts the top-level layout and router view | `entry-point`, `ui` | +| `src/main.ts`, `src/main.js` | Application bootstrap — creates Vue app instance, registers plugins, mounts to DOM | `entry-point`, `config` | +| `components/*.vue`, `components/**/*.vue` | Reusable UI components | `ui` | +| `views/*.vue`, `pages/*.vue` | Page-level components mapped to routes | `ui`, `routing` | +| `composables/*.ts`, `composables/*.js` | Composable functions — reusable stateful logic using Composition API | `service`, `utility` | +| `store/*.ts`, `stores/*.ts` | State management modules (Pinia stores or Vuex modules) | `service`, `state` | +| `router/*.ts`, `router/index.ts` | Vue Router configuration — route definitions, navigation guards | `config`, `routing` | +| `plugins/*.ts`, `plugins/*.js` | Vue plugin registrations — extend app functionality (i18n, auth, etc.) | `config` | +| `utils/*.ts`, `helpers/*.ts` | Pure utility functions | `utility` | +| `types/*.ts`, `types/*.d.ts` | TypeScript type definitions and interfaces | `type-definition` | +| `api/*.ts`, `services/*.ts` | API client functions and data-fetching logic | `service` | +| `directives/*.ts` | Custom Vue directives | `utility` | +| `tests/*.spec.ts`, `__tests__/*.spec.ts` | Unit and integration tests | `test` | + +### Edge Patterns to Look For + +**Component parent-child** — When a parent component uses a child component in its `