Add under-anything knowledge dashboard
15
Understand-Anything-main/.claude-plugin/marketplace.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "understand-anything",
|
||||||
|
"metadata": {
|
||||||
|
"description": "LLM-powered codebase analysis producing interactive knowledge graphs, guided tours, and deep-dive explanations"
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"name": "Lum1104"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "understand-anything",
|
||||||
|
"source": "./understand-anything-plugin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
Understand-Anything-main/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "understand-anything",
|
||||||
|
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
|
||||||
|
"version": "2.7.5",
|
||||||
|
"author": {
|
||||||
|
"name": "Lum1104"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/Lum1104/Understand-Anything",
|
||||||
|
"repository": "https://github.com/Lum1104/Understand-Anything",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"codebase-analysis",
|
||||||
|
"knowledge-graph",
|
||||||
|
"architecture",
|
||||||
|
"onboarding",
|
||||||
|
"dashboard"
|
||||||
|
]
|
||||||
|
}
|
||||||
14
Understand-Anything-main/.copilot-plugin/plugin.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "understand-anything",
|
||||||
|
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
|
||||||
|
"version": "2.7.5",
|
||||||
|
"author": {
|
||||||
|
"name": "Lum1104"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/Lum1104/Understand-Anything",
|
||||||
|
"repository": "https://github.com/Lum1104/Understand-Anything",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": ["codebase-analysis", "knowledge-graph", "architecture", "onboarding", "dashboard"],
|
||||||
|
"skills": "./understand-anything-plugin/skills/",
|
||||||
|
"agents": "./understand-anything-plugin/agents/"
|
||||||
|
}
|
||||||
15
Understand-Anything-main/.cursor-plugin/plugin.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "understand-anything",
|
||||||
|
"displayName": "Understand Anything",
|
||||||
|
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
|
||||||
|
"version": "2.7.5",
|
||||||
|
"author": {
|
||||||
|
"name": "Lum1104"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/Lum1104/Understand-Anything",
|
||||||
|
"repository": "https://github.com/Lum1104/Understand-Anything",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": ["codebase-analysis", "knowledge-graph", "architecture", "onboarding", "dashboard"],
|
||||||
|
"skills": "./understand-anything-plugin/skills/",
|
||||||
|
"agents": "./understand-anything-plugin/agents/"
|
||||||
|
}
|
||||||
1
Understand-Anything-main/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
patreon: Lum1104
|
||||||
36
Understand-Anything-main/.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: pnpm
|
||||||
|
cache-dependency-path: pnpm-lock.yaml
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: pnpm lint
|
||||||
|
|
||||||
|
- name: Build core
|
||||||
|
run: pnpm --filter @understand-anything/core build
|
||||||
|
|
||||||
|
- name: Build skill
|
||||||
|
run: pnpm --filter @understand-anything/skill build
|
||||||
|
|
||||||
|
- name: Test core
|
||||||
|
run: pnpm --filter @understand-anything/core test
|
||||||
|
|
||||||
|
- name: Test skill
|
||||||
|
run: pnpm test
|
||||||
68
Understand-Anything-main/.github/workflows/deploy-homepage.yml
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
name: Deploy Homepage
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'homepage/**'
|
||||||
|
- 'understand-anything-plugin/packages/dashboard/**'
|
||||||
|
- 'understand-anything-plugin/packages/core/**'
|
||||||
|
- '.github/workflows/deploy-homepage.yml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: pages
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: pnpm
|
||||||
|
cache-dependency-path: pnpm-lock.yaml
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Build homepage
|
||||||
|
working-directory: homepage
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Build core
|
||||||
|
run: pnpm --filter @understand-anything/core build
|
||||||
|
|
||||||
|
- name: Build demo dashboard
|
||||||
|
run: pnpm --filter @understand-anything/dashboard build:demo
|
||||||
|
env:
|
||||||
|
VITE_GRAPH_URL: ${{ vars.DEMO_GRAPH_URL }}
|
||||||
|
VITE_DOMAIN_GRAPH_URL: ${{ vars.DEMO_DOMAIN_GRAPH_URL }}
|
||||||
|
VITE_META_URL: ${{ vars.DEMO_META_URL }}
|
||||||
|
|
||||||
|
- name: Merge demo into homepage output
|
||||||
|
run: cp -r understand-anything-plugin/packages/dashboard/dist homepage/dist/demo
|
||||||
|
|
||||||
|
- uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: homepage/dist
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
steps:
|
||||||
|
- id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
16
Understand-Anything-main/.gitignore
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.understand-anything
|
||||||
|
*.tsbuildinfo
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
coverage/
|
||||||
|
*.log
|
||||||
|
__pycache__/
|
||||||
|
.claude/
|
||||||
|
.worktrees/
|
||||||
|
homepage/public/demo/
|
||||||
|
.private/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
2
Understand-Anything-main/.npmrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
shamefully-hoist=false
|
||||||
|
strict-peer-dependencies=false
|
||||||
98
Understand-Anything-main/CLAUDE.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Understand Anything
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
An open-source tool combining LLM intelligence + static analysis to produce interactive dashboards for understanding codebases.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- Node.js >= 22 (developed on v24)
|
||||||
|
- pnpm >= 10 (pinned via `packageManager` field in root `package.json`)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
- **Monorepo** with pnpm workspaces
|
||||||
|
- **understand-anything-plugin/** — Claude Code plugin containing all source code:
|
||||||
|
- **packages/core** — Shared analysis engine (types, persistence, tree-sitter, search, schema, tours, plugins)
|
||||||
|
- **packages/dashboard** — React + TypeScript web dashboard (React Flow, Zustand, TailwindCSS v4)
|
||||||
|
- **src/** — Skill TypeScript source for `/understand-chat`, `/understand-diff`, `/understand-explain`, `/understand-onboard`
|
||||||
|
- **skills/** — Skill definitions (`/understand`, `/understand-dashboard`, etc.)
|
||||||
|
- **agents/** — Agent definitions (project-scanner, file-analyzer, architecture-analyzer, tour-builder, graph-reviewer)
|
||||||
|
|
||||||
|
## Dashboard
|
||||||
|
- Dark luxury theme: deep blacks (#0a0a0a), gold/amber accents (#d4a574), DM Serif Display typography
|
||||||
|
- Graph-first layout: 75% graph + 360px right sidebar
|
||||||
|
- No ChatPanel or Monaco Editor
|
||||||
|
- Sidebar tabs: `Info` (ProjectOverview default → NodeInfo when node selected → LearnPanel in Learn persona, composing) and `Files` (FileExplorer tree built from the structural graph)
|
||||||
|
- Code viewer: prism-react-renderer source viewer that slides up from the bottom on file node click; an expand button promotes it into a full-screen modal. Source content is fetched from the dev server's `/file-content.json` endpoint, gated by access token + a graph-derived path allowlist
|
||||||
|
- Schema validation on graph load with error banner
|
||||||
|
|
||||||
|
## Agent Pipeline
|
||||||
|
- Agents write intermediate results to `.understand-anything/intermediate/` on disk (not returned to context)
|
||||||
|
- Agent model field is omitted from frontmatter so each platform falls back to its configured default — `inherit` was a Claude Code-only keyword that opencode (and similar tools) treated as a literal model id and rejected with `ProviderModelNotFoundError` (see #167)
|
||||||
|
- `/understand` auto-triggers `/understand-dashboard` after completion
|
||||||
|
- Intermediate files cleaned up after graph assembly
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
- `pnpm install` — Install all dependencies
|
||||||
|
- `pnpm --filter @understand-anything/core build` — Build the core package
|
||||||
|
- `pnpm --filter @understand-anything/core test` — Run core tests
|
||||||
|
- `pnpm --filter @understand-anything/skill build` — Build the plugin package
|
||||||
|
- `pnpm test` — Run all tests (skill tests live at repo-root `tests/skill/`, picked up by root `vitest.config.ts`)
|
||||||
|
- `pnpm --filter @understand-anything/dashboard build` — Build the dashboard
|
||||||
|
- `pnpm dev:dashboard` — Start dashboard dev server
|
||||||
|
- `pnpm lint` — Run ESLint across the project
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
- TypeScript strict mode everywhere
|
||||||
|
- Vitest for testing
|
||||||
|
- ESM modules (`"type": "module"`)
|
||||||
|
- Knowledge graph JSON lives in `.understand-anything/` directory of analyzed projects
|
||||||
|
- Core uses subpath exports (`./search`, `./types`, `./schema`) to avoid pulling Node.js modules into browser
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
- **tree-sitter**: Uses `web-tree-sitter` (WASM) instead of native `tree-sitter` — native bindings fail on darwin/arm64 + Node 24
|
||||||
|
- **Dashboard imports**: Dashboard must only import from core's browser-safe subpath exports (`./search`, `./types`, `./schema`), never the main entry point which pulls in Node.js modules
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
- `scripts/generate-large-graph.mjs` — Generates a fake knowledge graph for performance testing (e.g. large-graph layout). Writes to `.understand-anything/knowledge-graph.json`. Usage: `node scripts/generate-large-graph.mjs [nodeCount]` (default: 3000 nodes). Not part of the production pipeline.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
When pushing to remote, bump the version in **all five** of these files (keep them in sync):
|
||||||
|
- `understand-anything-plugin/package.json` → `"version"` field
|
||||||
|
- `understand-anything-plugin/.claude-plugin/plugin.json` → `"version"` field
|
||||||
|
- `.claude-plugin/plugin.json` → `"version"` field
|
||||||
|
- `.cursor-plugin/plugin.json` → `"version"` field
|
||||||
|
- `.copilot-plugin/plugin.json` → `"version"` field
|
||||||
|
|
||||||
|
Note: `.claude-plugin/marketplace.json` does **not** carry a version — the `plugins[]` entry only supports `name` and `source`, and adding other fields causes marketplace schema validation failures.
|
||||||
|
|
||||||
|
## Testing Local Plugin Changes
|
||||||
|
|
||||||
|
Claude Code caches installed plugins at `~/.claude/plugins/cache/understand-anything/understand-anything/<version>/`. 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 `<VERSION>` with the version from step 2:
|
||||||
|
```bash
|
||||||
|
rm -rf ~/.claude/plugins/cache/understand-anything/understand-anything/<VERSION>
|
||||||
|
cp -R ./understand-anything-plugin ~/.claude/plugins/cache/understand-anything/understand-anything/<VERSION>
|
||||||
|
```
|
||||||
|
|
||||||
|
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/<VERSION>/
|
||||||
|
```
|
||||||
|
|
||||||
|
**To revert to upstream:** Uninstall and reinstall the plugin from the marketplace — it repopulates the cache from the upstream repo.
|
||||||
274
Understand-Anything-main/CONTRIBUTING.md
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
# Contributing to Understand Anything
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to Understand Anything! This document provides guidelines and instructions for contributing to the project.
|
||||||
|
|
||||||
|
## 🌟 Ways to Contribute
|
||||||
|
|
||||||
|
- **Bug Reports**: Found a bug? Open an issue with detailed reproduction steps
|
||||||
|
- **Feature Requests**: Have an idea? Share it in the issues section
|
||||||
|
- **Documentation**: Improve or translate documentation
|
||||||
|
- **Code**: Fix bugs, add features, or improve performance
|
||||||
|
- **Testing**: Write tests to improve code coverage
|
||||||
|
|
||||||
|
## 🚀 Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Node.js >= 22 (developed on v24)
|
||||||
|
- pnpm >= 10 (pinned via `packageManager` field in root `package.json`)
|
||||||
|
- Git for version control
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. **Fork and Clone**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/YOUR_USERNAME/Understand-Anything.git
|
||||||
|
cd Understand-Anything
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install Dependencies**
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Build Core Package**
|
||||||
|
```bash
|
||||||
|
pnpm --filter @understand-anything/core build
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Run Tests**
|
||||||
|
```bash
|
||||||
|
pnpm --filter @understand-anything/core test
|
||||||
|
pnpm --filter @understand-anything/skill test
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Start Dashboard (Optional)**
|
||||||
|
```bash
|
||||||
|
pnpm dev:dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 Development Workflow
|
||||||
|
|
||||||
|
### 1. Create a Branch
|
||||||
|
|
||||||
|
Create a descriptive branch name:
|
||||||
|
```bash
|
||||||
|
git checkout -b feat/my-feature # For new features
|
||||||
|
git checkout -b fix/bug-description # For bug fixes
|
||||||
|
git checkout -b docs/update-readme # For documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Make Changes
|
||||||
|
|
||||||
|
- Write clean, readable code
|
||||||
|
- Follow existing code style and conventions
|
||||||
|
- Add tests for new functionality
|
||||||
|
- Update documentation as needed
|
||||||
|
|
||||||
|
### 3. Test Your Changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
pnpm --filter @understand-anything/core test
|
||||||
|
pnpm --filter @understand-anything/skill test
|
||||||
|
|
||||||
|
# Run linter
|
||||||
|
pnpm lint
|
||||||
|
|
||||||
|
# Build packages
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Commit Your Changes
|
||||||
|
|
||||||
|
Write clear, descriptive commit messages:
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "feat: add keyboard shortcuts to dashboard"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Commit Message Convention:**
|
||||||
|
- `feat:` - New feature
|
||||||
|
- `fix:` - Bug fix
|
||||||
|
- `docs:` - Documentation changes
|
||||||
|
- `style:` - Code style changes (formatting, etc.)
|
||||||
|
- `refactor:` - Code refactoring
|
||||||
|
- `test:` - Adding or updating tests
|
||||||
|
- `chore:` - Maintenance tasks
|
||||||
|
|
||||||
|
### 5. Push and Create Pull Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push origin your-branch-name
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open a Pull Request on GitHub with:
|
||||||
|
- Clear title describing the change
|
||||||
|
- Detailed description of what changed and why
|
||||||
|
- Link to related issues (if any)
|
||||||
|
- Screenshots (for UI changes)
|
||||||
|
|
||||||
|
## 🧪 Testing Guidelines
|
||||||
|
|
||||||
|
### Writing Tests
|
||||||
|
|
||||||
|
- Use Vitest for testing
|
||||||
|
- Place tests in `__tests__` directories or `*.test.ts` files
|
||||||
|
- Aim for high test coverage for new features
|
||||||
|
- Test edge cases and error conditions
|
||||||
|
|
||||||
|
Example test structure:
|
||||||
|
```typescript
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
describe('MyFeature', () => {
|
||||||
|
it('should do something', () => {
|
||||||
|
// Arrange
|
||||||
|
const input = 'test';
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = myFunction(input);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe('expected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
pnpm test
|
||||||
|
|
||||||
|
# Run tests for specific package
|
||||||
|
pnpm --filter @understand-anything/core test
|
||||||
|
|
||||||
|
# Run tests in watch mode
|
||||||
|
pnpm --filter @understand-anything/core test --watch
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Code Style Guidelines
|
||||||
|
|
||||||
|
### TypeScript
|
||||||
|
|
||||||
|
- Use TypeScript strict mode
|
||||||
|
- Define explicit types for function parameters and return values
|
||||||
|
- Avoid `any` type - use `unknown` if type is truly unknown
|
||||||
|
- Use interfaces for object shapes
|
||||||
|
- Use type aliases for unions and complex types
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
|
||||||
|
- The project uses ESLint for code quality
|
||||||
|
- Consistent indentation (2 spaces)
|
||||||
|
- Use meaningful variable and function names
|
||||||
|
- Keep functions small and focused
|
||||||
|
|
||||||
|
### React/Dashboard
|
||||||
|
|
||||||
|
- Use functional components with hooks
|
||||||
|
- Keep components focused and single-purpose
|
||||||
|
- Use Zustand for state management
|
||||||
|
- Follow the existing component structure
|
||||||
|
|
||||||
|
### Tech Stack
|
||||||
|
|
||||||
|
TypeScript, pnpm workspaces, React 18, Vite, TailwindCSS v4, React Flow, Zustand, web-tree-sitter, Fuse.js, Zod, Dagre
|
||||||
|
|
||||||
|
### File Organization
|
||||||
|
|
||||||
|
```
|
||||||
|
understand-anything-plugin/
|
||||||
|
├── packages/
|
||||||
|
│ ├── core/ # Core analysis engine
|
||||||
|
│ │ ├── src/
|
||||||
|
│ │ └── package.json
|
||||||
|
│ └── dashboard/ # React dashboard
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── components/
|
||||||
|
│ │ ├── utils/
|
||||||
|
│ │ └── store.ts
|
||||||
|
│ └── package.json
|
||||||
|
├── src/ # Plugin skills implementation
|
||||||
|
├── agents/ # AI agent prompts
|
||||||
|
└── skills/ # Skill definitions
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌍 Translation Guidelines
|
||||||
|
|
||||||
|
### Adding a New Language
|
||||||
|
|
||||||
|
1. Create `README.{language-code}.md` (e.g., `README.fr-FR.md`)
|
||||||
|
2. Translate all sections while maintaining formatting
|
||||||
|
3. Update main `README.md` to include language link
|
||||||
|
4. Keep technical terms in English where appropriate
|
||||||
|
5. Ensure all links still work
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```markdown
|
||||||
|
<a href="README.md">English</a> | <a href="README.fr-FR.md">Français</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐛 Bug Reports
|
||||||
|
|
||||||
|
When reporting bugs, include:
|
||||||
|
|
||||||
|
- **Description**: Clear description of the issue
|
||||||
|
- **Steps to Reproduce**: Detailed steps to reproduce the bug
|
||||||
|
- **Expected Behavior**: What you expected to happen
|
||||||
|
- **Actual Behavior**: What actually happened
|
||||||
|
- **Environment**: OS, Node version, pnpm version
|
||||||
|
- **Screenshots**: If applicable
|
||||||
|
- **Error Messages**: Full error output
|
||||||
|
|
||||||
|
## 💡 Feature Requests
|
||||||
|
|
||||||
|
When requesting features:
|
||||||
|
|
||||||
|
- **Use Case**: Describe the problem you're trying to solve
|
||||||
|
- **Proposed Solution**: How you envision the feature working
|
||||||
|
- **Alternatives**: Other solutions you've considered
|
||||||
|
- **Additional Context**: Any other relevant information
|
||||||
|
|
||||||
|
## 📋 Pull Request Checklist
|
||||||
|
|
||||||
|
Before submitting a PR, ensure:
|
||||||
|
|
||||||
|
- [ ] Code follows the project's style guidelines
|
||||||
|
- [ ] All tests pass (`pnpm test`)
|
||||||
|
- [ ] New code has test coverage
|
||||||
|
- [ ] Documentation is updated (if needed)
|
||||||
|
- [ ] Commit messages follow convention
|
||||||
|
- [ ] PR description clearly explains changes
|
||||||
|
- [ ] No console.log or debug code left behind
|
||||||
|
- [ ] Branch is up to date with main
|
||||||
|
|
||||||
|
## 🤝 Code Review Process
|
||||||
|
|
||||||
|
1. **Automated Checks**: CI runs tests and linting
|
||||||
|
2. **Maintainer Review**: Project maintainers review the code
|
||||||
|
3. **Feedback**: Address any requested changes
|
||||||
|
4. **Approval**: Once approved, PR will be merged
|
||||||
|
5. **Cleanup**: Delete your branch after merge
|
||||||
|
|
||||||
|
## 📞 Getting Help
|
||||||
|
|
||||||
|
- **Issues**: For bugs and feature requests
|
||||||
|
- **Discussions**: For questions and general discussion
|
||||||
|
- **Documentation**: Check existing docs first
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
By contributing, you agree that your contributions will be licensed under the MIT License.
|
||||||
|
|
||||||
|
## 🙏 Recognition
|
||||||
|
|
||||||
|
Contributors will be recognized in:
|
||||||
|
- GitHub contributors list
|
||||||
|
- Release notes (for significant contributions)
|
||||||
|
- Special mentions for exceptional contributions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Thank you for contributing to Understand Anything! Your contributions help make code understanding accessible to everyone.** 🚀
|
||||||
21
Understand-Anything-main/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Yuxiang Lin
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
348
Understand-Anything-main/README.md
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
<h1 align="center">Understand Anything</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>Turn any codebase, knowledge base, or docs into an interactive knowledge graph you can explore, search, and ask questions about.</strong>
|
||||||
|
<br />
|
||||||
|
<em>Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, and more.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Lum1104%2FUnderstand-Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="README.md">English</a> | <a href="READMEs/README.zh-CN.md">简体中文</a> | <a href="READMEs/README.zh-TW.md">繁體中文</a> | <a href="READMEs/README.ja-JP.md">日本語</a> | <a href="READMEs/README.ko-KR.md">한국어</a> | <a href="READMEs/README.es-ES.md">Español</a> | <a href="READMEs/README.tr-TR.md">Türkçe</a> | <a href="READMEs/README.ru-RU.md">Русский</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#-quick-start"><img src="https://img.shields.io/badge/Quick_Start-blue" alt="Quick Start" /></a>
|
||||||
|
<a href="https://github.com/Lum1104/Understand-Anything/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License: MIT" /></a>
|
||||||
|
<a href="https://docs.anthropic.com/en/docs/claude-code"><img src="https://img.shields.io/badge/Claude_Code-8A2BE2" alt="Claude Code" /></a>
|
||||||
|
<a href="#codex"><img src="https://img.shields.io/badge/Codex-000000" alt="Codex" /></a>
|
||||||
|
<a href="#vs-code--github-copilot"><img src="https://img.shields.io/badge/Copilot-24292e" alt="Copilot" /></a>
|
||||||
|
<a href="#copilot-cli"><img src="https://img.shields.io/badge/Copilot_CLI-24292e" alt="Copilot CLI" /></a>
|
||||||
|
<a href="#gemini-cli"><img src="https://img.shields.io/badge/Gemini_CLI-4285F4" alt="Gemini CLI" /></a>
|
||||||
|
<a href="#opencode"><img src="https://img.shields.io/badge/OpenCode-38bdf8" alt="OpenCode" /></a>
|
||||||
|
<a href="#mistral-vibe-cli"><img src="https://img.shields.io/badge/Vibe_CLI-7c3aed" alt="Vibe CLI" /></a>
|
||||||
|
<a href="#trae"><img src="https://img.shields.io/badge/Trae-7e22ce" alt="Trae" /></a>
|
||||||
|
<a href="https://understand-anything.com"><img src="https://img.shields.io/badge/Homepage-d4a574" alt="Homepage" /></a>
|
||||||
|
<a href="https://understand-anything.com/demo/"><img src="https://img.shields.io/badge/Live_Demo-00c853" alt="Live Demo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="assets/hero.png" alt="Understand Anything — Turn any codebase into an interactive knowledge graph" width="800" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>💬 <a href="https://discord.gg/pydat66RY">Join the Discord community →</a></strong>
|
||||||
|
<br />
|
||||||
|
<em>Ask questions, share what you've built, get help from the community.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**You just joined a new team. The codebase is 200,000 lines of code. Where do you even start?**
|
||||||
|
|
||||||
|
Understand Anything is a [Claude Code Plugin](https://code.claude.com/docs/en/plugins-reference#plugins-reference) that analyzes your project with a multi-agent pipeline, builds a knowledge graph of every file, function, class, and dependency, then gives you an interactive dashboard to explore it all visually. Stop reading code blind. Start seeing the big picture.
|
||||||
|
|
||||||
|
> **The goal isn't a graph that wows you with how complex your codebase is — it's a graph that quietly teaches you how every piece fits together.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **Want to skip the reading?** Try the [live demo](https://understand-anything.com/demo/) in our [homepage](https://understand-anything.com/) — a fully interactive dashboard you can pan, zoom, search, and explore right in your browser.
|
||||||
|
|
||||||
|
### Explore the structural graph
|
||||||
|
|
||||||
|
Navigate your codebase as an interactive knowledge graph — every file, function, and class is a node you can click, search, and explore. Select any node to see plain-English summaries, relationships, and guided tours.
|
||||||
|
|
||||||
|
### Understand business logic
|
||||||
|
|
||||||
|
Switch to the domain view and see how your code maps to real business processes — domains, flows, and steps laid out as a horizontal graph.
|
||||||
|
|
||||||
|
### Analyze knowledge bases
|
||||||
|
|
||||||
|
Point `/understand-knowledge` at a [Karpathy-pattern LLM wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) and get a force-directed knowledge graph with community clustering. The deterministic parser extracts wikilinks and categories from `index.md`, then LLM agents discover implicit relationships, extract entities, and surface claims — turning your wiki into a navigable graph of interconnected ideas.
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🧭 Guided Tours</h3>
|
||||||
|
<p>Auto-generated walkthroughs of the architecture, ordered by dependency. Learn the codebase in the right order.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🔍 Fuzzy & Semantic Search</h3>
|
||||||
|
<p>Find anything by name or by meaning. Search "which parts handle auth?" and get relevant results across the graph.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📊 Diff Impact Analysis</h3>
|
||||||
|
<p>See which parts of the system your changes affect before you commit. Understand ripple effects across the codebase.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🎭 Persona-Adaptive UI</h3>
|
||||||
|
<p>The dashboard adjusts its detail level based on who you are — junior dev, PM, or power user.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🏗️ Layer Visualization</h3>
|
||||||
|
<p>Automatic grouping by architectural layer — API, Service, Data, UI, Utility — with color-coded legend.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📚 Language Concepts</h3>
|
||||||
|
<p>12 programming patterns (generics, closures, decorators, etc.) explained in context wherever they appear.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
### 1. Install the plugin
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Analyze your codebase
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand
|
||||||
|
```
|
||||||
|
|
||||||
|
A multi-agent pipeline scans your project, extracts every file, function, class, and dependency, then builds a knowledge graph saved to `.understand-anything/knowledge-graph.json`.
|
||||||
|
|
||||||
|
**Localized output:** Use `--language` to generate content in your preferred language:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate Chinese content (知识图节点描述和 Dashboard UI)
|
||||||
|
/understand --language zh
|
||||||
|
|
||||||
|
# Supported languages: en (default), zh, zh-TW, ja, ko, ru
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--language` parameter affects:
|
||||||
|
- Node summaries and descriptions in the knowledge graph
|
||||||
|
- Dashboard UI labels, buttons, and tooltips
|
||||||
|
- Guided tour explanations
|
||||||
|
|
||||||
|
### 3. Explore the dashboard
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
An interactive web dashboard opens with your codebase visualized as a graph — color-coded by architectural layer, searchable, and clickable. Select any node to see its code, relationships, and a plain-English explanation.
|
||||||
|
|
||||||
|
### 4. Keep learning
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ask anything about the codebase
|
||||||
|
/understand-chat How does the payment flow work?
|
||||||
|
|
||||||
|
# Analyze impact of your current changes
|
||||||
|
/understand-diff
|
||||||
|
|
||||||
|
# Deep-dive into a specific file or function
|
||||||
|
/understand-explain src/auth/login.ts
|
||||||
|
|
||||||
|
# Generate an onboarding guide for new team members
|
||||||
|
/understand-onboard
|
||||||
|
|
||||||
|
# Extract business domain knowledge (domains, flows, steps)
|
||||||
|
/understand-domain
|
||||||
|
|
||||||
|
# Analyze a Karpathy-pattern LLM wiki knowledge base
|
||||||
|
/understand-knowledge ~/path/to/wiki
|
||||||
|
|
||||||
|
# Re-run anytime — incremental by default (only re-analyzes changed files)
|
||||||
|
/understand
|
||||||
|
|
||||||
|
# Auto-update on every commit via a post-commit hook
|
||||||
|
/understand --auto-update
|
||||||
|
|
||||||
|
# Scope to a subdirectory (for huge monorepos)
|
||||||
|
/understand src/frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Multi-Platform Installation
|
||||||
|
|
||||||
|
Understand-Anything works across multiple AI coding platforms.
|
||||||
|
|
||||||
|
### Claude Code (Native)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### One-line install (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Trae)
|
||||||
|
|
||||||
|
**macOS / Linux:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
|
||||||
|
# or skip the prompt by passing the platform:
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows (PowerShell):**
|
||||||
|
```powershell
|
||||||
|
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
|
||||||
|
```
|
||||||
|
|
||||||
|
The installer clones the repo to `~/.understand-anything/repo` and creates the right symlinks for the chosen platform. Restart your CLI/IDE afterwards.
|
||||||
|
|
||||||
|
- Supported `<platform>` values: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `trae`
|
||||||
|
- Update later: `./install.sh --update`
|
||||||
|
- Uninstall: `./install.sh --uninstall <platform>`
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
Cursor auto-discovers the plugin via `.cursor-plugin/plugin.json` when this repo is cloned. No manual installation needed — just clone and open in Cursor.
|
||||||
|
|
||||||
|
If auto-discovery doesn't pick it up, install it manually: open **Cursor Settings → Plugins**, paste `https://github.com/Lum1104/Understand-Anything` into the search field, and add it from there.
|
||||||
|
|
||||||
|
### VS Code + GitHub Copilot
|
||||||
|
|
||||||
|
VS Code with GitHub Copilot (v1.108+) auto-discovers the plugin via `.copilot-plugin/plugin.json` when this repo is cloned. No manual installation needed — just clone and open in VS Code.
|
||||||
|
|
||||||
|
For personal skills (available across all projects), run the `install.sh` above with the `vscode` platform.
|
||||||
|
|
||||||
|
### Copilot CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Platform Compatibility
|
||||||
|
|
||||||
|
| Platform | Status | Install Method |
|
||||||
|
|----------|--------|----------------|
|
||||||
|
| Claude Code | ✅ Native | Plugin marketplace |
|
||||||
|
| Cursor | ✅ Supported | Auto-discovery |
|
||||||
|
| VS Code + GitHub Copilot | ✅ Supported | Auto-discovery |
|
||||||
|
| Copilot CLI | ✅ Supported | Plugin install |
|
||||||
|
| Codex | ✅ Supported | `install.sh codex` |
|
||||||
|
| OpenCode | ✅ Supported | `install.sh opencode` |
|
||||||
|
| OpenClaw | ✅ Supported | `install.sh openclaw` |
|
||||||
|
| Antigravity | ✅ Supported | `install.sh antigravity` |
|
||||||
|
| Gemini CLI | ✅ Supported | `install.sh gemini` |
|
||||||
|
| Pi Agent | ✅ Supported | `install.sh pi` |
|
||||||
|
| Vibe CLI | ✅ Supported | `install.sh vibe` |
|
||||||
|
| Hermes | ✅ Supported | `install.sh hermes` |
|
||||||
|
| Cline | ✅ Supported | `install.sh cline` |
|
||||||
|
| KIMI CLI | ✅ Supported | `install.sh kimi` |
|
||||||
|
| Trae | ✅ Supported | `install.sh trae` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Share the Graph with Your Team
|
||||||
|
|
||||||
|
The graph is just JSON — **commit it once, and teammates skip the pipeline**. Good for onboarding, PR reviews, and docs-as-code.
|
||||||
|
|
||||||
|
> **Example:** [GoogleCloudPlatform/microservices-demo (fork)](https://github.com/Lum1104/microservices-demo) — Go / Java / Python / Node reference with a committed graph.
|
||||||
|
|
||||||
|
**What to commit:** everything in `.understand-anything/` *except* `intermediate/` and `diff-overlay.json` (those are local scratch).
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
.understand-anything/intermediate/
|
||||||
|
.understand-anything/diff-overlay.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keep it fresh:** enable `/understand --auto-update` — a post-commit hook incrementally patches the graph so each commit lands with a matching graph. Or re-run `/understand` manually before releases.
|
||||||
|
|
||||||
|
**Large graphs (10 MB+):** track with **git-lfs**.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git lfs install
|
||||||
|
git lfs track ".understand-anything/*.json"
|
||||||
|
git add .gitattributes .understand-anything/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Under the Hood
|
||||||
|
|
||||||
|
### Tree-sitter + LLM hybrid
|
||||||
|
|
||||||
|
Static analysis and LLMs do what each does best:
|
||||||
|
|
||||||
|
- **Tree-sitter (deterministic)** — parses source into a concrete syntax tree and extracts structural facts: imports, exports, function/class definitions, call sites, inheritance. Pre-resolved into an `importMap` during the scan phase and passed to file-analyzers so they don't re-derive imports from source. Same input → same output, every run. Also powers fingerprint-based change detection for incremental updates.
|
||||||
|
- **LLM (semantic)** — reads the parsed structure alongside the original source to produce what parsers can't: plain-English summaries, tags, architectural layer assignments, business-domain mapping, guided tours, language concept callouts.
|
||||||
|
|
||||||
|
This split is why the graph is reproducible on the structural side (the same code always yields the same edges) while still capturing intent on the semantic side (what a file is *for*, not just what it imports).
|
||||||
|
|
||||||
|
### Multi-Agent Pipeline
|
||||||
|
|
||||||
|
The `/understand` command orchestrates 5 specialized agents, and `/understand-domain` adds a 6th:
|
||||||
|
|
||||||
|
| Agent | Role |
|
||||||
|
|-------|------|
|
||||||
|
| `project-scanner` | Discover files, detect languages and frameworks |
|
||||||
|
| `file-analyzer` | Extract functions, classes, imports; produce graph nodes and edges |
|
||||||
|
| `architecture-analyzer` | Identify architectural layers |
|
||||||
|
| `tour-builder` | Generate guided learning tours |
|
||||||
|
| `graph-reviewer` | Validate graph completeness and referential integrity (runs inline by default; use `--review` for full LLM review) |
|
||||||
|
| `domain-analyzer` | Extract business domains, flows, and process steps (used by `/understand-domain`) |
|
||||||
|
| `article-analyzer` | Extract entities, claims, and implicit relationships from wiki articles (used by `/understand-knowledge`) |
|
||||||
|
|
||||||
|
File analyzers run in parallel (up to 5 concurrent, 20-30 files per batch). Supports incremental updates — only re-analyzes files that changed since the last run.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎥 Community
|
||||||
|
|
||||||
|
A community-made walkthrough by **Better Stack**.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.youtube.com/watch?v=VmIUXVlt7_I"><img src="https://img.youtube.com/vi/VmIUXVlt7_I/maxresdefault.jpg" alt="Community walkthrough by Better Stack — watch on YouTube" width="480" /></a>
|
||||||
|
<br />
|
||||||
|
<em><a href="https://www.youtube.com/watch?v=VmIUXVlt7_I">Watch on YouTube →</a></em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
Made a video, blog post, or tutorial? Open an issue or PR — happy to feature it here.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Here's how to get started:
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch (`git checkout -b feature/my-feature`)
|
||||||
|
3. Run the tests (`pnpm --filter @understand-anything/core test`)
|
||||||
|
4. Commit your changes and open a pull request
|
||||||
|
|
||||||
|
Please open an issue first for major changes so we can discuss the approach.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>Stop reading code blind. Start understanding everything.</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&theme=dark&legend=top-left" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<em>Thanks to everyone who's used and contributed — knowing this saves people time is what made it worth building.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
MIT License © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||||
|
</p>
|
||||||
344
Understand-Anything-main/READMEs/README.es-ES.md
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
<h1 align="center">Understand Anything</h1>
|
||||||
|
<p align="center">
|
||||||
|
<strong>Convierte cualquier código fuente, base de conocimiento o documentación en un grafo de conocimiento interactivo que puedes explorar, buscar y consultar.</strong>
|
||||||
|
<br />
|
||||||
|
<em>Compatible con Claude Code, Codex, Cursor, Copilot, Gemini CLI y más.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Lum1104%2FUnderstand-Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="../README.md">English</a> | <a href="README.zh-CN.md">简体中文</a> | <a href="README.zh-TW.md">繁體中文</a> | <a href="README.ja-JP.md">日本語</a> | <a href="README.ko-KR.md">한국어</a> | <a href="README.es-ES.md">Español</a> | <a href="README.tr-TR.md">Türkçe</a> | <a href="README.ru-RU.md">Русский</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#-inicio-rápido"><img src="https://img.shields.io/badge/Inicio_Rápido-blue" alt="Quick Start" /></a>
|
||||||
|
<a href="https://github.com/Lum1104/Understand-Anything/blob/main/LICENSE"><img src="https://img.shields.io/badge/Licencia-MIT-yellow" alt="License: MIT" /></a>
|
||||||
|
<a href="https://docs.anthropic.com/en/docs/claude-code"><img src="https://img.shields.io/badge/Claude_Code-8A2BE2" alt="Claude Code" /></a>
|
||||||
|
<a href="#codex"><img src="https://img.shields.io/badge/Codex-000000" alt="Codex" /></a>
|
||||||
|
<a href="#vs-code--github-copilot"><img src="https://img.shields.io/badge/Copilot-24292e" alt="Copilot" /></a>
|
||||||
|
<a href="#copilot-cli"><img src="https://img.shields.io/badge/Copilot_CLI-24292e" alt="Copilot CLI" /></a>
|
||||||
|
<a href="#gemini-cli"><img src="https://img.shields.io/badge/Gemini_CLI-4285F4" alt="Gemini CLI" /></a>
|
||||||
|
<a href="#opencode"><img src="https://img.shields.io/badge/OpenCode-38bdf8" alt="OpenCode" /></a>
|
||||||
|
<a href="https://understand-anything.com"><img src="https://img.shields.io/badge/Página_Principal-d4a574" alt="Homepage" /></a>
|
||||||
|
<a href="https://understand-anything.com/demo/"><img src="https://img.shields.io/badge/Demo_en_Vivo-00c853" alt="Live Demo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="../assets/hero.png" alt="Understand Anything — Convierte cualquier código fuente en un grafo de conocimiento interactivo" width="800" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>💬 <a href="https://discord.gg/pydat66RY">Únete a la comunidad de Discord →</a></strong>
|
||||||
|
<br />
|
||||||
|
<em>Pregunta, comparte lo que construyes y recibe ayuda de la comunidad.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Acabas de unirte a un nuevo equipo. El código tiene 200,000 líneas. ¿Por dónde empiezas?**
|
||||||
|
|
||||||
|
Understand Anything es un [Claude Code Plugin](https://code.claude.com/docs/en/plugins-reference#plugins-reference) que analiza tu proyecto con un pipeline multi-agente, construye un grafo de conocimiento de cada archivo, función, clase y dependencia, y luego te ofrece un panel interactivo para explorarlo visualmente. Deja de leer código a ciegas. Empieza a ver el panorama completo.
|
||||||
|
|
||||||
|
> **El objetivo no es un grafo que te impresione mostrándote lo complejo que es tu código — es un grafo que, sin alardes, te enseña cómo encaja cada pieza.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Características
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **¿Quieres probarlo directamente?** Prueba la [demo en vivo](https://understand-anything.com/demo/) en nuestra [página principal](https://understand-anything.com/) — un panel interactivo donde puedes navegar, hacer zoom, buscar y explorar directamente en tu navegador.
|
||||||
|
|
||||||
|
### Explora el grafo estructural
|
||||||
|
|
||||||
|
Navega tu código como un grafo de conocimiento interactivo: cada archivo, función y clase es un nodo que puedes hacer clic, buscar y explorar. Selecciona cualquier nodo para ver resúmenes en lenguaje natural, relaciones y recorridos guiados.
|
||||||
|
|
||||||
|
### Comprende la lógica de negocio
|
||||||
|
|
||||||
|
Cambia a la vista de dominio y observa cómo tu código se mapea a procesos de negocio reales: dominios, flujos y pasos representados como un grafo horizontal.
|
||||||
|
|
||||||
|
### Analiza bases de conocimiento
|
||||||
|
|
||||||
|
Apunta `/understand-knowledge` a un [wiki LLM con patrón Karpathy](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) y obtén un grafo de conocimiento dirigido por fuerzas con agrupación por comunidad. El parser determinístico extrae wikilinks y categorías de `index.md`, luego los agentes LLM descubren relaciones implícitas, extraen entidades y revelan afirmaciones, convirtiendo tu wiki en un grafo navegable de ideas interconectadas.
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🧭 Recorridos Guiados</h3>
|
||||||
|
<p>Recorridos generados automáticamente de la arquitectura, ordenados por dependencia. Aprende el código en el orden correcto.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🔍 Búsqueda Difusa y Semántica</h3>
|
||||||
|
<p>Encuentra cualquier cosa por nombre o por significado. Busca "¿qué partes manejan la autenticación?" y obtén resultados relevantes en todo el grafo.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📊 Análisis de Impacto de Cambios</h3>
|
||||||
|
<p>Visualiza qué partes del sistema afectan tus cambios antes de hacer commit. Comprende los efectos en cascada a través del código.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🎭 Interfaz Adaptativa por Persona</h3>
|
||||||
|
<p>El panel ajusta su nivel de detalle según quién eres: desarrollador junior, PM o usuario avanzado.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🏗️ Visualización por Capas</h3>
|
||||||
|
<p>Agrupación automática por capa arquitectónica — API, Servicio, Datos, UI, Utilidades — con leyenda codificada por colores.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📚 Conceptos del Lenguaje</h3>
|
||||||
|
<p>12 patrones de programación (genéricos, closures, decoradores, etc.) explicados en contexto donde aparecen.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Inicio Rápido
|
||||||
|
|
||||||
|
### 1. Instala el plugin
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Analiza tu código
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand
|
||||||
|
```
|
||||||
|
|
||||||
|
Un pipeline multi-agente escanea tu proyecto, extrae cada archivo, función, clase y dependencia, y construye un grafo de conocimiento guardado en `.understand-anything/knowledge-graph.json`.
|
||||||
|
|
||||||
|
**Salida localizada:** Usa `--language` para generar contenido en tu idioma preferido:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Genera contenido en el idioma preferido (descripciones de nodos y UI del dashboard)
|
||||||
|
/understand --language en
|
||||||
|
|
||||||
|
# Idiomas soportados: en (default), zh, zh-TW, ja, ko, ru
|
||||||
|
```
|
||||||
|
|
||||||
|
El parámetro `--language` afecta:
|
||||||
|
- Resúmenes y descripciones de nodos en el grafo de conocimiento
|
||||||
|
- Etiquetas, botones y tooltips de la UI del dashboard
|
||||||
|
- Explicaciones de los tours guiados
|
||||||
|
|
||||||
|
### 3. Explora el panel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
Se abre un panel web interactivo con tu código visualizado como un grafo, codificado por colores según la capa arquitectónica, con funciones de búsqueda y clic. Selecciona cualquier nodo para ver su código, relaciones y una explicación en lenguaje natural.
|
||||||
|
|
||||||
|
### 4. Sigue aprendiendo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pregunta cualquier cosa sobre el código
|
||||||
|
/understand-chat How does the payment flow work?
|
||||||
|
|
||||||
|
# Analiza el impacto de tus cambios actuales
|
||||||
|
/understand-diff
|
||||||
|
|
||||||
|
# Profundiza en un archivo o función específica
|
||||||
|
/understand-explain src/auth/login.ts
|
||||||
|
|
||||||
|
# Genera una guía de incorporación para nuevos miembros del equipo
|
||||||
|
/understand-onboard
|
||||||
|
|
||||||
|
# Extrae conocimiento de dominio de negocio (dominios, flujos, pasos)
|
||||||
|
/understand-domain
|
||||||
|
|
||||||
|
# Analiza un wiki LLM con patrón Karpathy
|
||||||
|
/understand-knowledge ~/path/to/wiki
|
||||||
|
|
||||||
|
# Vuelve a ejecutarlo cuando quieras — incremental por defecto (solo archivos modificados)
|
||||||
|
/understand
|
||||||
|
|
||||||
|
# Instala un hook post-commit para actualizaciones incrementales automáticas
|
||||||
|
/understand --auto-update
|
||||||
|
|
||||||
|
# Acota el análisis a un subdirectorio (útil para monorepos enormes)
|
||||||
|
/understand src/frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Instalación Multiplataforma
|
||||||
|
|
||||||
|
Understand-Anything funciona en múltiples plataformas de codificación con IA.
|
||||||
|
|
||||||
|
### Claude Code (Nativo)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### Instalación de una línea (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI)
|
||||||
|
|
||||||
|
**macOS / Linux:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
|
||||||
|
# o pasa la plataforma directamente para saltar el prompt:
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows (PowerShell):**
|
||||||
|
```powershell
|
||||||
|
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
|
||||||
|
```
|
||||||
|
|
||||||
|
El instalador clona el repositorio en `~/.understand-anything/repo` y crea los enlaces simbólicos correspondientes para la plataforma elegida. Reinicia tu CLI/IDE al terminar.
|
||||||
|
|
||||||
|
- Valores soportados de `<platform>`: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`
|
||||||
|
- Actualizar más adelante: `./install.sh --update`
|
||||||
|
- Desinstalar: `./install.sh --uninstall <platform>`
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
Cursor detecta automáticamente el plugin a través de `.cursor-plugin/plugin.json` cuando se clona este repositorio. No requiere instalación manual: simplemente clona y abre en Cursor.
|
||||||
|
|
||||||
|
Si la detección automática no lo reconoce, instálalo manualmente: abre **Cursor Settings → Plugins**, pega `https://github.com/Lum1104/Understand-Anything` en el campo de búsqueda y añádelo desde allí.
|
||||||
|
|
||||||
|
### VS Code + GitHub Copilot
|
||||||
|
|
||||||
|
VS Code con GitHub Copilot (v1.108+) detecta automáticamente el plugin a través de `.copilot-plugin/plugin.json` cuando se clona este repositorio. No requiere instalación manual: simplemente clona y abre en VS Code.
|
||||||
|
|
||||||
|
Para habilidades personales (disponibles en todos los proyectos), ejecuta el `install.sh` de arriba con la plataforma `vscode`.
|
||||||
|
|
||||||
|
### Copilot CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compatibilidad de Plataformas
|
||||||
|
|
||||||
|
| Plataforma | Estado | Método de Instalación |
|
||||||
|
|----------|--------|----------------|
|
||||||
|
| Claude Code | ✅ Nativo | Marketplace de plugins |
|
||||||
|
| Cursor | ✅ Soportado | Detección automática |
|
||||||
|
| VS Code + GitHub Copilot | ✅ Soportado | Detección automática |
|
||||||
|
| Copilot CLI | ✅ Soportado | Instalación de plugin |
|
||||||
|
| Codex | ✅ Soportado | `install.sh codex` |
|
||||||
|
| OpenCode | ✅ Soportado | `install.sh opencode` |
|
||||||
|
| OpenClaw | ✅ Soportado | `install.sh openclaw` |
|
||||||
|
| Antigravity | ✅ Soportado | `install.sh antigravity` |
|
||||||
|
| Gemini CLI | ✅ Soportado | `install.sh gemini` |
|
||||||
|
| Pi Agent | ✅ Soportado | `install.sh pi` |
|
||||||
|
| Vibe CLI | ✅ Soportado | `install.sh vibe` |
|
||||||
|
| Hermes | ✅ Soportado | `install.sh hermes` |
|
||||||
|
| Cline | ✅ Soportado | `install.sh cline` |
|
||||||
|
| KIMI CLI | ✅ Soportado | `install.sh kimi` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Comparte el Grafo con tu Equipo
|
||||||
|
|
||||||
|
El grafo es solo JSON — **confírmalo una vez y tus compañeros se saltan el pipeline**. Ideal para onboarding, revisiones de PR y flujos docs-as-code.
|
||||||
|
|
||||||
|
> **Ejemplo:** [GoogleCloudPlatform/microservices-demo (fork)](https://github.com/Lum1104/microservices-demo) — referencia políglota (Go / Java / Python / Node) con el grafo ya confirmado.
|
||||||
|
|
||||||
|
**Qué confirmar:** todo lo que hay en `.understand-anything/` *excepto* `intermediate/` y `diff-overlay.json` (archivos temporales locales).
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
.understand-anything/intermediate/
|
||||||
|
.understand-anything/diff-overlay.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mantenlo al día:** activa `/understand --auto-update` — un hook post-commit parchea el grafo de forma incremental, así cada commit llega con su grafo correspondiente. O vuelve a ejecutar `/understand` manualmente antes de cada release.
|
||||||
|
|
||||||
|
**Grafos grandes (10 MB o más):** úsalos con **git-lfs**.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git lfs install
|
||||||
|
git lfs track ".understand-anything/*.json"
|
||||||
|
git add .gitattributes .understand-anything/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Bajo el Capó
|
||||||
|
|
||||||
|
### Híbrido Tree-sitter + LLM
|
||||||
|
|
||||||
|
Lo determinista lo hace el análisis estático, lo semántico lo hace el LLM:
|
||||||
|
|
||||||
|
- **Tree-sitter (determinista)** — parsea el código a un árbol de sintaxis concreto y extrae hechos estructurales: imports, exports, definiciones de funciones/clases, llamadas, herencia. Se preresuelve como `importMap` en la fase de escaneo y se pasa al file-analyzer para que no tenga que volver a derivar los imports desde el código fuente. La misma entrada siempre produce la misma salida, y también es la base de los fingerprints usados para las actualizaciones incrementales.
|
||||||
|
- **LLM (semántico)** — lee la estructura parseada junto con el código original para producir lo que los parsers no pueden: resúmenes en lenguaje natural, etiquetas, asignaciones de capa arquitectónica, mapeo de dominios de negocio, tours guiados, notas sobre conceptos del lenguaje.
|
||||||
|
|
||||||
|
Esta división es la que hace que el grafo sea reproducible en lo estructural (el mismo código siempre genera las mismas aristas) y a la vez capture intención en lo semántico (para *qué* sirve un archivo, no solo qué importa).
|
||||||
|
|
||||||
|
### Pipeline Multi-Agente
|
||||||
|
|
||||||
|
El comando `/understand` orquesta 5 agentes especializados, y `/understand-domain` añade un sexto:
|
||||||
|
|
||||||
|
| Agente | Rol |
|
||||||
|
|-------|------|
|
||||||
|
| `project-scanner` | Descubre archivos, detecta lenguajes y frameworks |
|
||||||
|
| `file-analyzer` | Extrae funciones, clases e importaciones; produce nodos y aristas del grafo |
|
||||||
|
| `architecture-analyzer` | Identifica capas arquitectónicas |
|
||||||
|
| `tour-builder` | Genera recorridos de aprendizaje guiados |
|
||||||
|
| `graph-reviewer` | Valida la completitud y la integridad referencial del grafo (se ejecuta inline por defecto; usa `--review` para una revisión completa con LLM) |
|
||||||
|
| `domain-analyzer` | Extrae dominios de negocio, flujos y pasos de proceso (usado por `/understand-domain`) |
|
||||||
|
| `article-analyzer` | Extrae entidades, afirmaciones y relaciones implícitas de artículos wiki (usado por `/understand-knowledge`) |
|
||||||
|
|
||||||
|
Los analizadores de archivos se ejecutan en paralelo (hasta 5 concurrentes, 20-30 archivos por lote). Soporta actualizaciones incrementales: solo reanaliza los archivos que cambiaron desde la última ejecución.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎥 Comunidad
|
||||||
|
|
||||||
|
Un recorrido en video hecho por la comunidad de **Better Stack**.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.youtube.com/watch?v=VmIUXVlt7_I"><img src="https://img.youtube.com/vi/VmIUXVlt7_I/maxresdefault.jpg" alt="Recorrido en video por la comunidad de Better Stack — haz clic para ver en YouTube" width="480" /></a>
|
||||||
|
<br />
|
||||||
|
<em><a href="https://www.youtube.com/watch?v=VmIUXVlt7_I">Ver en YouTube →</a></em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
¿Has hecho un video, post o tutorial? Abre un issue o PR — estaremos encantados de mostrarlo aquí.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Contribuir
|
||||||
|
|
||||||
|
¡Las contribuciones son bienvenidas! Así puedes empezar:
|
||||||
|
|
||||||
|
1. Haz fork del repositorio
|
||||||
|
2. Crea una rama de funcionalidad (`git checkout -b feature/my-feature`)
|
||||||
|
3. Ejecuta las pruebas (`pnpm --filter @understand-anything/core test`)
|
||||||
|
4. Haz commit de tus cambios y abre un pull request
|
||||||
|
|
||||||
|
Para cambios importantes, abre primero un issue para que podamos discutir el enfoque.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>Deja de leer código a ciegas. Empieza a entenderlo todo.</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Historial de Stars
|
||||||
|
|
||||||
|
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&theme=dark&legend=top-left" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<em>Gracias a todas las personas que lo han usado y han contribuido — saber que les ahorra tiempo es lo que hizo que valiera la pena construirlo.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Licencia MIT © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||||
|
</p>
|
||||||
345
Understand-Anything-main/READMEs/README.ja-JP.md
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
<h1 align="center">Understand Anything</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>あらゆるコードベース、ナレッジベース、ドキュメントを、探索・検索・質問ができるインタラクティブなナレッジグラフに変換します。</strong>
|
||||||
|
<br />
|
||||||
|
<em>Claude Code、Codex、Cursor、Copilot、Gemini CLI など、マルチプラットフォーム対応。</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Lum1104%2FUnderstand-Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="../README.md">English</a> | <a href="README.zh-CN.md">简体中文</a> | <a href="README.zh-TW.md">繁體中文</a> | <a href="README.ja-JP.md">日本語</a> | <a href="README.ko-KR.md">한국어</a> | <a href="README.es-ES.md">Español</a> | <a href="README.tr-TR.md">Türkçe</a> | <a href="README.ru-RU.md">Русский</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#-クイックスタート"><img src="https://img.shields.io/badge/Quick_Start-blue" alt="クイックスタート" /></a>
|
||||||
|
<a href="https://github.com/Lum1104/Understand-Anything/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License: MIT" /></a>
|
||||||
|
<a href="https://docs.anthropic.com/en/docs/claude-code"><img src="https://img.shields.io/badge/Claude_Code-8A2BE2" alt="Claude Code" /></a>
|
||||||
|
<a href="#codex"><img src="https://img.shields.io/badge/Codex-000000" alt="Codex" /></a>
|
||||||
|
<a href="#vs-code--github-copilot"><img src="https://img.shields.io/badge/Copilot-24292e" alt="Copilot" /></a>
|
||||||
|
<a href="#copilot-cli"><img src="https://img.shields.io/badge/Copilot_CLI-24292e" alt="Copilot CLI" /></a>
|
||||||
|
<a href="#gemini-cli"><img src="https://img.shields.io/badge/Gemini_CLI-4285F4" alt="Gemini CLI" /></a>
|
||||||
|
<a href="#opencode"><img src="https://img.shields.io/badge/OpenCode-38bdf8" alt="OpenCode" /></a>
|
||||||
|
<a href="https://understand-anything.com"><img src="https://img.shields.io/badge/Homepage-d4a574" alt="ホームページ" /></a>
|
||||||
|
<a href="https://understand-anything.com/demo/"><img src="https://img.shields.io/badge/Live_Demo-00c853" alt="ライブデモ" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="../assets/hero.png" alt="Understand Anything — あらゆるコードベースをインタラクティブなナレッジグラフに変換" width="800" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>💬 <a href="https://discord.gg/pydat66RY">Discord コミュニティに参加 →</a></strong>
|
||||||
|
<br />
|
||||||
|
<em>質問・作品の共有・コミュニティとの交流はこちらから。</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**新しいチームに参加したばかり。コードベースは20万行。どこから手をつければいいのか?**
|
||||||
|
|
||||||
|
Understand Anything は [Claude Code Plugin](https://code.claude.com/docs/en/plugins-reference#plugins-reference) です。マルチエージェントパイプラインでプロジェクトを分析し、すべてのファイル・関数・クラス・依存関係のナレッジグラフを構築して、インタラクティブなダッシュボードで視覚的に探索できるようにします。コードを闇雲に読むのはやめて、全体像を把握しましょう。
|
||||||
|
|
||||||
|
> **目指すのは、コードベースの複雑さで圧倒するグラフではなく、すべてのパーツがどう噛み合っているかを静かに教えてくれるグラフ。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ 機能
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **すぐに試したいですか?** [ホームページ](https://understand-anything.com/)で[ライブデモ](https://understand-anything.com/demo/)をお試しください — パン、ズーム、検索、探索ができる完全インタラクティブなダッシュボードです。
|
||||||
|
|
||||||
|
### コード構造グラフを探索
|
||||||
|
|
||||||
|
コードベースをインタラクティブなナレッジグラフとして表示——すべてのファイル、関数、クラスがクリック・検索・探索可能なノードです。ノードを選択すると、わかりやすい要約、依存関係、ガイド付きツアーが表示されます。
|
||||||
|
|
||||||
|
### ビジネスロジックを理解
|
||||||
|
|
||||||
|
ドメインビューに切り替えると、コードが実際のビジネスプロセスにどう対応するかが一目でわかります——ドメイン、フロー、ステップが横方向のグラフとして表示されます。
|
||||||
|
|
||||||
|
### ナレッジベースを分析
|
||||||
|
|
||||||
|
`/understand-knowledge` を [Karpathy パターンの LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) に向けると、コミュニティクラスタリング付きのフォースディレクテッドナレッジグラフが生成されます。決定論的パーサーが `index.md` から wikilinks とカテゴリを抽出し、LLM エージェントが暗黙の関係を発見、エンティティを抽出、主張を浮き彫りにして、wiki をナビゲート可能な相互接続されたアイデアのグラフに変換します。
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🧭 ガイドツアー</h3>
|
||||||
|
<p>依存関係順に並べられた、自動生成のアーキテクチャウォークスルー。正しい順序でコードベースを学べます。</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🔍 ファジー&セマンティック検索</h3>
|
||||||
|
<p>名前や意味で何でも検索できます。「認証を処理する部分は?」と検索すれば、グラフ全体から関連する結果が得られます。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📊 差分影響分析</h3>
|
||||||
|
<p>コミット前に、変更がシステムのどの部分に影響するかを確認。コードベース全体への波及効果を把握できます。</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🎭 ペルソナ適応型UI</h3>
|
||||||
|
<p>ダッシュボードは、ジュニア開発者・PM・パワーユーザーなど、ユーザーに応じて詳細レベルを調整します。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🏗️ レイヤー可視化</h3>
|
||||||
|
<p>API・Service・Data・UI・Utilityなどのアーキテクチャ層ごとに自動グループ化。色分けされた凡例付き。</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📚 言語コンセプト</h3>
|
||||||
|
<p>ジェネリクス・クロージャ・デコレータなど12のプログラミングパターンが、出現箇所のコンテキストで説明されます。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 クイックスタート
|
||||||
|
|
||||||
|
### 1. プラグインをインストール
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. コードベースを分析
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand
|
||||||
|
```
|
||||||
|
|
||||||
|
マルチエージェントパイプラインがプロジェクトをスキャンし、すべてのファイル・関数・クラス・依存関係を抽出して、`.understand-anything/knowledge-graph.json` にナレッジグラフを保存します。
|
||||||
|
|
||||||
|
**ローカライズされた出力:** `--language` を使用して、希望の言語でコンテンツを生成:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 日本語でコンテンツを生成(ナレッジグラフのノード説明とダッシュボードUI)
|
||||||
|
/understand --language ja
|
||||||
|
|
||||||
|
# サポート言語:en(デフォルト)、zh、zh-TW、ja、ko、ru
|
||||||
|
```
|
||||||
|
|
||||||
|
`--language` パラメータは以下に影響します:
|
||||||
|
- ナレッジグラフのノードサマリーと説明
|
||||||
|
- ダッシュボードUIのラベル、ボタン、ツールチップ
|
||||||
|
- ガイド付きツアーの説明
|
||||||
|
|
||||||
|
### 3. ダッシュボードで探索
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
インタラクティブなWebダッシュボードが開き、コードベースがグラフとして可視化されます。アーキテクチャ層ごとに色分けされ、検索やクリックが可能です。ノードを選択すると、コード・関連関係・平易な説明が表示されます。
|
||||||
|
|
||||||
|
### 4. さらに学ぶ
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# コードベースについて何でも質問
|
||||||
|
/understand-chat 支払いフローはどう動いているの?
|
||||||
|
|
||||||
|
# 現在の変更の影響を分析
|
||||||
|
/understand-diff
|
||||||
|
|
||||||
|
# 特定のファイルや関数を詳しく調べる
|
||||||
|
/understand-explain src/auth/login.ts
|
||||||
|
|
||||||
|
# 新メンバー向けのオンボーディングガイドを生成
|
||||||
|
/understand-onboard
|
||||||
|
|
||||||
|
# ビジネスドメイン知識を抽出(ドメイン、フロー、ステップ)
|
||||||
|
/understand-domain
|
||||||
|
|
||||||
|
# Karpathy パターンの LLM Wiki ナレッジベースを分析
|
||||||
|
/understand-knowledge ~/path/to/wiki
|
||||||
|
|
||||||
|
# いつでも再実行 —— デフォルトでインクリメンタル(変更ファイルのみ再分析)
|
||||||
|
/understand
|
||||||
|
|
||||||
|
# post-commit フックをインストールしてコミットごとに自動更新
|
||||||
|
/understand --auto-update
|
||||||
|
|
||||||
|
# 巨大なモノレポでも安心 —— サブディレクトリにスコープを絞る
|
||||||
|
/understand src/frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 マルチプラットフォームインストール
|
||||||
|
|
||||||
|
Understand-Anythingは複数のAIコーディングプラットフォームで動作します。
|
||||||
|
|
||||||
|
### Claude Code(ネイティブ)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### ワンラインインストール(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI)
|
||||||
|
|
||||||
|
**macOS / Linux:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
|
||||||
|
# プラットフォームを直接指定して対話プロンプトをスキップすることもできます:
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows(PowerShell):**
|
||||||
|
```powershell
|
||||||
|
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
|
||||||
|
```
|
||||||
|
|
||||||
|
インストーラーはリポジトリを `~/.understand-anything/repo` にクローンし、選択したプラットフォーム用のシンボリックリンクを作成します。完了後はCLI/IDEを再起動してください。
|
||||||
|
|
||||||
|
- サポートされる `<platform>` 値:`gemini`、`codex`、`opencode`、`pi`、`openclaw`、`antigravity`、`vibe`、`vscode`、`hermes`、`cline`、`kimi`
|
||||||
|
- 後で更新:`./install.sh --update`
|
||||||
|
- アンインストール:`./install.sh --uninstall <platform>`
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
Cursorはこのリポジトリをクローンすると `.cursor-plugin/plugin.json` 経由でプラグインを自動検出します。手動インストールは不要です — クローンしてCursorで開くだけです。
|
||||||
|
|
||||||
|
自動検出されない場合は、手動でインストールしてください:**Cursor Settings → Plugins** を開き、検索欄に `https://github.com/Lum1104/Understand-Anything` を貼り付けて追加します。
|
||||||
|
|
||||||
|
### VS Code + GitHub Copilot
|
||||||
|
|
||||||
|
GitHub Copilot拡張機能(v1.108+)をインストールしたVS Codeは、`.copilot-plugin/plugin.json` 経由でプラグインを自動検出します。クローンしてVS Codeで開くだけで、手動インストールは不要です。
|
||||||
|
|
||||||
|
全プロジェクトで使用するパーソナルスキルとして導入したい場合は、上記の `install.sh` を `vscode` プラットフォームで実行してください。
|
||||||
|
|
||||||
|
### Copilot CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### プラットフォーム互換性
|
||||||
|
|
||||||
|
| プラットフォーム | ステータス | インストール方法 |
|
||||||
|
|----------|--------|----------------|
|
||||||
|
| Claude Code | ✅ ネイティブ | プラグインマーケットプレイス |
|
||||||
|
| Cursor | ✅ サポート | 自動検出 |
|
||||||
|
| VS Code + GitHub Copilot | ✅ サポート | 自動検出 |
|
||||||
|
| Copilot CLI | ✅ サポート | プラグインインストール |
|
||||||
|
| Codex | ✅ サポート | `install.sh codex` |
|
||||||
|
| OpenCode | ✅ サポート | `install.sh opencode` |
|
||||||
|
| OpenClaw | ✅ サポート | `install.sh openclaw` |
|
||||||
|
| Antigravity | ✅ サポート | `install.sh antigravity` |
|
||||||
|
| Gemini CLI | ✅ サポート | `install.sh gemini` |
|
||||||
|
| Pi Agent | ✅ サポート | `install.sh pi` |
|
||||||
|
| Vibe CLI | ✅ サポート | `install.sh vibe` |
|
||||||
|
| Hermes | ✅ サポート | `install.sh hermes` |
|
||||||
|
| Cline | ✅ サポート | `install.sh cline` |
|
||||||
|
| KIMI CLI | ✅ サポート | `install.sh kimi` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 チームでグラフを共有する
|
||||||
|
|
||||||
|
グラフは単なる JSON ファイルです——**一度コミットすれば、チームメンバーはパイプラインを実行せずに済みます**。オンボーディング、PR レビュー、docs-as-code ワークフローに最適です。
|
||||||
|
|
||||||
|
> **例:** [GoogleCloudPlatform/microservices-demo(fork)](https://github.com/Lum1104/microservices-demo) —— コミット済みのグラフを含む Go / Java / Python / Node のリファレンスプロジェクト。
|
||||||
|
|
||||||
|
**コミット対象:** `.understand-anything/` 内のすべてのファイル。ただし `intermediate/` と `diff-overlay.json` は除きます(これらはローカルの一時ファイルです)。
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
.understand-anything/intermediate/
|
||||||
|
.understand-anything/diff-overlay.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**最新状態を保つ:** `/understand --auto-update` を有効にすると、post-commit フックがグラフを増分的に更新し、各コミットに対応するグラフが揃います。またはリリース前に `/understand` を手動で再実行します。
|
||||||
|
|
||||||
|
**大きなグラフ(10 MB 以上):** **git-lfs** で管理します。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git lfs install
|
||||||
|
git lfs track ".understand-anything/*.json"
|
||||||
|
git add .gitattributes .understand-anything/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 内部の仕組み
|
||||||
|
|
||||||
|
### Tree-sitter + LLM ハイブリッド
|
||||||
|
|
||||||
|
決定論的にできることは静的解析、意味理解が必要なことは LLM、と役割を分けています:
|
||||||
|
|
||||||
|
- **Tree-sitter(決定論的)** —— ソースコードを具象構文木にパースし、構造的事実を抽出します:import、export、関数/クラス定義、呼び出し位置、継承関係。スキャンフェーズで `importMap` として事前解決し、file-analyzer に渡すことで、ソースから再度 import を導出する必要をなくしています。同じ入力からは常に同じ出力が得られ、インクリメンタル更新のフィンガープリントの基盤にもなります。
|
||||||
|
- **LLM(意味的)** —— パース済みの構造と原文ソースを併せて読み、パーサーには出せないものを生成します:plain-English の要約、タグ、アーキテクチャレイヤの割当、業務ドメインマッピング、ガイド付きツアー、言語コンセプトの注釈。
|
||||||
|
|
||||||
|
この分担により、構造面ではグラフが再現可能(同じコードからは常に同じエッジが出る)でありながら、意味面ではそのファイルが「何のために」あるのかという意図を捉えられます。
|
||||||
|
|
||||||
|
### マルチエージェントパイプライン
|
||||||
|
|
||||||
|
`/understand` コマンドは5つの専門エージェントをオーケストレーションし、`/understand-domain` は6つ目を追加します:
|
||||||
|
|
||||||
|
| エージェント | 役割 |
|
||||||
|
|-------|------|
|
||||||
|
| `project-scanner` | ファイルの検出、言語やフレームワークの検出 |
|
||||||
|
| `file-analyzer` | 関数・クラス・インポートの抽出、グラフノードとエッジの生成 |
|
||||||
|
| `architecture-analyzer` | アーキテクチャ層の特定 |
|
||||||
|
| `tour-builder` | ガイド学習ツアーの生成 |
|
||||||
|
| `graph-reviewer` | グラフの完全性と参照整合性の検証 |
|
||||||
|
| `domain-analyzer` | ビジネスドメイン、フロー、処理ステップの抽出(`/understand-domain` で使用) |
|
||||||
|
| `article-analyzer` | wiki 記事からエンティティ、主張、暗黙の関係を抽出(`/understand-knowledge` で使用) |
|
||||||
|
|
||||||
|
ファイルアナライザーは並列実行されます(最大3つ同時)。インクリメンタル更新に対応しており、前回の実行から変更されたファイルのみを再分析します。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎥 コミュニティ
|
||||||
|
|
||||||
|
**Better Stack** によるコミュニティ製ウォークスルー動画。
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.youtube.com/watch?v=VmIUXVlt7_I"><img src="https://img.youtube.com/vi/VmIUXVlt7_I/maxresdefault.jpg" alt="Better Stack によるコミュニティ製ウォークスルー動画 — クリックして YouTube で視聴" width="480" /></a>
|
||||||
|
<br />
|
||||||
|
<em><a href="https://www.youtube.com/watch?v=VmIUXVlt7_I">YouTube で視聴 →</a></em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
動画、ブログ、チュートリアルを作成しましたか?Issue または PR を開いてください — ここで紹介させていただきます。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 コントリビュート
|
||||||
|
|
||||||
|
コントリビュートを歓迎します!始め方は以下の通りです:
|
||||||
|
|
||||||
|
1. リポジトリをフォーク
|
||||||
|
2. フィーチャーブランチを作成(`git checkout -b feature/my-feature`)
|
||||||
|
3. テストを実行(`pnpm --filter @understand-anything/core test`)
|
||||||
|
4. 変更をコミットしてプルリクエストを作成
|
||||||
|
|
||||||
|
大きな変更については、まずIssueを作成してアプローチを議論してください。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>コードを闇雲に読むのはやめよう。すべてを理解しよう。</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&theme=dark&legend=top-left" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<em>使ってくれた、貢献してくれたすべての方へ ── 少しでも時間を節約できていると知ること、それがこれを作って良かったと思える理由です。</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
MIT License © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||||
|
</p>
|
||||||
344
Understand-Anything-main/READMEs/README.ko-KR.md
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
<h1 align="center">Understand Anything</h1>
|
||||||
|
<p align="center">
|
||||||
|
<strong>모든 코드베이스, 지식 베이스 또는 문서를 탐색, 검색, 질문할 수 있는 인터랙티브 지식 그래프로 변환합니다.</strong>
|
||||||
|
<br />
|
||||||
|
<em>Claude Code, Codex, Cursor, Copilot, Gemini CLI 등 다양한 플랫폼을 지원합니다.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Lum1104%2FUnderstand-Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="../README.md">English</a> | <a href="README.zh-CN.md">简体中文</a> | <a href="README.zh-TW.md">繁體中文</a> | <a href="README.ja-JP.md">日本語</a> | <a href="README.ko-KR.md">한국어</a> | <a href="README.es-ES.md">Español</a> | <a href="README.tr-TR.md">Türkçe</a> | <a href="README.ru-RU.md">Русский</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#-빠른-시작"><img src="https://img.shields.io/badge/빠른_시작-blue" alt="Quick Start" /></a>
|
||||||
|
<a href="https://github.com/Lum1104/Understand-Anything/blob/main/LICENSE"><img src="https://img.shields.io/badge/라이선스-MIT-yellow" alt="License: MIT" /></a>
|
||||||
|
<a href="https://docs.anthropic.com/en/docs/claude-code"><img src="https://img.shields.io/badge/Claude_Code-8A2BE2" alt="Claude Code" /></a>
|
||||||
|
<a href="#codex"><img src="https://img.shields.io/badge/Codex-000000" alt="Codex" /></a>
|
||||||
|
<a href="#vs-code--github-copilot"><img src="https://img.shields.io/badge/Copilot-24292e" alt="Copilot" /></a>
|
||||||
|
<a href="#copilot-cli"><img src="https://img.shields.io/badge/Copilot_CLI-24292e" alt="Copilot CLI" /></a>
|
||||||
|
<a href="#gemini-cli"><img src="https://img.shields.io/badge/Gemini_CLI-4285F4" alt="Gemini CLI" /></a>
|
||||||
|
<a href="#opencode"><img src="https://img.shields.io/badge/OpenCode-38bdf8" alt="OpenCode" /></a>
|
||||||
|
<a href="https://understand-anything.com"><img src="https://img.shields.io/badge/홈페이지-d4a574" alt="Homepage" /></a>
|
||||||
|
<a href="https://understand-anything.com/demo/"><img src="https://img.shields.io/badge/라이브_데모-00c853" alt="Live Demo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="../assets/hero.png" alt="Understand Anything — 모든 코드베이스를 인터랙티브 지식 그래프로 변환" width="800" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>💬 <a href="https://discord.gg/pydat66RY">Discord 커뮤니티 참여하기 →</a></strong>
|
||||||
|
<br />
|
||||||
|
<em>질문하고, 만든 것을 공유하고, 커뮤니티의 도움을 받으세요.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**새 팀에 합류했습니다. 코드베이스가 20만 줄입니다. 어디서부터 시작하시겠습니까?**
|
||||||
|
|
||||||
|
Understand Anything은 [Claude Code Plugin](https://code.claude.com/docs/en/plugins-reference#plugins-reference)으로, 멀티 에이전트 파이프라인을 통해 프로젝트를 분석하고, 모든 파일, 함수, 클래스, 의존성에 대한 지식 그래프를 구축한 뒤, 이를 시각적으로 탐색할 수 있는 인터랙티브 대시보드를 제공합니다. 더 이상 코드를 맹목적으로 읽지 마세요. 전체 그림을 파악하세요.
|
||||||
|
|
||||||
|
> **목표는 코드베이스의 복잡함으로 감탄을 자아내는 그래프가 아니라, 각 조각이 어떻게 맞물리는지 조용히 가르쳐주는 그래프입니다.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ 주요 기능
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **바로 체험해보세요!** [홈페이지](https://understand-anything.com/)에서 [라이브 데모](https://understand-anything.com/demo/)를 사용해보세요 — 팬, 줌, 검색, 탐색이 가능한 완전한 인터랙티브 대시보드입니다.
|
||||||
|
|
||||||
|
### 구조 그래프 탐색
|
||||||
|
|
||||||
|
코드베이스를 인터랙티브 지식 그래프로 탐색하세요. 모든 파일, 함수, 클래스가 클릭, 검색, 탐색 가능한 노드입니다. 노드를 선택하면 이해하기 쉬운 요약, 관계, 가이드 투어를 확인할 수 있습니다.
|
||||||
|
|
||||||
|
### 비즈니스 로직 이해
|
||||||
|
|
||||||
|
도메인 뷰로 전환하면 코드가 실제 비즈니스 프로세스에 어떻게 매핑되는지 확인할 수 있습니다. 도메인, 흐름, 단계가 수평 그래프로 표시됩니다.
|
||||||
|
|
||||||
|
### 지식 베이스 분석
|
||||||
|
|
||||||
|
`/understand-knowledge`를 [Karpathy 패턴 LLM 위키](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f)에 연결하면 커뮤니티 클러스터링이 적용된 힘 기반 지식 그래프를 생성합니다. 결정론적 파서가 `index.md`에서 위키링크와 카테고리를 추출한 후, LLM 에이전트가 암묵적 관계를 발견하고, 엔티티를 추출하며, 주장을 도출하여 위키를 탐색 가능한 상호 연결된 아이디어 그래프로 변환합니다.
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🧭 가이드 투어</h3>
|
||||||
|
<p>의존성 순서에 따라 자동 생성되는 아키텍처 워크스루입니다. 올바른 순서로 코드베이스를 학습하세요.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🔍 퍼지 및 시맨틱 검색</h3>
|
||||||
|
<p>이름 또는 의미로 무엇이든 검색하세요. "인증을 처리하는 부분은?" 같은 질문으로 그래프 전체에서 관련 결과를 얻을 수 있습니다.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📊 변경 영향 분석</h3>
|
||||||
|
<p>커밋 전에 변경 사항이 시스템의 어떤 부분에 영향을 미치는지 확인하세요. 코드베이스 전반의 파급 효과를 이해하세요.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🎭 페르소나 적응형 UI</h3>
|
||||||
|
<p>사용자 유형(주니어 개발자, PM, 파워 유저)에 따라 대시보드의 상세 수준이 자동으로 조정됩니다.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🏗️ 레이어 시각화</h3>
|
||||||
|
<p>아키텍처 레이어별 자동 그룹화 — API, 서비스, 데이터, UI, 유틸리티 — 색상 코드 범례가 함께 제공됩니다.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📚 프로그래밍 개념</h3>
|
||||||
|
<p>12가지 프로그래밍 패턴(제네릭, 클로저, 데코레이터 등)이 코드에 등장하는 맥락에서 설명됩니다.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 빠른 시작
|
||||||
|
|
||||||
|
### 1. 플러그인 설치
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 코드베이스 분석
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand
|
||||||
|
```
|
||||||
|
|
||||||
|
멀티 에이전트 파이프라인이 프로젝트를 스캔하고, 모든 파일, 함수, 클래스, 의존성을 추출한 뒤, `.understand-anything/knowledge-graph.json`에 지식 그래프를 저장합니다.
|
||||||
|
|
||||||
|
**로컬라이즈된 출력:** `--language`를 사용하여 원하는 언어로 내용을 생성:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 한국어로 내용 생성 (지식 그래프 노드 설명과 대시보드 UI)
|
||||||
|
/understand --language ko
|
||||||
|
|
||||||
|
# 지원 언어: en(기본값), zh, zh-TW, ja, ko, ru
|
||||||
|
```
|
||||||
|
|
||||||
|
`--language` 매개변수는 다음에 영향합니다:
|
||||||
|
- 지식 그래프의 노드 요약과 설명
|
||||||
|
- 대시보드 UI의 레이블, 버튼, 툴팁
|
||||||
|
- 가이드 투어의 설명
|
||||||
|
|
||||||
|
### 3. 대시보드 탐색
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
코드베이스가 그래프로 시각화된 인터랙티브 웹 대시보드가 열립니다. 아키텍처 레이어별로 색상이 구분되어 있으며, 검색과 클릭이 가능합니다. 노드를 선택하면 코드, 관계, 이해하기 쉬운 설명을 확인할 수 있습니다.
|
||||||
|
|
||||||
|
### 4. 더 깊이 탐구하기
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 코드베이스에 대해 무엇이든 질문하기
|
||||||
|
/understand-chat How does the payment flow work?
|
||||||
|
|
||||||
|
# 현재 변경 사항의 영향 분석
|
||||||
|
/understand-diff
|
||||||
|
|
||||||
|
# 특정 파일이나 함수를 심층 분석
|
||||||
|
/understand-explain src/auth/login.ts
|
||||||
|
|
||||||
|
# 새 팀원을 위한 온보딩 가이드 생성
|
||||||
|
/understand-onboard
|
||||||
|
|
||||||
|
# 비즈니스 도메인 지식 추출 (도메인, 흐름, 단계)
|
||||||
|
/understand-domain
|
||||||
|
|
||||||
|
# Karpathy 패턴 LLM 위키 지식 베이스 분석
|
||||||
|
/understand-knowledge ~/path/to/wiki
|
||||||
|
|
||||||
|
# 언제든 다시 실행 — 기본값은 증분 업데이트(변경된 파일만 재분석)
|
||||||
|
/understand
|
||||||
|
|
||||||
|
# post-commit 훅을 설치해 매 커밋마다 자동 증분 업데이트
|
||||||
|
/understand --auto-update
|
||||||
|
|
||||||
|
# 거대한 모노레포라면 하위 디렉터리로 범위 제한
|
||||||
|
/understand src/frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 멀티 플랫폼 설치
|
||||||
|
|
||||||
|
Understand-Anything은 다양한 AI 코딩 플랫폼에서 사용할 수 있습니다.
|
||||||
|
|
||||||
|
### Claude Code (네이티브)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### 한 줄 설치 (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI)
|
||||||
|
|
||||||
|
**macOS / Linux:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
|
||||||
|
# 플랫폼 이름을 직접 전달하여 프롬프트를 건너뛸 수도 있습니다:
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows (PowerShell):**
|
||||||
|
```powershell
|
||||||
|
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
|
||||||
|
```
|
||||||
|
|
||||||
|
설치 스크립트는 저장소를 `~/.understand-anything/repo`에 클론하고 선택한 플랫폼에 맞는 심볼릭 링크를 생성합니다. 설치 후 CLI 또는 IDE를 재시작하세요.
|
||||||
|
|
||||||
|
- 지원되는 `<platform>` 값: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`
|
||||||
|
- 이후 업데이트: `./install.sh --update`
|
||||||
|
- 제거: `./install.sh --uninstall <platform>`
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
이 저장소를 클론하면 Cursor가 `.cursor-plugin/plugin.json`을 통해 플러그인을 자동으로 인식합니다. 수동 설치가 필요 없습니다. 클론 후 Cursor에서 열기만 하면 됩니다.
|
||||||
|
|
||||||
|
자동 인식이 되지 않으면 수동으로 설치하세요: **Cursor Settings → Plugins**를 열고 검색란에 `https://github.com/Lum1104/Understand-Anything`를 붙여넣은 뒤 추가하세요.
|
||||||
|
|
||||||
|
### VS Code + GitHub Copilot
|
||||||
|
|
||||||
|
GitHub Copilot(v1.108+)이 설치된 VS Code는 `.copilot-plugin/plugin.json`을 통해 플러그인을 자동으로 인식합니다. 수동 설치가 필요 없습니다. 클론 후 VS Code에서 열기만 하면 됩니다.
|
||||||
|
|
||||||
|
모든 프로젝트에서 사용하는 개인 스킬로 설치하려면 위 `install.sh`를 `vscode` 플랫폼으로 실행하세요.
|
||||||
|
|
||||||
|
### Copilot CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 플랫폼 호환성
|
||||||
|
|
||||||
|
| 플랫폼 | 상태 | 설치 방법 |
|
||||||
|
|----------|--------|----------------|
|
||||||
|
| Claude Code | ✅ 네이티브 | 플러그인 마켓플레이스 |
|
||||||
|
| Cursor | ✅ 지원 | 자동 인식 |
|
||||||
|
| VS Code + GitHub Copilot | ✅ 지원 | 자동 인식 |
|
||||||
|
| Copilot CLI | ✅ 지원 | 플러그인 설치 |
|
||||||
|
| Codex | ✅ 지원 | `install.sh codex` |
|
||||||
|
| OpenCode | ✅ 지원 | `install.sh opencode` |
|
||||||
|
| OpenClaw | ✅ 지원 | `install.sh openclaw` |
|
||||||
|
| Antigravity | ✅ 지원 | `install.sh antigravity` |
|
||||||
|
| Gemini CLI | ✅ 지원 | `install.sh gemini` |
|
||||||
|
| Pi Agent | ✅ 지원 | `install.sh pi` |
|
||||||
|
| Vibe CLI | ✅ 지원 | `install.sh vibe` |
|
||||||
|
| Hermes | ✅ 지원 | `install.sh hermes` |
|
||||||
|
| Cline | ✅ 지원 | `install.sh cline` |
|
||||||
|
| KIMI CLI | ✅ 지원 | `install.sh kimi` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 팀과 그래프 공유하기
|
||||||
|
|
||||||
|
그래프는 단지 JSON 파일입니다 — **한 번만 커밋하면 팀원은 파이프라인을 건너뛸 수 있습니다**. 온보딩, PR 리뷰, docs-as-code 워크플로에 적합합니다.
|
||||||
|
|
||||||
|
> **예시:** [GoogleCloudPlatform/microservices-demo (fork)](https://github.com/Lum1104/microservices-demo) — 커밋된 그래프를 포함한 Go / Java / Python / Node 레퍼런스 프로젝트.
|
||||||
|
|
||||||
|
**커밋할 대상:** `.understand-anything/` 내부의 모든 파일. 단, `intermediate/` 와 `diff-overlay.json` 은 제외합니다 (이들은 로컬 임시 파일입니다).
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
.understand-anything/intermediate/
|
||||||
|
.understand-anything/diff-overlay.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**최신 상태 유지:** `/understand --auto-update` 를 활성화하면 post-commit 훅이 그래프를 증분 업데이트하여 각 커밋마다 일치하는 그래프가 유지됩니다. 또는 릴리스 전에 `/understand` 를 수동으로 다시 실행하세요.
|
||||||
|
|
||||||
|
**대용량 그래프 (10 MB 이상):** **git-lfs** 로 추적합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git lfs install
|
||||||
|
git lfs track ".understand-anything/*.json"
|
||||||
|
git add .gitattributes .understand-anything/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 작동 원리
|
||||||
|
|
||||||
|
### Tree-sitter + LLM 하이브리드
|
||||||
|
|
||||||
|
결정적으로 처리할 수 있는 일은 정적 분석이, 의미 이해가 필요한 일은 LLM 이 담당합니다:
|
||||||
|
|
||||||
|
- **Tree-sitter(결정적)** — 소스 코드를 구체 구문 트리로 파싱해 구조적 사실을 추출합니다: import, export, 함수/클래스 정의, 호출 위치, 상속. 스캔 단계에서 `importMap` 으로 사전 해석해 file-analyzer 에 전달하므로, 소스에서 다시 import 를 유도할 필요가 없습니다. 같은 입력은 항상 같은 출력을 내며, 증분 업데이트의 핑거프린트 기반이기도 합니다.
|
||||||
|
- **LLM(의미적)** — 파싱된 구조와 원본 소스를 함께 읽어 파서가 만들 수 없는 것들을 생성합니다: plain-English 요약, 태그, 아키텍처 레이어 할당, 비즈니스 도메인 매핑, 가이드 투어, 언어 개념 주석.
|
||||||
|
|
||||||
|
이 분담 덕분에 그래프는 구조 측면에서는 재현 가능하면서(같은 코드는 항상 같은 엣지를 만든다), 의미 측면에서는 의도를 포착할 수 있습니다(파일이 단지 무엇을 import 하는지가 아니라 *무엇을 위해* 존재하는지).
|
||||||
|
|
||||||
|
### 멀티 에이전트 파이프라인
|
||||||
|
|
||||||
|
`/understand` 명령은 5개의 전문 에이전트를 조율하며, `/understand-domain`은 6번째 에이전트를 추가합니다:
|
||||||
|
|
||||||
|
| 에이전트 | 역할 |
|
||||||
|
|-------|------|
|
||||||
|
| `project-scanner` | 파일 탐색, 언어 및 프레임워크 감지 |
|
||||||
|
| `file-analyzer` | 함수, 클래스, 임포트 추출; 그래프 노드 및 엣지 생성 |
|
||||||
|
| `architecture-analyzer` | 아키텍처 레이어 식별 |
|
||||||
|
| `tour-builder` | 가이드 학습 투어 생성 |
|
||||||
|
| `graph-reviewer` | 그래프 완전성 및 참조 무결성 검증 (기본적으로 인라인 실행; 전체 LLM 검토는 `--review` 사용) |
|
||||||
|
| `domain-analyzer` | 비즈니스 도메인, 흐름 및 프로세스 단계 추출 (`/understand-domain`에서 사용) |
|
||||||
|
| `article-analyzer` | 위키 문서에서 엔티티, 주장 및 암묵적 관계 추출 (`/understand-knowledge`에서 사용) |
|
||||||
|
|
||||||
|
파일 분석기는 병렬로 실행됩니다(최대 5개 동시, 배치당 20~30개 파일). 증분 업데이트를 지원하여 마지막 실행 이후 변경된 파일만 재분석합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎥 커뮤니티
|
||||||
|
|
||||||
|
**Better Stack**에서 만든 커뮤니티 워크스루 영상.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.youtube.com/watch?v=VmIUXVlt7_I"><img src="https://img.youtube.com/vi/VmIUXVlt7_I/maxresdefault.jpg" alt="Better Stack에서 만든 커뮤니티 워크스루 영상 — 클릭하여 YouTube에서 시청" width="480" /></a>
|
||||||
|
<br />
|
||||||
|
<em><a href="https://www.youtube.com/watch?v=VmIUXVlt7_I">YouTube에서 보기 →</a></em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
영상, 블로그 글, 튜토리얼을 만드셨나요? 이슈나 PR을 열어주세요 — 여기서 소개해드릴게요.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 기여하기
|
||||||
|
|
||||||
|
기여를 환영합니다! 시작하는 방법은 다음과 같습니다:
|
||||||
|
|
||||||
|
1. 저장소를 Fork합니다
|
||||||
|
2. 기능 브랜치를 생성합니다 (`git checkout -b feature/my-feature`)
|
||||||
|
3. 테스트를 실행합니다 (`pnpm --filter @understand-anything/core test`)
|
||||||
|
4. 변경 사항을 커밋하고 Pull Request를 생성합니다
|
||||||
|
|
||||||
|
주요 변경 사항의 경우, 먼저 Issue를 열어 접근 방식을 논의해 주세요.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>더 이상 코드를 맹목적으로 읽지 마세요. 모든 것을 이해하세요.</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Star 히스토리
|
||||||
|
|
||||||
|
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&theme=dark&legend=top-left" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<em>이 도구를 사용해 주시고 기여해 주신 모든 분들께 감사드립니다 — 누군가의 시간을 아껴드리고 있다는 사실이, 이걸 만들 가치가 있게 만든 이유였습니다.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
MIT 라이선스 © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||||
|
</p>
|
||||||
345
Understand-Anything-main/READMEs/README.ru-RU.md
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
<h1 align="center">Understand Anything</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>Превращай любую кодовую базу, базу знаний или документацию в интерактивный граф знаний, который можно исследовать, искать в нём и задавать вопросы.</strong>
|
||||||
|
<br />
|
||||||
|
<em>Работает с Claude Code, Codex, Cursor, Copilot, Gemini CLI и другими.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Lum1104%2FUnderstand-Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="../README.md">English</a> | <a href="README.zh-CN.md">简体中文</a> | <a href="README.zh-TW.md">繁體中文</a> | <a href="README.ja-JP.md">日本語</a> | <a href="README.ko-KR.md">한국어</a> | <a href="README.es-ES.md">Español</a> | <a href="README.tr-TR.md">Türkçe</a> | <a href="README.ru-RU.md">Русский</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#-быстрый-старт"><img src="https://img.shields.io/badge/Быстрый_старт-blue" alt="Quick Start" /></a>
|
||||||
|
<a href="https://github.com/Lum1104/Understand-Anything/blob/main/LICENSE"><img src="https://img.shields.io/badge/Лицензия-MIT-yellow" alt="License: MIT" /></a>
|
||||||
|
<a href="https://docs.anthropic.com/en/docs/claude-code"><img src="https://img.shields.io/badge/Claude_Code-8A2BE2" alt="Claude Code" /></a>
|
||||||
|
<a href="#codex"><img src="https://img.shields.io/badge/Codex-000000" alt="Codex" /></a>
|
||||||
|
<a href="#vs-code--github-copilot"><img src="https://img.shields.io/badge/Copilot-24292e" alt="Copilot" /></a>
|
||||||
|
<a href="#copilot-cli"><img src="https://img.shields.io/badge/Copilot_CLI-24292e" alt="Copilot CLI" /></a>
|
||||||
|
<a href="#gemini-cli"><img src="https://img.shields.io/badge/Gemini_CLI-4285F4" alt="Gemini CLI" /></a>
|
||||||
|
<a href="#opencode"><img src="https://img.shields.io/badge/OpenCode-38bdf8" alt="OpenCode" /></a>
|
||||||
|
<a href="https://understand-anything.com"><img src="https://img.shields.io/badge/Сайт-d4a574" alt="Homepage" /></a>
|
||||||
|
<a href="https://understand-anything.com/demo/"><img src="https://img.shields.io/badge/Демо-00c853" alt="Live Demo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="../assets/hero.png" alt="Understand Anything — Превратите любую кодовую базу в интерактивный граф знаний" width="800" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>💬 <a href="https://discord.gg/pydat66RY">Присоединяйтесь к сообществу в Discord →</a></strong>
|
||||||
|
<br />
|
||||||
|
<em>Задавайте вопросы, делитесь тем, что вы построили, получайте помощь от сообщества.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Вы только что присоединились к новой команде. Кодовая база — 200 000 строк. С чего вообще начинать?**
|
||||||
|
|
||||||
|
Understand Anything — это [плагин для Claude Code](https://code.claude.com/docs/en/plugins-reference#plugins-reference), который анализирует ваш проект с помощью мультиагентного пайплайна, строит граф знаний из всех файлов, функций, классов и зависимостей, а затем предоставляет интерактивную панель, чтобы исследовать всё это визуально. Хватит читать код вслепую. Пора увидеть общую картину.
|
||||||
|
|
||||||
|
> **Цель — не граф, который поражает сложностью вашей кодовой базы, а граф, который ненавязчиво объясняет, как все части складываются вместе.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Возможности
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **Хотите пропустить чтение?** Попробуйте [живое демо](https://understand-anything.com/demo/) на нашем [сайте](https://understand-anything.com/) — полностью интерактивная панель, по которой можно перемещаться, масштабировать, искать и исследовать прямо в браузере.
|
||||||
|
|
||||||
|
### Исследуйте структурный граф
|
||||||
|
|
||||||
|
Перемещайтесь по своему коду как по интерактивному графу знаний — каждый файл, функция и класс является узлом, который можно кликнуть, найти и изучить. Выберите любой узел, чтобы увидеть понятные описания, связи и пошаговые обзоры.
|
||||||
|
|
||||||
|
### Понимайте бизнес-логику
|
||||||
|
|
||||||
|
Переключитесь на доменное представление и увидите, как ваш код отображается на реальные бизнес-процессы — домены, потоки и шаги, выстроенные в виде горизонтального графа.
|
||||||
|
|
||||||
|
### Анализируйте базы знаний
|
||||||
|
|
||||||
|
Направьте `/understand-knowledge` на [LLM-вики в стиле Карпати](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) и получите force-directed граф знаний с кластеризацией по сообществам. Детерминированный парсер извлекает wikilinks и категории из `index.md`, а LLM-агенты находят неявные связи, извлекают сущности и выявляют утверждения — превращая вашу вики в навигируемый граф взаимосвязанных идей.
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🧭 Пошаговые обзоры</h3>
|
||||||
|
<p>Автоматически создаваемые экскурсии по архитектуре, упорядоченные по зависимостям. Изучайте кодовую базу в правильном порядке.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🔍 Нечёткий и семантический поиск</h3>
|
||||||
|
<p>Находите что угодно по имени или по смыслу. Поищите «какие части отвечают за авторизацию?» и получите релевантные результаты по всему графу.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📊 Анализ влияния изменений</h3>
|
||||||
|
<p>Смотрите, какие части системы затрагивают ваши изменения, ещё до коммита. Понимайте каскадные эффекты по всей кодовой базе.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🎭 UI, адаптирующийся к роли</h3>
|
||||||
|
<p>Панель подстраивает уровень детализации под пользователя — junior-разработчика, PM или продвинутого пользователя.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🏗️ Визуализация слоёв</h3>
|
||||||
|
<p>Автоматическая группировка по архитектурным слоям — API, Service, Data, UI, Utility — с цветовой легендой.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📚 Концепции языка</h3>
|
||||||
|
<p>12 шаблонов программирования (дженерики, замыкания, декораторы и т.д.) объясняются в контексте там, где они встречаются.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
|
### 1. Установите плагин
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Проанализируйте кодовую базу
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand
|
||||||
|
```
|
||||||
|
|
||||||
|
Мультиагентный пайплайн сканирует ваш проект, извлекает каждый файл, функцию, класс и зависимость, а затем строит граф знаний и сохраняет его в `.understand-anything/knowledge-graph.json`.
|
||||||
|
|
||||||
|
**Локализованный вывод:** используйте `--language`, чтобы генерировать контент на нужном языке:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Генерация контента на русском (описания узлов графа знаний и UI панели)
|
||||||
|
/understand --language ru
|
||||||
|
|
||||||
|
# Поддерживаемые языки: en (по умолчанию), zh, zh-TW, ja, ko, ru
|
||||||
|
```
|
||||||
|
|
||||||
|
Параметр `--language` влияет на:
|
||||||
|
- Резюме и описания узлов в графе знаний
|
||||||
|
- Подписи, кнопки и подсказки UI панели
|
||||||
|
- Объяснения в пошаговых обзорах
|
||||||
|
|
||||||
|
### 3. Откройте панель
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
Открывается интерактивная веб-панель с визуализацией вашей кодовой базы в виде графа — с цветовой кодировкой по архитектурным слоям, поиском и кликабельными узлами. Выберите любой узел, чтобы увидеть его код, связи и описание простым языком.
|
||||||
|
|
||||||
|
### 4. Продолжайте учиться
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Задайте любой вопрос о кодовой базе
|
||||||
|
/understand-chat How does the payment flow work?
|
||||||
|
|
||||||
|
# Проанализируйте влияние ваших текущих изменений
|
||||||
|
/understand-diff
|
||||||
|
|
||||||
|
# Подробно разберитесь с конкретным файлом или функцией
|
||||||
|
/understand-explain src/auth/login.ts
|
||||||
|
|
||||||
|
# Сгенерируйте онбординг-гайд для новых членов команды
|
||||||
|
/understand-onboard
|
||||||
|
|
||||||
|
# Извлеките знания о бизнес-доменах (домены, потоки, шаги)
|
||||||
|
/understand-domain
|
||||||
|
|
||||||
|
# Проанализируйте LLM-вики в стиле Карпати
|
||||||
|
/understand-knowledge ~/path/to/wiki
|
||||||
|
|
||||||
|
# Перезапускайте когда угодно — по умолчанию инкрементально (только изменённые файлы)
|
||||||
|
/understand
|
||||||
|
|
||||||
|
# Установите post-commit хук для автоматических инкрементальных обновлений
|
||||||
|
/understand --auto-update
|
||||||
|
|
||||||
|
# Огромный монорепозиторий? Ограничьте анализ подкаталогом
|
||||||
|
/understand src/frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Установка на разных платформах
|
||||||
|
|
||||||
|
Understand-Anything работает с несколькими платформами AI-разработки.
|
||||||
|
|
||||||
|
### Claude Code (нативно)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### Установка одной командой (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI)
|
||||||
|
|
||||||
|
**macOS / Linux:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
|
||||||
|
# или передайте платформу, чтобы пропустить интерактивный выбор:
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows (PowerShell):**
|
||||||
|
```powershell
|
||||||
|
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
|
||||||
|
```
|
||||||
|
|
||||||
|
Установщик клонирует репозиторий в `~/.understand-anything/repo` и создаёт нужные симлинки для выбранной платформы. После установки перезапустите свой CLI/IDE.
|
||||||
|
|
||||||
|
- Поддерживаемые значения `<platform>`: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`
|
||||||
|
- Обновление: `./install.sh --update`
|
||||||
|
- Удаление: `./install.sh --uninstall <platform>`
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
Cursor автоматически обнаруживает плагин через `.cursor-plugin/plugin.json` при клонировании этого репозитория. Ручная установка не требуется — просто склонируйте и откройте в Cursor.
|
||||||
|
|
||||||
|
Если автообнаружение не сработало, установите вручную: откройте **Cursor Settings → Plugins**, вставьте `https://github.com/Lum1104/Understand-Anything` в поле поиска и добавьте оттуда.
|
||||||
|
|
||||||
|
### VS Code + GitHub Copilot
|
||||||
|
|
||||||
|
VS Code с GitHub Copilot (v1.108+) автоматически обнаруживает плагин через `.copilot-plugin/plugin.json` при клонировании этого репозитория. Ручная установка не требуется — просто склонируйте и откройте в VS Code.
|
||||||
|
|
||||||
|
Для персональных skills (доступных во всех проектах) запустите `install.sh` выше с платформой `vscode`.
|
||||||
|
|
||||||
|
### Copilot CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Совместимость с платформами
|
||||||
|
|
||||||
|
| Платформа | Статус | Способ установки |
|
||||||
|
|----------|--------|----------------|
|
||||||
|
| Claude Code | ✅ Нативно | Marketplace плагинов |
|
||||||
|
| Cursor | ✅ Поддерживается | Автообнаружение |
|
||||||
|
| VS Code + GitHub Copilot | ✅ Поддерживается | Автообнаружение |
|
||||||
|
| Copilot CLI | ✅ Поддерживается | Установка плагина |
|
||||||
|
| Codex | ✅ Поддерживается | `install.sh codex` |
|
||||||
|
| OpenCode | ✅ Поддерживается | `install.sh opencode` |
|
||||||
|
| OpenClaw | ✅ Поддерживается | `install.sh openclaw` |
|
||||||
|
| Antigravity | ✅ Поддерживается | `install.sh antigravity` |
|
||||||
|
| Gemini CLI | ✅ Поддерживается | `install.sh gemini` |
|
||||||
|
| Pi Agent | ✅ Поддерживается | `install.sh pi` |
|
||||||
|
| Vibe CLI | ✅ Поддерживается | `install.sh vibe` |
|
||||||
|
| Hermes | ✅ Поддерживается | `install.sh hermes` |
|
||||||
|
| Cline | ✅ Поддерживается | `install.sh cline` |
|
||||||
|
| KIMI CLI | ✅ Поддерживается | `install.sh kimi` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Поделитесь графом с командой
|
||||||
|
|
||||||
|
Граф — это просто JSON. **Зафиксируйте его один раз, и коллеги смогут пропустить весь пайплайн.** Полезно для онбординга, ревью PR и подхода docs-as-code.
|
||||||
|
|
||||||
|
> **Пример:** [GoogleCloudPlatform/microservices-demo (форк)](https://github.com/Lum1104/microservices-demo) — мультиязыковой проект (Go / Java / Python / Node) с уже зафиксированным графом.
|
||||||
|
|
||||||
|
**Что коммитить:** всё содержимое `.understand-anything/`, *кроме* `intermediate/` и `diff-overlay.json` (это локальные временные файлы).
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
.understand-anything/intermediate/
|
||||||
|
.understand-anything/diff-overlay.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Держите граф в актуальном состоянии:** включите `/understand --auto-update` — post-commit хук будет инкрементально обновлять граф, так что каждый коммит сопровождается соответствующим графом. Либо запускайте `/understand` вручную перед релизами.
|
||||||
|
|
||||||
|
**Большие графы (10 МБ+):** храните через **git-lfs**.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git lfs install
|
||||||
|
git lfs track ".understand-anything/*.json"
|
||||||
|
git add .gitattributes .understand-anything/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Под капотом
|
||||||
|
|
||||||
|
### Гибрид Tree-sitter + LLM
|
||||||
|
|
||||||
|
Детерминированную работу делает статический анализ, семантическое понимание — LLM:
|
||||||
|
|
||||||
|
- **Tree-sitter (детерминированно)** — парсит исходный код в конкретное синтаксическое дерево и извлекает структурные факты: import'ы, export'ы, определения функций/классов, точки вызова, наследование. На фазе сканирования заранее разрешается в `importMap` и передаётся file-analyzer'ам, чтобы они не выводили import'ы из исходника заново. Одинаковый ввод всегда даёт одинаковый вывод; это же лежит в основе fingerprint'ов для инкрементальных обновлений.
|
||||||
|
- **LLM (семантически)** — читает разобранную структуру вместе с исходным текстом и производит то, что не способны парсеры: понятные человеку резюме, теги, назначение архитектурных слоёв, отображение бизнес-доменов, ведомые туры, заметки о концепциях языка.
|
||||||
|
|
||||||
|
Именно благодаря этому разделению граф воспроизводим со стороны структуры (один и тот же код всегда даёт одни и те же рёбра) и одновременно улавливает намерение со стороны семантики (для *чего* существует файл, а не только что он импортирует).
|
||||||
|
|
||||||
|
### Мультиагентный пайплайн
|
||||||
|
|
||||||
|
Команда `/understand` оркестрирует 5 специализированных агентов, а `/understand-domain` добавляет шестого:
|
||||||
|
|
||||||
|
| Агент | Роль |
|
||||||
|
|-------|------|
|
||||||
|
| `project-scanner` | Обнаружение файлов, определение языков и фреймворков |
|
||||||
|
| `file-analyzer` | Извлечение функций, классов, импортов; создание узлов и рёбер графа |
|
||||||
|
| `architecture-analyzer` | Определение архитектурных слоёв |
|
||||||
|
| `tour-builder` | Генерация пошаговых обучающих обзоров |
|
||||||
|
| `graph-reviewer` | Проверка полноты и целостности ссылок графа (по умолчанию выполняется inline; используйте `--review` для полного ревью с участием LLM) |
|
||||||
|
| `domain-analyzer` | Извлечение бизнес-доменов, потоков и шагов процессов (используется командой `/understand-domain`) |
|
||||||
|
| `article-analyzer` | Извлечение сущностей, утверждений и неявных связей из статей вики (используется командой `/understand-knowledge`) |
|
||||||
|
|
||||||
|
Анализаторы файлов работают параллельно (до 5 одновременно, 20–30 файлов на батч). Поддерживаются инкрементальные обновления — повторно анализируются только файлы, изменившиеся с прошлого запуска.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎥 Сообщество
|
||||||
|
|
||||||
|
Обзорное видео от сообщества, созданное **Better Stack**.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.youtube.com/watch?v=VmIUXVlt7_I"><img src="https://img.youtube.com/vi/VmIUXVlt7_I/maxresdefault.jpg" alt="Обзорное видео от сообщества Better Stack — нажмите, чтобы посмотреть на YouTube" width="480" /></a>
|
||||||
|
<br />
|
||||||
|
<em><a href="https://www.youtube.com/watch?v=VmIUXVlt7_I">Смотреть на YouTube →</a></em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
Сделали видео, статью или руководство? Откройте issue или PR — с удовольствием добавим сюда.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Вклад в проект
|
||||||
|
|
||||||
|
Будем рады вашим контрибьюшенам! Как начать:
|
||||||
|
|
||||||
|
1. Сделайте форк репозитория
|
||||||
|
2. Создайте ветку для фичи (`git checkout -b feature/my-feature`)
|
||||||
|
3. Запустите тесты (`pnpm --filter @understand-anything/core test`)
|
||||||
|
4. Закоммитьте изменения и откройте pull request
|
||||||
|
|
||||||
|
Для крупных изменений сначала откройте issue, чтобы можно было обсудить подход.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>Хватит читать код вслепую. Начните понимать всё.</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## История звёзд
|
||||||
|
|
||||||
|
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&theme=dark&legend=top-left" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<em>Спасибо всем, кто пользовался проектом и вкладывался в него — знание того, что это экономит людям время, и было главной причиной, ради которой стоило его делать.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Лицензия MIT © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||||
|
</p>
|
||||||
345
Understand-Anything-main/READMEs/README.tr-TR.md
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
<h1 align="center">Understand Anything</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>Herhangi bir kod tabanını, bilgi tabanını veya dokümantasyonu keşfedebileceğin, arayabileceğin ve hakkında sorular sorabileceğin interaktif bir bilgi grafiğine dönüştür.</strong>
|
||||||
|
<br />
|
||||||
|
<em>Claude Code, Codex, Cursor, Copilot, Gemini CLI ve daha fazlasıyla çalışır.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Lum1104%2FUnderstand-Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="../README.md">English</a> | <a href="README.zh-CN.md">简体中文</a> | <a href="README.zh-TW.md">繁體中文</a> | <a href="README.ja-JP.md">日本語</a> | <a href="README.ko-KR.md">한국어</a> | <a href="README.es-ES.md">Español</a> | <a href="README.tr-TR.md">Türkçe</a> | <a href="README.ru-RU.md">Русский</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#-hızlı-başlangıç"><img src="https://img.shields.io/badge/Hızlı_Başlangıç-blue" alt="Hızlı Başlangıç" /></a>
|
||||||
|
<a href="https://github.com/Lum1104/Understand-Anything/blob/main/LICENSE"><img src="https://img.shields.io/badge/Lisans-MIT-yellow" alt="Lisans: MIT" /></a>
|
||||||
|
<a href="https://docs.anthropic.com/en/docs/claude-code"><img src="https://img.shields.io/badge/Claude_Code-8A2BE2" alt="Claude Code" /></a>
|
||||||
|
<a href="#codex"><img src="https://img.shields.io/badge/Codex-000000" alt="Codex" /></a>
|
||||||
|
<a href="#vs-code--github-copilot"><img src="https://img.shields.io/badge/Copilot-24292e" alt="Copilot" /></a>
|
||||||
|
<a href="#copilot-cli"><img src="https://img.shields.io/badge/Copilot_CLI-24292e" alt="Copilot CLI" /></a>
|
||||||
|
<a href="#gemini-cli"><img src="https://img.shields.io/badge/Gemini_CLI-4285F4" alt="Gemini CLI" /></a>
|
||||||
|
<a href="#opencode"><img src="https://img.shields.io/badge/OpenCode-38bdf8" alt="OpenCode" /></a>
|
||||||
|
<a href="https://understand-anything.com"><img src="https://img.shields.io/badge/Ana_Sayfa-d4a574" alt="Ana Sayfa" /></a>
|
||||||
|
<a href="https://understand-anything.com/demo/"><img src="https://img.shields.io/badge/Canlı_Demo-00c853" alt="Canlı Demo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="../assets/hero.png" alt="Understand Anything — Herhangi bir kod tabanını interaktif bir bilgi grafiğine dönüştür" width="800" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>💬 <a href="https://discord.gg/pydat66RY">Discord topluluğuna katıl →</a></strong>
|
||||||
|
<br />
|
||||||
|
<em>Sorular sor, yaptıklarını paylaş, topluluktan yardım al.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Yeni bir ekibe katıldın. Kod tabanı 200.000 satır kod. Nereden başlayacaksın bile bilemiyorsun?**
|
||||||
|
|
||||||
|
Understand Anything, projenizi çok-ajan hattıyla analiz eden, her dosya, fonksiyon, sınıf ve bağımlılığın bilgi grafiğini oluşturan ve hepsini görsel olarak keşfetmen için interaktif bir kontrol paneli sunan bir [Claude Code Plugin](https://code.claude.com/docs/en/plugins-reference#plugins-reference)'dir. Kodu körü körüne okumayı bırak. Büyük resmi görmeye başla.
|
||||||
|
|
||||||
|
> **Amaç, kod tabanının ne kadar karmaşık olduğunu görkemle gösteren bir grafik değil — her parçanın nasıl birbirine geçtiğini sessizce öğreten bir grafik.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Özellikler
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **Hemen denemek ister misiniz?** [Ana sayfamızda](https://understand-anything.com/) [canlı demoyu](https://understand-anything.com/demo/) deneyin — doğrudan tarayıcınızda kaydırma, yakınlaştırma, arama ve keşfetme yapabileceğiniz tam etkileşimli bir kontrol paneli.
|
||||||
|
|
||||||
|
### Yapısal grafiği keşfedin
|
||||||
|
|
||||||
|
Kod tabanınızı interaktif bir bilgi grafiği olarak görüntüleyin — her dosya, fonksiyon ve sınıf tıklanabilir, aranabilir ve keşfedilebilir bir düğümdür. Herhangi bir düğümü seçerek anlaşılır özetleri, bağımlılıkları ve rehberli turları görün.
|
||||||
|
|
||||||
|
### İş mantığını anlayın
|
||||||
|
|
||||||
|
Alan görünümüne geçin ve kodunuzun gerçek iş süreçleriyle nasıl eşleştiğini görün — alanlar, akışlar ve adımlar yatay bir grafik olarak sunulur.
|
||||||
|
|
||||||
|
### Bilgi tabanlarını analiz et
|
||||||
|
|
||||||
|
`/understand-knowledge` komutunu bir [Karpathy deseni LLM Wiki'sine](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) yönlendirin ve topluluk kümeleme ile kuvvet yönelimli bir bilgi grafiği elde edin. Deterministik ayrıştırıcı `index.md`'den wikilinkleri ve kategorileri çıkarır, ardından LLM ajanları örtük ilişkileri keşfeder, varlıkları çıkarır ve iddiaları ortaya çıkarır — wiki'nizi gezinilebilir, birbirine bağlı fikirler grafiğine dönüştürür.
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🧭 Rehberli Turlar</h3>
|
||||||
|
<p>Bağımlılığa göre sıralanmış, mimarinin otomatik oluşturulmuş gözden geçirmeleri. Kod tabanını doğru sırayla öğren.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🔍 Bulanık ve Anlamsal Arama</h3>
|
||||||
|
<p>İsme veya anlamına göre her şeyi bul. "Kimlik doğrulamayı hangi parçalar yönetiyor?" ara ve grafik boyunca ilgili sonuçları al.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📊 Diff Etki Analizi</h3>
|
||||||
|
<p>Değişikliklerinin sistemin hangi bölümlerini etkilediğini commit etmeden önce gör. Kod tabanı boyunca dalgalanma etkilerini anla.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🎭 Kişiye Uyarlanabilir UI</h3>
|
||||||
|
<p>Kontrol paneli, kim olduğuna göre ayrıntı seviyesini ayarlar — junior geliştirici, ürün yöneticisi veya güçlü kullanıcı.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🏗️ Katman Görselleştirmesi</h3>
|
||||||
|
<p>Mimari katmana göre otomatik gruplama — API, Servis, Veri, UI, Yardımcı — renk kodlu efsaneyle.</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📚 Dil Kavramları</h3>
|
||||||
|
<p>12 programlama deseni (generikler, kapanışlar, dekoratörler, vb.) göründükleri her yerde bağlam içinde açıklanır.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Hızlı Başlangıç
|
||||||
|
|
||||||
|
### 1. Eklentiyi yükle
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Kod tabanını analiz et
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand
|
||||||
|
```
|
||||||
|
|
||||||
|
Çok-ajan hattı projenizi tarar, her dosya, fonksiyon, sınıf ve bağımlılığı çıkarır, ardından `.understand-anything/knowledge-graph.json` dosyasına kaydedilen bir bilgi grafiği oluşturur.
|
||||||
|
|
||||||
|
**Yerelleştirilmiş çıktı:** İstediğiniz dilde içerik oluşturmak için `--language` kullanın:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# İstediğiniz dilde içerik oluştur (düğüm açıklamaları ve dashboard UI)
|
||||||
|
/understand --language en
|
||||||
|
|
||||||
|
# Desteklenen diller: en (varsayılan), zh, zh-TW, ja, ko, ru
|
||||||
|
```
|
||||||
|
|
||||||
|
`--language` parametresi şunları etkiler:
|
||||||
|
- Bilgi grafiğindeki düğüm özetleri ve açıklamalar
|
||||||
|
- Dashboard UI etiketleri, butonlar ve araç ipuçları
|
||||||
|
- Rehberli tur açıklamaları
|
||||||
|
|
||||||
|
### 3. Kontrol panelini keşfet
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
Kod tabanın bir grafik olarak görselleştirilmiş, mimari katmana göre renklendirilmiş, aranabilir ve tıklanabilir interaktif bir web kontrol paneli açılır. Kodunu, ilişkilerini ve sade Türkçe açıklamasını görmek için herhangi bir düğüm seç.
|
||||||
|
|
||||||
|
### 4. Öğrenmeye devam et
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Kod tabanı hakkında her şeyi sor
|
||||||
|
/understand-chat Ödeme akışı nasıl çalışır?
|
||||||
|
|
||||||
|
# Mevcut değişikliklerinin etkisini analiz et
|
||||||
|
/understand-diff
|
||||||
|
|
||||||
|
# Belirli bir dosya veya fonksiyona derinlemesine dal
|
||||||
|
/understand-explain src/auth/login.ts
|
||||||
|
|
||||||
|
# Yeni ekip üyeleri için bir işe alıştırma rehberi oluştur
|
||||||
|
/understand-onboard
|
||||||
|
|
||||||
|
# İş alanı bilgisini çıkar (alanlar, akışlar, adımlar)
|
||||||
|
/understand-domain
|
||||||
|
|
||||||
|
# Karpathy deseni LLM Wiki bilgi tabanını analiz et
|
||||||
|
/understand-knowledge ~/path/to/wiki
|
||||||
|
|
||||||
|
# İstediğin zaman tekrar çalıştır — varsayılan olarak artımlıdır (yalnızca değişen dosyaları analiz eder)
|
||||||
|
/understand
|
||||||
|
|
||||||
|
# Her commit'te otomatik artımlı güncelleme için post-commit kancası kur
|
||||||
|
/understand --auto-update
|
||||||
|
|
||||||
|
# Devasa monorepo'larda analizi bir alt dizinle sınırla
|
||||||
|
/understand src/frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Çoklu Platform Kurulumu
|
||||||
|
|
||||||
|
Understand-Anything birden fazla AI kodlama platformunda çalışır.
|
||||||
|
|
||||||
|
### Claude Code (Yerli)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tek satırlık kurulum (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI)
|
||||||
|
|
||||||
|
**macOS / Linux:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
|
||||||
|
# veya platformu doğrudan geçirerek soruyu atla:
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows (PowerShell):**
|
||||||
|
```powershell
|
||||||
|
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
|
||||||
|
```
|
||||||
|
|
||||||
|
Kurulum betiği depoyu `~/.understand-anything/repo` dizinine klonlar ve seçilen platform için uygun sembolik bağlantıları oluşturur. Sonrasında CLI/IDE'ni yeniden başlat.
|
||||||
|
|
||||||
|
- Desteklenen `<platform>` değerleri: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`
|
||||||
|
- Daha sonra güncelle: `./install.sh --update`
|
||||||
|
- Kaldır: `./install.sh --uninstall <platform>`
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
Bu depo klonlandığında Cursor, eklentiyi `.cursor-plugin/plugin.json` aracılığıyla otomatik olarak keşfeder. Manuel kurulum gerekmez — sadece klonla ve Cursor'da aç.
|
||||||
|
|
||||||
|
Otomatik keşif çalışmazsa manuel kur: **Cursor Settings → Plugins**'i aç, arama alanına `https://github.com/Lum1104/Understand-Anything` yapıştır ve oradan ekle.
|
||||||
|
|
||||||
|
### VS Code + GitHub Copilot
|
||||||
|
|
||||||
|
GitHub Copilot uzantısı (v1.108+) yüklü VS Code, `.copilot-plugin/plugin.json` aracılığıyla eklentiyi otomatik keşfeder. Manuel kurulum gerekmez — sadece klonla ve VS Code'da aç.
|
||||||
|
|
||||||
|
Tüm projelerde kullanmak için kişisel beceri olarak kurmak istersen yukarıdaki `install.sh`'ı `vscode` platformuyla çalıştır.
|
||||||
|
|
||||||
|
### Copilot CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Platform Uyumluluğu
|
||||||
|
|
||||||
|
| Platform | Durum | Kurulum Yöntemi |
|
||||||
|
|----------|--------|----------------|
|
||||||
|
| Claude Code | ✅ Yerli | Eklenti pazarı |
|
||||||
|
| Cursor | ✅ Destekleniyor | Otomatik keşif |
|
||||||
|
| VS Code + GitHub Copilot | ✅ Destekleniyor | Otomatik keşif |
|
||||||
|
| Copilot CLI | ✅ Destekleniyor | Eklenti kurulumu |
|
||||||
|
| Codex | ✅ Destekleniyor | `install.sh codex` |
|
||||||
|
| OpenCode | ✅ Destekleniyor | `install.sh opencode` |
|
||||||
|
| OpenClaw | ✅ Destekleniyor | `install.sh openclaw` |
|
||||||
|
| Antigravity | ✅ Destekleniyor | `install.sh antigravity` |
|
||||||
|
| Gemini CLI | ✅ Destekleniyor | `install.sh gemini` |
|
||||||
|
| Pi Agent | ✅ Destekleniyor | `install.sh pi` |
|
||||||
|
| Vibe CLI | ✅ Destekleniyor | `install.sh vibe` |
|
||||||
|
| Hermes | ✅ Destekleniyor | `install.sh hermes` |
|
||||||
|
| Cline | ✅ Destekleniyor | `install.sh cline` |
|
||||||
|
| KIMI CLI | ✅ Destekleniyor | `install.sh kimi` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Grafı Ekibinizle Paylaşın
|
||||||
|
|
||||||
|
Graf yalnızca bir JSON dosyasıdır — **bir kez commit'leyin, ekip arkadaşlarınız pipeline'ı çalıştırmadan kullansın**. Yeni üye oryantasyonu, PR incelemeleri ve docs-as-code iş akışları için idealdir.
|
||||||
|
|
||||||
|
> **Örnek:** [GoogleCloudPlatform/microservices-demo (fork)](https://github.com/Lum1104/microservices-demo) — commit'lenmiş grafı içeren Go / Java / Python / Node çok dilli referans projesi.
|
||||||
|
|
||||||
|
**Neyi commit'leyin:** `.understand-anything/` içindeki her şey, *ancak* `intermediate/` ve `diff-overlay.json` hariç (bunlar yerel geçici dosyalardır).
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
.understand-anything/intermediate/
|
||||||
|
.understand-anything/diff-overlay.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Güncel tutun:** `/understand --auto-update` etkinleştirin — bir post-commit kancası grafı artımlı olarak yamalar, böylece her commit eşleşen bir grafla birlikte gelir. Veya sürümden önce `/understand` komutunu elle yeniden çalıştırın.
|
||||||
|
|
||||||
|
**Büyük graflar (10 MB+):** **git-lfs** ile takip edin.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git lfs install
|
||||||
|
git lfs track ".understand-anything/*.json"
|
||||||
|
git add .gitattributes .understand-anything/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Kaputun Altında
|
||||||
|
|
||||||
|
### Tree-sitter + LLM hibriti
|
||||||
|
|
||||||
|
Deterministik olarak yapılabilecek işleri statik analiz, anlam çıkarımı gerektiren işleri LLM üstlenir:
|
||||||
|
|
||||||
|
- **Tree-sitter (deterministik)** — kaynak kodu somut sözdizimi ağacına ayrıştırır ve yapısal gerçekleri çıkarır: import'lar, export'lar, fonksiyon/sınıf tanımları, çağrı noktaları, kalıtım. Tarama aşamasında önceden çözülmüş `importMap` olarak file-analyzer'a iletilir, böylece import'ları kaynaktan tekrar türetmek zorunda kalmaz. Aynı girdi her zaman aynı çıktıyı verir; ayrıca artımlı güncellemelerin parmak izlerinin de temelidir.
|
||||||
|
- **LLM (anlamsal)** — ayrıştırılmış yapıyı ve orijinal kaynağı birlikte okuyarak ayrıştırıcıların üretemediği şeyleri üretir: düz dilde özetler, etiketler, mimari katman atamaları, iş alanı eşlemeleri, rehberli turlar, dil kavramı notları.
|
||||||
|
|
||||||
|
Bu ayrım sayesinde graf yapısal tarafta yeniden üretilebilir kalırken (aynı kod her zaman aynı kenarları üretir) anlamsal tarafta niyeti yakalayabilir (bir dosya yalnızca neyi import ettiği değil, *ne için* var olduğu da görülür).
|
||||||
|
|
||||||
|
### Çok-Ajan Hattı
|
||||||
|
|
||||||
|
`/understand` komutu 5 özel ajan düzenler ve `/understand-domain` 6. ajanı ekler:
|
||||||
|
|
||||||
|
| Ajan | Rol |
|
||||||
|
|-------|------|
|
||||||
|
| `project-scanner` | Dosyaları keşfet, dilleri ve çerçeveleri tespit et |
|
||||||
|
| `file-analyzer` | Fonksiyonları, sınıfları, içe aktarmaları çıkar; grafik düğümleri ve kenarları üret |
|
||||||
|
| `architecture-analyzer` | Mimari katmanları tanımla |
|
||||||
|
| `tour-builder` | Rehberli öğrenme turları oluştur |
|
||||||
|
| `graph-reviewer` | Grafik bütünlüğünü ve referans bütünlüğünü doğrula |
|
||||||
|
| `domain-analyzer` | İş alanları, akışlar ve işlem adımlarını çıkar (`/understand-domain` tarafından kullanılır) |
|
||||||
|
| `article-analyzer` | Wiki makalelerinden varlıkları, iddiaları ve örtük ilişkileri çıkar (`/understand-knowledge` tarafından kullanılır) |
|
||||||
|
|
||||||
|
Dosya analizörleri paralel çalışır (en fazla 3 eşzamanlı). Artımlı güncellemeleri destekler — yalnızca son çalıştırmadan bu yana değişen dosyaları yeniden analiz eder.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎥 Topluluk
|
||||||
|
|
||||||
|
**Better Stack** tarafından hazırlanan topluluk tanıtım videosu.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.youtube.com/watch?v=VmIUXVlt7_I"><img src="https://img.youtube.com/vi/VmIUXVlt7_I/maxresdefault.jpg" alt="Better Stack tarafından hazırlanan topluluk tanıtım videosu — YouTube'da izlemek için tıklayın" width="480" /></a>
|
||||||
|
<br />
|
||||||
|
<em><a href="https://www.youtube.com/watch?v=VmIUXVlt7_I">YouTube'da izle →</a></em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
Bir video, blog yazısı veya eğitim hazırladınız mı? Issue veya PR açın — burada yer vermekten mutluluk duyarız.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Katkıda Bulunma
|
||||||
|
|
||||||
|
Katkılar memnuniyetle karşılanır! Başlamak için:
|
||||||
|
|
||||||
|
1. Depoyu fork'la
|
||||||
|
2. Bir özellik dalı oluştur (`git checkout -b feature/benim-ozellligim`)
|
||||||
|
3. Testleri çalıştır (`pnpm --filter @understand-anything/core test`)
|
||||||
|
4. Değişikliklerini commit et ve bir pull request aç
|
||||||
|
|
||||||
|
Büyük değişiklikler için lütfen önce bir issue aç ki yaklaşımı tartışalım.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>Kodu körü körüne okumayı bırak. Her şeyi anlamaya başla.</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Star Geçmişi
|
||||||
|
|
||||||
|
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&theme=dark&legend=top-left" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
<img alt="Star Geçmişi Grafiği" src="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<em>Kullanan ve katkıda bulunan herkese teşekkürler — bunun insanlara zaman kazandırdığını bilmek, yapmaya değer kılan tek şeydi.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
MIT Lisansı © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||||
|
</p>
|
||||||
344
Understand-Anything-main/READMEs/README.zh-CN.md
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
<h1 align="center">Understand Anything</h1>
|
||||||
|
<p align="center">
|
||||||
|
<strong>将任意代码库、知识库或文档转化为可探索、可搜索、可对话的交互式知识图谱</strong>
|
||||||
|
<br />
|
||||||
|
<em>支持 Claude Code、Codex、Cursor、Copilot、Gemini CLI 等多平台。</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Lum1104%2FUnderstand-Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="../README.md">English</a> | <a href="README.zh-CN.md">简体中文</a> | <a href="README.zh-TW.md">繁體中文</a> | <a href="README.ja-JP.md">日本語</a> | <a href="README.ko-KR.md">한국어</a> | <a href="README.es-ES.md">Español</a> | <a href="README.tr-TR.md">Türkçe</a> | <a href="README.ru-RU.md">Русский</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#-快速开始"><img src="https://img.shields.io/badge/快速开始-blue" alt="Quick Start" /></a>
|
||||||
|
<a href="https://github.com/Lum1104/Understand-Anything/blob/main/LICENSE"><img src="https://img.shields.io/badge/许可证-MIT-yellow" alt="License: MIT" /></a>
|
||||||
|
<a href="https://docs.anthropic.com/en/docs/claude-code"><img src="https://img.shields.io/badge/Claude_Code-8A2BE2" alt="Claude Code" /></a>
|
||||||
|
<a href="#codex"><img src="https://img.shields.io/badge/Codex-000000" alt="Codex" /></a>
|
||||||
|
<a href="#vs-code--github-copilot"><img src="https://img.shields.io/badge/Copilot-24292e" alt="Copilot" /></a>
|
||||||
|
<a href="#copilot-cli"><img src="https://img.shields.io/badge/Copilot_CLI-24292e" alt="Copilot CLI" /></a>
|
||||||
|
<a href="#gemini-cli"><img src="https://img.shields.io/badge/Gemini_CLI-4285F4" alt="Gemini CLI" /></a>
|
||||||
|
<a href="#opencode"><img src="https://img.shields.io/badge/OpenCode-38bdf8" alt="OpenCode" /></a>
|
||||||
|
<a href="https://understand-anything.com"><img src="https://img.shields.io/badge/项目主页-d4a574" alt="Homepage" /></a>
|
||||||
|
<a href="https://understand-anything.com/demo/"><img src="https://img.shields.io/badge/在线演示-00c853" alt="Live Demo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="../assets/hero.png" alt="Understand Anything — 将任何代码库转换为交互式知识图谱" width="800" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>💬 <a href="https://discord.gg/pydat66RY">加入 Discord 社区 →</a></strong>
|
||||||
|
<br />
|
||||||
|
<em>来提问、分享你的项目、和社区一起讨论。</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**当你刚加入一个新团队,面对 20 万行代码,你从哪里开始?**
|
||||||
|
|
||||||
|
Understand Anything 是一个 [Claude Code Plugin](https://code.claude.com/docs/en/plugins-reference#plugins-reference),通过多智能体(multi-agent)架构分析你的项目,构建包含文件、函数、类以及依赖关系的知识图谱,并提供一个可视化交互界面,帮助你理解整个系统。不再”盲读代码”,而是从全局视角理解系统结构。
|
||||||
|
|
||||||
|
> **目标不是用代码库的复杂程度来惊艳你 —— 而是默默告诉你每一块是怎么拼在一起的。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ 核心功能
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **想直接体验?** 在我们的[主页](https://understand-anything.com/)试试[在线演示](https://understand-anything.com/demo/) — 一个可以平移、缩放、搜索和探索的全交互式仪表盘。
|
||||||
|
|
||||||
|
### 探索代码结构图
|
||||||
|
|
||||||
|
将你的代码库以交互式知识图谱的形式呈现——每个文件、函数和类都是可点击、可搜索、可探索的节点。选择任意节点即可查看通俗易懂的摘要、依赖关系和引导式学习路径。
|
||||||
|
|
||||||
|
### 理解业务逻辑
|
||||||
|
|
||||||
|
切换到领域视图,查看代码如何映射到真实的业务流程——以水平图的形式展示领域、流程和步骤。
|
||||||
|
|
||||||
|
### 分析知识库
|
||||||
|
|
||||||
|
将 `/understand-knowledge` 指向一个 [Karpathy 模式的 LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f),即可获得带有社区聚类的力导向知识图谱。确定性解析器从 `index.md` 中提取 wikilinks 和分类,然后 LLM 代理发现隐式关系、提取实体并挖掘论断——将你的 wiki 转化为可导航的互联思想图谱。
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🧭 引导式学习</h3>
|
||||||
|
<p>自动生成架构学习路径,按依赖顺序学习。</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🔍 语义搜索</h3>
|
||||||
|
<p>支持模糊搜索 + 语义搜索,例如搜索"哪些部分处理身份验证?"即可在整个图中获取相关结果。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📊 变更影响分析</h3>
|
||||||
|
<p>提交更改前,查看更改会影响系统的哪些部分。了解更改对整个代码库的连锁反应。</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🎭 用户角色自适应 UI</h3>
|
||||||
|
<p>根据用户类型(初级开发 / 项目经理 / 高级用户)调整其详细程度。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🏗️ 层级可视化</h3>
|
||||||
|
<p>按架构层级自动分组 — API,服务,数据,UI, 系统工具 — 并附有颜色编码图例。</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📚 语言概念</h3>
|
||||||
|
<p>12 种编程模式(泛型、闭包、装饰器等)将在上下文中逐一解释。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1. 安装插件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 分析你的代码库
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand
|
||||||
|
```
|
||||||
|
|
||||||
|
多智能体(multi-agent)架构会:扫描你的项目,提取函数 / 类 / 依赖,构建知识图谱保存至`.understand-anything/knowledge-graph.json`.
|
||||||
|
|
||||||
|
**本地化输出:** 使用 `--language` 参数生成中文内容:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 生成中文内容(知识图节点描述和 Dashboard UI)
|
||||||
|
/understand --language zh
|
||||||
|
|
||||||
|
# 支持的语言:en(默认)、zh、zh-TW、ja、ko、ru
|
||||||
|
```
|
||||||
|
|
||||||
|
`--language` 参数会影响:
|
||||||
|
- 知识图谱中的节点摘要和描述
|
||||||
|
- Dashboard UI 的标签、按钮和提示
|
||||||
|
- 导览路线的解释说明
|
||||||
|
|
||||||
|
### 3. 打开数据看板
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
打开交互式网页数据看板,您的代码库将以图表形式呈现 — 按架构层级进行颜色编码,支持搜索和点击。选择任意节点即可查看其代码、关系以及简明易懂的解释。
|
||||||
|
|
||||||
|
### 4. 深度使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 询问任意代码库的问题
|
||||||
|
/understand-chat How does the payment flow work?
|
||||||
|
|
||||||
|
# 分析当前修改的影响
|
||||||
|
/understand-diff
|
||||||
|
|
||||||
|
# 深入理解某个文件
|
||||||
|
/understand-explain src/auth/login.ts
|
||||||
|
|
||||||
|
# 为新团队成员生成指南
|
||||||
|
/understand-onboard
|
||||||
|
|
||||||
|
# 提取业务领域知识(领域、流程、步骤)
|
||||||
|
/understand-domain
|
||||||
|
|
||||||
|
# 分析 Karpathy 模式的 LLM Wiki 知识库
|
||||||
|
/understand-knowledge ~/path/to/wiki
|
||||||
|
|
||||||
|
# 直接重跑即可 —— 默认增量更新,只分析变更的文件
|
||||||
|
/understand
|
||||||
|
|
||||||
|
# 安装 post-commit 钩子,每次提交自动增量更新
|
||||||
|
/understand --auto-update
|
||||||
|
|
||||||
|
# 大型 monorepo?把分析范围限定到某个子目录
|
||||||
|
/understand src/frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 多平台支持
|
||||||
|
|
||||||
|
Understand-Anything 可在多个 AI 编码平台上运行。
|
||||||
|
|
||||||
|
### Claude Code(原生)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### 一行命令安装(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI)
|
||||||
|
|
||||||
|
**macOS / Linux:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
|
||||||
|
# 也可以直接传入平台名跳过交互提示:
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows(PowerShell):**
|
||||||
|
```powershell
|
||||||
|
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
|
||||||
|
```
|
||||||
|
|
||||||
|
安装脚本会将仓库克隆到 `~/.understand-anything/repo`,并为所选平台创建相应的符号链接。安装完成后请重启 CLI 或 IDE。
|
||||||
|
|
||||||
|
- 支持的 `<platform>` 取值:`gemini`、`codex`、`opencode`、`pi`、`openclaw`、`antigravity`、`vibe`、`vscode`、`hermes`、`cline`、`kimi`
|
||||||
|
- 后续更新:`./install.sh --update`
|
||||||
|
- 卸载:`./install.sh --uninstall <platform>`
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
克隆此仓库后,Cursor 会自动通过 `.cursor-plugin/plugin.json`文件发现插件。无需手动安装 — 只需克隆并在 Cursor 中打开即可。
|
||||||
|
|
||||||
|
若自动发现未生效,可手动安装:打开 **Cursor Settings → Plugins**,在搜索框中粘贴 `https://github.com/Lum1104/Understand-Anything` 并添加。
|
||||||
|
|
||||||
|
### VS Code + GitHub Copilot
|
||||||
|
|
||||||
|
安装 GitHub Copilot 扩展(v1.108+)后,VS Code 会通过 `.copilot-plugin/plugin.json` 自动发现插件,克隆后直接在 VS Code 中打开即可,无需手动安装。
|
||||||
|
|
||||||
|
若需要在所有项目中使用(个人技能),运行上面的 `install.sh` 并选择 `vscode` 平台即可。
|
||||||
|
|
||||||
|
### Copilot CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 多平台兼容
|
||||||
|
|
||||||
|
| 平台 | 状态 | 安装方式 |
|
||||||
|
|----------|--------|----------------|
|
||||||
|
| Claude Code | ✅ 原生 | 插件市场 |
|
||||||
|
| Cursor | ✅ 支持 | 自动发现 |
|
||||||
|
| VS Code + GitHub Copilot | ✅ 支持 | 自动发现 |
|
||||||
|
| Copilot CLI | ✅ 支持 | 插件安装 |
|
||||||
|
| Codex | ✅ 支持 | `install.sh codex` |
|
||||||
|
| OpenCode | ✅ 支持 | `install.sh opencode` |
|
||||||
|
| OpenClaw | ✅ 支持 | `install.sh openclaw` |
|
||||||
|
| Antigravity | ✅ 支持 | `install.sh antigravity` |
|
||||||
|
| Gemini CLI | ✅ 支持 | `install.sh gemini` |
|
||||||
|
| Pi Agent | ✅ 支持 | `install.sh pi` |
|
||||||
|
| Vibe CLI | ✅ 支持 | `install.sh vibe` |
|
||||||
|
| Hermes | ✅ 支持 | `install.sh hermes` |
|
||||||
|
| Cline | ✅ 支持 | `install.sh cline` |
|
||||||
|
| KIMI CLI | ✅ 支持 | `install.sh kimi` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 与团队共享知识图谱
|
||||||
|
|
||||||
|
图谱就是一份 JSON 文件——**提交一次,团队成员就可以跳过整条流水线**。适合新人上手、PR 评审和 docs-as-code 工作流。
|
||||||
|
|
||||||
|
> **示例:** [GoogleCloudPlatform/microservices-demo(fork)](https://github.com/Lum1104/microservices-demo) —— 包含已提交图谱的 Go / Java / Python / Node 多语言参考项目。
|
||||||
|
|
||||||
|
**需要提交的内容:** `.understand-anything/` 下的全部文件,*除了* `intermediate/` 和 `diff-overlay.json`(这些是本地临时文件)。
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
.understand-anything/intermediate/
|
||||||
|
.understand-anything/diff-overlay.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**保持最新:** 启用 `/understand --auto-update` —— 一个 post-commit 钩子会增量更新图谱,每次提交都能得到匹配的图谱版本。也可以在发布前手动重跑 `/understand`。
|
||||||
|
|
||||||
|
**大型图谱(10 MB 以上):** 使用 **git-lfs** 跟踪。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git lfs install
|
||||||
|
git lfs track ".understand-anything/*.json"
|
||||||
|
git add .gitattributes .understand-anything/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 技术原理
|
||||||
|
|
||||||
|
### Tree-sitter + LLM 混合分析
|
||||||
|
|
||||||
|
把确定性的事情交给静态分析,把需要语义理解的事情交给 LLM:
|
||||||
|
|
||||||
|
- **Tree-sitter(确定性)** —— 将源码解析为具体语法树,提取结构性事实:导入、导出、函数 / 类定义、调用点、继承关系。在 scan 阶段预先解析为 `importMap` 并传给 file-analyzer,避免它们再从源码推导一次 import。同样的输入永远得到同样的输出,并作为增量更新的指纹基础。
|
||||||
|
- **LLM(语义)** —— 读取解析后的结构以及原始源码,生成解析器做不了的事:plain-English 摘要、标签、架构层归属、业务领域映射、引导路径、语言概念标注。
|
||||||
|
|
||||||
|
正因为这个分工,图谱在结构层面是可复现的(同样的代码总是产生同样的边),同时在语义层面又能捕捉意图(一个文件是「为了什么」存在,而不仅仅是它 import 了什么)。
|
||||||
|
|
||||||
|
### 多智能体架构
|
||||||
|
|
||||||
|
`/understand` 命令调用 5 个 agent,`/understand-domain` 额外增加第 6 个:
|
||||||
|
|
||||||
|
| Agent | 职责 |
|
||||||
|
|-------|------|
|
||||||
|
| `project-scanner` | 扫描项目文件,检测语言和框架 |
|
||||||
|
| `file-analyzer` | 提取代码结构(函数、类和导入),生成图节点和边 |
|
||||||
|
| `architecture-analyzer` | 识别架构层 |
|
||||||
|
| `tour-builder` | 生成引导式学习路径 |
|
||||||
|
| `graph-reviewer` | 验证图的完整性和引用完整性 |
|
||||||
|
| `domain-analyzer` | 提取业务领域、流程和处理步骤(由 `/understand-domain` 使用) |
|
||||||
|
| `article-analyzer` | 从 wiki 文章中提取实体、论断和隐式关系(由 `/understand-knowledge` 使用) |
|
||||||
|
|
||||||
|
文件分析器并行运行(最多 3 个并发)。支持增量更新 — 仅重新分析自上次运行以来发生更改的文件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎥 社区
|
||||||
|
|
||||||
|
由 **Better Stack** 制作的社区视频教程。
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.youtube.com/watch?v=VmIUXVlt7_I"><img src="https://img.youtube.com/vi/VmIUXVlt7_I/maxresdefault.jpg" alt="Better Stack 制作的社区视频教程 —— 点击在 YouTube 观看" width="480" /></a>
|
||||||
|
<br />
|
||||||
|
<em><a href="https://www.youtube.com/watch?v=VmIUXVlt7_I">在 YouTube 上观看 →</a></em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
写过视频、博客或教程?提个 issue 或 PR —— 我们很乐意把它放在这里。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 贡献
|
||||||
|
|
||||||
|
欢迎贡献!以下是贡献指南:
|
||||||
|
|
||||||
|
1. Fork 项目
|
||||||
|
2. 新建分支 (`git checkout -b feature/my-feature`)
|
||||||
|
3. 运行测试 (`pnpm --filter @understand-anything/core test`)
|
||||||
|
4. 提交更改并创建一个PR请求
|
||||||
|
|
||||||
|
对于重大变更,请先提交 issue,以便我们讨论解决方案。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>不再盲读代码,而是理解整个系统</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Star 历史记录
|
||||||
|
|
||||||
|
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&theme=dark&legend=top-left" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<em>感谢每一位使用过、贡献过的朋友 —— 知道它替你们省下了一些时间,就是当初做它最值得的理由。</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
MIT 许可证 © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||||
|
</p>
|
||||||
344
Understand-Anything-main/READMEs/README.zh-TW.md
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
<h1 align="center">Understand Anything</h1>
|
||||||
|
<p align="center">
|
||||||
|
<strong>將任意程式碼庫、知識庫或文件轉化為可探索、可搜尋、可對話的互動式知識圖譜</strong>
|
||||||
|
<br />
|
||||||
|
<em>支援 Claude Code、Codex、Cursor、Copilot、Gemini CLI 等多平台。</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Lum1104%2FUnderstand-Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="../README.md">English</a> | <a href="README.zh-CN.md">简体中文</a> | <a href="README.zh-TW.md">繁體中文</a> | <a href="README.ja-JP.md">日本語</a> | <a href="README.ko-KR.md">한국어</a> | <a href="README.es-ES.md">Español</a> | <a href="README.tr-TR.md">Türkçe</a> | <a href="README.ru-RU.md">Русский</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#-快速開始"><img src="https://img.shields.io/badge/快速開始-blue" alt="Quick Start" /></a>
|
||||||
|
<a href="https://github.com/Lum1104/Understand-Anything/blob/main/LICENSE"><img src="https://img.shields.io/badge/授權條款-MIT-yellow" alt="License: MIT" /></a>
|
||||||
|
<a href="https://docs.anthropic.com/en/docs/claude-code"><img src="https://img.shields.io/badge/Claude_Code-8A2BE2" alt="Claude Code" /></a>
|
||||||
|
<a href="#codex"><img src="https://img.shields.io/badge/Codex-000000" alt="Codex" /></a>
|
||||||
|
<a href="#vs-code--github-copilot"><img src="https://img.shields.io/badge/Copilot-24292e" alt="Copilot" /></a>
|
||||||
|
<a href="#copilot-cli"><img src="https://img.shields.io/badge/Copilot_CLI-24292e" alt="Copilot CLI" /></a>
|
||||||
|
<a href="#gemini-cli"><img src="https://img.shields.io/badge/Gemini_CLI-4285F4" alt="Gemini CLI" /></a>
|
||||||
|
<a href="#opencode"><img src="https://img.shields.io/badge/OpenCode-38bdf8" alt="OpenCode" /></a>
|
||||||
|
<a href="https://understand-anything.com"><img src="https://img.shields.io/badge/專案首頁-d4a574" alt="Homepage" /></a>
|
||||||
|
<a href="https://understand-anything.com/demo/"><img src="https://img.shields.io/badge/線上展示-00c853" alt="Live Demo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="../assets/hero.png" alt="Understand Anything — 將任何程式碼庫轉換為互動式知識圖譜" width="800" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>💬 <a href="https://discord.gg/pydat66RY">加入 Discord 社群 →</a></strong>
|
||||||
|
<br />
|
||||||
|
<em>來提問、分享你的專案、和社群一起討論。</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**當你剛加入一個新團隊,面對 20 萬行程式碼,你從哪裡開始?**
|
||||||
|
|
||||||
|
Understand Anything 是一個 [Claude Code Plugin](https://code.claude.com/docs/en/plugins-reference#plugins-reference),透過多智能體(multi-agent)架構分析你的專案,建構包含檔案、函式、類別以及相依關係的知識圖譜,並提供一個視覺化互動介面,幫助你理解整個系統。不再「盲讀程式碼」,而是從全局視角理解系統結構。
|
||||||
|
|
||||||
|
> **目標不是用程式碼庫的複雜程度驚豔你 —— 而是默默告訴你每一塊是怎麼拼在一起的。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ 核心功能
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **想直接體驗?** 在我們的[首頁](https://understand-anything.com/)試試[線上演示](https://understand-anything.com/demo/) — 一個可以平移、縮放、搜尋和探索的全互動式儀表盤。
|
||||||
|
|
||||||
|
### 探索程式碼結構圖
|
||||||
|
|
||||||
|
將你的程式碼庫以互動式知識圖譜呈現——每個檔案、函式和類別都是可點擊、可搜尋、可探索的節點。選取任意節點即可檢視淺顯易懂的摘要、依賴關係和引導式學習路徑。
|
||||||
|
|
||||||
|
### 理解業務邏輯
|
||||||
|
|
||||||
|
切換到領域視圖,查看程式碼如何對應到真實的業務流程——以水平圖的形式展示領域、流程和步驟。
|
||||||
|
|
||||||
|
### 分析知識庫
|
||||||
|
|
||||||
|
將 `/understand-knowledge` 指向一個 [Karpathy 模式的 LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f),即可獲得帶有社群聚類的力導向知識圖譜。確定性解析器從 `index.md` 中提取 wikilinks 和分類,然後 LLM 代理發現隱式關係、提取實體並挖掘論斷——將你的 wiki 轉化為可導航的互聯思想圖譜。
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🧭 引導式學習</h3>
|
||||||
|
<p>自動產生架構學習路徑,按相依順序學習。</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🔍 語意搜尋</h3>
|
||||||
|
<p>支援模糊搜尋 + 語意搜尋,例如搜尋「哪些部分處理身分驗證?」即可在整個圖中獲取相關結果。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📊 變更影響分析</h3>
|
||||||
|
<p>提交變更前,查看變更會影響系統的哪些部分。了解變更對整個程式碼庫的連鎖反應。</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🎭 使用者角色自適應 UI</h3>
|
||||||
|
<p>根據使用者類型(初級開發 / 專案經理 / 進階使用者)調整其詳細程度。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>🏗️ 層級視覺化</h3>
|
||||||
|
<p>按架構層級自動分組 — API、服務、資料、UI、系統工具 — 並附有顏色編碼圖例。</p>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top">
|
||||||
|
<h3>📚 語言概念</h3>
|
||||||
|
<p>12 種程式設計模式(泛型、閉包、裝飾器等)將在上下文中逐一解釋。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速開始
|
||||||
|
|
||||||
|
### 1. 安裝外掛程式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 分析你的程式碼庫
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand
|
||||||
|
```
|
||||||
|
|
||||||
|
多智能體(multi-agent)架構會:掃描你的專案,提取函式 / 類別 / 相依關係,建構知識圖譜並儲存至 `.understand-anything/knowledge-graph.json`。
|
||||||
|
|
||||||
|
**在地化輸出:** 使用 `--language` 參數產生中文內容:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 產生繁體中文內容(知識圖節點描述和 Dashboard UI)
|
||||||
|
/understand --language zh-TW
|
||||||
|
|
||||||
|
# 支援的語言:en(預設)、zh、zh-TW、ja、ko、ru
|
||||||
|
```
|
||||||
|
|
||||||
|
`--language` 參數會影響:
|
||||||
|
- 知識圖譜中的節點摘要和描述
|
||||||
|
- Dashboard UI 的標籤、按鈕和提示
|
||||||
|
-導覽路線的解釋說明
|
||||||
|
|
||||||
|
### 3. 開啟資料看板
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/understand-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
開啟互動式網頁資料看板,你的程式碼庫將以圖表形式呈現 — 按架構層級進行顏色編碼,支援搜尋和點擊。選擇任意節點即可查看其程式碼、關係以及簡明易懂的解釋。
|
||||||
|
|
||||||
|
### 4. 深度使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 詢問任意程式碼庫的問題
|
||||||
|
/understand-chat 付款流程是怎麼運作的?
|
||||||
|
|
||||||
|
# 分析目前修改的影響
|
||||||
|
/understand-diff
|
||||||
|
|
||||||
|
# 深入理解某個檔案
|
||||||
|
/understand-explain src/auth/login.ts
|
||||||
|
|
||||||
|
# 為新團隊成員產生指南
|
||||||
|
/understand-onboard
|
||||||
|
|
||||||
|
# 提取業務領域知識(領域、流程、步驟)
|
||||||
|
/understand-domain
|
||||||
|
|
||||||
|
# 分析 Karpathy 模式的 LLM Wiki 知識庫
|
||||||
|
/understand-knowledge ~/path/to/wiki
|
||||||
|
|
||||||
|
# 直接重跑即可 —— 預設增量更新,只分析變更的檔案
|
||||||
|
/understand
|
||||||
|
|
||||||
|
# 安裝 post-commit 掛鉤,每次提交自動增量更新
|
||||||
|
/understand --auto-update
|
||||||
|
|
||||||
|
# 大型 monorepo?把分析範圍限定到某個子目錄
|
||||||
|
/understand src/frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 多平台支援
|
||||||
|
|
||||||
|
Understand-Anything 可在多個 AI 編碼平台上執行。
|
||||||
|
|
||||||
|
### Claude Code(原生)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
```
|
||||||
|
|
||||||
|
### 一行指令安裝(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI)
|
||||||
|
|
||||||
|
**macOS / Linux:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
|
||||||
|
# 也可以直接傳入平台名稱跳過互動提示:
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows(PowerShell):**
|
||||||
|
```powershell
|
||||||
|
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
|
||||||
|
```
|
||||||
|
|
||||||
|
安裝指令稿會將儲存庫複製到 `~/.understand-anything/repo`,並為所選平台建立相應的符號連結。安裝完成後請重新啟動 CLI 或 IDE。
|
||||||
|
|
||||||
|
- 支援的 `<platform>` 取值:`gemini`、`codex`、`opencode`、`pi`、`openclaw`、`antigravity`、`vibe`、`vscode`、`hermes`、`cline`、`kimi`
|
||||||
|
- 後續更新:`./install.sh --update`
|
||||||
|
- 解除安裝:`./install.sh --uninstall <platform>`
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
複製此儲存庫後,Cursor 會自動透過 `.cursor-plugin/plugin.json` 檔案發現外掛程式。無需手動安裝 — 只需複製並在 Cursor 中開啟即可。
|
||||||
|
|
||||||
|
若自動發現未生效,可手動安裝:開啟 **Cursor Settings → Plugins**,在搜尋框中貼上 `https://github.com/Lum1104/Understand-Anything` 並新增。
|
||||||
|
|
||||||
|
### VS Code + GitHub Copilot
|
||||||
|
|
||||||
|
安裝 GitHub Copilot 擴充功能(v1.108+)後,VS Code 會透過 `.copilot-plugin/plugin.json` 自動發現外掛程式,複製後直接在 VS Code 中開啟即可,無需手動安裝。
|
||||||
|
|
||||||
|
若需要在所有專案中使用(個人技能),執行上面的 `install.sh` 並選擇 `vscode` 平台即可。
|
||||||
|
|
||||||
|
### Copilot CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 多平台相容性
|
||||||
|
|
||||||
|
| 平台 | 狀態 | 安裝方式 |
|
||||||
|
|----------|--------|----------------|
|
||||||
|
| Claude Code | ✅ 原生 | 外掛程式市集 |
|
||||||
|
| Cursor | ✅ 支援 | 自動發現 |
|
||||||
|
| VS Code + GitHub Copilot | ✅ 支援 | 自動發現 |
|
||||||
|
| Copilot CLI | ✅ 支援 | 外掛程式安裝 |
|
||||||
|
| Codex | ✅ 支援 | `install.sh codex` |
|
||||||
|
| OpenCode | ✅ 支援 | `install.sh opencode` |
|
||||||
|
| OpenClaw | ✅ 支援 | `install.sh openclaw` |
|
||||||
|
| Antigravity | ✅ 支援 | `install.sh antigravity` |
|
||||||
|
| Gemini CLI | ✅ 支援 | `install.sh gemini` |
|
||||||
|
| Pi Agent | ✅ 支援 | `install.sh pi` |
|
||||||
|
| Vibe CLI | ✅ 支援 | `install.sh vibe` |
|
||||||
|
| Hermes | ✅ 支援 | `install.sh hermes` |
|
||||||
|
| Cline | ✅ 支援 | `install.sh cline` |
|
||||||
|
| KIMI CLI | ✅ 支援 | `install.sh kimi` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 與團隊共享知識圖譜
|
||||||
|
|
||||||
|
圖譜就是一份 JSON 檔案——**提交一次,團隊成員就可以跳過整條流水線**。適合新人上手、PR 審查和 docs-as-code 工作流程。
|
||||||
|
|
||||||
|
> **範例:** [GoogleCloudPlatform/microservices-demo(fork)](https://github.com/Lum1104/microservices-demo) —— 包含已提交圖譜的 Go / Java / Python / Node 多語言參考專案。
|
||||||
|
|
||||||
|
**需要提交的內容:** `.understand-anything/` 底下的全部檔案,*除了* `intermediate/` 與 `diff-overlay.json`(這些是本機暫存檔)。
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
.understand-anything/intermediate/
|
||||||
|
.understand-anything/diff-overlay.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**保持最新:** 啟用 `/understand --auto-update` —— 一個 post-commit 掛鉤會增量更新圖譜,讓每次提交都有對應的圖譜版本。也可以在發布前手動重跑 `/understand`。
|
||||||
|
|
||||||
|
**大型圖譜(10 MB 以上):** 使用 **git-lfs** 追蹤。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git lfs install
|
||||||
|
git lfs track ".understand-anything/*.json"
|
||||||
|
git add .gitattributes .understand-anything/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 技術原理
|
||||||
|
|
||||||
|
### Tree-sitter + LLM 混合分析
|
||||||
|
|
||||||
|
把確定性的事情交給靜態分析,把需要語意理解的事情交給 LLM:
|
||||||
|
|
||||||
|
- **Tree-sitter(確定性)** —— 將原始碼解析為具體語法樹,提取結構性事實:import、export、函式 / 類別定義、呼叫點、繼承關係。在 scan 階段預先解析為 `importMap` 並傳給 file-analyzer,避免它們再從原始碼推導一次 import。相同的輸入永遠得到相同的輸出,並作為增量更新的指紋基礎。
|
||||||
|
- **LLM(語意)** —— 讀取解析後的結構以及原始碼,產生解析器做不到的事:plain-English 摘要、標籤、架構層歸屬、業務領域映射、導覽路徑、語言概念標註。
|
||||||
|
|
||||||
|
正是這個分工讓圖譜在結構層面具備可重現性(同樣的程式碼總是產生同樣的邊),同時在語意層面也能捕捉意圖(一個檔案是「為了什麼」而存在,不只是它 import 了什麼)。
|
||||||
|
|
||||||
|
### 多智能體架構
|
||||||
|
|
||||||
|
`/understand` 指令呼叫 5 個 agent,`/understand-domain` 額外增加第 6 個:
|
||||||
|
|
||||||
|
| Agent | 職責 |
|
||||||
|
|-------|------|
|
||||||
|
| `project-scanner` | 掃描專案檔案,偵測語言和框架 |
|
||||||
|
| `file-analyzer` | 提取程式碼結構(函式、類別和匯入),產生圖節點和邊 |
|
||||||
|
| `architecture-analyzer` | 識別架構層 |
|
||||||
|
| `tour-builder` | 產生引導式學習路徑 |
|
||||||
|
| `graph-reviewer` | 驗證圖的完整性和參考完整性 |
|
||||||
|
| `domain-analyzer` | 提取業務領域、流程和處理步驟(由 `/understand-domain` 使用) |
|
||||||
|
| `article-analyzer` | 從 wiki 文章中提取實體、論斷和隱式關係(由 `/understand-knowledge` 使用) |
|
||||||
|
|
||||||
|
檔案分析器並行執行(最多 3 個並發)。支援增量更新 — 僅重新分析自上次執行以來發生變更的檔案。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎥 社群
|
||||||
|
|
||||||
|
由 **Better Stack** 製作的社群影片導覽。
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.youtube.com/watch?v=VmIUXVlt7_I"><img src="https://img.youtube.com/vi/VmIUXVlt7_I/maxresdefault.jpg" alt="Better Stack 製作的社群影片導覽 —— 點擊在 YouTube 觀看" width="480" /></a>
|
||||||
|
<br />
|
||||||
|
<em><a href="https://www.youtube.com/watch?v=VmIUXVlt7_I">在 YouTube 上觀看 →</a></em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
寫過影片、部落格或教學?開一個 issue 或 PR —— 我們很樂意將它放在這裡。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 貢獻
|
||||||
|
|
||||||
|
歡迎貢獻!以下是貢獻指南:
|
||||||
|
|
||||||
|
1. Fork 專案
|
||||||
|
2. 新建分支(`git checkout -b feature/my-feature`)
|
||||||
|
3. 執行測試(`pnpm --filter @understand-anything/core test`)
|
||||||
|
4. 提交變更並建立 PR
|
||||||
|
|
||||||
|
對於重大變更,請先提交 issue,以便我們討論解決方案。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>不再盲讀程式碼,而是理解整個系統</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Star 歷史記錄
|
||||||
|
|
||||||
|
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&theme=dark&legend=top-left" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Lum1104/Understand-Anything&type=date&legend=top-left" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<em>感謝每一位使用過、貢獻過的朋友 —— 知道它替你們省下了一些時間,就是當初做它最值得的理由。</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
MIT 授權條款 © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||||
|
</p>
|
||||||
BIN
Understand-Anything-main/assets/hero.png
Normal file
|
After Width: | Height: | Size: 733 KiB |
BIN
Understand-Anything-main/assets/overview.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
@@ -0,0 +1,560 @@
|
|||||||
|
# Multi-Platform Simple Implementation Plan
|
||||||
|
|
||||||
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||||
|
|
||||||
|
**Goal:** Make Understand-Anything skills work across Codex, OpenClaw, OpenCode, and Cursor — same files everywhere, no build step.
|
||||||
|
|
||||||
|
**Architecture:** Move 5 pipeline agents into `skills/understand/` as prompt templates. Create a reusable `knowledge-graph-guide` agent. Move per-platform config directories to repo root for auto-discovery. Add Cursor and Claude plugin descriptors.
|
||||||
|
|
||||||
|
**Tech Stack:** Markdown (SKILL.md, INSTALL.md), YAML frontmatter, JSON (plugin descriptors), Bash (symlink/clone commands in install docs).
|
||||||
|
|
||||||
|
**Design Doc:** `docs/plans/2026-03-18-multi-platform-simple-design.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Move pipeline agents into skills/understand/ as prompt templates
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Move: `understand-anything-plugin/agents/project-scanner.md` → `understand-anything-plugin/skills/understand/project-scanner-prompt.md`
|
||||||
|
- Move: `understand-anything-plugin/agents/file-analyzer.md` → `understand-anything-plugin/skills/understand/file-analyzer-prompt.md`
|
||||||
|
- Move: `understand-anything-plugin/agents/architecture-analyzer.md` → `understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md`
|
||||||
|
- Move: `understand-anything-plugin/agents/tour-builder.md` → `understand-anything-plugin/skills/understand/tour-builder-prompt.md`
|
||||||
|
- Move: `understand-anything-plugin/agents/graph-reviewer.md` → `understand-anything-plugin/skills/understand/graph-reviewer-prompt.md`
|
||||||
|
|
||||||
|
**Step 1: Copy each agent file to the new location**
|
||||||
|
|
||||||
|
For each of the 5 files, copy from `agents/` to `skills/understand/` with the new name.
|
||||||
|
|
||||||
|
**Step 2: Strip agent frontmatter from the prompt templates**
|
||||||
|
|
||||||
|
Each prompt template file should remove the agent-specific YAML frontmatter (`name`, `description`, `tools`, `model`). Replace it with a simple Markdown header describing the template's purpose.
|
||||||
|
|
||||||
|
For example, `project-scanner-prompt.md` changes from:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: project-scanner
|
||||||
|
description: Scans a project directory...
|
||||||
|
tools: Bash, Glob, Grep, Read, Write
|
||||||
|
model: sonnet
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a meticulous project inventory specialist...
|
||||||
|
```
|
||||||
|
|
||||||
|
To:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Project Scanner — Prompt Template
|
||||||
|
|
||||||
|
> Used by `/understand` Phase 1. Dispatch as a subagent with this full content as the prompt.
|
||||||
|
|
||||||
|
You are a meticulous project inventory specialist...
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply this pattern to all 5 files:
|
||||||
|
- `project-scanner-prompt.md` — "Used by `/understand` Phase 1"
|
||||||
|
- `file-analyzer-prompt.md` — "Used by `/understand` Phase 2"
|
||||||
|
- `architecture-analyzer-prompt.md` — "Used by `/understand` Phase 4"
|
||||||
|
- `tour-builder-prompt.md` — "Used by `/understand` Phase 5"
|
||||||
|
- `graph-reviewer-prompt.md` — "Used by `/understand` Phase 6"
|
||||||
|
|
||||||
|
Keep the rest of the file content (the body instructions) exactly as-is.
|
||||||
|
|
||||||
|
**Step 3: Delete the original agent files**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd understand-anything-plugin
|
||||||
|
rm agents/project-scanner.md agents/file-analyzer.md agents/architecture-analyzer.md agents/tour-builder.md agents/graph-reviewer.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 4: Verify the files exist in the new location**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls understand-anything-plugin/skills/understand/
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `SKILL.md`, plus the 5 `*-prompt.md` files.
|
||||||
|
|
||||||
|
**Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add -A understand-anything-plugin/agents/ understand-anything-plugin/skills/understand/
|
||||||
|
git commit -m "refactor: move pipeline agents into skills/understand/ as prompt templates"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Update SKILL.md dispatch references with context injection
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/skills/understand/SKILL.md`
|
||||||
|
|
||||||
|
**Step 1: Read the current SKILL.md**
|
||||||
|
|
||||||
|
Read `understand-anything-plugin/skills/understand/SKILL.md` in full.
|
||||||
|
|
||||||
|
**Step 2: Update Phase 0 — add context collection**
|
||||||
|
|
||||||
|
After the decision logic table (line ~47), add a new section for collecting project context that will be injected into later phases:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
7. **Collect project context for subagent injection:**
|
||||||
|
- Read `README.md` (or `README.rst`, `readme.md`) from `$PROJECT_ROOT` if it exists. Store as `$README_CONTENT` (first 3000 characters).
|
||||||
|
- Read the primary package manifest (`package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `pom.xml`) if it exists. Store as `$MANIFEST_CONTENT`.
|
||||||
|
- Capture the top-level directory tree:
|
||||||
|
```bash
|
||||||
|
find $PROJECT_ROOT -maxdepth 2 -type f | 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`.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: Update Phase 1 dispatch — inject README + manifest**
|
||||||
|
|
||||||
|
Replace the Phase 1 dispatch line:
|
||||||
|
```
|
||||||
|
Dispatch the **project-scanner** agent with this prompt:
|
||||||
|
```
|
||||||
|
|
||||||
|
With:
|
||||||
|
```markdown
|
||||||
|
Dispatch a subagent using the prompt template at `./project-scanner-prompt.md`. Read the template file and pass the full content as the subagent's prompt, appending the following additional context:
|
||||||
|
|
||||||
|
> **Additional context from main session:**
|
||||||
|
>
|
||||||
|
> Project README (first 3000 chars):
|
||||||
|
> ```
|
||||||
|
> $README_CONTENT
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Package manifest:
|
||||||
|
> ```
|
||||||
|
> $MANIFEST_CONTENT
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Use this context to produce more accurate project name, description, and framework detection. The README and manifest are authoritative — prefer their information over heuristics.
|
||||||
|
|
||||||
|
Pass these parameters in the dispatch prompt:
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 4: Update Phase 2 dispatch — inject scan results + framework context**
|
||||||
|
|
||||||
|
Replace the Phase 2 dispatch paragraph:
|
||||||
|
```
|
||||||
|
For each batch, dispatch a **file-analyzer** agent. Run up to **3 agents concurrently** using parallel dispatch. Each agent gets this prompt:
|
||||||
|
```
|
||||||
|
|
||||||
|
With:
|
||||||
|
```markdown
|
||||||
|
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:
|
||||||
|
|
||||||
|
> **Additional context from main session:**
|
||||||
|
>
|
||||||
|
> Project: `<projectName>` — `<projectDescription>`
|
||||||
|
> Frameworks detected: `<frameworks from Phase 1>`
|
||||||
|
> Languages: `<languages from Phase 1>`
|
||||||
|
>
|
||||||
|
> 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 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.
|
||||||
|
|
||||||
|
Fill in batch-specific parameters below and dispatch:
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 5: Update Phase 4 dispatch — inject framework hints + directory tree**
|
||||||
|
|
||||||
|
Replace the Phase 4 dispatch line:
|
||||||
|
```
|
||||||
|
Dispatch the **architecture-analyzer** agent with this prompt:
|
||||||
|
```
|
||||||
|
|
||||||
|
With:
|
||||||
|
```markdown
|
||||||
|
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:
|
||||||
|
|
||||||
|
> **Additional context from main session:**
|
||||||
|
>
|
||||||
|
> Frameworks detected: `<frameworks from Phase 1>`
|
||||||
|
>
|
||||||
|
> Directory tree (top 2 levels):
|
||||||
|
> ```
|
||||||
|
> $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/` → API Layer, `models/` → Data Layer, `templates/` → UI Layer, `management/` → CLI 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.
|
||||||
|
|
||||||
|
Pass these parameters in the dispatch prompt:
|
||||||
|
```
|
||||||
|
|
||||||
|
Also add after the "For incremental updates" note:
|
||||||
|
```markdown
|
||||||
|
**Context for incremental updates:** When re-running architecture analysis, also inject the previous layer definitions:
|
||||||
|
|
||||||
|
> Previous layer definitions (for naming consistency):
|
||||||
|
> ```json
|
||||||
|
> [previous layers from existing graph]
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Maintain the same layer names and IDs where possible. Only add/remove layers if the file structure has materially changed.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 6: Update Phase 5 dispatch — inject README + entry point**
|
||||||
|
|
||||||
|
Replace the Phase 5 dispatch line:
|
||||||
|
```
|
||||||
|
Dispatch the **tour-builder** agent with this prompt:
|
||||||
|
```
|
||||||
|
|
||||||
|
With:
|
||||||
|
```markdown
|
||||||
|
Dispatch a subagent using the prompt template at `./tour-builder-prompt.md`. Read the template file and pass the full content as the subagent's prompt, appending the following additional context:
|
||||||
|
|
||||||
|
> **Additional context from main session:**
|
||||||
|
>
|
||||||
|
> Project README (first 3000 chars):
|
||||||
|
> ```
|
||||||
|
> $README_CONTENT
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Project entry point: `$ENTRY_POINT`
|
||||||
|
>
|
||||||
|
> Use the README to align the tour narrative with the project's own documentation. Start the tour from the entry point if one was detected. The tour should tell the same story the README tells, but through the lens of actual code structure.
|
||||||
|
|
||||||
|
Pass these parameters in the dispatch prompt:
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 7: Update Phase 6 dispatch — inject scan results for cross-validation**
|
||||||
|
|
||||||
|
Replace the Phase 6 dispatch line:
|
||||||
|
```
|
||||||
|
2. Dispatch the **graph-reviewer** agent with this prompt:
|
||||||
|
```
|
||||||
|
|
||||||
|
With:
|
||||||
|
```markdown
|
||||||
|
2. Dispatch a subagent using the prompt template at `./graph-reviewer-prompt.md`. Read the template file and pass the full content as the subagent's prompt, appending the following additional context:
|
||||||
|
|
||||||
|
> **Additional context from main session:**
|
||||||
|
>
|
||||||
|
> Phase 1 scan results (file inventory):
|
||||||
|
> ```json
|
||||||
|
> [list of {path, sizeLines} from scan-result.json]
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Phase warnings/errors accumulated during analysis:
|
||||||
|
> - [list any batch failures, skipped files, or warnings from Phases 2-5]
|
||||||
|
>
|
||||||
|
> Cross-validate: every file in the scan inventory should have a corresponding `file:` node in the graph. Flag any missing files. Also flag any graph nodes whose `filePath` doesn't appear in the scan inventory.
|
||||||
|
|
||||||
|
Pass these parameters in the dispatch prompt:
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 8: Update Error Handling section**
|
||||||
|
|
||||||
|
Change:
|
||||||
|
```
|
||||||
|
- If any agent dispatch fails, retry **once** with the same prompt plus additional context about the failure.
|
||||||
|
```
|
||||||
|
|
||||||
|
To:
|
||||||
|
```
|
||||||
|
- If any subagent dispatch fails, retry **once** with the same prompt plus additional context about the failure.
|
||||||
|
- Track all warnings and errors from each phase in a `$PHASE_WARNINGS` list. Pass this list to the graph-reviewer in Phase 6 for comprehensive validation.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 9: Verify no references to named agent dispatch remain**
|
||||||
|
|
||||||
|
Search for "Dispatch the **" in the file — should find 0 results.
|
||||||
|
|
||||||
|
**Step 10: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/skills/understand/SKILL.md
|
||||||
|
git commit -m "refactor: update SKILL.md to dispatch subagents with context injection"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Create knowledge-graph-guide agent
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `understand-anything-plugin/agents/knowledge-graph-guide.md`
|
||||||
|
|
||||||
|
**Step 1: Write the agent definition**
|
||||||
|
|
||||||
|
Create `understand-anything-plugin/agents/knowledge-graph-guide.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: knowledge-graph-guide
|
||||||
|
description: |
|
||||||
|
Use this agent when users need help understanding, querying, or working
|
||||||
|
with an Understand-Anything knowledge graph. Guides users through graph
|
||||||
|
structure, node/edge relationships, layer architecture, tours, and
|
||||||
|
dashboard usage.
|
||||||
|
model: inherit
|
||||||
|
---
|
||||||
|
|
||||||
|
You are an expert on Understand-Anything knowledge graphs. You help users navigate, query, and understand the `knowledge-graph.json` files produced by the `/understand` skill.
|
||||||
|
|
||||||
|
## What You Know
|
||||||
|
|
||||||
|
### Graph Location
|
||||||
|
|
||||||
|
The knowledge graph lives at `<project-root>/.understand-anything/knowledge-graph.json`. Metadata is at `<project-root>/.understand-anything/meta.json`.
|
||||||
|
|
||||||
|
### Graph Structure
|
||||||
|
|
||||||
|
The JSON has this top-level shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"project": { "name", "languages", "frameworks", "description", "analyzedAt", "gitCommitHash" },
|
||||||
|
"nodes": [...],
|
||||||
|
"edges": [...],
|
||||||
|
"layers": [...],
|
||||||
|
"tour": [...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Node Types (5)
|
||||||
|
|
||||||
|
| Type | ID Convention | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `file` | `file:<relative-path>` | Source file |
|
||||||
|
| `function` | `func:<relative-path>:<name>` | Function or method |
|
||||||
|
| `class` | `class:<relative-path>:<name>` | Class, interface, or type |
|
||||||
|
| `module` | `module:<name>` | Logical module or package |
|
||||||
|
| `concept` | `concept:<name>` | Abstract concept or pattern |
|
||||||
|
|
||||||
|
### Edge Types (18)
|
||||||
|
|
||||||
|
| Category | Types |
|
||||||
|
|---|---|
|
||||||
|
| Structural | `imports`, `exports`, `contains`, `inherits`, `implements` |
|
||||||
|
| Behavioral | `calls`, `subscribes`, `publishes`, `middleware` |
|
||||||
|
| Data flow | `reads_from`, `writes_to`, `transforms`, `validates` |
|
||||||
|
| Dependencies | `depends_on`, `tested_by`, `configures` |
|
||||||
|
| Semantic | `related`, `similar_to` |
|
||||||
|
|
||||||
|
### Layers
|
||||||
|
|
||||||
|
Layers represent architectural groupings (e.g., API, Service, Data, UI). Each layer has an `id`, `name`, `description`, and `nodeIds` array.
|
||||||
|
|
||||||
|
### Tours
|
||||||
|
|
||||||
|
Tours are guided walkthroughs with sequential steps. Each step has a `title`, `description`, `nodeId` (focus node), and optional `highlightEdges`.
|
||||||
|
|
||||||
|
## How to Help Users
|
||||||
|
|
||||||
|
1. **Finding things**: Help users locate nodes by file path, function name, or concept. Use `jq` or grep on the JSON.
|
||||||
|
2. **Understanding relationships**: Trace edges between nodes to explain dependencies, call chains, and data flow.
|
||||||
|
3. **Architecture overview**: Summarize layers and their contents.
|
||||||
|
4. **Onboarding**: Walk through the tour steps to explain the codebase.
|
||||||
|
5. **Dashboard**: Guide users to run `/understand-dashboard` to visualize the graph interactively.
|
||||||
|
6. **Querying**: Help users write `jq` commands to extract specific information from the graph JSON.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/agents/knowledge-graph-guide.md
|
||||||
|
git commit -m "feat: add knowledge-graph-guide agent for graph navigation and querying"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Move platform INSTALL.md files to repo root
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Move: `understand-anything-plugin/.codex/INSTALL.md` → `.codex/INSTALL.md`
|
||||||
|
- Move: `understand-anything-plugin/.opencode/INSTALL.md` → `.opencode/INSTALL.md`
|
||||||
|
- Move: `understand-anything-plugin/.openclaw/INSTALL.md` → `.openclaw/INSTALL.md`
|
||||||
|
- Delete: `understand-anything-plugin/.cursor/INSTALL.md` (replaced by `.cursor-plugin/plugin.json`)
|
||||||
|
|
||||||
|
**Step 1: Move the three platform directories to root**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/yuxianglin/Desktop/opensource/Understand-Anything
|
||||||
|
git mv understand-anything-plugin/.codex ./.codex
|
||||||
|
git mv understand-anything-plugin/.opencode ./.opencode
|
||||||
|
git mv understand-anything-plugin/.openclaw ./.openclaw
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Delete .cursor/ (replaced by .cursor-plugin/ in Task 5)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git rm -r understand-anything-plugin/.cursor/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: Verify symlink paths are correct**
|
||||||
|
|
||||||
|
Read each INSTALL.md. The symlink paths should reference `understand-anything-plugin/skills` — this is still correct since the skills directory remains inside the plugin wrapper.
|
||||||
|
|
||||||
|
**Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add -A
|
||||||
|
git commit -m "refactor: move platform config directories to repo root for discovery"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Add plugin descriptors
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `.cursor-plugin/plugin.json`
|
||||||
|
- Create: `.claude-plugin/plugin.json`
|
||||||
|
|
||||||
|
**Step 1: Create `.cursor-plugin/plugin.json`**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "understand-anything",
|
||||||
|
"displayName": "Understand Anything",
|
||||||
|
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
|
||||||
|
"version": "1.0.5",
|
||||||
|
"author": { "name": "Lum1104" },
|
||||||
|
"homepage": "https://github.com/Lum1104/Understand-Anything",
|
||||||
|
"repository": "https://github.com/Lum1104/Understand-Anything",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": ["codebase-analysis", "knowledge-graph", "architecture", "onboarding", "dashboard"],
|
||||||
|
"skills": "./understand-anything-plugin/skills/",
|
||||||
|
"agents": "./understand-anything-plugin/agents/"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: paths point into `understand-anything-plugin/` since the source stays nested.
|
||||||
|
|
||||||
|
**Step 2: Create `.claude-plugin/plugin.json`**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "understand-anything",
|
||||||
|
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
|
||||||
|
"version": "1.0.5",
|
||||||
|
"author": { "name": "Lum1104" },
|
||||||
|
"homepage": "https://github.com/Lum1104/Understand-Anything",
|
||||||
|
"repository": "https://github.com/Lum1104/Understand-Anything",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": ["codebase-analysis", "knowledge-graph", "architecture", "onboarding", "dashboard"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .cursor-plugin/ .claude-plugin/plugin.json
|
||||||
|
git commit -m "feat: add Cursor and Claude plugin descriptors for auto-discovery"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: Update README with corrected multi-platform URLs
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `README.md`
|
||||||
|
|
||||||
|
**Step 1: Read current README**
|
||||||
|
|
||||||
|
Read `README.md` in full.
|
||||||
|
|
||||||
|
**Step 2: Update raw GitHub URLs for INSTALL.md files**
|
||||||
|
|
||||||
|
The INSTALL.md files moved from `understand-anything-plugin/.codex/INSTALL.md` to `.codex/INSTALL.md`. Update all raw GitHub URLs:
|
||||||
|
|
||||||
|
```
|
||||||
|
OLD: .../refs/heads/main/understand-anything-plugin/.codex/INSTALL.md
|
||||||
|
NEW: .../refs/heads/main/.codex/INSTALL.md
|
||||||
|
|
||||||
|
OLD: .../refs/heads/main/understand-anything-plugin/.openclaw/INSTALL.md
|
||||||
|
NEW: .../refs/heads/main/.openclaw/INSTALL.md
|
||||||
|
|
||||||
|
OLD: .../refs/heads/main/understand-anything-plugin/.opencode/INSTALL.md
|
||||||
|
NEW: .../refs/heads/main/.opencode/INSTALL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: Replace Cursor section**
|
||||||
|
|
||||||
|
Replace the Cursor AI-driven install section with:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
Cursor auto-discovers the plugin via `.cursor-plugin/plugin.json` when this repo is cloned. No manual installation needed — just clone and open in Cursor.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add README.md
|
||||||
|
git commit -m "docs: update multi-platform URLs after moving configs to root"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 7: Verify everything works
|
||||||
|
|
||||||
|
**Step 1: Check platform configs at root**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls .codex/INSTALL.md .opencode/INSTALL.md .openclaw/INSTALL.md
|
||||||
|
ls .cursor-plugin/plugin.json .claude-plugin/plugin.json
|
||||||
|
```
|
||||||
|
|
||||||
|
All should exist.
|
||||||
|
|
||||||
|
**Step 2: Verify plugin source is intact**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls understand-anything-plugin/skills/understand/
|
||||||
|
ls understand-anything-plugin/agents/
|
||||||
|
ls understand-anything-plugin/packages/
|
||||||
|
```
|
||||||
|
|
||||||
|
Skills, agents, and packages should all still exist inside the wrapper.
|
||||||
|
|
||||||
|
**Step 3: Verify no platform configs remain inside the wrapper**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls understand-anything-plugin/.codex/ 2>/dev/null # should fail
|
||||||
|
ls understand-anything-plugin/.cursor/ 2>/dev/null # should fail
|
||||||
|
ls understand-anything-plugin/.opencode/ 2>/dev/null # should fail
|
||||||
|
ls understand-anything-plugin/.openclaw/ 2>/dev/null # should fail
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 4: Run tests**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @understand-anything/core build && pnpm --filter @understand-anything/core test
|
||||||
|
```
|
||||||
|
|
||||||
|
All tests should pass — only config files moved, not source code.
|
||||||
|
|
||||||
|
**Step 5: Verify marketplace.json is unchanged**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat .claude-plugin/marketplace.json | grep source
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `"source": "./understand-anything-plugin"` — unchanged, still correct.
|
||||||
|
|
||||||
|
**Step 6: Verify no stale raw GitHub URLs**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -r "understand-anything-plugin/\." README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: 0 results (no URLs pointing to old nested platform config locations).
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
# Design: Dashboard Robustness — Permissive Graph Loading
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
When the LLM agent produces a knowledge-graph.json that deviates from the strict Zod schema, the dashboard shows a blank screen with cryptic Zod error paths. Users don't know whether it's a system bug or an agent generation issue, and their only recourse is a full re-run of `/understand`.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
1. **Maximize what the user can see** — load valid nodes/edges even if some are broken
|
||||||
|
2. **Clearly communicate generation issues** — amber warnings (not red errors) with copy-paste-friendly messages
|
||||||
|
3. **Empower targeted fixes** — users can copy the issue report and ask their agent to fix specific problems instead of a full re-run
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### Three-Layer Robustness Pipeline
|
||||||
|
|
||||||
|
```
|
||||||
|
Raw JSON → Sanitize (Tier 1) → Normalize + Auto-fix (Tier 2) → Validate per-item (Tier 3) → Fatal check (Tier 4) → Dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tier 1: Sanitize Silently
|
||||||
|
|
||||||
|
Common LLM quirks that are pure noise — fix without reporting.
|
||||||
|
|
||||||
|
| Issue | Fix |
|
||||||
|
|-------|-----|
|
||||||
|
| `null` on optional fields (`filePath`, `lineRange`, `description`, `languageNotes`) | Convert to `undefined` |
|
||||||
|
| Mixed-case enum strings (`"Forward"`, `"SIMPLE"`) | Lowercase before matching |
|
||||||
|
|
||||||
|
### Tier 2: Auto-fix With Info Notice
|
||||||
|
|
||||||
|
Recoverable issues — apply sensible defaults, track as `auto-corrected` issues.
|
||||||
|
|
||||||
|
| Issue | Default | Notes |
|
||||||
|
|-------|---------|-------|
|
||||||
|
| Missing `complexity` | `"moderate"` | Most common LLM omission |
|
||||||
|
| Missing `tags` | `[]` | Empty is valid |
|
||||||
|
| Missing `weight` | `0.5` | Middle of 0–1 range |
|
||||||
|
| `weight` as string | Coerce to number | e.g., `"0.8"` → `0.8` |
|
||||||
|
| Missing `direction` | `"forward"` | Safe default |
|
||||||
|
| Missing `summary` | Use node `name` | Better than empty |
|
||||||
|
| `tour: null` / `layers: null` | `[]` | Null vs empty array |
|
||||||
|
| Complexity aliases | `low/easy→simple`, `medium/intermediate→moderate`, `high/hard→complex` | |
|
||||||
|
| Direction aliases | `to/outbound→forward`, `from/inbound→backward`, `both→bidirectional` | |
|
||||||
|
| Existing node/edge type aliases | Already handled by `normalizeGraph` | No change needed |
|
||||||
|
| Missing node `type` | `"file"` | Safe fallback |
|
||||||
|
| Missing edge `type` | `"depends_on"` | Generic fallback |
|
||||||
|
|
||||||
|
### Tier 3: Drop With Warning
|
||||||
|
|
||||||
|
Can't safely guess — remove the item, track as `dropped` issue.
|
||||||
|
|
||||||
|
| Issue | Action |
|
||||||
|
|-------|--------|
|
||||||
|
| Edge references non-existent node ID | Drop edge |
|
||||||
|
| Node missing `id` | Drop node |
|
||||||
|
| Node missing `name` | Drop node |
|
||||||
|
| Edge missing `source` or `target` | Drop edge |
|
||||||
|
| Unrecognizable `type` value (not in canonical or alias list) | Drop item |
|
||||||
|
| `weight` not coercible to number | Drop edge |
|
||||||
|
|
||||||
|
### Tier 4: Fatal
|
||||||
|
|
||||||
|
Graph is unsalvageable — show red error banner.
|
||||||
|
|
||||||
|
| Condition | Message |
|
||||||
|
|-----------|---------|
|
||||||
|
| 0 valid nodes after filtering | "No valid nodes found in knowledge graph" |
|
||||||
|
| Missing `project` metadata entirely | "Missing project metadata" |
|
||||||
|
| Input is not an object / not valid JSON | "Invalid input format" |
|
||||||
|
|
||||||
|
### Return Type
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface GraphIssue {
|
||||||
|
level: 'auto-corrected' | 'dropped' | 'fatal';
|
||||||
|
category: string; // e.g., "missing-field", "invalid-reference", "type-coercion"
|
||||||
|
message: string; // human-readable, copy-paste friendly
|
||||||
|
path?: string; // e.g., "nodes[3].complexity"
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ValidationResult {
|
||||||
|
success: boolean;
|
||||||
|
data?: KnowledgeGraph;
|
||||||
|
issues: GraphIssue[];
|
||||||
|
fatal?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dashboard UI: WarningBanner Component
|
||||||
|
|
||||||
|
**New component** in `packages/dashboard/src/components/WarningBanner.tsx`.
|
||||||
|
|
||||||
|
**Visual design:**
|
||||||
|
- **Amber/gold theme** — `bg-amber-900/20`, `border-amber-700`, `text-amber-200`
|
||||||
|
- Matches dashboard's gold accent aesthetic; signals "generation quality issue" not "system crash"
|
||||||
|
- **Collapsed by default** — summary line: "Knowledge graph loaded with 5 auto-corrections and 2 dropped items"
|
||||||
|
- **Expandable** — click to reveal categorized issue list
|
||||||
|
- **Copy button** — one-click copies the full issue report as a pre-formatted message
|
||||||
|
- **Actionable footer** — tells users to copy issues and ask their agent to fix them
|
||||||
|
|
||||||
|
**Copy-paste output format:**
|
||||||
|
```
|
||||||
|
The following issues were found in your knowledge-graph.json.
|
||||||
|
These are LLM generation errors — not a system bug.
|
||||||
|
You can ask your agent to fix these specific issues in the knowledge-graph.json file:
|
||||||
|
|
||||||
|
[Auto-corrected] nodes[3] ("AuthService"): missing "complexity" — defaulted to "moderate"
|
||||||
|
[Auto-corrected] nodes[7] ("utils.ts"): missing "tags" — defaulted to []
|
||||||
|
[Auto-corrected] edges[12]: weight was string "0.8" — coerced to number
|
||||||
|
[Dropped] edges[5]: target "file:src/nonexistent.ts" does not exist in nodes
|
||||||
|
[Dropped] nodes[14]: missing required "id" field — cannot recover
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fatal errors** stay red (`bg-red-900/30`) with message: "Knowledge graph is unsalvageable: [reason]. Please re-run `/understand` to generate a new one."
|
||||||
|
|
||||||
|
**Existing red error banner** for network/JSON-parse errors stays as-is (those ARE system/infra issues).
|
||||||
|
|
||||||
|
### App.tsx Changes
|
||||||
|
|
||||||
|
- On `result.success === true` with `result.issues.length > 0`: show `WarningBanner` with issues, load graph normally
|
||||||
|
- On `result.fatal`: show existing red banner with fatal message
|
||||||
|
- `console.warn` for auto-corrected items, `console.error` for dropped items
|
||||||
|
|
||||||
|
### Test Coverage
|
||||||
|
|
||||||
|
All in `packages/core/src/__tests__/schema.test.ts`:
|
||||||
|
|
||||||
|
- **Tier 1:** `null` optional fields silently become `undefined`
|
||||||
|
- **Tier 2:** Missing `complexity`/`tags`/`weight`/`direction`/`summary` get defaults; issues tracked
|
||||||
|
- **Tier 2:** String `weight` coerced; complexity/direction aliases mapped
|
||||||
|
- **Tier 3:** Dangling edge references dropped; nodes missing `id` dropped; issues recorded
|
||||||
|
- **Tier 4:** Empty graph after filtering → fatal; missing `project` → fatal
|
||||||
|
- **Integration:** Graph with mixed good/bad nodes → loads with correct node count + correct issues list
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `packages/core/src/schema.ts` | Sanitize, expanded normalize, permissive validate, new types |
|
||||||
|
| `packages/dashboard/src/components/WarningBanner.tsx` | New component |
|
||||||
|
| `packages/dashboard/src/App.tsx` | Wire issues to WarningBanner |
|
||||||
|
| `packages/core/src/__tests__/schema.test.ts` | Tests for all tiers |
|
||||||
|
|
||||||
|
### Files NOT Changed
|
||||||
|
|
||||||
|
- Agent prompts (can be tightened later as a separate effort)
|
||||||
|
- GraphView / store logic (they already handle valid `KnowledgeGraph` objects)
|
||||||
|
- Existing node/edge type alias maps (preserved, extended around)
|
||||||
@@ -0,0 +1,971 @@
|
|||||||
|
# Token Reduction Implementation Plan
|
||||||
|
|
||||||
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||||
|
|
||||||
|
**Goal:** Reduce `/understand` token cost by ~85% on large codebases through import pre-resolution, batch consolidation, addendum removal, payload slimming, and gating the LLM reviewer.
|
||||||
|
|
||||||
|
**Architecture:** Five changes (C5 → C4 → C3 → C1+C2) applied in rollout order — lowest risk first. All changes are to prompt/skill markdown files in `understand-anything-plugin/skills/understand/`. No TypeScript source changes required.
|
||||||
|
|
||||||
|
**Tech Stack:** Markdown skill files, Node.js inline scripts embedded in SKILL.md, knowledge-graph JSON pipeline.
|
||||||
|
|
||||||
|
**Design doc:** `docs/plans/2026-03-27-token-reduction-design.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: C5 — Gate graph-reviewer behind `--review` flag
|
||||||
|
|
||||||
|
Replaces the always-on LLM graph-reviewer subagent with a deterministic inline validation script. The LLM reviewer only runs when `--review` is in `$ARGUMENTS`. Saves ~58,500 tokens per default run.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/skills/understand/SKILL.md` (Phase 6, lines 330–362)
|
||||||
|
|
||||||
|
### Step 1: Open SKILL.md and locate Phase 6
|
||||||
|
|
||||||
|
Read the file and find "## Phase 6 — REVIEW" (line 297). Identify steps 3–6 (lines 330–362) which currently always dispatch the LLM graph-reviewer subagent.
|
||||||
|
|
||||||
|
### Step 2: Replace Phase 6 steps 3–6 with conditional reviewer logic
|
||||||
|
|
||||||
|
Replace lines 330–362 (from "3. Dispatch a subagent using the prompt template" through "6. **If `approved: true`:** Proceed to Phase 7.") with:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
3. **Check `$ARGUMENTS` for `--review` flag.** Then run the appropriate validation path:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Default path (no `--review`): inline deterministic validation
|
||||||
|
|
||||||
|
Write the following Node.js script to `$PROJECT_ROOT/.understand-anything/tmp/ua-inline-validate.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const graphPath = process.argv[2];
|
||||||
|
const outputPath = process.argv[3];
|
||||||
|
try {
|
||||||
|
const graph = JSON.parse(fs.readFileSync(graphPath, 'utf8'));
|
||||||
|
const issues = [], warnings = [];
|
||||||
|
const nodeIds = new Set();
|
||||||
|
const seen = new Map();
|
||||||
|
graph.nodes.forEach((n, i) => {
|
||||||
|
if (!n.id) { issues.push(`Node[${i}] missing id`); return; }
|
||||||
|
if (!n.type) issues.push(`Node[${i}] '${n.id}' missing type`);
|
||||||
|
if (!n.name) issues.push(`Node[${i}] '${n.id}' missing name`);
|
||||||
|
if (!n.summary) issues.push(`Node[${i}] '${n.id}' missing summary`);
|
||||||
|
if (!n.tags || !n.tags.length) issues.push(`Node[${i}] '${n.id}' missing tags`);
|
||||||
|
if (seen.has(n.id)) issues.push(`Duplicate node ID '${n.id}' at indices ${seen.get(n.id)} and ${i}`);
|
||||||
|
else seen.set(n.id, i);
|
||||||
|
nodeIds.add(n.id);
|
||||||
|
});
|
||||||
|
graph.edges.forEach((e, i) => {
|
||||||
|
if (!nodeIds.has(e.source)) issues.push(`Edge[${i}] source '${e.source}' not found`);
|
||||||
|
if (!nodeIds.has(e.target)) issues.push(`Edge[${i}] target '${e.target}' not found`);
|
||||||
|
});
|
||||||
|
const fileNodes = graph.nodes.filter(n => n.type === 'file').map(n => n.id);
|
||||||
|
const assigned = new Map();
|
||||||
|
(graph.layers || []).forEach(layer => {
|
||||||
|
(layer.nodeIds || []).forEach(id => {
|
||||||
|
if (!nodeIds.has(id)) issues.push(`Layer '${layer.id}' refs missing node '${id}'`);
|
||||||
|
if (assigned.has(id)) issues.push(`Node '${id}' appears in multiple layers`);
|
||||||
|
assigned.set(id, layer.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
fileNodes.forEach(id => {
|
||||||
|
if (!assigned.has(id)) issues.push(`File node '${id}' not in any layer`);
|
||||||
|
});
|
||||||
|
(graph.tour || []).forEach((step, i) => {
|
||||||
|
(step.nodeIds || []).forEach(id => {
|
||||||
|
if (!nodeIds.has(id)) issues.push(`Tour step[${i}] refs missing node '${id}'`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const withEdges = new Set([
|
||||||
|
...graph.edges.map(e => e.source),
|
||||||
|
...graph.edges.map(e => e.target)
|
||||||
|
]);
|
||||||
|
graph.nodes.forEach(n => {
|
||||||
|
if (!withEdges.has(n.id)) warnings.push(`Node '${n.id}' has no edges (orphan)`);
|
||||||
|
});
|
||||||
|
const stats = {
|
||||||
|
totalNodes: graph.nodes.length,
|
||||||
|
totalEdges: graph.edges.length,
|
||||||
|
totalLayers: (graph.layers || []).length,
|
||||||
|
tourSteps: (graph.tour || []).length,
|
||||||
|
nodeTypes: graph.nodes.reduce((a, n) => { a[n.type] = (a[n.type]||0)+1; return a; }, {}),
|
||||||
|
edgeTypes: graph.edges.reduce((a, e) => { a[e.type] = (a[e.type]||0)+1; return a; }, {})
|
||||||
|
};
|
||||||
|
fs.writeFileSync(outputPath, JSON.stringify({ issues, warnings, stats }, null, 2));
|
||||||
|
process.exit(0);
|
||||||
|
} catch (err) { process.stderr.write(err.message + '\n'); process.exit(1); }
|
||||||
|
```
|
||||||
|
|
||||||
|
Execute it:
|
||||||
|
```bash
|
||||||
|
node $PROJECT_ROOT/.understand-anything/tmp/ua-inline-validate.js \
|
||||||
|
"$PROJECT_ROOT/.understand-anything/intermediate/assembled-graph.json" \
|
||||||
|
"$PROJECT_ROOT/.understand-anything/intermediate/review.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
If the script exits non-zero, read stderr, fix the script, and retry once.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `--review` path: full LLM reviewer
|
||||||
|
|
||||||
|
If `--review` IS in `$ARGUMENTS`, dispatch the LLM graph-reviewer subagent as follows:
|
||||||
|
|
||||||
|
Dispatch a subagent using the prompt template at `./graph-reviewer-prompt.md`. Read the template file and pass the full content as the subagent's prompt, appending the following additional context:
|
||||||
|
|
||||||
|
> **Additional context from main session:**
|
||||||
|
>
|
||||||
|
> Phase 1 scan results (file inventory):
|
||||||
|
> ```json
|
||||||
|
> [list of {path, sizeLines} from scan-result.json]
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Phase warnings/errors accumulated during analysis:
|
||||||
|
> - [list any batch failures, skipped files, or warnings from Phases 2-5]
|
||||||
|
>
|
||||||
|
> Cross-validate: every file in the scan inventory should have a corresponding `file:` node in the graph. Flag any missing files. Also flag any graph nodes whose `filePath` doesn't appear in the scan inventory.
|
||||||
|
|
||||||
|
Pass these parameters in the dispatch prompt:
|
||||||
|
|
||||||
|
> Validate the knowledge graph at `$PROJECT_ROOT/.understand-anything/intermediate/assembled-graph.json`.
|
||||||
|
> Project root: `$PROJECT_ROOT`
|
||||||
|
> Read the file and validate it for completeness and correctness.
|
||||||
|
> Write output to: `$PROJECT_ROOT/.understand-anything/intermediate/review.json`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
4. Read `$PROJECT_ROOT/.understand-anything/intermediate/review.json`.
|
||||||
|
|
||||||
|
5. **If `issues` array is non-empty:**
|
||||||
|
- Review the `issues` list
|
||||||
|
- Apply automated fixes where possible:
|
||||||
|
- Remove edges with dangling references
|
||||||
|
- Fill missing required fields with sensible defaults (e.g., empty `tags` -> `["untagged"]`, empty `summary` -> `"No summary available"`)
|
||||||
|
- Remove nodes with invalid types
|
||||||
|
- Re-run the final graph validation after automated fixes
|
||||||
|
- If critical issues remain after one fix attempt, save the graph anyway but include the warnings in the final report and mark dashboard auto-launch as skipped
|
||||||
|
|
||||||
|
6. **If `issues` array is empty:** Proceed to Phase 7.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Verify the edit
|
||||||
|
|
||||||
|
Re-read SKILL.md lines 297–380 and confirm:
|
||||||
|
- Phase 6 step 3 now checks for `--review` flag
|
||||||
|
- The inline validation script is present and complete
|
||||||
|
- The `--review` path still dispatches the LLM subagent identically to before
|
||||||
|
- Steps 4–6 handle the `review.json` output the same way as before
|
||||||
|
|
||||||
|
### Step 4: Commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/skills/understand/SKILL.md
|
||||||
|
git commit -m "perf(understand): gate LLM graph-reviewer behind --review flag, add inline deterministic validation"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: C4a — Slim Phase 4 (architecture) node payload
|
||||||
|
|
||||||
|
Removes `name` and `languageNotes` from the file node format injected into the architecture-analyzer subagent. These fields are not needed for architectural layer assignment and add unnecessary tokens.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/skills/understand/SKILL.md` (Phase 4, around line 188–196)
|
||||||
|
|
||||||
|
### Step 1: Locate the Phase 4 dispatch prompt in SKILL.md
|
||||||
|
|
||||||
|
Find the block starting "Pass these parameters in the dispatch prompt:" under Phase 4 (around line 181). Look for:
|
||||||
|
|
||||||
|
```
|
||||||
|
> File nodes:
|
||||||
|
> ```json
|
||||||
|
> [list of {id, name, filePath, summary, tags} for all file-type nodes]
|
||||||
|
> ```
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Update the file node format
|
||||||
|
|
||||||
|
Change the file nodes line from:
|
||||||
|
```
|
||||||
|
> [list of {id, name, filePath, summary, tags} for all file-type nodes]
|
||||||
|
```
|
||||||
|
|
||||||
|
To:
|
||||||
|
```
|
||||||
|
> [list of {id, filePath, summary, tags} for all file-type nodes — omit name, complexity, languageNotes]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Verify
|
||||||
|
|
||||||
|
Re-read Phase 4 and confirm the node format line is updated. Import edges line below it (`[list of edges with type "imports"]`) is unchanged.
|
||||||
|
|
||||||
|
### Step 4: Commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/skills/understand/SKILL.md
|
||||||
|
git commit -m "perf(understand): slim Phase 4 architecture payload — drop redundant node fields"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: C4b — Slim Phase 5 (tour builder) payload
|
||||||
|
|
||||||
|
Phase 5 currently injects all nodes (including function/class), all edge types, and full layer objects (with nodeIds arrays). Only file nodes, import+calls edges, and slim layers are needed for tour design. This is the largest single payload change, saving ~105,000 tokens on a 500-file project.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/skills/understand/SKILL.md` (Phase 5, lines 257–270)
|
||||||
|
- Modify: `understand-anything-plugin/skills/understand/tour-builder-prompt.md` (input schema)
|
||||||
|
|
||||||
|
### Step 1: Locate the Phase 5 dispatch prompt in SKILL.md
|
||||||
|
|
||||||
|
Find the block starting with (around line 257):
|
||||||
|
```
|
||||||
|
> Nodes (summarized):
|
||||||
|
> ```json
|
||||||
|
> [list of {id, name, filePath, summary, type} for key nodes]
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Layers:
|
||||||
|
> ```json
|
||||||
|
> [layers from Phase 4]
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Key edges:
|
||||||
|
> ```json
|
||||||
|
> [imports and calls edges]
|
||||||
|
> ```
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Replace all three payload sections
|
||||||
|
|
||||||
|
Replace those lines with:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
> Nodes (file nodes only):
|
||||||
|
> ```json
|
||||||
|
> [list of {id, name, filePath, summary, type} for file-type nodes ONLY — do NOT include function or class nodes]
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Layers:
|
||||||
|
> ```json
|
||||||
|
> [list of {id, name, description} for each layer — omit nodeIds]
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Edges (imports and calls only):
|
||||||
|
> ```json
|
||||||
|
> [list of edges where type is "imports" or "calls" only — exclude all other edge types]
|
||||||
|
> ```
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Update tour-builder-prompt.md input schema
|
||||||
|
|
||||||
|
Open `tour-builder-prompt.md` and find the "Script Requirements" section (around line 18–35). The input schema currently shows:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nodes": [...],
|
||||||
|
"edges": [...],
|
||||||
|
"layers": [
|
||||||
|
{"id": "layer:core", "name": "Core", "nodeIds": ["file:src/index.ts"]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Update the layers example to reflect the slim format:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{"id": "file:src/index.ts", "type": "file", "name": "index.ts", "filePath": "src/index.ts", "summary": "..."}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{"source": "file:src/index.ts", "target": "file:src/utils.ts", "type": "imports"}
|
||||||
|
],
|
||||||
|
"layers": [
|
||||||
|
{"id": "layer:core", "name": "Core", "description": "Core application logic"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also update the "G. Node Summary Index" description (around line 84) to reflect that input nodes are file-type only:
|
||||||
|
|
||||||
|
Find:
|
||||||
|
```
|
||||||
|
**G. Node Summary Index**
|
||||||
|
|
||||||
|
Create a lookup of each node ID to its `summary`, `type`, `tags` (default to empty array `[]` if not present in input), and `name` for easy reference.
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a note after it:
|
||||||
|
```
|
||||||
|
Note: input nodes are file-type only. The nodeSummaryIndex will contain only file nodes.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Verify
|
||||||
|
|
||||||
|
- Re-read SKILL.md Phase 5 payload block: confirms file-only nodes, slim layers (no nodeIds), imports+calls edges only
|
||||||
|
- Re-read tour-builder-prompt.md input schema: layers no longer have nodeIds
|
||||||
|
|
||||||
|
### Step 5: Commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/skills/understand/SKILL.md \
|
||||||
|
understand-anything-plugin/skills/understand/tour-builder-prompt.md
|
||||||
|
git commit -m "perf(understand): slim Phase 5 tour payload — file nodes only, imports+calls edges, slim layers"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: C3 — Remove language/framework addendums from file-analyzer batches
|
||||||
|
|
||||||
|
The addendums (`languages/typescript.md`, `frameworks/react.md`, etc.) are currently injected into every file-analyzer batch prompt. They cost ~1,300 tokens × N batches. The model already knows these languages. Replace with a compact inline reference table (~150 tokens, paid once, embedded in the base template).
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/skills/understand/SKILL.md` (Phase 2, lines 104–117)
|
||||||
|
- Modify: `understand-anything-plugin/skills/understand/file-analyzer-prompt.md` (add quick reference section)
|
||||||
|
|
||||||
|
### Step 1: Update the "Build the combined prompt template" block in SKILL.md Phase 2
|
||||||
|
|
||||||
|
Find the block at lines 104–117:
|
||||||
|
```
|
||||||
|
**Build the combined prompt template:**
|
||||||
|
1. Read the base template at `./file-analyzer-prompt.md`.
|
||||||
|
2. **Language context injection:** ...
|
||||||
|
3. **Framework addendum injection:** ...
|
||||||
|
|
||||||
|
Then for each batch pass the combined template content as the subagent's prompt, appending the following additional context:
|
||||||
|
|
||||||
|
> **Additional context from main session:**
|
||||||
|
>
|
||||||
|
> Project: `<projectName>` — `<projectDescription>`
|
||||||
|
> Frameworks detected: `<frameworks from Phase 1>`
|
||||||
|
> Languages: `<languages from Phase 1>`
|
||||||
|
>
|
||||||
|
> Use the language context and framework addendums (appended above) to produce more accurate summaries and better classify file roles.
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace it with:
|
||||||
|
```markdown
|
||||||
|
**Build the prompt for each batch:**
|
||||||
|
1. Read the base template at `./file-analyzer-prompt.md`. (Language and framework hints are embedded in the template — do NOT append addendum files for Phase 2 batches. Addendums are reserved for Phase 4.)
|
||||||
|
|
||||||
|
Then for each batch pass the template content as the subagent's prompt, appending the following additional context:
|
||||||
|
|
||||||
|
> **Additional context from main session:**
|
||||||
|
>
|
||||||
|
> Project: `<projectName>` — `<projectDescription>`
|
||||||
|
> Languages: `<languages from Phase 1>`
|
||||||
|
```
|
||||||
|
|
||||||
|
This removes steps 2 and 3 (the addendum injection loops) entirely from Phase 2.
|
||||||
|
|
||||||
|
### Step 2: Add Language and Framework Quick Reference to file-analyzer-prompt.md
|
||||||
|
|
||||||
|
Open `file-analyzer-prompt.md`. Find the "## Critical Constraints" section near the bottom (around line 299). Insert the following new section **before** "## Critical Constraints":
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Language and Framework Quick Reference
|
||||||
|
|
||||||
|
Use these hints to improve tag and edge accuracy for common patterns. Your training knowledge covers these — this is a fast lookup for the most impactful signals.
|
||||||
|
|
||||||
|
**Tag signals:**
|
||||||
|
|
||||||
|
| Signal | Tags to apply |
|
||||||
|
|---|---|
|
||||||
|
| File in `hooks/`, exports a function starting with `use` | `hook`, `service` |
|
||||||
|
| File in `contexts/` or `context/`, exports a Provider component | `service`, `state` |
|
||||||
|
| File in `pages/` or `views/` | `ui`, `routing` |
|
||||||
|
| File in `store/`, `slices/`, `reducers/`, `state/` | `state` |
|
||||||
|
| File in `services/`, `api/`, `client/` | `service` |
|
||||||
|
| `__init__.py` at a package root with re-exports | `entry-point`, `barrel` |
|
||||||
|
| `manage.py` at the project root | `entry-point` |
|
||||||
|
| `mod.rs` in a directory | `barrel` |
|
||||||
|
| `main.go` in a `cmd/` subdirectory | `entry-point` |
|
||||||
|
|
||||||
|
**Edge signals:**
|
||||||
|
|
||||||
|
| Pattern | Edge to create |
|
||||||
|
|---|---|
|
||||||
|
| React component renders another component in its JSX | `contains` from parent to child |
|
||||||
|
| Component/hook calls a custom hook (`useX`) | `depends_on` from consumer to hook file |
|
||||||
|
| Context provider wraps components | `publishes` from provider to context definition |
|
||||||
|
| Component calls `useContext` or custom context hook | `subscribes` from consumer to context definition |
|
||||||
|
| Python file uses `from x import y` where x is a project file | `imports` edge (same rule as JS/TS) |
|
||||||
|
| Go file `import`s an internal package path | `imports` edge to the resolved file |
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Verify
|
||||||
|
|
||||||
|
- Re-read SKILL.md Phase 2 "Build the prompt" block: steps 2 and 3 (addendum loops) are gone; "Frameworks detected" line in additional context is gone
|
||||||
|
- Re-read file-analyzer-prompt.md: new "Language and Framework Quick Reference" section appears before Critical Constraints; no reference to addendum files
|
||||||
|
- Confirm Phase 4 "Build the combined prompt template" (lines 163–167) is **unchanged** — addendums still apply there
|
||||||
|
|
||||||
|
### Step 4: Commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/skills/understand/SKILL.md \
|
||||||
|
understand-anything-plugin/skills/understand/file-analyzer-prompt.md
|
||||||
|
git commit -m "perf(understand): remove addendum injection from Phase 2 batches, add compact inline hints to file-analyzer"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5: C1a — Extend scanner to pre-resolve imports
|
||||||
|
|
||||||
|
Adds a new Step 8 to the project scanner script: parse import statements from every source file and resolve relative imports against the discovered file list. The resolved map is written into `scan-result.json` as `importMap`. This is the data that lets us eliminate `allProjectFiles` from every batch in Task 7.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/skills/understand/project-scanner-prompt.md`
|
||||||
|
|
||||||
|
### Step 1: Add Step 8 to the scanner script requirements
|
||||||
|
|
||||||
|
Open `project-scanner-prompt.md`. Find "**Step 7 -- Project Name**" (around line 100). After its content (the priority list), add a new step:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
**Step 8 -- Import Resolution**
|
||||||
|
|
||||||
|
For each file in the discovered source list, extract and resolve relative import statements. The goal is to produce a map from each file's path to the list of project-internal files it imports. External package imports are ignored.
|
||||||
|
|
||||||
|
For each file, read its content and extract import paths using language-appropriate patterns:
|
||||||
|
|
||||||
|
| Language | Import patterns to match |
|
||||||
|
|---|---|
|
||||||
|
| TypeScript/JavaScript | `import ... from './...'` or `'../'`, `require('./...')` or `require('../...')` |
|
||||||
|
| Python | `from .x import y`, `from ..x import y`, `import .x` (relative only) |
|
||||||
|
| Go | Paths in `import (...)` blocks that start with the module path from `go.mod` |
|
||||||
|
| Rust | `use crate::`, `use super::`, `mod x` (within the same crate) |
|
||||||
|
| Java/Kotlin | Not resolvable by path — skip import resolution for these languages |
|
||||||
|
| Ruby | `require_relative '...'` paths |
|
||||||
|
|
||||||
|
For each extracted import path:
|
||||||
|
1. Compute the resolved file path relative to project root:
|
||||||
|
- For relative imports (`./x`, `../x`): resolve from the importing file's directory
|
||||||
|
- Try these extension variants in order if the import has no extension: `.ts`, `.tsx`, `.js`, `.jsx`, `/index.ts`, `/index.js`, `/index.tsx`, `/index.jsx`, `.py`, `.go`, `.rs`, `.rb`
|
||||||
|
2. Check if the resolved path exists in the discovered file list
|
||||||
|
3. If yes: add to this file's resolved imports list
|
||||||
|
4. If no: skip (external, unresolvable, or dynamic import)
|
||||||
|
|
||||||
|
Output format in the script result:
|
||||||
|
```json
|
||||||
|
"importMap": {
|
||||||
|
"src/index.ts": ["src/utils.ts", "src/config.ts"],
|
||||||
|
"src/utils.ts": [],
|
||||||
|
"src/components/App.tsx": ["src/hooks/useAuth.ts", "src/store/index.ts"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Keys are project-relative paths. Values are arrays of resolved project-relative paths. Every key in the file list must appear in `importMap` (use an empty array `[]` if no imports were resolved). External packages and unresolvable imports are omitted entirely.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Update the scanner script output format
|
||||||
|
|
||||||
|
Find the "### Script Output Format" section (around line 109) and update the example JSON to include `importMap`:
|
||||||
|
|
||||||
|
Find this in the example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scriptCompleted": true,
|
||||||
|
"name": "project-name",
|
||||||
|
...
|
||||||
|
"estimatedComplexity": "moderate"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `importMap` to the example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scriptCompleted": true,
|
||||||
|
"name": "project-name",
|
||||||
|
"rawDescription": "...",
|
||||||
|
"readmeHead": "...",
|
||||||
|
"languages": ["javascript", "typescript"],
|
||||||
|
"frameworks": ["React", "Vite"],
|
||||||
|
"files": [
|
||||||
|
{"path": "src/index.ts", "language": "typescript", "sizeLines": 150}
|
||||||
|
],
|
||||||
|
"totalFiles": 42,
|
||||||
|
"estimatedComplexity": "moderate",
|
||||||
|
"importMap": {
|
||||||
|
"src/index.ts": ["src/utils.ts", "src/config.ts"],
|
||||||
|
"src/utils.ts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also update the field documentation list below the example to add:
|
||||||
|
```
|
||||||
|
- `importMap` (object) — map from every source file path to its list of resolved project-internal import paths; empty array if no resolved imports; external packages excluded
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Update the final assembly section to preserve importMap
|
||||||
|
|
||||||
|
Find "## Phase 2 -- Description and Final Assembly" (around line 153). Find the IMPORTANT note:
|
||||||
|
```
|
||||||
|
**IMPORTANT:** The final output must NOT contain the `scriptCompleted`, `rawDescription`, or `readmeHead` fields.
|
||||||
|
```
|
||||||
|
|
||||||
|
Update it to:
|
||||||
|
```
|
||||||
|
**IMPORTANT:** The final output must NOT contain the `scriptCompleted`, `rawDescription`, or `readmeHead` fields. All other fields — including `importMap` — MUST be preserved exactly as output by the script.
|
||||||
|
```
|
||||||
|
|
||||||
|
Also update the final output example to include `importMap`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "project-name",
|
||||||
|
"description": "...",
|
||||||
|
"languages": ["typescript"],
|
||||||
|
"frameworks": ["React"],
|
||||||
|
"files": [...],
|
||||||
|
"totalFiles": 42,
|
||||||
|
"estimatedComplexity": "moderate",
|
||||||
|
"importMap": {
|
||||||
|
"src/index.ts": ["src/utils.ts"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Verify
|
||||||
|
|
||||||
|
Re-read `project-scanner-prompt.md` and confirm:
|
||||||
|
- Step 8 is present with full import resolution logic
|
||||||
|
- Script output format includes `importMap`
|
||||||
|
- Field documentation includes `importMap`
|
||||||
|
- Final assembly section preserves `importMap` in output
|
||||||
|
|
||||||
|
### Step 5: Commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/skills/understand/project-scanner-prompt.md
|
||||||
|
git commit -m "perf(understand): extend scanner to pre-resolve imports, output importMap in scan-result.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 6: C1b — Update file-analyzer to use batchImportData
|
||||||
|
|
||||||
|
Removes `allProjectFiles` from the file-analyzer input schema and replaces it with `batchImportData` (pre-resolved imports for this batch's files only). Updates the extraction script section to skip import resolution entirely (already done by scanner). Updates the edge creation step to use `batchImportData` directly.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/skills/understand/file-analyzer-prompt.md`
|
||||||
|
|
||||||
|
### Step 1: Update the input JSON schema (Script Requirements, step 1)
|
||||||
|
|
||||||
|
Find the input schema block around line 19:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"projectRoot": "/path/to/project",
|
||||||
|
"allProjectFiles": ["src/index.ts", "src/utils.ts", "..."],
|
||||||
|
"batchFiles": [
|
||||||
|
{"path": "src/index.ts", "language": "typescript", "sizeLines": 150},
|
||||||
|
{"path": "src/utils.ts", "language": "typescript", "sizeLines": 80}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"projectRoot": "/path/to/project",
|
||||||
|
"batchFiles": [
|
||||||
|
{"path": "src/index.ts", "language": "typescript", "sizeLines": 150},
|
||||||
|
{"path": "src/utils.ts", "language": "typescript", "sizeLines": 80}
|
||||||
|
],
|
||||||
|
"batchImportData": {
|
||||||
|
"src/index.ts": ["src/utils.ts", "src/config.ts"],
|
||||||
|
"src/utils.ts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Update the field descriptions:
|
||||||
|
- Remove: `allProjectFiles` description
|
||||||
|
- Add: `batchImportData` (object) — map from each batch file's project-relative path to its list of pre-resolved project-internal imports. Produced by the project scanner. Use this directly for import edge creation — do NOT attempt to re-resolve imports yourself.
|
||||||
|
|
||||||
|
### Step 2: Remove the imports extraction from "What the Script Must Extract"
|
||||||
|
|
||||||
|
Find the "**Imports:**" subsection under "What the Script Must Extract" (around lines 49–53):
|
||||||
|
```
|
||||||
|
**Imports:**
|
||||||
|
- Source module path (exactly as written in the import statement)
|
||||||
|
- Imported specifiers (named imports, default import, namespace import)
|
||||||
|
- Line number
|
||||||
|
- For relative imports (starting with `./` or `../`), compute the resolved path...
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace this entire subsection with:
|
||||||
|
```markdown
|
||||||
|
**Imports:**
|
||||||
|
- Do NOT extract imports in the script. Import resolution has already been performed by the project scanner.
|
||||||
|
- The pre-resolved imports for each file are provided in `batchImportData` in the input JSON.
|
||||||
|
- Do not include an `imports` field in the script output — import edges will be created in Phase 2 using `batchImportData` directly.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Update the script output format to remove imports
|
||||||
|
|
||||||
|
Find the `results` array in the script output format (around line 67). The current `imports` array in the output:
|
||||||
|
```json
|
||||||
|
"imports": [
|
||||||
|
{"source": "./utils", "resolvedPath": "src/utils.ts", "specifiers": ["formatDate"], "line": 1, "isExternal": false},
|
||||||
|
{"source": "express", "resolvedPath": null, "specifiers": ["default"], "line": 2, "isExternal": true}
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove the `imports` array from the script output format entirely. The result for each file should be:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"path": "src/index.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"totalLines": 150,
|
||||||
|
"nonEmptyLines": 120,
|
||||||
|
"functions": [...],
|
||||||
|
"classes": [...],
|
||||||
|
"exports": [...],
|
||||||
|
"metrics": {
|
||||||
|
"importCount": 5,
|
||||||
|
"exportCount": 3,
|
||||||
|
"functionCount": 4,
|
||||||
|
"classCount": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep `metrics.importCount` (derived from `batchImportData[path].length`) as a useful metric.
|
||||||
|
|
||||||
|
Update the metrics description to say:
|
||||||
|
```
|
||||||
|
- `importCount` (integer) — use `batchImportData[file.path].length` from the input JSON
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Update "Preparing the Script Input" section
|
||||||
|
|
||||||
|
Find the `cat` command around line 113 that creates the input JSON:
|
||||||
|
```bash
|
||||||
|
cat > $PROJECT_ROOT/.understand-anything/tmp/ua-file-analyzer-input-<batchIndex>.json << 'ENDJSON'
|
||||||
|
{
|
||||||
|
"projectRoot": "<project-root>",
|
||||||
|
"allProjectFiles": [<full file list from scan>],
|
||||||
|
"batchFiles": [<this batch's files>]
|
||||||
|
}
|
||||||
|
ENDJSON
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
```bash
|
||||||
|
cat > $PROJECT_ROOT/.understand-anything/tmp/ua-file-analyzer-input-<batchIndex>.json << 'ENDJSON'
|
||||||
|
{
|
||||||
|
"projectRoot": "<project-root>",
|
||||||
|
"batchFiles": [<this batch's files>],
|
||||||
|
"batchImportData": <batchImportData JSON object — provided in your dispatch prompt>
|
||||||
|
}
|
||||||
|
ENDJSON
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Update Step 3 (Create Edges) — Import edge creation rule
|
||||||
|
|
||||||
|
Find the "**Import edge creation rule:**" in the "Step 3 -- Create Edges" section (around line 213):
|
||||||
|
```
|
||||||
|
**Import edge creation rule:** For each import in the script output where `isExternal` is `false` and `resolvedPath` is non-null, create an `imports` edge from the current file node to `file:<resolvedPath>`. Do NOT create edges for external package imports.
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
```markdown
|
||||||
|
**Import edge creation rule:** For each resolved path in `batchImportData[filePath]` (provided in the input JSON), create an `imports` edge from the current file node to `file:<resolvedPath>`. The `batchImportData` values contain only resolved project-internal paths — external packages have already been filtered out. Do NOT attempt to re-resolve imports from source.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Remove `allProjectFiles` references from Critical Constraints
|
||||||
|
|
||||||
|
Find the last bullet in "## Critical Constraints" (around line 304):
|
||||||
|
```
|
||||||
|
- For import edges, use the script's `resolvedPath` field directly. Do NOT attempt to resolve import paths yourself -- the script already did this deterministically.
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
```markdown
|
||||||
|
- For import edges, use `batchImportData[filePath]` directly from the input JSON. Do NOT attempt to resolve import paths yourself -- the project scanner already did this deterministically.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7: Verify
|
||||||
|
|
||||||
|
Re-read `file-analyzer-prompt.md` and confirm:
|
||||||
|
- Input schema has `batchImportData`, no `allProjectFiles`
|
||||||
|
- Script "What to Extract" section: imports extraction replaced with "do not extract"
|
||||||
|
- Script output format: no `imports` array per file
|
||||||
|
- Preparing the Script Input: cat command has no `allProjectFiles`
|
||||||
|
- Import edge creation rule: uses `batchImportData` not script output
|
||||||
|
- Critical Constraints: no reference to `resolvedPath` from script
|
||||||
|
|
||||||
|
### Step 8: Commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/skills/understand/file-analyzer-prompt.md
|
||||||
|
git commit -m "perf(understand): replace allProjectFiles with batchImportData in file-analyzer — import resolution now done by scanner"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 7: C1c + C2 — Update SKILL.md Phase 2 orchestration
|
||||||
|
|
||||||
|
Wires up the `importMap` from Phase 1 into per-batch `batchImportData` slices. Increases batch size from 5-10 to 20-30 files. Increases concurrency from 3 to 5. Removes `allProjectFiles` from the dispatch prompt.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/skills/understand/SKILL.md` (Phase 0, Phase 1, Phase 2)
|
||||||
|
|
||||||
|
### Step 1: Update Phase 1 to note importMap is now in scan-result.json
|
||||||
|
|
||||||
|
Find Phase 1 (around line 62) where it says:
|
||||||
|
```
|
||||||
|
After the subagent completes, read `$PROJECT_ROOT/.understand-anything/intermediate/scan-result.json` to get:
|
||||||
|
- Project name, description
|
||||||
|
- Languages, frameworks
|
||||||
|
- File list with line counts
|
||||||
|
- Complexity estimate
|
||||||
|
```
|
||||||
|
|
||||||
|
Add one item to the list:
|
||||||
|
```
|
||||||
|
- Import map (`importMap`): pre-resolved project-internal imports per file
|
||||||
|
```
|
||||||
|
|
||||||
|
Also add a note:
|
||||||
|
```
|
||||||
|
Store `importMap` in memory as `$IMPORT_MAP` for use in Phase 2 batch construction.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Change batch size and concurrency in Phase 2
|
||||||
|
|
||||||
|
Find line 100:
|
||||||
|
```
|
||||||
|
Batch the file list from Phase 1 into groups of **5-10 files each** (aim for balanced batch sizes).
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
```
|
||||||
|
Batch the file list from Phase 1 into groups of **20-30 files each** (aim for ~25 files per batch for balanced sizes).
|
||||||
|
```
|
||||||
|
|
||||||
|
Find line 102:
|
||||||
|
```
|
||||||
|
For each batch, dispatch a subagent using the prompt template at `./file-analyzer-prompt.md`. Run up to **3 subagents concurrently** using parallel dispatch.
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
```
|
||||||
|
For each batch, dispatch a subagent using the prompt template at `./file-analyzer-prompt.md`. Run up to **5 subagents concurrently** using parallel dispatch.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Add batchImportData construction to the dispatch block
|
||||||
|
|
||||||
|
Find the dispatch prompt block (around lines 119–134):
|
||||||
|
```
|
||||||
|
Fill in batch-specific parameters below and dispatch:
|
||||||
|
|
||||||
|
> Analyze these source files and produce GraphNode and GraphEdge objects.
|
||||||
|
> Project root: `$PROJECT_ROOT`
|
||||||
|
> Project: `<projectName>`
|
||||||
|
> Languages: `<languages>`
|
||||||
|
> Batch index: `<batchIndex>`
|
||||||
|
> Write output to: `$PROJECT_ROOT/.understand-anything/intermediate/batch-<batchIndex>.json`
|
||||||
|
>
|
||||||
|
> All project files (for import resolution):
|
||||||
|
> `<full file path list from scan>`
|
||||||
|
>
|
||||||
|
> Files to analyze in this batch:
|
||||||
|
> 1. `<path>` (<sizeLines> lines)
|
||||||
|
> ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
```markdown
|
||||||
|
Before dispatching each batch, construct `batchImportData` from `$IMPORT_MAP`:
|
||||||
|
```json
|
||||||
|
batchImportData = {}
|
||||||
|
for each file in this batch:
|
||||||
|
batchImportData[file.path] = $IMPORT_MAP[file.path] ?? []
|
||||||
|
```
|
||||||
|
|
||||||
|
Fill in batch-specific parameters below and dispatch:
|
||||||
|
|
||||||
|
> Analyze these source files and produce GraphNode and GraphEdge objects.
|
||||||
|
> Project root: `$PROJECT_ROOT`
|
||||||
|
> Project: `<projectName>`
|
||||||
|
> Languages: `<languages>`
|
||||||
|
> Batch index: `<batchIndex>`
|
||||||
|
> Write output to: `$PROJECT_ROOT/.understand-anything/intermediate/batch-<batchIndex>.json`
|
||||||
|
>
|
||||||
|
> Pre-resolved import data for this batch (use this for all import edge creation — do NOT re-resolve imports from source):
|
||||||
|
> ```json
|
||||||
|
> <batchImportData JSON>
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Files to analyze in this batch:
|
||||||
|
> 1. `<path>` (<sizeLines> lines)
|
||||||
|
> 2. `<path>` (<sizeLines> lines)
|
||||||
|
> ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Update incremental update path
|
||||||
|
|
||||||
|
Find "### Incremental update path" (around line 140):
|
||||||
|
```
|
||||||
|
Use the changed files list from Phase 0. Batch and dispatch file-analyzer subagents using the same process as above, but only for changed files.
|
||||||
|
```
|
||||||
|
|
||||||
|
Update to clarify that batchImportData still applies:
|
||||||
|
```
|
||||||
|
Use the changed files list from Phase 0. Batch and dispatch file-analyzer subagents using the same process as above (20-30 files per batch, up to 5 concurrent, with batchImportData constructed from $IMPORT_MAP), but only for changed files.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Verify all Phase 2 changes
|
||||||
|
|
||||||
|
Re-read SKILL.md Phase 2 in full and confirm:
|
||||||
|
- Batch size says "20-30 files"
|
||||||
|
- Concurrency says "5 subagents concurrently"
|
||||||
|
- "Build the prompt" block: only step 1 (read base template), no addendum steps
|
||||||
|
- Additional context block: no "Frameworks detected" line, no addendum reference
|
||||||
|
- Dispatch prompt: has `batchImportData` injection, no `allProjectFiles`
|
||||||
|
- Incremental path: mentions batchImportData
|
||||||
|
|
||||||
|
### Step 6: Commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/skills/understand/SKILL.md
|
||||||
|
git commit -m "perf(understand): wire importMap into batchImportData per batch, increase batch size 5-10→20-30, concurrency 3→5"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 8: Version bump
|
||||||
|
|
||||||
|
Per project convention, all four version files must stay in sync when changes are pushed.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/package.json`
|
||||||
|
- Modify: `.claude-plugin/marketplace.json`
|
||||||
|
- Modify: `.claude-plugin/plugin.json`
|
||||||
|
- Modify: `.cursor-plugin/plugin.json`
|
||||||
|
|
||||||
|
### Step 1: Read current version
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node -e "const p = require('./understand-anything-plugin/package.json'); console.log(p.version)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `1.2.1` (or whatever the current version is).
|
||||||
|
|
||||||
|
### Step 2: Bump patch version in all four files
|
||||||
|
|
||||||
|
New version: `1.2.2` (patch bump — internal optimization, no API changes).
|
||||||
|
|
||||||
|
Update each file:
|
||||||
|
- `understand-anything-plugin/package.json`: `"version": "1.2.2"`
|
||||||
|
- `.claude-plugin/marketplace.json`: `"version": "1.2.2"` in `plugins[0]`
|
||||||
|
- `.claude-plugin/plugin.json`: `"version": "1.2.2"`
|
||||||
|
- `.cursor-plugin/plugin.json`: `"version": "1.2.2"`
|
||||||
|
|
||||||
|
### Step 3: Verify all four files match
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -r '"version"' understand-anything-plugin/package.json .claude-plugin/marketplace.json .claude-plugin/plugin.json .cursor-plugin/plugin.json
|
||||||
|
```
|
||||||
|
|
||||||
|
All four should show `"version": "1.2.2"`.
|
||||||
|
|
||||||
|
### Step 4: Commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/package.json \
|
||||||
|
.claude-plugin/marketplace.json \
|
||||||
|
.claude-plugin/plugin.json \
|
||||||
|
.cursor-plugin/plugin.json
|
||||||
|
git commit -m "chore: bump version to 1.2.2"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 9: Build and smoke test
|
||||||
|
|
||||||
|
Verifies all changes work end-to-end by running `/understand --full` against a real project.
|
||||||
|
|
||||||
|
**Files:** None (testing only)
|
||||||
|
|
||||||
|
### Step 1: Build the packages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @understand-anything/core build
|
||||||
|
pnpm --filter @understand-anything/skill build
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: both build without errors.
|
||||||
|
|
||||||
|
### Step 2: Find installed plugin version and copy to cache
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls ~/.claude/plugins/cache/understand-anything/understand-anything/
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the version (e.g., `1.0.1`). Copy local build into the cache:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
VERSION=$(node -e "const p = require('./understand-anything-plugin/package.json'); console.log(p.version)")
|
||||||
|
rm -rf ~/.claude/plugins/cache/understand-anything/understand-anything/$VERSION
|
||||||
|
cp -R ./understand-anything-plugin ~/.claude/plugins/cache/understand-anything/understand-anything/$VERSION
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Smoke test on a small project (~20 files)
|
||||||
|
|
||||||
|
Open a fresh Claude Code session in a small TypeScript project. Run:
|
||||||
|
```
|
||||||
|
/understand --full
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
- Phases 0–7 complete without errors
|
||||||
|
- `knowledge-graph.json` is created
|
||||||
|
- Node count and edge count are reasonable
|
||||||
|
- Layers and tour are present
|
||||||
|
- No "allProjectFiles" or addendum errors in the output
|
||||||
|
|
||||||
|
### Step 4: Smoke test on a larger project (~100+ files)
|
||||||
|
|
||||||
|
Run `/understand --full` on a medium/large TypeScript+React project.
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
- Batch count is ~4-6 (at 20-30 files per batch for 100 files), not 10-20
|
||||||
|
- No errors about missing import resolution
|
||||||
|
- `importMap` is present in `scan-result.json` (check `.understand-anything/intermediate/` before cleanup, or add a temporary debug log)
|
||||||
|
- Graph quality is comparable to before (summaries are descriptive, layers are correct)
|
||||||
|
|
||||||
|
### Step 5: Test `--review` flag
|
||||||
|
|
||||||
|
Run `/understand --full --review` on the same project.
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
- Phase 6 now dispatches the LLM graph-reviewer subagent (not the inline script)
|
||||||
|
- `review.json` is produced with `approved` field
|
||||||
|
- Pipeline completes normally
|
||||||
|
|
||||||
|
### Step 6: Final commit (if any fixes needed from smoke test)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add -A
|
||||||
|
git commit -m "fix(understand): smoke test fixes for token reduction changes"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Task | Change | Risk |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | C5: Gate reviewer | Low |
|
||||||
|
| 2 | C4a: Slim Phase 4 payload | Low |
|
||||||
|
| 3 | C4b: Slim Phase 5 payload | Low |
|
||||||
|
| 4 | C3: Remove addendums from batches | Low |
|
||||||
|
| 5 | C1a: Scanner import resolution | Medium |
|
||||||
|
| 6 | C1b: File-analyzer uses batchImportData | Medium |
|
||||||
|
| 7 | C1c+C2: SKILL.md orchestration + batch size | Medium |
|
||||||
|
| 8 | Version bump | Low |
|
||||||
|
| 9 | Smoke test | — |
|
||||||
|
|
||||||
|
Tasks 1–4 are independent of Tasks 5–7. They can be shipped separately if needed. Tasks 5, 6, and 7 are tightly coupled (scanner produces importMap → SKILL.md passes batchImportData → file-analyzer consumes it) and must be shipped together.
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
# Homepage Feature Update Implementation Plan
|
||||||
|
|
||||||
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||||
|
|
||||||
|
**Goal:** Update the Astro homepage to reflect features from v1.2.0–v2.0.0 releases.
|
||||||
|
|
||||||
|
**Architecture:** Three file edits — expand Features.astro from 3→6 cards, update Install.astro platform note, update Footer.astro tagline. No new files or structural changes.
|
||||||
|
|
||||||
|
**Tech Stack:** Astro 6, CSS grid
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Update Features.astro — Replace 3 Cards with 6
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `homepage/src/components/Features.astro`
|
||||||
|
|
||||||
|
**Step 1: Replace the features array (lines 2–18)**
|
||||||
|
|
||||||
|
Replace the entire frontmatter features array with:
|
||||||
|
|
||||||
|
```astro
|
||||||
|
---
|
||||||
|
const features = [
|
||||||
|
{
|
||||||
|
icon: '◈',
|
||||||
|
title: 'Interactive Knowledge Graph',
|
||||||
|
description: 'Visualize files, functions, and dependencies as an explorable graph with hierarchical drill-down and smart layout.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '⬡',
|
||||||
|
title: 'Beyond Code Analysis',
|
||||||
|
description: 'Analyze your entire project — Dockerfiles, Terraform, SQL, Markdown, and 26+ file types mapped into one unified graph.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '⊘',
|
||||||
|
title: 'Smart Filtering & Search',
|
||||||
|
description: 'Filter by node type, complexity, layer, or edge category. Fuzzy and semantic search to find anything instantly.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '⎙',
|
||||||
|
title: 'Export & Share',
|
||||||
|
description: 'Export your knowledge graph as high-quality PNG, SVG, or filtered JSON — ready for docs, presentations, or further analysis.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '⟿',
|
||||||
|
title: 'Dependency Path Finder',
|
||||||
|
description: 'Find the shortest path between any two components. Understand how parts of your system connect at a glance.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '⟐',
|
||||||
|
title: 'Guided Tours & Onboarding',
|
||||||
|
description: 'AI-generated walkthroughs that teach the codebase step by step, plus onboarding guides for new team members.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Update the reveal delay logic (line 24)**
|
||||||
|
|
||||||
|
The current `reveal-delay-${i + 1}` only has CSS for delays 1–3. With 6 cards in 2 rows, use modulo so each row staggers 1/2/3:
|
||||||
|
|
||||||
|
```astro
|
||||||
|
<div class={`feature-card reveal reveal-delay-${(i % 3) + 1}`}>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: Update the grid CSS to handle 2 rows properly**
|
||||||
|
|
||||||
|
No change needed — `grid-template-columns: repeat(3, 1fr)` already wraps to a second row. The mobile `1fr` breakpoint also works. No CSS changes required.
|
||||||
|
|
||||||
|
**Step 4: Verify build**
|
||||||
|
|
||||||
|
Run: `cd homepage && npx astro build`
|
||||||
|
Expected: Build completes with no errors.
|
||||||
|
|
||||||
|
**Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add homepage/src/components/Features.astro
|
||||||
|
git commit -m "feat(homepage): expand features section to 6 cards for v2.0.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Update Install.astro — Multi-Platform Note
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `homepage/src/components/Install.astro`
|
||||||
|
|
||||||
|
**Step 1: Replace the platform note (line 13)**
|
||||||
|
|
||||||
|
Change:
|
||||||
|
```html
|
||||||
|
<p class="install-note">Works with <strong>Claude Code</strong> — Anthropic's official CLI for Claude.</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
To:
|
||||||
|
```html
|
||||||
|
<p class="install-note">Works with <strong>Claude Code</strong>, <strong>Codex</strong>, <strong>OpenCode</strong>, <strong>Gemini CLI</strong>, and more.</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add homepage/src/components/Install.astro
|
||||||
|
git commit -m "feat(homepage): update install note for multi-platform support"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Update Footer.astro — Tagline
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `homepage/src/components/Footer.astro`
|
||||||
|
|
||||||
|
**Step 1: Replace the tagline (line 13)**
|
||||||
|
|
||||||
|
Change:
|
||||||
|
```html
|
||||||
|
<p class="footer-note">Built as a Claude Code plugin</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
To:
|
||||||
|
```html
|
||||||
|
<p class="footer-note">Built for AI coding assistants</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Verify full build**
|
||||||
|
|
||||||
|
Run: `cd homepage && npx astro build`
|
||||||
|
Expected: Clean build, no errors.
|
||||||
|
|
||||||
|
**Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add homepage/src/components/Footer.astro
|
||||||
|
git commit -m "feat(homepage): update footer tagline for multi-platform"
|
||||||
|
```
|
||||||
@@ -0,0 +1,776 @@
|
|||||||
|
# .understandignore Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Add user-configurable file exclusion via `.understandignore` files using `.gitignore` syntax, with auto-generated starter files and a pre-analysis review pause.
|
||||||
|
|
||||||
|
**Architecture:** An `IgnoreFilter` module in `packages/core` uses the `ignore` npm package to parse `.understandignore` files and filter paths. A companion `IgnoreGenerator` scans the project for common patterns and produces a commented-out starter file. The `project-scanner` agent applies the filter as a second pass after its existing hardcoded exclusions. The `/understand` skill adds a Phase 0.5 that generates the starter file and pauses for user review.
|
||||||
|
|
||||||
|
**Tech Stack:** TypeScript, `ignore` npm package, Vitest
|
||||||
|
|
||||||
|
**Spec:** `docs/superpowers/specs/2026-04-10-understandignore-design.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
### Core package
|
||||||
|
- Create: `understand-anything-plugin/packages/core/src/ignore-filter.ts` — parse .understandignore, merge with defaults, filter paths
|
||||||
|
- Create: `understand-anything-plugin/packages/core/src/ignore-generator.ts` — generate starter .understandignore by scanning project
|
||||||
|
- Create: `understand-anything-plugin/packages/core/src/__tests__/ignore-filter.test.ts` — filter tests
|
||||||
|
- Create: `understand-anything-plugin/packages/core/src/__tests__/ignore-generator.test.ts` — generator tests
|
||||||
|
- Modify: `understand-anything-plugin/packages/core/src/index.ts` — export new modules
|
||||||
|
- Modify: `understand-anything-plugin/packages/core/package.json` — add `ignore` dependency
|
||||||
|
|
||||||
|
### Agents & skills
|
||||||
|
- Modify: `understand-anything-plugin/agents/project-scanner.md` — add Layer 2 filtering step
|
||||||
|
- Modify: `understand-anything-plugin/skills/understand/SKILL.md` — add Phase 0.5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: Add `ignore` dependency
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/packages/core/package.json`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Install the `ignore` npm package**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
cd understand-anything-plugin && pnpm add --filter @understand-anything/core ignore
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify it was added**
|
||||||
|
|
||||||
|
Run: `grep ignore understand-anything-plugin/packages/core/package.json`
|
||||||
|
Expected: `"ignore": "^7.x.x"` (or similar) in dependencies
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/packages/core/package.json understand-anything-plugin/pnpm-lock.yaml
|
||||||
|
git commit -m "chore(core): add ignore package for .understandignore support"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: Create IgnoreFilter module with tests (TDD)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `understand-anything-plugin/packages/core/src/ignore-filter.ts`
|
||||||
|
- Create: `understand-anything-plugin/packages/core/src/__tests__/ignore-filter.test.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing tests**
|
||||||
|
|
||||||
|
Create `understand-anything-plugin/packages/core/src/__tests__/ignore-filter.test.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||||
|
import { createIgnoreFilter, DEFAULT_IGNORE_PATTERNS } from "../ignore-filter";
|
||||||
|
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
|
||||||
|
describe("IgnoreFilter", () => {
|
||||||
|
let testDir: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testDir = join(tmpdir(), `ignore-filter-test-${Date.now()}`);
|
||||||
|
mkdirSync(testDir, { recursive: true });
|
||||||
|
mkdirSync(join(testDir, ".understand-anything"), { recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
rmSync(testDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("DEFAULT_IGNORE_PATTERNS", () => {
|
||||||
|
it("contains node_modules", () => {
|
||||||
|
expect(DEFAULT_IGNORE_PATTERNS).toContain("node_modules/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("contains .git", () => {
|
||||||
|
expect(DEFAULT_IGNORE_PATTERNS).toContain(".git/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("contains bin and obj for .NET", () => {
|
||||||
|
expect(DEFAULT_IGNORE_PATTERNS).toContain("bin/");
|
||||||
|
expect(DEFAULT_IGNORE_PATTERNS).toContain("obj/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("contains build output directories", () => {
|
||||||
|
expect(DEFAULT_IGNORE_PATTERNS).toContain("dist/");
|
||||||
|
expect(DEFAULT_IGNORE_PATTERNS).toContain("build/");
|
||||||
|
expect(DEFAULT_IGNORE_PATTERNS).toContain("out/");
|
||||||
|
expect(DEFAULT_IGNORE_PATTERNS).toContain("coverage/");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("createIgnoreFilter with no user file", () => {
|
||||||
|
it("ignores files matching default patterns", () => {
|
||||||
|
const filter = createIgnoreFilter(testDir);
|
||||||
|
expect(filter.isIgnored("node_modules/foo/bar.js")).toBe(true);
|
||||||
|
expect(filter.isIgnored("dist/index.js")).toBe(true);
|
||||||
|
expect(filter.isIgnored(".git/config")).toBe(true);
|
||||||
|
expect(filter.isIgnored("bin/Debug/app.dll")).toBe(true);
|
||||||
|
expect(filter.isIgnored("obj/Release/net8.0/app.dll")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not ignore source files", () => {
|
||||||
|
const filter = createIgnoreFilter(testDir);
|
||||||
|
expect(filter.isIgnored("src/index.ts")).toBe(false);
|
||||||
|
expect(filter.isIgnored("README.md")).toBe(false);
|
||||||
|
expect(filter.isIgnored("package.json")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores lock files", () => {
|
||||||
|
const filter = createIgnoreFilter(testDir);
|
||||||
|
expect(filter.isIgnored("pnpm-lock.yaml")).toBe(true);
|
||||||
|
expect(filter.isIgnored("package-lock.json")).toBe(true);
|
||||||
|
expect(filter.isIgnored("yarn.lock")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores binary/asset files", () => {
|
||||||
|
const filter = createIgnoreFilter(testDir);
|
||||||
|
expect(filter.isIgnored("logo.png")).toBe(true);
|
||||||
|
expect(filter.isIgnored("font.woff2")).toBe(true);
|
||||||
|
expect(filter.isIgnored("doc.pdf")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores generated files", () => {
|
||||||
|
const filter = createIgnoreFilter(testDir);
|
||||||
|
expect(filter.isIgnored("bundle.min.js")).toBe(true);
|
||||||
|
expect(filter.isIgnored("style.min.css")).toBe(true);
|
||||||
|
expect(filter.isIgnored("source.map")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores IDE directories", () => {
|
||||||
|
const filter = createIgnoreFilter(testDir);
|
||||||
|
expect(filter.isIgnored(".idea/workspace.xml")).toBe(true);
|
||||||
|
expect(filter.isIgnored(".vscode/settings.json")).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("createIgnoreFilter with user .understandignore", () => {
|
||||||
|
it("reads patterns from .understand-anything/.understandignore", () => {
|
||||||
|
writeFileSync(
|
||||||
|
join(testDir, ".understand-anything", ".understandignore"),
|
||||||
|
"# Exclude tests\n__tests__/\n*.test.ts\n"
|
||||||
|
);
|
||||||
|
const filter = createIgnoreFilter(testDir);
|
||||||
|
expect(filter.isIgnored("__tests__/foo.test.ts")).toBe(true);
|
||||||
|
expect(filter.isIgnored("src/utils.test.ts")).toBe(true);
|
||||||
|
expect(filter.isIgnored("src/utils.ts")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reads patterns from project root .understandignore", () => {
|
||||||
|
writeFileSync(
|
||||||
|
join(testDir, ".understandignore"),
|
||||||
|
"docs/\n"
|
||||||
|
);
|
||||||
|
const filter = createIgnoreFilter(testDir);
|
||||||
|
expect(filter.isIgnored("docs/README.md")).toBe(true);
|
||||||
|
expect(filter.isIgnored("src/index.ts")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles # comments and blank lines", () => {
|
||||||
|
writeFileSync(
|
||||||
|
join(testDir, ".understand-anything", ".understandignore"),
|
||||||
|
"# This is a comment\n\n\nfixtures/\n\n# Another comment\n"
|
||||||
|
);
|
||||||
|
const filter = createIgnoreFilter(testDir);
|
||||||
|
expect(filter.isIgnored("fixtures/data.json")).toBe(true);
|
||||||
|
expect(filter.isIgnored("src/index.ts")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports ! negation to override defaults", () => {
|
||||||
|
writeFileSync(
|
||||||
|
join(testDir, ".understand-anything", ".understandignore"),
|
||||||
|
"!dist/\n"
|
||||||
|
);
|
||||||
|
const filter = createIgnoreFilter(testDir);
|
||||||
|
// dist/ is in defaults but negated by user
|
||||||
|
expect(filter.isIgnored("dist/index.js")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports ** recursive matching", () => {
|
||||||
|
writeFileSync(
|
||||||
|
join(testDir, ".understand-anything", ".understandignore"),
|
||||||
|
"**/snapshots/\n"
|
||||||
|
);
|
||||||
|
const filter = createIgnoreFilter(testDir);
|
||||||
|
expect(filter.isIgnored("src/components/snapshots/Button.snap")).toBe(true);
|
||||||
|
expect(filter.isIgnored("snapshots/foo.snap")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("merges .understand-anything/ and root .understandignore", () => {
|
||||||
|
writeFileSync(
|
||||||
|
join(testDir, ".understand-anything", ".understandignore"),
|
||||||
|
"__tests__/\n"
|
||||||
|
);
|
||||||
|
writeFileSync(
|
||||||
|
join(testDir, ".understandignore"),
|
||||||
|
"fixtures/\n"
|
||||||
|
);
|
||||||
|
const filter = createIgnoreFilter(testDir);
|
||||||
|
expect(filter.isIgnored("__tests__/foo.ts")).toBe(true);
|
||||||
|
expect(filter.isIgnored("fixtures/data.json")).toBe(true);
|
||||||
|
expect(filter.isIgnored("src/index.ts")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests to verify they fail**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/core test -- --run src/__tests__/ignore-filter.test.ts`
|
||||||
|
Expected: FAIL — module not found
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement IgnoreFilter**
|
||||||
|
|
||||||
|
Create `understand-anything-plugin/packages/core/src/ignore-filter.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import ignore, { type Ignore } from "ignore";
|
||||||
|
import { readFileSync, existsSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hardcoded default ignore patterns matching the project-scanner agent's
|
||||||
|
* exclusion rules, plus bin/obj for .NET projects.
|
||||||
|
*/
|
||||||
|
export const DEFAULT_IGNORE_PATTERNS: string[] = [
|
||||||
|
// Dependency directories
|
||||||
|
"node_modules/",
|
||||||
|
".git/",
|
||||||
|
"vendor/",
|
||||||
|
"venv/",
|
||||||
|
".venv/",
|
||||||
|
"__pycache__/",
|
||||||
|
|
||||||
|
// Build output
|
||||||
|
"dist/",
|
||||||
|
"build/",
|
||||||
|
"out/",
|
||||||
|
"coverage/",
|
||||||
|
".next/",
|
||||||
|
".cache/",
|
||||||
|
".turbo/",
|
||||||
|
"target/",
|
||||||
|
"bin/",
|
||||||
|
"obj/",
|
||||||
|
|
||||||
|
// Lock files
|
||||||
|
"*.lock",
|
||||||
|
"package-lock.json",
|
||||||
|
"yarn.lock",
|
||||||
|
"pnpm-lock.yaml",
|
||||||
|
|
||||||
|
// Binary/asset files
|
||||||
|
"*.png",
|
||||||
|
"*.jpg",
|
||||||
|
"*.jpeg",
|
||||||
|
"*.gif",
|
||||||
|
"*.svg",
|
||||||
|
"*.ico",
|
||||||
|
"*.woff",
|
||||||
|
"*.woff2",
|
||||||
|
"*.ttf",
|
||||||
|
"*.eot",
|
||||||
|
"*.mp3",
|
||||||
|
"*.mp4",
|
||||||
|
"*.pdf",
|
||||||
|
"*.zip",
|
||||||
|
"*.tar",
|
||||||
|
"*.gz",
|
||||||
|
|
||||||
|
// Generated files
|
||||||
|
"*.min.js",
|
||||||
|
"*.min.css",
|
||||||
|
"*.map",
|
||||||
|
"*.generated.*",
|
||||||
|
|
||||||
|
// IDE/editor
|
||||||
|
".idea/",
|
||||||
|
".vscode/",
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
"LICENSE",
|
||||||
|
".gitignore",
|
||||||
|
".editorconfig",
|
||||||
|
".prettierrc",
|
||||||
|
".eslintrc*",
|
||||||
|
"*.log",
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface IgnoreFilter {
|
||||||
|
/** Returns true if the given relative path should be excluded from analysis. */
|
||||||
|
isIgnored(relativePath: string): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an IgnoreFilter that merges hardcoded defaults with user-defined
|
||||||
|
* patterns from .understandignore files.
|
||||||
|
*
|
||||||
|
* Pattern load order (later entries can override earlier ones via ! negation):
|
||||||
|
* 1. Hardcoded defaults
|
||||||
|
* 2. .understand-anything/.understandignore (if exists)
|
||||||
|
* 3. .understandignore at project root (if exists)
|
||||||
|
*/
|
||||||
|
export function createIgnoreFilter(projectRoot: string): IgnoreFilter {
|
||||||
|
const ig: Ignore = ignore();
|
||||||
|
|
||||||
|
// Layer 1: hardcoded defaults
|
||||||
|
ig.add(DEFAULT_IGNORE_PATTERNS);
|
||||||
|
|
||||||
|
// Layer 2: .understand-anything/.understandignore
|
||||||
|
const projectIgnorePath = join(projectRoot, ".understand-anything", ".understandignore");
|
||||||
|
if (existsSync(projectIgnorePath)) {
|
||||||
|
const content = readFileSync(projectIgnorePath, "utf-8");
|
||||||
|
ig.add(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer 3: .understandignore at project root
|
||||||
|
const rootIgnorePath = join(projectRoot, ".understandignore");
|
||||||
|
if (existsSync(rootIgnorePath)) {
|
||||||
|
const content = readFileSync(rootIgnorePath, "utf-8");
|
||||||
|
ig.add(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isIgnored(relativePath: string): boolean {
|
||||||
|
return ig.ignores(relativePath);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run tests to verify they pass**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/core test -- --run src/__tests__/ignore-filter.test.ts`
|
||||||
|
Expected: All tests PASS
|
||||||
|
|
||||||
|
- [ ] **Step 5: Build to verify no type errors**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/core build`
|
||||||
|
Expected: Clean build
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/packages/core/src/ignore-filter.ts understand-anything-plugin/packages/core/src/__tests__/ignore-filter.test.ts
|
||||||
|
git commit -m "feat(core): add IgnoreFilter module with .understandignore parsing and tests"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: Create IgnoreGenerator module with tests (TDD)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `understand-anything-plugin/packages/core/src/ignore-generator.ts`
|
||||||
|
- Create: `understand-anything-plugin/packages/core/src/__tests__/ignore-generator.test.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing tests**
|
||||||
|
|
||||||
|
Create `understand-anything-plugin/packages/core/src/__tests__/ignore-generator.test.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||||
|
import { generateStarterIgnoreFile } from "../ignore-generator";
|
||||||
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
|
||||||
|
describe("generateStarterIgnoreFile", () => {
|
||||||
|
let testDir: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testDir = join(tmpdir(), `ignore-gen-test-${Date.now()}`);
|
||||||
|
mkdirSync(testDir, { recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
rmSync(testDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes a header comment explaining the file", () => {
|
||||||
|
const content = generateStarterIgnoreFile(testDir);
|
||||||
|
expect(content).toContain(".understandignore");
|
||||||
|
expect(content).toContain("same as .gitignore");
|
||||||
|
expect(content).toContain("Built-in defaults");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("all suggestions are commented out", () => {
|
||||||
|
// Create some directories to trigger suggestions
|
||||||
|
mkdirSync(join(testDir, "__tests__"), { recursive: true });
|
||||||
|
mkdirSync(join(testDir, "docs"), { recursive: true });
|
||||||
|
const content = generateStarterIgnoreFile(testDir);
|
||||||
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
||||||
|
// No active (uncommented) patterns
|
||||||
|
expect(lines).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("suggests __tests__ when __tests__ directory exists", () => {
|
||||||
|
mkdirSync(join(testDir, "__tests__"), { recursive: true });
|
||||||
|
const content = generateStarterIgnoreFile(testDir);
|
||||||
|
expect(content).toContain("# __tests__/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("suggests docs when docs directory exists", () => {
|
||||||
|
mkdirSync(join(testDir, "docs"), { recursive: true });
|
||||||
|
const content = generateStarterIgnoreFile(testDir);
|
||||||
|
expect(content).toContain("# docs/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("suggests test directories when they exist", () => {
|
||||||
|
mkdirSync(join(testDir, "test"), { recursive: true });
|
||||||
|
mkdirSync(join(testDir, "tests"), { recursive: true });
|
||||||
|
const content = generateStarterIgnoreFile(testDir);
|
||||||
|
expect(content).toContain("# test/");
|
||||||
|
expect(content).toContain("# tests/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("suggests fixtures when fixtures directory exists", () => {
|
||||||
|
mkdirSync(join(testDir, "fixtures"), { recursive: true });
|
||||||
|
const content = generateStarterIgnoreFile(testDir);
|
||||||
|
expect(content).toContain("# fixtures/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("suggests examples when examples directory exists", () => {
|
||||||
|
mkdirSync(join(testDir, "examples"), { recursive: true });
|
||||||
|
const content = generateStarterIgnoreFile(testDir);
|
||||||
|
expect(content).toContain("# examples/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("suggests .storybook when .storybook directory exists", () => {
|
||||||
|
mkdirSync(join(testDir, ".storybook"), { recursive: true });
|
||||||
|
const content = generateStarterIgnoreFile(testDir);
|
||||||
|
expect(content).toContain("# .storybook/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("suggests migrations when migrations directory exists", () => {
|
||||||
|
mkdirSync(join(testDir, "migrations"), { recursive: true });
|
||||||
|
const content = generateStarterIgnoreFile(testDir);
|
||||||
|
expect(content).toContain("# migrations/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("suggests scripts when scripts directory exists", () => {
|
||||||
|
mkdirSync(join(testDir, "scripts"), { recursive: true });
|
||||||
|
const content = generateStarterIgnoreFile(testDir);
|
||||||
|
expect(content).toContain("# scripts/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("always includes generic suggestions", () => {
|
||||||
|
const content = generateStarterIgnoreFile(testDir);
|
||||||
|
expect(content).toContain("# *.snap");
|
||||||
|
expect(content).toContain("# *.test.*");
|
||||||
|
expect(content).toContain("# *.spec.*");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not suggest directories that don't exist", () => {
|
||||||
|
const content = generateStarterIgnoreFile(testDir);
|
||||||
|
// __tests__ doesn't exist, so it shouldn't be in directory suggestions
|
||||||
|
// (it may still be in generic test file patterns)
|
||||||
|
expect(content).not.toContain("# __tests__/");
|
||||||
|
expect(content).not.toContain("# .storybook/");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests to verify they fail**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/core test -- --run src/__tests__/ignore-generator.test.ts`
|
||||||
|
Expected: FAIL — module not found
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement IgnoreGenerator**
|
||||||
|
|
||||||
|
Create `understand-anything-plugin/packages/core/src/ignore-generator.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { existsSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
|
||||||
|
const HEADER = `# .understandignore — patterns for files/dirs to exclude from analysis
|
||||||
|
# Syntax: same as .gitignore (globs, # comments, ! negation, trailing / for dirs)
|
||||||
|
# Lines below are suggestions — uncomment to activate.
|
||||||
|
# Use ! prefix to force-include something excluded by defaults.
|
||||||
|
#
|
||||||
|
# Built-in defaults (always excluded unless negated):
|
||||||
|
# node_modules/, .git/, dist/, build/, bin/, obj/, *.lock, *.min.js, etc.
|
||||||
|
#
|
||||||
|
`;
|
||||||
|
|
||||||
|
/** Directories to check for and suggest excluding. */
|
||||||
|
const DETECTABLE_DIRS = [
|
||||||
|
{ dir: "__tests__", pattern: "__tests__/" },
|
||||||
|
{ dir: "test", pattern: "test/" },
|
||||||
|
{ dir: "tests", pattern: "tests/" },
|
||||||
|
{ dir: "fixtures", pattern: "fixtures/" },
|
||||||
|
{ dir: "testdata", pattern: "testdata/" },
|
||||||
|
{ dir: "docs", pattern: "docs/" },
|
||||||
|
{ dir: "examples", pattern: "examples/" },
|
||||||
|
{ dir: "scripts", pattern: "scripts/" },
|
||||||
|
{ dir: "migrations", pattern: "migrations/" },
|
||||||
|
{ dir: ".storybook", pattern: ".storybook/" },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Always-included generic suggestions. */
|
||||||
|
const GENERIC_SUGGESTIONS = [
|
||||||
|
"*.test.*",
|
||||||
|
"*.spec.*",
|
||||||
|
"*.snap",
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a starter .understandignore file by scanning the project root
|
||||||
|
* for common directories and suggesting them as commented-out exclusions.
|
||||||
|
*
|
||||||
|
* All suggestions are commented out — the user must uncomment to activate.
|
||||||
|
* Returns the file content as a string.
|
||||||
|
*/
|
||||||
|
export function generateStarterIgnoreFile(projectRoot: string): string {
|
||||||
|
const sections: string[] = [HEADER];
|
||||||
|
|
||||||
|
// Detected directory suggestions
|
||||||
|
const detected: string[] = [];
|
||||||
|
for (const { dir, pattern } of DETECTABLE_DIRS) {
|
||||||
|
if (existsSync(join(projectRoot, dir))) {
|
||||||
|
detected.push(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detected.length > 0) {
|
||||||
|
sections.push("# --- Detected directories (uncomment to exclude) ---\n");
|
||||||
|
for (const pattern of detected) {
|
||||||
|
sections.push(`# ${pattern}`);
|
||||||
|
}
|
||||||
|
sections.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic suggestions (always included)
|
||||||
|
sections.push("# --- Test file patterns (uncomment to exclude) ---\n");
|
||||||
|
for (const pattern of GENERIC_SUGGESTIONS) {
|
||||||
|
sections.push(`# ${pattern}`);
|
||||||
|
}
|
||||||
|
sections.push("");
|
||||||
|
|
||||||
|
return sections.join("\n");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run tests to verify they pass**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/core test -- --run src/__tests__/ignore-generator.test.ts`
|
||||||
|
Expected: All tests PASS
|
||||||
|
|
||||||
|
- [ ] **Step 5: Build**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/core build`
|
||||||
|
Expected: Clean build
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/packages/core/src/ignore-generator.ts understand-anything-plugin/packages/core/src/__tests__/ignore-generator.test.ts
|
||||||
|
git commit -m "feat(core): add IgnoreGenerator for starter .understandignore file creation"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: Export new modules from core
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/packages/core/src/index.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add exports**
|
||||||
|
|
||||||
|
Add to the end of `understand-anything-plugin/packages/core/src/index.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export {
|
||||||
|
createIgnoreFilter,
|
||||||
|
DEFAULT_IGNORE_PATTERNS,
|
||||||
|
type IgnoreFilter,
|
||||||
|
} from "./ignore-filter.js";
|
||||||
|
export { generateStarterIgnoreFile } from "./ignore-generator.js";
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Build and run all tests**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/core build && pnpm --filter @understand-anything/core test -- --run`
|
||||||
|
Expected: Clean build, all tests pass
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/packages/core/src/index.ts
|
||||||
|
git commit -m "feat(core): export IgnoreFilter and IgnoreGenerator from core index"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5: Update project-scanner agent
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/agents/project-scanner.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Read the current project-scanner.md**
|
||||||
|
|
||||||
|
Read `understand-anything-plugin/agents/project-scanner.md` to understand the current structure.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add bin/ and obj/ to hardcoded exclusions**
|
||||||
|
|
||||||
|
In Step 2 (Exclusion Filtering), add `bin/` and `obj/` to the "Build output" line:
|
||||||
|
|
||||||
|
Change:
|
||||||
|
```
|
||||||
|
- **Build output:** paths with a directory segment matching `dist/`, `build/`, `out/`, `coverage/`, `.next/`, `.cache/`, `.turbo/`, `target/` (Rust)
|
||||||
|
```
|
||||||
|
|
||||||
|
To:
|
||||||
|
```
|
||||||
|
- **Build output:** paths with a directory segment matching `dist/`, `build/`, `out/`, `coverage/`, `.next/`, `.cache/`, `.turbo/`, `target/` (Rust), `bin/` (.NET), `obj/` (.NET)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add Layer 2 filtering step**
|
||||||
|
|
||||||
|
After Step 2 (Exclusion Filtering), add a new step:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
**Step 2.5 -- User-Configured Filtering (.understandignore)**
|
||||||
|
|
||||||
|
After applying the hardcoded exclusion filters above, apply user-configured patterns from `.understandignore`:
|
||||||
|
|
||||||
|
1. Check if `.understand-anything/.understandignore` exists in the project root. If so, read it.
|
||||||
|
2. Check if `.understandignore` exists in the project root. If so, read it.
|
||||||
|
3. Parse both files using `.gitignore` syntax (glob patterns, `#` comments, blank lines ignored, `!` prefix for negation, trailing `/` for directories, `**/` for recursive matching).
|
||||||
|
4. Filter the remaining file list through these patterns. Files matching any pattern are excluded.
|
||||||
|
5. `!` negation patterns override the hardcoded exclusions from Step 2 (e.g., `!dist/` force-includes dist/).
|
||||||
|
6. Track the count of files removed by this step as `filteredByIgnore`.
|
||||||
|
|
||||||
|
This filtering must be deterministic (not LLM-based). Use a Node.js script with the `ignore` npm package if implementing programmatically, or apply the patterns manually if the file list is small.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Update scan output schema**
|
||||||
|
|
||||||
|
Find the output JSON schema section and add `filteredByIgnore` field:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "...",
|
||||||
|
"description": "...",
|
||||||
|
"languages": ["..."],
|
||||||
|
"frameworks": ["..."],
|
||||||
|
"files": [...],
|
||||||
|
"totalFiles": 123,
|
||||||
|
"filteredByIgnore": 5,
|
||||||
|
"estimatedComplexity": "moderate",
|
||||||
|
"importMap": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/agents/project-scanner.md
|
||||||
|
git commit -m "feat(agent): add .understandignore support and bin/obj exclusions to project-scanner"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 6: Update /understand skill with Phase 0.5
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `understand-anything-plugin/skills/understand/SKILL.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Read the current SKILL.md Phase 0 section**
|
||||||
|
|
||||||
|
Read `understand-anything-plugin/skills/understand/SKILL.md` lines 22-80 to understand Phase 0.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add Phase 0.5 after Phase 0**
|
||||||
|
|
||||||
|
After the Phase 0 section (after the `---` separator before Phase 1), insert:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Phase 0.5 — Ignore Configuration
|
||||||
|
|
||||||
|
Set up and verify the `.understandignore` file before scanning.
|
||||||
|
|
||||||
|
1. Check if `$PROJECT_ROOT/.understand-anything/.understandignore` exists.
|
||||||
|
2. **If it does NOT exist**, generate a starter file:
|
||||||
|
- Run a Node.js script (or inline logic) that scans `$PROJECT_ROOT` for common directories (`__tests__/`, `test/`, `tests/`, `fixtures/`, `testdata/`, `docs/`, `examples/`, `scripts/`, `migrations/`, `.storybook/`) and generates a `.understandignore` file with commented-out suggestions.
|
||||||
|
- Write the generated content to `$PROJECT_ROOT/.understand-anything/.understandignore`.
|
||||||
|
- Report to the user:
|
||||||
|
> "Generated `.understand-anything/.understandignore` with suggested exclusions based on your project structure. Please review it and uncomment any patterns you'd like to exclude from analysis. When ready, confirm to continue."
|
||||||
|
- **Wait for user confirmation before proceeding.**
|
||||||
|
3. **If it already exists**, report:
|
||||||
|
> "Found `.understand-anything/.understandignore`. Review it if needed, then confirm to continue."
|
||||||
|
- **Wait for user confirmation before proceeding.**
|
||||||
|
4. After confirmation, proceed to Phase 1.
|
||||||
|
|
||||||
|
**Note:** The `.understandignore` file uses `.gitignore` syntax. The user can add patterns to exclude files from analysis, or use `!` prefix to force-include files excluded by built-in defaults (e.g., `!dist/` to analyze dist/ files).
|
||||||
|
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update Phase 1 reporting**
|
||||||
|
|
||||||
|
In the Phase 1 section, after the gate check (~line 114), add a note about reporting ignore stats:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
After scanning, if the scan result includes `filteredByIgnore > 0`, report:
|
||||||
|
> "Scanned {totalFiles} files ({filteredByIgnore} excluded by .understandignore)"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add understand-anything-plugin/skills/understand/SKILL.md
|
||||||
|
git commit -m "feat(skill): add Phase 0.5 for .understandignore setup and review pause"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 7: Build, test, and verify end-to-end
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- All modified files
|
||||||
|
|
||||||
|
- [ ] **Step 1: Build core**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/core build`
|
||||||
|
Expected: Clean build
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run all core tests**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/core test -- --run`
|
||||||
|
Expected: All tests pass (existing + new ignore-filter + ignore-generator tests)
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build skill package**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/skill build`
|
||||||
|
Expected: Clean build
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify files exist**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
ls understand-anything-plugin/packages/core/src/ignore-filter.ts understand-anything-plugin/packages/core/src/ignore-generator.ts
|
||||||
|
```
|
||||||
|
Expected: Both files listed
|
||||||
|
|
||||||
|
- [ ] **Step 5: Verify exports work**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node -e "import('@understand-anything/core').then(m => { console.log('IgnoreFilter:', typeof m.createIgnoreFilter); console.log('Generator:', typeof m.generateStarterIgnoreFile); })"
|
||||||
|
```
|
||||||
|
Expected: Both show `function`
|
||||||
|
|
||||||
|
- [ ] **Step 6: Final commit (if any unstaged changes)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status
|
||||||
|
# If clean, skip. If changes exist:
|
||||||
|
git add -A && git commit -m "chore: final verification for .understandignore support"
|
||||||
|
```
|
||||||
@@ -0,0 +1,856 @@
|
|||||||
|
# Language-Specific Extractor Architecture Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** (1) Decouple AST extraction logic from TS/JS-specific node types so 8 additional code languages (Python, Go, Rust, Java, Ruby, PHP, C/C++, C#) get tree-sitter-powered structural analysis. Swift and Kotlin are excluded — no WASM grammar packages available. (2) Replace the file-analyzer agent's ad-hoc regex script generation with a deterministic, pre-built tree-sitter extraction script.
|
||||||
|
|
||||||
|
**Architecture:** Introduce a `LanguageExtractor` interface that each language implements. `TreeSitterPlugin` delegates extraction to the registered extractor for the file's language. A bundled `extract-structure.mjs` script in `skills/understand/` uses `PluginRegistry` (which includes both `TreeSitterPlugin` and the non-code parsers) to provide deterministic structural extraction for the file-analyzer agent — replacing the current approach where the LLM writes throwaway regex scripts every run.
|
||||||
|
|
||||||
|
**Tech Stack:** web-tree-sitter (WASM), TypeScript, Vitest
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/core/src/plugins/
|
||||||
|
├── extractors/
|
||||||
|
│ ├── types.ts # LanguageExtractor interface + TreeSitterNode re-export
|
||||||
|
│ ├── base-extractor.ts # Shared utilities (traverse, getStringValue)
|
||||||
|
│ ├── typescript-extractor.ts # TS/JS (moved from tree-sitter-plugin.ts)
|
||||||
|
│ ├── python-extractor.ts
|
||||||
|
│ ├── go-extractor.ts
|
||||||
|
│ ├── rust-extractor.ts
|
||||||
|
│ ├── java-extractor.ts
|
||||||
|
│ ├── ruby-extractor.ts
|
||||||
|
│ ├── php-extractor.ts
|
||||||
|
│ ├── cpp-extractor.ts
|
||||||
|
│ ├── csharp-extractor.ts
|
||||||
|
│ └── index.ts # builtinExtractors array + re-exports
|
||||||
|
├── tree-sitter-plugin.ts # Refactored to use extractors
|
||||||
|
└── tree-sitter-plugin.test.ts # Existing tests (should still pass)
|
||||||
|
|
||||||
|
packages/core/src/plugins/__tests__/
|
||||||
|
└── extractors.test.ts # Tests for all new extractors
|
||||||
|
|
||||||
|
skills/understand/
|
||||||
|
├── extract-structure.mjs # Pre-built tree-sitter extraction script (NEW)
|
||||||
|
└── SKILL.md # Updated to reference extract-structure.mjs
|
||||||
|
|
||||||
|
agents/
|
||||||
|
└── file-analyzer.md # Phase 1 rewritten to execute pre-built script
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Create LanguageExtractor interface and shared utilities
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `packages/core/src/plugins/extractors/types.ts`
|
||||||
|
- Create: `packages/core/src/plugins/extractors/base-extractor.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the extractor interface**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// packages/core/src/plugins/extractors/types.ts
|
||||||
|
import type { StructuralAnalysis, CallGraphEntry } from "../../types.js";
|
||||||
|
|
||||||
|
// Re-export the tree-sitter Node type for use by extractors
|
||||||
|
export type TreeSitterNode = import("web-tree-sitter").Node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Language-specific extractor that maps a tree-sitter AST
|
||||||
|
* to the common StructuralAnalysis / CallGraphEntry types.
|
||||||
|
*/
|
||||||
|
export interface LanguageExtractor {
|
||||||
|
/** Language IDs this extractor handles (must match LanguageConfig.id) */
|
||||||
|
languageIds: string[];
|
||||||
|
|
||||||
|
/** Extract functions, classes, imports, exports from the root AST node */
|
||||||
|
extractStructure(rootNode: TreeSitterNode): StructuralAnalysis;
|
||||||
|
|
||||||
|
/** Extract caller→callee relationships from the root AST node */
|
||||||
|
extractCallGraph(rootNode: TreeSitterNode): CallGraphEntry[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Create base-extractor with shared utilities**
|
||||||
|
|
||||||
|
Move `traverse()` and `getStringValue()` from `tree-sitter-plugin.ts` into a shared module:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// packages/core/src/plugins/extractors/base-extractor.ts
|
||||||
|
import type { TreeSitterNode } from "./types.js";
|
||||||
|
|
||||||
|
/** Recursively traverse an AST tree, calling the visitor for each node. */
|
||||||
|
export function traverse(
|
||||||
|
node: TreeSitterNode,
|
||||||
|
visitor: (node: TreeSitterNode) => void,
|
||||||
|
): void {
|
||||||
|
visitor(node);
|
||||||
|
for (let i = 0; i < node.childCount; i++) {
|
||||||
|
const child = node.child(i);
|
||||||
|
if (child) traverse(child, visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extract the unquoted string value from a string-like node. */
|
||||||
|
export function getStringValue(node: TreeSitterNode): string {
|
||||||
|
for (let i = 0; i < node.childCount; i++) {
|
||||||
|
const child = node.child(i);
|
||||||
|
if (child && child.type === "string_fragment") {
|
||||||
|
return child.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node.text.replace(/^['"`]|['"`]$/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find the first child matching a type. */
|
||||||
|
export function findChild(node: TreeSitterNode, type: string): TreeSitterNode | null {
|
||||||
|
for (let i = 0; i < node.childCount; i++) {
|
||||||
|
const child = node.child(i);
|
||||||
|
if (child && child.type === type) return child;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find all children matching a type. */
|
||||||
|
export function findChildren(node: TreeSitterNode, type: string): TreeSitterNode[] {
|
||||||
|
const result: TreeSitterNode[] = [];
|
||||||
|
for (let i = 0; i < node.childCount; i++) {
|
||||||
|
const child = node.child(i);
|
||||||
|
if (child && child.type === type) result.push(child);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check if a node has a child of the given type (used for export/visibility checks). */
|
||||||
|
export function hasChildOfType(node: TreeSitterNode, type: string): boolean {
|
||||||
|
for (let i = 0; i < node.childCount; i++) {
|
||||||
|
const child = node.child(i);
|
||||||
|
if (child && child.type === type) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add packages/core/src/plugins/extractors/types.ts packages/core/src/plugins/extractors/base-extractor.ts
|
||||||
|
git commit -m "feat: add LanguageExtractor interface and shared base utilities"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Move TS/JS extraction logic into TypeScriptExtractor
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `packages/core/src/plugins/extractors/typescript-extractor.ts`
|
||||||
|
- Modify: `packages/core/src/plugins/tree-sitter-plugin.ts`
|
||||||
|
|
||||||
|
This is a pure refactor. All existing tests must still pass with zero changes.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create TypeScriptExtractor**
|
||||||
|
|
||||||
|
Move all the TS/JS-specific extraction methods (`extractFunction`, `extractClass`, `extractVariableDeclarations`, `extractImport`, `processExportStatement`, `extractParams`, `extractReturnType`, `extractImportSpecifiers`, and the call graph walker) from `tree-sitter-plugin.ts` into `typescript-extractor.ts`, implementing the `LanguageExtractor` interface.
|
||||||
|
|
||||||
|
The `languageIds` should be `["typescript", "javascript"]`. Do NOT include `"tsx"` — it is a synthetic key internal to `TreeSitterPlugin` for grammar selection, not a `LanguageConfig.id`. The tsx→typescript mapping is handled in `getExtractor()` below.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Refactor TreeSitterPlugin to use extractors**
|
||||||
|
|
||||||
|
Replace the hardcoded extraction logic in `TreeSitterPlugin` with extractor dispatch:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In TreeSitterPlugin
|
||||||
|
private extractors = new Map<string, LanguageExtractor>();
|
||||||
|
|
||||||
|
registerExtractor(extractor: LanguageExtractor): void {
|
||||||
|
for (const id of extractor.languageIds) {
|
||||||
|
this.extractors.set(id, extractor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getExtractor(langKey: string): LanguageExtractor | null {
|
||||||
|
// tsx is a synthetic grammar key — extraction logic is identical to typescript
|
||||||
|
const key = langKey === "tsx" ? "typescript" : langKey;
|
||||||
|
return this.extractors.get(key) ?? null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `analyzeFile()` method becomes:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
analyzeFile(filePath: string, content: string): StructuralAnalysis {
|
||||||
|
const parser = this.getParser(filePath);
|
||||||
|
if (!parser) return { functions: [], classes: [], imports: [], exports: [] };
|
||||||
|
|
||||||
|
const tree = parser.parse(content);
|
||||||
|
if (!tree) { parser.delete(); return { functions: [], classes: [], imports: [], exports: [] }; }
|
||||||
|
|
||||||
|
const langKey = this.languageKeyFromPath(filePath);
|
||||||
|
const extractor = langKey ? this.getExtractor(langKey) : null;
|
||||||
|
|
||||||
|
let result: StructuralAnalysis;
|
||||||
|
if (extractor) {
|
||||||
|
result = extractor.extractStructure(tree.rootNode);
|
||||||
|
} else {
|
||||||
|
result = { functions: [], classes: [], imports: [], exports: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.delete();
|
||||||
|
parser.delete();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `extractCallGraph()` method follows the same pattern — parser lifecycle must be managed identically:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
extractCallGraph(filePath: string, content: string): CallGraphEntry[] {
|
||||||
|
const parser = this.getParser(filePath);
|
||||||
|
if (!parser) return [];
|
||||||
|
|
||||||
|
const tree = parser.parse(content);
|
||||||
|
if (!tree) { parser.delete(); return []; }
|
||||||
|
|
||||||
|
const langKey = this.languageKeyFromPath(filePath);
|
||||||
|
const extractor = langKey ? this.getExtractor(langKey) : null;
|
||||||
|
const result = extractor ? extractor.extractCallGraph(tree.rootNode) : [];
|
||||||
|
|
||||||
|
tree.delete();
|
||||||
|
parser.delete();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The constructor should accept an optional `extractors` array and register them. If none provided, register the built-in `TypeScriptExtractor` for backward compatibility.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run existing tests to verify zero behavior change**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/core test`
|
||||||
|
Expected: All 426 tests pass (identical to before)
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add packages/core/src/plugins/extractors/typescript-extractor.ts packages/core/src/plugins/tree-sitter-plugin.ts
|
||||||
|
git commit -m "refactor: move TS/JS extraction logic to TypeScriptExtractor, dispatch via LanguageExtractor interface"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2.5: Add extractCallGraph to PluginRegistry and update DEFAULT_PLUGIN_CONFIG
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `packages/core/src/plugins/registry.ts`
|
||||||
|
- Modify: `packages/core/src/plugins/discovery.ts`
|
||||||
|
|
||||||
|
**Context:** `PluginRegistry` currently only exposes `analyzeFile` and `resolveImports` — it has no `extractCallGraph`. The `extract-structure.mjs` script (Task 13) needs call graph data through the registry. Also, `DEFAULT_PLUGIN_CONFIG` hardcodes `["typescript", "javascript"]` which needs to reflect all supported languages.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add extractCallGraph to PluginRegistry**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In PluginRegistry (registry.ts)
|
||||||
|
extractCallGraph(filePath: string, content: string): CallGraphEntry[] | null {
|
||||||
|
const plugin = this.getPluginForFile(filePath);
|
||||||
|
if (!plugin?.extractCallGraph) return null;
|
||||||
|
return plugin.extractCallGraph(filePath, content);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update DEFAULT_PLUGIN_CONFIG to derive languages dynamically**
|
||||||
|
|
||||||
|
In `discovery.ts`, replace the hardcoded `["typescript", "javascript"]` with a dynamic derivation from `builtinLanguageConfigs`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { builtinLanguageConfigs } from "../languages/configs/index.js";
|
||||||
|
|
||||||
|
export const DEFAULT_PLUGIN_CONFIG: PluginConfig = {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: "tree-sitter",
|
||||||
|
enabled: true,
|
||||||
|
languages: builtinLanguageConfigs
|
||||||
|
.filter((c) => c.treeSitter)
|
||||||
|
.map((c) => c.id),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run tests, commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @understand-anything/core test
|
||||||
|
git add packages/core/src/plugins/registry.ts packages/core/src/plugins/discovery.ts
|
||||||
|
git commit -m "feat: add extractCallGraph to PluginRegistry, derive DEFAULT_PLUGIN_CONFIG from configs"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Add npm dependencies and treeSitter configs for all 10 languages
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `packages/core/package.json` (add 8 deps: python, go, rust, java, ruby, php, cpp, c-sharp)
|
||||||
|
- Modify: 10 config files in `packages/core/src/languages/configs/`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add tree-sitter grammar dependencies to package.json**
|
||||||
|
|
||||||
|
Add to `dependencies`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"tree-sitter-c-sharp": "^0.23.1",
|
||||||
|
"tree-sitter-cpp": "^0.23.4",
|
||||||
|
"tree-sitter-go": "^0.25.0",
|
||||||
|
"tree-sitter-java": "^0.23.5",
|
||||||
|
"tree-sitter-php": "^0.23.11",
|
||||||
|
"tree-sitter-python": "^0.25.0",
|
||||||
|
"tree-sitter-ruby": "^0.23.1",
|
||||||
|
"tree-sitter-rust": "^0.24.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run `pnpm install`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add treeSitter field to all 10 language configs**
|
||||||
|
|
||||||
|
Each config gets a `treeSitter` block. Examples:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// python.ts
|
||||||
|
treeSitter: { wasmPackage: "tree-sitter-python", wasmFile: "tree-sitter-python.wasm" },
|
||||||
|
|
||||||
|
// go.ts
|
||||||
|
treeSitter: { wasmPackage: "tree-sitter-go", wasmFile: "tree-sitter-go.wasm" },
|
||||||
|
|
||||||
|
// rust.ts
|
||||||
|
treeSitter: { wasmPackage: "tree-sitter-rust", wasmFile: "tree-sitter-rust.wasm" },
|
||||||
|
|
||||||
|
// java.ts
|
||||||
|
treeSitter: { wasmPackage: "tree-sitter-java", wasmFile: "tree-sitter-java.wasm" },
|
||||||
|
|
||||||
|
// ruby.ts
|
||||||
|
treeSitter: { wasmPackage: "tree-sitter-ruby", wasmFile: "tree-sitter-ruby.wasm" },
|
||||||
|
|
||||||
|
// php.ts
|
||||||
|
treeSitter: { wasmPackage: "tree-sitter-php", wasmFile: "tree-sitter-php.wasm" },
|
||||||
|
|
||||||
|
// cpp.ts
|
||||||
|
treeSitter: { wasmPackage: "tree-sitter-cpp", wasmFile: "tree-sitter-cpp.wasm" },
|
||||||
|
|
||||||
|
// csharp.ts
|
||||||
|
treeSitter: { wasmPackage: "tree-sitter-c-sharp", wasmFile: "tree-sitter-c_sharp.wasm" },
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: Swift and Kotlin configs are NOT changed (no WASM packages available).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run pnpm install and verify WASM files resolve**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
node -e "const r=require('module').createRequire(import.meta.url??__filename); console.log(r.resolve('tree-sitter-python/tree-sitter-python.wasm'))"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add packages/core/package.json pnpm-lock.yaml packages/core/src/languages/configs/
|
||||||
|
git commit -m "feat: add tree-sitter grammar deps and treeSitter configs for 10 languages"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Create Python extractor
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `packages/core/src/plugins/extractors/python-extractor.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the Python extractor**
|
||||||
|
|
||||||
|
Key Python tree-sitter node types:
|
||||||
|
- Functions: `function_definition` (name, parameters, return_type)
|
||||||
|
- Classes: `class_definition` (name, body → methods + assignments as properties)
|
||||||
|
- Imports: `import_statement`, `import_from_statement`
|
||||||
|
- Decorated: `decorated_definition` wrapping function_definition or class_definition
|
||||||
|
- Calls: `call` (function field)
|
||||||
|
- No formal exports (all top-level names are "exported")
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
languageIds: ["python"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write tests for Python extractor**
|
||||||
|
|
||||||
|
Test with representative Python code:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class DataProcessor:
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def __init__(self, name: str):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def process(self, data: list) -> dict:
|
||||||
|
return transform(data)
|
||||||
|
|
||||||
|
def helper(x: int) -> str:
|
||||||
|
return str(x)
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def decorated_func():
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify: 2 functions (helper, decorated_func), 1 class (DataProcessor with methods __init__/process and property name), 3 imports, call graph (process→transform).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run tests**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/core test`
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Create Go extractor
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `packages/core/src/plugins/extractors/go-extractor.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the Go extractor**
|
||||||
|
|
||||||
|
Key Go tree-sitter node types:
|
||||||
|
- Functions: `function_declaration` (name, parameter_list, result)
|
||||||
|
- Methods: `method_declaration` (receiver, name, parameter_list, result)
|
||||||
|
- Structs: `type_declaration` → `type_spec` → `struct_type`
|
||||||
|
- Interfaces: `type_declaration` → `type_spec` → `interface_type`
|
||||||
|
- Imports: `import_declaration` → `import_spec_list` → `import_spec`
|
||||||
|
- Exports: capitalized first letter of name
|
||||||
|
- Calls: `call_expression` (function field)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
languageIds: ["go"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write tests**
|
||||||
|
|
||||||
|
Test with:
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start() error {
|
||||||
|
fmt.Println("starting")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(host string, port int) *Server {
|
||||||
|
return &Server{Host: host, Port: port}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify: 2 functions (Start, NewServer), 1 class/struct (Server with method Start, properties Host/Port), 2 imports, exports (Server, Start, NewServer — all capitalized), call graph (Start→fmt.Println).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run tests and commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: Create Rust extractor
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `packages/core/src/plugins/extractors/rust-extractor.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the Rust extractor**
|
||||||
|
|
||||||
|
Key Rust tree-sitter node types:
|
||||||
|
- Functions: `function_item` (name, parameters, return_type via `->`)
|
||||||
|
- Structs: `struct_item` (name, field_declaration_list)
|
||||||
|
- Enums: `enum_item`
|
||||||
|
- Impl blocks: `impl_item` (type, body containing function_items)
|
||||||
|
- Traits: `trait_item`
|
||||||
|
- Imports: `use_declaration` (scoped_identifier, use_list, use_wildcard)
|
||||||
|
- Exports: `visibility_modifier` containing `pub`
|
||||||
|
- Calls: `call_expression` (function field)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
languageIds: ["rust"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write tests**
|
||||||
|
|
||||||
|
Test with:
|
||||||
|
```rust
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::{self, Read};
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
name: String,
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn new(name: String, port: u16) -> Self {
|
||||||
|
Config { name, port }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self) -> bool {
|
||||||
|
check_port(self.port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_port(port: u16) -> bool {
|
||||||
|
port > 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify: 3 functions (new, validate, check_port), 1 class/struct (Config with methods new/validate, properties name/port), 2 imports, exports (Config, new, check_port — those with `pub`), call graph (validate→check_port).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run tests and commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 7: Create Java extractor
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `packages/core/src/plugins/extractors/java-extractor.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the Java extractor**
|
||||||
|
|
||||||
|
Key Java tree-sitter node types:
|
||||||
|
- Methods: `method_declaration` (name, formal_parameters, type/dimensions)
|
||||||
|
- Constructors: `constructor_declaration` (name, formal_parameters)
|
||||||
|
- Classes: `class_declaration` (name, class_body)
|
||||||
|
- Interfaces: `interface_declaration`
|
||||||
|
- Fields: `field_declaration` (declarator → variable_declarator → identifier)
|
||||||
|
- Imports: `import_declaration` (scoped_identifier)
|
||||||
|
- Exports: `public` modifier (modifiers node)
|
||||||
|
- Calls: `method_invocation` (name, object, arguments)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
languageIds: ["java"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write tests with representative Java code, run, commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 8: Create Ruby extractor
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `packages/core/src/plugins/extractors/ruby-extractor.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the Ruby extractor**
|
||||||
|
|
||||||
|
Key Ruby tree-sitter node types:
|
||||||
|
- Methods: `method` (name, parameters)
|
||||||
|
- Classes: `class` (name, body containing methods)
|
||||||
|
- Modules: `module` (name)
|
||||||
|
- Imports: `call` where method is `require` or `require_relative` (Ruby uses method calls for imports)
|
||||||
|
- Calls: `call` (method, receiver, arguments)
|
||||||
|
- No formal export syntax
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
languageIds: ["ruby"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write tests, run, commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 9: Create PHP extractor
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `packages/core/src/plugins/extractors/php-extractor.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the PHP extractor**
|
||||||
|
|
||||||
|
Key PHP tree-sitter node types:
|
||||||
|
- Functions: `function_definition` (name, formal_parameters, return_type)
|
||||||
|
- Methods: `method_declaration` (name, formal_parameters, return_type)
|
||||||
|
- Classes: `class_declaration` (name, declaration_list)
|
||||||
|
- Imports: `namespace_use_declaration` (namespace_use_clause)
|
||||||
|
- Calls: `function_call_expression` / `member_call_expression`
|
||||||
|
- Note: PHP tree wraps everything in a `program` → `php_tag` + statements
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
languageIds: ["php"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write tests, run, commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 10: Create C/C++ extractor
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `packages/core/src/plugins/extractors/cpp-extractor.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the C/C++ extractor**
|
||||||
|
|
||||||
|
Key C/C++ tree-sitter node types:
|
||||||
|
- Functions: `function_definition` (declarator → function_declarator → identifier + parameter_list)
|
||||||
|
- Classes: `class_specifier` (name, body → field_declaration_list)
|
||||||
|
- Structs: `struct_specifier` (name, body)
|
||||||
|
- Includes: `preproc_include` (path → string_literal or system_lib_string)
|
||||||
|
- Namespaces: `namespace_definition`
|
||||||
|
- Calls: `call_expression` (function, arguments)
|
||||||
|
|
||||||
|
Note: C/C++ function signatures are nested (the name is inside a `function_declarator` inside the `declarator` field).
|
||||||
|
|
||||||
|
The `cppConfig` has `id: "cpp"` and `extensions: [".cpp", ".cc", ".cxx", ".c", ".h", ".hpp", ".hxx"]`. Pure C files (`.c`, `.h`) are parsed with the C++ grammar, which works but won't produce C++-specific node types like `class_specifier`. The extractor must handle their absence gracefully (return empty arrays for classes when parsing pure C).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
languageIds: ["cpp"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write tests for both C++ and pure C code, run, commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 11: Create C# extractor
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `packages/core/src/plugins/extractors/csharp-extractor.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the C# extractor**
|
||||||
|
|
||||||
|
Key C# tree-sitter node types:
|
||||||
|
- Methods: `method_declaration` (name, parameter_list, return type)
|
||||||
|
- Constructors: `constructor_declaration`
|
||||||
|
- Classes: `class_declaration` (name, declaration_list)
|
||||||
|
- Interfaces: `interface_declaration`
|
||||||
|
- Properties: `property_declaration` (name, type)
|
||||||
|
- Imports: `using_directive` (qualified_name)
|
||||||
|
- Calls: `invocation_expression` (identifier/member_access, argument_list)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
languageIds: ["csharp"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write tests, run, commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 12: Create extractor index and wire into TreeSitterPlugin
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `packages/core/src/plugins/extractors/index.ts`
|
||||||
|
- Modify: `packages/core/src/plugins/tree-sitter-plugin.ts` (import builtinExtractors)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create index.ts exporting all extractors**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// packages/core/src/plugins/extractors/index.ts
|
||||||
|
export type { LanguageExtractor, TreeSitterNode } from "./types.js";
|
||||||
|
export { traverse, getStringValue, findChild, findChildren, hasChildOfType } from "./base-extractor.js";
|
||||||
|
export { TypeScriptExtractor } from "./typescript-extractor.js";
|
||||||
|
export { PythonExtractor } from "./python-extractor.js";
|
||||||
|
export { GoExtractor } from "./go-extractor.js";
|
||||||
|
export { RustExtractor } from "./rust-extractor.js";
|
||||||
|
export { JavaExtractor } from "./java-extractor.js";
|
||||||
|
export { RubyExtractor } from "./ruby-extractor.js";
|
||||||
|
export { PhpExtractor } from "./php-extractor.js";
|
||||||
|
export { CppExtractor } from "./cpp-extractor.js";
|
||||||
|
export { CSharpExtractor } from "./csharp-extractor.js";
|
||||||
|
|
||||||
|
import type { LanguageExtractor } from "./types.js";
|
||||||
|
import { TypeScriptExtractor } from "./typescript-extractor.js";
|
||||||
|
import { PythonExtractor } from "./python-extractor.js";
|
||||||
|
import { GoExtractor } from "./go-extractor.js";
|
||||||
|
import { RustExtractor } from "./rust-extractor.js";
|
||||||
|
import { JavaExtractor } from "./java-extractor.js";
|
||||||
|
import { RubyExtractor } from "./ruby-extractor.js";
|
||||||
|
import { PhpExtractor } from "./php-extractor.js";
|
||||||
|
import { CppExtractor } from "./cpp-extractor.js";
|
||||||
|
import { CSharpExtractor } from "./csharp-extractor.js";
|
||||||
|
|
||||||
|
export const builtinExtractors: LanguageExtractor[] = [
|
||||||
|
new TypeScriptExtractor(),
|
||||||
|
new PythonExtractor(),
|
||||||
|
new GoExtractor(),
|
||||||
|
new RustExtractor(),
|
||||||
|
new JavaExtractor(),
|
||||||
|
new RubyExtractor(),
|
||||||
|
new PhpExtractor(),
|
||||||
|
new CppExtractor(),
|
||||||
|
new CSharpExtractor(),
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Wire builtinExtractors into TreeSitterPlugin constructor**
|
||||||
|
|
||||||
|
When no extractors are provided, default to `builtinExtractors`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run full test suite**
|
||||||
|
|
||||||
|
Run: `pnpm --filter @understand-anything/core test`
|
||||||
|
Expected: All tests pass (existing + new extractor tests)
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 13: Create bundled extract-structure.mjs script
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `skills/understand/extract-structure.mjs`
|
||||||
|
|
||||||
|
**Context:** Currently the file-analyzer agent (Phase 1) instructs the LLM to write a throwaway regex-based Node.js/Python script every run. This is slow, non-deterministic, and ignores the tree-sitter infrastructure we just built. This task replaces that with a pre-built script that uses `PluginRegistry` (which routes to `TreeSitterPlugin` for code files and to the regex parsers for non-code files).
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create extract-structure.mjs**
|
||||||
|
|
||||||
|
The script:
|
||||||
|
1. Accepts input JSON path (arg 1) and output JSON path (arg 2)
|
||||||
|
2. Input format matches what file-analyzer.md already specifies: `{ projectRoot, batchFiles: [{path, language, sizeLines, fileCategory}], batchImportData }`
|
||||||
|
3. Resolves `@understand-anything/core` from the plugin's own `node_modules` using `createRequire` relative to the script's own location (two directories up to plugin root)
|
||||||
|
4. Creates a `PluginRegistry` with `TreeSitterPlugin` (all builtin language configs) + all non-code parsers registered
|
||||||
|
5. For each file: reads content, calls `registry.analyzeFile()`, formats output to match the existing script output schema (functions, classes, exports, sections, definitions, services, etc.)
|
||||||
|
6. For code files with tree-sitter support: also extracts call graph via `plugin.extractCallGraph()`
|
||||||
|
7. For files where no plugin exists (Swift, Kotlin, unknown languages): outputs `{ path, language, fileCategory, totalLines, nonEmptyLines, metrics }` with empty structural data — the LLM agent handles these in Phase 2
|
||||||
|
8. Writes output JSON matching the existing `scriptCompleted/filesAnalyzed/filesSkipped/results` schema
|
||||||
|
|
||||||
|
Key resolution logic (with fallback for different install layouts):
|
||||||
|
```javascript
|
||||||
|
import { createRequire } from 'node:module';
|
||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const pluginRoot = resolve(__dirname, '../..');
|
||||||
|
const require = createRequire(resolve(pluginRoot, 'package.json'));
|
||||||
|
|
||||||
|
let core;
|
||||||
|
try {
|
||||||
|
core = await import(require.resolve('@understand-anything/core'));
|
||||||
|
} catch {
|
||||||
|
// Fallback: direct path for installed plugin cache where pnpm symlinks may differ
|
||||||
|
core = await import(resolve(pluginRoot, 'packages/core/dist/index.js'));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Test the script locally**
|
||||||
|
|
||||||
|
Create a small test input JSON with a TS file, a Python file, and a YAML file. Run:
|
||||||
|
```bash
|
||||||
|
node skills/understand/extract-structure.mjs test-input.json test-output.json
|
||||||
|
```
|
||||||
|
Verify the output contains structural data for all three.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add skills/understand/extract-structure.mjs
|
||||||
|
git commit -m "feat: add bundled tree-sitter extraction script for file-analyzer agent"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 14: Rewrite file-analyzer.md Phase 1 to use bundled script
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `agents/file-analyzer.md`
|
||||||
|
|
||||||
|
**Context:** Phase 1 currently has ~150 lines instructing the agent to write a custom extraction script from scratch. Replace this with a short section that tells the agent to execute the pre-built `extract-structure.mjs` script.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace Phase 1 in file-analyzer.md**
|
||||||
|
|
||||||
|
Delete the entire current Phase 1 (~150 lines of regex script generation instructions). Replace with:
|
||||||
|
|
||||||
|
1. Tell the agent to prepare the input JSON file (same format as before):
|
||||||
|
```bash
|
||||||
|
cat > $PROJECT_ROOT/.understand-anything/tmp/ua-file-analyzer-input-<batchIndex>.json << 'ENDJSON'
|
||||||
|
{
|
||||||
|
"projectRoot": "<project-root>",
|
||||||
|
"batchFiles": [<this batch's files including fileCategory>],
|
||||||
|
"batchImportData": <batchImportData JSON>
|
||||||
|
}
|
||||||
|
ENDJSON
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Execute the bundled script:
|
||||||
|
```bash
|
||||||
|
node <SKILL_DIR>/extract-structure.mjs \
|
||||||
|
$PROJECT_ROOT/.understand-anything/tmp/ua-file-analyzer-input-<batchIndex>.json \
|
||||||
|
$PROJECT_ROOT/.understand-anything/tmp/ua-file-extract-results-<batchIndex>.json
|
||||||
|
```
|
||||||
|
|
||||||
|
3. If the script exits non-zero, read stderr, diagnose and report the error. Do NOT fall back to writing a manual script — the bundled script is the sole extraction path.
|
||||||
|
|
||||||
|
4. Keep the existing output format — Phase 2 (semantic analysis) is unchanged.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update SKILL.md to pass SKILL_DIR to file-analyzer dispatch**
|
||||||
|
|
||||||
|
In SKILL.md Phase 2, the file-analyzer dispatch prompt must include the skill directory path so the agent can locate `extract-structure.mjs`.
|
||||||
|
|
||||||
|
Add to the dispatch parameters:
|
||||||
|
```
|
||||||
|
> Skill directory (for bundled scripts): `<SKILL_DIR>`
|
||||||
|
```
|
||||||
|
|
||||||
|
This follows the established pattern — SKILL.md already passes `<SKILL_DIR>` for `merge-batch-graphs.py` (line 213) and `merge-subdomain-graphs.py` (line 44) using the same mechanism.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify the file-analyzer output format is unchanged**
|
||||||
|
|
||||||
|
Phase 2 of file-analyzer.md should NOT need changes — it reads the same JSON structure from the script results. Verify the output schema from `extract-structure.mjs` matches what Phase 2 expects.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add agents/file-analyzer.md skills/understand/SKILL.md
|
||||||
|
git commit -m "feat: file-analyzer uses bundled tree-sitter script instead of LLM-generated regex"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 15: Final integration verification and cleanup
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add exports to packages/core/src/index.ts**
|
||||||
|
|
||||||
|
This is required — `extract-structure.mjs` and external consumers need these exports:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export type { LanguageExtractor } from "./plugins/extractors/types.js";
|
||||||
|
export { builtinExtractors } from "./plugins/extractors/index.js";
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Build the full package**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @understand-anything/core build
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run full test suite one final time**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @understand-anything/core test
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Final commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "feat: complete language extractor architecture — 10 languages with tree-sitter support"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
**Test file convention:** Each language extractor gets its own test file at `packages/core/src/plugins/extractors/__tests__/<language>-extractor.test.ts`. This follows the existing pattern where `tree-sitter-plugin.test.ts` is co-located.
|
||||||
|
|
||||||
|
**Lazy grammar loading (future optimization):** The current `TreeSitterPlugin.init()` loads all grammar WASMs upfront via `Promise.all`. With 10 grammars (~12MB total WASM), this may cause noticeable init delay. A future improvement: load TS/JS eagerly (most common), defer others to first use. Not required for this PR — measure first.
|
||||||
|
|
||||||
|
**Fingerprint side effect:** `buildFingerprintStore` in `fingerprint.ts` uses `PluginRegistry.analyzeFile` internally. Once the new extractors are wired up, fingerprinting for Python/Go/Rust/etc. will automatically produce structural fingerprints instead of content-hash-only. No code changes needed — it happens for free.
|
||||||
|
|
||||||
|
**PHP grammar note:** `tree-sitter-php` ships both `tree-sitter-php.wasm` (full PHP + embedded HTML/CSS/JS) and `tree-sitter-php_only.wasm` (PHP only). We use `tree-sitter-php.wasm`. The PHP extractor should be robust to non-PHP AST nodes that appear when parsing files with embedded HTML templates.
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
# Understand Anything — Design & Implementation Plan
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
AI coding tools have made writing code easy, but understanding code remains hard. Junior developers, non-programmers (PMs, designers), and even experienced devs working in unfamiliar languages struggle to comprehend codebases they didn't write — or that AI wrote for them. The only entity that "understands" the code is the AI itself.
|
||||||
|
|
||||||
|
**Understand Anything** bridges this gap: an open-source tool that combines LLM intelligence with static analysis to produce an interactive, multi-persona dashboard for understanding any codebase. It runs as a Claude Code skill (leveraging the active session) and serves a rich web dashboard.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture: Monorepo with Shared Core
|
||||||
|
|
||||||
|
```
|
||||||
|
understand-anything/
|
||||||
|
├── packages/
|
||||||
|
│ ├── core/ # Shared analysis engine
|
||||||
|
│ │ ├── analyzer/ # LLM + tree-sitter analysis
|
||||||
|
│ │ ├── graph/ # Knowledge graph builder & schema
|
||||||
|
│ │ ├── plugins/ # Plugin system for language analyzers
|
||||||
|
│ │ └── persistence/ # JSON read/write, staleness detection
|
||||||
|
│ ├── skill/ # Claude Code skill (5 commands)
|
||||||
|
│ └── dashboard/ # React + TypeScript multi-panel workspace
|
||||||
|
├── plugins/ # Built-in language analyzer plugins
|
||||||
|
│ └── tree-sitter/ # Tree-sitter based multi-language analyzer
|
||||||
|
├── docs/
|
||||||
|
│ └── plans/
|
||||||
|
├── package.json # Monorepo root (pnpm workspaces)
|
||||||
|
├── tsconfig.json
|
||||||
|
└── .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key decisions:**
|
||||||
|
- **Monorepo** (pnpm workspaces) — skill and dashboard share the core analysis engine
|
||||||
|
- **JSON interchange** — knowledge graph is a JSON file, readable by both skill and dashboard
|
||||||
|
- **Committable + auto-sync** — graph persists in `.understand-anything/`, can be committed to git, auto-detects staleness via git diff
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Knowledge Graph Schema
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface KnowledgeGraph {
|
||||||
|
version: string;
|
||||||
|
project: ProjectMeta;
|
||||||
|
nodes: GraphNode[];
|
||||||
|
edges: GraphEdge[];
|
||||||
|
layers: Layer[];
|
||||||
|
tour: TourStep[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProjectMeta {
|
||||||
|
name: string;
|
||||||
|
languages: string[];
|
||||||
|
frameworks: string[];
|
||||||
|
description: string; // LLM-generated project summary
|
||||||
|
analyzedAt: string; // ISO timestamp
|
||||||
|
gitCommitHash: string; // For staleness detection
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GraphNode {
|
||||||
|
id: string;
|
||||||
|
type: "file" | "function" | "class" | "module" | "concept";
|
||||||
|
name: string;
|
||||||
|
filePath?: string;
|
||||||
|
lineRange?: [number, number];
|
||||||
|
summary: string; // Plain-English description
|
||||||
|
tags: string[]; // Searchable tags
|
||||||
|
complexity: "simple" | "moderate" | "complex";
|
||||||
|
languageNotes?: string; // Language-specific explanations
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GraphEdge {
|
||||||
|
source: string;
|
||||||
|
target: string;
|
||||||
|
type: EdgeType;
|
||||||
|
direction: "forward" | "backward" | "bidirectional";
|
||||||
|
description?: string;
|
||||||
|
weight: number; // 0-1 importance
|
||||||
|
}
|
||||||
|
|
||||||
|
type EdgeType =
|
||||||
|
// Structural
|
||||||
|
| "imports" | "exports" | "contains" | "inherits" | "implements"
|
||||||
|
// Behavioral
|
||||||
|
| "calls" | "subscribes" | "publishes" | "middleware"
|
||||||
|
// Data flow
|
||||||
|
| "reads_from" | "writes_to" | "transforms" | "validates"
|
||||||
|
// Dependencies
|
||||||
|
| "depends_on" | "tested_by" | "configures"
|
||||||
|
// Semantic
|
||||||
|
| "related" | "similar_to";
|
||||||
|
|
||||||
|
interface Layer {
|
||||||
|
id: string;
|
||||||
|
name: string; // e.g., "API Layer", "Data Layer"
|
||||||
|
description: string;
|
||||||
|
nodeIds: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TourStep {
|
||||||
|
order: number;
|
||||||
|
title: string;
|
||||||
|
description: string; // Markdown explanation
|
||||||
|
nodeIds: string[]; // Nodes to highlight
|
||||||
|
languageLesson?: string; // Optional language concept explanation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dashboard: Multi-Panel Workspace (React + TypeScript)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 🔍 Natural Language Search: "communication layer" │
|
||||||
|
├──────────────────────┬──────────────────────────────────┤
|
||||||
|
│ │ │
|
||||||
|
│ GRAPH VIEW │ CODE VIEWER │
|
||||||
|
│ (React Flow) │ (Monaco Editor, read-only) │
|
||||||
|
│ │ │
|
||||||
|
│ Interactive node │ Source code + syntax highlight │
|
||||||
|
│ graph. Click to │ LLM annotations inline. │
|
||||||
|
│ select. Search │ │
|
||||||
|
│ highlights. │ │
|
||||||
|
├──────────────────────┼──────────────────────────────────┤
|
||||||
|
│ │ │
|
||||||
|
│ CHAT PANEL │ LEARN PANEL │
|
||||||
|
│ │ │
|
||||||
|
│ Context-aware Q&A │ Tour mode + Contextual mode │
|
||||||
|
│ about selected │ Language lessons in context │
|
||||||
|
│ nodes / project. │ of YOUR code. │
|
||||||
|
│ │ │
|
||||||
|
└──────────────────────┴──────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tech stack:**
|
||||||
|
- React 18 + TypeScript + Vite
|
||||||
|
- React Flow — graph visualization (built for node graphs, better than raw D3 for this)
|
||||||
|
- Monaco Editor — code viewer with syntax highlighting (same as VS Code)
|
||||||
|
- TailwindCSS — styling
|
||||||
|
- Zustand — state management (lightweight, no boilerplate)
|
||||||
|
|
||||||
|
**Persona modes:**
|
||||||
|
- Non-technical: High-level concept nodes, code viewer hidden, learn panel expanded
|
||||||
|
- Junior dev: All panels, learn panel prominent, complexity indicators
|
||||||
|
- Experienced dev: Code viewer prominent, chat panel for deep dives
|
||||||
|
|
||||||
|
**Natural language search:**
|
||||||
|
- Searches against node `tags`, `summary`, and `name` fields
|
||||||
|
- Uses embedding similarity if available, falls back to keyword matching
|
||||||
|
- Highlights matching nodes in the graph, filters the list
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Claude Code Skill Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/understand` | Full analysis (or incremental update if graph exists) + open dashboard |
|
||||||
|
| `/understand-chat "<query>"` | In-terminal Q&A using the knowledge graph |
|
||||||
|
| `/understand-diff` | Analyze current PR/diff — explain changes, affected areas, risks |
|
||||||
|
| `/understand-explain <path>` | Deep-dive explanation of a specific file or function |
|
||||||
|
| `/understand-onboard` | Generate structured onboarding guide for new team members |
|
||||||
|
|
||||||
|
**LLM strategy:**
|
||||||
|
- Inside Claude Code → uses the active Claude session (zero extra cost)
|
||||||
|
- Standalone dashboard → users provide Claude API key for chat features
|
||||||
|
- Graph browsing, search, and learn mode work offline (pre-generated data)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Persistence & Staleness Detection
|
||||||
|
|
||||||
|
```
|
||||||
|
.understand-anything/
|
||||||
|
├── knowledge-graph.json # The full graph (committable)
|
||||||
|
├── meta.json # Analysis metadata
|
||||||
|
│ {
|
||||||
|
│ "lastAnalyzedAt": "2026-03-14T...",
|
||||||
|
│ "gitCommitHash": "abc123",
|
||||||
|
│ "version": "1.0.0",
|
||||||
|
│ "analyzedFiles": 47
|
||||||
|
│ }
|
||||||
|
├── cache/ # Per-file analysis cache
|
||||||
|
│ ├── src__index.ts.json
|
||||||
|
│ └── src__auth__login.ts.json
|
||||||
|
└── tours/
|
||||||
|
└── default-tour.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Auto-sync flow:**
|
||||||
|
1. Skill starts → reads `meta.json` → gets last analyzed commit hash
|
||||||
|
2. Runs `git diff <last-hash>..HEAD --name-only` → gets changed files
|
||||||
|
3. If no changes → serves existing graph
|
||||||
|
4. If changes → re-analyzes only changed files → merges into existing graph → updates meta
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plugin System
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface AnalyzerPlugin {
|
||||||
|
name: string;
|
||||||
|
languages: string[];
|
||||||
|
analyzeFile(filePath: string, content: string): StructuralAnalysis;
|
||||||
|
resolveImports(filePath: string, content: string): ImportResolution[];
|
||||||
|
extractCallGraph?(filePath: string, content: string): CallGraphEntry[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Day 1: tree-sitter plugin** — uses `node-tree-sitter` with language grammars for:
|
||||||
|
- TypeScript/JavaScript, Python, Go, Java, Rust, C/C++
|
||||||
|
- Extracts: function/class boundaries, import/export statements, call sites
|
||||||
|
- Combined with LLM analysis for semantic understanding
|
||||||
|
|
||||||
|
**Future: community plugins** for language-specific deep analysis.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Phases
|
||||||
|
|
||||||
|
### Phase 1: Foundation (MVP)
|
||||||
|
1. Project scaffolding — monorepo, TypeScript config, build setup
|
||||||
|
2. Core: Knowledge graph schema + JSON persistence
|
||||||
|
3. Core: LLM analysis engine (file-by-file analysis using prompts)
|
||||||
|
4. Core: tree-sitter integration for structural analysis
|
||||||
|
5. Skill: `/understand` command — analyze + persist graph
|
||||||
|
6. Dashboard: Basic React app that reads and renders the graph
|
||||||
|
7. Dashboard: Graph view with React Flow
|
||||||
|
8. Dashboard: Code viewer with Monaco Editor
|
||||||
|
|
||||||
|
### Phase 2: Intelligence
|
||||||
|
9. Natural language search across graph nodes
|
||||||
|
10. Skill: `/understand-chat` — terminal Q&A
|
||||||
|
11. Dashboard: Chat panel with context-aware Q&A
|
||||||
|
12. Staleness detection + incremental updates
|
||||||
|
13. Layer auto-detection (group nodes into logical layers)
|
||||||
|
|
||||||
|
### Phase 3: Learn Mode
|
||||||
|
14. Tour generation — guided project walkthrough
|
||||||
|
15. Contextual explanations — click-to-explain
|
||||||
|
16. Language-specific lessons in context of the user's code
|
||||||
|
17. Persona modes (non-technical / junior / experienced)
|
||||||
|
|
||||||
|
### Phase 4: Advanced
|
||||||
|
18. Skill: `/understand-diff` — PR/diff analysis
|
||||||
|
19. Skill: `/understand-explain` — deep-dive on specific files
|
||||||
|
20. Skill: `/understand-onboard` — onboarding guide generation
|
||||||
|
21. Community plugin system
|
||||||
|
22. Embedding-based semantic search (optional enhancement)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
### How to test end-to-end:
|
||||||
|
1. **Skill analysis**: Run `/understand` on a sample project → verify `.understand-anything/knowledge-graph.json` is generated with correct schema
|
||||||
|
2. **Incremental update**: Modify a file → run `/understand` again → verify only the changed file is re-analyzed
|
||||||
|
3. **Dashboard**: Open `http://localhost:5173` → verify graph renders, nodes are clickable, search works
|
||||||
|
4. **Chat**: Ask a question in the chat panel → verify it returns a relevant answer using the knowledge graph
|
||||||
|
5. **Learn mode**: Start the tour → verify it walks through the project step by step
|
||||||
|
6. **Tree-sitter**: Analyze a TypeScript file → verify function boundaries and import relationships match the actual code
|
||||||
|
|
||||||
|
### Test projects to validate against:
|
||||||
|
- A small TypeScript project (the tool itself)
|
||||||
|
- A Python Flask/Django API
|
||||||
|
- A Go microservice
|
||||||
|
- A mixed-language monorepo
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# Understand Anything — Project Homepage Design
|
||||||
|
|
||||||
|
**Date**: 2026-03-15
|
||||||
|
**Goal**: Attract new users to the Understand Anything Claude Code plugin
|
||||||
|
**Approach**: "The Reveal" — cinematic scroll-driven single-page site
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Astro** (static site generator, zero JS framework overhead)
|
||||||
|
- **Self-hosted fonts** (no Google Fonts CDN dependency — works in China)
|
||||||
|
- **CSS** with variables matching dashboard theme
|
||||||
|
- **Vanilla JS** for `IntersectionObserver` scroll animations
|
||||||
|
- **GitHub Actions** for CI/CD to `gh-pages` branch
|
||||||
|
|
||||||
|
## Source & Deployment
|
||||||
|
|
||||||
|
- Source: `homepage/` directory on `main` branch
|
||||||
|
- Build output: deployed to `gh-pages` branch via GitHub Actions
|
||||||
|
- URL: `understand-anything.com`
|
||||||
|
|
||||||
|
## Page Structure (scroll order)
|
||||||
|
|
||||||
|
### 1. Nav Bar
|
||||||
|
Minimal floating nav. Logo/wordmark left, GitHub star button + "Get Started" CTA right. Transparent, becomes solid on scroll.
|
||||||
|
|
||||||
|
### 2. Hero (full viewport)
|
||||||
|
- Headline: **"Understand Any Codebase"**
|
||||||
|
- Subheadline: "Turn 200,000 lines of code into an interactive knowledge graph you can explore, search, and learn from — powered by multi-agent AI analysis."
|
||||||
|
- CTA: "Get Started" (gold button, scrolls to install section)
|
||||||
|
- Secondary: "View on GitHub" (text link)
|
||||||
|
- Background: `hero.jpg` with dark gradient overlay
|
||||||
|
|
||||||
|
### 3. Dashboard Showcase
|
||||||
|
- Label: "See your codebase come alive"
|
||||||
|
- `overview.png` in a stylized browser frame with gold glow shadow
|
||||||
|
- Fade-in on scroll
|
||||||
|
|
||||||
|
### 4. Feature Cards (3 columns)
|
||||||
|
Staggered fade-in animation:
|
||||||
|
1. **Interactive Knowledge Graph** — "Visualize files, functions, and dependencies as an explorable graph with smart layout."
|
||||||
|
2. **Plain-English Summaries** — "Every node explained in language anyone can understand — from junior devs to product managers."
|
||||||
|
3. **Guided Tours** — "AI-generated walkthroughs that teach you the codebase step by step."
|
||||||
|
|
||||||
|
### 5. Install CTA
|
||||||
|
- Headline: "Get started in 30 seconds"
|
||||||
|
- Code block:
|
||||||
|
```
|
||||||
|
/plugin marketplace add Lum1104/Understand-Anything
|
||||||
|
/plugin install understand-anything
|
||||||
|
/understand
|
||||||
|
```
|
||||||
|
- "Works with Claude Code" note
|
||||||
|
|
||||||
|
### 6. Footer
|
||||||
|
- "Understand Anything" wordmark
|
||||||
|
- GitHub link, license
|
||||||
|
- "Built as a Claude Code plugin"
|
||||||
|
|
||||||
|
## Visual Design System
|
||||||
|
|
||||||
|
### Colors (matching dashboard)
|
||||||
|
| Token | Value | Usage |
|
||||||
|
|-------|-------|-------|
|
||||||
|
| `--bg` | `#0a0a0a` | Page background |
|
||||||
|
| `--surface` | `#141414` | Card backgrounds |
|
||||||
|
| `--border` | `#1a1a1a` | Borders, dividers |
|
||||||
|
| `--accent` | `#d4a574` | Gold/amber primary accent |
|
||||||
|
| `--text` | `#e8e2d8` | Primary text (warm white) |
|
||||||
|
| `--text-muted` | `#8a8578` | Secondary text |
|
||||||
|
|
||||||
|
### Typography (self-hosted, with fallbacks)
|
||||||
|
- **Headings**: DM Serif Display → Georgia, "Times New Roman", serif
|
||||||
|
- **Body**: Inter → -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif
|
||||||
|
- **Code**: JetBrains Mono → "SF Mono", "Cascadia Code", "Fira Code", monospace
|
||||||
|
- Hero headline: ~4rem serif with subtle text-shadow glow
|
||||||
|
|
||||||
|
### Effects
|
||||||
|
- Gold glow on dashboard screenshot frame (`box-shadow` with gold at low opacity)
|
||||||
|
- Subtle noise texture overlay (SVG, matching dashboard)
|
||||||
|
- Scroll-triggered fade+slide-up animations (CSS `@keyframes` + `IntersectionObserver`)
|
||||||
|
- CTA button: gold background with hover glow pulse
|
||||||
|
- Cards: glass-morphism with `backdrop-filter: blur`
|
||||||
|
- Responsive: 768px (tablet), 480px (mobile)
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
# Multi-Platform Skill Support — Simplified Design
|
||||||
|
|
||||||
|
**Date**: 2026-03-18
|
||||||
|
**Status**: Approved
|
||||||
|
**Goal**: Make Understand-Anything skills work across Codex, OpenClaw, OpenCode, and Cursor with zero build step — same files everywhere.
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
Follows the [obra/superpowers](https://github.com/obra/superpowers) pattern:
|
||||||
|
1. **Same files, all platforms** — no template markers, no build step, no platform-specific variants
|
||||||
|
2. **`model: inherit`** — agents use the parent session's model, making them platform-agnostic
|
||||||
|
3. **AI-driven installation** — `.{platform}/INSTALL.md` files that the AI agent reads and executes
|
||||||
|
4. **Self-contained skills** — pipeline prompt templates live inside the skill directory, not in a separate `agents/` folder
|
||||||
|
|
||||||
|
## Change 1: Move Pipeline Agents Into Skill
|
||||||
|
|
||||||
|
The 5 pipeline agents (project-scanner, file-analyzer, architecture-analyzer, tour-builder, graph-reviewer) are used exclusively by the `/understand` skill. They become prompt templates co-located with the skill:
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```
|
||||||
|
agents/
|
||||||
|
project-scanner.md # agent definition
|
||||||
|
file-analyzer.md
|
||||||
|
architecture-analyzer.md
|
||||||
|
tour-builder.md
|
||||||
|
graph-reviewer.md
|
||||||
|
skills/understand/
|
||||||
|
SKILL.md # dispatches named agents
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```
|
||||||
|
skills/understand/
|
||||||
|
SKILL.md # dispatches subagents using templates
|
||||||
|
project-scanner-prompt.md # prompt template (no agent frontmatter)
|
||||||
|
file-analyzer-prompt.md
|
||||||
|
architecture-analyzer-prompt.md
|
||||||
|
tour-builder-prompt.md
|
||||||
|
graph-reviewer-prompt.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The prompt template files retain the full instruction content but drop the agent frontmatter (`name`, `tools`, `model`). The `SKILL.md` dispatch changes from "Dispatch the **project-scanner** agent" to "Dispatch a subagent using the template at `./project-scanner-prompt.md`".
|
||||||
|
|
||||||
|
### Context Cost
|
||||||
|
|
||||||
|
Reading templates through the main session adds ~11K tokens total (~5.5% of 200K context). This is sequential (one template at a time), and context compression reclaims earlier content. Acceptable trade-off for portability.
|
||||||
|
|
||||||
|
## Change 2: New Registered Agent — knowledge-graph-guide
|
||||||
|
|
||||||
|
Create a reusable agent that any skill or user can invoke to work with knowledge graphs:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# agents/knowledge-graph-guide.md
|
||||||
|
---
|
||||||
|
name: knowledge-graph-guide
|
||||||
|
description: |
|
||||||
|
Use this agent when users need help understanding, querying, or working
|
||||||
|
with an Understand-Anything knowledge graph. Guides users through graph
|
||||||
|
structure, node/edge relationships, layer architecture, tours, and
|
||||||
|
dashboard usage.
|
||||||
|
model: inherit
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
This agent knows:
|
||||||
|
- The KnowledgeGraph JSON schema (nodes, edges, layers, tours)
|
||||||
|
- The 5 node types and 18 edge types
|
||||||
|
- How to navigate and query the graph
|
||||||
|
- How to use the interactive dashboard
|
||||||
|
- How to interpret architectural layers and guided tours
|
||||||
|
|
||||||
|
## Change 3: Platform Installation Files
|
||||||
|
|
||||||
|
Each platform gets an `INSTALL.md` that the AI agent can fetch and follow:
|
||||||
|
|
||||||
|
| File | Platform | Install Mechanism |
|
||||||
|
|------|----------|-------------------|
|
||||||
|
| `.codex/INSTALL.md` | Codex | `git clone` + symlink to `~/.agents/skills/` |
|
||||||
|
| `.opencode/INSTALL.md` | OpenCode | Plugin config in `opencode.json` |
|
||||||
|
| `.openclaw/INSTALL.md` | OpenClaw | `git clone` + symlink to `~/.openclaw/skills/` |
|
||||||
|
| `.cursor/INSTALL.md` | Cursor | `git clone` + symlink to `.cursor/plugins/` |
|
||||||
|
|
||||||
|
User tells the agent one line:
|
||||||
|
```
|
||||||
|
Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/understand-anything-plugin/.codex/INSTALL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The agent executes the clone + symlink/config automatically.
|
||||||
|
|
||||||
|
## Change 4: README Update
|
||||||
|
|
||||||
|
Add a "Multi-Platform Installation" section to README.md with one-liner per platform.
|
||||||
|
|
||||||
|
## File Summary
|
||||||
|
|
||||||
|
| Action | Files |
|
||||||
|
|--------|-------|
|
||||||
|
| Delete | `agents/project-scanner.md`, `agents/file-analyzer.md`, `agents/architecture-analyzer.md`, `agents/tour-builder.md`, `agents/graph-reviewer.md` |
|
||||||
|
| Create | `skills/understand/project-scanner-prompt.md`, `skills/understand/file-analyzer-prompt.md`, `skills/understand/architecture-analyzer-prompt.md`, `skills/understand/tour-builder-prompt.md`, `skills/understand/graph-reviewer-prompt.md` |
|
||||||
|
| Create | `agents/knowledge-graph-guide.md` |
|
||||||
|
| Create | `.codex/INSTALL.md`, `.opencode/INSTALL.md`, `.openclaw/INSTALL.md`, `.cursor/INSTALL.md` |
|
||||||
|
| Modify | `skills/understand/SKILL.md` (dispatch references) |
|
||||||
|
| Modify | `README.md` (multi-platform section) |
|
||||||
|
|
||||||
|
## What We Don't Need
|
||||||
|
|
||||||
|
- ~~`platforms/platform-config.json`~~ — same files everywhere
|
||||||
|
- ~~`platforms/build.mjs`~~ — no build step
|
||||||
|
- ~~`{{MARKER}}` template markers~~ — no templating
|
||||||
|
- ~~`scripts/install-*.sh`~~ — AI agent follows INSTALL.md
|
||||||
|
- ~~`dist-platforms/`~~ — no generated output
|
||||||
|
|
||||||
|
## Platform Compatibility
|
||||||
|
|
||||||
|
| Platform | Install Method | Agent Discovery | Skill Discovery |
|
||||||
|
|----------|---------------|-----------------|-----------------|
|
||||||
|
| Claude Code | Marketplace (existing) | `agents/` dir | `skills/` dir |
|
||||||
|
| Codex | INSTALL.md → symlink | N/A (templates in skill) | `~/.agents/skills/` |
|
||||||
|
| OpenCode | INSTALL.md → plugin config | N/A (templates in skill) | Plugin auto-registers |
|
||||||
|
| OpenClaw | INSTALL.md → symlink | N/A (templates in skill) | `~/.openclaw/skills/` |
|
||||||
|
| Cursor | INSTALL.md → symlink | `agents/` dir | `.cursor/plugins/` |
|
||||||
@@ -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<string, string>; // 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<string, LanguageConfig>;
|
||||||
|
|
||||||
|
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<FileAnalysis> {
|
||||||
|
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
|
||||||
@@ -0,0 +1,415 @@
|
|||||||
|
# Theme System Design
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Add a curated theme preset system with accent color customization to the dashboard. Users select from 5 hand-designed theme presets and optionally swap the accent color within each preset from a set of 8-10 tested swatches.
|
||||||
|
|
||||||
|
### Goals
|
||||||
|
- Support 5 theme presets: Dark Gold (current), Dark Ocean, Dark Forest, Dark Rose, Light Minimal
|
||||||
|
- Allow accent color customization within each preset (curated swatches only, no free picker)
|
||||||
|
- Persist theme preference in both `localStorage` (personal) and `meta.json` (project-level)
|
||||||
|
- Maintain visual coherence — no user-breakable color combinations
|
||||||
|
- Zero-reload theme switching via CSS variable injection at runtime
|
||||||
|
|
||||||
|
### Non-Goals
|
||||||
|
- Free color picker (risk of ugly/unreadable combos)
|
||||||
|
- Per-component color overrides
|
||||||
|
- Multiple simultaneous themes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Theme Presets & Color System
|
||||||
|
|
||||||
|
### 1.1 Preset Definitions
|
||||||
|
|
||||||
|
Each preset is a complete mapping of CSS variable names to values. The 5 presets:
|
||||||
|
|
||||||
|
| Token | Dark Gold | Dark Ocean | Dark Forest | Dark Rose | Light Minimal |
|
||||||
|
|-------|-----------|------------|-------------|-----------|---------------|
|
||||||
|
| `--color-root` | `#0a0a0a` | `#0a0e14` | `#0a100a` | `#100a0a` | `#f5f3f0` |
|
||||||
|
| `--color-surface` | `#111111` | `#111820` | `#111811` | `#181111` | `#eae7e3` |
|
||||||
|
| `--color-elevated` | `#1a1a1a` | `#1a222c` | `#1a241a` | `#221a1a` | `#ffffff` |
|
||||||
|
| `--color-panel` | `#141414` | `#141c24` | `#141c14` | `#1c1414` | `#f0ede9` |
|
||||||
|
| `--color-gold`* | `#d4a574` | `#5ba4cf` | `#5ea67a` | `#cf7a8a` | `#4a6fa5` |
|
||||||
|
| `--color-gold-dim`* | `#c9a96e` | `#4e93ba` | `#4e9468` | `#b96e7e` | `#3d5f8f` |
|
||||||
|
| `--color-gold-bright`* | `#e8c49a` | `#7abce0` | `#78c492` | `#e094a4` | `#6088bf` |
|
||||||
|
| `--color-text-primary` | `#f5f0eb` | `#e8edf2` | `#ebf0eb` | `#f2e8ea` | `#1a1a1a` |
|
||||||
|
| `--color-text-secondary` | `#a39787` | `#87939f` | `#87a38f` | `#9f8790` | `#6b6b6b` |
|
||||||
|
| `--color-text-muted` | `#6b5f53` | `#536b7a` | `#536b5a` | `#6b535a` | `#a0a0a0` |
|
||||||
|
| `--color-border-subtle` | `rgba(212,165,116,0.12)` | `rgba(91,164,207,0.12)` | `rgba(94,166,122,0.12)` | `rgba(207,122,138,0.12)` | `rgba(74,111,165,0.10)` |
|
||||||
|
| `--color-border-medium` | `rgba(212,165,116,0.25)` | `rgba(91,164,207,0.25)` | `rgba(94,166,122,0.25)` | `rgba(207,122,138,0.25)` | `rgba(74,111,165,0.18)` |
|
||||||
|
|
||||||
|
*\* The CSS variable names stay as `--color-gold`, `--color-gold-dim`, `--color-gold-bright` even for non-gold themes. They represent "the accent color" generically. Renaming them to `--color-accent` is a refactor we can do, but not required — the variable name is an implementation detail invisible to users.*
|
||||||
|
|
||||||
|
**Decision: Rename `--color-gold*` to `--color-accent*`** to avoid confusion. This is a find-and-replace across the codebase with no behavioral change.
|
||||||
|
|
||||||
|
### 1.2 Glass Effects
|
||||||
|
|
||||||
|
Glass effects derive from base colors and need per-preset values:
|
||||||
|
|
||||||
|
| Token | Dark themes | Light Minimal |
|
||||||
|
|-------|-------------|---------------|
|
||||||
|
| `--glass-bg` | `rgba(20,20,20,0.8)` | `rgba(255,255,255,0.8)` |
|
||||||
|
| `--glass-bg-heavy` | `rgba(20,20,20,0.95)` | `rgba(255,255,255,0.95)` |
|
||||||
|
| `--glass-border` | `rgba(accent,0.1)` | `rgba(accent,0.08)` |
|
||||||
|
| `--glass-border-heavy` | `rgba(accent,0.15)` | `rgba(accent,0.12)` |
|
||||||
|
|
||||||
|
The `.glass` and `.glass-heavy` CSS classes will reference these variables instead of hardcoded values.
|
||||||
|
|
||||||
|
### 1.3 Scrollbar & Glow Colors
|
||||||
|
|
||||||
|
These also derive from the accent color and need to become CSS variables:
|
||||||
|
|
||||||
|
| Token | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `--scrollbar-thumb` | `rgba(accent, 0.2)` |
|
||||||
|
| `--scrollbar-thumb-hover` | `rgba(accent, 0.35)` |
|
||||||
|
| `--glow-color` | `rgba(accent, 0.4)` for node selection glow |
|
||||||
|
| `--glow-pulse` | `rgba(accent, 0.6)` for tour highlight pulse |
|
||||||
|
|
||||||
|
### 1.4 Node-Type & Diff Colors
|
||||||
|
|
||||||
|
These are **semantic** and stay fixed across all dark themes:
|
||||||
|
|
||||||
|
| Variable | Value | Purpose |
|
||||||
|
|----------|-------|---------|
|
||||||
|
| `--color-node-file` | `#4a7c9b` | File nodes |
|
||||||
|
| `--color-node-function` | `#5a9e6f` | Function nodes |
|
||||||
|
| `--color-node-class` | `#8b6fb0` | Class nodes |
|
||||||
|
| `--color-node-module` | `#c9a06c` | Module nodes |
|
||||||
|
| `--color-node-concept` | `#b07a8a` | Concept nodes |
|
||||||
|
| `--color-diff-changed` | `#e05252` | Changed nodes |
|
||||||
|
| `--color-diff-affected` | `#d4a030` | Affected nodes |
|
||||||
|
|
||||||
|
For **Light Minimal only**, these are slightly desaturated/darkened to maintain readability on light backgrounds:
|
||||||
|
|
||||||
|
| Variable | Light Minimal Value |
|
||||||
|
|----------|-------------------|
|
||||||
|
| `--color-node-file` | `#3a6a87` |
|
||||||
|
| `--color-node-function` | `#488a5b` |
|
||||||
|
| `--color-node-class` | `#755d99` |
|
||||||
|
| `--color-node-module` | `#a88a56` |
|
||||||
|
| `--color-node-concept` | `#966674` |
|
||||||
|
|
||||||
|
### 1.5 Accent Swatches
|
||||||
|
|
||||||
|
Each preset offers 8 accent color options. The first is the "native" default for that preset. Each swatch provides 3 values (accent, accent-dim, accent-bright) plus auto-derived border and glass opacities.
|
||||||
|
|
||||||
|
**Dark theme accent swatches** (shared across all 4 dark presets):
|
||||||
|
|
||||||
|
| Name | Accent | Dim | Bright |
|
||||||
|
|------|--------|-----|--------|
|
||||||
|
| Gold | `#d4a574` | `#c9a96e` | `#e8c49a` |
|
||||||
|
| Ocean | `#5ba4cf` | `#4e93ba` | `#7abce0` |
|
||||||
|
| Emerald | `#5ea67a` | `#4e9468` | `#78c492` |
|
||||||
|
| Rose | `#cf7a8a` | `#b96e7e` | `#e094a4` |
|
||||||
|
| Purple | `#9b7abf` | `#876bb0` | `#b494d4` |
|
||||||
|
| Amber | `#c9963a` | `#b5862e` | `#ddb05c` |
|
||||||
|
| Teal | `#4aab9a` | `#3d9686` | `#68c4b4` |
|
||||||
|
| Silver | `#a0a8b0` | `#8e959c` | `#b8bfc6` |
|
||||||
|
|
||||||
|
**Light Minimal accent swatches:**
|
||||||
|
|
||||||
|
| Name | Accent | Dim | Bright |
|
||||||
|
|------|--------|-----|--------|
|
||||||
|
| Indigo | `#4a6fa5` | `#3d5f8f` | `#6088bf` |
|
||||||
|
| Ocean | `#3a8ab5` | `#2e7aa0` | `#55a0cc` |
|
||||||
|
| Emerald | `#3a8a5c` | `#2e7a4e` | `#55a878` |
|
||||||
|
| Rose | `#a5566a` | `#8f4a5c` | `#bf6e82` |
|
||||||
|
| Purple | `#6b5a9e` | `#5c4d8a` | `#8474b5` |
|
||||||
|
| Amber | `#9e7a30` | `#8a6a28` | `#b5923e` |
|
||||||
|
| Teal | `#2e8a7a` | `#267a6c` | `#45a595` |
|
||||||
|
| Slate | `#5a6570` | `#4e5860` | `#6e7a85` |
|
||||||
|
|
||||||
|
### 1.6 Border & Glass Derivation
|
||||||
|
|
||||||
|
When an accent swatch is selected, borders and glass effects are auto-derived:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function deriveFromAccent(accentHex: string, isDark: boolean) {
|
||||||
|
return {
|
||||||
|
borderSubtle: `rgba(${hexToRgb(accentHex)}, ${isDark ? 0.12 : 0.10})`,
|
||||||
|
borderMedium: `rgba(${hexToRgb(accentHex)}, ${isDark ? 0.25 : 0.18})`,
|
||||||
|
glassBorder: `rgba(${hexToRgb(accentHex)}, ${isDark ? 0.1 : 0.08})`,
|
||||||
|
glassBorderHeavy: `rgba(${hexToRgb(accentHex)}, ${isDark ? 0.15 : 0.12})`,
|
||||||
|
scrollbarThumb: `rgba(${hexToRgb(accentHex)}, 0.2)`,
|
||||||
|
scrollbarThumbHover: `rgba(${hexToRgb(accentHex)}, 0.35)`,
|
||||||
|
glowColor: `rgba(${hexToRgb(accentHex)}, 0.4)`,
|
||||||
|
glowPulse: `rgba(${hexToRgb(accentHex)}, 0.6)`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Architecture & Data Flow
|
||||||
|
|
||||||
|
### 2.1 File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/dashboard/src/
|
||||||
|
themes/
|
||||||
|
types.ts # ThemePreset, AccentSwatch, ThemeConfig types
|
||||||
|
presets.ts # 5 preset definitions + accent swatch arrays
|
||||||
|
theme-engine.ts # applyTheme(), deriveFromAccent(), hexToRgb()
|
||||||
|
ThemeContext.tsx # React context + provider + useTheme() hook
|
||||||
|
components/
|
||||||
|
ThemePicker.tsx # Popover UI for preset + accent selection
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Type Definitions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// themes/types.ts
|
||||||
|
|
||||||
|
export type PresetId = 'dark-gold' | 'dark-ocean' | 'dark-forest' | 'dark-rose' | 'light-minimal';
|
||||||
|
|
||||||
|
export interface ThemePreset {
|
||||||
|
id: PresetId;
|
||||||
|
name: string; // Display name: "Dark Gold"
|
||||||
|
isDark: boolean; // true for dark themes, false for light
|
||||||
|
colors: Record<string, string>; // CSS variable name -> value (without --)
|
||||||
|
accentSwatches: AccentSwatch[];
|
||||||
|
defaultAccentId: string; // Which swatch is the native default
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccentSwatch {
|
||||||
|
id: string; // e.g. 'gold', 'ocean'
|
||||||
|
name: string; // Display name: "Gold"
|
||||||
|
accent: string; // Primary accent hex
|
||||||
|
accentDim: string; // Dimmed accent hex
|
||||||
|
accentBright: string; // Bright accent hex
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThemeConfig {
|
||||||
|
presetId: PresetId;
|
||||||
|
accentId: string; // Selected accent swatch ID
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Theme Engine
|
||||||
|
|
||||||
|
The theme engine is a pure function layer (no React dependency):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// themes/theme-engine.ts
|
||||||
|
|
||||||
|
export function applyTheme(config: ThemeConfig): void {
|
||||||
|
const preset = getPreset(config.presetId);
|
||||||
|
const accent = getAccent(preset, config.accentId);
|
||||||
|
|
||||||
|
// 1. Apply base preset colors
|
||||||
|
for (const [key, value] of Object.entries(preset.colors)) {
|
||||||
|
document.documentElement.style.setProperty(`--color-${key}`, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Override accent colors from swatch
|
||||||
|
document.documentElement.style.setProperty('--color-accent', accent.accent);
|
||||||
|
document.documentElement.style.setProperty('--color-accent-dim', accent.accentDim);
|
||||||
|
document.documentElement.style.setProperty('--color-accent-bright', accent.accentBright);
|
||||||
|
|
||||||
|
// 3. Apply derived values (borders, glass, scrollbar, glow)
|
||||||
|
const derived = deriveFromAccent(accent.accent, preset.isDark);
|
||||||
|
for (const [key, value] of Object.entries(derived)) {
|
||||||
|
document.documentElement.style.setProperty(`--${key}`, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Set data-theme attribute for any CSS-only selectors needed
|
||||||
|
document.documentElement.setAttribute('data-theme', preset.isDark ? 'dark' : 'light');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 React Context
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// themes/ThemeContext.tsx
|
||||||
|
|
||||||
|
interface ThemeContextValue {
|
||||||
|
config: ThemeConfig;
|
||||||
|
preset: ThemePreset;
|
||||||
|
setPreset: (presetId: PresetId) => void;
|
||||||
|
setAccent: (accentId: string) => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The provider:
|
||||||
|
1. On mount: resolves theme from `localStorage` > `meta.json` field in loaded graph > default (`dark-gold`)
|
||||||
|
2. Calls `applyTheme()` on every config change
|
||||||
|
3. Persists to `localStorage` on every change
|
||||||
|
4. Does NOT write to `meta.json` from the dashboard (the dashboard is read-only for meta.json; meta.json is written by the CLI/plugin side)
|
||||||
|
|
||||||
|
### 2.5 Integration with Zustand Store
|
||||||
|
|
||||||
|
The theme system is **separate from the Zustand store** — it uses its own React context. Rationale:
|
||||||
|
- Theme state is orthogonal to graph/UI state
|
||||||
|
- Theme needs to apply before the graph even loads (avoid flash of wrong theme)
|
||||||
|
- Keeps the store focused on graph interaction
|
||||||
|
|
||||||
|
The store does NOT gain any theme-related fields.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. UI Components
|
||||||
|
|
||||||
|
### 3.1 Theme Picker Button (Header)
|
||||||
|
|
||||||
|
A small palette icon button in the top header bar, positioned after existing controls (PersonaSelector, DiffToggle, etc.).
|
||||||
|
|
||||||
|
- Click opens a popover/dropdown panel
|
||||||
|
- Popover has two sections:
|
||||||
|
- **Presets**: 5 cards/buttons showing preset name + small color preview circles
|
||||||
|
- **Accent Colors**: row of 8 color circles for the active preset
|
||||||
|
- Active preset and accent are highlighted with a ring/check
|
||||||
|
- Selecting a preset instantly applies it; selecting an accent instantly applies it
|
||||||
|
- Clicking outside or pressing Escape closes the popover
|
||||||
|
|
||||||
|
### 3.2 Preset Preview
|
||||||
|
|
||||||
|
Each preset card shows:
|
||||||
|
- Name (e.g., "Dark Gold")
|
||||||
|
- 3-4 small circles showing root, surface, and accent colors as a visual preview
|
||||||
|
- Check mark or ring on the active one
|
||||||
|
|
||||||
|
### 3.3 Accent Swatch Row
|
||||||
|
|
||||||
|
- 8 small filled circles in a horizontal row
|
||||||
|
- Tooltip or label on hover showing the accent name
|
||||||
|
- Active one has a ring/border indicator
|
||||||
|
|
||||||
|
### 3.4 Transitions
|
||||||
|
|
||||||
|
When switching themes:
|
||||||
|
- CSS variables update instantly (no transition needed for most properties)
|
||||||
|
- Optionally add a subtle `transition: background-color 0.2s, color 0.2s` on `html` for a smooth feel
|
||||||
|
- No page reload required
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Persistence & Resolution
|
||||||
|
|
||||||
|
### 4.1 Storage Locations
|
||||||
|
|
||||||
|
| Location | Format | Written by | Read by |
|
||||||
|
|----------|--------|-----------|---------|
|
||||||
|
| `localStorage` key: `ua-theme` | `JSON.stringify(ThemeConfig)` | Dashboard (on every change) | Dashboard (on mount) |
|
||||||
|
| `.understand-anything/meta.json` | `{ ..., theme?: ThemeConfig }` | CLI/plugin (during analysis or explicit set) | Dashboard (on mount, as fallback) |
|
||||||
|
|
||||||
|
### 4.2 Resolution Order
|
||||||
|
|
||||||
|
```
|
||||||
|
1. localStorage('ua-theme') → user's personal preference (wins)
|
||||||
|
2. meta.json.theme → project-level default (fallback)
|
||||||
|
3. { presetId: 'dark-gold', accentId: 'gold' } → hard default
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 meta.json Schema Extension
|
||||||
|
|
||||||
|
Extend `AnalysisMeta` in `packages/core/src/types.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface AnalysisMeta {
|
||||||
|
lastAnalyzedAt: string;
|
||||||
|
gitCommitHash: string;
|
||||||
|
version: string;
|
||||||
|
analyzedFiles: number;
|
||||||
|
theme?: ThemeConfig; // NEW — optional, project-level theme preference
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Dashboard Reads meta.json Theme
|
||||||
|
|
||||||
|
The dashboard currently loads `/knowledge-graph.json` on mount. It also needs to load `/meta.json` (or the theme field can be embedded in `knowledge-graph.json`).
|
||||||
|
|
||||||
|
**Decision:** Load `/meta.json` separately — it's a small file and keeps concerns separated. The dashboard fetches `/meta.json` on mount, extracts the `theme` field if present, and uses it as fallback when `localStorage` has no theme.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Hardcoded Color Consolidation
|
||||||
|
|
||||||
|
### 5.1 Problem
|
||||||
|
|
||||||
|
Many components use hardcoded RGBA values instead of CSS variables:
|
||||||
|
- `rgba(212,165,116,0.3)` scattered in GraphView, CustomNode, etc.
|
||||||
|
- `rgba(20,20,20,0.8)` in glass effects
|
||||||
|
- `rgba(224,82,82,0.25)` in diff overlays
|
||||||
|
|
||||||
|
These won't respond to theme changes.
|
||||||
|
|
||||||
|
### 5.2 Solution
|
||||||
|
|
||||||
|
Before implementing theme switching, consolidate all hardcoded color references:
|
||||||
|
|
||||||
|
1. **Audit**: grep for hardcoded hex/rgba values in component files
|
||||||
|
2. **Replace with CSS variables**: create new variables where needed (e.g., `--edge-color`, `--edge-color-dim`)
|
||||||
|
3. **Glass classes**: update `.glass` and `.glass-heavy` in `index.css` to use variables
|
||||||
|
4. **Scrollbar**: update scrollbar styles to use variables
|
||||||
|
5. **Glow effects**: update `.node-glow`, `.diff-changed-glow`, `.diff-affected-glow` to use variables
|
||||||
|
|
||||||
|
Key hardcoded patterns to consolidate:
|
||||||
|
|
||||||
|
| Hardcoded Value | Replace With |
|
||||||
|
|-----------------|-------------|
|
||||||
|
| `rgba(212,165,116,X)` | `var(--color-accent)` with opacity modifier or dedicated variable |
|
||||||
|
| `rgba(20,20,20,0.8)` | `var(--glass-bg)` |
|
||||||
|
| `rgba(20,20,20,0.95)` | `var(--glass-bg-heavy)` |
|
||||||
|
| `color="rgba(212,165,116,0.15)"` in React Flow | Variable reference |
|
||||||
|
| Amber colors in WarningBanner | Keep as-is (semantic warning color, theme-independent) |
|
||||||
|
|
||||||
|
### 5.3 CSS Variable Rename
|
||||||
|
|
||||||
|
Rename throughout codebase:
|
||||||
|
- `--color-gold` -> `--color-accent`
|
||||||
|
- `--color-gold-dim` -> `--color-accent-dim`
|
||||||
|
- `--color-gold-bright` -> `--color-accent-bright`
|
||||||
|
- All Tailwind class usages: `text-gold` -> `text-accent`, `bg-gold` -> `bg-accent`, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Light Theme Considerations
|
||||||
|
|
||||||
|
The Light Minimal theme requires special attention:
|
||||||
|
|
||||||
|
### 6.1 Inverted Contrast
|
||||||
|
|
||||||
|
- Text is dark on light backgrounds (flipped from dark themes)
|
||||||
|
- Borders need lower opacity to avoid looking harsh
|
||||||
|
- Glass effects use white-based rgba instead of black-based
|
||||||
|
|
||||||
|
### 6.2 Node Colors
|
||||||
|
|
||||||
|
Slightly darker/desaturated variants for readability on light backgrounds (see Section 1.4).
|
||||||
|
|
||||||
|
### 6.3 data-theme Attribute
|
||||||
|
|
||||||
|
Set `data-theme="light"` on `<html>` for any styles that can't be handled purely through CSS variables (e.g., third-party component overrides, box-shadow directions).
|
||||||
|
|
||||||
|
### 6.4 React Flow
|
||||||
|
|
||||||
|
React Flow's background, minimap, and edge colors all need to respect the theme. The existing `!important` override on `.react-flow__background` already uses `var(--color-root)`, which is good. MiniMap colors in GraphView.tsx are currently hardcoded and need to be updated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Summary of Changes by Package
|
||||||
|
|
||||||
|
### packages/core
|
||||||
|
- Extend `AnalysisMeta` type with optional `theme?: ThemeConfig`
|
||||||
|
- Export `ThemeConfig` and `PresetId` types from `./types` subpath
|
||||||
|
|
||||||
|
### packages/dashboard
|
||||||
|
- New `themes/` directory with types, presets, engine, and context
|
||||||
|
- New `ThemePicker` component in header
|
||||||
|
- Rename `--color-gold*` to `--color-accent*` across all files
|
||||||
|
- Consolidate hardcoded RGBA values into CSS variables
|
||||||
|
- Update `index.css`: glass classes, scrollbar, glow effects to use variables
|
||||||
|
- Update `App.tsx`: wrap with ThemeProvider, add ThemePicker to header, fetch meta.json
|
||||||
|
- Update components with hardcoded colors: GraphView, CustomNode, LayerLegend, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Out of Scope
|
||||||
|
|
||||||
|
- Theme import/export
|
||||||
|
- Custom theme creation UI
|
||||||
|
- Per-node color customization
|
||||||
|
- Animated theme transitions beyond simple CSS transitions
|
||||||
|
- Syncing theme across browser tabs (nice-to-have for later)
|
||||||
@@ -0,0 +1,395 @@
|
|||||||
|
# Token Reduction Design
|
||||||
|
|
||||||
|
**Date:** 2026-03-27
|
||||||
|
**Status:** Draft
|
||||||
|
**Goal:** Reduce total token cost of `/understand` by ~85-90% on large codebases (200+ files)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
For large codebases, the `/understand` pipeline spends the vast majority of its tokens on **repeated context injection**. The same data is sent to every subagent independently, even when that data could be computed once and shared.
|
||||||
|
|
||||||
|
### Token cost breakdown (500-file TypeScript+React project, baseline)
|
||||||
|
|
||||||
|
| Source | Phase | Tokens (input) | % of total |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `allProjectFiles` list × 67 batches | Phase 2 | ~167,000 | ~50% |
|
||||||
|
| `file-analyzer-prompt.md` × 67 batches | Phase 2 | ~134,000 | ~40% |
|
||||||
|
| Language/framework addendums × 67 batches | Phase 2 | ~68,000 | ~20% |
|
||||||
|
| Tour builder payload (all nodes + edges) | Phase 5 | ~80,000 | ~24% |
|
||||||
|
| Graph reviewer (assembled graph + inventory) | Phase 6 | ~58,000 | ~17% |
|
||||||
|
| Architecture analyzer payload | Phase 4 | ~22,000 | ~7% |
|
||||||
|
| **Total** | | **~529,000** | |
|
||||||
|
|
||||||
|
The root cause: **Phase 2 runs 67 batches (at 5-10 files each), and every single batch receives the full 500-file list for import resolution.** The file list alone costs ~2,500 tokens × 67 repetitions = 167,000 tokens on input, doing work that is entirely redundant between batches.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Reduce total input tokens by 85%+ on a 500-file project
|
||||||
|
- No degradation in graph quality for standard projects
|
||||||
|
- Preserve the `--full` / incremental / scope flags
|
||||||
|
- Maintain backward compatibility with existing `knowledge-graph.json` output schema
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
Five changes compose the full approach (C1–C5). Each is independent and can be shipped separately, but all five are needed for the full reduction.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### C1 — Pre-resolve imports in the project scanner
|
||||||
|
|
||||||
|
**Root cause addressed:** `allProjectFiles` (the entire file list) is injected into every file-analyzer batch solely so each batch's extraction script can resolve relative imports. This is redundant: the full file list is available during Phase 1, and import resolution is deterministic. It should happen once, not 67 times.
|
||||||
|
|
||||||
|
**Change:** Extend the Phase 1 scanner script to also parse import statements from every source file and resolve relative imports against the discovered file list. The resolved results are written into `scan-result.json` as a new `importMap` field. File-analyzer batches then receive only their own batch's pre-resolved imports — not the full file list.
|
||||||
|
|
||||||
|
#### Scanner output addition
|
||||||
|
|
||||||
|
`scan-result.json` gains:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"importMap": {
|
||||||
|
"src/index.ts": ["src/utils.ts", "src/config.ts"],
|
||||||
|
"src/utils.ts": [],
|
||||||
|
"src/components/App.tsx": ["src/hooks/useAuth.ts", "src/store/index.ts"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Keys are project-relative paths (matching `files[*].path`)
|
||||||
|
- Values are resolved project-relative paths only (external/unresolvable imports are omitted)
|
||||||
|
- External imports (`node_modules`, unresolvable paths) are excluded from the map entirely
|
||||||
|
|
||||||
|
#### Scanner script additions (Phase 1 Step 8)
|
||||||
|
|
||||||
|
After the existing 7 steps, the scanner script adds a new step:
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 8 — Import Resolution
|
||||||
|
|
||||||
|
For each file in the discovered source list:
|
||||||
|
1. Read the file content
|
||||||
|
2. Extract import statements (language-specific patterns per Step 3's language detection):
|
||||||
|
- TypeScript/JavaScript: `import ... from '...'`, `require('...')`
|
||||||
|
- Python: `import ...`, `from ... import ...`
|
||||||
|
- Go: `import "..."` blocks
|
||||||
|
- Rust: `use ...` statements
|
||||||
|
- Java/Kotlin: `import ...` statements
|
||||||
|
- Ruby: `require`, `require_relative`
|
||||||
|
3. For each relative import (starts with `./` or `../`):
|
||||||
|
a. Compute the resolved path from the current file's directory
|
||||||
|
b. Normalize to project-relative format
|
||||||
|
c. Try common extension variants if the import has no extension:
|
||||||
|
`.ts`, `.tsx`, `.js`, `.jsx`, `/index.ts`, `/index.js`, `/index.tsx`
|
||||||
|
d. If any variant exists in the discovered file list, record it; otherwise skip
|
||||||
|
4. For absolute imports (no `.` prefix): skip (external package)
|
||||||
|
|
||||||
|
Output the full importMap in the JSON result.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### File-analyzer input schema change
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"projectRoot": "/path/to/project",
|
||||||
|
"allProjectFiles": ["src/index.ts", "src/utils.ts", "...500 paths..."],
|
||||||
|
"batchFiles": [
|
||||||
|
{"path": "src/index.ts", "language": "typescript", "sizeLines": 150}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"projectRoot": "/path/to/project",
|
||||||
|
"batchFiles": [
|
||||||
|
{"path": "src/index.ts", "language": "typescript", "sizeLines": 150}
|
||||||
|
],
|
||||||
|
"batchImportData": {
|
||||||
|
"src/index.ts": ["src/utils.ts", "src/config.ts"],
|
||||||
|
"src/components/App.tsx": ["src/hooks/useAuth.ts"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`allProjectFiles` is removed entirely. `batchImportData` contains only the pre-resolved imports for the files in this batch (sliced from `importMap` by the orchestrator).
|
||||||
|
|
||||||
|
#### File-analyzer extraction script change
|
||||||
|
|
||||||
|
The extraction script no longer performs import resolution. It:
|
||||||
|
- Still extracts: functions, classes, exports, metrics (unchanged)
|
||||||
|
- For imports: reads `batchImportData[file.path]` from the input JSON — no cross-referencing needed
|
||||||
|
- The `imports` array in each file result becomes: `batchImportData[file.path]` mapped to import edge objects with `resolvedPath` already populated, `isExternal: false`
|
||||||
|
|
||||||
|
#### SKILL.md Phase 2 change
|
||||||
|
|
||||||
|
Remove the `allProjectFiles` injection from the batch dispatch prompt. Replace with a per-batch `batchImportData` slice:
|
||||||
|
|
||||||
|
```
|
||||||
|
For each batch, slice importData from the importMap read in Phase 1:
|
||||||
|
batchImportData = { [file.path]: importMap[file.path] ?? [] }
|
||||||
|
for each file in this batch
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Token savings estimate
|
||||||
|
|
||||||
|
| | Batches | Tokens/batch | Total |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Before | 67 | ~2,500 (file list) | ~167,500 |
|
||||||
|
| After (C1 alone) | 67 | ~200 (batch importData) | ~13,400 |
|
||||||
|
| **Savings** | | | **~154,100** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### C2 — Increase batch size from 5-10 to 20-30 files
|
||||||
|
|
||||||
|
**Root cause addressed:** Every batch incurs the full cost of `file-analyzer-prompt.md` (~2,000 tokens) plus the batch dispatch overhead. With 67 batches, this adds up even without `allProjectFiles`. Fewer, larger batches directly reduce this repetition.
|
||||||
|
|
||||||
|
**Change:** In SKILL.md Phase 2, change the batch size guidance:
|
||||||
|
|
||||||
|
- **Before:** "Batch the file list from Phase 1 into groups of **5-10 files each**"
|
||||||
|
- **After:** "Batch the file list from Phase 1 into groups of **20-30 files each** (aim for ~25 per batch)"
|
||||||
|
|
||||||
|
Also update the concurrency limit from 3 to **5** concurrent batches. Fewer total batches means we can afford more parallelism without overwhelming the system.
|
||||||
|
|
||||||
|
#### Trade-offs
|
||||||
|
|
||||||
|
| | Smaller batches (current) | Larger batches (new) |
|
||||||
|
|---|---|---|
|
||||||
|
| Files per batch | 5-10 | 20-30 |
|
||||||
|
| Total batches (500 files) | ~67 | ~20 |
|
||||||
|
| Prompt repetition | 67× | 20× |
|
||||||
|
| Quality risk | Lower (focused) | Slightly higher (more files per subagent) |
|
||||||
|
| Concurrency | 3 | 5 |
|
||||||
|
|
||||||
|
Quality risk is low: each subagent still operates on distinct, non-overlapping file groups. The extraction script is deterministic regardless of batch size. Semantic analysis (summaries, tags) may be marginally less focused, but the quality difference is negligible in practice for well-structured files.
|
||||||
|
|
||||||
|
#### Token savings estimate (combined with C1)
|
||||||
|
|
||||||
|
| | Batches | Tokens/batch (prompt) | Total |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Before (C1 only) | 67 | ~2,000 | ~134,000 |
|
||||||
|
| After (C1+C2) | 20 | ~2,000 | ~40,000 |
|
||||||
|
| **Savings from C2** | | | **~94,000** |
|
||||||
|
|
||||||
|
C1+C2 combined eliminate ~248,000 tokens from Phase 2 (down from ~301,500 to ~53,500, a ~82% Phase 2 reduction).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### C3 — Remove language/framework addendums from file-analyzer batches
|
||||||
|
|
||||||
|
**Root cause addressed:** `languages/typescript.md` (~600 tokens) and `frameworks/react.md` (~700 tokens) are read and injected into every file-analyzer batch prompt. For a TypeScript+React project with 20 batches (after C2), this costs 20 × 1,300 = 26,000 additional tokens — and the model already has deep knowledge of these languages from training.
|
||||||
|
|
||||||
|
**Change:** Stop injecting addendum files into Phase 2 batch prompts entirely. The addendums remain injected into Phase 4 (architecture analyzer) where there is only **one** subagent call, making the cost acceptable.
|
||||||
|
|
||||||
|
Instead, add a compact "Language and Framework Hints" reference section directly into `file-analyzer-prompt.md`. This section is a distilled, one-time addition (~150 tokens total) that captures the most useful patterns from all addendums in a concise lookup table.
|
||||||
|
|
||||||
|
#### New section in `file-analyzer-prompt.md` (replace addendum injection)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Language and Framework Quick Reference
|
||||||
|
|
||||||
|
Use these hints to improve tag and edge accuracy. These supplement your training knowledge.
|
||||||
|
|
||||||
|
| Signal | Tag(s) | Note |
|
||||||
|
|---|---|---|
|
||||||
|
| File in `hooks/`, exports function starting with `use` | `hook`, `service` | React custom hook |
|
||||||
|
| File in `contexts/`, exports a Provider | `service`, `state` | React context |
|
||||||
|
| File in `pages/` or `views/` | `ui`, `routing` | Page-level component |
|
||||||
|
| File in `store/`, `slices/`, `reducers/` | `state` | State management |
|
||||||
|
| File in `services/`, `api/` | `service` | Data-fetching / API client |
|
||||||
|
| `__init__.py` with re-exports | `entry-point`, `barrel` | Python package root |
|
||||||
|
| `manage.py` at project root | `entry-point` | Django management entry |
|
||||||
|
| File named `mod.rs` | `barrel` | Rust module barrel |
|
||||||
|
| File named `main.go` in `cmd/` | `entry-point` | Go binary entry |
|
||||||
|
|
||||||
|
For React: create `depends_on` edges from components to hooks they call. Create `publishes`/`subscribes` edges for Context provider/consumer patterns.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### SKILL.md Phase 2 change
|
||||||
|
|
||||||
|
Remove steps 2 and 3 from the "Build the combined prompt template" block:
|
||||||
|
- **Remove:** Step 2 (Language context injection — read `./languages/<language-id>.md` per detected language)
|
||||||
|
- **Remove:** Step 3 (Framework addendum injection — read `./frameworks/<framework-id>.md` per detected framework)
|
||||||
|
- **Keep:** Step 1 (Read the base template at `./file-analyzer-prompt.md`)
|
||||||
|
|
||||||
|
The addendum injection steps **remain unchanged** in Phase 4 (architecture analyzer), since they run once.
|
||||||
|
|
||||||
|
#### Token savings estimate
|
||||||
|
|
||||||
|
| | Batches | Addendum tokens/batch | Total |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Before (after C2) | 20 | ~1,300 (TS+React) | ~26,000 |
|
||||||
|
| After | 20 | ~150 (inline hints) | ~3,000 |
|
||||||
|
| **Savings** | | | **~23,000** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### C4 — Slim Phase 4 and Phase 5 payloads
|
||||||
|
|
||||||
|
**Root cause addressed:** Phase 5 (tour builder) receives all nodes (file + function + class) and all edges (imports + contains + calls + exports + ...). For a 500-file project, this can include 1,500+ nodes and 3,000+ edges. Most of this data is not needed for tour design.
|
||||||
|
|
||||||
|
#### Phase 4 (Architecture Analyzer) — minor trim
|
||||||
|
|
||||||
|
Phase 4 already only sends file-type nodes, which is correct. Minor change: explicitly strip `languageNotes` from each node object in the payload (it's not useful for layer assignment and can be verbose). Also strip `name` — it is always derivable as the basename of `filePath`.
|
||||||
|
|
||||||
|
**Before per node:** `{id, name, filePath, summary, tags, complexity, languageNotes?}`
|
||||||
|
**After per node:** `{id, filePath, summary, tags}`
|
||||||
|
|
||||||
|
Savings: ~15-20% fewer tokens per node, ~3,000–5,000 tokens total for Phase 4.
|
||||||
|
|
||||||
|
#### Phase 5 (Tour Builder) — major trim
|
||||||
|
|
||||||
|
Three changes to what the orchestrator injects into the tour-builder subagent:
|
||||||
|
|
||||||
|
**1. File nodes only (strip function/class nodes)**
|
||||||
|
|
||||||
|
The tour references node IDs for wayfinding. In practice the tour always references `file:` nodes — function and class nodes are visible in the dashboard's NodeInfo sidebar once a file is selected, but the tour itself navigates at the file level.
|
||||||
|
|
||||||
|
- **Before:** all nodes (file + function + class) — for 500 files, maybe 1,500+ nodes
|
||||||
|
- **After:** file-type nodes only — 500 nodes
|
||||||
|
|
||||||
|
**2. Slim node format**
|
||||||
|
|
||||||
|
The tour builder script only uses node IDs, names, and types for graph computation. Summaries and tags are used in Phase 2 (pedagogical narrative writing). Strip heavy optional fields from the injected payload:
|
||||||
|
|
||||||
|
- **Before per node:** `{id, name, filePath, summary, type, tags, complexity, languageNotes?}`
|
||||||
|
- **After per node:** `{id, name, filePath, summary, type}` (drop tags, complexity, languageNotes)
|
||||||
|
|
||||||
|
**3. Slim edges (imports + calls only) and slim layers**
|
||||||
|
|
||||||
|
The tour's BFS traversal only traverses `imports` and `calls` edges. `contains`, `exports`, `tested_by`, `depends_on`, and other edge types add no value to the traversal and inflate the payload.
|
||||||
|
|
||||||
|
- **Before edges:** all edge types (~3,000+ edges including all `contains` edges to function/class nodes)
|
||||||
|
- **After edges:** only `imports` and `calls` edge types (~400–800 edges for typical projects)
|
||||||
|
|
||||||
|
For layers, the tour builder uses layer data only to inform the tour's narrative arc (which layer to introduce first, second, etc.). It does not need the full `nodeIds` arrays — those can be very large.
|
||||||
|
|
||||||
|
- **Before per layer:** `{id, name, description, nodeIds: [...hundreds of IDs]}`
|
||||||
|
- **After per layer:** `{id, name, description}` (drop nodeIds)
|
||||||
|
|
||||||
|
#### Token savings estimate (Phase 5)
|
||||||
|
|
||||||
|
| Data | Before | After |
|
||||||
|
|---|---|---|
|
||||||
|
| Node count | ~1,500 × ~180 chars | ~500 × ~120 chars |
|
||||||
|
| Node tokens | ~67,500 | ~15,000 |
|
||||||
|
| Edge count | ~3,000 × ~80 chars | ~600 × ~80 chars |
|
||||||
|
| Edge tokens | ~60,000 | ~12,000 |
|
||||||
|
| Layer tokens | ~5,000 | ~500 |
|
||||||
|
| **Phase 5 total** | **~132,500** | **~27,500** |
|
||||||
|
| **Savings** | | **~105,000** |
|
||||||
|
|
||||||
|
#### SKILL.md changes
|
||||||
|
|
||||||
|
In **Phase 4** dispatch prompt template, update the file node format:
|
||||||
|
```
|
||||||
|
File nodes:
|
||||||
|
[list of {id, filePath, summary, tags} for all file-type nodes]
|
||||||
|
```
|
||||||
|
|
||||||
|
In **Phase 5** dispatch prompt template, update all three payload specs:
|
||||||
|
```
|
||||||
|
Nodes (file nodes only):
|
||||||
|
[list of {id, name, filePath, summary, type} for all file-type nodes only — do NOT include function or class nodes]
|
||||||
|
|
||||||
|
Key edges (imports and calls only):
|
||||||
|
[list of edges where type is "imports" or "calls" only]
|
||||||
|
|
||||||
|
Layers:
|
||||||
|
[list of {id, name, description} — omit nodeIds]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### C5 — Gate the graph-reviewer subagent behind `--review`
|
||||||
|
|
||||||
|
**Root cause addressed:** The graph-reviewer subagent (Phase 6) reads the entire assembled graph (~500 nodes, all edges, layers, tour) and runs a LLM-powered validation. However, its Phase 1 is entirely a deterministic script, and its Phase 2 is a simple threshold decision: if `issues.length === 0`, approve. There is no LLM judgment needed for the happy path.
|
||||||
|
|
||||||
|
**Change:** By default, skip the graph-reviewer subagent. The orchestrator performs inline deterministic validation using a pre-written script. Only when `--review` is explicitly passed in `$ARGUMENTS` does the full LLM reviewer subagent run.
|
||||||
|
|
||||||
|
#### Default path (no `--review`)
|
||||||
|
|
||||||
|
In Phase 6, instead of dispatching the graph-reviewer subagent, the orchestrator:
|
||||||
|
|
||||||
|
1. Writes a compact validation script inline (embedded in SKILL.md, ~50 lines of Node.js):
|
||||||
|
- Check: every edge source/target references a real node ID
|
||||||
|
- Check: every file node appears in exactly one layer
|
||||||
|
- Check: every tour step nodeId exists
|
||||||
|
- Check: no duplicate node IDs
|
||||||
|
- Check: required fields present on nodes and edges
|
||||||
|
2. Runs the script against `assembled-graph.json`
|
||||||
|
3. If `issues.length === 0`: proceed to Phase 7 (save)
|
||||||
|
4. If `issues.length > 0`: apply the same automated fixes as before (remove dangling edges, fill defaults), then save
|
||||||
|
|
||||||
|
This is sufficient for standard runs. The LLM reviewer adds value for catching subtle quality issues (generic summaries, orphan nodes, tour step coherence) — but those are nice-to-have, not blocking.
|
||||||
|
|
||||||
|
#### `--review` path
|
||||||
|
|
||||||
|
When `--review` is in `$ARGUMENTS`, the full graph-reviewer subagent runs as it does today. No change to that code path.
|
||||||
|
|
||||||
|
#### Token savings estimate
|
||||||
|
|
||||||
|
| Path | Tokens |
|
||||||
|
|---|---|
|
||||||
|
| Current (always runs LLM reviewer) | ~58,000 input + ~500 output |
|
||||||
|
| Default (inline script, no LLM) | ~0 |
|
||||||
|
| `--review` (unchanged) | ~58,000 (same as current) |
|
||||||
|
| **Savings for default runs** | **~58,500** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Combined savings summary
|
||||||
|
|
||||||
|
| Change | Tokens before | Tokens after | Savings |
|
||||||
|
|---|---|---|---|
|
||||||
|
| C1+C2: import map + batch consolidation | ~301,500 | ~53,500 | ~248,000 |
|
||||||
|
| C3: remove addendums from batches | ~26,000 | ~3,000 | ~23,000 |
|
||||||
|
| C4: slim Phase 4+5 payloads | ~154,500 | ~33,000 | ~121,500 |
|
||||||
|
| C5: gate reviewer (default path) | ~58,500 | ~0 | ~58,500 |
|
||||||
|
| **Total** | **~540,500** | **~89,500** | **~451,000 (~83%)** |
|
||||||
|
|
||||||
|
Estimates are for a 500-file TypeScript+React project. Actual savings scale with project size — a 1,000-file project would see proportionally larger savings from C1+C2 (more batches = more repetition eliminated).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File changes
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|---|---|
|
||||||
|
| `skills/understand/project-scanner-prompt.md` | Add Step 8 (import resolution); add `importMap` to output schema |
|
||||||
|
| `skills/understand/file-analyzer-prompt.md` | Replace `allProjectFiles` with `batchImportData` in input schema; update extraction script to use pre-resolved imports; add compact Language/Framework Quick Reference section; remove addendum injection steps |
|
||||||
|
| `skills/understand/SKILL.md` | Phase 1: note importMap in scan result; Phase 2: remove addendum injection (steps 2+3), increase batch size 5-10→20-30, increase concurrency 3→5, replace `allProjectFiles` injection with `batchImportData` slice; Phase 4: slim node format in dispatch; Phase 5: file nodes only + slim edges + slim layers in dispatch; Phase 6: conditional reviewer — default inline script, `--review` flag for LLM reviewer |
|
||||||
|
| `skills/understand/architecture-analyzer-prompt.md` | No change (addendums still injected here) |
|
||||||
|
| `skills/understand/tour-builder-prompt.md` | Update input schema to reflect file-only nodes, imports+calls-only edges, slim layer format |
|
||||||
|
| `skills/understand/graph-reviewer-prompt.md` | No change (only used when `--review` flag is passed) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Risks and mitigations
|
||||||
|
|
||||||
|
| Risk | Likelihood | Mitigation |
|
||||||
|
|---|---|---|
|
||||||
|
| Scanner import resolution misses edge cases (complex re-exports, dynamic imports) | Medium | Log unresolved imports; file-analyzer still uses resolved data and creates edges only for confirmed matches. Missed imports = missing edges, which is same behavior as before for unresolvable imports |
|
||||||
|
| Larger batches (C2) reduce summary quality | Low | Summary quality is driven by the model's analysis of individual files. Batch size mainly affects how many files share one subagent's context window, not per-file quality. 20-30 files remains well within context limits |
|
||||||
|
| Stripping function/class nodes from tour (C4) breaks existing tour steps | None | Tour steps reference `file:` node IDs. No existing tour data references function/class nodes at the step level |
|
||||||
|
| Removing reviewer by default (C5) misses graph errors | Low | The inline deterministic script catches all critical structural issues (dangling refs, missing layers, duplicate IDs). The LLM reviewer's additional value is quality warnings (orphan nodes, generic summaries), which are non-blocking |
|
||||||
|
| Import map generation slows down Phase 1 | Low | The scanner script already reads all files for line counting. Import parsing adds one regex pass per file — negligible overhead |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phased rollout recommendation
|
||||||
|
|
||||||
|
Given the risk profile, implement in this order:
|
||||||
|
|
||||||
|
1. **C5 first** — gate the reviewer, lowest risk, immediate 58K token savings per run
|
||||||
|
2. **C4** — slim Phase 5 payload, no scanner changes, no quality risk
|
||||||
|
3. **C3** — remove addendums from batches, add inline hints
|
||||||
|
4. **C1+C2 together** — scanner changes and batch consolidation, test thoroughly on small/medium/large projects before releasing
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
# Understand Anything: Universal File Type Support
|
||||||
|
|
||||||
|
**Date**: 2026-03-28
|
||||||
|
**Status**: Approved
|
||||||
|
**Approach**: Big Bang — all file types in one release
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
1. Extend Understand Anything to analyze **any** file type, not just code
|
||||||
|
2. Support both holistic project enrichment (non-code files enrich code graphs) and standalone analysis (docs-only repos, SQL schema collections, IaC projects)
|
||||||
|
3. Maintain backward compatibility with existing code-only analysis
|
||||||
|
|
||||||
|
## Supported File Types (26 new)
|
||||||
|
|
||||||
|
### Documentation (3)
|
||||||
|
|
||||||
|
| Type | Extensions | Parser | Node Types |
|
||||||
|
|------|-----------|--------|------------|
|
||||||
|
| Markdown | `.md`, `.mdx` | LLM + regex heading extraction | `document` |
|
||||||
|
| reStructuredText | `.rst` | LLM | `document` |
|
||||||
|
| Plain text | `.txt` | LLM | `document` |
|
||||||
|
|
||||||
|
### Configuration (5)
|
||||||
|
|
||||||
|
| Type | Extensions | Parser | Node Types |
|
||||||
|
|------|-----------|--------|------------|
|
||||||
|
| YAML | `.yaml`, `.yml` | `yaml` npm package | `config` |
|
||||||
|
| JSON | `.json`, `.jsonc` | `JSON.parse` / `jsonc-parser` | `config`, `schema` |
|
||||||
|
| TOML | `.toml` | `@iarna/toml` or similar | `config` |
|
||||||
|
| .env | `.env`, `.env.*` | Regex line parser | `config` |
|
||||||
|
| XML | `.xml` | LLM (optionally `fast-xml-parser`) | `config` |
|
||||||
|
|
||||||
|
### Infrastructure & DevOps (7)
|
||||||
|
|
||||||
|
| Type | Extensions | Parser | Node Types |
|
||||||
|
|------|-----------|--------|------------|
|
||||||
|
| Dockerfile | `Dockerfile`, `Dockerfile.*`, `.dockerfile` | Custom instruction parser | `service`, `pipeline` |
|
||||||
|
| Docker Compose | `docker-compose.yml`, `compose.yml` | YAML parser + service extraction | `service` |
|
||||||
|
| Terraform | `.tf`, `.tfvars` | Regex block parser | `resource` |
|
||||||
|
| Kubernetes | K8s YAML (detected by `apiVersion` field) | YAML + kind detection | `service`, `resource` |
|
||||||
|
| GitHub Actions | `.github/workflows/*.yml` | YAML + job/step extraction | `pipeline` |
|
||||||
|
| Jenkinsfile | `Jenkinsfile` | LLM (Groovy DSL) | `pipeline` |
|
||||||
|
| Makefile | `Makefile`, `*.mk` | Regex target parser | `pipeline` |
|
||||||
|
|
||||||
|
### Data & Schema (6)
|
||||||
|
|
||||||
|
| Type | Extensions | Parser | Node Types |
|
||||||
|
|------|-----------|--------|------------|
|
||||||
|
| SQL | `.sql` | Simple DDL parser | `table`, `endpoint` |
|
||||||
|
| GraphQL | `.graphql`, `.gql` | Regex type/query parser | `schema`, `endpoint` |
|
||||||
|
| OpenAPI/Swagger | `openapi.yaml`, `swagger.json` | YAML/JSON + path extraction | `endpoint`, `schema` |
|
||||||
|
| Protocol Buffers | `.proto` | Regex message/service parser | `schema` |
|
||||||
|
| JSON Schema | `*.schema.json` | JSON + `$ref`/`$defs` extraction | `schema` |
|
||||||
|
| CSV/TSV | `.csv`, `.tsv` | Header row extraction | `table` |
|
||||||
|
|
||||||
|
### Shell & Scripts (3)
|
||||||
|
|
||||||
|
| Type | Extensions | Parser | Node Types |
|
||||||
|
|------|-----------|--------|------------|
|
||||||
|
| Shell | `.sh`, `.bash`, `.zsh` | Regex function parser | `file`, `function` |
|
||||||
|
| PowerShell | `.ps1`, `.psm1` | LLM | `file`, `function` |
|
||||||
|
| Batch | `.bat`, `.cmd` | LLM | `file` |
|
||||||
|
|
||||||
|
### Markup (2)
|
||||||
|
|
||||||
|
| Type | Extensions | Parser | Node Types |
|
||||||
|
|------|-----------|--------|------------|
|
||||||
|
| HTML | `.html`, `.htm` | LLM (tag structure) | `document` |
|
||||||
|
| CSS/SCSS/Less | `.css`, `.scss`, `.less` | LLM | `file` |
|
||||||
|
|
||||||
|
## Schema Extensions
|
||||||
|
|
||||||
|
### New Node Types (8)
|
||||||
|
|
||||||
|
Added to the existing `file | function | class | module | concept`:
|
||||||
|
|
||||||
|
| Node Type | Purpose | Example |
|
||||||
|
|-----------|---------|---------|
|
||||||
|
| `config` | Configuration files and key settings | `package.json`, `tsconfig.json`, env vars |
|
||||||
|
| `document` | Documentation, prose, guides | `README.md`, API docs |
|
||||||
|
| `service` | Deployable services/containers | Docker containers, K8s Deployments |
|
||||||
|
| `table` | Data tables, database objects | SQL tables, CSV datasets |
|
||||||
|
| `endpoint` | API routes, queries, mutations | REST paths, GraphQL queries |
|
||||||
|
| `pipeline` | CI/CD workflows, build steps | GitHub Actions jobs, Makefile targets |
|
||||||
|
| `schema` | Type definitions for data interchange | Protobuf messages, JSON Schema |
|
||||||
|
| `resource` | Infrastructure resources | Terraform resources, K8s ConfigMaps |
|
||||||
|
|
||||||
|
### New Edge Types (8)
|
||||||
|
|
||||||
|
Added to the existing 18 edge types:
|
||||||
|
|
||||||
|
| Edge Type | Category | Meaning | Example |
|
||||||
|
|-----------|----------|---------|---------|
|
||||||
|
| `deploys` | Infrastructure | Service deploys code | Dockerfile -> app source |
|
||||||
|
| `serves` | Infrastructure | Service exposes endpoint | K8s Service -> API endpoint |
|
||||||
|
| `migrates` | Data flow | Migration modifies table | SQL migration -> table |
|
||||||
|
| `documents` | Semantic | Doc describes code | README -> module |
|
||||||
|
| `provisions` | Infrastructure | IaC creates resource | Terraform -> AWS resource |
|
||||||
|
| `routes` | Behavioral | Routes traffic to service | nginx config -> service |
|
||||||
|
| `defines_schema` | Data flow | Defines data shape | Protobuf -> endpoint |
|
||||||
|
| `triggers` | Behavioral | Triggers pipeline/action | Git push -> GitHub Actions |
|
||||||
|
|
||||||
|
### Schema Validation Auto-Fix Aliases
|
||||||
|
|
||||||
|
New node type aliases:
|
||||||
|
- `container` -> `service`, `migration` -> `table`, `workflow` -> `pipeline`
|
||||||
|
- `route` -> `endpoint`, `doc` -> `document`, `setting` -> `config`, `infra` -> `resource`
|
||||||
|
|
||||||
|
New edge type aliases:
|
||||||
|
- `describes` -> `documents`, `creates` -> `provisions`, `exposes` -> `serves`
|
||||||
|
|
||||||
|
## Plugin Architecture Changes
|
||||||
|
|
||||||
|
### Generalized AnalyzerPlugin Interface
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface AnalyzerPlugin {
|
||||||
|
name: string;
|
||||||
|
languages: string[];
|
||||||
|
analyzeFile(filePath: string, content: string): StructuralAnalysis;
|
||||||
|
resolveImports?(filePath: string, content: string): ImportResolution[]; // Now optional
|
||||||
|
extractCallGraph?(filePath: string, content: string): CallGraphEntry[];
|
||||||
|
extractReferences?(filePath: string, content: string): ReferenceResolution[]; // NEW
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReferenceResolution {
|
||||||
|
source: string; // File making the reference
|
||||||
|
target: string; // Referenced file or identifier
|
||||||
|
type: string; // Reference type: "file", "image", "schema", "service"
|
||||||
|
line?: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extended StructuralAnalysis
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface StructuralAnalysis {
|
||||||
|
// Existing (unchanged)
|
||||||
|
functions: FunctionInfo[];
|
||||||
|
classes: ClassInfo[];
|
||||||
|
imports: ImportInfo[];
|
||||||
|
exports: ExportInfo[];
|
||||||
|
// New (all optional for backward compat)
|
||||||
|
sections?: SectionInfo[]; // Documents: headings, chapters
|
||||||
|
definitions?: DefinitionInfo[]; // Schemas: types, messages, tables
|
||||||
|
services?: ServiceInfo[]; // Infra: containers, deployments
|
||||||
|
endpoints?: EndpointInfo[]; // APIs: routes, queries
|
||||||
|
steps?: StepInfo[]; // Pipelines: jobs, stages, targets
|
||||||
|
resources?: ResourceInfo[]; // IaC: terraform resources, K8s objects
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Parsers (12)
|
||||||
|
|
||||||
|
All lightweight — mostly regex-based, minimal dependencies:
|
||||||
|
|
||||||
|
| Parser | Implementation | Extracts |
|
||||||
|
|--------|---------------|----------|
|
||||||
|
| `MarkdownParser` | Regex | Headings, links, code blocks, front matter |
|
||||||
|
| `YAMLParser` | `yaml` npm | Key hierarchy, anchors, multi-doc |
|
||||||
|
| `JSONParser` | Built-in `JSON.parse` | Key structure, `$ref`/`$defs` |
|
||||||
|
| `TOMLParser` | `@iarna/toml` | Section structure |
|
||||||
|
| `EnvParser` | Regex | Variable names and references |
|
||||||
|
| `DockerfileParser` | Regex | FROM stages, EXPOSE ports, COPY sources |
|
||||||
|
| `SQLParser` | Regex | CREATE TABLE/VIEW/INDEX, columns, foreign keys |
|
||||||
|
| `GraphQLParser` | Regex | Types, queries, mutations, subscriptions |
|
||||||
|
| `ProtobufParser` | Regex | Messages, services, enums, RPCs |
|
||||||
|
| `TerraformParser` | Regex | Resources, modules, variables, outputs |
|
||||||
|
| `MakefileParser` | Regex | Targets, dependencies, variables |
|
||||||
|
| `ShellParser` | Regex | Functions, sourced files |
|
||||||
|
|
||||||
|
## Agent Pipeline Changes
|
||||||
|
|
||||||
|
### Project Scanner
|
||||||
|
|
||||||
|
1. Scan ALL file types (remove code-only filter)
|
||||||
|
2. Tag each file with category: `code`, `config`, `docs`, `infra`, `data`, `script`, `markup`
|
||||||
|
3. Smart batch grouping: keep related files together (e.g., Dockerfile + docker-compose.yml)
|
||||||
|
|
||||||
|
### File Analyzer
|
||||||
|
|
||||||
|
Type-aware prompt templates by category:
|
||||||
|
|
||||||
|
- **Code**: Current behavior (functions, classes, imports, call graph)
|
||||||
|
- **Config**: Extract key settings, what they configure, which code files they affect
|
||||||
|
- **Documentation**: Extract sections, key concepts, which code components are documented
|
||||||
|
- **Infrastructure**: Extract services, ports, volumes, dependencies, which code they deploy
|
||||||
|
- **Data/Schema**: Extract tables, columns, types, relationships, which code consumes this data
|
||||||
|
- **Pipelines**: Extract jobs, steps, triggers, which code/infra they build/deploy
|
||||||
|
|
||||||
|
### Cross-Type Reference Resolution
|
||||||
|
|
||||||
|
Post-analysis step connecting:
|
||||||
|
- Dockerfile `COPY` -> source code directories
|
||||||
|
- CI config `run: npm test` -> test files
|
||||||
|
- K8s manifest `image:` -> Dockerfile
|
||||||
|
- SQL foreign keys -> other tables
|
||||||
|
- OpenAPI `$ref` -> schema definitions
|
||||||
|
- Markdown links -> referenced files
|
||||||
|
|
||||||
|
### Architecture Analyzer
|
||||||
|
|
||||||
|
New pattern detection:
|
||||||
|
- Deployment topology: Dockerfile -> compose -> K8s chain
|
||||||
|
- Data flow: Schema -> migration -> API endpoint -> client code
|
||||||
|
- Documentation coverage: which modules have docs vs. not
|
||||||
|
- Configuration dependency: which config files affect which code paths
|
||||||
|
|
||||||
|
### Tour Builder
|
||||||
|
|
||||||
|
Include non-code tour stops:
|
||||||
|
- Project README overview
|
||||||
|
- Dockerfile containerization
|
||||||
|
- SQL migration database schema
|
||||||
|
- CI/CD pipeline explanation
|
||||||
|
|
||||||
|
## Dashboard Visualization
|
||||||
|
|
||||||
|
### New Node Visual Styles
|
||||||
|
|
||||||
|
| Node Type | Shape | Color | Icon |
|
||||||
|
|-----------|-------|-------|------|
|
||||||
|
| `config` | Rounded rect | Teal (#5eead4) | Gear |
|
||||||
|
| `document` | Rounded rect | Sky blue (#7dd3fc) | Document |
|
||||||
|
| `service` | Hexagon | Violet (#a78bfa) | Container/Box |
|
||||||
|
| `table` | Rectangle | Emerald (#6ee7b7) | Grid |
|
||||||
|
| `endpoint` | Pill/Stadium | Orange (#fdba74) | Arrow-right |
|
||||||
|
| `pipeline` | Rounded rect | Rose (#fda4af) | Play/Workflow |
|
||||||
|
| `schema` | Diamond | Amber (#fcd34d) | Blueprint |
|
||||||
|
| `resource` | Cloud shape | Indigo (#a5b4fc) | Cloud |
|
||||||
|
|
||||||
|
### Graph Layout
|
||||||
|
|
||||||
|
1. Layer grouping by category — non-code nodes cluster separately from code nodes
|
||||||
|
2. Legend update with 8 new node types
|
||||||
|
3. Filter controls — checkboxes to show/hide each file category
|
||||||
|
|
||||||
|
### Sidebar Enhancements
|
||||||
|
|
||||||
|
NodeInfo panel updates per node type:
|
||||||
|
- **Config**: key-value pairs, referencing code files
|
||||||
|
- **Document**: heading outline, linked code components
|
||||||
|
- **Service**: ports, volumes, dependencies, deployed code
|
||||||
|
- **Table**: columns, types, foreign key relationships
|
||||||
|
- **Endpoint**: HTTP method, path, request/response schema
|
||||||
|
- **Pipeline**: jobs, triggers, deployed targets
|
||||||
|
- **Schema**: fields, nested types, consumers
|
||||||
|
- **Resource**: provider, type, dependencies
|
||||||
|
|
||||||
|
ProjectOverview panel: add "File Types" breakdown (code vs. non-code distribution).
|
||||||
|
|
||||||
|
## New Dependencies
|
||||||
|
|
||||||
|
- `yaml` — YAML parsing (already common, ~50KB)
|
||||||
|
- `@iarna/toml` — TOML parsing (~30KB)
|
||||||
|
- `jsonc-parser` — JSON with comments (~20KB)
|
||||||
|
|
||||||
|
No tree-sitter WASM additions. All other parsers are regex-based with zero dependencies.
|
||||||
|
|
||||||
|
## Backward Compatibility
|
||||||
|
|
||||||
|
- All new `StructuralAnalysis` fields are optional
|
||||||
|
- `resolveImports` becomes optional on `AnalyzerPlugin`
|
||||||
|
- Existing `LanguageConfig` entries unchanged
|
||||||
|
- Schema validation auto-fixes new type aliases
|
||||||
|
- Existing knowledge graphs remain valid (new types are additive)
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Homepage Update Design — 2026-03-29
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Update the Astro homepage (`homepage/`) to reflect features added across v1.2.0, v1.3.0, and v2.0.0 releases. The README and homepage structure/layout stay unchanged.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Three areas to update:
|
||||||
|
|
||||||
|
### 1. Features Section — Expand from 3 to 6 Cards
|
||||||
|
|
||||||
|
Current (3 cards):
|
||||||
|
- Interactive Knowledge Graph
|
||||||
|
- Plain-English Summaries
|
||||||
|
- Guided Tours
|
||||||
|
|
||||||
|
Updated (6 cards, 2 rows of 3):
|
||||||
|
|
||||||
|
| # | Title | Icon | Description |
|
||||||
|
|---|-------|------|-------------|
|
||||||
|
| 1 | Interactive Knowledge Graph | `◈` | Visualize files, functions, and dependencies as an explorable graph with hierarchical drill-down and smart layout. |
|
||||||
|
| 2 | Beyond Code Analysis | `⬡` | Analyze your entire project — Dockerfiles, Terraform, SQL, Markdown, and 26+ file types mapped into one unified graph. |
|
||||||
|
| 3 | Smart Filtering & Search | `⊘` | Filter by node type, complexity, layer, or edge category. Fuzzy and semantic search to find anything instantly. |
|
||||||
|
| 4 | Export & Share | `⎙` | Export your knowledge graph as high-quality PNG, SVG, or filtered JSON — ready for docs, presentations, or further analysis. |
|
||||||
|
| 5 | Dependency Path Finder | `⟿` | Find the shortest path between any two components. Understand how parts of your system connect at a glance. |
|
||||||
|
| 6 | Guided Tours & Onboarding | `⟐` | AI-generated walkthroughs that teach the codebase step by step, plus onboarding guides for new team members. |
|
||||||
|
|
||||||
|
### 2. Install Section
|
||||||
|
|
||||||
|
Update the note from Claude Code-only to multi-platform:
|
||||||
|
- Before: "Works with Claude Code — Anthropic's official CLI for Claude."
|
||||||
|
- After: "Works with Claude Code, Codex, OpenCode, Gemini CLI, and more."
|
||||||
|
|
||||||
|
### 3. Footer
|
||||||
|
|
||||||
|
Update tagline:
|
||||||
|
- Before: "Built as a Claude Code plugin"
|
||||||
|
- After: "Built for AI coding assistants"
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
- `homepage/src/components/Features.astro` — replace 3 cards with 6
|
||||||
|
- `homepage/src/components/Install.astro` — update platform note
|
||||||
|
- `homepage/src/components/Footer.astro` — update tagline
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- README.md updates
|
||||||
|
- Showcase section / screenshot
|
||||||
|
- Nav component
|
||||||
|
- Hero section
|
||||||
|
- Layout / global CSS structure changes
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
# Business Domain Knowledge Extraction — Design Spec
|
||||||
|
|
||||||
|
**Issue:** [#61](https://github.com/Lum1104/Understand-Anything/issues/61)
|
||||||
|
**Date:** 2026-04-01
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The current knowledge graph shows file-level dependency relationships, but this has limited value — you can already see imports in an IDE. When files are many, listing dependency edges doesn't reduce cognitive load; you still mentally reconstruct what the code *does*. What's needed is business domain knowledge: the logic and domain concepts embedded within the code, not the structural wiring.
|
||||||
|
|
||||||
|
## Solution Overview
|
||||||
|
|
||||||
|
A new `/understand-domain` skill that extracts business domain knowledge and renders it as a horizontal flow graph in the dashboard. Two viewing modes: a high-level **Domain view** (default when available) and the existing **Structural view**, with a toggle to switch between them.
|
||||||
|
|
||||||
|
## Architecture: Separate File, Shared Schema (Approach C)
|
||||||
|
|
||||||
|
Domain data lives in a **separate file** (`domain-graph.json`) using the **same `KnowledgeGraph` type system** — extended with new node/edge types. The dashboard detects both files and offers a view toggle. Domain nodes can reference structural nodes by ID for drill-down.
|
||||||
|
|
||||||
|
**Why separate files:**
|
||||||
|
- `/understand-domain` works standalone (lightweight) or alongside full graph
|
||||||
|
- Shared schema means search, validation, and filtering work for both
|
||||||
|
- No risk of polluting the structural graph
|
||||||
|
- Each file is independently valid
|
||||||
|
|
||||||
|
## Section 1: Domain Graph Schema
|
||||||
|
|
||||||
|
### Three-Level Hierarchy
|
||||||
|
|
||||||
|
1. **Business Domain** (top) — e.g., "Purchasing", "Logistics", "Warehouse Management"
|
||||||
|
2. **Business Flow** (mid) — e.g., "Create Order", "Process Refund"
|
||||||
|
3. **Business Step** (leaf) — e.g., "Validate input", "Check inventory", "Persist order"
|
||||||
|
|
||||||
|
### New Node Types (3)
|
||||||
|
|
||||||
|
| Type | Purpose | Example |
|
||||||
|
|------|---------|---------|
|
||||||
|
| `domain` | Business domain cluster | "Order Management", "Logistics" |
|
||||||
|
| `flow` | A business process within a domain | "Create Order", "Process Refund" |
|
||||||
|
| `step` | A single step in a flow | "Validate order input" |
|
||||||
|
|
||||||
|
### New Edge Types (4)
|
||||||
|
|
||||||
|
| Type | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `contains_flow` | domain → flow |
|
||||||
|
| `flow_step` | flow → step (ordered via `weight` field, e.g., 0.1, 0.2, ...) |
|
||||||
|
| `cross_domain` | domain → domain (interaction between domains) |
|
||||||
|
| `implements` | step → file/function node ID (reference into structural graph) |
|
||||||
|
|
||||||
|
### Domain Node Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// domain node
|
||||||
|
{
|
||||||
|
id: "domain:order-management",
|
||||||
|
type: "domain",
|
||||||
|
name: "Order Management",
|
||||||
|
summary: "Handles the complete order lifecycle...",
|
||||||
|
tags: ["e-commerce", "core-business"],
|
||||||
|
complexity: "complex",
|
||||||
|
domainMeta?: {
|
||||||
|
entities: ["Order", "LineItem", "OrderStatus"],
|
||||||
|
businessRules: ["Orders require inventory check before confirmation"],
|
||||||
|
crossDomainInteractions: ["Triggers Logistics on order confirmed", "Reads from Customer Service for buyer info"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flow Node Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
id: "flow:create-order",
|
||||||
|
type: "flow",
|
||||||
|
name: "Create Order",
|
||||||
|
summary: "Customer submits a new order through the API",
|
||||||
|
tags: ["write-path", "api"],
|
||||||
|
complexity: "moderate",
|
||||||
|
domainMeta?: {
|
||||||
|
entryPoint: "POST /api/orders",
|
||||||
|
entryType: "http" | "cli" | "event" | "cron" | "manual"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step Node Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
id: "step:create-order:validate-input",
|
||||||
|
type: "step",
|
||||||
|
name: "Validate order input",
|
||||||
|
summary: "Checks request body against order schema, rejects invalid payloads",
|
||||||
|
tags: ["validation"],
|
||||||
|
complexity: "simple",
|
||||||
|
filePath: "src/validators/order-validator.ts",
|
||||||
|
lineRange: [12, 45]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Output
|
||||||
|
|
||||||
|
Saved to `.understand-anything/domain-graph.json` — same `KnowledgeGraph` shape, valid on its own.
|
||||||
|
|
||||||
|
## Section 2: Analysis Pipeline
|
||||||
|
|
||||||
|
### Two Paths, Same Output
|
||||||
|
|
||||||
|
**Path 1: Lightweight scan (no existing graph)**
|
||||||
|
|
||||||
|
```
|
||||||
|
File tree scan
|
||||||
|
→ Static entry point detection (tree-sitter)
|
||||||
|
→ Route definitions, exported handlers, main(), event listeners, cron decorators
|
||||||
|
→ Feed to LLM: file tree + detected entry points + sampled file contents
|
||||||
|
→ LLM outputs: domains, flows, steps, cross-domain interactions
|
||||||
|
→ Build domain-graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Token cost: ~10-20% of a full `/understand` scan.
|
||||||
|
|
||||||
|
**Path 2: Derive from existing graph**
|
||||||
|
|
||||||
|
```
|
||||||
|
Load knowledge-graph.json
|
||||||
|
→ Extract: all nodes, edges, layers, summaries, tour
|
||||||
|
→ Feed to LLM: graph data as structured context
|
||||||
|
→ LLM outputs: domains, flows, steps, cross-domain interactions
|
||||||
|
→ Build domain-graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Very cheap — no file reading needed, LLM reasons over existing summaries and call edges.
|
||||||
|
|
||||||
|
**Path Selection:** `/understand-domain` checks if `.understand-anything/knowledge-graph.json` exists. If yes → Path 2. If no → Path 1.
|
||||||
|
|
||||||
|
### Agent Structure
|
||||||
|
|
||||||
|
One new agent: **`domain-analyzer`** (opus model). Handles both paths. For large codebases, can batch by detected entry point groups.
|
||||||
|
|
||||||
|
## Section 3: Preprocessing Script & Skill Integration
|
||||||
|
|
||||||
|
### Script: `understand-anything-plugin/skills/understand-domain/extract-domain-context.py`
|
||||||
|
|
||||||
|
Bundled with the skill (not in `scripts/` which is for development tooling). Runs before the LLM agent. Outputs `.understand-anything/intermediate/domain-context.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fileTree": ["src/api/orders.ts", "src/services/...", "..."],
|
||||||
|
"entryPoints": [
|
||||||
|
{
|
||||||
|
"file": "src/api/orders.ts",
|
||||||
|
"type": "http",
|
||||||
|
"method": "POST",
|
||||||
|
"path": "/api/orders",
|
||||||
|
"handler": "createOrder",
|
||||||
|
"lineRange": [15, 45],
|
||||||
|
"snippet": "async function createOrder(req, res) { ... }"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileSignatures": {
|
||||||
|
"src/services/order-service.ts": {
|
||||||
|
"exports": ["createOrder", "cancelOrder", "getOrderById"],
|
||||||
|
"imports": ["inventory-service", "pricing-service", "order-repo"],
|
||||||
|
"summary": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Python script (no heavy dependencies — uses `ast` for Python, regex for other languages). Uses:
|
||||||
|
- Walk the file tree (respecting `.gitignore`)
|
||||||
|
- Detect entry points by pattern: route decorators, `app.get/post`, `export default handler`, `main()`, event listeners
|
||||||
|
- Extract function signatures and import/export lists per file
|
||||||
|
- Keep code snippets short (signature + first few lines, not full bodies)
|
||||||
|
|
||||||
|
### Skill Integration
|
||||||
|
|
||||||
|
The `/understand-domain` skill markdown:
|
||||||
|
|
||||||
|
1. Runs `understand-anything-plugin/skills/understand-domain/extract-domain-context.py`
|
||||||
|
2. Checks for existing `knowledge-graph.json`
|
||||||
|
3. If exists → passes both `domain-context.json` + graph data to domain-analyzer agent
|
||||||
|
4. If not → passes only `domain-context.json`
|
||||||
|
5. Agent outputs `domain-graph.json`
|
||||||
|
6. Cleans up intermediate files
|
||||||
|
7. Auto-triggers `/understand-dashboard`
|
||||||
|
|
||||||
|
## Section 4: Dashboard — Domain View
|
||||||
|
|
||||||
|
### View Toggle
|
||||||
|
|
||||||
|
- Top-left corner: pill toggle — **"Domain" / "Structural"**
|
||||||
|
- Domain view is default when `domain-graph.json` exists
|
||||||
|
- If only one graph file exists, no toggle shown
|
||||||
|
- Switching views preserves sidebar state
|
||||||
|
|
||||||
|
### Horizontal Flow Layout
|
||||||
|
|
||||||
|
- **Layout engine:** Dagre with `rankdir: "LR"` (left-to-right)
|
||||||
|
- **Zoom levels:**
|
||||||
|
- **Zoomed out:** Domain clusters as large rounded rectangles, `cross_domain` edges between them
|
||||||
|
- **Click domain:** Expands to show flows as horizontal lanes
|
||||||
|
- **Click flow:** Shows step-by-step trace left-to-right
|
||||||
|
|
||||||
|
### Domain Cluster Rendering
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Order Management │
|
||||||
|
│ "Handles the complete order..." │
|
||||||
|
│ │
|
||||||
|
│ Entities: Order, LineItem, Status │
|
||||||
|
│ Flows: Create Order, Cancel Order │
|
||||||
|
│ Rules: "Requires inventory check" │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
──cross_domain──→ [Logistics]
|
||||||
|
```
|
||||||
|
|
||||||
|
- Gold/amber border for domain clusters (matches existing theme)
|
||||||
|
- Shows summary, entity list, flow count on the cluster face
|
||||||
|
- Cross-domain edges: thick dashed lines with labels
|
||||||
|
|
||||||
|
### Flow Trace Rendering
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/orders
|
||||||
|
┌──────────┐ ┌──────────────┐ ┌───────────┐ ┌──────────┐ ┌────────────┐
|
||||||
|
│ Validate │───→│ Check │───→│ Calculate │───→│ Persist │───→│ Send │
|
||||||
|
│ Input │ │ Inventory │ │ Pricing │ │ Order │ │ Confirm │
|
||||||
|
└──────────┘ └──────────────┘ └───────────┘ └──────────┘ └────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- Steps connected left-to-right by `flow_step` edges (ordered by `weight`)
|
||||||
|
- Entry point label at the left as flow trigger
|
||||||
|
- Clicking a step → sidebar shows detail + link to structural view
|
||||||
|
|
||||||
|
### Sidebar Adaptations
|
||||||
|
|
||||||
|
**Domain node selected:** Summary, business rules, entities, cross-domain interactions, list of flows (clickable)
|
||||||
|
|
||||||
|
**Flow node selected:** Entry point info, step list in order, complexity
|
||||||
|
|
||||||
|
**Step node selected:** Description, "View in code" link (switches to structural view + navigates to file/function), previous/next step links
|
||||||
|
|
||||||
|
### Drill-Down: Domain → Structural
|
||||||
|
|
||||||
|
When a step has an `implements` edge referencing a structural node ID:
|
||||||
|
- "View implementation" button in sidebar
|
||||||
|
- Switches to structural view and navigates to that node
|
||||||
|
- Breadcrumb: `Domain: Order Management > Flow: Create Order > Step: Validate Input → [structural view]`
|
||||||
|
|
||||||
|
## Section 5: Skill Definition
|
||||||
|
|
||||||
|
### `/understand-domain` Skill
|
||||||
|
|
||||||
|
- **File:** `skills/understand-domain.md`
|
||||||
|
- **Arguments:** Optional `--full` flag to force Path 1 (rescan even if graph exists)
|
||||||
|
|
||||||
|
### Execution Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Run scripts/extract-domain-context.mjs
|
||||||
|
2. Check for .understand-anything/knowledge-graph.json
|
||||||
|
├── Exists → Path 2: load graph + domain-context.json
|
||||||
|
└── Missing → Path 1: domain-context.json only
|
||||||
|
3. Invoke domain-analyzer agent (opus)
|
||||||
|
4. Validate output against schema
|
||||||
|
5. Save .understand-anything/domain-graph.json
|
||||||
|
6. Clean up intermediate/domain-context.json
|
||||||
|
7. Auto-trigger /understand-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
### Domain Analyzer Agent
|
||||||
|
|
||||||
|
- **File:** `agents/domain-analyzer.md`
|
||||||
|
- **Model:** opus
|
||||||
|
- **Input:** Either (file tree + entry points) or (existing knowledge graph)
|
||||||
|
- **Output:** Complete domain graph JSON
|
||||||
|
|
||||||
|
### Change Map
|
||||||
|
|
||||||
|
| Area | Changes |
|
||||||
|
|------|---------|
|
||||||
|
| `packages/core/src/types.ts` | Add 3 node types, 4 edge types, `domainMeta` optional field |
|
||||||
|
| `packages/core/src/schema.ts` | Extend Zod schemas + aliases for new types |
|
||||||
|
| `packages/core/src/persistence/` | Add `loadDomainGraph()` / `saveDomainGraph()` |
|
||||||
|
| `understand-anything-plugin/skills/understand-domain/extract-domain-context.py` | New preprocessing script (bundled with skill) |
|
||||||
|
| `agents/domain-analyzer.md` | New agent definition |
|
||||||
|
| `skills/understand-domain.md` | New skill definition |
|
||||||
|
| `packages/dashboard/src/store.ts` | Add `domainGraph`, `viewMode` state |
|
||||||
|
| `packages/dashboard/src/components/` | New: `DomainGraphView.tsx`, `DomainClusterNode.tsx`, `FlowTraceNode.tsx`, `StepNode.tsx` |
|
||||||
|
| `packages/dashboard/src/components/` | Modify: `App.tsx` (view toggle), `NodeInfo.tsx` (domain sidebar), `FilterPanel.tsx` (domain filters) |
|
||||||
|
| `packages/dashboard/src/utils/` | New: `domain-layout.ts` (horizontal Dagre config) |
|
||||||
|
|
||||||
|
## Section 6: Error Tolerance
|
||||||
|
|
||||||
|
### Pipeline-Level Tolerance
|
||||||
|
|
||||||
|
| Stage | Error Handling |
|
||||||
|
|-------|---------------|
|
||||||
|
| Preprocessing script | If tree-sitter fails on a file, skip and continue. Log skipped files. Entry point detection is best-effort. |
|
||||||
|
| LLM output parsing | Same strategy as existing `parseTourGenerationResponse()` — extract JSON from markdown, handle partial responses. |
|
||||||
|
| Schema validation | Existing auto-fix pipeline: sanitize → normalize (aliases) → apply defaults → validate. Drop broken nodes/edges, don't fail the whole graph. |
|
||||||
|
| Cross-graph references | `implements` edges pointing to non-existent structural node IDs → keep edge but mark as `unresolved`. Dashboard shows step without drill-down link. |
|
||||||
|
|
||||||
|
### Domain-Specific Validation Rules
|
||||||
|
|
||||||
|
- **Domain with no flows:** Warn, keep (summary/entities still useful)
|
||||||
|
- **Flow with no steps:** Warn, keep (entry point info still valuable)
|
||||||
|
- **Steps with broken ordering:** Re-number sequentially by array position if `weight` values missing/duplicate
|
||||||
|
- **Orphan steps:** Steps not connected to any flow → attach to synthetic "Uncategorized" flow
|
||||||
|
- **Duplicate domains:** Merge by name similarity (fuzzy match), combine flows
|
||||||
|
- **Empty domain graph:** Error banner in dashboard: "Domain extraction failed — try running `/understand` first for richer context, then `/understand-domain`"
|
||||||
|
|
||||||
|
### Dashboard Resilience
|
||||||
|
|
||||||
|
- If `domainMeta` missing on a domain node, sidebar shows only summary/tags
|
||||||
|
- If `domain-graph.json` fails validation entirely, fall back to structural view with warning banner
|
||||||
|
- Partial graphs render what's valid
|
||||||
|
|
||||||
|
### Normalization Aliases for Domain Types
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Node type aliases
|
||||||
|
"business_domain" → "domain"
|
||||||
|
"process" → "flow"
|
||||||
|
"workflow" → "flow"
|
||||||
|
"action" → "step"
|
||||||
|
"task" → "step"
|
||||||
|
|
||||||
|
// Edge type aliases
|
||||||
|
"has_flow" → "contains_flow"
|
||||||
|
"next_step" → "flow_step"
|
||||||
|
"interacts_with" → "cross_domain"
|
||||||
|
"implemented_by" → "implements"
|
||||||
|
```
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
# /understand-knowledge — Personal Knowledge Base Plugin Design
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
A new `/understand-knowledge` skill within the existing Understand Anything plugin that takes any folder of markdown notes and produces an interactive knowledge graph visualized in the existing dashboard.
|
||||||
|
|
||||||
|
Inspired by Andrej Karpathy's LLM Wiki pattern — where an LLM compiles and maintains a structured wiki from raw sources — this plugin goes further by adding typed relationship discovery and interactive graph visualization that tools like Obsidian and Logseq cannot provide.
|
||||||
|
|
||||||
|
### Goals
|
||||||
|
|
||||||
|
- Accept any markdown-based knowledge base (Obsidian vault, Logseq graph, Dendron workspace, Foam, Karpathy-style LLM wiki, Zettelkasten, or plain markdown)
|
||||||
|
- Auto-detect the format and adapt parsing accordingly
|
||||||
|
- Use LLM analysis to discover implicit relationships beyond explicit links
|
||||||
|
- Produce a knowledge graph with typed nodes and edges
|
||||||
|
- Visualize in the existing dashboard with knowledge-specific layout, sidebar, and reading mode
|
||||||
|
|
||||||
|
### Non-Goals
|
||||||
|
|
||||||
|
- Real-time sync with the knowledge base tool (Obsidian, Logseq, etc.)
|
||||||
|
- Replacing the user's existing PKM tool — this is a visualization/analysis layer on top
|
||||||
|
- Supporting non-markdown formats (PDFs, bookmarks) in v1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Schema Extensions
|
||||||
|
|
||||||
|
### New Node Types (5)
|
||||||
|
|
||||||
|
Added to the existing `NodeType` union (currently 16 types):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export type NodeType =
|
||||||
|
// existing (16)
|
||||||
|
| "file" | "function" | "class" | "module" | "concept"
|
||||||
|
| "config" | "document" | "service" | "table" | "endpoint"
|
||||||
|
| "pipeline" | "schema" | "resource"
|
||||||
|
| "domain" | "flow" | "step"
|
||||||
|
// knowledge (5 new → 21 total)
|
||||||
|
| "article" | "entity" | "topic" | "claim" | "source";
|
||||||
|
```
|
||||||
|
|
||||||
|
| Type | What it represents | Example |
|
||||||
|
|------|-------------------|---------|
|
||||||
|
| `article` | A wiki/note page — the primary content unit | "LLM Knowledge Bases.md" |
|
||||||
|
| `entity` | A named thing: person, tool, paper, org, project | "Andrej Karpathy", "Obsidian" |
|
||||||
|
| `topic` | A thematic cluster grouping related articles | "Personal Knowledge Management" |
|
||||||
|
| `claim` | A specific assertion, insight, or takeaway | "RAG loses context at chunk boundaries" |
|
||||||
|
| `source` | Raw/reference material that articles are compiled from | A paper URL, a raw PDF reference |
|
||||||
|
|
||||||
|
### New Edge Types (6)
|
||||||
|
|
||||||
|
Added to the existing `EdgeType` union (currently 29 types):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export type EdgeType =
|
||||||
|
// existing (29)
|
||||||
|
| ...
|
||||||
|
// knowledge (6 new → 35 total)
|
||||||
|
| "cites" | "contradicts" | "builds_on"
|
||||||
|
| "exemplifies" | "categorized_under" | "authored_by";
|
||||||
|
```
|
||||||
|
|
||||||
|
| Type | Direction | Meaning |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| `cites` | article → source | References or draws from |
|
||||||
|
| `contradicts` | claim → claim | Conflicts or disagrees with |
|
||||||
|
| `builds_on` | article → article | Extends, refines, or deepens |
|
||||||
|
| `exemplifies` | entity → concept/topic | Is a concrete example of |
|
||||||
|
| `categorized_under` | article/entity → topic | Belongs to this theme |
|
||||||
|
| `authored_by` | article → entity | Written or created by |
|
||||||
|
|
||||||
|
### New Metadata Interface
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface KnowledgeMeta {
|
||||||
|
format?: "obsidian" | "logseq" | "dendron" | "foam" | "karpathy" | "zettelkasten" | "plain";
|
||||||
|
wikilinks?: string[];
|
||||||
|
backlinks?: string[];
|
||||||
|
frontmatter?: Record<string, unknown>;
|
||||||
|
sourceUrl?: string;
|
||||||
|
confidence?: number; // 0-1, for LLM-inferred relationships
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Added as an optional field on `GraphNode`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface GraphNode {
|
||||||
|
// ...existing fields
|
||||||
|
knowledgeMeta?: KnowledgeMeta;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Graph-Level Kind Flag
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface KnowledgeGraph {
|
||||||
|
version: string;
|
||||||
|
kind: "codebase" | "knowledge"; // NEW
|
||||||
|
project: ProjectMeta;
|
||||||
|
nodes: GraphNode[];
|
||||||
|
edges: GraphEdge[];
|
||||||
|
layers: Layer[];
|
||||||
|
tour: TourStep[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `kind` field tells the dashboard which layout, sidebar, and visual styling to use. For backward compatibility, graphs without a `kind` field default to `"codebase"`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Format Detection & Format Guides
|
||||||
|
|
||||||
|
### Auto-Detection Logic
|
||||||
|
|
||||||
|
Scans the target directory for signature files/patterns. Priority order (first match wins):
|
||||||
|
|
||||||
|
| Priority | Signal | Detected Format |
|
||||||
|
|----------|--------|----------------|
|
||||||
|
| 1 | `.obsidian/` directory | Obsidian |
|
||||||
|
| 2 | `logseq/` + `pages/` directories | Logseq |
|
||||||
|
| 3 | `.dendron.yml` or `*.schema.yml` | Dendron |
|
||||||
|
| 4 | `.foam/` or `.vscode/foam.json` | Foam |
|
||||||
|
| 5 | `raw/` + `wiki/` + `index.md` | Karpathy |
|
||||||
|
| 6 | `[[wikilinks]]` + unique ID prefixes in filenames | Zettelkasten |
|
||||||
|
| 7 | Fallback | Plain markdown |
|
||||||
|
|
||||||
|
### Format Guides
|
||||||
|
|
||||||
|
Located at `skills/understand-knowledge/formats/`. Each guide tells the LLM agents how to parse that format:
|
||||||
|
|
||||||
|
```
|
||||||
|
skills/understand-knowledge/
|
||||||
|
SKILL.md
|
||||||
|
formats/
|
||||||
|
obsidian.md — [[wikilinks]], [[note|alias]], [[note#heading]],
|
||||||
|
#tags, YAML frontmatter, .obsidian/ config,
|
||||||
|
dataview annotations, canvas files
|
||||||
|
logseq.md — block-based outliner, ((block-refs)),
|
||||||
|
journals/YYYY_MM_DD.md, pages/,
|
||||||
|
property:: value syntax, TODO/DONE states
|
||||||
|
dendron.md — dot-delimited hierarchy (a.b.c.md),
|
||||||
|
.schema.yml for structure validation,
|
||||||
|
cross-vault links, refactoring rules
|
||||||
|
foam.md — [[wikilinks]] + link reference definitions
|
||||||
|
at file bottom, .foam/config, placeholder links
|
||||||
|
karpathy.md — raw/ → wiki/ pipeline, index.md master map,
|
||||||
|
log.md append-only record, _meta/ state,
|
||||||
|
LLM-maintained cross-references
|
||||||
|
zettelkasten.md — atomic notes, unique ID prefixes (timestamps),
|
||||||
|
typed semantic links, one idea per note
|
||||||
|
plain.md — standard [markdown](links), folder hierarchy,
|
||||||
|
heading structure, no special conventions
|
||||||
|
```
|
||||||
|
|
||||||
|
Each format guide covers:
|
||||||
|
- How to parse links (wikilinks vs standard vs block refs)
|
||||||
|
- Where metadata lives (frontmatter vs inline properties vs block properties)
|
||||||
|
- What the folder structure means (journals/ = daily notes, pages/ = permanent notes)
|
||||||
|
- What conventions to respect vs what to infer
|
||||||
|
|
||||||
|
### Format Guide Authoring Process
|
||||||
|
|
||||||
|
Format guides must be research-backed. During implementation, the agent building each format guide must:
|
||||||
|
1. Read the official documentation for that format (Obsidian Help, Logseq docs, Dendron wiki, Foam docs, etc.)
|
||||||
|
2. Study real-world examples of that format's structure
|
||||||
|
3. Write the guide based on verified behavior, not assumptions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent Pipeline
|
||||||
|
|
||||||
|
```
|
||||||
|
knowledge-scanner → format-detector → article-analyzer → relationship-builder → graph-reviewer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Agent Definitions
|
||||||
|
|
||||||
|
| Agent | Input | Output | Model |
|
||||||
|
|-------|-------|--------|-------|
|
||||||
|
| `knowledge-scanner` | Target directory path | File manifest: all `.md` files with paths, sizes, first 20 lines preview | `inherit` |
|
||||||
|
| `format-detector` | File manifest + directory structure | Detected format + format-specific parsing hints | `inherit` |
|
||||||
|
| `article-analyzer` | Individual `.md` file + format guide | Per-file nodes (article, entities, claims) + explicit edges (wikilinks, tags) | `inherit` |
|
||||||
|
| `relationship-builder` | All per-file results | Cross-file implicit edges (builds_on, contradicts, categorized_under) + topic clustering + layers | `inherit` |
|
||||||
|
| `graph-reviewer` | Assembled graph | Validated graph — deduped entities, consistent edge weights, orphan detection | `inherit` |
|
||||||
|
|
||||||
|
### Key Differences from Codebase Pipeline
|
||||||
|
|
||||||
|
- **No tree-sitter** — markdown parsing is simpler, mostly regex + LLM interpretation
|
||||||
|
- **format-detector** replaces framework detection — picks the right format guide
|
||||||
|
- **article-analyzer** replaces file-analyzer — extracts knowledge concepts instead of code structure
|
||||||
|
- **relationship-builder** is the heavy LLM step — discovers implicit connections across files that explicit links miss
|
||||||
|
- **graph-reviewer** stays similar — validates the assembled graph for consistency
|
||||||
|
|
||||||
|
### Intermediate Files
|
||||||
|
|
||||||
|
Same pattern as codebase analysis:
|
||||||
|
|
||||||
|
```
|
||||||
|
.understand-anything/intermediate/
|
||||||
|
knowledge-manifest.json — scanner output
|
||||||
|
format-detection.json — detected format + hints
|
||||||
|
article-*.json — per-file analysis
|
||||||
|
relationships.json — cross-file edges
|
||||||
|
knowledge-graph.json — final assembled graph
|
||||||
|
```
|
||||||
|
|
||||||
|
Intermediate files are cleaned up after graph assembly (same as codebase flow).
|
||||||
|
|
||||||
|
### Incremental Mode (`--ingest`)
|
||||||
|
|
||||||
|
When the user runs `/understand-knowledge --ingest path/to/new-source.md`:
|
||||||
|
|
||||||
|
1. **knowledge-scanner** — runs on just the new file(s)
|
||||||
|
2. **format-detector** — skipped (format already known from initial scan)
|
||||||
|
3. **article-analyzer** — processes only new/changed files
|
||||||
|
4. **relationship-builder** — runs on new nodes against the existing graph, finds connections to what's already there
|
||||||
|
5. **graph-reviewer** — validates the merged result
|
||||||
|
|
||||||
|
Existing nodes are preserved; only new nodes/edges are added or updated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dashboard Changes
|
||||||
|
|
||||||
|
All changes are scoped to graphs with `"kind": "knowledge"`.
|
||||||
|
|
||||||
|
### Vertical Flow Layout
|
||||||
|
|
||||||
|
- Default to top-down vertical layout (like existing domain/business flow view)
|
||||||
|
- Topics at top → articles in middle → entities/claims/sources at bottom
|
||||||
|
- Reads like a knowledge hierarchy: broad themes flow down into specifics
|
||||||
|
- User can still switch to horizontal or force-directed layout via controls
|
||||||
|
|
||||||
|
### Knowledge Sidebar
|
||||||
|
|
||||||
|
Replaces NodeInfo when a knowledge graph is loaded:
|
||||||
|
|
||||||
|
| Selection | Sidebar Shows |
|
||||||
|
|-----------|---------------|
|
||||||
|
| Nothing selected | ProjectOverview: format detected, total articles/entities/topics/claims/sources |
|
||||||
|
| Article node | Title, summary, tags, frontmatter metadata, backlinks list (clickable), outgoing links, related topics |
|
||||||
|
| Entity node | Name, type (person/tool/paper/org), articles that mention it, relationships to other entities |
|
||||||
|
| Topic node | Description, child articles, child entities, cross-topic connections |
|
||||||
|
| Claim node | Assertion text, supporting articles, contradicting claims (if any), confidence score |
|
||||||
|
| Source node | Original URL/path, articles that cite it, ingestion date |
|
||||||
|
|
||||||
|
### Reading Mode
|
||||||
|
|
||||||
|
- Clicking an article node triggers a reading panel that slides up from the bottom (same pattern as current code viewer overlay)
|
||||||
|
- Shows the full compiled markdown rendered as HTML
|
||||||
|
- Includes a mini backlinks sidebar within the panel
|
||||||
|
- Clicking a `[[wikilink]]` or entity reference in the reading panel navigates the graph to that node
|
||||||
|
|
||||||
|
### Node Visual Styling
|
||||||
|
|
||||||
|
| Node Type | Shape | Color Accent |
|
||||||
|
|-----------|-------|-------------|
|
||||||
|
| `article` | Rounded rectangle | Warm amber |
|
||||||
|
| `entity` | Circle | Soft blue |
|
||||||
|
| `topic` | Large rounded rectangle | Muted gold |
|
||||||
|
| `claim` | Diamond | Green/red depending on contradictions |
|
||||||
|
| `source` | Small square | Gray |
|
||||||
|
|
||||||
|
### Edge Visual Styling
|
||||||
|
|
||||||
|
| Edge Type | Style |
|
||||||
|
|-----------|-------|
|
||||||
|
| `cites` | Dashed line |
|
||||||
|
| `contradicts` | Red line |
|
||||||
|
| `builds_on` | Solid with arrow |
|
||||||
|
| `categorized_under` | Thin gray |
|
||||||
|
| `authored_by` | Dotted blue |
|
||||||
|
| `exemplifies` | Dotted green |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Skill Interface
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Full scan — first time or rescan
|
||||||
|
/understand-knowledge
|
||||||
|
|
||||||
|
# Point at a specific directory
|
||||||
|
/understand-knowledge path/to/my-notes
|
||||||
|
|
||||||
|
# Incremental ingest — add new sources to existing graph
|
||||||
|
/understand-knowledge --ingest path/to/new-note.md
|
||||||
|
/understand-knowledge --ingest path/to/new-folder/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
|
||||||
|
1. Auto-detects format (Obsidian, Logseq, Karpathy, etc.)
|
||||||
|
2. Announces: "Detected Obsidian vault with 342 notes. Scanning..."
|
||||||
|
3. Runs the agent pipeline (scanner → detector → analyzer → relationship-builder → reviewer)
|
||||||
|
4. Writes `knowledge-graph.json` to `.understand-anything/` with `"kind": "knowledge"`
|
||||||
|
5. Auto-triggers `/understand-dashboard` after completion
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
skills/understand-knowledge/
|
||||||
|
SKILL.md — skill entry point, orchestration logic
|
||||||
|
formats/
|
||||||
|
obsidian.md
|
||||||
|
logseq.md
|
||||||
|
dendron.md
|
||||||
|
foam.md
|
||||||
|
karpathy.md
|
||||||
|
zettelkasten.md
|
||||||
|
plain.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Coexistence with `/understand`
|
||||||
|
|
||||||
|
- `/understand` produces `"kind": "codebase"` graphs
|
||||||
|
- `/understand-knowledge` produces `"kind": "knowledge"` graphs
|
||||||
|
- Both write to `.understand-anything/knowledge-graph.json`
|
||||||
|
- Running one replaces the other
|
||||||
|
- To scope knowledge analysis to a subdirectory (e.g., `docs/` within a code repo), use `/understand-knowledge path/to/docs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What This Enables That Nothing Else Does
|
||||||
|
|
||||||
|
| Existing Tools | Limitation | Our Advantage |
|
||||||
|
|---------------|-----------|---------------|
|
||||||
|
| Obsidian graph view | Untyped edges — all links look the same | Typed edges: cites, contradicts, builds_on |
|
||||||
|
| Logseq graph | Only shows explicit links | LLM discovers implicit relationships |
|
||||||
|
| All PKM tools | Single-format only | Cross-format support with auto-detection |
|
||||||
|
| Karpathy LLM Wiki | Flat text wiki, no visualization | Interactive graph dashboard with guided tours |
|
||||||
|
| None | No knowledge graph tours | Tour mode walks through a knowledge base step by step |
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
# .understandignore Design Spec
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Add user-configurable file exclusion via `.understandignore` files, using `.gitignore` syntax. This makes analysis faster by skipping irrelevant files (vendor code, generated output, test fixtures) without modifying hardcoded defaults.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Let users exclude files/directories from analysis via `.understandignore`
|
||||||
|
- Use `.gitignore` syntax (familiar, no learning curve)
|
||||||
|
- Keep hardcoded defaults as built-in — `.understandignore` adds patterns on top
|
||||||
|
- Allow `!` negation to force-include files excluded by defaults
|
||||||
|
- Auto-generate a commented-out starter file on first run (deterministic code, not LLM)
|
||||||
|
- Pause before analysis to let user review the ignore file
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- Replacing `.gitignore` — this is analysis-specific
|
||||||
|
- Per-directory `.understandignore` files (project root and `.understand-anything/` only)
|
||||||
|
- GUI for editing ignore patterns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IgnoreFilter Module
|
||||||
|
|
||||||
|
New file: `packages/core/src/ignore-filter.ts`
|
||||||
|
|
||||||
|
Uses the [`ignore`](https://www.npmjs.com/package/ignore) npm package for gitignore-compatible pattern matching.
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface IgnoreFilter {
|
||||||
|
isIgnored(relativePath: string): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createIgnoreFilter(projectRoot: string): IgnoreFilter;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
|
||||||
|
`createIgnoreFilter` loads patterns in this order (later entries can override earlier ones):
|
||||||
|
|
||||||
|
1. **Hardcoded defaults** — the existing exclusion patterns from project-scanner (node_modules/, .git/, dist/, build/, bin/, obj/, *.lock, *.min.js, etc.)
|
||||||
|
2. **`.understand-anything/.understandignore`** — project-level, lives alongside the output
|
||||||
|
3. **`.understandignore`** at project root — alternative location for visibility
|
||||||
|
|
||||||
|
Patterns merge additively. `!` negation in user files can override hardcoded defaults (e.g., `!dist/` force-includes dist/).
|
||||||
|
|
||||||
|
### Hardcoded Default Patterns
|
||||||
|
|
||||||
|
These are the built-in defaults (matching current project-scanner behavior, plus bin/obj for .NET):
|
||||||
|
|
||||||
|
```
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
.git/
|
||||||
|
vendor/
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
out/
|
||||||
|
coverage/
|
||||||
|
.next/
|
||||||
|
.cache/
|
||||||
|
.turbo/
|
||||||
|
target/
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
|
||||||
|
# Lock files
|
||||||
|
*.lock
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
pnpm-lock.yaml
|
||||||
|
|
||||||
|
# Binary/asset files
|
||||||
|
*.png
|
||||||
|
*.jpg
|
||||||
|
*.jpeg
|
||||||
|
*.gif
|
||||||
|
*.svg
|
||||||
|
*.ico
|
||||||
|
*.woff
|
||||||
|
*.woff2
|
||||||
|
*.ttf
|
||||||
|
*.eot
|
||||||
|
*.mp3
|
||||||
|
*.mp4
|
||||||
|
*.pdf
|
||||||
|
*.zip
|
||||||
|
*.tar
|
||||||
|
*.gz
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
*.min.js
|
||||||
|
*.min.css
|
||||||
|
*.map
|
||||||
|
*.generated.*
|
||||||
|
|
||||||
|
# IDE/editor
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
LICENSE
|
||||||
|
.gitignore
|
||||||
|
.editorconfig
|
||||||
|
.prettierrc
|
||||||
|
.eslintrc*
|
||||||
|
*.log
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Starter File Generator
|
||||||
|
|
||||||
|
New file: `packages/core/src/ignore-generator.ts`
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export function generateStarterIgnoreFile(projectRoot: string): string;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
|
||||||
|
- Deterministic code — scans the project directory for common patterns
|
||||||
|
- Returns the file content as a string (caller writes it to disk)
|
||||||
|
- All suggestions are **commented out** — user must uncomment to activate
|
||||||
|
- Header comment explains the file, syntax, and built-in defaults
|
||||||
|
|
||||||
|
### Detection Logic
|
||||||
|
|
||||||
|
| If exists | Suggest |
|
||||||
|
|-----------|---------|
|
||||||
|
| `__tests__/` or `*.test.*` files | `# __tests__/`, `# *.test.*`, `# *.spec.*` |
|
||||||
|
| `fixtures/` or `testdata/` | `# fixtures/`, `# testdata/` |
|
||||||
|
| `test/` or `tests/` | `# test/`, `# tests/` |
|
||||||
|
| `.storybook/` | `# .storybook/` |
|
||||||
|
| `docs/` | `# docs/` |
|
||||||
|
| `examples/` | `# examples/` |
|
||||||
|
| `scripts/` | `# scripts/` |
|
||||||
|
| `migrations/` | `# migrations/` |
|
||||||
|
| `*.snap` files | `# *.snap` |
|
||||||
|
| `bin/` (non-.NET, i.e. shell scripts) | `# bin/` |
|
||||||
|
| `obj/` | `# obj/` |
|
||||||
|
|
||||||
|
### Generated File Format
|
||||||
|
|
||||||
|
```
|
||||||
|
# .understandignore — patterns for files/dirs to exclude from analysis
|
||||||
|
# Syntax: same as .gitignore (globs, # comments, ! negation, trailing / for dirs)
|
||||||
|
# Lines below are suggestions — uncomment to activate.
|
||||||
|
# Use ! prefix to force-include something excluded by defaults.
|
||||||
|
#
|
||||||
|
# Built-in defaults (always excluded unless negated):
|
||||||
|
# node_modules/, .git/, dist/, build/, bin/, obj/, *.lock, *.min.js, etc.
|
||||||
|
#
|
||||||
|
|
||||||
|
# --- Suggested exclusions (uncomment to activate) ---
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
# __tests__/
|
||||||
|
# *.test.*
|
||||||
|
# *.spec.*
|
||||||
|
|
||||||
|
# Test data
|
||||||
|
# fixtures/
|
||||||
|
# testdata/
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
# docs/
|
||||||
|
|
||||||
|
# ... (more suggestions based on detection)
|
||||||
|
```
|
||||||
|
|
||||||
|
Only generated if `.understand-anything/.understandignore` doesn't already exist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Skill Integration
|
||||||
|
|
||||||
|
### Phase 0.5: Ignore Setup (new phase in SKILL.md)
|
||||||
|
|
||||||
|
Added between Pre-flight (Phase 0) and SCAN (Phase 1):
|
||||||
|
|
||||||
|
1. Check if `.understand-anything/.understandignore` exists
|
||||||
|
2. If not, run `generateStarterIgnoreFile(projectRoot)` and write the result to `.understand-anything/.understandignore`
|
||||||
|
3. Report to user:
|
||||||
|
- **First run:** "Generated `.understand-anything/.understandignore` with suggested exclusions. Please review it and uncomment any patterns you'd like to exclude. When ready, confirm to continue."
|
||||||
|
- **Subsequent runs:** "Found `.understand-anything/.understandignore`. Review it if needed, then confirm to continue."
|
||||||
|
4. Wait for user confirmation before proceeding
|
||||||
|
|
||||||
|
### Phase 1: SCAN changes
|
||||||
|
|
||||||
|
The `project-scanner` agent's scan script is updated to:
|
||||||
|
|
||||||
|
1. Collect files via `git ls-files` (or fallback)
|
||||||
|
2. Apply agent's hardcoded pattern filter (Layer 1 — existing behavior)
|
||||||
|
3. Apply `IgnoreFilter` from core (Layer 2 — user patterns)
|
||||||
|
4. Add `filteredByIgnore` count to scan output
|
||||||
|
5. Report: "Scanned {totalFiles} files ({filteredByIgnore} excluded by .understandignore)"
|
||||||
|
|
||||||
|
Two-layer filtering:
|
||||||
|
- **Layer 1:** Agent's hardcoded patterns in the prompt (fast, coarse filter)
|
||||||
|
- **Layer 2:** `IgnoreFilter` from core (deterministic code, user-configurable)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Scanner Agent Update
|
||||||
|
|
||||||
|
Changes to `understand-anything-plugin/agents/project-scanner.md`:
|
||||||
|
|
||||||
|
- After the file list is built and Layer 1 filtering is applied, the agent runs a Node.js script that imports `createIgnoreFilter` from `@understand-anything/core` and filters the remaining paths
|
||||||
|
- The scan result JSON includes a new `filteredByIgnore: number` field
|
||||||
|
- Existing hardcoded exclusion patterns in the agent prompt remain for backward compatibility
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### `packages/core/src/__tests__/ignore-filter.test.ts`
|
||||||
|
|
||||||
|
- Parses basic glob patterns (`*.log`, `dist/`)
|
||||||
|
- Handles `#` comments and blank lines
|
||||||
|
- Handles `!` negation (force-include)
|
||||||
|
- Handles `**/` recursive matching
|
||||||
|
- Handles trailing `/` for directory-only patterns
|
||||||
|
- Merges defaults + user patterns correctly
|
||||||
|
- `!` in user file overrides hardcoded defaults
|
||||||
|
- Returns `false` for paths not matching any pattern
|
||||||
|
|
||||||
|
### `packages/core/src/__tests__/ignore-generator.test.ts`
|
||||||
|
|
||||||
|
- Generates starter file with header comment
|
||||||
|
- Detects existing directories and suggests relevant patterns
|
||||||
|
- All suggestions are commented out (prefixed with `# `)
|
||||||
|
- Doesn't overwrite existing file
|
||||||
|
- Includes bin/obj suggestions when relevant
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `packages/core/src/ignore-filter.ts` | Parse .understandignore, merge with defaults, filter paths |
|
||||||
|
| `packages/core/src/ignore-generator.ts` | Generate starter file by scanning project structure |
|
||||||
|
| `packages/core/src/__tests__/ignore-filter.test.ts` | Filter logic tests |
|
||||||
|
| `packages/core/src/__tests__/ignore-generator.test.ts` | Generator tests |
|
||||||
|
| `agents/project-scanner.md` | Add Layer 2 filtering via IgnoreFilter |
|
||||||
|
| `skills/understand/SKILL.md` | Add Phase 0.5 (generate + pause for review) |
|
||||||
|
| `packages/core/package.json` | Add `ignore` npm dependency |
|
||||||
@@ -0,0 +1,488 @@
|
|||||||
|
# Dashboard Graph Layout Scaling — Design
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
When a structural-graph layer contains many nodes, the current `applyDagreLayout` (TB direction) places same-rank nodes in a single horizontal row. With 50+ nodes per rank, the row stretches into thousands of pixels and the view becomes unreadable: nodes shrink, labels disappear, edges tangle, and there are no visual anchors to orient the reader.
|
||||||
|
|
||||||
|
This design replaces dagre with ELK across all structural-style views, introduces folder/community-based **containers** for the layer-detail view, and computes layout in **two lazy stages** — a single-pass over containers, then per-container child layout on demand.
|
||||||
|
|
||||||
|
The graph schema and pipeline output (`graph.json`) are unchanged. All improvements derive from existing data.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Eliminate horizontal sprawl in layer-detail views at ≤100 nodes per layer (current target), and remain workable up to 1000+ nodes (future scaling).
|
||||||
|
- Give each layer-detail view explicit visual anchors so structure is readable at a glance.
|
||||||
|
- Aggregate cross-cluster edges by default; surface individual edges on demand.
|
||||||
|
- Keep visual style continuous with the existing layer-cluster (overview-level) presentation.
|
||||||
|
- Treat layout failures with the same `GraphIssue` model already used for schema validation.
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- No regeneration of `graph.json`. All grouping is derived client-side.
|
||||||
|
- No change to KnowledgeGraphView (already force-directed; out of scope).
|
||||||
|
- No multi-level container nesting (single depth only in v1).
|
||||||
|
- No remote error reporting (Sentry-style) — open-source plugin, no default telemetry.
|
||||||
|
- No persona-specific grouping behavior beyond the existing node-type filter.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Three views are affected:
|
||||||
|
|
||||||
|
| View | Change |
|
||||||
|
|---|---|
|
||||||
|
| Overview (layer clusters) | Replace dagre → ELK. No new grouping (layers are already groups). |
|
||||||
|
| DomainGraphView | Replace dagre → ELK with domain-as-parent of flow/step. |
|
||||||
|
| Layer-detail | Replace dagre → ELK + new folder/community containers + edge aggregation + lazy two-stage layout. |
|
||||||
|
|
||||||
|
KnowledgeGraphView remains on `applyForceLayout` and is not touched.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1. Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
existing graph (immutable)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
deriveContainers(nodes, edges) // §2 — folder strategy with community fallback
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
buildCompoundGraph() // §4 — aggregate inter-container edges, keep intra-container
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
runStage1Layout(containers, aggEdges) // §6 — ELK on containers only; uses size memory
|
||||||
|
│
|
||||||
|
▼ ┌──────────────────────────────┐
|
||||||
|
│ │ render: containers laid │
|
||||||
|
│ │ out, children unrendered │
|
||||||
|
│ └──────────────────────────────┘
|
||||||
|
│
|
||||||
|
│ triggered by: click | zoom > 1.0 | search/focus/tour hit child
|
||||||
|
▼
|
||||||
|
runStage2Layout(container) // §6 — ELK on one container's children; cached
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
React Flow render (parentId for parent-child) + visual overlay (selection/diff/search/tour)
|
||||||
|
```
|
||||||
|
|
||||||
|
Two invariants preserved from current code:
|
||||||
|
|
||||||
|
1. **Layout computation is pure and memoized.** It only re-runs when graph topology / persona / diff / focus / nodeTypeFilters change.
|
||||||
|
2. **Visual state is a separate O(n) overlay pass.** Selection, search highlight, tour highlight, hover do not trigger relayout.
|
||||||
|
|
||||||
|
This matches the existing `useLayerDetailTopology` / `useLayerDetailGraph` split in `GraphView.tsx`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §2. Container Derivation (Layer-Detail Only)
|
||||||
|
|
||||||
|
### 2.1 Folder strategy (default)
|
||||||
|
|
||||||
|
1. Collect every node's `filePath` in the layer.
|
||||||
|
2. Compute longest common prefix (LCP) across all paths and strip it.
|
||||||
|
3. Group by the **first path segment after the LCP**.
|
||||||
|
- `auth/login.go` → container `auth`
|
||||||
|
- `auth/handlers/oauth.go` → container `auth`
|
||||||
|
- `cart/cart.go` → container `cart`
|
||||||
|
4. Single-depth grouping only; no recursive nesting in v1.
|
||||||
|
5. Nodes with no `filePath` (e.g. `concept` type) → container `~` (rendered as `(root)`, dimmed).
|
||||||
|
|
||||||
|
### 2.2 Community fallback (Louvain)
|
||||||
|
|
||||||
|
Triggered when **any** of:
|
||||||
|
|
||||||
|
- All nodes share the same single folder after LCP stripping.
|
||||||
|
- Bucket count (folders + rooted) `< 2`.
|
||||||
|
- Any single bucket (folder or rooted) holds `> 70%` of nodes.
|
||||||
|
|
||||||
|
Run Louvain modularity-based community detection on the layer's internal edges. Each community becomes a container. Names are placeholders (`Cluster A`, `Cluster B`, ...) since no semantic name is available.
|
||||||
|
|
||||||
|
Implementation: use `graphology` + `graphology-communities-louvain` (~30KB total). Pure JS, no native deps, runs on main thread synchronously for layer-internal edges.
|
||||||
|
|
||||||
|
### 2.3 Edge cases
|
||||||
|
|
||||||
|
| Case | Behavior |
|
||||||
|
|---|---|
|
||||||
|
| Container has 1 child (only when layer total ≥ 3) | No container box rendered; child becomes a top-level node in Stage 1 layout |
|
||||||
|
| Container has 2 children | Container rendered; label dimmed |
|
||||||
|
| All nodes lack `filePath` | All go to `~` container; if it would become single-child, fall back to flat |
|
||||||
|
|
||||||
|
### 2.4 Function signature
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function deriveContainers(
|
||||||
|
nodes: GraphNode[],
|
||||||
|
edges: GraphEdge[],
|
||||||
|
): {
|
||||||
|
containers: Array<{
|
||||||
|
id: string; // e.g. "container:auth" or "container:cluster-0"
|
||||||
|
name: string; // "auth" or "Cluster A"
|
||||||
|
nodeIds: string[];
|
||||||
|
strategy: "folder" | "community";
|
||||||
|
}>;
|
||||||
|
ungrouped: string[]; // nodes that bypass containerization
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The `strategy` field is exposed in the UI ("Grouped by folder" vs "Grouped by edge density") so the user knows how a particular layer was organized.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §3. ELK Integration
|
||||||
|
|
||||||
|
### 3.1 Package
|
||||||
|
|
||||||
|
- `elkjs` ^0.9 (~250KB gzipped). Use `elk.bundled.js`, not the worker variant.
|
||||||
|
- Promise-based API. Runs on main thread for graphs ≤500 nodes; <100ms typical.
|
||||||
|
|
||||||
|
### 3.2 Configuration
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
algorithm: "layered",
|
||||||
|
"elk.direction": "DOWN", // matches dagre TB
|
||||||
|
"elk.layered.spacing.nodeNodeBetweenLayers": 80,
|
||||||
|
"elk.spacing.nodeNode": 60,
|
||||||
|
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||||
|
"elk.edgeRouting": "ORTHOGONAL",
|
||||||
|
"elk.layered.compaction.postCompaction.strategy": "LEFT",
|
||||||
|
"elk.padding": "[top=40,left=20,right=20,bottom=20]", // container internal padding
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`hierarchyHandling: INCLUDE_CHILDREN` is **not** used — the two-stage approach (§6) issues separate ELK calls for top-level containers and per-container children, so a single compound graph is never assembled.
|
||||||
|
|
||||||
|
### 3.3 Per-view input shaping
|
||||||
|
|
||||||
|
| View | ELK input |
|
||||||
|
|---|---|
|
||||||
|
| Overview | Flat. Children = layer-cluster nodes. |
|
||||||
|
| DomainGraphView | Flat in v1 (domain stays as the only grouping; flow/step nodes positioned within). |
|
||||||
|
| Layer-detail Stage 1 | Flat. Children = containers (treated as opaque atoms). |
|
||||||
|
| Layer-detail Stage 2 | Flat per container. Children = files within. |
|
||||||
|
|
||||||
|
A single `runElk(input): Promise<positioned>` function services all four cases.
|
||||||
|
|
||||||
|
### 3.4 Boundaries with existing `utils/layout.ts`
|
||||||
|
|
||||||
|
| Function | Status |
|
||||||
|
|---|---|
|
||||||
|
| `applyDagreLayout` | Kept temporarily; removed in the version after layout migration is verified stable |
|
||||||
|
| `applyForceLayout` | Untouched (KnowledgeGraphView only) |
|
||||||
|
| `applyElkLayout` (new) | Wrapper that handles repair → ELK → result coercion |
|
||||||
|
|
||||||
|
### 3.5 Async + loading state
|
||||||
|
|
||||||
|
Stage 1 runs in a `useEffect` with cancellation on dependency change:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
setLayoutStatus("computing");
|
||||||
|
applyElkLayout(input).then(result => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setLayout(result);
|
||||||
|
setLayoutStatus("ready");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () => { cancelled = true };
|
||||||
|
}, [graph, activeLayerId, persona, diffMode, nodeTypeFilters]);
|
||||||
|
```
|
||||||
|
|
||||||
|
While `layoutStatus === "computing"`, render a `"Computing layout…"` overlay (semi-transparent, centered). Stale layout from the previous state is kept underneath so the viewport doesn't blink.
|
||||||
|
|
||||||
|
### 3.6 Failure handling — reuses existing GraphIssue model
|
||||||
|
|
||||||
|
Before invoking ELK, run `repairElkInput()` over the assembled input. Each repair emits a `GraphIssue` consumed by the existing `WarningBanner`.
|
||||||
|
|
||||||
|
| Repair function | Triggered by | Issue level |
|
||||||
|
|---|---|---|
|
||||||
|
| `ensureNodeDimensions` | Node missing width/height | `auto-corrected` |
|
||||||
|
| `dedupeNodeIds` | Duplicate child id under same parent | `auto-corrected` |
|
||||||
|
| `dropOrphanEdges` | Edge source/target not in node set | `dropped` |
|
||||||
|
| `dropOrphanChildren` | Child references a non-existent parent | `dropped` |
|
||||||
|
| `dropCircularContainment` | Container containment cycle | `dropped` |
|
||||||
|
|
||||||
|
If ELK still rejects after repair → emit a `fatal` `GraphIssue`, render an empty graph + the existing fatal banner. The fatal copy text is augmented with "this looks like a dashboard rendering bug — please file an issue with the copied error" so the user knows to direct the report at the dashboard, not the graph data.
|
||||||
|
|
||||||
|
### 3.7 Dev mode strict failures
|
||||||
|
|
||||||
|
Both `repairElkInput` and `runElk` accept a `strict: boolean`. In `import.meta.env.DEV`, strict is on — repairs and ELK errors throw immediately rather than producing graceful issues. This catches input-construction bugs during development before they ship as silent fallbacks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §4. Edge Aggregation
|
||||||
|
|
||||||
|
### 4.1 Algorithm
|
||||||
|
|
||||||
|
Performed inside `buildCompoundGraph()`, before either ELK stage.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function aggregateContainerEdges(
|
||||||
|
nodes: GraphNode[],
|
||||||
|
edges: GraphEdge[],
|
||||||
|
nodeToContainer: Map<string, string>,
|
||||||
|
): {
|
||||||
|
intraContainer: Edge[]; // preserved as-is
|
||||||
|
interContainerAggregated: AggregatedEdge[]; // one per (sourceContainer, targetContainer)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- For each edge, look up source/target containers.
|
||||||
|
- Same container → intra (unchanged).
|
||||||
|
- Different containers → bucket by `(sourceContainer, targetContainer)`. Direction matters: A→B and B→A are independent.
|
||||||
|
- Each aggregated edge carries `count` and `types` (set of edge types appearing in the bucket).
|
||||||
|
|
||||||
|
### 4.2 Visual
|
||||||
|
|
||||||
|
Reuse the styling pattern already in overview-level edge aggregation (`GraphView.tsx` line ~186):
|
||||||
|
|
||||||
|
- `strokeWidth: Math.min(1 + Math.log2(count + 1), 5)`
|
||||||
|
- Label: count number
|
||||||
|
- Color: existing `rgba(212,165,116,0.4)`
|
||||||
|
|
||||||
|
### 4.3 Expand / collapse
|
||||||
|
|
||||||
|
State (zustand store):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
expandedContainers: Set<string>; // currently expanded container ids
|
||||||
|
```
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
- **Click container** → toggle membership.
|
||||||
|
- **Click empty canvas** or `Esc` → clear all.
|
||||||
|
- **Multi-container expansion is allowed** (user comparing two folders' relationships).
|
||||||
|
|
||||||
|
When a container is expanded:
|
||||||
|
|
||||||
|
- Its inter-container aggregated edges (both directions) are replaced with the underlying file→file individual edges.
|
||||||
|
- Other containers' aggregated edges remain aggregated.
|
||||||
|
- Position re-layout is **not** triggered. Only React Flow's edge array changes.
|
||||||
|
|
||||||
|
### 4.4 Interactions with persona / diff
|
||||||
|
|
||||||
|
- **Persona filter** changes `count` (post-filter edges only). Aggregated edge re-derived in the memoized pipeline.
|
||||||
|
- **Diff mode**: aggregated edge containing any changed node → red stroke + animated; on expand, individual edges follow normal diff styling.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §5. Container Visual
|
||||||
|
|
||||||
|
### 5.1 New component: `ContainerNode`
|
||||||
|
|
||||||
|
A new React Flow node type `"container"` registered alongside the existing `custom` / `layer-cluster` / `portal`.
|
||||||
|
|
||||||
|
It does **not** reuse `LayerClusterNode` because:
|
||||||
|
|
||||||
|
- Click semantics differ (`LayerClusterNode` drills into a layer; `ContainerNode` toggles edge expansion).
|
||||||
|
- Metadata differs (`ContainerNode` does not carry `aggregateComplexity`).
|
||||||
|
|
||||||
|
Visual language is shared: rounded translucent box, gold border, DM Serif title.
|
||||||
|
|
||||||
|
### 5.2 Spec
|
||||||
|
|
||||||
|
| Element | Style |
|
||||||
|
|---|---|
|
||||||
|
| Border (default) | `1px solid rgba(212,165,116,0.25)` |
|
||||||
|
| Border (hover / expanded) | `1.5px rgba(212,165,116,0.6)`, expanded adds chevron `▾` |
|
||||||
|
| Background | `rgba(255,255,255,0.02)` |
|
||||||
|
| Corner radius | `12px` |
|
||||||
|
| Title | DM Serif, 14px, `#d4a574`, top-left padding `12px 16px` |
|
||||||
|
| Child-count badge | top-right chip, `#a39787`, 11px |
|
||||||
|
| Internal padding (around children) | `40px top / 20px L,R,B` |
|
||||||
|
|
||||||
|
### 5.3 Color coding
|
||||||
|
|
||||||
|
Container index modulo 12-color palette (same palette used for `layerColorIndex` in `LayerClusterNode`). Hue is applied at low saturation to border + title only — never to the body fill — so the palette doesn't overpower individual nodes inside.
|
||||||
|
|
||||||
|
### 5.4 State styles
|
||||||
|
|
||||||
|
| State | Visual |
|
||||||
|
|---|---|
|
||||||
|
| `default` | Base spec |
|
||||||
|
| `hover` | Brighter border, title underline |
|
||||||
|
| `expanded` | 1.5px gold border + chevron `▾` |
|
||||||
|
| `search-hit-inside` | Search badge in title row showing match count |
|
||||||
|
| `diff-affected` | Border swaps to `rgba(224,82,82,0.5)` |
|
||||||
|
| `focused-via-child` | Same as expanded plus brightness boost |
|
||||||
|
|
||||||
|
### 5.5 Label source
|
||||||
|
|
||||||
|
| Strategy | Label |
|
||||||
|
|---|---|
|
||||||
|
| `folder` | First path segment after LCP (e.g. `auth`) |
|
||||||
|
| `community` | `Cluster A`, `Cluster B`, ... ordered by community id |
|
||||||
|
| `~` (root) | `(root)` in dimmed style |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §6. Lazy Two-Stage Layout
|
||||||
|
|
||||||
|
### 6.1 State machine
|
||||||
|
|
||||||
|
```
|
||||||
|
[layer entered]
|
||||||
|
│
|
||||||
|
│ Stage 1: ELK on containers (always runs)
|
||||||
|
▼
|
||||||
|
[containers laid out, children unrendered]
|
||||||
|
│
|
||||||
|
├── click container ─────┐
|
||||||
|
├── zoom > 1.0 in viewport (200ms debounce, hysteresis) ─┤
|
||||||
|
└── search / focus / tour hit a child ─┘
|
||||||
|
▼
|
||||||
|
Stage 2 (per container)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[container expanded, children laid out + rendered]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Store extensions
|
||||||
|
|
||||||
|
```ts
|
||||||
|
expandedContainers: Set<string>;
|
||||||
|
containerLayoutCache: Map<string, {
|
||||||
|
childPositions: Map<string, { x: number; y: number }>;
|
||||||
|
actualSize: { width: number; height: number };
|
||||||
|
}>;
|
||||||
|
containerSizeMemory: Map<string, { width: number; height: number }>;
|
||||||
|
```
|
||||||
|
|
||||||
|
- `containerLayoutCache` invalidated by `(graphHash, containerId)`.
|
||||||
|
- `containerSizeMemory` persists across container collapses to prevent jitter on next expand.
|
||||||
|
|
||||||
|
### 6.3 Stage 1
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async function runStage1Layout(containers, aggregatedInterEdges, sizeMemory) {
|
||||||
|
const elkInput = {
|
||||||
|
id: "root",
|
||||||
|
children: containers.map(c => ({
|
||||||
|
id: c.id,
|
||||||
|
width: sizeMemory.get(c.id)?.width
|
||||||
|
?? Math.sqrt(c.nodeIds.length) * NODE_WIDTH * 1.2,
|
||||||
|
height: sizeMemory.get(c.id)?.height
|
||||||
|
?? Math.sqrt(c.nodeIds.length) * NODE_HEIGHT * 1.2,
|
||||||
|
})),
|
||||||
|
edges: aggregatedInterEdges.map(toElkEdge),
|
||||||
|
};
|
||||||
|
return runElk(elkInput);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Container size is estimated from `sqrt(childCount)` so it grows sub-linearly with content. If memory has the actual size from a previous run, that wins.
|
||||||
|
|
||||||
|
### 6.4 Stage 2
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async function runStage2Layout(container, intraEdges) {
|
||||||
|
if (containerLayoutCache.has(container.id)) {
|
||||||
|
return containerLayoutCache.get(container.id)!;
|
||||||
|
}
|
||||||
|
const elkInput = {
|
||||||
|
id: container.id,
|
||||||
|
children: container.nodeIds.map(toElkChild),
|
||||||
|
edges: intraEdges.filter(e => isWithin(container, e)).map(toElkEdge),
|
||||||
|
};
|
||||||
|
const result = await runElk(elkInput);
|
||||||
|
containerLayoutCache.set(container.id, result);
|
||||||
|
containerSizeMemory.set(container.id, result.actualSize);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If `result.actualSize` differs from the Stage 1 estimate by **> 20%** in either dimension, trigger a Stage 1 re-layout (full re-run; <100ms at this scale, so the user perceives a small reflow rather than two distinct layouts).
|
||||||
|
|
||||||
|
### 6.5 Auto-expand triggers
|
||||||
|
|
||||||
|
| Trigger | Implementation |
|
||||||
|
|---|---|
|
||||||
|
| Click | `onClick` toggles `expandedContainers` |
|
||||||
|
| Zoom | React Flow `onMove` listener (200ms debounce). When viewport zoom > 1.0, all containers in viewport added to `expandedContainers`. Hysteresis: containers don't auto-collapse until zoom < 0.6, preventing flapping. |
|
||||||
|
| Search / focus / tour | `useEffect` watches `searchResults` / `focusNodeId` / `tourHighlightedNodeIds`; finds the parent container of any matched leaf node and adds to `expandedContainers` |
|
||||||
|
|
||||||
|
### 6.6 Performance budget
|
||||||
|
|
||||||
|
| Operation | Target |
|
||||||
|
|---|---|
|
||||||
|
| Stage 1 (any layer) | < 100ms |
|
||||||
|
| Stage 2 (first expand of a container) | < 100ms |
|
||||||
|
| Stage 2 (cache hit) | < 5ms |
|
||||||
|
| Zoom-driven auto-expand | 200ms debounce |
|
||||||
|
| Stage 1 re-layout after >20% deviation | < 100ms (re-uses Stage 1 path) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §7. Interaction Matrix
|
||||||
|
|
||||||
|
| Existing feature | Behavior with new layout |
|
||||||
|
|---|---|
|
||||||
|
| Persona filter | Drives `nodeTypeFilters` dependency in Stage 1 memo. Filtered-out nodes don't enter container derivation; containers with all-filtered children disappear. |
|
||||||
|
| Diff mode | Container with a changed child gets red border (§5.4); aggregated edges containing a changed node animate red; on expand, individual diff styling applies. |
|
||||||
|
| Focus mode (1-hop) | Focus node's container auto-expands. Non-neighbor containers fade to opacity 0.2; their children remain unrendered. |
|
||||||
|
| Search | Container with a hit gets search badge in title; container does **not** auto-expand to avoid expanding many at once. Clicking the badge expands and `fitView`s. |
|
||||||
|
| Tour | Tour-highlighted child auto-expands its container. `TourFitView` fits to the highlighted leaf positions (cached after expand). |
|
||||||
|
| Drill-in (`overview → layer-detail`) | Unchanged. After drill-in, Stage 1 runs on the new layer's containers. |
|
||||||
|
| Breadcrumb | Containers do not enter the breadcrumb. Path remains `Project > LAYER`. |
|
||||||
|
| Code viewer | Unchanged. Click a file node inside a container → existing slide-up viewer. |
|
||||||
|
| WarningBanner | Layout repair issues feed the same banner. Fatal copy text augmented to differentiate render bugs from data bugs. |
|
||||||
|
| Export (PNG/SVG) | Captures current state including expanded containers. Filename includes layer name. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §8. Files & Test Plan
|
||||||
|
|
||||||
|
### 8.1 Files
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/dashboard/src/
|
||||||
|
├── utils/
|
||||||
|
│ ├── layout.ts [modify] add applyElkLayout export
|
||||||
|
│ ├── elk-layout.ts [new] runElk + repairElkInput + GraphIssue mapping
|
||||||
|
│ ├── containers.ts [new] deriveContainers (folder + community fallback)
|
||||||
|
│ ├── louvain.ts [new] thin wrapper around graphology-communities-louvain
|
||||||
|
│ └── edgeAggregation.ts [modify] add aggregateContainerEdges
|
||||||
|
├── components/
|
||||||
|
│ ├── ContainerNode.tsx [new] container box visual
|
||||||
|
│ ├── GraphView.tsx [modify] Stage 1 / Stage 2 wiring, expand state, auto-expand triggers
|
||||||
|
│ └── DomainGraphView.tsx [modify] dagre → ELK
|
||||||
|
├── store.ts [modify] expandedContainers, containerLayoutCache, containerSizeMemory
|
||||||
|
└── package.json [modify] add elkjs ^0.9, graphology, graphology-communities-louvain
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Test matrix
|
||||||
|
|
||||||
|
| Type | Target | Cases |
|
||||||
|
|---|---|---|
|
||||||
|
| Unit | `deriveContainers` | folder grouping happy path; all-in-root fallback; <2 buckets fallback; >70% concentration fallback; no-`filePath` nodes; single-child container suppression (gated by layer ≥ 3) |
|
||||||
|
| Unit | `aggregateContainerEdges` | empty edges; multiple same-direction edges merge; bidirectional edges split; intra + inter mix; types deduped |
|
||||||
|
| Unit | `repairElkInput` | each repair function in isolation; validates correct `GraphIssue` level emitted |
|
||||||
|
| Unit | `runElk` | minimal valid input; dev-mode strict throw; production graceful fatal; cancellation on dependency change |
|
||||||
|
| Integration | Stage 1 + Stage 2 flow | 50-node fixture; click → cache miss; second click → cache hit; size-deviation >20% → re-layout |
|
||||||
|
| Integration | Persona / focus / search interactions | switching persona reruns Stage 1; focusing a child auto-expands its container; search hit adds badge without auto-expanding |
|
||||||
|
| Visual regression (optional) | Playwright + microservices-demo fixture | baseline screenshots for overview, layer-detail, domain views |
|
||||||
|
|
||||||
|
### 8.3 Performance benchmarks
|
||||||
|
|
||||||
|
Generate fixtures with `scripts/generate-large-graph.mjs` at 500 / 1000 / 3000 nodes. Verify:
|
||||||
|
|
||||||
|
- Stage 1 < 200ms at 500 nodes; < 500ms at 3000 nodes.
|
||||||
|
- Stage 2 any container < 100ms.
|
||||||
|
|
||||||
|
If 3000-node Stage 1 misses the budget, revisit container size estimation or ELK config — do not lower the budget.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
None at this point. All decisions made during brainstorming are captured above.
|
||||||
|
|
||||||
|
## Migration Notes
|
||||||
|
|
||||||
|
- `applyDagreLayout` is kept in the codebase for one release after this lands, then removed in the next. This gives a fallback path during the rollout and a clean uninstall once stable.
|
||||||
|
- No graph data migration needed.
|
||||||
|
- New dependencies (elkjs, graphology, graphology-communities-louvain) are pure JS, no native bindings — safe across the supported platform matrix.
|
||||||
@@ -0,0 +1,587 @@
|
|||||||
|
# Semantic Batching and Output Chunking Design
|
||||||
|
|
||||||
|
**Date:** 2026-05-24
|
||||||
|
**Status:** Draft
|
||||||
|
**Branch:** `feat/semantic-batching-and-output-chunking`
|
||||||
|
**Issue:** [#159](https://github.com/Lum1104/Understand-Anything/issues/159) — Frequently seeing output limit exceeded
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The `/understand` skill's Phase 2 dispatches `file-analyzer` subagents in batches of 20-30 files each (`skills/understand/SKILL.md:282`). Two issues compound on output-constrained LLM backends (notably Bedrock OPUS with default max_tokens of 4096-8192):
|
||||||
|
|
||||||
|
1. **Output cap pressure.** Each `file-analyzer` writes one `batch-<N>.json` containing all nodes (file + functions + classes) and edges for its batch. For 25 dense files the JSON content easily exceeds the per-turn `Write(content=...)` token budget. The agent improvises by entering an undefined "minimal output mode" and drops nodes/edges silently. Issue #159 reports this for OPUS on Bedrock at the 100-file scale.
|
||||||
|
|
||||||
|
2. **Count-based batching breaks module semantics.** Files are batched by count, not by logical relationship. Files that import each other (and would together form an `auth` module, an `api` module, etc.) get split across batches. The file-analyzer only sees within-batch edges confidently; `calls`/`related`/`inherits`/`implements` edges between modules get dropped at batch boundaries.
|
||||||
|
|
||||||
|
The existing `recover_imports_from_scan` in `merge-batch-graphs.py:913` is a deterministic safety net for `imports` edges — but it cannot recover semantic edges (calls / related / inherits / implements). Those are lost.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Eliminate "Batch X failed (output limit)" from `/understand` runs on Bedrock OPUS for projects up to 500 files.
|
||||||
|
- Improve cross-batch semantic edge coverage by replacing count-based batching with Louvain community detection on the import graph.
|
||||||
|
- Maintain `imports` edge coverage parity (no regression on existing safety net).
|
||||||
|
- Stay within one PR — defer broader refactors to follow-ups (Section "Out of scope").
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Refactoring Phase 1 / 2 tree-sitter usage to deduplicate per-batch extraction.
|
||||||
|
- Adding LLM-generated file summaries to neighborMap.
|
||||||
|
- Auto-tuning output thresholds per provider.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Pipeline before:
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1 project-scanner → scan-result.json (files + importMap)
|
||||||
|
Phase 2 file-analyzer (×N concur) → batch-<i>.json (one per batch; SKILL.md prose batching)
|
||||||
|
Phase 2末 merge-batch-graphs.py → assembled-graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Pipeline after:
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1 project-scanner → scan-result.json (unchanged)
|
||||||
|
Phase 1.5 compute-batches.mjs → batches.json (NEW — semantic batching + neighborMap)
|
||||||
|
Phase 2 file-analyzer (×N concur) → batch-<i>.json (single) OR batch-<i>-part-<k>.json (split)
|
||||||
|
Phase 2末 merge-batch-graphs.py → assembled-graph.json (verified, no code change)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 1.5 single responsibility:** topology decision + neighborMap construction. Pure algorithm — reads `scan-result.json`, writes `batches.json`, no LLM calls.
|
||||||
|
|
||||||
|
**Phase 2 changes:** SKILL.md stops doing prose batching; iterates `batches.json` and dispatches one file-analyzer per batch.
|
||||||
|
|
||||||
|
**file-analyzer changes:** consumes neighborMap; self-checks output size before writing; splits into `batch-<i>-part-<k>.json` when above thresholds.
|
||||||
|
|
||||||
|
**merge-batch-graphs.py:** no code changes — the `batch-*.json` glob and sort-key regex already accept multi-part naming. Test fixture and stderr report enhancement added.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component 1 — `compute-batches.mjs`
|
||||||
|
|
||||||
|
**Location:** `understand-anything-plugin/skills/understand/compute-batches.mjs`
|
||||||
|
|
||||||
|
**Invocation:** `node <SKILL_DIR>/compute-batches.mjs $PROJECT_ROOT [--changed-files=<path>]`
|
||||||
|
|
||||||
|
**Input:** `$PROJECT_ROOT/.understand-anything/intermediate/scan-result.json`
|
||||||
|
|
||||||
|
**Output:** `$PROJECT_ROOT/.understand-anything/intermediate/batches.json`
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
Added to `understand-anything-plugin/package.json`:
|
||||||
|
|
||||||
|
- `graphology` (~10KB)
|
||||||
|
- `graphology-communities-louvain` (~30KB)
|
||||||
|
|
||||||
|
Reuses `@understand-anything/core`'s `TreeSitterPlugin` and `PluginRegistry` (already imported by `extract-structure.mjs`).
|
||||||
|
|
||||||
|
### Algorithm
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Load scan-result.json.
|
||||||
|
|
||||||
|
2. Partition files by fileCategory:
|
||||||
|
- codeFiles = files where fileCategory === "code"
|
||||||
|
- nonCodeFiles = the rest
|
||||||
|
|
||||||
|
3. Code batching (Louvain on import graph):
|
||||||
|
a. Build undirected graph: nodes = codeFiles, edges = importMap relations
|
||||||
|
(weight=1, undirected so import and imported-by both count).
|
||||||
|
b. Run graphology-communities-louvain → community assignment per file.
|
||||||
|
c. For any community with size > 35 (max): split via edge-betweenness greedy
|
||||||
|
cut (or simpler weakly-connected-component partition) until each
|
||||||
|
sub-community ≤ 35. Log warning per split.
|
||||||
|
(Whether this branch fires is decided by the implementation prototype
|
||||||
|
step — see "Prototype-first implementation" below.)
|
||||||
|
d. Communities with size < 5 are kept as-is. Wasted dispatches are
|
||||||
|
bounded by the 5-concurrent cap, and the alternative ("merge small")
|
||||||
|
adds edge cases without proportional value.
|
||||||
|
|
||||||
|
4. Non-code batching (hardcoded heuristics, moved from SKILL.md prose):
|
||||||
|
- Group A: For each directory containing a `Dockerfile`, bundle that
|
||||||
|
directory's `Dockerfile` + any `docker-compose.*` + any
|
||||||
|
`.dockerignore` → one batch per such directory (so multi-service
|
||||||
|
repos with several Dockerfiles get one batch per service).
|
||||||
|
- Group B: `.github/workflows/*.yml` files → one batch.
|
||||||
|
- Group C: `.gitlab-ci.yml` + files under `.circleci/` → one batch.
|
||||||
|
- Group D: SQL files under any `migrations/` or `migration/` directory,
|
||||||
|
sorted by filename → one batch per directory.
|
||||||
|
- Group E: All other non-code files grouped by their immediate parent
|
||||||
|
directory, max 20 per batch.
|
||||||
|
|
||||||
|
5. Assign batchIndex: code communities first (1..N), non-code groups
|
||||||
|
second (N+1..M).
|
||||||
|
|
||||||
|
6. Exports extraction:
|
||||||
|
- For each code file, run TreeSitterPlugin.extract() and collect
|
||||||
|
top-level exports (function names, class names, exported const names).
|
||||||
|
- Per-file failures: catch, set exports = [], emit warning.
|
||||||
|
- Non-code files: exports = [].
|
||||||
|
|
||||||
|
7. Construct neighborMap (1-hop):
|
||||||
|
For each file F in batch B:
|
||||||
|
neighborMap[F.path] = [
|
||||||
|
{ path: G.path, batchIndex: G.batch, symbols: G.exports }
|
||||||
|
for G in importMap[F.path] ∪ reverseImportMap[F.path]
|
||||||
|
where G.batch ≠ B
|
||||||
|
]
|
||||||
|
If neighborMap[F.path].length > 50, truncate to top 50 by neighbor
|
||||||
|
degree (highest-imported neighbors kept), emit warning.
|
||||||
|
|
||||||
|
8. Construct batchImportData:
|
||||||
|
For each batch B:
|
||||||
|
batchImportData[F.path] = importMap[F.path] for F in B.files
|
||||||
|
|
||||||
|
9. Write batches.json.
|
||||||
|
|
||||||
|
Fallback (script-internal): If steps 3a-3c throw, catch → emit warning
|
||||||
|
→ assign batches by alphabetical chunking (12 files per code batch).
|
||||||
|
Steps 4, 6, 7, 8 still run normally. Set `algorithm: "count-fallback"`
|
||||||
|
in the output.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Louvain implementation
|
||||||
|
|
||||||
|
Use `graphology-communities-louvain`'s default modularity-greedy algorithm:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import Graph from 'graphology';
|
||||||
|
import louvain from 'graphology-communities-louvain';
|
||||||
|
|
||||||
|
const graph = new Graph({ type: 'undirected' });
|
||||||
|
for (const file of codeFiles) graph.addNode(file.path);
|
||||||
|
for (const [src, targets] of Object.entries(importMap)) {
|
||||||
|
for (const tgt of targets) {
|
||||||
|
if (graph.hasNode(src) && graph.hasNode(tgt) && !graph.hasEdge(src, tgt)) {
|
||||||
|
graph.addEdge(src, tgt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const communities = louvain(graph); // { nodeId: communityId }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output schema (`batches.json`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"algorithm": "louvain",
|
||||||
|
"totalFiles": 100,
|
||||||
|
"totalBatches": 7,
|
||||||
|
"batches": [
|
||||||
|
{
|
||||||
|
"batchIndex": 1,
|
||||||
|
"files": [
|
||||||
|
{ "path": "src/auth/login.ts", "language": "typescript",
|
||||||
|
"sizeLines": 120, "fileCategory": "code" }
|
||||||
|
],
|
||||||
|
"batchImportData": {
|
||||||
|
"src/auth/login.ts": ["src/auth/session.ts", "src/db/users.ts"]
|
||||||
|
},
|
||||||
|
"neighborMap": {
|
||||||
|
"src/auth/login.ts": [
|
||||||
|
{ "path": "src/db/users.ts", "batchIndex": 3,
|
||||||
|
"symbols": ["User", "findById", "createUser"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`algorithm` is `"louvain"` on the happy path, `"count-fallback"` when the Louvain branch crashed.
|
||||||
|
|
||||||
|
### `--changed-files` mode
|
||||||
|
|
||||||
|
When invoked with `--changed-files=<path>`, the script:
|
||||||
|
|
||||||
|
- Loads file paths from `<path>` (one per line).
|
||||||
|
- Still builds the full project import graph (for accurate neighborMap construction).
|
||||||
|
- Only emits batches containing changed files.
|
||||||
|
- neighborMap entries reference unchanged files with their batchIndex from the deterministic full-graph Louvain re-run. The seed is fixed so the assignment is reproducible across incremental invocations.
|
||||||
|
|
||||||
|
### Prototype-first implementation
|
||||||
|
|
||||||
|
Before writing the full script, build a minimal skeleton:
|
||||||
|
|
||||||
|
1. Load `scan-result.json` from this repo's `.understand-anything/` directory (if absent, generate via `/understand --full`).
|
||||||
|
2. Run Louvain only — no size enforcement, no neighborMap.
|
||||||
|
3. Print community size distribution.
|
||||||
|
4. Decide: do real-world communities cluster in [5, 35]? If yes, size enforcement branch may be unnecessary or trivially defensive. If no, implement edge-betweenness split.
|
||||||
|
|
||||||
|
This gates the more speculative code (size enforcement) on empirical observation rather than upfront design.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component 2 — `skills/understand/SKILL.md` changes
|
||||||
|
|
||||||
|
### Add — Phase 1.5 section (after Phase 1)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Phase 1.5 — BATCH
|
||||||
|
|
||||||
|
Report: `[Phase 1.5/7] Computing semantic batches...`
|
||||||
|
|
||||||
|
Run the bundled batching script:
|
||||||
|
\`\`\`bash
|
||||||
|
node <SKILL_DIR>/compute-batches.mjs $PROJECT_ROOT
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Reads `.understand-anything/intermediate/scan-result.json`, writes
|
||||||
|
`.understand-anything/intermediate/batches.json`.
|
||||||
|
|
||||||
|
Capture stderr. Append any line starting with `Warning:` to
|
||||||
|
$PHASE_WARNINGS for the final report.
|
||||||
|
|
||||||
|
If the script exits non-zero, the failure is hard — relay the full
|
||||||
|
stderr to the user as a Phase 1.5 failure. Do not attempt to recover;
|
||||||
|
the script's internal fallback (count-based) already handles recoverable
|
||||||
|
issues. A non-zero exit means a fundamental problem (missing input file,
|
||||||
|
malformed JSON, etc.).
|
||||||
|
```
|
||||||
|
|
||||||
|
### Replace — Phase 2 ANALYZE section (current SKILL.md:280-332)
|
||||||
|
|
||||||
|
Delete the existing "Batch the file list from Phase 1 into groups of 20-30 files each" prose, the non-code grouping prose (now in compute-batches), and the dispatch-time `batchImportData` construction prose (now provided in batches.json). Replace with:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Phase 2 — ANALYZE
|
||||||
|
|
||||||
|
### Full analysis path
|
||||||
|
|
||||||
|
Load `.understand-anything/intermediate/batches.json` (produced by
|
||||||
|
Phase 1.5). Iterate the `batches[]` array.
|
||||||
|
|
||||||
|
Report: `[Phase 2/7] Analyzing files — <totalFiles> files in
|
||||||
|
<totalBatches> batches (up to 5 concurrent)...`
|
||||||
|
|
||||||
|
For each batch, dispatch a `file-analyzer` subagent (up to 5
|
||||||
|
concurrent). Dispatch prompt template:
|
||||||
|
|
||||||
|
> Analyze these files and produce GraphNode and GraphEdge objects.
|
||||||
|
> Project root: `$PROJECT_ROOT`
|
||||||
|
> Project: `<projectName>`
|
||||||
|
> Languages: `<languages>`
|
||||||
|
> Batch: `<batchIndex>/<totalBatches>`
|
||||||
|
> Skill directory: `<SKILL_DIR>`
|
||||||
|
> Output: write to
|
||||||
|
> `$PROJECT_ROOT/.understand-anything/intermediate/batch-<batchIndex>.json`
|
||||||
|
> (single-file mode) OR `batch-<batchIndex>-part-<k>.json` (split mode,
|
||||||
|
> per Step B of your output protocol).
|
||||||
|
>
|
||||||
|
> Pre-resolved import data (use directly — do NOT re-resolve from source):
|
||||||
|
> \`\`\`json
|
||||||
|
> <batchImportData JSON inline from batches.json[i].batchImportData>
|
||||||
|
> \`\`\`
|
||||||
|
>
|
||||||
|
> Cross-batch neighbors with their exported symbols (confidence boost
|
||||||
|
> for cross-batch edges):
|
||||||
|
> \`\`\`json
|
||||||
|
> <neighborMap JSON inline from batches.json[i].neighborMap>
|
||||||
|
> \`\`\`
|
||||||
|
>
|
||||||
|
> Files to analyze:
|
||||||
|
> 1. `<path>` (<sizeLines> lines, language: `<language>`,
|
||||||
|
> fileCategory: `<fileCategory>`)
|
||||||
|
> ...
|
||||||
|
|
||||||
|
$LANGUAGE_DIRECTIVE
|
||||||
|
|
||||||
|
After ALL batches complete, run the merge-and-normalize script:
|
||||||
|
\`\`\`bash
|
||||||
|
python <SKILL_DIR>/merge-batch-graphs.py $PROJECT_ROOT
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
(Rest of Phase 2 unchanged.)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Replace — Incremental update path (current SKILL.md:355-366)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Incremental update path
|
||||||
|
|
||||||
|
Run compute-batches.mjs with `--changed-files=<path>`, where `<path>`
|
||||||
|
is a temp file listing changed file paths (one per line). The script
|
||||||
|
reuses the full project's import graph for neighborMap computation
|
||||||
|
but only emits batches containing changed files. Dispatch file-analyzer
|
||||||
|
subagents per the same template as the full path.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Line budget
|
||||||
|
|
||||||
|
Net added LLM-context prose: Phase 1.5 (~12 lines) + Phase 2 template clarifications (~5 lines) − removed batching prose (~15 lines) − removed batchImportData construction prose (~6 lines) ≈ **−4 lines**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component 3 — `agents/file-analyzer.md` changes
|
||||||
|
|
||||||
|
### Add — Cross-batch context section
|
||||||
|
|
||||||
|
Insert after "Step 1: Input file construction":
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Cross-batch context (neighborMap)
|
||||||
|
|
||||||
|
Your dispatch prompt includes a `neighborMap` — for each file in your
|
||||||
|
batch, it lists project-internal neighbors in OTHER batches (files that
|
||||||
|
import yours or that you import), with their exported symbols.
|
||||||
|
|
||||||
|
Use neighborMap as a confidence boost for cross-batch edges (`calls`,
|
||||||
|
`related`, `inherits`, `implements` to nodes outside your batch):
|
||||||
|
|
||||||
|
- If your source clearly references a symbol that appears in some
|
||||||
|
`neighbor.symbols`, emit the edge to
|
||||||
|
`function:<neighbor.path>:<symbol>` or
|
||||||
|
`class:<neighbor.path>:<symbol>` with confidence.
|
||||||
|
- If your source references a cross-batch symbol that is NOT in
|
||||||
|
neighborMap (the project-scanner may not have extracted it), you may
|
||||||
|
still emit the edge if you saw it explicitly in the imported file's
|
||||||
|
surface — but prefer matching neighborMap symbols when available.
|
||||||
|
- Imports continue to use `batchImportData` (fully resolved), not
|
||||||
|
neighborMap.
|
||||||
|
|
||||||
|
The merge script's dangling-edge dropper is the safety net for
|
||||||
|
genuinely unresolvable targets.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Replace — Writing Results section (current file-analyzer.md:467-475)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Writing Results — single or multi-part
|
||||||
|
|
||||||
|
**Step A — Compute totals.**
|
||||||
|
\`\`\`
|
||||||
|
nodeCount = nodes.length
|
||||||
|
edgeCount = edges.length
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**Step B — Decide split.**
|
||||||
|
- If `nodeCount ≤ 60` AND `edgeCount ≤ 120`: write ONE file to
|
||||||
|
`.understand-anything/intermediate/batch-<batchIndex>.json`. Done.
|
||||||
|
Skip to Step E.
|
||||||
|
- Otherwise: `parts = ceil(max(nodeCount / 60, edgeCount / 120))`.
|
||||||
|
|
||||||
|
**Step C — Partition.**
|
||||||
|
Sort files in your batch alphabetically by path. Chunk them sequentially
|
||||||
|
into `parts` groups of size `ceil(N / parts)`. For each part:
|
||||||
|
- All nodes whose `filePath` is in this part's files (for non-file
|
||||||
|
nodes like `module`/`concept`, use the file they belong to).
|
||||||
|
- All edges whose `source` is in this part's nodes (target may be
|
||||||
|
anywhere — same part, different part of same batch, different batch).
|
||||||
|
|
||||||
|
**Step D — Write each part.**
|
||||||
|
Write part `k` (1-indexed) to
|
||||||
|
`.understand-anything/intermediate/batch-<batchIndex>-part-<k>.json`.
|
||||||
|
Each part is a valid GraphFragment: `{ "nodes": [...], "edges": [...] }`.
|
||||||
|
|
||||||
|
**Step E — Self-validate.**
|
||||||
|
For each file written, verify:
|
||||||
|
- Valid JSON.
|
||||||
|
- `nodes` array exists and is well-formed.
|
||||||
|
- For every edge: `source` and `target` both appear as either (a) a
|
||||||
|
node `id` in this part's nodes, OR (b) a `file:<path>` reference
|
||||||
|
where `<path>` is in `neighborMap` or `batchImportData`, OR (c) a
|
||||||
|
`function:<path>:<symbol>` / `class:<path>:<symbol>` reference where
|
||||||
|
`<symbol>` is in some `neighbor.symbols`.
|
||||||
|
|
||||||
|
If validation fails on a part, do NOT silently rebuild. Respond with
|
||||||
|
an explicit error stating which part failed, which edge(s) failed
|
||||||
|
validation, and why. The dispatching session can then retry.
|
||||||
|
|
||||||
|
**Step F — Respond.**
|
||||||
|
Respond with ONLY a brief text summary: parts written (1 or more),
|
||||||
|
total nodes/edges across all parts, any files skipped. Do NOT include
|
||||||
|
JSON content in the response.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Threshold rationale
|
||||||
|
|
||||||
|
`60 nodes / 120 edges per part` derives from:
|
||||||
|
|
||||||
|
- File node JSON serialized ≈ 150-300 chars; function/class ≈ 80-150 chars; edge ≈ 100-150 chars.
|
||||||
|
- 60 nodes + 120 edges ≈ 25-35KB JSON ≈ 7000-9000 output tokens (JSON tokenization is dense).
|
||||||
|
- Bedrock OPUS default `max_tokens` 4096-8192 → ~10% safety margin.
|
||||||
|
|
||||||
|
These constants live as file-analyzer.md prose for now. Auto-tuning per provider is deferred to follow-up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component 4 — `merge-batch-graphs.py` (verify-only)
|
||||||
|
|
||||||
|
### Confirmed compatibility
|
||||||
|
|
||||||
|
The existing glob and sort-key already handle multi-part files transparently:
|
||||||
|
|
||||||
|
- `intermediate_dir.glob("batch-*.json")` matches `batch-3-part-1.json`.
|
||||||
|
- `re.search(r"batch-(\d+)", p.stem)` extracts `3` from `batch-3-part-1`, giving the same sort key as `batch-3.json`. Python `sorted` is stable, so parts load in lexicographic tie-break order.
|
||||||
|
- `merge_and_normalize` walks `all_nodes.extend(...)` / `all_edges.extend(...)`; load order does not affect dedup correctness.
|
||||||
|
- `recover_imports_from_scan` operates on the merged graph — transparent to multi-part inputs.
|
||||||
|
- `link_tests` operates on the merged node pool — transparent.
|
||||||
|
|
||||||
|
No code change required for correctness.
|
||||||
|
|
||||||
|
### Add — Multi-part awareness in stderr report
|
||||||
|
|
||||||
|
`merge-batch-graphs.py:1026` currently prints `Found {N} batch files:`. Enhance:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from collections import defaultdict
|
||||||
|
by_batch = defaultdict(list)
|
||||||
|
for f in batch_files:
|
||||||
|
m = re.match(r"batch-(\d+)(?:-part-(\d+))?\.json", f.name)
|
||||||
|
if m:
|
||||||
|
by_batch[int(m.group(1))].append(f.name)
|
||||||
|
|
||||||
|
logical_count = len(by_batch)
|
||||||
|
multi_part = sum(1 for files in by_batch.values() if len(files) > 1)
|
||||||
|
print(
|
||||||
|
f"Found {len(batch_files)} batch files "
|
||||||
|
f"({logical_count} logical batches, {multi_part} multi-part)",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add — Missing-part warning
|
||||||
|
|
||||||
|
After grouping, detect logical batches with non-contiguous part numbers (e.g. parts `{2, 3}` present but `1` missing) and emit:
|
||||||
|
|
||||||
|
```
|
||||||
|
Warning: merge: batch <i> has parts {<set>} but missing part {<missing>}
|
||||||
|
— possible truncated write — affected nodes/edges may be lost
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Failure modes & observability
|
||||||
|
|
||||||
|
| Failure point | Behavior | Safety net | Required warning text |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Louvain library throws | exception | Script-internal: catch → count-based fallback (12 files/batch); neighborMap still built | `Warning: compute-batches: Louvain failed (<msg>) — falling back to count-based grouping (12 files/batch) — module semantic boundaries lost` |
|
||||||
|
| tree-sitter exports per-file failure | empty exports | symbols=[] in neighborMap | `Warning: compute-batches: exports extraction failed for <path> (<msg>) — symbols=[] in neighborMap — cross-batch edges to this file limited to file-level` |
|
||||||
|
| Louvain produces oversized community | size > 35 | Edge-betweenness split | `Warning: compute-batches: community size <N> > max 35 — splitting via edge-betweenness — modularity may decrease` |
|
||||||
|
| compute-batches complete crash | exit non-zero, no batches.json | SKILL.md surfaces full stderr to user; no Phase 2 fallback | (script's own error to stderr; SKILL.md relays verbatim) |
|
||||||
|
| neighborMap truncation | > 50 neighbors | Top-50 by degree kept | `Warning: compute-batches: neighborMap for <path> truncated from <N> to top 50 (by neighbor degree)` |
|
||||||
|
| file-analyzer part JSON malformed | `load_batch` skips | Existing `load_batch:139` warns and skips | (existing — verify the warning is not swallowed) |
|
||||||
|
| Missing part in multi-part batch | gap in parts | merge detects and warns | `Warning: merge: batch <i> has parts {<set>} but missing part {<missing>} — possible truncated write — affected nodes/edges may be lost` |
|
||||||
|
| file-analyzer dangling edges | source/target missing | merge drops, adds to `unfixable` (existing) | (existing) |
|
||||||
|
| file-analyzer dispatch fails | subagent error | existing retry-once mechanism | (existing) |
|
||||||
|
|
||||||
|
### Observability invariant
|
||||||
|
|
||||||
|
Every fallback / degrade / drop MUST:
|
||||||
|
|
||||||
|
1. Write a stderr line in `Warning: <component>: <what happened> — <why> — <impact>` format.
|
||||||
|
2. Bubble up to `$PHASE_WARNINGS` (SKILL.md existing mechanism) → user-facing Phase 7 final report.
|
||||||
|
3. Never use silent `catch {}` / `except: pass`. Code review treats this as a blocker.
|
||||||
|
|
||||||
|
### Invariants
|
||||||
|
|
||||||
|
1. **scan-result.json is source of truth.** Any batching/topology change preserves importMap; `recover_imports_from_scan` always restores `imports` edges.
|
||||||
|
2. **Dangling-edge dropper is final defense.** No batch-generated edge can connect to a nonexistent node in the assembled graph.
|
||||||
|
3. **No silent fallback.** `batches.json` missing → loud failure. Internal compute-batches fallback → loud warning that bubbles to user.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Unit tests — `compute-batches.mjs`
|
||||||
|
|
||||||
|
New file: `understand-anything-plugin/skills/understand/test_compute_batches.test.mjs` (Vitest).
|
||||||
|
|
||||||
|
Required cases:
|
||||||
|
|
||||||
|
- **Louvain basic:** 3 disjoint cliques → 3 batches.
|
||||||
|
- **Empty importMap:** independent files → count-fallback batches by alphabetical chunking.
|
||||||
|
- **Oversized community:** 50-node complete graph → split triggered, all sub-batches ≤ 35.
|
||||||
|
- **Non-code grouping A:** `Dockerfile` + `docker-compose.yml` + `.dockerignore` siblings → one batch per directory cluster.
|
||||||
|
- **Non-code grouping B:** `.github/workflows/*.yml` → one batch.
|
||||||
|
- **Non-code grouping C:** SQL migrations under `migrations/` → one batch per directory.
|
||||||
|
- **Mixed code + non-code:** non-code batchIndex follows code batches.
|
||||||
|
- **neighborMap correctness:** file A imports file B across batches → `neighborMap[A]` contains `{path: B, batchIndex: B's, symbols: B's exports}`.
|
||||||
|
- **neighborMap excludes same-batch:** A and C in same batch → `neighborMap[A]` does not contain C.
|
||||||
|
- **Exports failure tolerance:** mock TreeSitter to throw on one file → `exports = []` for that file, others unaffected.
|
||||||
|
- **`--changed-files`:** input subset → output contains only batches with changed files; neighborMap may reference unchanged files.
|
||||||
|
- **Fallback triggers:** mock Louvain throw → `algorithm` field = `"count-fallback"`, warning in stderr.
|
||||||
|
- **Warning assertion per fallback:** for each of {Louvain crash, exports failure, oversize split, neighborMap truncation}, assert the exact warning string appears in stderr.
|
||||||
|
|
||||||
|
### Unit tests — `merge-batch-graphs.py`
|
||||||
|
|
||||||
|
New test class `TestMultiPart` in `test_merge_batch_graphs.py`:
|
||||||
|
|
||||||
|
- Two parts of one logical batch: `batch-1-part-1.json` + `batch-1-part-2.json` → assembled contains all nodes/edges from both.
|
||||||
|
- Three parts of one logical batch.
|
||||||
|
- Cross-part edges: edge with source in part-1, target node in part-2 → connected after merge.
|
||||||
|
- Malformed part-1 + valid part-2: part-1 skipped with warning, part-2 contents present.
|
||||||
|
- Mixed single-batch and multi-part inputs.
|
||||||
|
- Missing part detection: `batch-1-part-2.json` + `batch-1-part-3.json` (no part-1) → warning emitted with exact text.
|
||||||
|
- stderr format: assert `"X logical batches, Y multi-part"` appears.
|
||||||
|
|
||||||
|
### Integration — PR acceptance gate (manual)
|
||||||
|
|
||||||
|
Documented in the PR's Test plan:
|
||||||
|
|
||||||
|
- [ ] `pnpm install` (graphology installs cleanly).
|
||||||
|
- [ ] `pnpm --filter @understand-anything/core build`.
|
||||||
|
- [ ] Run `/understand --full` on this repo (Understand-Anything itself):
|
||||||
|
- `batches.json` generated; community size distribution sanity-check (mix of small and medium batches).
|
||||||
|
- At least one batch produces multi-part output.
|
||||||
|
- `assembled-graph.json` node/edge counts within expected range vs current main.
|
||||||
|
- Dashboard renders normally.
|
||||||
|
- Phase 7 final report includes any `$PHASE_WARNINGS` from compute-batches (visually verify warnings reach user-facing output, not just stderr).
|
||||||
|
- [ ] Run on a ~100-file repo matching ayushghosh's scenario; confirm no "output limit" errors.
|
||||||
|
- [ ] Run on a 5-10 file small repo: fallback path (all one batch) works correctly.
|
||||||
|
|
||||||
|
### Not tested
|
||||||
|
|
||||||
|
- Louvain algorithm correctness (trust `graphology-communities-louvain`'s own tests).
|
||||||
|
- Performance benchmarks (sub-second on 100-500 files is empirical; not gated).
|
||||||
|
- Multiple LLM provider output-cap variations (thresholds are conservative for Bedrock OPUS; first-party Anthropic is more permissive).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Out of scope (tracked for follow-up)
|
||||||
|
|
||||||
|
### Tree-sitter deduplication
|
||||||
|
|
||||||
|
Currently Phase 1 (project-scanner), Phase 1.5 (compute-batches), and Phase 2 (file-analyzer per-batch) each run tree-sitter independently. Consolidating into a single Phase 1.5 structure extraction would simplify file-analyzer and save time on large projects. Defer because it requires reorganizing file-analyzer's protocol significantly.
|
||||||
|
|
||||||
|
### neighborMap LLM summaries
|
||||||
|
|
||||||
|
Adding one-sentence summaries per file to neighborMap would enable file-analyzer to emit `related` edges across batches with semantic justification. Requires a new lightweight summary-pass agent; defer until the tree-sitter dedup lands (Phase 1.5 will already have full structure → cheaper to add).
|
||||||
|
|
||||||
|
### Adaptive thresholds
|
||||||
|
|
||||||
|
`60 nodes / 120 edges` are conservative for Bedrock OPUS. Anthropic first-party supports much larger output caps. Adding a `--output-cap=<N>` CLI to compute-batches and propagating to file-analyzer would unlock larger parts on permissive backends. Track real-world part counts before implementing.
|
||||||
|
|
||||||
|
### Cross-batch edge audit
|
||||||
|
|
||||||
|
A post-merge audit comparing neighborMap-suggested edges vs actually-emitted edges would surface gaps. Mirror the existing `recover_imports_from_scan` pattern. Requires preserving `batches.json` for merge-time consumption.
|
||||||
|
|
||||||
|
### Multi-language monorepo handling
|
||||||
|
|
||||||
|
Multi-language repos (TS + Python) tend to naturally split via Louvain (no cross-language imports). Bridge files (OpenAPI, protobuf) might create odd communities. Address only if real reports surface.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation order
|
||||||
|
|
||||||
|
1. **Prototype:** minimal `compute-batches.mjs` skeleton — load scan-result.json, run Louvain, print community sizes. Run against this repo's `scan-result.json` (generate if missing via `/understand --full`). Decide whether size-enforcement branch is needed; if needed, choose between edge-betweenness and weakly-connected-component split.
|
||||||
|
2. Add exports extraction (reuse TreeSitterPlugin).
|
||||||
|
3. Add neighborMap construction + batchImportData passthrough.
|
||||||
|
4. Add non-code grouping heuristics (Groups A-E).
|
||||||
|
5. Add fallback path + warning emissions for every failure mode listed in the Failure modes table.
|
||||||
|
6. Write unit tests for compute-batches (per Testing section), including warning-text assertions.
|
||||||
|
7. Modify `agents/file-analyzer.md` — add Cross-batch context section, replace Writing Results.
|
||||||
|
8. Modify `skills/understand/SKILL.md` — add Phase 1.5, replace Phase 2 ANALYZE batching prose, replace incremental path.
|
||||||
|
9. Add multi-part stderr report + missing-part warning to `merge-batch-graphs.py`.
|
||||||
|
10. Write unit tests for `merge-batch-graphs.py` multi-part handling.
|
||||||
|
11. Add `graphology` + `graphology-communities-louvain` to `understand-anything-plugin/package.json`.
|
||||||
|
12. Run integration acceptance gate.
|
||||||
|
13. Bump version in all five `package.json` / `plugin.json` files per the project's CLAUDE.md versioning rule.
|
||||||
56
Understand-Anything-main/eslint.config.mjs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import eslint from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import globals from 'globals';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
'**/node_modules/**',
|
||||||
|
'**/dist/**',
|
||||||
|
'**/build/**',
|
||||||
|
'**/public/**',
|
||||||
|
'**/coverage/**',
|
||||||
|
'**/.understand-anything/**',
|
||||||
|
'**/.claude-plugin/**',
|
||||||
|
'**/.cursor-plugin/**',
|
||||||
|
'**/.copilot-plugin/**',
|
||||||
|
'**/.astro/**',
|
||||||
|
'.private/**',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
destructuredArrayIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-irregular-whitespace': ['error', { skipComments: true }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['understand-anything-plugin/packages/dashboard/**/*.{ts,tsx,js,jsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.test.ts', '**/*.test.tsx', '**/*.test.mjs', '**/__tests__/**/*.{ts,tsx,mjs}'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
24
Understand-Anything-main/homepage/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# build output
|
||||||
|
dist/
|
||||||
|
# generated types
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# macOS-specific files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# jetbrains setting folder
|
||||||
|
.idea/
|
||||||
4
Understand-Anything-main/homepage/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["astro-build.astro-vscode"],
|
||||||
|
"unwantedRecommendations": []
|
||||||
|
}
|
||||||
11
Understand-Anything-main/homepage/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"command": "./node_modules/.bin/astro dev",
|
||||||
|
"name": "Development server",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
43
Understand-Anything-main/homepage/README.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Astro Starter Kit: Minimal
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm create astro@latest -- --template minimal
|
||||||
|
```
|
||||||
|
|
||||||
|
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||||
|
|
||||||
|
## 🚀 Project Structure
|
||||||
|
|
||||||
|
Inside of your Astro project, you'll see the following folders and files:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/
|
||||||
|
├── public/
|
||||||
|
├── src/
|
||||||
|
│ └── pages/
|
||||||
|
│ └── index.astro
|
||||||
|
└── package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||||
|
|
||||||
|
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||||
|
|
||||||
|
Any static assets, like images, can be placed in the `public/` directory.
|
||||||
|
|
||||||
|
## 🧞 Commands
|
||||||
|
|
||||||
|
All commands are run from the root of the project, from a terminal:
|
||||||
|
|
||||||
|
| Command | Action |
|
||||||
|
| :------------------------ | :----------------------------------------------- |
|
||||||
|
| `pnpm install` | Installs dependencies |
|
||||||
|
| `pnpm dev` | Starts local dev server at `localhost:4321` |
|
||||||
|
| `pnpm build` | Build your production site to `./dist/` |
|
||||||
|
| `pnpm preview` | Preview your build locally, before deploying |
|
||||||
|
| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||||
|
| `pnpm astro -- --help` | Get help using the Astro CLI |
|
||||||
|
|
||||||
|
## 👀 Want to learn more?
|
||||||
|
|
||||||
|
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||||
7
Understand-Anything-main/homepage/astro.config.mjs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({
|
||||||
|
site: 'https://understand-anything.com',
|
||||||
|
});
|
||||||
17
Understand-Anything-main/homepage/package.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "homepage",
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.12.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"astro": "astro"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"astro": "^6.1.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
0
Understand-Anything-main/homepage/public/.gitkeep
Normal file
1
Understand-Anything-main/homepage/public/CNAME
Normal file
@@ -0,0 +1 @@
|
|||||||
|
understand-anything.com
|
||||||
BIN
Understand-Anything-main/homepage/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 655 B |
4
Understand-Anything-main/homepage/public/favicon.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<rect width="32" height="32" rx="6" fill="#0a0a0a"/>
|
||||||
|
<text x="16" y="23" font-family="Georgia, serif" font-size="20" fill="#d4a574" text-anchor="middle" font-weight="bold">U</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 253 B |
BIN
Understand-Anything-main/homepage/public/images/hero.jpg
Normal file
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 8.0 MiB |
|
After Width: | Height: | Size: 10 MiB |
BIN
Understand-Anything-main/homepage/public/images/overview.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
@@ -0,0 +1,143 @@
|
|||||||
|
---
|
||||||
|
const youTubeId = 'VmIUXVlt7_I';
|
||||||
|
const youTubeUrl = `https://www.youtube.com/watch?v=${youTubeId}`;
|
||||||
|
const embedUrl = `https://www.youtube.com/embed/${youTubeId}?si=IB3cjpjbq9wis5D7`;
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="community-video">
|
||||||
|
<span class="community-video-label reveal">Community</span>
|
||||||
|
<h2 class="community-video-heading reveal">
|
||||||
|
A walkthrough from the
|
||||||
|
<span style="display:inline;background:linear-gradient(135deg,#b8865c,#d4a574);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;">community</span>
|
||||||
|
</h2>
|
||||||
|
<p class="community-video-desc reveal">
|
||||||
|
A community-made video tour by
|
||||||
|
<a class="community-video-credit" href={youTubeUrl} target="_blank" rel="noopener noreferrer">Better Stack</a>.
|
||||||
|
</p>
|
||||||
|
<div class="community-video-frame reveal reveal-delay-1">
|
||||||
|
<div class="community-video-titlebar">
|
||||||
|
<span class="dot red"></span>
|
||||||
|
<span class="dot yellow"></span>
|
||||||
|
<span class="dot green"></span>
|
||||||
|
<span class="community-video-titlebar-text">YouTube · Better Stack</span>
|
||||||
|
</div>
|
||||||
|
<div class="community-video-iframe-wrap">
|
||||||
|
<iframe
|
||||||
|
src={embedUrl}
|
||||||
|
title="Community video by Better Stack — Understand Anything walkthrough"
|
||||||
|
class="community-video-iframe"
|
||||||
|
loading="lazy"
|
||||||
|
frameborder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
referrerpolicy="strict-origin-when-cross-origin"
|
||||||
|
allowfullscreen
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.community-video {
|
||||||
|
padding: 6rem 2rem 4rem;
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-video-label {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #d4a574;
|
||||||
|
border: 1px solid rgba(212, 165, 116, 0.3);
|
||||||
|
padding: 0.3rem 1rem;
|
||||||
|
border-radius: 100px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-video-heading {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(1.75rem, 4.5vw, 3rem);
|
||||||
|
color: var(--text);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-video-desc {
|
||||||
|
font-size: clamp(0.95rem, 1.5vw, 1.05rem);
|
||||||
|
color: var(--text-muted);
|
||||||
|
max-width: 640px;
|
||||||
|
margin: 0 auto 2.5rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-video-credit {
|
||||||
|
color: #d4a574;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dashed rgba(212, 165, 116, 0.4);
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-video-credit:hover {
|
||||||
|
border-bottom-color: #d4a574;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-video-frame {
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid rgba(212, 165, 116, 0.2);
|
||||||
|
box-shadow:
|
||||||
|
0 0 60px rgba(212, 165, 116, 0.08),
|
||||||
|
0 25px 50px rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-video-titlebar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: rgba(20, 20, 20, 0.8);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-video-titlebar-text {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.red { background: #ff5f57; }
|
||||||
|
.dot.yellow { background: #ffbd2e; }
|
||||||
|
.dot.green { background: #28c840; }
|
||||||
|
|
||||||
|
.community-video-iframe-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
background: #0a0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-video-iframe {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.community-video { padding: 4rem 1rem 3rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
109
Understand-Anything-main/homepage/src/components/Features.astro
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
---
|
||||||
|
const features = [
|
||||||
|
{
|
||||||
|
icon: '◈',
|
||||||
|
title: 'Interactive Knowledge Graph',
|
||||||
|
description: 'Explorable graph with hierarchical drill-down, smart layout, and community clustering.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '⬡',
|
||||||
|
title: 'Beyond Code Analysis',
|
||||||
|
description: 'Dockerfiles, Terraform, SQL, Markdown, and 26+ file types in one unified graph.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '⊘',
|
||||||
|
title: 'Smart Filtering & Search',
|
||||||
|
description: 'Filter by type, complexity, or layer. Fuzzy and semantic search to find anything.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '⎙',
|
||||||
|
title: 'Export & Share',
|
||||||
|
description: 'High-quality PNG, SVG, or filtered JSON for docs, presentations, or analysis.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '⟿',
|
||||||
|
title: 'Dependency Path Finder',
|
||||||
|
description: 'Shortest path between any two components. See how your system connects.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '⟐',
|
||||||
|
title: 'Guided Tours & Onboarding',
|
||||||
|
description: 'AI-generated walkthroughs that teach the codebase step by step.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="features">
|
||||||
|
<div class="features-grid">
|
||||||
|
{features.map((f, i) => (
|
||||||
|
<div class={`feature-card reveal reveal-delay-${(i % 2) + 1}`}>
|
||||||
|
<span class="feature-icon grad">{f.icon}</span>
|
||||||
|
<div class="feature-body">
|
||||||
|
<h3 class="feature-title">{f.title}</h3>
|
||||||
|
<p class="feature-desc">{f.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.features {
|
||||||
|
padding: 4rem 2rem 8rem;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.features-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1.25rem;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 2rem;
|
||||||
|
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:hover {
|
||||||
|
border-color: rgba(212, 165, 116, 0.25);
|
||||||
|
box-shadow: 0 0 24px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-body {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-title {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--text);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-desc {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.features { padding: 2rem 1rem 6rem; }
|
||||||
|
.features-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
|
||||||
|
const discordUrl = 'https://discord.gg/pydat66RY';
|
||||||
|
const contactEmail = 'lum@understand-anything.com';
|
||||||
|
---
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="footer-inner">
|
||||||
|
<span class="footer-logo">Understand Anything</span>
|
||||||
|
<div class="footer-links">
|
||||||
|
<a href={githubUrl} target="_blank" rel="noopener noreferrer">GitHub</a>
|
||||||
|
<span class="footer-sep">·</span>
|
||||||
|
<a href={discordUrl} target="_blank" rel="noopener noreferrer">Discord</a>
|
||||||
|
<span class="footer-sep">·</span>
|
||||||
|
<a href="/demo/">Live Demo</a>
|
||||||
|
<span class="footer-sep">·</span>
|
||||||
|
<a href={`mailto:${contactEmail}`}>Contact</a>
|
||||||
|
<span class="footer-sep">·</span>
|
||||||
|
<a href={`${githubUrl}/blob/main/LICENSE`} target="_blank" rel="noopener noreferrer">MIT License</a>
|
||||||
|
</div>
|
||||||
|
<p class="footer-note">Graphs that teach. Built for AI coding assistants.</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.footer {
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-inner {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-logo {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.15rem;
|
||||||
|
color: var(--text);
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links a {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links a:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-sep {
|
||||||
|
color: var(--border);
|
||||||
|
margin: 0 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-note {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.footer { padding: 3rem 1.25rem; }
|
||||||
|
.footer-sep { display: none; }
|
||||||
|
.footer-links { gap: 0.25rem 1rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
584
Understand-Anything-main/homepage/src/components/Hero.astro
Normal file
@@ -0,0 +1,584 @@
|
|||||||
|
---
|
||||||
|
const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
|
||||||
|
const discordUrl = 'https://discord.gg/pydat66RY';
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<div class="hero-bg">
|
||||||
|
<img src="/images/hero.jpg" alt="" class="hero-bg-img" loading="eager" />
|
||||||
|
<div class="hero-overlay"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hero-content">
|
||||||
|
<span class="hero-badge anim anim-1">AI-Powered Code Understanding</span>
|
||||||
|
|
||||||
|
<h1 class="hero-title anim anim-2">
|
||||||
|
<span style="display:inline-block;background:linear-gradient(135deg,#c9867a,#b8865c,#d4a574);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;">Understand</span> Anything
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p class="hero-tagline anim anim-3">
|
||||||
|
Other tools show you a <span class="hero-problem">hairball</span>.
|
||||||
|
We <span class="hero-value">teach</span> you the codebase.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="hero-pillars anim anim-4">
|
||||||
|
<span class="pillar">Structure & Dependencies</span>
|
||||||
|
<span class="pillar-dot">·</span>
|
||||||
|
<span class="pillar">Business Logic</span>
|
||||||
|
<span class="pillar-dot">·</span>
|
||||||
|
<span class="pillar">Guided Tours</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hero-actions anim anim-5">
|
||||||
|
<a href="#install" class="hero-cta">Get Started</a>
|
||||||
|
<a href="/demo/" class="hero-demo">Live Demo →</a>
|
||||||
|
<a href={discordUrl} target="_blank" rel="noopener noreferrer" class="hero-discord">
|
||||||
|
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418Z"/>
|
||||||
|
</svg>
|
||||||
|
Join Discord
|
||||||
|
</a>
|
||||||
|
<a href={githubUrl} target="_blank" rel="noopener noreferrer" class="hero-secondary">
|
||||||
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" style="vertical-align:-2px;margin-right:4px;"><path d="M12 .587l3.668 7.568L24 9.306l-6 5.986 1.416 8.421L12 19.897l-7.416 3.816L6 15.292 0 9.306l8.332-1.151z"/></svg>
|
||||||
|
Star on GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="https://trendshift.io/repositories/23482" target="_blank" rel="noopener noreferrer" class="hero-trendshift anim anim-5">
|
||||||
|
<img src="https://trendshift.io/api/badge/repositories/23482" alt="Lum1104/Understand-Anything | Trendshift" width="250" height="55" loading="lazy" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="mailto:lum@understand-anything.com" class="hero-enterprise anim anim-5">
|
||||||
|
<span class="hero-enterprise-label">Enterprise</span>
|
||||||
|
<span class="hero-enterprise-divider">·</span>
|
||||||
|
<span class="hero-enterprise-email">lum@understand-anything.com</span>
|
||||||
|
<span class="hero-enterprise-arrow">→</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://lum.is-a.dev/" target="_blank" rel="noopener noreferrer" class="hero-author anim anim-5">
|
||||||
|
<span class="hero-author-label">Author</span>
|
||||||
|
<span class="hero-author-divider">·</span>
|
||||||
|
<span class="hero-author-handle">Lum1104</span>
|
||||||
|
<span class="hero-author-arrow">→</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<details class="hero-subscribe anim anim-6">
|
||||||
|
<summary class="hero-subscribe-toggle">
|
||||||
|
<span>Subscribe to release updates</span>
|
||||||
|
<svg class="hero-subscribe-chevron" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
|
</svg>
|
||||||
|
</summary>
|
||||||
|
<div class="hero-subscribe-frame">
|
||||||
|
<p class="hero-subscribe-hint">No spam, unsubscribe anytime</p>
|
||||||
|
<iframe
|
||||||
|
src="https://yuxianglin.substack.com/embed"
|
||||||
|
title="Subscribe to release updates"
|
||||||
|
width="480"
|
||||||
|
height="150"
|
||||||
|
loading="lazy"
|
||||||
|
scrolling="no"
|
||||||
|
frameborder="0"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.hero {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-bg {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-bg-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(10, 10, 10, 0.4) 0%,
|
||||||
|
rgba(10, 10, 10, 0.75) 55%,
|
||||||
|
var(--bg) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-width: 900px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badge */
|
||||||
|
.hero-badge {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: #d4a574;
|
||||||
|
border: 1px solid rgba(212, 165, 116, 0.3);
|
||||||
|
padding: 0.4rem 1.2rem;
|
||||||
|
border-radius: 100px;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headline */
|
||||||
|
.hero-title {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(2.75rem, 9vw, 7rem);
|
||||||
|
color: #e8e2d8;
|
||||||
|
line-height: 1.05;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-grad {
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(135deg, #c9867a, #b8865c, #d4a574);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tagline */
|
||||||
|
.hero-tagline {
|
||||||
|
font-size: clamp(1rem, 2vw, 1.4rem);
|
||||||
|
color: #8a8578;
|
||||||
|
line-height: 1.7;
|
||||||
|
max-width: 900px;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-problem {
|
||||||
|
color: #c9867a;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-value {
|
||||||
|
color: #d4a574;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pillars */
|
||||||
|
.hero-pillars {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #e8e2d8;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar-dot {
|
||||||
|
color: #d4a574;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CTAs */
|
||||||
|
.hero-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-cta {
|
||||||
|
background: #d4a574;
|
||||||
|
color: #0a0a0a;
|
||||||
|
padding: 0.85rem 2.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: box-shadow 0.3s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-cta:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 0 30px rgba(212, 165, 116, 0.15), 0 0 60px rgba(212, 165, 116, 0.08);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-demo {
|
||||||
|
border: 1.5px solid #d4a574;
|
||||||
|
color: #d4a574;
|
||||||
|
padding: 0.85rem 2.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.3s ease, box-shadow 0.3s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-demo:hover {
|
||||||
|
background: rgba(212, 165, 116, 0.1);
|
||||||
|
box-shadow: 0 0 20px rgba(212, 165, 116, 0.15);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-discord {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.55rem;
|
||||||
|
background: #5865F2;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 0.85rem 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: box-shadow 0.3s ease, transform 0.2s ease, background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-discord:hover {
|
||||||
|
background: #4752C4;
|
||||||
|
box-shadow: 0 0 30px rgba(88, 101, 242, 0.25), 0 0 60px rgba(88, 101, 242, 0.12);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
text-decoration: none;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-discord svg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-secondary {
|
||||||
|
color: #8a8578;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-secondary:hover {
|
||||||
|
color: #d4a574;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trendshift badge — sits between the action row and the enterprise pill */
|
||||||
|
.hero-trendshift {
|
||||||
|
margin-top: 1.75rem;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 0;
|
||||||
|
transition: transform 0.2s ease, filter 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-trendshift img {
|
||||||
|
display: block;
|
||||||
|
width: 250px;
|
||||||
|
height: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-trendshift:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
filter: drop-shadow(0 0 14px rgba(212, 165, 116, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enterprise contact — pill that mirrors the hero badge at the top */
|
||||||
|
.hero-enterprise {
|
||||||
|
margin-top: 1.75rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.55rem;
|
||||||
|
padding: 0.45rem 1.1rem;
|
||||||
|
border: 1px solid rgba(212, 165, 116, 0.3);
|
||||||
|
border-radius: 100px;
|
||||||
|
background: rgba(212, 165, 116, 0.04);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: border-color 0.25s ease, box-shadow 0.25s ease, transform 0.2s ease,
|
||||||
|
background-color 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-enterprise:hover {
|
||||||
|
border-color: rgba(212, 165, 116, 0.55);
|
||||||
|
background: rgba(212, 165, 116, 0.08);
|
||||||
|
box-shadow: 0 0 24px rgba(212, 165, 116, 0.15);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-enterprise-label {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #d4a574;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-enterprise-divider {
|
||||||
|
color: rgba(212, 165, 116, 0.45);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-enterprise-email {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #e8e2d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-enterprise-arrow {
|
||||||
|
color: #d4a574;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-enterprise:hover .hero-enterprise-arrow {
|
||||||
|
transform: translateX(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Author homepage — pill mirroring Enterprise, sits directly under it */
|
||||||
|
.hero-author {
|
||||||
|
margin-top: 0.6rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.55rem;
|
||||||
|
padding: 0.45rem 1.1rem;
|
||||||
|
border: 1px solid rgba(212, 165, 116, 0.3);
|
||||||
|
border-radius: 100px;
|
||||||
|
background: rgba(212, 165, 116, 0.04);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: border-color 0.25s ease, box-shadow 0.25s ease, transform 0.2s ease,
|
||||||
|
background-color 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-author:hover {
|
||||||
|
border-color: rgba(212, 165, 116, 0.55);
|
||||||
|
background: rgba(212, 165, 116, 0.08);
|
||||||
|
box-shadow: 0 0 24px rgba(212, 165, 116, 0.15);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-author-label {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #d4a574;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-author-divider {
|
||||||
|
color: rgba(212, 165, 116, 0.45);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-author-handle {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #e8e2d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-author-arrow {
|
||||||
|
color: #d4a574;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-author:hover .hero-author-arrow {
|
||||||
|
transform: translateX(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subscribe */
|
||||||
|
.hero-subscribe {
|
||||||
|
margin-top: 2rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 480px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subscribe-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #8a8578;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
list-style: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subscribe-toggle::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subscribe-toggle:hover {
|
||||||
|
color: #d4a574;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subscribe-chevron {
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subscribe[open] .hero-subscribe-chevron {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subscribe-frame {
|
||||||
|
margin-top: 1rem;
|
||||||
|
background: rgba(10, 10, 10, 0.6);
|
||||||
|
border: 1px solid rgba(212, 165, 116, 0.25);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 6px 6px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
animation: subscribeFadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subscribe-hint {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: #8a8578;
|
||||||
|
margin: 0.25rem 0 0.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subscribe-frame iframe {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 150px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes subscribeFadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(-4px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Staggered entrance */
|
||||||
|
@keyframes heroIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(24px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim {
|
||||||
|
opacity: 0;
|
||||||
|
animation: heroIn 0.8s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim-1 { animation-delay: 0.1s; }
|
||||||
|
.anim-2 { animation-delay: 0.3s; }
|
||||||
|
.anim-3 { animation-delay: 0.55s; }
|
||||||
|
.anim-4 { animation-delay: 0.75s; }
|
||||||
|
.anim-5 { animation-delay: 0.95s; }
|
||||||
|
.anim-6 { animation-delay: 1.15s; }
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.hero-tagline {
|
||||||
|
white-space: normal;
|
||||||
|
max-width: 36ch;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero { padding: 5rem 1.5rem 3rem; min-height: auto; }
|
||||||
|
.hero-content { gap: 0; }
|
||||||
|
.hero-pillars { gap: 0.5rem; margin-bottom: 2.25rem; }
|
||||||
|
.pillar { font-size: 0.8rem; }
|
||||||
|
.hero-badge {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
padding: 0.35rem 1rem;
|
||||||
|
margin-bottom: 1.75rem;
|
||||||
|
}
|
||||||
|
.hero-title { margin-bottom: 1.5rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.hero-title {
|
||||||
|
white-space: normal;
|
||||||
|
line-height: 1.02;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
/* Stack the two words on dedicated lines for editorial drama */
|
||||||
|
.hero-title > span { display: block; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 540px) {
|
||||||
|
.hero-enterprise {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.6rem 1.25rem;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.hero-enterprise-divider { display: none; }
|
||||||
|
.hero-enterprise-email { font-size: 0.78rem; }
|
||||||
|
.hero-author {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.6rem 1.25rem;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.hero-author-divider { display: none; }
|
||||||
|
.hero-author-handle { font-size: 0.78rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.hero { padding: 4rem 1.25rem 2.25rem; }
|
||||||
|
.hero-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
.hero-cta,
|
||||||
|
.hero-demo,
|
||||||
|
.hero-discord {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.85rem 1.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.hero-pillars { flex-direction: column; gap: 0.4rem; }
|
||||||
|
.pillar-dot { display: none; }
|
||||||
|
.hero-subscribe { margin-top: 1.5rem; }
|
||||||
|
.hero-subscribe-toggle { font-size: 0.85rem; }
|
||||||
|
.hero-subscribe-frame iframe { height: 130px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
144
Understand-Anything-main/homepage/src/components/Install.astro
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<section class="install" id="install">
|
||||||
|
<div class="install-inner reveal">
|
||||||
|
<h2 class="install-title">Get started in <span class="grad">30 seconds</span>.</h2>
|
||||||
|
<div class="install-code">
|
||||||
|
<div class="install-code-header">
|
||||||
|
<span class="install-code-dot"></span>
|
||||||
|
<span class="install-code-label">Claude Code</span>
|
||||||
|
<button class="install-copy" id="copy-btn" type="button" aria-label="Copy to clipboard">
|
||||||
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||||
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="install-copy-label">Copy</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre id="install-snippet"><code><span class="cmd">/plugin marketplace add</span> Lum1104/Understand-Anything
|
||||||
|
<span class="cmd">/plugin install</span> understand-anything
|
||||||
|
<span class="cmd">/understand</span></code></pre>
|
||||||
|
</div>
|
||||||
|
<p class="install-note">Works with <strong>Claude Code</strong>, <strong>Codex</strong>, <strong>OpenCode</strong>, <strong>Gemini CLI</strong>, and more.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const btn = document.getElementById('copy-btn');
|
||||||
|
const snippet = document.getElementById('install-snippet');
|
||||||
|
if (btn && snippet) {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const text = snippet.textContent || '';
|
||||||
|
await navigator.clipboard.writeText(text.trim());
|
||||||
|
const label = btn.querySelector('.install-copy-label');
|
||||||
|
if (label) {
|
||||||
|
label.textContent = 'Copied!';
|
||||||
|
setTimeout(() => { label.textContent = 'Copy'; }, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.install {
|
||||||
|
padding: 8rem 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-inner {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-title {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(1.5rem, 3vw, 2.25rem);
|
||||||
|
color: var(--text);
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-code {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-code-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-code-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-code-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-family: var(--font-code);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-copy {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.3rem 0.6rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-copy:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
border-color: rgba(212, 165, 116, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 1.25rem 1.5rem;
|
||||||
|
margin: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmd {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-note {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-note strong {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.install { padding: 4.5rem 1.25rem; }
|
||||||
|
.install-title { margin-bottom: 1.75rem; }
|
||||||
|
pre { padding: 1rem 1.1rem; }
|
||||||
|
code { font-size: 0.82rem; line-height: 1.7; }
|
||||||
|
.install-code-header { padding: 0.65rem 0.85rem; }
|
||||||
|
.install-code-label { font-size: 0.72rem; }
|
||||||
|
.install-note { font-size: 0.82rem; line-height: 1.55; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
107
Understand-Anything-main/homepage/src/components/Nav.astro
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
|
||||||
|
---
|
||||||
|
|
||||||
|
<nav class="nav" id="nav">
|
||||||
|
<div class="nav-inner">
|
||||||
|
<a href="/" class="nav-logo">Understand Anything</a>
|
||||||
|
<div class="nav-links">
|
||||||
|
<a href={githubUrl} target="_blank" rel="noopener noreferrer" class="nav-github">
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"/>
|
||||||
|
</svg>
|
||||||
|
<span>GitHub</span>
|
||||||
|
</a>
|
||||||
|
<a href="#install" class="nav-cta">Get Started</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const nav = document.getElementById('nav');
|
||||||
|
if (nav) {
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
nav.classList.toggle('scrolled', window.scrollY > 50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.nav {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
transition: background-color 0.3s ease, backdrop-filter 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav.scrolled {
|
||||||
|
background-color: rgba(10, 10, 10, 0.85);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-inner {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-logo {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-logo:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-github {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-github:hover {
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-cta {
|
||||||
|
background: var(--accent);
|
||||||
|
color: var(--bg);
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-cta:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 0 20px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.nav { padding: 0.75rem 1rem; }
|
||||||
|
.nav-github span { display: none; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
220
Understand-Anything-main/homepage/src/components/Problem.astro
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
<section class="problem">
|
||||||
|
<h2 class="problem-heading reveal">
|
||||||
|
Most code graphs show you <span style="display:inline;background:linear-gradient(135deg,#c9867a,#b8865c);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;">structure</span>.<br />
|
||||||
|
Files, functions, edges. A map with no legend.
|
||||||
|
</h2>
|
||||||
|
<p class="problem-promise reveal">
|
||||||
|
We show you <span style="display:inline;background:linear-gradient(135deg,#b8865c,#d4a574);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;">meaning</span> —
|
||||||
|
how your code maps to real business domains, processes, and flows.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="problem-compare reveal reveal-delay-1">
|
||||||
|
<div class="problem-card problem-card--them">
|
||||||
|
<div class="problem-card-label">Typical code graph</div>
|
||||||
|
<div class="problem-card-visual">
|
||||||
|
<svg viewBox="0 0 320 200" class="hairball" aria-label="A tangled, unreadable graph of dots and lines">
|
||||||
|
<!-- scattered nodes -->
|
||||||
|
<circle cx="60" cy="40" r="6" /><circle cx="140" cy="25" r="8" />
|
||||||
|
<circle cx="220" cy="50" r="5" /><circle cx="280" cy="30" r="7" />
|
||||||
|
<circle cx="40" cy="100" r="7" /><circle cx="100" cy="80" r="5" />
|
||||||
|
<circle cx="170" cy="95" r="9" /><circle cx="250" cy="90" r="6" />
|
||||||
|
<circle cx="300" cy="110" r="5" /><circle cx="80" cy="150" r="6" />
|
||||||
|
<circle cx="150" cy="160" r="7" /><circle cx="210" cy="145" r="5" />
|
||||||
|
<circle cx="270" cy="165" r="8" /><circle cx="120" cy="120" r="4" />
|
||||||
|
<circle cx="190" cy="130" r="6" /><circle cx="50" cy="180" r="5" />
|
||||||
|
<circle cx="160" cy="185" r="4" /><circle cx="240" cy="180" r="6" />
|
||||||
|
<!-- tangled edges -->
|
||||||
|
<line x1="60" y1="40" x2="140" y2="25" /><line x1="140" y1="25" x2="220" y2="50" />
|
||||||
|
<line x1="220" y1="50" x2="280" y2="30" /><line x1="40" y1="100" x2="170" y2="95" />
|
||||||
|
<line x1="100" y1="80" x2="250" y2="90" /><line x1="170" y1="95" x2="300" y2="110" />
|
||||||
|
<line x1="60" y1="40" x2="100" y2="80" /><line x1="140" y1="25" x2="170" y2="95" />
|
||||||
|
<line x1="80" y1="150" x2="150" y2="160" /><line x1="150" y1="160" x2="270" y2="165" />
|
||||||
|
<line x1="120" y1="120" x2="190" y2="130" /><line x1="190" y1="130" x2="210" y2="145" />
|
||||||
|
<line x1="40" y1="100" x2="80" y2="150" /><line x1="250" y1="90" x2="270" y2="165" />
|
||||||
|
<line x1="100" y1="80" x2="120" y2="120" /><line x1="170" y1="95" x2="150" y2="160" />
|
||||||
|
<line x1="280" y1="30" x2="300" y2="110" /><line x1="210" y1="145" x2="240" y2="180" />
|
||||||
|
<line x1="50" y1="180" x2="160" y2="185" /><line x1="160" y1="185" x2="240" y2="180" />
|
||||||
|
<line x1="60" y1="40" x2="250" y2="90" /><line x1="40" y1="100" x2="210" y2="145" />
|
||||||
|
<line x1="120" y1="120" x2="270" y2="165" /><line x1="80" y1="150" x2="190" y2="130" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p class="problem-card-caption">23 nodes. 34 edges. Now what?</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="problem-card problem-card--us">
|
||||||
|
<div class="problem-card-label">Understand Anything</div>
|
||||||
|
<div class="problem-card-visual">
|
||||||
|
<div class="domain-preview">
|
||||||
|
<div class="domain-group">
|
||||||
|
<span class="domain-badge">Authentication</span>
|
||||||
|
<span class="domain-item">login()</span>
|
||||||
|
<span class="domain-item">verify_token()</span>
|
||||||
|
<span class="domain-item">session management</span>
|
||||||
|
</div>
|
||||||
|
<div class="domain-group">
|
||||||
|
<span class="domain-badge">User Lifecycle</span>
|
||||||
|
<span class="domain-item">create_user()</span>
|
||||||
|
<span class="domain-item">get_profile()</span>
|
||||||
|
<span class="domain-item">permissions</span>
|
||||||
|
</div>
|
||||||
|
<div class="domain-group">
|
||||||
|
<span class="domain-badge">Data Layer</span>
|
||||||
|
<span class="domain-item">db_connect()</span>
|
||||||
|
<span class="domain-item">migrations</span>
|
||||||
|
<span class="domain-item">models</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="problem-card-caption">Auth flow. Session management. Now you understand.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.problem {
|
||||||
|
padding: 8rem 2rem 10rem;
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-heading {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(1.5rem, 4vw, 2.5rem);
|
||||||
|
color: var(--text-muted);
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-promise {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(1.3rem, 3.5vw, 2.2rem);
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1.4;
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0 auto 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grad-warm {
|
||||||
|
background: linear-gradient(135deg, var(--grad-mid), var(--grad-warm));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-compare {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 2rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-card--them {
|
||||||
|
background: var(--surface);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-card--us {
|
||||||
|
background: var(--surface);
|
||||||
|
border-color: rgba(212, 165, 116, 0.2);
|
||||||
|
box-shadow: 0 0 40px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-card-label {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-card--us .problem-card-label {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-card-visual {
|
||||||
|
min-height: 200px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hairball SVG */
|
||||||
|
.hairball {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 320px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hairball circle {
|
||||||
|
fill: var(--text-muted);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hairball line {
|
||||||
|
stroke: var(--text-muted);
|
||||||
|
stroke-width: 0.8;
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Domain preview */
|
||||||
|
.domain-preview {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-badge {
|
||||||
|
background: linear-gradient(135deg, rgba(201, 134, 122, 0.15), rgba(212, 165, 116, 0.15));
|
||||||
|
border: 1px solid rgba(212, 165, 116, 0.3);
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 0.4rem 0.85rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-item {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 0.3rem 0.6rem;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-card-caption {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-card--us .problem-card-caption {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.problem { padding: 4rem 1rem 6rem; }
|
||||||
|
.problem-compare {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
204
Understand-Anything-main/homepage/src/components/Showcase.astro
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
---
|
||||||
|
const base = import.meta.env.BASE_URL; /* "/" after custom domain migration */
|
||||||
|
const demoUrl = `${base.endsWith('/') ? base : base + '/'}demo/index.html`;
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Live Demo -->
|
||||||
|
<section class="showcase showcase--demo">
|
||||||
|
<span class="showcase-label showcase-label--accent reveal">Live Demo</span>
|
||||||
|
<h2 class="showcase-heading reveal">
|
||||||
|
See what your code is <span style="display:inline;background:linear-gradient(135deg,#b8865c,#d4a574);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;">really</span> doing
|
||||||
|
</h2>
|
||||||
|
<p class="showcase-desc showcase-desc--wide reveal">
|
||||||
|
Explore every file, function, and dependency — or switch to <span style="display:inline;background:linear-gradient(135deg,#b8865c,#d4a574);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;">business
|
||||||
|
knowledge</span> mode and watch your code transform into authentication flows,
|
||||||
|
payment pipelines, and user lifecycles. Not just a map.
|
||||||
|
<strong>The story behind your codebase, fully interactive.</strong>
|
||||||
|
</p>
|
||||||
|
<div class="showcase-frame showcase-frame--featured reveal reveal-delay-1">
|
||||||
|
<div class="showcase-titlebar">
|
||||||
|
<span class="dot red"></span>
|
||||||
|
<span class="dot yellow"></span>
|
||||||
|
<span class="dot green"></span>
|
||||||
|
<span class="showcase-titlebar-text">Interactive Dashboard</span>
|
||||||
|
</div>
|
||||||
|
<div class="showcase-iframe-wrap">
|
||||||
|
<iframe
|
||||||
|
src={demoUrl}
|
||||||
|
title="Understand Anything — live demo"
|
||||||
|
class="showcase-iframe"
|
||||||
|
loading="lazy"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="showcase-features reveal reveal-delay-2">
|
||||||
|
<span class="showcase-pill">Hierarchical drill-down</span>
|
||||||
|
<span class="showcase-pill">Fuzzy search</span>
|
||||||
|
<span class="showcase-pill">Filter by type</span>
|
||||||
|
<span class="showcase-pill">26+ file types</span>
|
||||||
|
<span class="showcase-pill showcase-pill--accent">Domain mapping</span>
|
||||||
|
<span class="showcase-pill showcase-pill--accent">Business flows</span>
|
||||||
|
<span class="showcase-pill showcase-pill--accent">Process steps</span>
|
||||||
|
<span class="showcase-pill showcase-pill--accent">AI-generated tours</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.showcase {
|
||||||
|
padding: 6rem 2rem 4rem;
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showcase--demo {
|
||||||
|
padding: 8rem 2rem 6rem;
|
||||||
|
max-width: 1400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section label */
|
||||||
|
.showcase-label {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-muted);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 0.3rem 1rem;
|
||||||
|
border-radius: 100px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showcase-label--accent {
|
||||||
|
color: #d4a574;
|
||||||
|
border-color: rgba(212, 165, 116, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Heading */
|
||||||
|
.showcase-heading {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: clamp(1.75rem, 4.5vw, 3rem);
|
||||||
|
color: var(--text);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Description */
|
||||||
|
.showcase-desc {
|
||||||
|
font-size: clamp(0.95rem, 1.5vw, 1.1rem);
|
||||||
|
color: var(--text-muted);
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto 3rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showcase-desc--wide {
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showcase-desc strong {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* macOS frame */
|
||||||
|
.showcase-frame {
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.showcase-frame--featured {
|
||||||
|
border-color: rgba(212, 165, 116, 0.2);
|
||||||
|
box-shadow:
|
||||||
|
0 0 60px rgba(212, 165, 116, 0.1),
|
||||||
|
0 30px 60px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.showcase-titlebar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: rgba(20, 20, 20, 0.8);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.showcase-titlebar-text {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-right: 30px; /* offset for dots */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.red { background: #ff5f57; }
|
||||||
|
.dot.yellow { background: #ffbd2e; }
|
||||||
|
.dot.green { background: #28c840; }
|
||||||
|
|
||||||
|
.showcase-media {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Iframe embed */
|
||||||
|
.showcase-iframe-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
background: #0a0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showcase-iframe {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Feature pills below frame */
|
||||||
|
.showcase-features {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showcase-pill {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #8a8578;
|
||||||
|
padding: 0.4rem 1rem;
|
||||||
|
border: 1px solid rgba(138, 133, 120, 0.25);
|
||||||
|
border-radius: 100px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.showcase-pill--accent {
|
||||||
|
color: #d4a574;
|
||||||
|
border-color: rgba(212, 165, 116, 0.3);
|
||||||
|
background: rgba(212, 165, 116, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.showcase { padding: 4rem 1rem 2rem; }
|
||||||
|
.showcase--demo { padding: 4rem 1rem 4rem; }
|
||||||
|
.showcase-features { gap: 0.5rem; }
|
||||||
|
.showcase-pill { font-size: 0.7rem; padding: 0.25rem 0.65rem; }
|
||||||
|
.showcase-iframe-wrap { aspect-ratio: 4 / 3; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
25
Understand-Anything-main/homepage/src/layouts/Layout.astro
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content="Turn any codebase into an interactive knowledge graph you can explore, search, and learn from." />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<title>{title}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<slot />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
@import '../styles/global.css';
|
||||||
|
</style>
|
||||||
38
Understand-Anything-main/homepage/src/pages/index.astro
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
import Layout from '../layouts/Layout.astro';
|
||||||
|
import Nav from '../components/Nav.astro';
|
||||||
|
import Hero from '../components/Hero.astro';
|
||||||
|
import Problem from '../components/Problem.astro';
|
||||||
|
import Showcase from '../components/Showcase.astro';
|
||||||
|
import CommunityVideo from '../components/CommunityVideo.astro';
|
||||||
|
import Features from '../components/Features.astro';
|
||||||
|
import Install from '../components/Install.astro';
|
||||||
|
import Footer from '../components/Footer.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title="Understand Anything — Graphs that teach the codebase">
|
||||||
|
<Nav />
|
||||||
|
<Hero />
|
||||||
|
<Problem />
|
||||||
|
<Showcase />
|
||||||
|
<CommunityVideo />
|
||||||
|
<Features />
|
||||||
|
<Install />
|
||||||
|
<Footer />
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
entry.target.classList.add('visible');
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ threshold: 0.15 }
|
||||||
|
);
|
||||||
|
|
||||||
|
document.querySelectorAll('.reveal').forEach((el) => observer.observe(el));
|
||||||
|
</script>
|
||||||
126
Understand-Anything-main/homepage/src/styles/global.css
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
/* Font declarations — self-hosted, no external CDN dependency */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'DM Serif Display';
|
||||||
|
src: url('/fonts/DMSerifDisplay-Regular.woff2') format('woff2');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
src: url('/fonts/Inter-Regular.woff2') format('woff2');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
src: url('/fonts/Inter-SemiBold.woff2') format('woff2');
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
src: url('/fonts/JetBrainsMono-Regular.woff2') format('woff2');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Design tokens */
|
||||||
|
:root {
|
||||||
|
--bg: #0a0a0a;
|
||||||
|
--surface: #141414;
|
||||||
|
--border: #1a1a1a;
|
||||||
|
--accent: #d4a574;
|
||||||
|
--accent-glow: rgba(212, 165, 116, 0.15);
|
||||||
|
--text: #e8e2d8;
|
||||||
|
--text-muted: #8a8578;
|
||||||
|
|
||||||
|
/* Gradient sweep: rose gold → gold */
|
||||||
|
--grad-cool: #c9867a;
|
||||||
|
--grad-mid: #b8865c;
|
||||||
|
--grad-warm: #d4a574;
|
||||||
|
--gradient: linear-gradient(135deg, var(--grad-cool), var(--grad-mid), var(--grad-warm));
|
||||||
|
|
||||||
|
--font-heading: 'DM Serif Display', Georgia, 'Times New Roman', serif;
|
||||||
|
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
--font-code: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset & base */
|
||||||
|
*, *::before, *::after {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
background-color: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1.6;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Noise texture overlay */
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 9999;
|
||||||
|
opacity: 0.03;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scroll-reveal animation */
|
||||||
|
@keyframes fadeSlideUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reveal {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reveal.visible {
|
||||||
|
animation: fadeSlideUp 0.8s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stagger delays for feature cards */
|
||||||
|
.reveal-delay-1 { animation-delay: 0.1s; }
|
||||||
|
.reveal-delay-2 { animation-delay: 0.25s; }
|
||||||
|
.reveal-delay-3 { animation-delay: 0.4s; }
|
||||||
|
|
||||||
|
/* Gradient text utility */
|
||||||
|
.grad {
|
||||||
|
background: var(--gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
5
Understand-Anything-main/homepage/tsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"include": [".astro/types.d.ts", "**/*"],
|
||||||
|
"exclude": ["dist"]
|
||||||
|
}
|
||||||
236
Understand-Anything-main/install.ps1
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Understand-Anything installer for Windows (PowerShell).
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Clones the repo and creates skill symlinks/junctions for the chosen platform.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
./install.ps1 # prompt for platform
|
||||||
|
./install.ps1 codex # install for codex
|
||||||
|
./install.ps1 -Update # pull latest changes
|
||||||
|
./install.ps1 -Uninstall codex # remove links for codex
|
||||||
|
#>
|
||||||
|
|
||||||
|
param(
|
||||||
|
[Parameter(Position = 0)]
|
||||||
|
[string]$Platform,
|
||||||
|
[switch]$Update,
|
||||||
|
[string]$Uninstall,
|
||||||
|
[switch]$Help
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$RepoUrl = if ($env:UA_REPO_URL) { $env:UA_REPO_URL } else { 'https://github.com/Lum1104/Understand-Anything.git' }
|
||||||
|
$RepoDir = if ($env:UA_DIR) { $env:UA_DIR } else { Join-Path $HOME '.understand-anything\repo' }
|
||||||
|
$PluginLink = Join-Path $HOME '.understand-anything-plugin'
|
||||||
|
|
||||||
|
# Platform table — Target = skills directory; Style = "per-skill" | "folder"
|
||||||
|
$Platforms = [ordered]@{
|
||||||
|
gemini = @{ Target = (Join-Path $HOME '.agents\skills'); Style = 'per-skill' }
|
||||||
|
codex = @{ Target = (Join-Path $HOME '.agents\skills'); Style = 'per-skill' }
|
||||||
|
opencode = @{ Target = (Join-Path $HOME '.agents\skills'); Style = 'per-skill' }
|
||||||
|
pi = @{ Target = (Join-Path $HOME '.agents\skills'); Style = 'per-skill' }
|
||||||
|
openclaw = @{ Target = (Join-Path $HOME '.openclaw\skills'); Style = 'folder' }
|
||||||
|
antigravity = @{ Target = (Join-Path $HOME '.gemini\antigravity\skills'); Style = 'folder' }
|
||||||
|
vscode = @{ Target = (Join-Path $HOME '.copilot\skills'); Style = 'per-skill' }
|
||||||
|
hermes = @{ Target = (Join-Path $HOME '.hermes\skills'); Style = 'folder' }
|
||||||
|
cline = @{ Target = (Join-Path $HOME '.cline\skills'); Style = 'folder' }
|
||||||
|
kimi = @{ Target = (Join-Path $HOME '.kimi\skills'); Style = 'folder' }
|
||||||
|
trae = @{ Target = (Join-Path $HOME '.trae\skills'); Style = 'per-skill' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Show-Usage {
|
||||||
|
@"
|
||||||
|
Understand-Anything installer (Windows)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
install.ps1 [<platform>] Install for <platform> (or prompt if omitted)
|
||||||
|
install.ps1 -Update Pull latest changes
|
||||||
|
install.ps1 -Uninstall <platform> Remove links for <platform>
|
||||||
|
install.ps1 -Help
|
||||||
|
|
||||||
|
Supported platforms:
|
||||||
|
$($Platforms.Keys -join ', ')
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
UA_REPO_URL Override clone URL
|
||||||
|
UA_DIR Override clone destination (default: %USERPROFILE%\.understand-anything\repo)
|
||||||
|
"@
|
||||||
|
}
|
||||||
|
|
||||||
|
function Resolve-Platform([string]$Id) {
|
||||||
|
if (-not $Platforms.Contains($Id)) {
|
||||||
|
Write-Error "Unknown platform: $Id. Supported: $($Platforms.Keys -join ', ')"
|
||||||
|
}
|
||||||
|
return $Platforms[$Id]
|
||||||
|
}
|
||||||
|
|
||||||
|
function Prompt-Platform {
|
||||||
|
$ids = @($Platforms.Keys)
|
||||||
|
Write-Host 'Which platform are you installing for?'
|
||||||
|
for ($i = 0; $i -lt $ids.Count; $i++) {
|
||||||
|
Write-Host (" {0}) {1}" -f ($i + 1), $ids[$i])
|
||||||
|
}
|
||||||
|
$choice = Read-Host ("Choose [1-{0}]" -f $ids.Count)
|
||||||
|
$n = 0
|
||||||
|
if (-not [int]::TryParse($choice, [ref]$n) -or $n -lt 1 -or $n -gt $ids.Count) {
|
||||||
|
Write-Error "Invalid choice: $choice"
|
||||||
|
}
|
||||||
|
return $ids[$n - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SkillsRoot { Join-Path $RepoDir 'understand-anything-plugin\skills' }
|
||||||
|
|
||||||
|
function Clone-Or-Update {
|
||||||
|
if (Test-Path (Join-Path $RepoDir '.git')) {
|
||||||
|
Write-Host "→ Updating existing checkout at $RepoDir"
|
||||||
|
git -C $RepoDir pull --ff-only
|
||||||
|
} else {
|
||||||
|
Write-Host "→ Cloning $RepoUrl → $RepoDir"
|
||||||
|
$parent = Split-Path -Parent $RepoDir
|
||||||
|
if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent | Out-Null }
|
||||||
|
git clone $RepoUrl $RepoDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SkillNames {
|
||||||
|
$root = Get-SkillsRoot
|
||||||
|
if (-not (Test-Path $root)) { Write-Error "Skills directory not found: $root" }
|
||||||
|
Get-ChildItem -Path $root -Directory | Select-Object -ExpandProperty Name
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-IsReparse([string]$Path) {
|
||||||
|
if (-not (Test-Path $Path)) { return $false }
|
||||||
|
$item = Get-Item -LiteralPath $Path -Force
|
||||||
|
return ($item.LinkType -eq 'Junction' -or $item.LinkType -eq 'SymbolicLink')
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-Reparse([string]$Path) {
|
||||||
|
# Removes a junction/symlink without touching its target. Refuses to touch
|
||||||
|
# real files or directories so an existing user folder at the same path is
|
||||||
|
# never destroyed.
|
||||||
|
if (-not (Test-Path $Path)) { return $false }
|
||||||
|
$item = Get-Item -LiteralPath $Path -Force
|
||||||
|
if ($item.LinkType -eq 'Junction' -or $item.LinkType -eq 'SymbolicLink') {
|
||||||
|
$item.Delete()
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
Write-Warning "Refusing to delete $Path — it is a real file/directory, not a junction/symlink we created. Remove it manually if you intended to."
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function New-Junction([string]$LinkPath, [string]$TargetPath) {
|
||||||
|
if (Test-Path $LinkPath) {
|
||||||
|
if (Test-IsReparse $LinkPath) {
|
||||||
|
(Get-Item -LiteralPath $LinkPath -Force).Delete()
|
||||||
|
} else {
|
||||||
|
Write-Error "Refusing to overwrite $LinkPath — it is a real file/directory, not a junction. Move or remove it first."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
New-Item -ItemType Junction -Path $LinkPath -Target $TargetPath | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Link-Skills([string]$Target, [string]$Style) {
|
||||||
|
$root = Get-SkillsRoot
|
||||||
|
if (-not (Test-Path $Target)) { New-Item -ItemType Directory -Path $Target | Out-Null }
|
||||||
|
|
||||||
|
switch ($Style) {
|
||||||
|
'per-skill' {
|
||||||
|
foreach ($skill in Get-SkillNames) {
|
||||||
|
$link = Join-Path $Target $skill
|
||||||
|
$src = Join-Path $root $skill
|
||||||
|
New-Junction $link $src
|
||||||
|
Write-Host " ✓ $link → $src"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'folder' {
|
||||||
|
$link = Join-Path $Target 'understand-anything'
|
||||||
|
New-Junction $link $root
|
||||||
|
Write-Host " ✓ $link → $root"
|
||||||
|
}
|
||||||
|
default { Write-Error "Unknown style: $Style" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Unlink-Skills([string]$Target, [string]$Style) {
|
||||||
|
if (-not (Test-Path $Target)) { return }
|
||||||
|
switch ($Style) {
|
||||||
|
'per-skill' {
|
||||||
|
$skillsRoot = Get-SkillsRoot
|
||||||
|
if (Test-Path $skillsRoot) {
|
||||||
|
foreach ($skill in Get-SkillNames) {
|
||||||
|
Remove-Reparse (Join-Path $Target $skill) | Out-Null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
# Checkout is gone — scan the target dir for stale links pointing
|
||||||
|
# into our plugin tree so we can still clean up.
|
||||||
|
Get-ChildItem -LiteralPath $Target -Force | ForEach-Object {
|
||||||
|
if ($_.LinkType -eq 'Junction' -or $_.LinkType -eq 'SymbolicLink') {
|
||||||
|
if ($_.Target -match 'understand-anything-plugin[\\/]+skills[\\/]+') {
|
||||||
|
Remove-Reparse $_.FullName | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'folder' {
|
||||||
|
Remove-Reparse (Join-Path $Target 'understand-anything') | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Link-Plugin-Root {
|
||||||
|
if (Test-Path $PluginLink) {
|
||||||
|
Write-Host " • $PluginLink already exists, leaving as-is"
|
||||||
|
} else {
|
||||||
|
$src = Join-Path $RepoDir 'understand-anything-plugin'
|
||||||
|
New-Item -ItemType Junction -Path $PluginLink -Target $src | Out-Null
|
||||||
|
Write-Host " ✓ $PluginLink → $src"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Cmd-Install([string]$Id) {
|
||||||
|
$cfg = Resolve-Platform $Id
|
||||||
|
Clone-Or-Update
|
||||||
|
Write-Host "→ Linking skills for $Id ($($cfg.Style) → $($cfg.Target))"
|
||||||
|
Link-Skills $cfg.Target $cfg.Style
|
||||||
|
Write-Host '→ Linking universal plugin root'
|
||||||
|
Link-Plugin-Root
|
||||||
|
|
||||||
|
Write-Host "`n✓ Installed Understand-Anything for $Id"
|
||||||
|
Write-Host ' Restart your CLI or IDE to pick up the skills.'
|
||||||
|
if ($Id -eq 'vscode') {
|
||||||
|
Write-Host "`n Tip: VS Code can also auto-discover the plugin by opening this repo"
|
||||||
|
Write-Host ' directly (it reads .copilot-plugin/plugin.json), no symlinks needed.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Cmd-Uninstall([string]$Id) {
|
||||||
|
$cfg = Resolve-Platform $Id
|
||||||
|
Write-Host "→ Removing skill links for $Id"
|
||||||
|
Unlink-Skills $cfg.Target $cfg.Style
|
||||||
|
if (Remove-Reparse $PluginLink) {
|
||||||
|
Write-Host " ✓ removed $PluginLink"
|
||||||
|
}
|
||||||
|
if (Test-Path $RepoDir) {
|
||||||
|
Write-Host "`nThe checkout at $RepoDir was kept (other platforms may still use it)."
|
||||||
|
Write-Host "To remove it: Remove-Item -Recurse -Force '$RepoDir'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Cmd-Update {
|
||||||
|
if (-not (Test-Path (Join-Path $RepoDir '.git'))) {
|
||||||
|
Write-Error "No installation found at $RepoDir. Run install first."
|
||||||
|
}
|
||||||
|
git -C $RepoDir pull --ff-only
|
||||||
|
Write-Host '✓ Updated.'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Help) { Show-Usage; return }
|
||||||
|
if ($Update) { Cmd-Update; return }
|
||||||
|
if ($Uninstall) { Cmd-Uninstall $Uninstall; return }
|
||||||
|
|
||||||
|
if (-not $Platform) { $Platform = Prompt-Platform }
|
||||||
|
Cmd-Install $Platform
|
||||||
280
Understand-Anything-main/install.sh
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Understand-Anything installer (macOS / Linux)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./install.sh Prompt for platform
|
||||||
|
# ./install.sh <platform> Install for <platform>
|
||||||
|
# ./install.sh --update Pull latest changes
|
||||||
|
# ./install.sh --uninstall <plat> Remove links for <plat>
|
||||||
|
# ./install.sh --help
|
||||||
|
#
|
||||||
|
# Curl-pipe usage:
|
||||||
|
# curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
|
||||||
|
# curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# UA_REPO_URL Override clone URL (default: official GitHub repo)
|
||||||
|
# UA_DIR Override clone destination (default: $HOME/.understand-anything/repo)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_URL="${UA_REPO_URL:-https://github.com/Lum1104/Understand-Anything.git}"
|
||||||
|
REPO_DIR="${UA_DIR:-$HOME/.understand-anything/repo}"
|
||||||
|
PLUGIN_LINK="$HOME/.understand-anything-plugin"
|
||||||
|
|
||||||
|
# Platform table — id|skills-target-dir|style
|
||||||
|
# style "per-skill": one symlink per skill into the target dir
|
||||||
|
# style "folder": one symlink for the whole skills/ dir into the target,
|
||||||
|
# named "understand-anything"
|
||||||
|
platforms_table() {
|
||||||
|
cat <<EOF
|
||||||
|
gemini|$HOME/.agents/skills|per-skill
|
||||||
|
codex|$HOME/.agents/skills|per-skill
|
||||||
|
opencode|$HOME/.agents/skills|per-skill
|
||||||
|
pi|$HOME/.agents/skills|per-skill
|
||||||
|
openclaw|$HOME/.openclaw/skills|folder
|
||||||
|
antigravity|$HOME/.gemini/antigravity/skills|folder
|
||||||
|
vibe|$HOME/.vibe/skills|per-skill
|
||||||
|
vscode|$HOME/.copilot/skills|per-skill
|
||||||
|
hermes|$HOME/.hermes/skills|folder
|
||||||
|
cline|$HOME/.cline/skills|folder
|
||||||
|
kimi|$HOME/.kimi/skills|folder
|
||||||
|
trae|$HOME/.trae/skills|per-skill
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_ids() { platforms_table | cut -d'|' -f1; }
|
||||||
|
|
||||||
|
resolve_platform() {
|
||||||
|
local id="$1"
|
||||||
|
local row
|
||||||
|
row="$(platforms_table | awk -F'|' -v id="$id" '$1==id {print; exit}')"
|
||||||
|
if [[ -z "$row" ]]; then
|
||||||
|
printf 'Unknown platform: %s\n' "$id" >&2
|
||||||
|
printf 'Supported: %s\n' "$(platform_ids | tr '\n' ' ')" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$row"
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_platform() {
|
||||||
|
local ids=()
|
||||||
|
while IFS= read -r id; do ids+=("$id"); done < <(platform_ids)
|
||||||
|
|
||||||
|
printf 'Which platform are you installing for?\n' >&2
|
||||||
|
local i=1
|
||||||
|
for id in "${ids[@]}"; do
|
||||||
|
printf ' %d) %s\n' "$i" "$id" >&2
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
printf 'Choose [1-%d]: ' "${#ids[@]}" >&2
|
||||||
|
|
||||||
|
local choice=""
|
||||||
|
if { exec 3</dev/tty; } 2>/dev/null; then
|
||||||
|
read -r choice <&3 || true
|
||||||
|
exec 3<&-
|
||||||
|
else
|
||||||
|
read -r choice || true
|
||||||
|
fi
|
||||||
|
if [[ -z "$choice" ]]; then
|
||||||
|
printf '\nNo input received. Pass the platform as an argument instead, e.g.:\n' >&2
|
||||||
|
printf ' install.sh codex\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#ids[@]} )); then
|
||||||
|
printf 'Invalid choice: %s\n' "$choice" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
printf '%s\n' "${ids[$((choice-1))]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
clone_or_update() {
|
||||||
|
if [[ -d "$REPO_DIR/.git" ]]; then
|
||||||
|
printf -- '→ Updating existing checkout at %s\n' "$REPO_DIR"
|
||||||
|
git -C "$REPO_DIR" pull --ff-only
|
||||||
|
else
|
||||||
|
printf -- '→ Cloning %s → %s\n' "$REPO_URL" "$REPO_DIR"
|
||||||
|
mkdir -p "$(dirname "$REPO_DIR")"
|
||||||
|
git clone "$REPO_URL" "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
skills_root() { printf '%s\n' "$REPO_DIR/understand-anything-plugin/skills"; }
|
||||||
|
|
||||||
|
list_skills() {
|
||||||
|
local root
|
||||||
|
root="$(skills_root)"
|
||||||
|
if [[ ! -d "$root" ]]; then
|
||||||
|
printf 'Skills directory not found: %s\n' "$root" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
local d
|
||||||
|
for d in "$root"/*/; do
|
||||||
|
[[ -d "$d" ]] || continue
|
||||||
|
basename "$d"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
link_skills() {
|
||||||
|
local target="$1" style="$2"
|
||||||
|
local root
|
||||||
|
root="$(skills_root)"
|
||||||
|
mkdir -p "$target"
|
||||||
|
case "$style" in
|
||||||
|
per-skill)
|
||||||
|
local skill
|
||||||
|
while IFS= read -r skill; do
|
||||||
|
ln -sfn "$root/$skill" "$target/$skill"
|
||||||
|
printf ' ✓ %s → %s\n' "$target/$skill" "$root/$skill"
|
||||||
|
done < <(list_skills)
|
||||||
|
;;
|
||||||
|
folder)
|
||||||
|
ln -sfn "$root" "$target/understand-anything"
|
||||||
|
printf ' ✓ %s → %s\n' "$target/understand-anything" "$root"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'Unknown style: %s\n' "$style" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink_skills() {
|
||||||
|
local target="$1" style="$2"
|
||||||
|
[[ -d "$target" ]] || return 0
|
||||||
|
case "$style" in
|
||||||
|
per-skill)
|
||||||
|
if [[ -d "$(skills_root)" ]]; then
|
||||||
|
local skill
|
||||||
|
while IFS= read -r skill; do
|
||||||
|
[[ -L "$target/$skill" ]] && rm -f "$target/$skill"
|
||||||
|
done < <(list_skills)
|
||||||
|
else
|
||||||
|
# Checkout is gone — scan the target dir for stale links pointing into
|
||||||
|
# our plugin tree so we can still clean up.
|
||||||
|
local link resolved
|
||||||
|
for link in "$target"/*; do
|
||||||
|
[[ -L "$link" ]] || continue
|
||||||
|
resolved="$(readlink "$link" 2>/dev/null || true)"
|
||||||
|
[[ "$resolved" == *"/understand-anything-plugin/skills/"* ]] || continue
|
||||||
|
rm -f "$link"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
folder)
|
||||||
|
[[ -L "$target/understand-anything" ]] && rm -f "$target/understand-anything"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
link_plugin_root() {
|
||||||
|
if [[ -L "$PLUGIN_LINK" || -e "$PLUGIN_LINK" ]]; then
|
||||||
|
printf ' • %s already exists, leaving as-is\n' "$PLUGIN_LINK"
|
||||||
|
else
|
||||||
|
ln -s "$REPO_DIR/understand-anything-plugin" "$PLUGIN_LINK"
|
||||||
|
printf ' ✓ %s → %s\n' "$PLUGIN_LINK" "$REPO_DIR/understand-anything-plugin"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_install() {
|
||||||
|
local id="$1"
|
||||||
|
local row target style
|
||||||
|
row="$(resolve_platform "$id")"
|
||||||
|
target="$(printf '%s\n' "$row" | cut -d'|' -f2)"
|
||||||
|
style="$(printf '%s\n' "$row" | cut -d'|' -f3)"
|
||||||
|
|
||||||
|
clone_or_update
|
||||||
|
printf -- '→ Linking skills for %s (%s → %s)\n' "$id" "$style" "$target"
|
||||||
|
link_skills "$target" "$style"
|
||||||
|
printf -- '→ Linking universal plugin root\n'
|
||||||
|
link_plugin_root
|
||||||
|
|
||||||
|
printf '\n✓ Installed Understand-Anything for %s\n' "$id"
|
||||||
|
printf ' Restart your CLI or IDE to pick up the skills.\n'
|
||||||
|
if [[ "$id" == "vscode" ]]; then
|
||||||
|
printf '\n Tip: VS Code can also auto-discover the plugin by opening this repo\n'
|
||||||
|
printf ' directly (it reads .copilot-plugin/plugin.json), no symlinks needed.\n'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_uninstall() {
|
||||||
|
local id="$1"
|
||||||
|
local row target style
|
||||||
|
row="$(resolve_platform "$id")"
|
||||||
|
target="$(printf '%s\n' "$row" | cut -d'|' -f2)"
|
||||||
|
style="$(printf '%s\n' "$row" | cut -d'|' -f3)"
|
||||||
|
|
||||||
|
printf -- '→ Removing skill links for %s\n' "$id"
|
||||||
|
unlink_skills "$target" "$style"
|
||||||
|
if [[ -L "$PLUGIN_LINK" ]]; then
|
||||||
|
rm -f "$PLUGIN_LINK"
|
||||||
|
printf ' ✓ removed %s\n' "$PLUGIN_LINK"
|
||||||
|
fi
|
||||||
|
if [[ -d "$REPO_DIR" ]]; then
|
||||||
|
printf '\nThe checkout at %s was kept (other platforms may still use it).\n' "$REPO_DIR"
|
||||||
|
printf 'To remove it: rm -rf "%s"\n' "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_update() {
|
||||||
|
if [[ ! -d "$REPO_DIR/.git" ]]; then
|
||||||
|
printf 'No installation found at %s. Run install first.\n' "$REPO_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
git -C "$REPO_DIR" pull --ff-only
|
||||||
|
printf '✓ Updated.\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<USAGE
|
||||||
|
Understand-Anything installer
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
install.sh [<platform>] Install for <platform> (or prompt if omitted)
|
||||||
|
install.sh --update Pull latest changes (skills update through symlinks)
|
||||||
|
install.sh --uninstall <platform> Remove links for <platform>
|
||||||
|
install.sh --help
|
||||||
|
|
||||||
|
Supported platforms:
|
||||||
|
$(platform_ids | sed 's/^/ - /')
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
UA_REPO_URL Override clone URL (default: official repo)
|
||||||
|
UA_DIR Override clone destination (default: \$HOME/.understand-anything/repo)
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
case "${1:-}" in
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
--update)
|
||||||
|
cmd_update
|
||||||
|
;;
|
||||||
|
--uninstall)
|
||||||
|
shift
|
||||||
|
if [[ -z "${1:-}" ]]; then
|
||||||
|
printf '%s\n' '--uninstall requires a platform argument' >&2
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cmd_uninstall "$1"
|
||||||
|
;;
|
||||||
|
"")
|
||||||
|
local id
|
||||||
|
id="$(prompt_platform)"
|
||||||
|
cmd_install "$id"
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
printf 'Unknown option: %s\n' "$1" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
cmd_install "$1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
39
Understand-Anything-main/package.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "understand-anything",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": ".opencode/plugins/understand-anything.js",
|
||||||
|
"packageManager": "pnpm@10.6.2+sha512.47870716bea1572b53df34ad8647b42962bc790ce2bf4562ba0f643237d7302a3d6a8ecef9e4bdfc01d23af1969aa90485d4cebb0b9638fa5ef1daef656f6c1b",
|
||||||
|
"scripts": {
|
||||||
|
"prepare": "pnpm --filter @understand-anything/core build",
|
||||||
|
"build": "pnpm -r build",
|
||||||
|
"test": "vitest run",
|
||||||
|
"dev:dashboard": "pnpm --filter @understand-anything/dashboard dev",
|
||||||
|
"lint": "eslint ."
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.0.0",
|
||||||
|
"eslint": "^9.0.0",
|
||||||
|
"globals": "^17.6.0",
|
||||||
|
"typescript": "^5.7.0",
|
||||||
|
"typescript-eslint": "^8.0.0",
|
||||||
|
"vitest": "^3.1.0"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"esbuild",
|
||||||
|
"sharp",
|
||||||
|
"tree-sitter-c",
|
||||||
|
"tree-sitter-c-sharp",
|
||||||
|
"tree-sitter-cpp",
|
||||||
|
"tree-sitter-go",
|
||||||
|
"tree-sitter-java",
|
||||||
|
"tree-sitter-javascript",
|
||||||
|
"tree-sitter-php",
|
||||||
|
"tree-sitter-python",
|
||||||
|
"tree-sitter-ruby",
|
||||||
|
"tree-sitter-rust",
|
||||||
|
"tree-sitter-typescript"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
6676
Understand-Anything-main/pnpm-lock.yaml
generated
Normal file
4
Understand-Anything-main/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
packages:
|
||||||
|
- 'understand-anything-plugin/packages/*'
|
||||||
|
- 'understand-anything-plugin'
|
||||||
|
- 'homepage'
|
||||||
297
Understand-Anything-main/scripts/generate-large-graph.mjs
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Generate a large fake knowledge graph for testing.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node scripts/generate-large-graph.mjs [nodeCount]
|
||||||
|
* node scripts/generate-large-graph.mjs [nodeCount] --messy
|
||||||
|
*
|
||||||
|
* Flags:
|
||||||
|
* --messy Inject LLM-style issues into ~20% of nodes/edges to test the
|
||||||
|
* dashboard robustness pipeline (Tier 1-3: null fields, wrong cases,
|
||||||
|
* missing fields, aliases, dangling refs, unrecognizable types).
|
||||||
|
*
|
||||||
|
* Default: 3000 nodes. Writes to .understand-anything/knowledge-graph.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { writeFileSync, mkdirSync } from "node:fs";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const MESSY = args.includes("--messy");
|
||||||
|
const numArg = args.find((a) => !a.startsWith("--"));
|
||||||
|
const NODE_COUNT = parseInt(numArg || "3000", 10);
|
||||||
|
const EDGE_RATIO = 1.7; // edges per node (realistic for codebases)
|
||||||
|
|
||||||
|
const nodeTypes = ["file", "function", "class", "module", "concept"];
|
||||||
|
const edgeTypes = [
|
||||||
|
"imports", "exports", "contains", "inherits", "implements",
|
||||||
|
"calls", "subscribes", "publishes", "middleware",
|
||||||
|
"reads_from", "writes_to", "transforms", "validates",
|
||||||
|
"depends_on", "tested_by", "configures",
|
||||||
|
"related", "similar_to",
|
||||||
|
];
|
||||||
|
const complexities = ["simple", "moderate", "complex"];
|
||||||
|
const languages = ["TypeScript", "JavaScript", "Python", "Go", "Rust"];
|
||||||
|
const frameworks = ["React", "Express", "FastAPI", "Gin", "Actix"];
|
||||||
|
|
||||||
|
function pick(arr) {
|
||||||
|
return arr[Math.floor(Math.random() * arr.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNodes(count) {
|
||||||
|
const nodes = [];
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const type = pick(nodeTypes);
|
||||||
|
const name = `${type}_${i}`;
|
||||||
|
nodes.push({
|
||||||
|
id: `node-${i}`,
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
filePath: type === "file" ? `src/${name}.ts` : undefined,
|
||||||
|
summary: `Auto-generated ${type} node #${i} for performance testing.`,
|
||||||
|
tags: [type, `group-${i % 20}`],
|
||||||
|
complexity: pick(complexities),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateEdges(nodes, edgeCount) {
|
||||||
|
const edges = [];
|
||||||
|
const seen = new Set();
|
||||||
|
const n = nodes.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < edgeCount; i++) {
|
||||||
|
let src, tgt;
|
||||||
|
// Forward-only edges to avoid cycles (dagre blows the stack on large cyclic graphs)
|
||||||
|
do {
|
||||||
|
src = Math.floor(Math.random() * (n - 1));
|
||||||
|
const offset = Math.floor(Math.random() * Math.min(50, n - src - 1)) + 1;
|
||||||
|
tgt = src + offset;
|
||||||
|
} while (tgt >= n || src === tgt || seen.has(`${src}-${tgt}`));
|
||||||
|
|
||||||
|
seen.add(`${src}-${tgt}`);
|
||||||
|
edges.push({
|
||||||
|
source: nodes[src].id,
|
||||||
|
target: nodes[tgt].id,
|
||||||
|
type: pick(edgeTypes),
|
||||||
|
direction: "forward",
|
||||||
|
weight: Math.round(Math.random() * 100) / 100,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateLayers(nodes) {
|
||||||
|
const layers = [];
|
||||||
|
const layerNames = [
|
||||||
|
"Presentation", "Application", "Domain", "Infrastructure",
|
||||||
|
"API Gateway", "Data Access", "Utilities", "Testing",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < layerNames.length; i++) {
|
||||||
|
const start = Math.floor((i / layerNames.length) * nodes.length);
|
||||||
|
const end = Math.floor(((i + 1) / layerNames.length) * nodes.length);
|
||||||
|
layers.push({
|
||||||
|
id: `layer-${i}`,
|
||||||
|
name: layerNames[i],
|
||||||
|
description: `${layerNames[i]} layer (auto-generated)`,
|
||||||
|
nodeIds: nodes.slice(start, end).map((n) => n.id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTour(nodes) {
|
||||||
|
const steps = [];
|
||||||
|
const stepCount = Math.min(8, Math.floor(nodes.length / 100));
|
||||||
|
for (let i = 0; i < stepCount; i++) {
|
||||||
|
const idx = Math.floor((i / stepCount) * nodes.length);
|
||||||
|
steps.push({
|
||||||
|
order: i + 1,
|
||||||
|
title: `Step ${i + 1}: Explore ${nodes[idx].name}`,
|
||||||
|
description: `This tour step highlights node **${nodes[idx].name}** and its surrounding context.`,
|
||||||
|
nodeIds: [nodes[idx].id, nodes[Math.min(idx + 1, nodes.length - 1)].id],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Messy injection (--messy flag) ──
|
||||||
|
|
||||||
|
// Tier 1: silent fixes — null optional fields, mixed-case enums
|
||||||
|
function injectTier1(node) {
|
||||||
|
const issues = [];
|
||||||
|
if (Math.random() < 0.5 && node.filePath !== undefined) {
|
||||||
|
node.filePath = null; // null on optional field
|
||||||
|
issues.push("null filePath");
|
||||||
|
}
|
||||||
|
if (Math.random() < 0.5) {
|
||||||
|
node.type = node.type.toUpperCase(); // "FILE", "FUNCTION"
|
||||||
|
issues.push(`uppercase type "${node.type}"`);
|
||||||
|
}
|
||||||
|
if (Math.random() < 0.5) {
|
||||||
|
node.complexity = node.complexity[0].toUpperCase() + node.complexity.slice(1); // "Simple"
|
||||||
|
issues.push(`mixed-case complexity "${node.complexity}"`);
|
||||||
|
}
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tier 2: auto-fixable — missing fields, aliases, string weights
|
||||||
|
function injectTier2Node(node) {
|
||||||
|
const issues = [];
|
||||||
|
const r = Math.random();
|
||||||
|
if (r < 0.2) {
|
||||||
|
delete node.complexity;
|
||||||
|
issues.push("missing complexity");
|
||||||
|
} else if (r < 0.4) {
|
||||||
|
node.complexity = pick(["low", "easy", "medium", "intermediate", "high", "hard"]);
|
||||||
|
issues.push(`complexity alias "${node.complexity}"`);
|
||||||
|
}
|
||||||
|
if (Math.random() < 0.3) {
|
||||||
|
delete node.tags;
|
||||||
|
issues.push("missing tags");
|
||||||
|
}
|
||||||
|
if (Math.random() < 0.2) {
|
||||||
|
delete node.summary;
|
||||||
|
issues.push("missing summary");
|
||||||
|
}
|
||||||
|
if (Math.random() < 0.15) {
|
||||||
|
node.type = pick(["func", "fn", "method", "interface", "struct", "mod", "pkg"]);
|
||||||
|
issues.push(`type alias "${node.type}"`);
|
||||||
|
}
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectTier2Edge(edge) {
|
||||||
|
const issues = [];
|
||||||
|
if (Math.random() < 0.3) {
|
||||||
|
edge.weight = String(edge.weight); // string weight
|
||||||
|
issues.push(`string weight "${edge.weight}"`);
|
||||||
|
}
|
||||||
|
if (Math.random() < 0.2) {
|
||||||
|
delete edge.direction;
|
||||||
|
issues.push("missing direction");
|
||||||
|
} else if (Math.random() < 0.3) {
|
||||||
|
edge.direction = pick(["to", "outbound", "from", "inbound", "both"]);
|
||||||
|
issues.push(`direction alias "${edge.direction}"`);
|
||||||
|
}
|
||||||
|
if (Math.random() < 0.15) {
|
||||||
|
edge.type = pick(["extends", "invokes", "uses", "requires", "relates_to"]);
|
||||||
|
issues.push(`edge type alias "${edge.type}"`);
|
||||||
|
}
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tier 3: unrecoverable — missing id/name, dangling refs, bad types
|
||||||
|
function injectTier3Node(node) {
|
||||||
|
const r = Math.random();
|
||||||
|
if (r < 0.4) {
|
||||||
|
delete node.id;
|
||||||
|
return "missing id";
|
||||||
|
} else if (r < 0.7) {
|
||||||
|
delete node.name;
|
||||||
|
return "missing name";
|
||||||
|
} else {
|
||||||
|
node.type = "totally_bogus_type";
|
||||||
|
return `unrecognizable type "${node.type}"`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectTier3Edge(edge, _validNodeIds) {
|
||||||
|
const r = Math.random();
|
||||||
|
if (r < 0.4) {
|
||||||
|
edge.target = "nonexistent-node-999999";
|
||||||
|
return "dangling target ref";
|
||||||
|
} else if (r < 0.7) {
|
||||||
|
edge.source = "nonexistent-node-888888";
|
||||||
|
return "dangling source ref";
|
||||||
|
} else {
|
||||||
|
edge.weight = "not_a_number";
|
||||||
|
return "non-coercible weight";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyMessy(nodes, edges) {
|
||||||
|
const stats = { tier1: 0, tier2: 0, tier3: 0 };
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
const r = Math.random();
|
||||||
|
if (r < 0.10) {
|
||||||
|
// ~10% get Tier 3 issues (will be dropped)
|
||||||
|
injectTier3Node(node);
|
||||||
|
stats.tier3++;
|
||||||
|
} else if (r < 0.30) {
|
||||||
|
// ~20% get Tier 2 issues (will be auto-corrected)
|
||||||
|
injectTier2Node(node);
|
||||||
|
stats.tier2++;
|
||||||
|
} else if (r < 0.40) {
|
||||||
|
// ~10% get Tier 1 issues (silently fixed)
|
||||||
|
injectTier1(node);
|
||||||
|
stats.tier1++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validIds = new Set(nodes.filter((n) => n.id).map((n) => n.id));
|
||||||
|
for (const edge of edges) {
|
||||||
|
const r = Math.random();
|
||||||
|
if (r < 0.05) {
|
||||||
|
injectTier3Edge(edge, validIds);
|
||||||
|
stats.tier3++;
|
||||||
|
} else if (r < 0.20) {
|
||||||
|
injectTier2Edge(edge);
|
||||||
|
stats.tier2++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also set tour/layers to null (Tier 1 null-vs-empty)
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Generate ──
|
||||||
|
|
||||||
|
const nodes = generateNodes(NODE_COUNT);
|
||||||
|
const edgeCount = Math.floor(NODE_COUNT * EDGE_RATIO);
|
||||||
|
const edges = generateEdges(nodes, edgeCount);
|
||||||
|
const layers = generateLayers(nodes);
|
||||||
|
const tour = generateTour(nodes);
|
||||||
|
|
||||||
|
let messyStats = null;
|
||||||
|
if (MESSY) {
|
||||||
|
messyStats = applyMessy(nodes, edges);
|
||||||
|
}
|
||||||
|
|
||||||
|
const graph = {
|
||||||
|
version: "1.0",
|
||||||
|
project: {
|
||||||
|
name: "large-test-project",
|
||||||
|
languages: languages.slice(0, 3),
|
||||||
|
frameworks: frameworks.slice(0, 2),
|
||||||
|
description: `Auto-generated project with ${NODE_COUNT} nodes for ${MESSY ? "robustness" : "performance"} testing.`,
|
||||||
|
analyzedAt: new Date().toISOString(),
|
||||||
|
gitCommitHash: "0000000000000000000000000000000000000000",
|
||||||
|
},
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
layers: MESSY && Math.random() < 0.5 ? null : layers,
|
||||||
|
tour: MESSY && Math.random() < 0.5 ? null : tour,
|
||||||
|
};
|
||||||
|
|
||||||
|
const outDir = resolve(process.cwd(), ".understand-anything");
|
||||||
|
mkdirSync(outDir, { recursive: true });
|
||||||
|
const outPath = resolve(outDir, "knowledge-graph.json");
|
||||||
|
writeFileSync(outPath, JSON.stringify(graph, null, 2));
|
||||||
|
|
||||||
|
console.log(`Generated knowledge graph${MESSY ? " (messy mode)" : ""}:`);
|
||||||
|
console.log(` Nodes: ${nodes.length}`);
|
||||||
|
console.log(` Edges: ${edges.length}`);
|
||||||
|
console.log(` Layers: ${graph.layers === null ? "null (Tier 1 test)" : layers.length}`);
|
||||||
|
console.log(` Tour steps: ${graph.tour === null ? "null (Tier 1 test)" : tour.length}`);
|
||||||
|
if (messyStats) {
|
||||||
|
console.log(` Injected issues:`);
|
||||||
|
console.log(` Tier 1 (silent fix): ~${messyStats.tier1} items`);
|
||||||
|
console.log(` Tier 2 (auto-correct): ~${messyStats.tier2} items`);
|
||||||
|
console.log(` Tier 3 (will be dropped): ~${messyStats.tier3} items`);
|
||||||
|
}
|
||||||
|
console.log(` Written to: ${outPath}`);
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "fixture-3-cliques",
|
||||||
|
"description": "Three disjoint import cliques for Louvain testing",
|
||||||
|
"languages": ["typescript"],
|
||||||
|
"frameworks": [],
|
||||||
|
"files": [
|
||||||
|
{"path": "src/auth/login.ts", "language": "typescript", "sizeLines": 50, "fileCategory": "code"},
|
||||||
|
{"path": "src/auth/session.ts", "language": "typescript", "sizeLines": 40, "fileCategory": "code"},
|
||||||
|
{"path": "src/auth/tokens.ts", "language": "typescript", "sizeLines": 60, "fileCategory": "code"},
|
||||||
|
{"path": "src/api/handlers.ts", "language": "typescript", "sizeLines": 80, "fileCategory": "code"},
|
||||||
|
{"path": "src/api/middleware.ts", "language": "typescript", "sizeLines": 30, "fileCategory": "code"},
|
||||||
|
{"path": "src/api/routes.ts", "language": "typescript", "sizeLines": 45, "fileCategory": "code"},
|
||||||
|
{"path": "src/db/users.ts", "language": "typescript", "sizeLines": 70, "fileCategory": "code"},
|
||||||
|
{"path": "src/db/queries.ts", "language": "typescript", "sizeLines": 55, "fileCategory": "code"},
|
||||||
|
{"path": "src/db/migrations.ts", "language": "typescript", "sizeLines": 35, "fileCategory": "code"}
|
||||||
|
],
|
||||||
|
"totalFiles": 9,
|
||||||
|
"filteredByIgnore": 0,
|
||||||
|
"estimatedComplexity": "small",
|
||||||
|
"importMap": {
|
||||||
|
"src/auth/login.ts": ["src/auth/session.ts", "src/auth/tokens.ts"],
|
||||||
|
"src/auth/session.ts": ["src/auth/tokens.ts"],
|
||||||
|
"src/auth/tokens.ts": [],
|
||||||
|
"src/api/handlers.ts": ["src/api/middleware.ts", "src/api/routes.ts"],
|
||||||
|
"src/api/middleware.ts": ["src/api/routes.ts", "src/auth/session.ts"],
|
||||||
|
"src/api/routes.ts": [],
|
||||||
|
"src/db/users.ts": ["src/db/queries.ts", "src/db/migrations.ts"],
|
||||||
|
"src/db/queries.ts": ["src/db/migrations.ts"],
|
||||||
|
"src/db/migrations.ts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
{
|
||||||
|
"name": "fixture-merge-respects-non-mergeable",
|
||||||
|
"description": "Regression guard for mergeSmallBatches: a small non-mergeable batch (Dockerfile cluster, marked mergeable=false by buildNonCodeBatches Group A) must NOT be pooled into the misc bucket alongside isolated code singletons, even though its size (1) is well below MIN_BATCH_SIZE=3. Pooling Dockerfiles into misc would destroy the semantic atom — an LLM analyzing the misc batch loses the per-service infra context.",
|
||||||
|
"languages": [
|
||||||
|
"typescript",
|
||||||
|
"dockerfile"
|
||||||
|
],
|
||||||
|
"frameworks": [],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "src/leaf000.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf001.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf002.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf003.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf004.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf005.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf006.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf007.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf008.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf009.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf010.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf011.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf012.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf013.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf014.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf015.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf016.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf017.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf018.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf019.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf020.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf021.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf022.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf023.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf024.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf025.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf026.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf027.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf028.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf029.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "services/api/Dockerfile",
|
||||||
|
"language": "dockerfile",
|
||||||
|
"sizeLines": 18,
|
||||||
|
"fileCategory": "infra"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"totalFiles": 31,
|
||||||
|
"filteredByIgnore": 0,
|
||||||
|
"estimatedComplexity": "moderate",
|
||||||
|
"importMap": {
|
||||||
|
"src/leaf000.ts": [],
|
||||||
|
"src/leaf001.ts": [],
|
||||||
|
"src/leaf002.ts": [],
|
||||||
|
"src/leaf003.ts": [],
|
||||||
|
"src/leaf004.ts": [],
|
||||||
|
"src/leaf005.ts": [],
|
||||||
|
"src/leaf006.ts": [],
|
||||||
|
"src/leaf007.ts": [],
|
||||||
|
"src/leaf008.ts": [],
|
||||||
|
"src/leaf009.ts": [],
|
||||||
|
"src/leaf010.ts": [],
|
||||||
|
"src/leaf011.ts": [],
|
||||||
|
"src/leaf012.ts": [],
|
||||||
|
"src/leaf013.ts": [],
|
||||||
|
"src/leaf014.ts": [],
|
||||||
|
"src/leaf015.ts": [],
|
||||||
|
"src/leaf016.ts": [],
|
||||||
|
"src/leaf017.ts": [],
|
||||||
|
"src/leaf018.ts": [],
|
||||||
|
"src/leaf019.ts": [],
|
||||||
|
"src/leaf020.ts": [],
|
||||||
|
"src/leaf021.ts": [],
|
||||||
|
"src/leaf022.ts": [],
|
||||||
|
"src/leaf023.ts": [],
|
||||||
|
"src/leaf024.ts": [],
|
||||||
|
"src/leaf025.ts": [],
|
||||||
|
"src/leaf026.ts": [],
|
||||||
|
"src/leaf027.ts": [],
|
||||||
|
"src/leaf028.ts": [],
|
||||||
|
"src/leaf029.ts": [],
|
||||||
|
"services/api/Dockerfile": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "fixture-non-code",
|
||||||
|
"description": "Mix of non-code files exercising Groups A-E. The src/ clique has 3 mutually-importing files so it survives merge-small (size >= MIN_BATCH_SIZE=3) and stays a pure-code batch — required by the 'non-code batch indices follow code batches' assertion.",
|
||||||
|
"languages": ["typescript", "dockerfile", "yaml", "sql", "markdown"],
|
||||||
|
"frameworks": [],
|
||||||
|
"files": [
|
||||||
|
{"path": "src/index.ts", "language": "typescript", "sizeLines": 10, "fileCategory": "code"},
|
||||||
|
{"path": "src/server.ts", "language": "typescript", "sizeLines": 15, "fileCategory": "code"},
|
||||||
|
{"path": "src/router.ts", "language": "typescript", "sizeLines": 12, "fileCategory": "code"},
|
||||||
|
{"path": "Dockerfile", "language": "dockerfile", "sizeLines": 20, "fileCategory": "infra"},
|
||||||
|
{"path": "docker-compose.yml", "language": "yaml", "sizeLines": 15, "fileCategory": "infra"},
|
||||||
|
{"path": ".dockerignore", "language": "config", "sizeLines": 5, "fileCategory": "config"},
|
||||||
|
{"path": "services/api/Dockerfile", "language": "dockerfile", "sizeLines": 18, "fileCategory": "infra"},
|
||||||
|
{"path": "services/api/docker-compose.yml", "language": "yaml", "sizeLines": 12, "fileCategory": "infra"},
|
||||||
|
{"path": ".github/workflows/ci.yml", "language": "yaml", "sizeLines": 30, "fileCategory": "infra"},
|
||||||
|
{"path": ".github/workflows/deploy.yml", "language": "yaml", "sizeLines": 25, "fileCategory": "infra"},
|
||||||
|
{"path": ".gitlab-ci.yml", "language": "yaml", "sizeLines": 20, "fileCategory": "infra"},
|
||||||
|
{"path": ".circleci/config.yml", "language": "yaml", "sizeLines": 25, "fileCategory": "infra"},
|
||||||
|
{"path": "migrations/001_init.sql", "language": "sql", "sizeLines": 40, "fileCategory": "data"},
|
||||||
|
{"path": "migrations/002_users.sql", "language": "sql", "sizeLines": 20, "fileCategory": "data"},
|
||||||
|
{"path": "docs/getting-started.md", "language": "markdown", "sizeLines": 100, "fileCategory": "docs"},
|
||||||
|
{"path": "README.md", "language": "markdown", "sizeLines": 200, "fileCategory": "docs"}
|
||||||
|
],
|
||||||
|
"totalFiles": 16,
|
||||||
|
"filteredByIgnore": 0,
|
||||||
|
"estimatedComplexity": "small",
|
||||||
|
"importMap": {
|
||||||
|
"src/index.ts": ["src/server.ts", "src/router.ts"],
|
||||||
|
"src/server.ts": ["src/router.ts"],
|
||||||
|
"src/router.ts": [],
|
||||||
|
"Dockerfile": [], "docker-compose.yml": [], ".dockerignore": [],
|
||||||
|
"services/api/Dockerfile": [], "services/api/docker-compose.yml": [],
|
||||||
|
".github/workflows/ci.yml": [], ".github/workflows/deploy.yml": [],
|
||||||
|
".gitlab-ci.yml": [], ".circleci/config.yml": [],
|
||||||
|
"migrations/001_init.sql": [], "migrations/002_users.sql": [],
|
||||||
|
"docs/getting-started.md": [], "README.md": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,715 @@
|
|||||||
|
{
|
||||||
|
"name": "fixture-singletons",
|
||||||
|
"description": "100 isolated TS files that should merge into ~4 misc batches",
|
||||||
|
"languages": [
|
||||||
|
"typescript"
|
||||||
|
],
|
||||||
|
"frameworks": [],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "src/leaf000.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf001.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf002.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf003.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf004.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf005.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf006.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf007.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf008.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf009.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf010.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf011.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf012.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf013.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf014.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf015.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf016.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf017.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf018.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf019.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf020.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf021.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf022.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf023.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf024.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf025.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf026.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf027.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf028.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf029.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf030.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf031.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf032.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf033.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf034.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf035.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf036.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf037.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf038.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf039.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf040.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf041.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf042.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf043.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf044.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf045.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf046.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf047.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf048.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf049.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf050.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf051.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf052.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf053.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf054.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf055.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf056.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf057.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf058.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf059.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf060.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf061.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf062.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf063.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf064.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf065.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf066.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf067.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf068.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf069.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf070.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf071.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf072.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf073.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf074.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf075.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf076.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf077.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf078.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf079.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf080.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf081.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf082.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf083.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf084.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf085.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf086.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf087.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf088.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf089.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf090.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf091.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf092.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf093.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf094.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf095.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf096.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf097.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf098.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/leaf099.ts",
|
||||||
|
"language": "typescript",
|
||||||
|
"sizeLines": 10,
|
||||||
|
"fileCategory": "code"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"totalFiles": 100,
|
||||||
|
"filteredByIgnore": 0,
|
||||||
|
"estimatedComplexity": "moderate",
|
||||||
|
"importMap": {
|
||||||
|
"src/leaf000.ts": [],
|
||||||
|
"src/leaf001.ts": [],
|
||||||
|
"src/leaf002.ts": [],
|
||||||
|
"src/leaf003.ts": [],
|
||||||
|
"src/leaf004.ts": [],
|
||||||
|
"src/leaf005.ts": [],
|
||||||
|
"src/leaf006.ts": [],
|
||||||
|
"src/leaf007.ts": [],
|
||||||
|
"src/leaf008.ts": [],
|
||||||
|
"src/leaf009.ts": [],
|
||||||
|
"src/leaf010.ts": [],
|
||||||
|
"src/leaf011.ts": [],
|
||||||
|
"src/leaf012.ts": [],
|
||||||
|
"src/leaf013.ts": [],
|
||||||
|
"src/leaf014.ts": [],
|
||||||
|
"src/leaf015.ts": [],
|
||||||
|
"src/leaf016.ts": [],
|
||||||
|
"src/leaf017.ts": [],
|
||||||
|
"src/leaf018.ts": [],
|
||||||
|
"src/leaf019.ts": [],
|
||||||
|
"src/leaf020.ts": [],
|
||||||
|
"src/leaf021.ts": [],
|
||||||
|
"src/leaf022.ts": [],
|
||||||
|
"src/leaf023.ts": [],
|
||||||
|
"src/leaf024.ts": [],
|
||||||
|
"src/leaf025.ts": [],
|
||||||
|
"src/leaf026.ts": [],
|
||||||
|
"src/leaf027.ts": [],
|
||||||
|
"src/leaf028.ts": [],
|
||||||
|
"src/leaf029.ts": [],
|
||||||
|
"src/leaf030.ts": [],
|
||||||
|
"src/leaf031.ts": [],
|
||||||
|
"src/leaf032.ts": [],
|
||||||
|
"src/leaf033.ts": [],
|
||||||
|
"src/leaf034.ts": [],
|
||||||
|
"src/leaf035.ts": [],
|
||||||
|
"src/leaf036.ts": [],
|
||||||
|
"src/leaf037.ts": [],
|
||||||
|
"src/leaf038.ts": [],
|
||||||
|
"src/leaf039.ts": [],
|
||||||
|
"src/leaf040.ts": [],
|
||||||
|
"src/leaf041.ts": [],
|
||||||
|
"src/leaf042.ts": [],
|
||||||
|
"src/leaf043.ts": [],
|
||||||
|
"src/leaf044.ts": [],
|
||||||
|
"src/leaf045.ts": [],
|
||||||
|
"src/leaf046.ts": [],
|
||||||
|
"src/leaf047.ts": [],
|
||||||
|
"src/leaf048.ts": [],
|
||||||
|
"src/leaf049.ts": [],
|
||||||
|
"src/leaf050.ts": [],
|
||||||
|
"src/leaf051.ts": [],
|
||||||
|
"src/leaf052.ts": [],
|
||||||
|
"src/leaf053.ts": [],
|
||||||
|
"src/leaf054.ts": [],
|
||||||
|
"src/leaf055.ts": [],
|
||||||
|
"src/leaf056.ts": [],
|
||||||
|
"src/leaf057.ts": [],
|
||||||
|
"src/leaf058.ts": [],
|
||||||
|
"src/leaf059.ts": [],
|
||||||
|
"src/leaf060.ts": [],
|
||||||
|
"src/leaf061.ts": [],
|
||||||
|
"src/leaf062.ts": [],
|
||||||
|
"src/leaf063.ts": [],
|
||||||
|
"src/leaf064.ts": [],
|
||||||
|
"src/leaf065.ts": [],
|
||||||
|
"src/leaf066.ts": [],
|
||||||
|
"src/leaf067.ts": [],
|
||||||
|
"src/leaf068.ts": [],
|
||||||
|
"src/leaf069.ts": [],
|
||||||
|
"src/leaf070.ts": [],
|
||||||
|
"src/leaf071.ts": [],
|
||||||
|
"src/leaf072.ts": [],
|
||||||
|
"src/leaf073.ts": [],
|
||||||
|
"src/leaf074.ts": [],
|
||||||
|
"src/leaf075.ts": [],
|
||||||
|
"src/leaf076.ts": [],
|
||||||
|
"src/leaf077.ts": [],
|
||||||
|
"src/leaf078.ts": [],
|
||||||
|
"src/leaf079.ts": [],
|
||||||
|
"src/leaf080.ts": [],
|
||||||
|
"src/leaf081.ts": [],
|
||||||
|
"src/leaf082.ts": [],
|
||||||
|
"src/leaf083.ts": [],
|
||||||
|
"src/leaf084.ts": [],
|
||||||
|
"src/leaf085.ts": [],
|
||||||
|
"src/leaf086.ts": [],
|
||||||
|
"src/leaf087.ts": [],
|
||||||
|
"src/leaf088.ts": [],
|
||||||
|
"src/leaf089.ts": [],
|
||||||
|
"src/leaf090.ts": [],
|
||||||
|
"src/leaf091.ts": [],
|
||||||
|
"src/leaf092.ts": [],
|
||||||
|
"src/leaf093.ts": [],
|
||||||
|
"src/leaf094.ts": [],
|
||||||
|
"src/leaf095.ts": [],
|
||||||
|
"src/leaf096.ts": [],
|
||||||
|
"src/leaf097.ts": [],
|
||||||
|
"src/leaf098.ts": [],
|
||||||
|
"src/leaf099.ts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,602 @@
|
|||||||
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const SCRIPT = resolve(__dirname, '../../../understand-anything-plugin/skills/understand/compute-batches.mjs');
|
||||||
|
const FIXTURES = resolve(__dirname, 'fixtures');
|
||||||
|
|
||||||
|
function runScript(projectRoot, extraArgs = []) {
|
||||||
|
return spawnSync('node', [SCRIPT, projectRoot, ...extraArgs], {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupProject(fixtureName) {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), 'ua-cb-test-'));
|
||||||
|
mkdirSync(join(root, '.understand-anything', 'intermediate'), { recursive: true });
|
||||||
|
const fixturePath = join(FIXTURES, fixtureName);
|
||||||
|
const dest = join(root, '.understand-anything', 'intermediate', 'scan-result.json');
|
||||||
|
writeFileSync(dest, readFileSync(fixturePath, 'utf-8'));
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readBatches(projectRoot) {
|
||||||
|
const p = join(projectRoot, '.understand-anything', 'intermediate', 'batches.json');
|
||||||
|
return JSON.parse(readFileSync(p, 'utf-8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('compute-batches.mjs — Louvain basic', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
projectRoot = setupProject('scan-result-3-cliques.json');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('produces 3 batches for 3 disjoint cliques', () => {
|
||||||
|
const result = runScript(projectRoot);
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
|
||||||
|
const batches = readBatches(projectRoot);
|
||||||
|
expect(batches.algorithm).toBe('louvain');
|
||||||
|
expect(batches.totalFiles).toBe(9);
|
||||||
|
expect(batches.batches.length).toBe(3);
|
||||||
|
expect(batches.schemaVersion).toBe(1);
|
||||||
|
expect(batches.totalBatches).toBe(3);
|
||||||
|
expect(batches.batches.map(b => b.batchIndex)).toEqual([1, 2, 3]);
|
||||||
|
|
||||||
|
// Each batch should contain exactly one clique (3 files)
|
||||||
|
for (const b of batches.batches) {
|
||||||
|
expect(b.files.length).toBe(3);
|
||||||
|
const dirs = new Set(b.files.map(f => f.path.split('/')[1]));
|
||||||
|
expect(dirs.size).toBe(1); // all files in the batch share src/<dir>/
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('produces deterministic output across runs', () => {
|
||||||
|
const r1 = runScript(projectRoot);
|
||||||
|
expect(r1.status).toBe(0);
|
||||||
|
const json1 = readFileSync(
|
||||||
|
join(projectRoot, '.understand-anything', 'intermediate', 'batches.json'),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
|
||||||
|
const r2 = runScript(projectRoot);
|
||||||
|
expect(r2.status).toBe(0);
|
||||||
|
const json2 = readFileSync(
|
||||||
|
join(projectRoot, '.understand-anything', 'intermediate', 'batches.json'),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(json1).toBe(json2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compute-batches.mjs — size enforcement', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
projectRoot = setupProject('scan-result-large-community.json');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('splits a 40-node clique into batches ≤ 35', () => {
|
||||||
|
const result = runScript(projectRoot);
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
|
||||||
|
const batches = readBatches(projectRoot);
|
||||||
|
expect(batches.algorithm).toBe('louvain'); // confirm fallback didn't fire
|
||||||
|
expect(batches.totalFiles).toBe(40);
|
||||||
|
expect(batches.batches.length).toBe(2);
|
||||||
|
expect(batches.batches.map(b => b.files.length).sort()).toEqual([20, 20]);
|
||||||
|
// Sum of all batch file counts equals total files
|
||||||
|
const sum = batches.batches.reduce((acc, b) => acc + b.files.length, 0);
|
||||||
|
expect(sum).toBe(40);
|
||||||
|
// Warning was emitted to stderr
|
||||||
|
expect(result.stderr).toMatch(/Warning: compute-batches: community size 40 > max 35/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compute-batches.mjs — exports extraction', () => {
|
||||||
|
let root;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (root) rmSync(root, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('populates exports for code files via tree-sitter', () => {
|
||||||
|
root = mkdtempSync(join(tmpdir(), 'ua-cb-exp-'));
|
||||||
|
mkdirSync(join(root, '.understand-anything', 'intermediate'), { recursive: true });
|
||||||
|
mkdirSync(join(root, 'src'), { recursive: true });
|
||||||
|
writeFileSync(join(root, 'src', 'a.ts'),
|
||||||
|
'export function greet(name: string) { return "hi " + name; }\n' +
|
||||||
|
'export class Greeter { greet(n: string) { return "hi " + n; } }\n');
|
||||||
|
writeFileSync(join(root, 'src', 'b.ts'),
|
||||||
|
'import { greet } from "./a";\nexport const helper = () => greet("world");\n');
|
||||||
|
|
||||||
|
const scan = {
|
||||||
|
name: 'exports-test',
|
||||||
|
description: '',
|
||||||
|
languages: ['typescript'],
|
||||||
|
frameworks: [],
|
||||||
|
files: [
|
||||||
|
{ path: 'src/a.ts', language: 'typescript', sizeLines: 2, fileCategory: 'code' },
|
||||||
|
{ path: 'src/b.ts', language: 'typescript', sizeLines: 2, fileCategory: 'code' },
|
||||||
|
],
|
||||||
|
totalFiles: 2, filteredByIgnore: 0, estimatedComplexity: 'small',
|
||||||
|
importMap: { 'src/a.ts': [], 'src/b.ts': ['src/a.ts'] },
|
||||||
|
};
|
||||||
|
writeFileSync(
|
||||||
|
join(root, '.understand-anything', 'intermediate', 'scan-result.json'),
|
||||||
|
JSON.stringify(scan));
|
||||||
|
|
||||||
|
const result = runScript(root);
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
|
||||||
|
const batches = readBatches(root);
|
||||||
|
expect(batches.exportsByPath).toBeDefined();
|
||||||
|
expect(batches.exportsByPath['src/a.ts']).toEqual(
|
||||||
|
expect.arrayContaining(['greet', 'Greeter']));
|
||||||
|
expect(batches.exportsByPath['src/b.ts']).toEqual(
|
||||||
|
expect.arrayContaining(['helper']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits warning when file is missing from disk (read error path)', () => {
|
||||||
|
root = mkdtempSync(join(tmpdir(), 'ua-cb-exp-err-'));
|
||||||
|
mkdirSync(join(root, '.understand-anything', 'intermediate'), { recursive: true });
|
||||||
|
// Note: NOT creating the file on disk — scan-result.json references it,
|
||||||
|
// but the file doesn't exist, so the read branch fires.
|
||||||
|
const scan = {
|
||||||
|
name: 'missing-file-test',
|
||||||
|
description: '',
|
||||||
|
languages: ['typescript'],
|
||||||
|
frameworks: [],
|
||||||
|
files: [
|
||||||
|
{ path: 'src/missing.ts', language: 'typescript', sizeLines: 1, fileCategory: 'code' },
|
||||||
|
],
|
||||||
|
totalFiles: 1, filteredByIgnore: 0, estimatedComplexity: 'small',
|
||||||
|
importMap: { 'src/missing.ts': [] },
|
||||||
|
};
|
||||||
|
writeFileSync(
|
||||||
|
join(root, '.understand-anything', 'intermediate', 'scan-result.json'),
|
||||||
|
JSON.stringify(scan));
|
||||||
|
|
||||||
|
const result = runScript(root);
|
||||||
|
expect(result.status).toBe(0); // script must still succeed
|
||||||
|
expect(result.stderr).toMatch(
|
||||||
|
/Warning: compute-batches: exports extraction failed for src\/missing\.ts \(read error:/);
|
||||||
|
|
||||||
|
const batches = readBatches(root);
|
||||||
|
expect(batches.exportsByPath['src/missing.ts']).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compute-batches.mjs — non-code grouping', () => {
|
||||||
|
let root;
|
||||||
|
let batches;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
root = setupProject('scan-result-non-code.json');
|
||||||
|
const result = runScript(root);
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
batches = readBatches(root);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (root) rmSync(root, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Group A: bundles Dockerfile cluster per directory', () => {
|
||||||
|
// Root-level cluster: Dockerfile + docker-compose.yml + .dockerignore → one batch
|
||||||
|
const rootDockerBatch = batches.batches.find(b =>
|
||||||
|
b.files.some(f => f.path === 'Dockerfile'));
|
||||||
|
expect(rootDockerBatch).toBeDefined();
|
||||||
|
const paths = rootDockerBatch.files.map(f => f.path).sort();
|
||||||
|
expect(paths).toEqual(['.dockerignore', 'Dockerfile', 'docker-compose.yml']);
|
||||||
|
|
||||||
|
// services/api cluster is a separate batch
|
||||||
|
const apiDockerBatch = batches.batches.find(b =>
|
||||||
|
b.files.some(f => f.path === 'services/api/Dockerfile'));
|
||||||
|
expect(apiDockerBatch).toBeDefined();
|
||||||
|
expect(apiDockerBatch).not.toBe(rootDockerBatch);
|
||||||
|
expect(apiDockerBatch.files.map(f => f.path).sort()).toEqual([
|
||||||
|
'services/api/Dockerfile', 'services/api/docker-compose.yml',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Group B: .github/workflows/* all in one batch', () => {
|
||||||
|
const wfBatch = batches.batches.find(b =>
|
||||||
|
b.files.some(f => f.path.startsWith('.github/workflows/')));
|
||||||
|
expect(wfBatch).toBeDefined();
|
||||||
|
const wfPaths = wfBatch.files.map(f => f.path).filter(p => p.startsWith('.github/workflows/'));
|
||||||
|
expect(wfPaths.sort()).toEqual([
|
||||||
|
'.github/workflows/ci.yml', '.github/workflows/deploy.yml',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Group C: .gitlab-ci.yml + .circleci/* in one batch', () => {
|
||||||
|
const ciBatch = batches.batches.find(b =>
|
||||||
|
b.files.some(f => f.path === '.gitlab-ci.yml'));
|
||||||
|
expect(ciBatch).toBeDefined();
|
||||||
|
const ciPaths = ciBatch.files.map(f => f.path).sort();
|
||||||
|
expect(ciPaths).toEqual(['.circleci/config.yml', '.gitlab-ci.yml']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Group D: SQL migrations under migrations/ in one batch', () => {
|
||||||
|
const migBatch = batches.batches.find(b =>
|
||||||
|
b.files.some(f => f.path.startsWith('migrations/')));
|
||||||
|
expect(migBatch).toBeDefined();
|
||||||
|
const migPaths = migBatch.files.map(f => f.path).filter(p => p.startsWith('migrations/'));
|
||||||
|
expect(migPaths.sort()).toEqual([
|
||||||
|
'migrations/001_init.sql', 'migrations/002_users.sql',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('non-code batch indices follow code batches', () => {
|
||||||
|
const codeBatches = batches.batches.filter(b =>
|
||||||
|
b.files.every(f => f.fileCategory === 'code'));
|
||||||
|
const nonCodeBatches = batches.batches.filter(b =>
|
||||||
|
b.files.some(f => f.fileCategory !== 'code'));
|
||||||
|
expect(codeBatches.length).toBeGreaterThan(0);
|
||||||
|
expect(nonCodeBatches.length).toBeGreaterThan(0);
|
||||||
|
const maxCodeIdx = Math.max(...codeBatches.map(b => b.batchIndex));
|
||||||
|
const minNonCodeIdx = Math.min(...nonCodeBatches.map(b => b.batchIndex));
|
||||||
|
expect(minNonCodeIdx).toBeGreaterThan(maxCodeIdx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compute-batches.mjs — Group E MAX_E split', () => {
|
||||||
|
let root;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (root) rmSync(root, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('splits 25 .md files under docs/ into [20, 5]', () => {
|
||||||
|
root = mkdtempSync(join(tmpdir(), 'ua-cb-maxe-'));
|
||||||
|
mkdirSync(join(root, '.understand-anything', 'intermediate'), { recursive: true });
|
||||||
|
|
||||||
|
const files = [];
|
||||||
|
const importMap = {};
|
||||||
|
for (let i = 0; i < 25; i++) {
|
||||||
|
const p = `docs/page${String(i).padStart(2, '0')}.md`;
|
||||||
|
files.push({ path: p, language: 'markdown', sizeLines: 10, fileCategory: 'docs' });
|
||||||
|
importMap[p] = [];
|
||||||
|
}
|
||||||
|
const scan = {
|
||||||
|
name: 'maxe-test', description: '',
|
||||||
|
languages: ['markdown'], frameworks: [],
|
||||||
|
files, totalFiles: 25, filteredByIgnore: 0,
|
||||||
|
estimatedComplexity: 'small', importMap,
|
||||||
|
};
|
||||||
|
writeFileSync(
|
||||||
|
join(root, '.understand-anything', 'intermediate', 'scan-result.json'),
|
||||||
|
JSON.stringify(scan));
|
||||||
|
|
||||||
|
const result = runScript(root);
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
|
||||||
|
const batches = readBatches(root);
|
||||||
|
// All 25 docs/ files go through Group E with MAX_E = 20, split into [20, 5].
|
||||||
|
const docsBatches = batches.batches.filter(b =>
|
||||||
|
b.files.every(f => f.path.startsWith('docs/')));
|
||||||
|
expect(docsBatches.length).toBe(2);
|
||||||
|
const sizes = docsBatches.map(b => b.files.length).sort((a, b) => b - a);
|
||||||
|
expect(sizes).toEqual([20, 5]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compute-batches.mjs — neighborMap + batchImportData', () => {
|
||||||
|
let batches;
|
||||||
|
let batchOf; // path → batchIndex
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
projectRoot = setupProject('scan-result-3-cliques.json');
|
||||||
|
const result = runScript(projectRoot);
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
batches = readBatches(projectRoot);
|
||||||
|
batchOf = new Map();
|
||||||
|
for (const b of batches.batches) {
|
||||||
|
for (const f of b.files) batchOf.set(f.path, b.batchIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('batchImportData mirrors scan importMap per batch', () => {
|
||||||
|
for (const b of batches.batches) {
|
||||||
|
for (const f of b.files) {
|
||||||
|
expect(b.batchImportData[f.path]).toBeDefined();
|
||||||
|
expect(Array.isArray(b.batchImportData[f.path])).toBe(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// src/auth/login.ts imports src/auth/session.ts and src/auth/tokens.ts
|
||||||
|
const loginBatch = batches.batches.find(b =>
|
||||||
|
b.files.some(f => f.path === 'src/auth/login.ts'));
|
||||||
|
expect(loginBatch.batchImportData['src/auth/login.ts'].sort()).toEqual([
|
||||||
|
'src/auth/session.ts', 'src/auth/tokens.ts',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('neighborMap excludes same-batch files', () => {
|
||||||
|
// The fixture's three cliques each go into one batch — all imports are
|
||||||
|
// intra-batch, so no neighbor map should reference any same-batch file.
|
||||||
|
for (const b of batches.batches) {
|
||||||
|
const sameBatchPaths = new Set(b.files.map(f => f.path));
|
||||||
|
for (const [, neighbors] of Object.entries(b.neighborMap)) {
|
||||||
|
for (const n of neighbors) {
|
||||||
|
expect(sameBatchPaths.has(n.path)).toBe(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('neighborMap entries carry symbols when target has exports', () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), 'ua-cb-nbr-'));
|
||||||
|
mkdirSync(join(root, '.understand-anything', 'intermediate'), { recursive: true });
|
||||||
|
mkdirSync(join(root, 'src', 'a'), { recursive: true });
|
||||||
|
mkdirSync(join(root, 'src', 'b'), { recursive: true });
|
||||||
|
|
||||||
|
// Cluster A: 3 tightly-imported files. a/core.ts exports symbols.
|
||||||
|
writeFileSync(join(root, 'src', 'a', 'core.ts'),
|
||||||
|
'export function findUser(id: string) { return null; }\nexport class User {}\n');
|
||||||
|
writeFileSync(join(root, 'src', 'a', 'helper1.ts'),
|
||||||
|
'import { findUser } from "./core";\nexport const h1 = () => findUser("x");\n');
|
||||||
|
writeFileSync(join(root, 'src', 'a', 'helper2.ts'),
|
||||||
|
'import { User } from "./core";\nimport { h1 } from "./helper1";\nexport const h2 = () => h1();\n');
|
||||||
|
|
||||||
|
// Cluster B: 3 tightly-imported files. b/entry.ts has ONE cross-cluster import to a/core.ts.
|
||||||
|
writeFileSync(join(root, 'src', 'b', 'entry.ts'),
|
||||||
|
'import { findUser } from "../a/core";\nexport const entry = () => findUser("y");\n');
|
||||||
|
writeFileSync(join(root, 'src', 'b', 'middle.ts'),
|
||||||
|
'import { entry } from "./entry";\nexport const middle = () => entry();\n');
|
||||||
|
writeFileSync(join(root, 'src', 'b', 'leaf.ts'),
|
||||||
|
'import { middle } from "./middle";\nexport const leaf = () => middle();\n');
|
||||||
|
|
||||||
|
const files = [
|
||||||
|
{ path: 'src/a/core.ts', language: 'typescript', sizeLines: 2, fileCategory: 'code' },
|
||||||
|
{ path: 'src/a/helper1.ts', language: 'typescript', sizeLines: 2, fileCategory: 'code' },
|
||||||
|
{ path: 'src/a/helper2.ts', language: 'typescript', sizeLines: 3, fileCategory: 'code' },
|
||||||
|
{ path: 'src/b/entry.ts', language: 'typescript', sizeLines: 2, fileCategory: 'code' },
|
||||||
|
{ path: 'src/b/middle.ts', language: 'typescript', sizeLines: 2, fileCategory: 'code' },
|
||||||
|
{ path: 'src/b/leaf.ts', language: 'typescript', sizeLines: 2, fileCategory: 'code' },
|
||||||
|
];
|
||||||
|
const scan = {
|
||||||
|
name: 't', description: '',
|
||||||
|
languages: ['typescript'], frameworks: [],
|
||||||
|
files,
|
||||||
|
totalFiles: 6, filteredByIgnore: 0, estimatedComplexity: 'small',
|
||||||
|
importMap: {
|
||||||
|
'src/a/core.ts': [],
|
||||||
|
'src/a/helper1.ts': ['src/a/core.ts'],
|
||||||
|
'src/a/helper2.ts': ['src/a/core.ts', 'src/a/helper1.ts'],
|
||||||
|
'src/b/entry.ts': ['src/a/core.ts'], // CROSS-CLUSTER
|
||||||
|
'src/b/middle.ts': ['src/b/entry.ts'],
|
||||||
|
'src/b/leaf.ts': ['src/b/middle.ts'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
writeFileSync(
|
||||||
|
join(root, '.understand-anything', 'intermediate', 'scan-result.json'),
|
||||||
|
JSON.stringify(scan));
|
||||||
|
|
||||||
|
const result = runScript(root);
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
const out = readBatches(root);
|
||||||
|
|
||||||
|
// Expect 2 communities (cluster A and cluster B). Verify that some batch's
|
||||||
|
// neighborMap entry references src/a/core.ts with its symbols.
|
||||||
|
let sawSymbols = false;
|
||||||
|
for (const batch of out.batches) {
|
||||||
|
for (const [, neighbors] of Object.entries(batch.neighborMap)) {
|
||||||
|
for (const n of neighbors) {
|
||||||
|
if (n.path === 'src/a/core.ts') {
|
||||||
|
expect(n.symbols).toEqual(expect.arrayContaining(['findUser', 'User']));
|
||||||
|
sawSymbols = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(sawSymbols).toBe(true);
|
||||||
|
|
||||||
|
rmSync(root, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compute-batches.mjs — neighborMap truncation', () => {
|
||||||
|
let root;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (root) rmSync(root, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('truncates and warns when neighbors > 50', () => {
|
||||||
|
root = mkdtempSync(join(tmpdir(), 'ua-cb-trunc-'));
|
||||||
|
mkdirSync(join(root, '.understand-anything', 'intermediate'), { recursive: true });
|
||||||
|
// hub.ts imported by 60 other files
|
||||||
|
const files = [{ path: 'src/hub.ts', language: 'typescript', sizeLines: 1, fileCategory: 'code' }];
|
||||||
|
const importMap = { 'src/hub.ts': [] };
|
||||||
|
for (let i = 0; i < 60; i++) {
|
||||||
|
const p = `src/leaf${i}.ts`;
|
||||||
|
files.push({ path: p, language: 'typescript', sizeLines: 1, fileCategory: 'code' });
|
||||||
|
importMap[p] = ['src/hub.ts'];
|
||||||
|
}
|
||||||
|
const scan = {
|
||||||
|
name: 't', description: '', languages: ['typescript'], frameworks: [],
|
||||||
|
files, totalFiles: files.length, filteredByIgnore: 0,
|
||||||
|
estimatedComplexity: 'moderate', importMap,
|
||||||
|
};
|
||||||
|
writeFileSync(
|
||||||
|
join(root, '.understand-anything', 'intermediate', 'scan-result.json'),
|
||||||
|
JSON.stringify(scan));
|
||||||
|
const result = runScript(root);
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
expect(result.stderr).toMatch(
|
||||||
|
/neighborMap for src\/hub\.ts has high 1-hop degree 60 — exceeds soft cap of 50/);
|
||||||
|
const out = readBatches(root);
|
||||||
|
// Find hub.ts and confirm its neighbor list capped at 50 (in whichever batch it landed)
|
||||||
|
for (const b of out.batches) {
|
||||||
|
const nbrs = b.neighborMap['src/hub.ts'];
|
||||||
|
if (nbrs) expect(nbrs.length).toBeLessThanOrEqual(50);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compute-batches.mjs — fallback', () => {
|
||||||
|
let root;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (root) rmSync(root, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to count-based when Louvain throws (env-injected mock)', () => {
|
||||||
|
// We can't easily monkey-patch louvain mid-script in Vitest because the
|
||||||
|
// script runs in a subprocess. Instead, set an env var the script honors:
|
||||||
|
// UA_COMPUTE_BATCHES_FORCE_LOUVAIN_THROW=1 → script throws inside its
|
||||||
|
// Louvain branch, exercising the fallback path.
|
||||||
|
root = setupProject('scan-result-3-cliques.json');
|
||||||
|
const result = spawnSync('node',
|
||||||
|
[SCRIPT, root],
|
||||||
|
{ encoding: 'utf-8', env: { ...process.env, UA_COMPUTE_BATCHES_FORCE_LOUVAIN_THROW: '1' } },
|
||||||
|
);
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
expect(result.stderr).toMatch(
|
||||||
|
/Warning: compute-batches: Louvain failed.*falling back to count-based grouping/);
|
||||||
|
const out = readBatches(root);
|
||||||
|
expect(out.algorithm).toBe('count-fallback');
|
||||||
|
expect(out.totalFiles).toBe(9);
|
||||||
|
// Count-based: 12 files per batch → all 9 fit in one batch
|
||||||
|
const codeBatchFileCount = out.batches
|
||||||
|
.filter(b => b.files.every(f => f.fileCategory === 'code'))
|
||||||
|
.reduce((sum, b) => sum + b.files.length, 0);
|
||||||
|
expect(codeBatchFileCount).toBe(9);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compute-batches.mjs — merge-small', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
projectRoot = setupProject('scan-result-singletons.json');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('merges 100 isolated singletons into a small number of misc batches', () => {
|
||||||
|
const result = runScript(projectRoot);
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
|
||||||
|
const batches = readBatches(projectRoot);
|
||||||
|
expect(batches.totalFiles).toBe(100);
|
||||||
|
|
||||||
|
// Without merge: 100 singletons → 100 batches.
|
||||||
|
// With merge-small (MAX_MERGE_TARGET=25): ceil(100 / 25) = exactly 4 misc
|
||||||
|
// batches. Pin the exact count — a loose >=4 && <=8 would mask off-by-one
|
||||||
|
// regressions in the slice math (e.g., a stride miscalculation that
|
||||||
|
// splintered the pool into 5-7 underfull buckets).
|
||||||
|
expect(batches.batches.length).toBe(4);
|
||||||
|
|
||||||
|
// All files accounted for
|
||||||
|
const totalAssigned = batches.batches.reduce((sum, b) => sum + b.files.length, 0);
|
||||||
|
expect(totalAssigned).toBe(100);
|
||||||
|
|
||||||
|
// Bucket-fullness check: 100 singletons evenly divisible by
|
||||||
|
// MAX_MERGE_TARGET=25, so every bucket must be exactly 25 — not just
|
||||||
|
// ≤ 25. Drift toward [25, 25, 25, 24, 1] etc. would slip past a
|
||||||
|
// ≤25 bound while indicating a stride bug.
|
||||||
|
for (const b of batches.batches) {
|
||||||
|
expect(b.files.length).toBe(25);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info: (not Warning:) — merge-small is a routine optimization, not a
|
||||||
|
// fallback path. See compute-batches.mjs mergeSmallBatches WHY comment.
|
||||||
|
expect(result.stderr).toMatch(
|
||||||
|
/Info: compute-batches: merged \d+ small batches \(\d+ files\) into \d+ misc batches/);
|
||||||
|
expect(result.stderr).not.toMatch(/Warning: compute-batches: merged \d+ small batches/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves non-mergeable batches: Dockerfile cluster not pooled into misc', () => {
|
||||||
|
// Dedicated fixture: 30 isolated TS singletons + 1 Dockerfile-only cluster.
|
||||||
|
// Group A marks the Dockerfile batch mergeable=false; even though its size
|
||||||
|
// (1) is below MIN_BATCH_SIZE=3, mergeSmallBatches must leave it intact.
|
||||||
|
const altRoot = setupProject('scan-result-merge-respects-non-mergeable.json');
|
||||||
|
try {
|
||||||
|
const result = runScript(altRoot);
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
|
||||||
|
const out = readBatches(altRoot);
|
||||||
|
expect(out.totalFiles).toBe(31);
|
||||||
|
|
||||||
|
const dockerBatch = out.batches.find(b =>
|
||||||
|
b.files.some(f => f.path === 'services/api/Dockerfile'));
|
||||||
|
expect(dockerBatch).toBeDefined();
|
||||||
|
// Standalone: exactly the Dockerfile, nothing pooled in alongside it.
|
||||||
|
expect(dockerBatch.files.length).toBe(1);
|
||||||
|
expect(dockerBatch.files[0].path).toBe('services/api/Dockerfile');
|
||||||
|
|
||||||
|
// The TS singletons must still merge into at least one misc batch —
|
||||||
|
// and that misc batch must NOT contain the Dockerfile.
|
||||||
|
const miscBatches = out.batches.filter(b =>
|
||||||
|
b.files.some(f => f.path.startsWith('src/leaf')));
|
||||||
|
expect(miscBatches.length).toBeGreaterThanOrEqual(1);
|
||||||
|
for (const m of miscBatches) {
|
||||||
|
for (const f of m.files) {
|
||||||
|
expect(f.path).not.toBe('services/api/Dockerfile');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every TS singleton accounted for across the misc bucket(s).
|
||||||
|
const tsInMisc = miscBatches.flatMap(b => b.files.map(f => f.path))
|
||||||
|
.filter(p => p.startsWith('src/leaf'));
|
||||||
|
expect(tsInMisc.length).toBe(30);
|
||||||
|
} finally {
|
||||||
|
rmSync(altRoot, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compute-batches.mjs — --changed-files', () => {
|
||||||
|
let root;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (root) rmSync(root, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits only batches containing changed files', () => {
|
||||||
|
root = setupProject('scan-result-3-cliques.json');
|
||||||
|
const changedPath = join(root, 'changed.txt');
|
||||||
|
// Only the auth clique is changed
|
||||||
|
writeFileSync(changedPath, ['src/auth/login.ts', 'src/auth/tokens.ts'].join('\n'));
|
||||||
|
|
||||||
|
const result = runScript(root, [`--changed-files=${changedPath}`]);
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
|
||||||
|
const out = readBatches(root);
|
||||||
|
// Auth files are in batches; other cliques' batches must be omitted
|
||||||
|
const allPaths = out.batches.flatMap(b => b.files.map(f => f.path));
|
||||||
|
expect(allPaths).toContain('src/auth/login.ts');
|
||||||
|
expect(allPaths).toContain('src/auth/tokens.ts');
|
||||||
|
expect(allPaths).not.toContain('src/api/handlers.ts');
|
||||||
|
expect(allPaths).not.toContain('src/db/users.ts');
|
||||||
|
|
||||||
|
// neighborMap may still reference unchanged files (with their full-graph batchIndex)
|
||||||
|
const loginBatch = out.batches.find(b =>
|
||||||
|
b.files.some(f => f.path === 'src/auth/login.ts'));
|
||||||
|
expect(loginBatch).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,738 @@
|
|||||||
|
import { describe, it, expect, afterEach } from 'vitest';
|
||||||
|
import {
|
||||||
|
mkdtempSync,
|
||||||
|
mkdirSync,
|
||||||
|
writeFileSync,
|
||||||
|
readFileSync,
|
||||||
|
rmSync,
|
||||||
|
chmodSync,
|
||||||
|
existsSync,
|
||||||
|
} from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join, dirname, resolve } from 'node:path';
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const SCRIPT = resolve(
|
||||||
|
__dirname,
|
||||||
|
'../../../understand-anything-plugin/skills/understand/scan-project.mjs',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a project tree from a `{ relPath: contents }` object. Creates parent
|
||||||
|
* directories as needed. Initializes a real git repo so the script's preferred
|
||||||
|
* `git ls-files` enumeration path is exercised — tests that need the walker
|
||||||
|
* fallback can set `gitInit=false`.
|
||||||
|
*/
|
||||||
|
function setupTree(files, { gitInit = true } = {}) {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), 'ua-scan-test-'));
|
||||||
|
for (const [relPath, contents] of Object.entries(files)) {
|
||||||
|
const abs = join(root, relPath);
|
||||||
|
mkdirSync(dirname(abs), { recursive: true });
|
||||||
|
writeFileSync(abs, contents, 'utf-8');
|
||||||
|
}
|
||||||
|
if (gitInit) {
|
||||||
|
// `git ls-files -co --exclude-standard` returns BOTH cached and others
|
||||||
|
// (modulo gitignore), so an `add` is unnecessary for our tests — the
|
||||||
|
// bare repo init is enough for ls-files to enumerate.
|
||||||
|
const init = spawnSync('git', ['init', '-q'], { cwd: root, encoding: 'utf-8' });
|
||||||
|
if (init.status !== 0) {
|
||||||
|
// CI without git: continue without it; the walker fallback will fire.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks every temp output dir created by runScript() so the global
|
||||||
|
* cleanup can sweep them between tests. The output file must live
|
||||||
|
* OUTSIDE projectRoot because the project's default ignore patterns
|
||||||
|
* do NOT exclude `.understand-anything/` (the dir is reserved for
|
||||||
|
* persistent state, not transient scratch). If we wrote inside
|
||||||
|
* projectRoot, the second call in the determinism test would
|
||||||
|
* enumerate the first call's output file and produce drift.
|
||||||
|
*/
|
||||||
|
const _runScriptOutputDirs = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run scan-project.mjs against `projectRoot`. Returns
|
||||||
|
* { status, stdout, stderr, output } where `output` is the parsed JSON
|
||||||
|
* written by the script (or null on failure).
|
||||||
|
*/
|
||||||
|
function runScript(projectRoot) {
|
||||||
|
const outputDir = mkdtempSync(join(tmpdir(), 'ua-scan-out-'));
|
||||||
|
_runScriptOutputDirs.push(outputDir);
|
||||||
|
const outputPath = join(outputDir, 'scan-output.json');
|
||||||
|
const result = spawnSync('node', [SCRIPT, projectRoot, outputPath], {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
});
|
||||||
|
let output = null;
|
||||||
|
try {
|
||||||
|
output = JSON.parse(readFileSync(outputPath, 'utf-8'));
|
||||||
|
} catch {
|
||||||
|
/* output missing on hard failure */
|
||||||
|
}
|
||||||
|
return { status: result.status, stdout: result.stdout, stderr: result.stderr, output };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the `files[]` entry for a given path. Returns undefined if not
|
||||||
|
* present — callers should `expect(byPath('x')).toBeDefined()` first.
|
||||||
|
*/
|
||||||
|
function byPath(output, path) {
|
||||||
|
return output.files.find(f => f.path === path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sweep every output dir created during a test back to disk-empty between
|
||||||
|
// tests. The top-level afterEach fires after each `it()` regardless of which
|
||||||
|
// describe block it lives in, so a single hook covers the whole file.
|
||||||
|
afterEach(() => {
|
||||||
|
while (_runScriptOutputDirs.length) {
|
||||||
|
const d = _runScriptOutputDirs.pop();
|
||||||
|
rmSync(d, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('scan-project.mjs — language detection', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) {
|
||||||
|
rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
projectRoot = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps TypeScript/JavaScript extensions to typescript/javascript', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'a.ts': 'export const a = 1;\n',
|
||||||
|
'b.tsx': 'export const B = () => null;\n',
|
||||||
|
'c.js': 'module.exports = {};\n',
|
||||||
|
'd.jsx': 'export default () => null;\n',
|
||||||
|
'e.mjs': 'export const e = 1;\n',
|
||||||
|
'f.cjs': 'module.exports = 1;\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'a.ts').language).toBe('typescript');
|
||||||
|
expect(byPath(r.output, 'b.tsx').language).toBe('typescript');
|
||||||
|
expect(byPath(r.output, 'c.js').language).toBe('javascript');
|
||||||
|
expect(byPath(r.output, 'd.jsx').language).toBe('javascript');
|
||||||
|
expect(byPath(r.output, 'e.mjs').language).toBe('javascript');
|
||||||
|
expect(byPath(r.output, 'f.cjs').language).toBe('javascript');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps Python, Go, Rust, Java, Kotlin, C# to their language ids', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'a.py': 'x = 1\n',
|
||||||
|
'b.go': 'package main\n',
|
||||||
|
'c.rs': 'fn main() {}\n',
|
||||||
|
'd.java': 'class D {}\n',
|
||||||
|
'e.kt': 'fun main() {}\n',
|
||||||
|
'f.cs': 'class F {}\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'a.py').language).toBe('python');
|
||||||
|
expect(byPath(r.output, 'b.go').language).toBe('go');
|
||||||
|
expect(byPath(r.output, 'c.rs').language).toBe('rust');
|
||||||
|
expect(byPath(r.output, 'd.java').language).toBe('java');
|
||||||
|
expect(byPath(r.output, 'e.kt').language).toBe('kotlin');
|
||||||
|
expect(byPath(r.output, 'f.cs').language).toBe('csharp');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps Ruby, PHP, C, C++ to their language ids', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'a.rb': 'puts 1\n',
|
||||||
|
'b.php': '<?php echo 1;\n',
|
||||||
|
'c.c': 'int main() { return 0; }\n',
|
||||||
|
'd.h': 'void f();\n',
|
||||||
|
'e.cpp': 'int main() {}\n',
|
||||||
|
'f.hpp': 'class F {};\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'a.rb').language).toBe('ruby');
|
||||||
|
expect(byPath(r.output, 'b.php').language).toBe('php');
|
||||||
|
expect(byPath(r.output, 'c.c').language).toBe('c');
|
||||||
|
expect(byPath(r.output, 'd.h').language).toBe('c');
|
||||||
|
expect(byPath(r.output, 'e.cpp').language).toBe('cpp');
|
||||||
|
expect(byPath(r.output, 'f.hpp').language).toBe('cpp');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps web markup (HTML, CSS) to their language ids', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'a.html': '<!doctype html><html></html>\n',
|
||||||
|
'b.htm': '<html></html>\n',
|
||||||
|
'c.css': '.a { }\n',
|
||||||
|
'd.scss': '$x: 1;\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'a.html').language).toBe('html');
|
||||||
|
expect(byPath(r.output, 'b.htm').language).toBe('html');
|
||||||
|
expect(byPath(r.output, 'c.css').language).toBe('css');
|
||||||
|
expect(byPath(r.output, 'd.scss').language).toBe('css');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps configuration formats (YAML, JSON, JSONC, TOML, XML, Markdown) to their language ids', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'a.yaml': 'x: 1\n',
|
||||||
|
'b.yml': 'x: 1\n',
|
||||||
|
'c.json': '{}\n',
|
||||||
|
'd.jsonc': '{ /* c */ }\n',
|
||||||
|
'e.toml': 'x = 1\n',
|
||||||
|
'f.xml': '<x/>\n',
|
||||||
|
'g.md': '# h\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'a.yaml').language).toBe('yaml');
|
||||||
|
expect(byPath(r.output, 'b.yml').language).toBe('yaml');
|
||||||
|
expect(byPath(r.output, 'c.json').language).toBe('json');
|
||||||
|
expect(byPath(r.output, 'd.jsonc').language).toBe('jsonc');
|
||||||
|
expect(byPath(r.output, 'e.toml').language).toBe('toml');
|
||||||
|
expect(byPath(r.output, 'f.xml').language).toBe('xml');
|
||||||
|
expect(byPath(r.output, 'g.md').language).toBe('markdown');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps shell + batch + Dockerfile (no extension) to their language ids', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'a.sh': 'echo 1\n',
|
||||||
|
'b.bat': '@echo off\n',
|
||||||
|
Dockerfile: 'FROM node:22\n',
|
||||||
|
'Dockerfile.dev': 'FROM node:22\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'a.sh').language).toBe('shell');
|
||||||
|
expect(byPath(r.output, 'b.bat').language).toBe('batch');
|
||||||
|
expect(byPath(r.output, 'Dockerfile').language).toBe('dockerfile');
|
||||||
|
expect(byPath(r.output, 'Dockerfile.dev').language).toBe('dockerfile');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to "unknown" for files with no extension and no filename match', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
WEIRD_FILE: 'mystery contents\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'WEIRD_FILE').language).toBe('unknown');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to bare extension (without dot) for unknown extensions', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'data.weirdext': 'some data\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'data.weirdext').language).toBe('weirdext');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('scan-project.mjs — category assignment (project-scanner.md Step 4)', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) {
|
||||||
|
rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
projectRoot = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns code to TypeScript, JavaScript, Python, Go, Rust source files', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'src/a.ts': 'export const a = 1;\n',
|
||||||
|
'src/b.py': 'def b(): pass\n',
|
||||||
|
'src/c.go': 'package main\n',
|
||||||
|
'src/d.rs': 'fn main() {}\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'src/a.ts').fileCategory).toBe('code');
|
||||||
|
expect(byPath(r.output, 'src/b.py').fileCategory).toBe('code');
|
||||||
|
expect(byPath(r.output, 'src/c.go').fileCategory).toBe('code');
|
||||||
|
expect(byPath(r.output, 'src/d.rs').fileCategory).toBe('code');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns config to JSON/YAML/TOML/INI/XML', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'package.json': '{}\n',
|
||||||
|
'tsconfig.json': '{}\n',
|
||||||
|
'pyproject.toml': '[project]\nname = "p"\n',
|
||||||
|
'config.yaml': 'x: 1\n',
|
||||||
|
'app.ini': '[s]\nk=v\n',
|
||||||
|
'data.xml': '<x/>\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'package.json').fileCategory).toBe('config');
|
||||||
|
expect(byPath(r.output, 'tsconfig.json').fileCategory).toBe('config');
|
||||||
|
expect(byPath(r.output, 'pyproject.toml').fileCategory).toBe('config');
|
||||||
|
expect(byPath(r.output, 'config.yaml').fileCategory).toBe('config');
|
||||||
|
expect(byPath(r.output, 'app.ini').fileCategory).toBe('config');
|
||||||
|
expect(byPath(r.output, 'data.xml').fileCategory).toBe('config');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns docs to .md / .rst / .txt (but NOT to LICENSE)', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'README.md': '# x\n',
|
||||||
|
'docs/guide.rst': 'Guide\n=====\n',
|
||||||
|
'NOTES.txt': 'notes\n',
|
||||||
|
LICENSE: 'Apache-2.0\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'README.md').fileCategory).toBe('docs');
|
||||||
|
expect(byPath(r.output, 'docs/guide.rst').fileCategory).toBe('docs');
|
||||||
|
expect(byPath(r.output, 'NOTES.txt').fileCategory).toBe('docs');
|
||||||
|
// LICENSE exception: must NOT be docs. The default ignore filter
|
||||||
|
// normally drops LICENSE entirely, so we re-include it via
|
||||||
|
// `!LICENSE` so the category test can fire.
|
||||||
|
writeFileSync(join(projectRoot, '.understandignore'), '!LICENSE\n');
|
||||||
|
const r2 = runScript(projectRoot);
|
||||||
|
const license = byPath(r2.output, 'LICENSE');
|
||||||
|
expect(license).toBeDefined();
|
||||||
|
expect(license.fileCategory).not.toBe('docs');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns infra to Dockerfile, docker-compose, .gitlab-ci.yml, .tf, .github/workflows/, Makefile, Jenkinsfile, k8s paths', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
Dockerfile: 'FROM node:22\n',
|
||||||
|
'docker-compose.yml': 'services: {}\n',
|
||||||
|
'.gitlab-ci.yml': 'stages: []\n',
|
||||||
|
'infra/main.tf': 'resource "x" "y" {}\n',
|
||||||
|
'.github/workflows/ci.yml': 'name: ci\n',
|
||||||
|
Makefile: 'all:\n\t@echo hi\n',
|
||||||
|
Jenkinsfile: 'pipeline { }\n',
|
||||||
|
'k8s/deploy.yaml': 'kind: Deployment\n',
|
||||||
|
'kubernetes/svc.yaml': 'kind: Service\n',
|
||||||
|
'foo.k8s.yaml': 'kind: ConfigMap\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'Dockerfile').fileCategory).toBe('infra');
|
||||||
|
expect(byPath(r.output, 'docker-compose.yml').fileCategory).toBe('infra');
|
||||||
|
expect(byPath(r.output, '.gitlab-ci.yml').fileCategory).toBe('infra');
|
||||||
|
expect(byPath(r.output, 'infra/main.tf').fileCategory).toBe('infra');
|
||||||
|
expect(byPath(r.output, '.github/workflows/ci.yml').fileCategory).toBe('infra');
|
||||||
|
expect(byPath(r.output, 'Makefile').fileCategory).toBe('infra');
|
||||||
|
expect(byPath(r.output, 'Jenkinsfile').fileCategory).toBe('infra');
|
||||||
|
expect(byPath(r.output, 'k8s/deploy.yaml').fileCategory).toBe('infra');
|
||||||
|
expect(byPath(r.output, 'kubernetes/svc.yaml').fileCategory).toBe('infra');
|
||||||
|
expect(byPath(r.output, 'foo.k8s.yaml').fileCategory).toBe('infra');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns data to SQL, GraphQL, Proto, Prisma, CSV', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'db/schema.sql': 'CREATE TABLE x (id INT);\n',
|
||||||
|
'api/schema.graphql': 'type X { id: ID! }\n',
|
||||||
|
'api/types.proto': 'syntax = "proto3";\n',
|
||||||
|
'prisma/schema.prisma': 'model X { id Int @id }\n',
|
||||||
|
'data/seed.csv': 'a,b\n1,2\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'db/schema.sql').fileCategory).toBe('data');
|
||||||
|
expect(byPath(r.output, 'api/schema.graphql').fileCategory).toBe('data');
|
||||||
|
expect(byPath(r.output, 'api/types.proto').fileCategory).toBe('data');
|
||||||
|
expect(byPath(r.output, 'prisma/schema.prisma').fileCategory).toBe('data');
|
||||||
|
expect(byPath(r.output, 'data/seed.csv').fileCategory).toBe('data');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns script to shell + batch files (.sh, .bash, .ps1, .bat)', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'scripts/build.sh': '#!/bin/bash\necho 1\n',
|
||||||
|
'scripts/run.bash': '#!/bin/bash\necho run\n',
|
||||||
|
'scripts/build.ps1': 'Write-Output 1\n',
|
||||||
|
'scripts/setup.bat': '@echo off\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'scripts/build.sh').fileCategory).toBe('script');
|
||||||
|
expect(byPath(r.output, 'scripts/run.bash').fileCategory).toBe('script');
|
||||||
|
expect(byPath(r.output, 'scripts/build.ps1').fileCategory).toBe('script');
|
||||||
|
expect(byPath(r.output, 'scripts/setup.bat').fileCategory).toBe('script');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns markup to HTML + CSS variants', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'public/index.html': '<!doctype html>\n',
|
||||||
|
'public/page.htm': '<html></html>\n',
|
||||||
|
'styles/app.css': 'body { }\n',
|
||||||
|
'styles/app.scss': '$x: 1;\n',
|
||||||
|
'styles/app.less': '@x: 1;\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'public/index.html').fileCategory).toBe('markup');
|
||||||
|
expect(byPath(r.output, 'public/page.htm').fileCategory).toBe('markup');
|
||||||
|
expect(byPath(r.output, 'styles/app.css').fileCategory).toBe('markup');
|
||||||
|
expect(byPath(r.output, 'styles/app.scss').fileCategory).toBe('markup');
|
||||||
|
expect(byPath(r.output, 'styles/app.less').fileCategory).toBe('markup');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('priority: docker-compose.yml maps to infra, not config', () => {
|
||||||
|
// The .yml extension would normally route to `config`, but the
|
||||||
|
// docker-compose.* filename rule fires first per Step 4 priority.
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'docker-compose.yml': 'services: {}\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'docker-compose.yml').fileCategory).toBe('infra');
|
||||||
|
expect(byPath(r.output, 'docker-compose.yml').language).toBe('yaml');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Regression: path.extname returns '' for `.env` and the second segment
|
||||||
|
// for `.env.local` — neither hits CATEGORY_BY_EXT['.env']. Dotfile-style
|
||||||
|
// configs were falling through to `code` / `unknown`. Caught by Codex
|
||||||
|
// review on PR #204.
|
||||||
|
it('dotfile configs (.env, .env.local, .env.production) map to config + env language', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'.env': 'API_KEY=abc\n',
|
||||||
|
'.env.local': 'LOCAL=1\n',
|
||||||
|
'.env.production': 'PROD=1\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
for (const p of ['.env', '.env.local', '.env.production']) {
|
||||||
|
expect(byPath(r.output, p).fileCategory).toBe('config');
|
||||||
|
// LANGUAGE_BY_EXT['.env'] -> 'config' (the language id itself; not
|
||||||
|
// a typo — the language for env files is the 'config' bucket).
|
||||||
|
expect(byPath(r.output, p).language).toBe('config');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('scan-project.mjs — .understandignore handling', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) {
|
||||||
|
rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
projectRoot = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('respects .understandignore patterns and increments filteredByIgnore', () => {
|
||||||
|
// `**/*.log` is NOT in the hardcoded defaults at the recursive level
|
||||||
|
// — wait, `*.log` is. Use a custom pattern to exercise user-driven drops.
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'.understandignore': 'fixtures/\n',
|
||||||
|
'src/index.ts': 'export const x = 1;\n',
|
||||||
|
'fixtures/snap1.json': '{ "a": 1 }\n',
|
||||||
|
'fixtures/snap2.json': '{ "b": 2 }\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
// fixtures/ files dropped
|
||||||
|
expect(byPath(r.output, 'fixtures/snap1.json')).toBeUndefined();
|
||||||
|
expect(byPath(r.output, 'fixtures/snap2.json')).toBeUndefined();
|
||||||
|
// Counted as user-driven
|
||||||
|
expect(r.output.filteredByIgnore).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports `!pattern` negation to re-include defaults-excluded files', () => {
|
||||||
|
// `*.log` is in the hardcoded defaults; the user re-includes a
|
||||||
|
// specific file with `!keep.log`. After the override, keep.log MUST
|
||||||
|
// appear in the output. It is NOT counted in filteredByIgnore (it
|
||||||
|
// was re-included, not additionally filtered).
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'.understandignore': '!keep.log\n',
|
||||||
|
'src/index.ts': 'export const x = 1;\n',
|
||||||
|
'keep.log': 'important diagnostic\n',
|
||||||
|
'drop.log': 'noise\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(byPath(r.output, 'keep.log')).toBeDefined();
|
||||||
|
// drop.log still excluded by defaults (no negation for it)
|
||||||
|
expect(byPath(r.output, 'drop.log')).toBeUndefined();
|
||||||
|
// The defaults dropped drop.log — that's a baseline default drop,
|
||||||
|
// NOT a user-driven drop. filteredByIgnore should be 0.
|
||||||
|
expect(r.output.filteredByIgnore).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('scan-project.mjs — special-file recognition', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) {
|
||||||
|
rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
projectRoot = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Dockerfile (no extension) is language=dockerfile, category=infra', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
Dockerfile: 'FROM alpine:3\nCMD ["sh"]\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
const entry = byPath(r.output, 'Dockerfile');
|
||||||
|
expect(entry).toBeDefined();
|
||||||
|
expect(entry.language).toBe('dockerfile');
|
||||||
|
expect(entry.fileCategory).toBe('infra');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('scan-project.mjs — determinism', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) {
|
||||||
|
rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
projectRoot = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('produces byte-identical output across runs for the same input tree', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'README.md': '# project\n',
|
||||||
|
'src/a.ts': 'export const a = 1;\n',
|
||||||
|
'src/b.ts': 'export const b = 2;\n',
|
||||||
|
'src/lib/c.ts': 'export const c = 3;\n',
|
||||||
|
'package.json': '{}\n',
|
||||||
|
'tsconfig.json': '{}\n',
|
||||||
|
});
|
||||||
|
const r1 = runScript(projectRoot);
|
||||||
|
const r2 = runScript(projectRoot);
|
||||||
|
expect(r1.status).toBe(0);
|
||||||
|
expect(r2.status).toBe(0);
|
||||||
|
expect(JSON.stringify(r1.output)).toBe(JSON.stringify(r2.output));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('scan-project.mjs — empty repo', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) {
|
||||||
|
rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
projectRoot = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles a project with zero files without crashing', () => {
|
||||||
|
projectRoot = setupTree({}, { gitInit: true });
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(r.output.scriptCompleted).toBe(true);
|
||||||
|
expect(r.output.totalFiles).toBe(0);
|
||||||
|
expect(r.output.files).toEqual([]);
|
||||||
|
expect(r.output.filteredByIgnore).toBe(0);
|
||||||
|
expect(r.output.estimatedComplexity).toBe('small');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('scan-project.mjs — per-file failure resilience', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) {
|
||||||
|
// Restore permissions on any chmod'd file before delete, so cleanup
|
||||||
|
// succeeds even when a test left a 000-permission file behind.
|
||||||
|
try {
|
||||||
|
const f = join(projectRoot, 'src/unreadable.ts');
|
||||||
|
if (existsSync(f)) chmodSync(f, 0o644);
|
||||||
|
} catch { /* best-effort */ }
|
||||||
|
rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
projectRoot = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits a Warning: and skips a file with unreadable permissions; other files survive', () => {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
// chmod permission bits don't apply on Windows the same way; skip.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (process.getuid && process.getuid() === 0) {
|
||||||
|
// Running as root bypasses permission checks; the test cannot exercise
|
||||||
|
// its failure mode. Skip rather than emit a false pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'src/good.ts': 'export const good = 1;\n',
|
||||||
|
'src/unreadable.ts': 'export const bad = 2;\n',
|
||||||
|
});
|
||||||
|
// Strip read permission on the synthetic file.
|
||||||
|
chmodSync(join(projectRoot, 'src/unreadable.ts'), 0o000);
|
||||||
|
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(r.output.scriptCompleted).toBe(true);
|
||||||
|
// The good file is in the output.
|
||||||
|
expect(byPath(r.output, 'src/good.ts')).toBeDefined();
|
||||||
|
// The unreadable file is dropped.
|
||||||
|
expect(byPath(r.output, 'src/unreadable.ts')).toBeUndefined();
|
||||||
|
// A visible warning was emitted with the documented prefix.
|
||||||
|
expect(r.stderr).toMatch(
|
||||||
|
/Warning: scan-project: src\/unreadable\.ts — line count failed/,
|
||||||
|
);
|
||||||
|
expect(r.stderr).toMatch(/file skipped from output/);
|
||||||
|
// Final summary line still fires.
|
||||||
|
expect(r.stderr).toMatch(
|
||||||
|
/scan-project: filesScanned=1 filteredByIgnore=0 complexity=small/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('scan-project.mjs — estimatedComplexity thresholds', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) {
|
||||||
|
rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
projectRoot = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a tree with exactly N .ts files at the top level. Used to
|
||||||
|
* lock in the complexity-tier boundary points from project-scanner.md
|
||||||
|
* Step 7: small (≤30), moderate (31-150), large (151-500), very-large
|
||||||
|
* (>500).
|
||||||
|
*/
|
||||||
|
function setupNFiles(n) {
|
||||||
|
const tree = {};
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
// Pad indices so localeCompare gives the natural order for any N.
|
||||||
|
tree[`f${String(i).padStart(4, '0')}.ts`] = 'export const x = 1;\n';
|
||||||
|
}
|
||||||
|
return setupTree(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('30 files -> small (upper boundary of small)', () => {
|
||||||
|
projectRoot = setupNFiles(30);
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(r.output.totalFiles).toBe(30);
|
||||||
|
expect(r.output.estimatedComplexity).toBe('small');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('31 files -> moderate (lower boundary of moderate)', () => {
|
||||||
|
projectRoot = setupNFiles(31);
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(r.output.totalFiles).toBe(31);
|
||||||
|
expect(r.output.estimatedComplexity).toBe('moderate');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('150 files -> moderate (upper boundary of moderate)', () => {
|
||||||
|
projectRoot = setupNFiles(150);
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(r.output.totalFiles).toBe(150);
|
||||||
|
expect(r.output.estimatedComplexity).toBe('moderate');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('151 files -> large (lower boundary of large)', () => {
|
||||||
|
projectRoot = setupNFiles(151);
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(r.output.totalFiles).toBe(151);
|
||||||
|
expect(r.output.estimatedComplexity).toBe('large');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('501 files -> very-large (lower boundary of very-large)', () => {
|
||||||
|
projectRoot = setupNFiles(501);
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(r.output.totalFiles).toBe(501);
|
||||||
|
expect(r.output.estimatedComplexity).toBe('very-large');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('scan-project.mjs — CLI entry guard + invocation', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) {
|
||||||
|
rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
projectRoot = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invokes successfully via subprocess and produces a parseable output file', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'README.md': '# proj\n',
|
||||||
|
'src/index.ts': 'export const x = 1;\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
expect(r.output).not.toBeNull();
|
||||||
|
expect(r.output.scriptCompleted).toBe(true);
|
||||||
|
// Stats summary line fires on stderr.
|
||||||
|
expect(r.stderr).toMatch(
|
||||||
|
/scan-project: filesScanned=2 filteredByIgnore=0 complexity=small/,
|
||||||
|
);
|
||||||
|
// Two files captured.
|
||||||
|
expect(r.output.totalFiles).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails fast with usage message when projectRoot is missing', () => {
|
||||||
|
const result = spawnSync('node', [SCRIPT], { encoding: 'utf-8' });
|
||||||
|
expect(result.status).toBe(1);
|
||||||
|
expect(result.stderr).toMatch(/Usage: node scan-project\.mjs/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('scan-project.mjs — output schema invariants', () => {
|
||||||
|
let projectRoot;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (projectRoot) {
|
||||||
|
rmSync(projectRoot, { recursive: true, force: true });
|
||||||
|
projectRoot = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits the documented top-level fields with correct shapes', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'src/a.ts': 'export const a = 1;\n',
|
||||||
|
'README.md': '# x\n',
|
||||||
|
'package.json': '{}\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
const out = r.output;
|
||||||
|
expect(out.scriptCompleted).toBe(true);
|
||||||
|
expect(Array.isArray(out.files)).toBe(true);
|
||||||
|
expect(typeof out.totalFiles).toBe('number');
|
||||||
|
expect(out.totalFiles).toBe(out.files.length);
|
||||||
|
expect(typeof out.filteredByIgnore).toBe('number');
|
||||||
|
expect(['small', 'moderate', 'large', 'very-large']).toContain(
|
||||||
|
out.estimatedComplexity,
|
||||||
|
);
|
||||||
|
expect(out.stats).toBeDefined();
|
||||||
|
expect(out.stats.filesScanned).toBe(out.files.length);
|
||||||
|
expect(typeof out.stats.byCategory).toBe('object');
|
||||||
|
expect(typeof out.stats.byLanguage).toBe('object');
|
||||||
|
// Per-file shape
|
||||||
|
for (const f of out.files) {
|
||||||
|
expect(typeof f.path).toBe('string');
|
||||||
|
expect(typeof f.language).toBe('string');
|
||||||
|
expect(typeof f.sizeLines).toBe('number');
|
||||||
|
expect([
|
||||||
|
'code', 'config', 'docs', 'infra', 'data', 'script', 'markup',
|
||||||
|
]).toContain(f.fileCategory);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('files[] is sorted by path.localeCompare', () => {
|
||||||
|
projectRoot = setupTree({
|
||||||
|
'zzz.ts': '\n',
|
||||||
|
'aaa.ts': '\n',
|
||||||
|
'mmm.ts': '\n',
|
||||||
|
'subdir/file.ts': '\n',
|
||||||
|
});
|
||||||
|
const r = runScript(projectRoot);
|
||||||
|
expect(r.status).toBe(0);
|
||||||
|
const paths = r.output.files.map(f => f.path);
|
||||||
|
const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
|
||||||
|
expect(paths).toEqual(sortedPaths);
|
||||||
|
});
|
||||||
|
});
|
||||||