Merge branch 'main' into feat/add-kiro-platform-support

This commit is contained in:
정수민/아이엔소프트
2026-06-15 11:34:39 +09:00
committed by GitHub
54 changed files with 2244 additions and 531 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
"description": "LLM-powered codebase analysis producing interactive knowledge graphs, guided tours, and deep-dive explanations"
},
"owner": {
"name": "Lum1104"
"name": "Egonex"
},
"plugins": [
{
+4 -4
View File
@@ -1,12 +1,12 @@
{
"name": "understand-anything",
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
"version": "2.7.5",
"version": "2.7.7",
"author": {
"name": "Lum1104"
"name": "Egonex"
},
"homepage": "https://github.com/Lum1104/Understand-Anything",
"repository": "https://github.com/Lum1104/Understand-Anything",
"homepage": "https://github.com/Egonex-AI/Understand-Anything",
"repository": "https://github.com/Egonex-AI/Understand-Anything",
"license": "MIT",
"keywords": [
"codebase-analysis",
+4 -4
View File
@@ -1,12 +1,12 @@
{
"name": "understand-anything",
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
"version": "2.7.5",
"version": "2.7.7",
"author": {
"name": "Lum1104"
"name": "Egonex"
},
"homepage": "https://github.com/Lum1104/Understand-Anything",
"repository": "https://github.com/Lum1104/Understand-Anything",
"homepage": "https://github.com/Egonex-AI/Understand-Anything",
"repository": "https://github.com/Egonex-AI/Understand-Anything",
"license": "MIT",
"keywords": ["codebase-analysis", "knowledge-graph", "architecture", "onboarding", "dashboard"],
"skills": "./understand-anything-plugin/skills/",
+4 -4
View File
@@ -2,12 +2,12 @@
"name": "understand-anything",
"displayName": "Understand Anything",
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
"version": "2.7.5",
"version": "2.7.7",
"author": {
"name": "Lum1104"
"name": "Egonex"
},
"homepage": "https://github.com/Lum1104/Understand-Anything",
"repository": "https://github.com/Lum1104/Understand-Anything",
"homepage": "https://github.com/Egonex-AI/Understand-Anything",
"repository": "https://github.com/Egonex-AI/Understand-Anything",
"license": "MIT",
"keywords": ["codebase-analysis", "knowledge-graph", "architecture", "onboarding", "dashboard"],
"skills": "./understand-anything-plugin/skills/",
-1
View File
@@ -1 +0,0 @@
patreon: Lum1104
+85
View File
@@ -0,0 +1,85 @@
name: Bug report
description: Report something that isn't working
title: "bug: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to file a bug. The more concrete you can be,
the faster it gets fixed. If you can include the analyzed project's
primary language and an approximate file count, that's gold.
- type: textarea
id: what-happened
attributes:
label: What happened?
description: What did you do, what did you expect to happen, and what actually happened?
placeholder: |
1. Ran `/understand --full` on a ~3,000 file Rust project
2. Expected: dashboard opens with the graph
3. Got: dashboard shows "Failed to load graph: schema validation error"
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Minimal reproduction
description: Smallest set of steps (or a link to a public repo) that reproduces the issue.
validations:
required: false
- type: input
id: version
attributes:
label: Plugin version
description: Run `/understand --version` or check `~/.claude/plugins/cache/understand-anything/understand-anything/`.
placeholder: "e.g. 2.7.4"
validations:
required: true
- type: dropdown
id: platform
attributes:
label: Platform / client
multiple: true
options:
- Claude Code (CLI)
- Claude Code (VS Code extension)
- Claude Code (JetBrains)
- Cursor
- GitHub Copilot CLI
- opencode
- Other (please describe in "What happened?")
validations:
required: true
- type: input
id: os
attributes:
label: OS + Node version
placeholder: "e.g. macOS 14.5 (arm64), Node v22.6.0"
validations:
required: true
- type: input
id: project-language
attributes:
label: Primary language of the analyzed project
placeholder: "e.g. TypeScript, Python, Swift…"
- type: input
id: file-count
attributes:
label: Approximate file count of the analyzed project
placeholder: "e.g. ~3,000"
- type: textarea
id: logs
attributes:
label: Relevant logs
description: |
Any console output, the contents of `.understand-anything/intermediate/`
if it still exists, or screenshots of the dashboard error.
render: shell
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: README & docs
url: https://github.com/Egonex-AI/Understand-Anything#readme
about: Most usage questions are answered in the project README.
- name: Discussions
url: https://github.com/Egonex-AI/Understand-Anything/discussions
about: For open-ended discussion, design proposals, or sharing how you use the tool.
@@ -0,0 +1,34 @@
name: Feature request
description: Suggest an idea or improvement
title: "feat: "
labels: ["enhancement"]
body:
- type: textarea
id: problem
attributes:
label: What problem are you trying to solve?
description: Describe the user pain or workflow gap. Concrete examples help more than abstract framing.
placeholder: |
When onboarding new engineers to our 8k-file Go monorepo, they spend
days finding the auth boundary. /understand finds the files but the
dashboard doesn't visually separate "trusted" from "untrusted" zones.
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed solution (optional)
description: If you have an idea for what the feature should look like, share it. Skip if you'd rather just describe the problem.
- type: textarea
id: alternatives
attributes:
label: Alternatives you've considered
description: Other tools, workarounds, or approaches you've tried.
- type: input
id: scope
attributes:
label: Which part of the project?
placeholder: "skill / dashboard / core / agents / all"
+24
View File
@@ -0,0 +1,24 @@
name: Question / usage help
description: Ask a question about how to use the project
title: "question: "
labels: ["question"]
body:
- type: markdown
attributes:
value: |
For general usage questions. If you found a bug, please use the bug
report template instead — it asks for the information needed to
reproduce.
- type: textarea
id: question
attributes:
label: Your question
validations:
required: true
- type: textarea
id: tried
attributes:
label: What have you already tried?
description: Helps avoid suggesting things you've already ruled out.
+26
View File
@@ -0,0 +1,26 @@
## Summary
<!-- One or two sentences on what this PR changes and why. -->
## Linked issue(s)
<!-- e.g. Closes #123, Refs #456. Leave empty if there's no tracking issue. -->
## How I tested this
<!-- Concrete steps. "Ran the test suite" is fine; "Ran /understand on a 3k-file
Swift repo and verified the dashboard shows non-empty edges" is better. -->
- [ ] `pnpm lint`
- [ ] `pnpm --filter @understand-anything/core test`
- [ ] `pnpm test`
- [ ] Manual smoke test (describe above)
## Versioning
<!-- If this PR ships a user-visible behavior change, bump the version in ALL
five manifests per CLAUDE.md. If it's docs/tests/internal-only, leave them
alone and the maintainer will bump on merge. -->
- [ ] Version bumped in all five manifests, OR
- [ ] N/A — internal/docs-only change
+14
View File
@@ -1,7 +1,21 @@
name: CI
on:
# Run on every PR so a contributor's first push gets feedback.
pull_request:
# Also run on direct pushes to main so the "main is green" signal is real.
# Without this, main can silently break for days when someone bypasses
# review. (#249)
push:
branches: [main]
# Cancel any in-flight CI for the same ref when a new commit is pushed —
# saves runner minutes and keeps the latest commit's status the only one
# anyone reads. `github.ref` is a controlled value (refs/heads/* or
# refs/pull/*/merge), not user-controlled input, so it's safe to interpolate.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
ci:
+4 -1
View File
@@ -12,5 +12,8 @@ __pycache__/
.worktrees/
homepage/public/demo/
.private/
__pycache__/
.venv/
venv/
*.pyc
*.pyo
Thumbs.db
+39
View File
@@ -0,0 +1,39 @@
# Code of Conduct
We want this project to be a welcoming place for everyone who wants to
contribute, learn, or use it — regardless of experience level, background, or
identity.
## In short
- **Be respectful.** Treat others the way you'd want to be treated.
- **Assume good intent.** Most disagreements are misunderstandings.
- **Be constructive.** Critique ideas, not people. Suggest improvements.
- **Keep it on-topic.** This project is about understanding codebases.
## What's not OK
- Personal attacks, insults, or sustained disruption of discussions.
- Posting someone's private information without their explicit permission.
- Repeatedly ignoring requests from maintainers to change behavior.
## Reporting
If you see behavior that violates this code, please open a private email to
the maintainer listed in the repository profile, or use GitHub's
[private vulnerability / abuse reporting](https://docs.github.com/en/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam).
Maintainers will review reports and take whatever action they think is
appropriate — typically a private warning, sometimes a temporary or permanent
ban from the project. Reports will be kept confidential.
## Scope
This code applies in all project spaces: issues, pull requests, discussions,
commits, and any other project-affiliated channel.
---
This document is intentionally short. It's based on the spirit of the
[Contributor Covenant](https://www.contributor-covenant.org/) without
reproducing it verbatim.
+1
View File
@@ -1,6 +1,7 @@
MIT License
Copyright (c) 2026 Yuxiang Lin
Copyright (c) 2026 Infinite Universe, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+32 -19
View File
@@ -7,7 +7,13 @@
</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>
<strong>Understand Anything. <a href="https://egonex.ai">Understand Anyone.</a></strong>
<br />
<em>AI should help people, not replace them.</em>
</p>
<p align="center">
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Understand Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<p align="center">
@@ -16,7 +22,7 @@
<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://github.com/Egonex-AI/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>
@@ -27,6 +33,7 @@
<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>
<a href="https://egonex.ai"><img src="https://img.shields.io/badge/Understand_Anyone-egonex.ai-d4a574" alt="Understand Anyone" /></a>
</p>
<p align="center">
@@ -34,9 +41,9 @@
</p>
<p align="center">
<strong>💬 <a href="https://discord.gg/pydat66RY">Join the Discord community &rarr;</a></strong>
<strong>An open-source project from <a href="https://github.com/Egonex-AI">Egonex</a></strong>
<br />
<em>Ask questions, share what you've built, get help from the community.</em>
<em>Originally created by <a href="https://github.com/Lum1104">Lum1104</a>.</em>
</p>
---
@@ -106,7 +113,7 @@ Point `/understand-knowledge` at a [Karpathy-pattern LLM wiki](https://gist.gith
### 1. Install the plugin
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
@@ -127,6 +134,8 @@ A multi-agent pipeline scans your project, extracts every file, function, class,
# Supported languages: en (default), zh, zh-TW, ja, ko, ru
```
On the **first run** in a project — when you don't pass `--language` and no language is stored yet — `/understand` detects the language you're conversing in. If it isn't English, it asks you to confirm (or override) before generating; English conversations are unaffected. Your choice is saved to `.understand-anything/config.json` and reused on every later run.
The `--language` parameter affects:
- Node summaries and descriptions in the knowledge graph
- Dashboard UI labels, buttons, and tooltips
@@ -180,27 +189,29 @@ Understand-Anything works across multiple AI coding platforms.
### Claude Code (Native)
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/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 / Kiro)
### One-line install (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Trae / Nanobot / Kiro)
**macOS / Linux:**
```bash
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/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
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex
```
**Windows (PowerShell):**
```powershell
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
iwr -useb https://raw.githubusercontent.com/Egonex-AI/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`, `kiro`
- Supported `<platform>` values: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `trae`, `nanobot`, `kiro`
- Update later: `./install.sh --update`
- Uninstall: `./install.sh --uninstall <platform>`
@@ -208,7 +219,7 @@ The installer clones the repo to `~/.understand-anything/repo` and creates the r
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.
If auto-discovery doesn't pick it up, install it manually: open **Cursor Settings → Plugins**, paste `https://github.com/Egonex-AI/Understand-Anything` into the search field, and add it from there.
### VS Code + GitHub Copilot
@@ -219,7 +230,7 @@ For personal skills (available across all projects), run the `install.sh` above
### Copilot CLI
```bash
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin
```
### Kiro CLI / IDE
@@ -253,15 +264,17 @@ For personal skills (available across all projects), run the `install.sh` above
| Cline | ✅ Supported | `install.sh cline` |
| KIMI CLI | ✅ Supported | `install.sh kimi` |
| Trae | ✅ Supported | `install.sh trae` |
| Nanobot | ✅ Supported | `install.sh nanobot` |
| Kiro CLI / IDE | ✅ Supported | `install.sh kiro` |
---
## 📦 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.
> **Example:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/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).
@@ -344,11 +357,11 @@ Please open an issue first for major changes so we can discuss the approach.
## Star History
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
<a href="https://www.star-history.com/?repos=Egonex-AI%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" />
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
</picture>
</a>
@@ -357,5 +370,5 @@ Please open an issue first for major changes so we can discuss the approach.
</p>
<p align="center">
MIT License &copy; <a href="https://github.com/Lum1104">Lum1104</a>
MIT License &copy; Yuxiang Lin and Infinite Universe, Inc.
</p>
+20 -19
View File
@@ -6,7 +6,7 @@
</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>
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Understand Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<p align="center">
@@ -15,7 +15,7 @@
<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://github.com/Egonex-AI/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>
@@ -31,9 +31,9 @@
</p>
<p align="center">
<strong>💬 <a href="https://discord.gg/pydat66RY">Únete a la comunidad de Discord &rarr;</a></strong>
<strong>An open-source project from <a href="https://github.com/Egonex-AI">Egonex</a></strong>
<br />
<em>Pregunta, comparte lo que construyes y recibe ayuda de la comunidad.</em>
<em>Originally created by <a href="https://github.com/Lum1104">Lum1104</a>.</em>
</p>
---
@@ -103,7 +103,7 @@ Apunta `/understand-knowledge` a un [wiki LLM con patrón Karpathy](https://gist
### 1. Instala el plugin
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
@@ -177,27 +177,27 @@ Understand-Anything funciona en múltiples plataformas de codificación con IA.
### Claude Code (Nativo)
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/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)
### Instalación de una línea (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot)
**macOS / Linux:**
```bash
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/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
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex
```
**Windows (PowerShell):**
```powershell
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
iwr -useb https://raw.githubusercontent.com/Egonex-AI/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`
- Valores soportados de `<platform>`: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `nanobot`
- Actualizar más adelante: `./install.sh --update`
- Desinstalar: `./install.sh --uninstall <platform>`
@@ -205,7 +205,7 @@ El instalador clona el repositorio en `~/.understand-anything/repo` y crea los e
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í.
Si la detección automática no lo reconoce, instálalo manualmente: abre **Cursor Settings → Plugins**, pega `https://github.com/Egonex-AI/Understand-Anything` en el campo de búsqueda y añádelo desde allí.
### VS Code + GitHub Copilot
@@ -216,7 +216,7 @@ Para habilidades personales (disponibles en todos los proyectos), ejecuta el `in
### Copilot CLI
```bash
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin
```
### Compatibilidad de Plataformas
@@ -237,6 +237,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
| Hermes | ✅ Soportado | `install.sh hermes` |
| Cline | ✅ Soportado | `install.sh cline` |
| KIMI CLI | ✅ Soportado | `install.sh kimi` |
| Nanobot | ✅ Soportado | `install.sh nanobot` |
---
@@ -244,7 +245,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
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.
> **Ejemplo:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/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).
@@ -327,11 +328,11 @@ Para cambios importantes, abre primero un issue para que podamos discutir el enf
## Historial de Stars
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
<a href="https://www.star-history.com/?repos=Egonex-AI%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" />
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
</picture>
</a>
@@ -340,5 +341,5 @@ Para cambios importantes, abre primero un issue para que podamos discutir el enf
</p>
<p align="center">
Licencia MIT &copy; <a href="https://github.com/Lum1104">Lum1104</a>
MIT License &copy; Yuxiang Lin and Infinite Universe, Inc.
</p>
+20 -19
View File
@@ -7,7 +7,7 @@
</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>
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Understand Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<p align="center">
@@ -16,7 +16,7 @@
<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://github.com/Egonex-AI/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>
@@ -32,9 +32,9 @@
</p>
<p align="center">
<strong>💬 <a href="https://discord.gg/pydat66RY">Discord コミュニティに参加 &rarr;</a></strong>
<strong>An open-source project from <a href="https://github.com/Egonex-AI">Egonex</a></strong>
<br />
<em>質問・作品の共有・コミュニティとの交流はこちらから。</em>
<em>Originally created by <a href="https://github.com/Lum1104">Lum1104</a>.</em>
</p>
---
@@ -104,7 +104,7 @@ Understand Anything は [Claude Code Plugin](https://code.claude.com/docs/en/plu
### 1. プラグインをインストール
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
@@ -178,27 +178,27 @@ Understand-Anythingは複数のAIコーディングプラットフォームで
### Claude Code(ネイティブ)
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
### ワンラインインストール(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI
### ワンラインインストール(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot
**macOS / Linux**
```bash
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash
# プラットフォームを直接指定して対話プロンプトをスキップすることもできます:
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex
```
**WindowsPowerShell):**
```powershell
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex
```
インストーラーはリポジトリを `~/.understand-anything/repo` にクローンし、選択したプラットフォーム用のシンボリックリンクを作成します。完了後はCLI/IDEを再起動してください。
- サポートされる `<platform>` 値:`gemini``codex``opencode``pi``openclaw``antigravity``vibe``vscode``hermes``cline``kimi`
- サポートされる `<platform>` 値:`gemini``codex``opencode``pi``openclaw``antigravity``vibe``vscode``hermes``cline``kimi``nanobot`
- 後で更新:`./install.sh --update`
- アンインストール:`./install.sh --uninstall <platform>`
@@ -206,7 +206,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins
Cursorはこのリポジトリをクローンすると `.cursor-plugin/plugin.json` 経由でプラグインを自動検出します。手動インストールは不要です — クローンしてCursorで開くだけです。
自動検出されない場合は、手動でインストールしてください:**Cursor Settings → Plugins** を開き、検索欄に `https://github.com/Lum1104/Understand-Anything` を貼り付けて追加します。
自動検出されない場合は、手動でインストールしてください:**Cursor Settings → Plugins** を開き、検索欄に `https://github.com/Egonex-AI/Understand-Anything` を貼り付けて追加します。
### VS Code + GitHub Copilot
@@ -217,7 +217,7 @@ GitHub Copilot拡張機能(v1.108+)をインストールしたVS Codeは、`
### Copilot CLI
```bash
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin
```
### プラットフォーム互換性
@@ -238,6 +238,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
| Hermes | ✅ サポート | `install.sh hermes` |
| Cline | ✅ サポート | `install.sh cline` |
| KIMI CLI | ✅ サポート | `install.sh kimi` |
| Nanobot | ✅ サポート | `install.sh nanobot` |
---
@@ -245,7 +246,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
グラフは単なる JSON ファイルです——**一度コミットすれば、チームメンバーはパイプラインを実行せずに済みます**。オンボーディング、PR レビュー、docs-as-code ワークフローに最適です。
> **例:** [GoogleCloudPlatform/microservices-demofork](https://github.com/Lum1104/microservices-demo) —— コミット済みのグラフを含む Go / Java / Python / Node のリファレンスプロジェクト。
> **例:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/microservices-demo) —— コミット済みのグラフを含む Go / Java / Python / Node のリファレンスプロジェクト。
**コミット対象:** `.understand-anything/` 内のすべてのファイル。ただし `intermediate/``diff-overlay.json` は除きます(これらはローカルの一時ファイルです)。
@@ -328,11 +329,11 @@ git add .gitattributes .understand-anything/
## Star History
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
<a href="https://www.star-history.com/?repos=Egonex-AI%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" />
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
</picture>
</a>
@@ -341,5 +342,5 @@ git add .gitattributes .understand-anything/
</p>
<p align="center">
MIT License &copy; <a href="https://github.com/Lum1104">Lum1104</a>
MIT License &copy; Yuxiang Lin and Infinite Universe, Inc.
</p>
+20 -19
View File
@@ -6,7 +6,7 @@
</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>
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Understand Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<p align="center">
@@ -15,7 +15,7 @@
<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://github.com/Egonex-AI/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>
@@ -31,9 +31,9 @@
</p>
<p align="center">
<strong>💬 <a href="https://discord.gg/pydat66RY">Discord 커뮤니티 참여하기 &rarr;</a></strong>
<strong>An open-source project from <a href="https://github.com/Egonex-AI">Egonex</a></strong>
<br />
<em>질문하고, 만든 것을 공유하고, 커뮤니티의 도움을 받으세요.</em>
<em>Originally created by <a href="https://github.com/Lum1104">Lum1104</a>.</em>
</p>
---
@@ -103,7 +103,7 @@ Understand Anything은 [Claude Code Plugin](https://code.claude.com/docs/en/plug
### 1. 플러그인 설치
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
@@ -177,27 +177,27 @@ Understand-Anything은 다양한 AI 코딩 플랫폼에서 사용할 수 있습
### Claude Code (네이티브)
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
### 한 줄 설치 (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI)
### 한 줄 설치 (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot)
**macOS / Linux:**
```bash
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash
# 플랫폼 이름을 직접 전달하여 프롬프트를 건너뛸 수도 있습니다:
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex
```
**Windows (PowerShell):**
```powershell
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex
```
설치 스크립트는 저장소를 `~/.understand-anything/repo`에 클론하고 선택한 플랫폼에 맞는 심볼릭 링크를 생성합니다. 설치 후 CLI 또는 IDE를 재시작하세요.
- 지원되는 `<platform>` 값: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`
- 지원되는 `<platform>` 값: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `nanobot`
- 이후 업데이트: `./install.sh --update`
- 제거: `./install.sh --uninstall <platform>`
@@ -205,7 +205,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins
이 저장소를 클론하면 Cursor가 `.cursor-plugin/plugin.json`을 통해 플러그인을 자동으로 인식합니다. 수동 설치가 필요 없습니다. 클론 후 Cursor에서 열기만 하면 됩니다.
자동 인식이 되지 않으면 수동으로 설치하세요: **Cursor Settings → Plugins**를 열고 검색란에 `https://github.com/Lum1104/Understand-Anything`를 붙여넣은 뒤 추가하세요.
자동 인식이 되지 않으면 수동으로 설치하세요: **Cursor Settings → Plugins**를 열고 검색란에 `https://github.com/Egonex-AI/Understand-Anything`를 붙여넣은 뒤 추가하세요.
### VS Code + GitHub Copilot
@@ -216,7 +216,7 @@ GitHub Copilot(v1.108+)이 설치된 VS Code는 `.copilot-plugin/plugin.json`을
### Copilot CLI
```bash
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin
```
### 플랫폼 호환성
@@ -237,6 +237,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
| Hermes | ✅ 지원 | `install.sh hermes` |
| Cline | ✅ 지원 | `install.sh cline` |
| KIMI CLI | ✅ 지원 | `install.sh kimi` |
| Nanobot | ✅ 지원 | `install.sh nanobot` |
---
@@ -244,7 +245,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
그래프는 단지 JSON 파일입니다 — **한 번만 커밋하면 팀원은 파이프라인을 건너뛸 수 있습니다**. 온보딩, PR 리뷰, docs-as-code 워크플로에 적합합니다.
> **예시:** [GoogleCloudPlatform/microservices-demo (fork)](https://github.com/Lum1104/microservices-demo) — 커밋된 그래프를 포함한 Go / Java / Python / Node 레퍼런스 프로젝트.
> **예시:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/microservices-demo) — 커밋된 그래프를 포함한 Go / Java / Python / Node 레퍼런스 프로젝트.
**커밋할 대상:** `.understand-anything/` 내부의 모든 파일. 단, `intermediate/``diff-overlay.json` 은 제외합니다 (이들은 로컬 임시 파일입니다).
@@ -327,11 +328,11 @@ git add .gitattributes .understand-anything/
## Star 히스토리
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
<a href="https://www.star-history.com/?repos=Egonex-AI%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" />
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
</picture>
</a>
@@ -340,5 +341,5 @@ git add .gitattributes .understand-anything/
</p>
<p align="center">
MIT 라이선스 &copy; <a href="https://github.com/Lum1104">Lum1104</a>
MIT License &copy; Yuxiang Lin and Infinite Universe, Inc.
</p>
+20 -19
View File
@@ -7,7 +7,7 @@
</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>
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Understand Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<p align="center">
@@ -16,7 +16,7 @@
<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://github.com/Egonex-AI/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>
@@ -32,9 +32,9 @@
</p>
<p align="center">
<strong>💬 <a href="https://discord.gg/pydat66RY">Присоединяйтесь к сообществу в Discord &rarr;</a></strong>
<strong>An open-source project from <a href="https://github.com/Egonex-AI">Egonex</a></strong>
<br />
<em>Задавайте вопросы, делитесь тем, что вы построили, получайте помощь от сообщества.</em>
<em>Originally created by <a href="https://github.com/Lum1104">Lum1104</a>.</em>
</p>
---
@@ -104,7 +104,7 @@ Understand Anything — это [плагин для Claude Code](https://code.cl
### 1. Установите плагин
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
@@ -178,27 +178,27 @@ Understand-Anything работает с несколькими платформ
### Claude Code (нативно)
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
### Установка одной командой (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI)
### Установка одной командой (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot)
**macOS / Linux:**
```bash
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash
# или передайте платформу, чтобы пропустить интерактивный выбор:
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex
```
**Windows (PowerShell):**
```powershell
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex
```
Установщик клонирует репозиторий в `~/.understand-anything/repo` и создаёт нужные симлинки для выбранной платформы. После установки перезапустите свой CLI/IDE.
- Поддерживаемые значения `<platform>`: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`
- Поддерживаемые значения `<platform>`: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `nanobot`
- Обновление: `./install.sh --update`
- Удаление: `./install.sh --uninstall <platform>`
@@ -206,7 +206,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins
Cursor автоматически обнаруживает плагин через `.cursor-plugin/plugin.json` при клонировании этого репозитория. Ручная установка не требуется — просто склонируйте и откройте в Cursor.
Если автообнаружение не сработало, установите вручную: откройте **Cursor Settings → Plugins**, вставьте `https://github.com/Lum1104/Understand-Anything` в поле поиска и добавьте оттуда.
Если автообнаружение не сработало, установите вручную: откройте **Cursor Settings → Plugins**, вставьте `https://github.com/Egonex-AI/Understand-Anything` в поле поиска и добавьте оттуда.
### VS Code + GitHub Copilot
@@ -217,7 +217,7 @@ VS Code с GitHub Copilot (v1.108+) автоматически обнаружи
### Copilot CLI
```bash
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin
```
### Совместимость с платформами
@@ -238,6 +238,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
| Hermes | ✅ Поддерживается | `install.sh hermes` |
| Cline | ✅ Поддерживается | `install.sh cline` |
| KIMI CLI | ✅ Поддерживается | `install.sh kimi` |
| Nanobot | ✅ Поддерживается | `install.sh nanobot` |
---
@@ -245,7 +246,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
Граф — это просто JSON. **Зафиксируйте его один раз, и коллеги смогут пропустить весь пайплайн.** Полезно для онбординга, ревью PR и подхода docs-as-code.
> **Пример:** [GoogleCloudPlatform/microservices-demo (форк)](https://github.com/Lum1104/microservices-demo) — мультиязыковой проект (Go / Java / Python / Node) с уже зафиксированным графом.
> **Пример:** [GoogleCloudPlatform/microservices-demo (форк)](https://github.com/GoogleCloudPlatform/microservices-demo) — мультиязыковой проект (Go / Java / Python / Node) с уже зафиксированным графом.
**Что коммитить:** всё содержимое `.understand-anything/`, *кроме* `intermediate/` и `diff-overlay.json` (это локальные временные файлы).
@@ -328,11 +329,11 @@ git add .gitattributes .understand-anything/
## История звёзд
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
<a href="https://www.star-history.com/?repos=Egonex-AI%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" />
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
</picture>
</a>
@@ -341,5 +342,5 @@ git add .gitattributes .understand-anything/
</p>
<p align="center">
Лицензия MIT &copy; <a href="https://github.com/Lum1104">Lum1104</a>
MIT License &copy; Yuxiang Lin and Infinite Universe, Inc.
</p>
+20 -19
View File
@@ -7,7 +7,7 @@
</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>
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Understand Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<p align="center">
@@ -16,7 +16,7 @@
<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://github.com/Egonex-AI/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>
@@ -32,9 +32,9 @@
</p>
<p align="center">
<strong>💬 <a href="https://discord.gg/pydat66RY">Discord topluluğuna katıl &rarr;</a></strong>
<strong>An open-source project from <a href="https://github.com/Egonex-AI">Egonex</a></strong>
<br />
<em>Sorular sor, yaptıklarını paylaş, topluluktan yardım al.</em>
<em>Originally created by <a href="https://github.com/Lum1104">Lum1104</a>.</em>
</p>
---
@@ -104,7 +104,7 @@ Alan görünümüne geçin ve kodunuzun gerçek iş süreçleriyle nasıl eşle
### 1. Eklentiyi yükle
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
@@ -178,27 +178,27 @@ Understand-Anything birden fazla AI kodlama platformunda çalışır.
### Claude Code (Yerli)
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/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)
### Tek satırlık kurulum (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot)
**macOS / Linux:**
```bash
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/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
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex
```
**Windows (PowerShell):**
```powershell
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
iwr -useb https://raw.githubusercontent.com/Egonex-AI/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`
- Desteklenen `<platform>` değerleri: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `nanobot`
- Daha sonra güncelle: `./install.sh --update`
- Kaldır: `./install.sh --uninstall <platform>`
@@ -206,7 +206,7 @@ Kurulum betiği depoyu `~/.understand-anything/repo` dizinine klonlar ve seçile
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.
Otomatik keşif çalışmazsa manuel kur: **Cursor Settings → Plugins**'i aç, arama alanına `https://github.com/Egonex-AI/Understand-Anything` yapıştır ve oradan ekle.
### VS Code + GitHub Copilot
@@ -217,7 +217,7 @@ Tüm projelerde kullanmak için kişisel beceri olarak kurmak istersen yukarıda
### Copilot CLI
```bash
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin
```
### Platform Uyumluluğu
@@ -238,6 +238,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
| Hermes | ✅ Destekleniyor | `install.sh hermes` |
| Cline | ✅ Destekleniyor | `install.sh cline` |
| KIMI CLI | ✅ Destekleniyor | `install.sh kimi` |
| Nanobot | ✅ Destekleniyor | `install.sh nanobot` |
---
@@ -245,7 +246,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
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.
> **Örnek:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/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).
@@ -328,11 +329,11 @@ Büyük değişiklikler için lütfen önce bir issue aç ki yaklaşımı tartı
## Star Geçmişi
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
<a href="https://www.star-history.com/?repos=Egonex-AI%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" />
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
<img alt="Star Geçmişi Grafiği" src="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
</picture>
</a>
@@ -341,5 +342,5 @@ Büyük değişiklikler için lütfen önce bir issue aç ki yaklaşımı tartı
</p>
<p align="center">
MIT Lisansı &copy; <a href="https://github.com/Lum1104">Lum1104</a>
MIT License &copy; Yuxiang Lin and Infinite Universe, Inc.
</p>
+20 -19
View File
@@ -6,7 +6,7 @@
</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>
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Understand Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<p align="center">
@@ -15,7 +15,7 @@
<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://github.com/Egonex-AI/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>
@@ -31,9 +31,9 @@
</p>
<p align="center">
<strong>💬 <a href="https://discord.gg/pydat66RY">加入 Discord 社区 &rarr;</a></strong>
<strong>An open-source project from <a href="https://github.com/Egonex-AI">Egonex</a></strong>
<br />
<em>来提问、分享你的项目、和社区一起讨论。</em>
<em>Originally created by <a href="https://github.com/Lum1104">Lum1104</a>.</em>
</p>
---
@@ -103,7 +103,7 @@ Understand Anything 是一个 [Claude Code Plugin](https://code.claude.com/docs/
### 1. 安装插件
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
@@ -177,27 +177,27 @@ Understand-Anything 可在多个 AI 编码平台上运行。
### Claude Code(原生)
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
### 一行命令安装(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI
### 一行命令安装(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot
**macOS / Linux**
```bash
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash
# 也可以直接传入平台名跳过交互提示:
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex
```
**WindowsPowerShell):**
```powershell
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex
```
安装脚本会将仓库克隆到 `~/.understand-anything/repo`,并为所选平台创建相应的符号链接。安装完成后请重启 CLI 或 IDE。
- 支持的 `<platform>` 取值:`gemini``codex``opencode``pi``openclaw``antigravity``vibe``vscode``hermes``cline``kimi`
- 支持的 `<platform>` 取值:`gemini``codex``opencode``pi``openclaw``antigravity``vibe``vscode``hermes``cline``kimi``nanobot`
- 后续更新:`./install.sh --update`
- 卸载:`./install.sh --uninstall <platform>`
@@ -205,7 +205,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins
克隆此仓库后,Cursor 会自动通过 `.cursor-plugin/plugin.json`文件发现插件。无需手动安装 — 只需克隆并在 Cursor 中打开即可。
若自动发现未生效,可手动安装:打开 **Cursor Settings → Plugins**,在搜索框中粘贴 `https://github.com/Lum1104/Understand-Anything` 并添加。
若自动发现未生效,可手动安装:打开 **Cursor Settings → Plugins**,在搜索框中粘贴 `https://github.com/Egonex-AI/Understand-Anything` 并添加。
### VS Code + GitHub Copilot
@@ -216,7 +216,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins
### Copilot CLI
```bash
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin
```
### 多平台兼容
@@ -237,6 +237,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
| Hermes | ✅ 支持 | `install.sh hermes` |
| Cline | ✅ 支持 | `install.sh cline` |
| KIMI CLI | ✅ 支持 | `install.sh kimi` |
| Nanobot | ✅ 支持 | `install.sh nanobot` |
---
@@ -244,7 +245,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
图谱就是一份 JSON 文件——**提交一次,团队成员就可以跳过整条流水线**。适合新人上手、PR 评审和 docs-as-code 工作流。
> **示例:** [GoogleCloudPlatform/microservices-demofork](https://github.com/Lum1104/microservices-demo) —— 包含已提交图谱的 Go / Java / Python / Node 多语言参考项目。
> **示例:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/microservices-demo) —— 包含已提交图谱的 Go / Java / Python / Node 多语言参考项目。
**需要提交的内容:** `.understand-anything/` 下的全部文件,*除了* `intermediate/``diff-overlay.json`(这些是本地临时文件)。
@@ -327,11 +328,11 @@ git add .gitattributes .understand-anything/
## Star 历史记录
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
<a href="https://www.star-history.com/?repos=Egonex-AI%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" />
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
</picture>
</a>
@@ -340,5 +341,5 @@ git add .gitattributes .understand-anything/
</p>
<p align="center">
MIT 许可证 &copy; <a href="https://github.com/Lum1104">Lum1104</a>
MIT License &copy; Yuxiang Lin and Infinite Universe, Inc.
</p>
+20 -19
View File
@@ -6,7 +6,7 @@
</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>
<a href="https://trendshift.io/repositories/23482" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23482" alt="Understand Anything | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<p align="center">
@@ -15,7 +15,7 @@
<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://github.com/Egonex-AI/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>
@@ -31,9 +31,9 @@
</p>
<p align="center">
<strong>💬 <a href="https://discord.gg/pydat66RY">加入 Discord 社群 &rarr;</a></strong>
<strong>An open-source project from <a href="https://github.com/Egonex-AI">Egonex</a></strong>
<br />
<em>來提問、分享你的專案、和社群一起討論。</em>
<em>Originally created by <a href="https://github.com/Lum1104">Lum1104</a>.</em>
</p>
---
@@ -103,7 +103,7 @@ Understand Anything 是一個 [Claude Code Plugin](https://code.claude.com/docs/
### 1. 安裝外掛程式
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
@@ -177,27 +177,27 @@ Understand-Anything 可在多個 AI 編碼平台上執行。
### Claude Code(原生)
```bash
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
```
### 一行指令安裝(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI
### 一行指令安裝(Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Nanobot
**macOS / Linux**
```bash
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash
# 也可以直接傳入平台名稱跳過互動提示:
curl -fsSL https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.sh | bash -s codex
curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex
```
**WindowsPowerShell):**
```powershell
iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/install.ps1 | iex
iwr -useb https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.ps1 | iex
```
安裝指令稿會將儲存庫複製到 `~/.understand-anything/repo`,並為所選平台建立相應的符號連結。安裝完成後請重新啟動 CLI 或 IDE。
- 支援的 `<platform>` 取值:`gemini``codex``opencode``pi``openclaw``antigravity``vibe``vscode``hermes``cline``kimi`
- 支援的 `<platform>` 取值:`gemini``codex``opencode``pi``openclaw``antigravity``vibe``vscode``hermes``cline``kimi``nanobot`
- 後續更新:`./install.sh --update`
- 解除安裝:`./install.sh --uninstall <platform>`
@@ -205,7 +205,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins
複製此儲存庫後,Cursor 會自動透過 `.cursor-plugin/plugin.json` 檔案發現外掛程式。無需手動安裝 — 只需複製並在 Cursor 中開啟即可。
若自動發現未生效,可手動安裝:開啟 **Cursor Settings → Plugins**,在搜尋框中貼上 `https://github.com/Lum1104/Understand-Anything` 並新增。
若自動發現未生效,可手動安裝:開啟 **Cursor Settings → Plugins**,在搜尋框中貼上 `https://github.com/Egonex-AI/Understand-Anything` 並新增。
### VS Code + GitHub Copilot
@@ -216,7 +216,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins
### Copilot CLI
```bash
copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
copilot plugin install Egonex-AI/Understand-Anything:understand-anything-plugin
```
### 多平台相容性
@@ -237,6 +237,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
| Hermes | ✅ 支援 | `install.sh hermes` |
| Cline | ✅ 支援 | `install.sh cline` |
| KIMI CLI | ✅ 支援 | `install.sh kimi` |
| Nanobot | ✅ 支援 | `install.sh nanobot` |
---
@@ -244,7 +245,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
圖譜就是一份 JSON 檔案——**提交一次,團隊成員就可以跳過整條流水線**。適合新人上手、PR 審查和 docs-as-code 工作流程。
> **範例:** [GoogleCloudPlatform/microservices-demofork](https://github.com/Lum1104/microservices-demo) —— 包含已提交圖譜的 Go / Java / Python / Node 多語言參考專案。
> **範例:** [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/microservices-demo) —— 包含已提交圖譜的 Go / Java / Python / Node 多語言參考專案。
**需要提交的內容:** `.understand-anything/` 底下的全部檔案,*除了* `intermediate/``diff-overlay.json`(這些是本機暫存檔)。
@@ -327,11 +328,11 @@ git add .gitattributes .understand-anything/
## Star 歷史記錄
<a href="https://www.star-history.com/?repos=Lum1104%2FUnderstand-Anything&type=date&legend=top-left">
<a href="https://www.star-history.com/?repos=Egonex-AI%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" />
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=Egonex-AI/Understand-Anything&type=date&legend=top-left" />
</picture>
</a>
@@ -340,5 +341,5 @@ git add .gitattributes .understand-anything/
</p>
<p align="center">
MIT 授權條款 &copy; <a href="https://github.com/Lum1104">Lum1104</a>
MIT License &copy; Yuxiang Lin and Infinite Universe, Inc.
</p>
+51
View File
@@ -0,0 +1,51 @@
# Reporting security issues
Thanks for taking the time to disclose responsibly.
## How to report
Please use GitHub's [private vulnerability reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability)
on this repository. That keeps the report visible to the maintainer without
exposing the details publicly.
If private reporting is unavailable for any reason, open a regular issue
titled `security: brief description` **without** any exploit details, and
the maintainer will reply with a private channel.
## What to include
- A description of the issue and its potential impact.
- Steps to reproduce — minimal is fine, a full PoC is not required.
- Affected versions if you've narrowed them down.
- Whether you'd like to be credited in the eventual fix.
## What to expect
- Initial acknowledgement within a few days.
- A fix or mitigation plan within ~30 days for confirmed issues; longer for
cases that require coordinated disclosure with upstream dependencies.
- Public credit once a fix has shipped, if you'd like.
## Scope
This project is a **local-only** static-analysis tool. It runs on a
developer's machine, reads the analyzed project, and writes the resulting
graph to `.understand-anything/`. It does not phone home and the dashboard's
file-content endpoint is gated behind an access token and a graph-derived
path allowlist.
Issues we care about:
- Code execution triggered by analyzing a hostile project (e.g. a path in a
hostile file leaking outside the analyzed directory, or untrusted JSON in
the graph being executed by the dashboard).
- The dashboard's file-content endpoint serving files outside the allowlist.
- The `/understand` skill running shell commands derived from untrusted
paths or contents.
Issues that are **out of scope**:
- Bugs that require a malicious local user with write access to the
analyzed project (they could just edit the source directly).
- Anything that requires the user to copy a malicious URL and paste it back
into the dashboard.
@@ -274,7 +274,7 @@ Create `homepage/src/components/Nav.astro`:
```astro
---
const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
const githubUrl = 'https://github.com/Egonex-AI/Understand-Anything';
---
<nav class="nav" id="nav">
@@ -437,7 +437,7 @@ Create `homepage/src/components/Hero.astro`:
```astro
---
const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
const githubUrl = 'https://github.com/Egonex-AI/Understand-Anything';
---
<section class="hero">
@@ -817,7 +817,7 @@ Create `homepage/src/components/Install.astro`:
<span class="install-code-dot"></span>
<span class="install-code-label">Claude Code</span>
</div>
<pre><code><span class="cmd">/plugin marketplace add</span> Lum1104/Understand-Anything
<pre><code><span class="cmd">/plugin marketplace add</span> Egonex-AI/Understand-Anything
<span class="cmd">/plugin install</span> understand-anything
<span class="cmd">/understand</span></code></pre>
</div>
@@ -928,7 +928,7 @@ Create `homepage/src/components/Footer.astro`:
```astro
---
const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
const githubUrl = 'https://github.com/Egonex-AI/Understand-Anything';
---
<footer class="footer">
@@ -1223,7 +1223,7 @@ Verify in browser:
Add a "Homepage" link near the top of the README, after the badges section. Add a single line:
```markdown
**[Homepage](https://understand-anything.com)** | **[GitHub](https://github.com/Lum1104/Understand-Anything)**
**[Homepage](https://understand-anything.com)** | **[GitHub](https://github.com/Egonex-AI/Understand-Anything)**
```
**Step 4: Final commit**
@@ -426,9 +426,9 @@ git commit -m "refactor: move platform config directories to repo root for disco
"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",
"author": { "name": "Egonex" },
"homepage": "https://github.com/Egonex-AI/Understand-Anything",
"repository": "https://github.com/Egonex-AI/Understand-Anything",
"license": "MIT",
"keywords": ["codebase-analysis", "knowledge-graph", "architecture", "onboarding", "dashboard"],
"skills": "./understand-anything-plugin/skills/",
@@ -445,9 +445,9 @@ Note: paths point into `understand-anything-plugin/` since the source stays nest
"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",
"author": { "name": "Egonex" },
"homepage": "https://github.com/Egonex-AI/Understand-Anything",
"repository": "https://github.com/Egonex-AI/Understand-Anything",
"license": "MIT",
"keywords": ["codebase-analysis", "knowledge-graph", "architecture", "onboarding", "dashboard"]
}
@@ -0,0 +1,210 @@
# Conversation-Language Auto-Detection 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:** On the first `/understand` run in a project, detect the user's conversation language and confirm it before generating — without changing anything for English users.
**Architecture:** A pure prompt-logic change. The entire language decision lives in one place — `SKILL.md` step 3.6. We expand the `if --language NOT specified` branch into a resolution chain that adds conversation-language detection + a non-English-only, first-run-only confirmation gate before the existing `en` default. The resolved value is persisted to `config.json` so the gate fires at most once per project. A README sentence documents it.
**Tech Stack:** Markdown skill prompt (`SKILL.md`), Markdown docs (`README.md`). No code, no schema, no TypeScript. There is no automated-test hook for skill-prompt behavior, so verification is the manual scenario walkthrough in Task 3 (this matches how the existing `--language` flag is verified).
---
## File Structure
| File | Responsibility | Change |
|---|---|---|
| `understand-anything-plugin/skills/understand/SKILL.md` | The `/understand` skill prompt; step 3.6 resolves `$OUTPUT_LANGUAGE`. | Rewrite the `If --language is NOT specified` sub-block (currently lines 142144). |
| `README.md` | User-facing docs; *Localized output* section (~line 121). | Add one paragraph describing first-run auto-detection. |
No other files. The `outputLanguage` field already exists in `packages/core/src/types.ts:119`; the `locales/<lang>.md` injection at `SKILL.md` step 4 (line ~424) and the `$LANGUAGE_DIRECTIVE` template (lines ~148151) are unchanged and require no edits.
---
## Task 1: Expand the language-resolution branch in SKILL.md
**Files:**
- Modify: `understand-anything-plugin/skills/understand/SKILL.md` (step 3.6, the `If --language is NOT specified` sub-block)
- [ ] **Step 1: Confirm the current text is unchanged**
Run: `sed -n '142,144p' understand-anything-plugin/skills/understand/SKILL.md`
Expected output (exactly these three lines):
```
- If `--language` is NOT specified:
- Check `$PROJECT_ROOT/.understand-anything/config.json` for an existing `outputLanguage` field. If present, use that.
- If no stored preference, default to `en` (English).
```
If the text differs, STOP and re-read step 3.6 to find the equivalent block before editing — line numbers may have drifted.
- [ ] **Step 2: Replace the block**
Use the Edit tool on `understand-anything-plugin/skills/understand/SKILL.md`.
`old_string` (match exactly, including leading spaces):
```
- If `--language` is NOT specified:
- Check `$PROJECT_ROOT/.understand-anything/config.json` for an existing `outputLanguage` field. If present, use that.
- If no stored preference, default to `en` (English).
```
`new_string` (describes the confirmation *intent* in one instruction rather than
hard-coding a literal prompt box — the skill is a prompt the model interprets):
```
- If `--language` is NOT specified:
- **Stored preference wins.** If `$PROJECT_ROOT/.understand-anything/config.json` has an `outputLanguage` field, set `$OUTPUT_LANGUAGE` to it and skip the rest.
- **Otherwise detect (first run only).** Infer the predominant language of the user's conversation as an ISO 639-1 code (`$DETECTED_LANG`). If it is `en` or cannot be confidently determined, set `$OUTPUT_LANGUAGE=en` and proceed silently — no prompt (English users see no change).
- **If `$DETECTED_LANG` ≠ `en`, confirm once before analyzing:** tell the user you detected `<language>` and ask whether to generate all content in it; they press Enter/"yes" to accept, or type another language code/name to override (normalize via the friendly-name map above). If running non-interactively (no reply possible), skip the wait, use `$DETECTED_LANG`, and print a one-line notice instead of blocking.
- **Persist** the resolved `$OUTPUT_LANGUAGE` (including `en`) into `config.json` so it never re-prompts for this project.
```
- [ ] **Step 3: Verify the edit landed and is well-formed**
Run: `sed -n '142,170p' understand-anything-plugin/skills/understand/SKILL.md`
Expected: the new multi-level block is present; the very next content after it is the unchanged `- If `--language` IS specified:` line. Confirm no duplicate `default to en` line remains.
Run: `grep -c 'default to \`en\`' understand-anything-plugin/skills/understand/SKILL.md`
Expected: `0` (the old standalone default line is gone; the new wording says "set `$OUTPUT_LANGUAGE` to `en`").
- [ ] **Step 4: Commit**
```bash
git add understand-anything-plugin/skills/understand/SKILL.md
git commit -m "feat(understand): detect conversation language on first run
Expand SKILL.md step 3.6 with conversation-language detection as a
fallback before the en default, gated behind a first-run-only,
non-English-only confirmation. Resolved value is persisted to
config.json so the gate fires at most once. English users see no change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```
---
## Task 2: Document first-run auto-detection in README
**Files:**
- Modify: `README.md` (*Localized output* section, before the "The `--language` parameter affects:" line, ~line 130)
- [ ] **Step 1: Confirm the anchor line exists**
Run: `grep -n 'The `--language` parameter affects:' README.md`
Expected: one match around line 130.
- [ ] **Step 2: Insert the auto-detection paragraph**
Use the Edit tool on `README.md`.
`old_string`:
```
The `--language` parameter affects:
```
`new_string`:
```
On the **first run** in a project — when you don't pass `--language` and no language is stored yet — `/understand` detects the language you're conversing in. If it isn't English, it asks you to confirm (or override) before generating; English conversations are unaffected. Your choice is saved to `.understand-anything/config.json` and reused on every later run.
The `--language` parameter affects:
```
- [ ] **Step 3: Verify**
Run: `grep -n 'first run' README.md`
Expected: one match with the new sentence, located just above "The `--language` parameter affects:".
- [ ] **Step 4: Commit**
```bash
git add README.md
git commit -m "docs: note first-run conversation-language auto-detection
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```
---
## Task 3: Manual verification (no automated test hook exists)
There is no unit-test harness for skill-prompt behavior. Verify by reasoning through each scenario against the edited step 3.6 text and confirming the prompt logic produces the right `$OUTPUT_LANGUAGE` and `config.json` write. Record the result of each in the PR description.
- [ ] **Step 1: Walk the five scenarios**
For each, trace the edited step 3.6 and confirm the expected outcome:
| # | Situation (fresh project, no `config.json`) | Expected |
|---|---|---|
| 1 | Conversation in Chinese, run `/understand` | Gate appears → confirm → `$OUTPUT_LANGUAGE=zh`; `config.json` gets `"outputLanguage":"zh"` |
| 2 | Re-run in the same project (config now has `zh`) | No gate (stored preference wins); generates `zh` |
| 3 | Conversation in English, run `/understand` | No gate; `$OUTPUT_LANGUAGE=en`; English output (no regression) |
| 4 | `--language ja` on a fresh project | No gate (flag wins); `config.json` gets `"outputLanguage":"ja"` |
| 5 | Detected `zh`, user types `en` at the gate | `$OUTPUT_LANGUAGE=en`; `config.json` gets `"outputLanguage":"en"` |
- [ ] **Step 2: Confirm no-regression invariant**
Re-read the edited block and confirm: there is NO code path where an English-only conversation with no flag/config produces a prompt. (Scenario 3 must be silent.) This is the single most important property for upstream acceptance.
- [ ] **Step 3: Optional live smoke test**
If you want a real run: in a throwaway repo with no `.understand-anything/config.json`, converse briefly in Chinese, run `/understand`, and confirm the gate appears and `config.json` is written with `outputLanguage: "zh"` after confirming. (Skip if a full analysis run is too costly; the trace in Step 1 is sufficient for the PR.)
---
## Task 4: Open the PR
- [ ] **Step 1: Push the branch**
```bash
git push -u origin feat/understand-language-auto-detection
```
- [ ] **Step 2: Create the PR**
```bash
gh pr create --title "feat(understand): detect conversation language on first run" --body "$(cat <<'EOF'
## What
On the first `/understand` run in a project (no `--language` flag, no stored `outputLanguage`), the skill now detects the language of the conversation and — **only when it is not English** — asks the user to confirm or override before generating. The choice is persisted to `.understand-anything/config.json` and the gate never fires again.
## Why
The output language defaulted silently to English. A user conversing in Chinese would run the simplest command and only discover the English output after paying the full cost of an analysis run, then had to re-run with `--language zh`.
## Zero change for English users
The confirmation gate fires **only** when the detected language is non-English and no language has been chosen yet. English conversations follow the exact same silent `en` path as before. `--language` flag and stored config both take priority over detection.
## Scope
- Only `understand-anything-plugin/skills/understand/SKILL.md` step 3.6 + a README sentence.
- No code/schema changes. Other skills and the auto-update hook are unchanged (separate known gaps).
- Non-interactive invocations fall back to the detected language with a notice instead of blocking.
Design + plan: `docs/superpowers/specs/2026-06-03-language-auto-detection-design.md`, `docs/superpowers/plans/2026-06-03-language-auto-detection.md`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
```
---
## Self-Review
**Spec coverage:**
- Resolution chain (param > config > detect > en) → Task 1 Step 2. ✓
- Non-English-only, first-run-only gate → Task 1 Step 2 (gate "shown ONLY when `$DETECTED_LANG` ≠ `en`"). ✓
- Persist resolved value incl. `en` → Task 1 Step 2 ("Persist the resolved value"). ✓
- Edge: uncertain/mixed → `en` silent → Task 1 Step 2 first bullet. ✓
- Edge: non-interactive fallback → Task 1 Step 2 ("Non-interactive fallback"). ✓
- Edge: auto-update hook unaffected → no task needed (no code path touched); noted here. ✓
- `locales/<lang>.md` already wired → unchanged, noted in File Structure. ✓
- README sentence → Task 2. ✓
- Manual verification scenarios (5) → Task 3. ✓
**Placeholder scan:** No "TBD/TODO/handle edge cases" left vague — every edge case has explicit resolved behavior. The `<language name>`/`<code>` tokens inside the gate are intentional template placeholders the skill fills at runtime, not plan gaps. ✓
**Type/name consistency:** Variable names used consistently — `$OUTPUT_LANGUAGE`, `$DETECTED_LANG`, `outputLanguage` (config key), `--language` (flag). These match the existing names in SKILL.md step 3.6. ✓
@@ -45,7 +45,7 @@ Staggered fade-in animation:
- Headline: "Get started in 30 seconds"
- Code block:
```
/plugin marketplace add Lum1104/Understand-Anything
/plugin marketplace add Egonex-AI/Understand-Anything
/plugin install understand-anything
/understand
```
@@ -82,7 +82,7 @@ Each platform gets an `INSTALL.md` that the AI agent can fetch and follow:
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
Fetch and follow instructions from https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/refs/heads/main/understand-anything-plugin/.codex/INSTALL.md
```
The agent executes the clone + symlink/config automatically.
@@ -1,6 +1,6 @@
# Business Domain Knowledge Extraction — Design Spec
**Issue:** [#61](https://github.com/Lum1104/Understand-Anything/issues/61)
**Issue:** [#61](https://github.com/Egonex-AI/Understand-Anything/issues/61)
**Date:** 2026-04-01
## Problem
@@ -3,7 +3,7 @@
**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
**Issue:** [#159](https://github.com/Egonex-AI/Understand-Anything/issues/159) — Frequently seeing output limit exceeded
---
@@ -0,0 +1,147 @@
# Conversation-Language Auto-Detection for `/understand`
**Date:** 2026-06-03
**Status:** Approved — ready for implementation plan
**Scope:** `understand-anything-plugin/skills/understand/SKILL.md` (primary), `README.md` (docs)
## Problem
`/understand` generates all LLM-authored content (node summaries, tags, layer
names, guided tours, language notes) in **English by default**. The output
language is controlled solely by an explicit `--language <lang>` flag or a
previously stored `outputLanguage` value in `.understand-anything/config.json`.
The skill performs **no detection** of the language the user is actually working
in. `SKILL.md` step 3.6 (lines 142144) hard-defaults to `en` whenever the flag
and config are both absent:
```
- If `--language` is NOT specified:
- Check config.json for outputLanguage. If present, use that.
- If no stored preference, default to `en` (English).
```
**Observed failure:** A user conversing entirely in Chinese ran the simplest
`/understand` command and received an English knowledge graph. They only
discovered the mismatch after paying the full cost of an analysis run (time +
tokens), then had to re-run with `--language zh`. The default is silent and
undiscoverable — nothing surfaces the language decision at the point it matters.
## Goal
On **first analysis of a project**, infer the user's working language from the
conversation and confirm it before spending the analysis budget — without
changing anything for English-speaking users (the project's core audience) and
without breaking non-interactive invocations.
## Non-Goals
- No side-by-side bilingual output. Output remains single-language per graph.
- No changes to other content-generating skills (`understand-domain`,
`understand-knowledge`, `understand-explain`, etc.). They currently ignore
`outputLanguage` entirely; that is a separate, known gap left for follow-up
PRs.
- No changes to the autonomous auto-update hook path
(`hooks/auto-update-prompt.md`). It reuses the existing graph and does not
resolve a language, so detection never runs there. Noted as a known separate
gap, out of scope here.
- No code, schema, or TypeScript changes. The `outputLanguage` field already
exists in `ProjectConfig` (`packages/core/src/types.ts:119`).
## Approach (chosen)
Add conversation-language **detection as a fallback** in the resolution chain,
placed *before* the `en` default, gated behind a **first-run-only, non-English-only**
confirmation. This was selected over two alternatives discussed:
- **(A) Personal workaround** (always pass `--language zh` / hand-edit config):
zero upstream value, rejected — the goal is a contributable fix.
- **(B, chosen) Detection-as-fallback in `SKILL.md`:** minimal diff, strictly
better for non-English users, invisible to English users.
- **(C) Always-prompt menu on every run:** rejected — reintroduces friction and
is the most likely to be rejected by upstream maintainers.
The confirmation gate (rather than silent application) was chosen at the user's
direction for explicitness, but constrained so it never affects English users.
## Detailed Design
### Resolution chain (rewrite of `SKILL.md` step 3.6)
Priority, highest first:
1. **`--language <lang>` flag present** → normalize via the existing
friendly-name map (line 140), persist to `config.json`, use. *(unchanged)*
2. **`outputLanguage` present in `config.json`** → use it. *(unchanged)*
3. **First run (no flag AND no config) → NEW: detect conversation language.**
- Infer the predominant language of the user's messages in the current
conversation → `$DETECTED_LANG` (ISO 639-1 code, e.g. `zh`, `ja`).
- **If `$DETECTED_LANG` is `en`, or cannot be confidently determined, or the
conversation is mixed/ambiguous** → set `$OUTPUT_LANGUAGE = en`, persist,
**proceed with no prompt.** (Preserves current behavior exactly for the
core audience.)
- **If `$DETECTED_LANG``en`** → show the confirmation gate below, resolve
`$OUTPUT_LANGUAGE`, persist to `config.json`.
In all branches the resolved value is written to `config.json`
(`{"outputLanguage": "<lang>"}` merged into existing config), so the gate fires
**at most once per project**.
### Confirmation gate (only when `$DETECTED_LANG` ≠ `en`)
Shown before any pipeline phase runs. `SKILL.md` describes the *intent* in one
instruction rather than hard-coding a literal prompt box — the skill is a prompt
the model interprets, so it renders the question itself at runtime. The
instruction tells the model to:
- State the detected language and ask whether to generate all content in it.
- Accept Enter / "yes" / the detected code as confirmation → `$OUTPUT_LANGUAGE = $DETECTED_LANG`.
- Accept any other language code or friendly name as an override → normalize via
the existing friendly-name map (line ~140) and use it. This doubles as the
"chatting in Chinese but want English docs for my team" escape hatch.
The instruction text in `SKILL.md` stays in **English** (skill prompts are
English); only the *generated content* becomes the target language.
### Edge cases
| Case | Behavior |
|---|---|
| Detection uncertain / mixed languages | Treat as `en`, proceed silently. Never block on a guess. |
| Non-interactive invocation (headless/CI, no user to answer) | Fall back to `$DETECTED_LANG` with a one-line notice instead of hanging on the gate; persist. Confirm is best-effort, never a hard block. |
| Autonomous auto-update hook | Unaffected — that path does not resolve language. |
| Detected language has a `locales/<lang>.md` file | Already wired at step 4 (line 424); no change. |
| Detected language has no locale file | `$LANGUAGE_DIRECTIVE` still applies (existing "skip silently" behavior). |
## Files Touched
- `understand-anything-plugin/skills/understand/SKILL.md` — rewrite the step 3.6
`if --language NOT specified` branch into the 4-step resolution chain above.
**Primary change.**
- `README.md` — add a sentence under the *Localized output* section (~line 121)
noting first-run auto-detection.
No other files. No schema, code, or test-harness files change.
## Testing / Verification
Prompt-logic change → no unit-test hook. Verification is manual and documented
in the PR description:
1. Fresh project, no config, converse in Chinese, run `/understand` → gate
appears → confirm → content generated in `zh`; `config.json` contains
`outputLanguage: "zh"`.
2. Re-run in same project → no gate (config wins).
3. Fresh project, converse in English → no gate, English output (proves no
regression for the core audience).
4. Fresh project, `--language ja` → no gate, flag wins, config stores `ja`.
5. Fresh project, override at the gate (detected `zh`, type `en`) → English
output, config stores `en`.
## Risks & Upstream Framing
The main reviewer concern is *any* added interactivity. Mitigations are built in:
the gate is **first-run-only**, **non-English-only**, and **degrades gracefully**
when non-interactive. The PR description leads with: **"Zero behavior change for
English users; the gate only appears when the conversation is non-English and no
language has been chosen yet."**
+15 -30
View File
@@ -1,43 +1,28 @@
# Astro Starter Kit: Minimal
# Understand Anything Homepage
```sh
pnpm create astro@latest -- --template minimal
```
Astro site for the Understand Anything project homepage.
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
## Project Structure
```text
/
homepage/
├── public/
├── src/
│ ├── components/
│ └── 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.
The homepage is published as a static Astro site. Public messaging should use
the Egonex organization identity and link company/product traffic to
`https://egonex.ai` where relevant.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
## Commands
Any static assets, like images, can be placed in the `public/` directory.
Run these from the repository root:
## 🧞 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).
| Command | Action |
| :-- | :-- |
| `pnpm --filter homepage dev` | Start the local dev server |
| `pnpm --filter homepage build` | Build the static site |
| `pnpm --filter homepage preview` | Preview the built site |
+13 -8
View File
@@ -1,7 +1,5 @@
---
const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
const discordUrl = 'https://discord.gg/pydat66RY';
const contactEmail = 'lum@understand-anything.com';
const githubUrl = 'https://github.com/Egonex-AI/Understand-Anything';
---
<footer class="footer">
@@ -10,15 +8,12 @@ const contactEmail = 'lum@understand-anything.com';
<div class="footer-links">
<a href={githubUrl} target="_blank" rel="noopener noreferrer">GitHub</a>
<span class="footer-sep">&middot;</span>
<a href={discordUrl} target="_blank" rel="noopener noreferrer">Discord</a>
<span class="footer-sep">&middot;</span>
<a href="/demo/">Live Demo</a>
<span class="footer-sep">&middot;</span>
<a href={`mailto:${contactEmail}`}>Contact</a>
<span class="footer-sep">&middot;</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>
<p class="footer-note">An open-source project from Egonex. Originally created by Lum1104.</p>
<p class="footer-note">&copy; 2026 Infinite Universe, Inc.</p>
</div>
</footer>
@@ -73,6 +68,16 @@ const contactEmail = 'lum@understand-anything.com';
.footer-note {
font-size: 0.8rem;
color: var(--text-muted);
margin: 0.25rem 0;
}
.footer-note a {
color: var(--accent);
text-decoration: none;
}
.footer-note a:hover {
text-decoration: underline;
}
@media (max-width: 480px) {
+95 -232
View File
@@ -1,6 +1,6 @@
---
const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
const discordUrl = 'https://discord.gg/pydat66RY';
const githubUrl = 'https://github.com/Egonex-AI/Understand-Anything';
const egonexUrl = 'https://egonex.ai';
---
<section class="hero">
@@ -13,15 +13,23 @@ const discordUrl = 'https://discord.gg/pydat66RY';
<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>&nbsp;Anything
<span class="hero-grad">Understand</span> Anything
</h1>
<p class="hero-tagline anim anim-3">
<p class="hero-bridge anim anim-3">
<a href={egonexUrl} target="_blank" rel="noopener noreferrer">Understand Anyone.</a>
</p>
<p class="hero-belief anim anim-4">
AI should help people, not replace them.
</p>
<p class="hero-tagline anim anim-4">
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">
<div class="hero-pillars anim anim-5">
<span class="pillar">Structure & Dependencies</span>
<span class="pillar-dot">&middot;</span>
<span class="pillar">Business Logic</span>
@@ -29,59 +37,25 @@ const discordUrl = 'https://discord.gg/pydat66RY';
<span class="pillar">Guided Tours</span>
</div>
<div class="hero-actions anim anim-5">
<div class="hero-actions anim anim-6">
<a href="#install" class="hero-cta">Get Started</a>
<a href="/demo/" class="hero-demo">Live Demo &rarr;</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 href="https://trendshift.io/repositories/23482" target="_blank" rel="noopener noreferrer" class="hero-trendshift anim anim-6">
<img src="https://trendshift.io/api/badge/repositories/23482" alt="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">&middot;</span>
<span class="hero-enterprise-email">lum@understand-anything.com</span>
<span class="hero-enterprise-arrow">&rarr;</span>
<a href={egonexUrl} target="_blank" rel="noopener noreferrer" class="hero-company anim anim-6">
<span class="hero-company-label">Company</span>
<span class="hero-company-divider">&middot;</span>
<span class="hero-company-site">egonex.ai</span>
<span class="hero-company-arrow">&rarr;</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">&middot;</span>
<span class="hero-author-handle">Lum1104</span>
<span class="hero-author-arrow">&rarr;</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>
@@ -124,6 +98,7 @@ const discordUrl = 'https://discord.gg/pydat66RY';
.hero-content {
position: relative;
z-index: 1;
width: min(100%, 900px);
max-width: 900px;
display: flex;
flex-direction: column;
@@ -165,11 +140,43 @@ const discordUrl = 'https://discord.gg/pydat66RY';
text-shadow: none;
}
.hero-bridge {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 0.5rem;
color: #e8e2d8;
font-family: var(--font-heading);
font-size: 1.8rem;
line-height: 1.25;
margin: -0.4rem 0 0.8rem;
}
.hero-bridge a {
color: #d4a574;
text-decoration: none;
transition: color 0.2s ease, text-shadow 0.2s ease;
}
.hero-bridge a:hover {
color: #e8c99e;
text-decoration: none;
text-shadow: 0 0 20px rgba(212, 165, 116, 0.18);
}
.hero-belief {
color: #a9a196;
font-size: 1.08rem;
line-height: 1.6;
margin: 0 0 0.75rem;
}
/* Tagline */
.hero-tagline {
font-size: clamp(1rem, 2vw, 1.4rem);
font-size: 1.08rem;
color: #8a8578;
line-height: 1.7;
line-height: 1.6;
max-width: 900px;
white-space: nowrap;
margin-bottom: 1.5rem;
@@ -252,32 +259,6 @@ const discordUrl = 'https://discord.gg/pydat66RY';
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;
@@ -290,7 +271,7 @@ const discordUrl = 'https://discord.gg/pydat66RY';
text-decoration: none;
}
/* Trendshift badge — sits between the action row and the enterprise pill */
/* Trendshift badge */
.hero-trendshift {
margin-top: 1.75rem;
display: inline-block;
@@ -309,9 +290,8 @@ const discordUrl = 'https://discord.gg/pydat66RY';
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;
.hero-company {
margin-top: 1.25rem;
display: inline-flex;
align-items: center;
gap: 0.55rem;
@@ -324,7 +304,7 @@ const discordUrl = 'https://discord.gg/pydat66RY';
background-color 0.25s ease;
}
.hero-enterprise:hover {
.hero-company: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);
@@ -332,7 +312,7 @@ const discordUrl = 'https://discord.gg/pydat66RY';
text-decoration: none;
}
.hero-enterprise-label {
.hero-company-label {
font-family: var(--font-code);
font-size: 0.7rem;
letter-spacing: 0.14em;
@@ -340,149 +320,29 @@ const discordUrl = 'https://discord.gg/pydat66RY';
color: #d4a574;
}
.hero-enterprise-divider {
.hero-company-divider {
color: rgba(212, 165, 116, 0.45);
font-size: 0.85rem;
line-height: 1;
}
.hero-enterprise-email {
.hero-company-site {
font-family: var(--font-code);
font-size: 0.82rem;
color: #e8e2d8;
}
.hero-enterprise-arrow {
.hero-company-arrow {
color: #d4a574;
font-size: 0.95rem;
line-height: 1;
transition: transform 0.2s ease;
}
.hero-enterprise:hover .hero-enterprise-arrow {
.hero-company:hover .hero-company-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 {
@@ -505,10 +365,16 @@ const discordUrl = 'https://discord.gg/pydat66RY';
.anim-3 { animation-delay: 0.55s; }
.anim-4 { animation-delay: 0.75s; }
.anim-5 { animation-delay: 0.95s; }
.anim-6 { animation-delay: 1.15s; }
.anim-6 { animation-delay: 1.1s; }
/* Responsive */
@media (max-width: 900px) {
.hero-bridge {
max-width: 26ch;
}
.hero-belief {
max-width: 34ch;
}
.hero-tagline {
white-space: normal;
max-width: 36ch;
@@ -528,6 +394,14 @@ const discordUrl = 'https://discord.gg/pydat66RY';
margin-bottom: 1.75rem;
}
.hero-title { margin-bottom: 1.5rem; }
.hero-bridge {
font-size: 1.35rem;
margin-top: -0.25rem;
}
.hero-belief,
.hero-tagline {
font-size: 0.98rem;
}
}
@media (max-width: 640px) {
@@ -535,32 +409,13 @@ const discordUrl = 'https://discord.gg/pydat66RY';
white-space: normal;
line-height: 1.02;
letter-spacing: -0.03em;
text-wrap: balance;
}
/* Stack the two words on dedicated lines for editorial drama */
.hero-title > span { display: block; }
.hero-title .hero-grad { 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;
@@ -568,8 +423,7 @@ const discordUrl = 'https://discord.gg/pydat66RY';
max-width: 320px;
}
.hero-cta,
.hero-demo,
.hero-discord {
.hero-demo {
width: 100%;
text-align: center;
padding: 0.85rem 1.5rem;
@@ -577,8 +431,17 @@ const discordUrl = 'https://discord.gg/pydat66RY';
}
.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; }
.hero-company {
flex-direction: column;
gap: 0.25rem;
padding: 0.6rem 1.25rem;
max-width: 100%;
}
.hero-company-divider { display: none; }
.hero-company-site { font-size: 0.78rem; }
}
@media (max-width: 480px) {
.hero { padding: 4rem 1.25rem 2.25rem; }
}
</style>
+1 -1
View File
@@ -13,7 +13,7 @@
<span class="install-copy-label">Copy</span>
</button>
</div>
<pre id="install-snippet"><code><span class="cmd">/plugin marketplace add</span> Lum1104/Understand-Anything
<pre id="install-snippet"><code><span class="cmd">/plugin marketplace add</span> Egonex-AI/Understand-Anything
<span class="cmd">/plugin install</span> understand-anything
<span class="cmd">/understand</span></code></pre>
</div>
+15 -1
View File
@@ -1,5 +1,5 @@
---
const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
const githubUrl = 'https://github.com/Egonex-AI/Understand-Anything';
---
<nav class="nav" id="nav">
@@ -49,6 +49,7 @@ const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.nav-logo {
@@ -56,6 +57,7 @@ const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
font-size: 1.25rem;
color: var(--text);
text-decoration: none;
min-width: 0;
}
.nav-logo:hover {
@@ -104,4 +106,16 @@ const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
.nav { padding: 0.75rem 1rem; }
.nav-github span { display: none; }
}
@media (max-width: 540px) {
.nav { padding: 0.75rem 1rem; }
.nav-logo { font-size: 1.05rem; }
.nav-links { gap: 0.85rem; }
.nav-github span { display: none; }
.nav-cta {
padding: 0.48rem 0.85rem;
font-size: 0.82rem;
white-space: nowrap;
}
}
</style>
+2 -1
View File
@@ -22,7 +22,7 @@ param(
$ErrorActionPreference = 'Stop'
$RepoUrl = if ($env:UA_REPO_URL) { $env:UA_REPO_URL } else { 'https://github.com/Lum1104/Understand-Anything.git' }
$RepoUrl = if ($env:UA_REPO_URL) { $env:UA_REPO_URL } else { 'https://github.com/Egonex-AI/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'
@@ -39,6 +39,7 @@ $Platforms = [ordered]@{
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' }
nanobot = @{ Target = (Join-Path $HOME '.nanobot\workspace\skills'); Style = 'per-skill' }
}
function Show-Usage {
+4 -3
View File
@@ -9,8 +9,8 @@
# ./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
# curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash
# curl -fsSL https://raw.githubusercontent.com/Egonex-AI/Understand-Anything/main/install.sh | bash -s codex
#
# Environment:
# UA_REPO_URL Override clone URL (default: official GitHub repo)
@@ -18,7 +18,7 @@
set -euo pipefail
REPO_URL="${UA_REPO_URL:-https://github.com/Lum1104/Understand-Anything.git}"
REPO_URL="${UA_REPO_URL:-https://github.com/Egonex-AI/Understand-Anything.git}"
REPO_DIR="${UA_DIR:-$HOME/.understand-anything/repo}"
PLUGIN_LINK="$HOME/.understand-anything-plugin"
@@ -40,6 +40,7 @@ hermes|$HOME/.hermes/skills|folder
cline|$HOME/.cline/skills|folder
kimi|$HOME/.kimi/skills|folder
trae|$HOME/.trae/skills|per-skill
nanobot|$HOME/.nanobot/workspace/skills|per-skill
kiro|$HOME/.kiro/skills|per-skill
EOF
}
+22
View File
@@ -2,6 +2,28 @@
"name": "understand-anything",
"private": true,
"type": "module",
"description": "An open-source tool combining LLM intelligence + static analysis to produce interactive dashboards for understanding codebases.",
"license": "MIT",
"homepage": "https://github.com/Egonex-AI/Understand-Anything#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/Egonex-AI/Understand-Anything.git"
},
"bugs": {
"url": "https://github.com/Egonex-AI/Understand-Anything/issues"
},
"keywords": [
"claude-code",
"codebase-analysis",
"knowledge-graph",
"tree-sitter",
"llm",
"static-analysis",
"developer-tools",
"code-understanding",
"code-onboarding",
"claude-plugin"
],
"main": ".opencode/plugins/understand-anything.js",
"packageManager": "pnpm@10.6.2+sha512.47870716bea1572b53df34ad8647b42962bc790ce2bf4562ba0f643237d7302a3d6a8ecef9e4bdfc01d23af1969aa90485d4cebb0b9638fa5ef1daef656f6c1b",
"scripts": {
+24
View File
@@ -57,6 +57,9 @@ importers:
understand-anything-plugin/packages/core:
dependencies:
'@tree-sitter-grammars/tree-sitter-kotlin':
specifier: 1.1.0
version: 1.1.0
fuse.js:
specifier: ^7.1.0
version: 7.1.0
@@ -1120,6 +1123,14 @@ packages:
peerDependencies:
vite: ^5.2.0 || ^6 || ^7
'@tree-sitter-grammars/tree-sitter-kotlin@1.1.0':
resolution: {integrity: sha512-vlVXaxEE8t2kpJgfZpa8XVvxcnKw9AYtRTgy7KWjsDmAsadk06RxAT80IXOgGQnmM9i/orQn1nD84gPNUHu6DQ==}
peerDependencies:
tree-sitter: ^0.22.4
peerDependenciesMeta:
tree-sitter:
optional: true
'@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
@@ -2442,6 +2453,11 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
npm-check-updates@17.1.18:
resolution: {integrity: sha512-bkUy2g4v1i+3FeUf5fXMLbxmV95eG4/sS7lYE32GrUeVgQRfQEk39gpskksFunyaxQgTIdrvYbnuNbO/pSUSqw==}
engines: {node: ^18.18.0 || >=20.0.0, npm: '>=8.12.1'}
hasBin: true
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
@@ -4048,6 +4064,12 @@ snapshots:
tailwindcss: 4.2.1
vite: 6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
'@tree-sitter-grammars/tree-sitter-kotlin@1.1.0':
dependencies:
node-addon-api: 8.6.0
node-gyp-build: 4.8.4
npm-check-updates: 17.1.18
'@types/babel__core@7.20.5':
dependencies:
'@babel/parser': 7.29.0
@@ -5760,6 +5782,8 @@ snapshots:
normalize-path@3.0.0: {}
npm-check-updates@17.1.18: {}
nth-check@2.1.1:
dependencies:
boolbase: 1.0.0
@@ -342,6 +342,150 @@ describe('extract-import-map.mjs — TypeScript / JavaScript resolver', () => {
expect(result.status).toBe(0);
expect(result.output.importMap['src/app.ts']).toContain('lib/thing.ts');
});
// ── #294: NodeNext / ESM TypeScript `.js → .ts` rewrite ────────────────
//
// Under `moduleResolution: NodeNext`, TypeScript does NOT rewrite import
// specifiers during compilation — what you write in the .ts source is
// emitted verbatim. Because Node's ESM loader requires explicit file
// extensions at runtime, the TS source must already spell the import with
// the `.js` extension that will only be correct AFTER compilation:
//
// import { x } from './config.js'; // on disk: config.ts
//
// Before the fix, every such import resolved to null, leaving ESM-TS
// projects with a near-edgeless knowledge graph.
it('resolves NodeNext .js → .ts relative imports (the main #294 case)', () => {
projectRoot = setupTree({
'src/index.ts': `import { resolveBackend } from './llm-backend-selector.js';\nimport { loadConfig } from './config.js';\n`,
'src/llm-backend-selector.ts': `export function resolveBackend() {}\n`,
'src/config.ts': `export function loadConfig() {}\n`,
});
const result = runScript(projectRoot, {
projectRoot,
files: [
{ path: 'src/index.ts', language: 'typescript', fileCategory: 'code' },
{ path: 'src/llm-backend-selector.ts', language: 'typescript', fileCategory: 'code' },
{ path: 'src/config.ts', language: 'typescript', fileCategory: 'code' },
],
});
expect(result.status).toBe(0);
expect(result.output.importMap['src/index.ts']).toEqual([
'src/config.ts',
'src/llm-backend-selector.ts',
]);
});
it('resolves NodeNext .jsx → .tsx and .mjs → .mts rewrites', () => {
projectRoot = setupTree({
'src/index.ts': `import Comp from './Comp.jsx';\nimport { fn } from './worker.mjs';\n`,
'src/Comp.tsx': `export default function Comp() {}\n`,
'src/worker.mts': `export function fn() {}\n`,
});
const result = runScript(projectRoot, {
projectRoot,
files: [
{ path: 'src/index.ts', language: 'typescript', fileCategory: 'code' },
{ path: 'src/Comp.tsx', language: 'typescript', fileCategory: 'code' },
{ path: 'src/worker.mts', language: 'typescript', fileCategory: 'code' },
],
});
expect(result.status).toBe(0);
expect(result.output.importMap['src/index.ts']).toEqual([
'src/Comp.tsx',
'src/worker.mts',
]);
});
it('resolves to the .js when both .ts and .js exist on disk', () => {
// Rare but possible during a partial migration: both `config.ts` and
// `config.js` exist. An `import './config.js'` is an exact-disk match
// and should resolve to that exact file — the NodeNext rewrite only
// kicks in when the .js *doesn't* exist on disk. We assert this to
// pin the disambiguation and avoid future regressions where the rewrite
// accidentally prefers `.ts` over an existing `.js`.
projectRoot = setupTree({
'src/index.ts': `import { x } from './config.js';\n`,
'src/config.ts': `export const x = 1;\n`,
'src/config.js': `export const x = 1;\n`,
});
const result = runScript(projectRoot, {
projectRoot,
files: [
{ path: 'src/index.ts', language: 'typescript', fileCategory: 'code' },
{ path: 'src/config.ts', language: 'typescript', fileCategory: 'code' },
{ path: 'src/config.js', language: 'javascript', fileCategory: 'code' },
],
});
expect(result.status).toBe(0);
expect(result.output.importMap['src/index.ts']).toEqual(['src/config.js']);
});
it('still resolves traditional .js → .js imports unchanged', () => {
// The rewrite must not break the case where `.js` IS the real file on
// disk (pure JavaScript projects, untyped libraries).
projectRoot = setupTree({
'src/index.js': `import { x } from './util.js';\n`,
'src/util.js': `export const x = 1;\n`,
});
const result = runScript(projectRoot, {
projectRoot,
files: [
{ path: 'src/index.js', language: 'javascript', fileCategory: 'code' },
{ path: 'src/util.js', language: 'javascript', fileCategory: 'code' },
],
});
expect(result.status).toBe(0);
expect(result.output.importMap['src/index.js']).toEqual(['src/util.js']);
});
it('leaves the historical "no extension" probe behaviour intact', () => {
// An import like `./utils` (no extension) must still go through the
// append-extensions loop and resolve to `./utils.ts` — the new rewrite
// path is only triggered when the import already ends with a compiled
// extension.
projectRoot = setupTree({
'src/index.ts': `import { foo } from './utils';\n`,
'src/utils.ts': `export function foo() {}\n`,
});
const result = runScript(projectRoot, {
projectRoot,
files: [
{ path: 'src/index.ts', language: 'typescript', fileCategory: 'code' },
{ path: 'src/utils.ts', language: 'typescript', fileCategory: 'code' },
],
});
expect(result.status).toBe(0);
expect(result.output.importMap['src/index.ts']).toEqual(['src/utils.ts']);
});
it('returns null (no resolution) for a .js import whose .ts source is missing', () => {
// The rewrite must NOT silently invent a target when neither the .js nor
// the .ts file exists. The old behaviour would also return null for this
// case — we're verifying the rewrite path doesn't regress it.
projectRoot = setupTree({
'src/index.ts': `import './completely-missing.js';\n`,
});
const result = runScript(projectRoot, {
projectRoot,
files: [{ path: 'src/index.ts', language: 'typescript', fileCategory: 'code' }],
});
expect(result.status).toBe(0);
expect(result.output.importMap['src/index.ts']).toEqual([]);
});
});
describe('extract-import-map.mjs — Python resolver', () => {
@@ -1597,3 +1741,55 @@ describe('extract-import-map.mjs — tree-sitter init graceful failure', () => {
expect(result.output.stats.totalEdges).toBe(0);
});
});
describe('extract-import-map.mjs — deterministic stderr ordering across loaders', () => {
let projectRoot;
afterEach(() => {
if (projectRoot) {
rmSync(projectRoot, { recursive: true, force: true });
projectRoot = null;
}
});
// Regression for the parallel-loader stderr-order bug surfaced in
// PR #346 review: tsconfig / go.mod / composer.json loaders now run
// concurrently, but warnings must still emit in the pre-PR canonical
// order (tsconfig → go → php). If the loaders streamed warnings
// mid-flight, I/O timing could reorder them — the assertions below
// catch that regression.
it('emits warnings in canonical order (tsconfig, go, php) regardless of I/O timing', () => {
projectRoot = setupTree({
'tsconfig.json': '{ "compilerOptions": { "baseUrl": ".", ', // unterminated
'composer.json': '{ "autoload": { "psr-4": { "App\\\\": "src/" }, ', // unterminated
'src/index.ts': `import { foo } from './foo';\n`,
'src/foo.ts': `export const foo = 1;\n`,
'src/Http/Controller.php':
`<?php\nnamespace App\\Http;\n\nuse App\\Models\\User;\n\nclass Controller { }\n`,
'src/Models/User.php':
`<?php\nnamespace App\\Models;\nclass User { }\n`,
});
const result = runScript(projectRoot, {
projectRoot,
files: [
{ path: 'tsconfig.json', language: 'json', fileCategory: 'config' },
{ path: 'composer.json', language: 'json', fileCategory: 'config' },
{ path: 'src/index.ts', language: 'typescript', fileCategory: 'code' },
{ path: 'src/foo.ts', language: 'typescript', fileCategory: 'code' },
{ path: 'src/Http/Controller.php', language: 'php', fileCategory: 'code' },
{ path: 'src/Models/User.php', language: 'php', fileCategory: 'code' },
],
});
expect(result.status).toBe(0);
const tsLineIdx = result.stderr.indexOf('tsconfig.json at');
const composerLineIdx = result.stderr.indexOf('composer.json at');
expect(tsLineIdx).toBeGreaterThanOrEqual(0);
expect(composerLineIdx).toBeGreaterThanOrEqual(0);
// Canonical order: tsconfig warnings precede composer warnings.
// Pre-PR-346 this fell out of sequential loader passes; post-fix it
// falls out of buffering + ordered drain in buildResolutionContext.
expect(tsLineIdx).toBeLessThan(composerLineIdx);
});
});
@@ -1,12 +1,12 @@
{
"name": "understand-anything",
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
"version": "2.7.5",
"version": "2.7.7",
"author": {
"name": "Lum1104"
"name": "Egonex"
},
"homepage": "https://github.com/Lum1104/Understand-Anything",
"repository": "https://github.com/Lum1104/Understand-Anything",
"homepage": "https://github.com/Egonex-AI/Understand-Anything",
"repository": "https://github.com/Egonex-AI/Understand-Anything",
"license": "MIT",
"keywords": [
"codebase-analysis",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@understand-anything/skill",
"version": "2.7.5",
"version": "2.7.7",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -37,6 +37,7 @@
"vitest": "^3.1.0"
},
"dependencies": {
"@tree-sitter-grammars/tree-sitter-kotlin": "1.1.0",
"fuse.js": "^7.1.0",
"ignore": "^7.0.5",
"tree-sitter-c-sharp": "^0.23.1",
@@ -4,6 +4,10 @@ export const kotlinConfig = {
id: "kotlin",
displayName: "Kotlin",
extensions: [".kt", ".kts"],
treeSitter: {
wasmPackage: "@tree-sitter-grammars/tree-sitter-kotlin",
wasmFile: "tree-sitter-kotlin.wasm",
},
concepts: [
"coroutines",
"data classes",
@@ -0,0 +1,364 @@
import { describe, it, expect, beforeAll } from "vitest";
import { createRequire } from "node:module";
import { KotlinExtractor } from "../kotlin-extractor.js";
const require = createRequire(import.meta.url);
let Parser: any;
let Language: any;
let kotlinLang: any;
beforeAll(async () => {
const mod = await import("web-tree-sitter");
Parser = mod.Parser;
Language = mod.Language;
await Parser.init();
const wasmPath = require.resolve(
"@tree-sitter-grammars/tree-sitter-kotlin/tree-sitter-kotlin.wasm",
);
kotlinLang = await Language.load(wasmPath);
});
function parse(code: string) {
const parser = new Parser();
parser.setLanguage(kotlinLang);
const tree = parser.parse(code);
const root = tree.rootNode;
return { tree, parser, root };
}
describe("KotlinExtractor", () => {
const extractor = new KotlinExtractor();
it("has correct languageIds", () => {
expect(extractor.languageIds).toEqual(["kotlin"]);
});
describe("extractStructure - functions", () => {
it("extracts a simple top-level function with params and return type", () => {
const { tree, parser, root } = parse(`fun add(a: Int, b: Int): Int = a + b
`);
const result = extractor.extractStructure(root);
expect(result.functions).toHaveLength(1);
expect(result.functions[0].name).toBe("add");
expect(result.functions[0].params).toEqual(["a", "b"]);
expect(result.functions[0].returnType).toBe("Int");
tree.delete();
parser.delete();
});
it("extracts function with no params and no return type", () => {
const { tree, parser, root } = parse(`fun noop() {}
`);
const result = extractor.extractStructure(root);
expect(result.functions).toHaveLength(1);
expect(result.functions[0].name).toBe("noop");
expect(result.functions[0].params).toEqual([]);
expect(result.functions[0].returnType).toBeUndefined();
tree.delete();
parser.delete();
});
it("extracts suspending and generic functions", () => {
const { tree, parser, root } = parse(`suspend fun <T> fetch(id: String): T? {
return null
}
`);
const result = extractor.extractStructure(root);
expect(result.functions).toHaveLength(1);
expect(result.functions[0].name).toBe("fetch");
expect(result.functions[0].params).toEqual(["id"]);
// Nullable type — Kotlin formats it as "T?"
expect(result.functions[0].returnType).toBe("T?");
tree.delete();
parser.delete();
});
it("extracts multiple top-level functions in declaration order", () => {
const { tree, parser, root } = parse(`fun one() {}
fun two(x: Int): Int = x
fun three(): String = ""
`);
const result = extractor.extractStructure(root);
expect(result.functions.map((f) => f.name)).toEqual([
"one",
"two",
"three",
]);
tree.delete();
parser.delete();
});
});
describe("extractStructure - classes", () => {
it("extracts a class with primary-constructor val properties + methods", () => {
const { tree, parser, root } = parse(`class Foo(val bar: Int) {
val baz: String = "hi"
fun compute(): Int = bar * 2
}
`);
const result = extractor.extractStructure(root);
expect(result.classes).toHaveLength(1);
expect(result.classes[0].name).toBe("Foo");
// Both the constructor val and the body val are properties
expect(result.classes[0].properties).toEqual(
expect.arrayContaining(["bar", "baz"]),
);
expect(result.classes[0].methods).toContain("compute");
tree.delete();
parser.delete();
});
it("extracts an empty class", () => {
const { tree, parser, root } = parse(`class Empty
`);
const result = extractor.extractStructure(root);
expect(result.classes).toHaveLength(1);
expect(result.classes[0].name).toBe("Empty");
expect(result.classes[0].methods).toEqual([]);
expect(result.classes[0].properties).toEqual([]);
tree.delete();
parser.delete();
});
it("extracts a data class and surfaces its constructor parameters as properties", () => {
const { tree, parser, root } = parse(`data class Point(val x: Double, val y: Double)
`);
const result = extractor.extractStructure(root);
expect(result.classes).toHaveLength(1);
expect(result.classes[0].name).toBe("Point");
expect(result.classes[0].properties).toEqual(["x", "y"]);
tree.delete();
parser.delete();
});
it("extracts class methods into functions[] as well as the class's methods[]", () => {
// Mirrors the Go/Swift extractor convention so the graph builder can
// create function nodes for class methods.
const { tree, parser, root } = parse(`class Foo {
fun bar(): Int = 1
}
`);
const result = extractor.extractStructure(root);
expect(result.functions.map((f) => f.name)).toContain("bar");
expect(result.classes[0].methods).toContain("bar");
tree.delete();
parser.delete();
});
});
describe("extractStructure - interfaces", () => {
it("extracts an interface with method requirements as a class-like entry", () => {
const { tree, parser, root } = parse(`interface Greeter {
fun greet(name: String): String
fun farewell(): String
}
`);
const result = extractor.extractStructure(root);
expect(result.classes).toHaveLength(1);
expect(result.classes[0].name).toBe("Greeter");
expect(result.classes[0].methods).toEqual(
expect.arrayContaining(["greet", "farewell"]),
);
tree.delete();
parser.delete();
});
});
describe("extractStructure - object declarations", () => {
it("extracts a singleton `object` with methods", () => {
const { tree, parser, root } = parse(`object Logger {
fun info(msg: String) {}
fun warn(msg: String) {}
}
`);
const result = extractor.extractStructure(root);
expect(result.classes).toHaveLength(1);
expect(result.classes[0].name).toBe("Logger");
expect(result.classes[0].methods).toEqual(
expect.arrayContaining(["info", "warn"]),
);
tree.delete();
parser.delete();
});
});
describe("extractStructure - imports", () => {
it("extracts a simple dotted import", () => {
const { tree, parser, root } = parse(`import kotlin.io.println
`);
const result = extractor.extractStructure(root);
expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("kotlin.io.println");
// The specifier is the final dotted segment
expect(result.imports[0].specifiers).toEqual(["println"]);
tree.delete();
parser.delete();
});
it("extracts a wildcard import", () => {
const { tree, parser, root } = parse(`import kotlinx.coroutines.*
`);
const result = extractor.extractStructure(root);
expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("kotlinx.coroutines");
// Wildcard is preserved as the specifier so consumers can distinguish it
// from a regular dotted import of a specific symbol.
expect(result.imports[0].specifiers).toEqual(["*"]);
tree.delete();
parser.delete();
});
it("extracts an aliased import", () => {
const { tree, parser, root } = parse(`import com.example.foo.Bar as Baz
`);
const result = extractor.extractStructure(root);
expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("com.example.foo.Bar");
// The alias is the user-visible name in this file
expect(result.imports[0].specifiers).toEqual(["Baz"]);
tree.delete();
parser.delete();
});
it("extracts multiple imports in declaration order", () => {
const { tree, parser, root } = parse(`package com.example.app
import kotlinx.coroutines.flow.Flow
import kotlin.io.println
import kotlinx.coroutines.*
`);
const result = extractor.extractStructure(root);
expect(result.imports).toHaveLength(3);
expect(result.imports[0].source).toBe("kotlinx.coroutines.flow.Flow");
expect(result.imports[1].source).toBe("kotlin.io.println");
expect(result.imports[2].source).toBe("kotlinx.coroutines");
tree.delete();
parser.delete();
});
});
describe("extractStructure - exports / visibility", () => {
it("treats no-modifier declarations as exported (Kotlin default is public)", () => {
const { tree, parser, root } = parse(`fun greet() {}
class Greeter {}
`);
const result = extractor.extractStructure(root);
const exportNames = result.exports.map((e) => e.name);
expect(exportNames).toEqual(expect.arrayContaining(["greet", "Greeter"]));
tree.delete();
parser.delete();
});
it("treats public/internal/protected as exported", () => {
const { tree, parser, root } = parse(`public fun a() {}
internal class B {}
`);
const result = extractor.extractStructure(root);
const exportNames = result.exports.map((e) => e.name);
expect(exportNames).toEqual(expect.arrayContaining(["a", "B"]));
tree.delete();
parser.delete();
});
it("does NOT treat private declarations as exported", () => {
const { tree, parser, root } = parse(`private fun helper() {}
private class Internal {}
`);
const result = extractor.extractStructure(root);
const exportNames = result.exports.map((e) => e.name);
expect(exportNames).not.toContain("helper");
expect(exportNames).not.toContain("Internal");
tree.delete();
parser.delete();
});
it("exports an object declaration by default", () => {
const { tree, parser, root } = parse(`object Logger {}
`);
const result = extractor.extractStructure(root);
expect(result.exports.map((e) => e.name)).toContain("Logger");
tree.delete();
parser.delete();
});
});
describe("extractCallGraph", () => {
it("extracts a call from one function to another", () => {
const { tree, parser, root } = parse(`fun helper(): Int = 1
fun caller(): Int {
return helper()
}
`);
const entries = extractor.extractCallGraph(root);
const helperCall = entries.find((e) => e.callee === "helper");
expect(helperCall).toBeDefined();
expect(helperCall!.caller).toBe("caller");
tree.delete();
parser.delete();
});
it("extracts method calls (x.foo()) and attributes them to the enclosing function", () => {
const { tree, parser, root } = parse(`fun run() {
val s = "hi".uppercase()
}
`);
const entries = extractor.extractCallGraph(root);
const callees = entries.map((e) => e.callee);
expect(callees).toContain("uppercase");
tree.delete();
parser.delete();
});
it("returns an empty array when there are no calls", () => {
const { tree, parser, root } = parse(`fun a(): Int = 1
`);
const entries = extractor.extractCallGraph(root);
expect(entries).toEqual([]);
tree.delete();
parser.delete();
});
});
});
@@ -9,6 +9,7 @@ export { RubyExtractor } from "./ruby-extractor.js";
export { PhpExtractor } from "./php-extractor.js";
export { CppExtractor } from "./cpp-extractor.js";
export { CSharpExtractor } from "./csharp-extractor.js";
export { KotlinExtractor } from "./kotlin-extractor.js";
import type { LanguageExtractor } from "./types.js";
import { TypeScriptExtractor } from "./typescript-extractor.js";
@@ -20,6 +21,7 @@ import { RubyExtractor } from "./ruby-extractor.js";
import { PhpExtractor } from "./php-extractor.js";
import { CppExtractor } from "./cpp-extractor.js";
import { CSharpExtractor } from "./csharp-extractor.js";
import { KotlinExtractor } from "./kotlin-extractor.js";
export const builtinExtractors: LanguageExtractor[] = [
new TypeScriptExtractor(),
@@ -31,4 +33,5 @@ export const builtinExtractors: LanguageExtractor[] = [
new PhpExtractor(),
new CppExtractor(),
new CSharpExtractor(),
new KotlinExtractor(),
];
@@ -0,0 +1,425 @@
import type { StructuralAnalysis, CallGraphEntry } from "../../types.js";
import type { LanguageExtractor, TreeSitterNode } from "./types.js";
import { findChild, findChildren } from "./base-extractor.js";
/**
* Extract the visibility keyword text (e.g., "public", "private") from a
* declaration's `modifiers` child, or return null when no modifier is present.
*
* Kotlin's default visibility is `public`, so a `null` result means the
* declaration IS exported callers must treat absence as exported, not the
* other way around.
*/
function extractVisibility(declNode: TreeSitterNode): string | null {
const modifiers = findChild(declNode, "modifiers");
if (!modifiers) return null;
const visibility = findChild(modifiers, "visibility_modifier");
if (!visibility) return null;
return visibility.text;
}
/**
* Whether a Kotlin declaration is visible to other files.
*
* Default visibility in Kotlin is `public`, so a declaration with NO
* modifier counts as exported. Only an explicit `private` opts out.
* `internal` and `protected` remain exported in the project-graph sense
* because they are still resolvable from other files (within the module
* or via inheritance respectively).
*/
function isExported(declNode: TreeSitterNode): boolean {
const visibility = extractVisibility(declNode);
return visibility === null || visibility !== "private";
}
/**
* Get the identifier-text name of a Kotlin declaration. Works for
* function_declaration / class_declaration / object_declaration / interface
* all carry the name as the first `identifier` child after the keyword.
*/
function extractDeclarationName(declNode: TreeSitterNode): string | null {
for (let i = 0; i < declNode.childCount; i++) {
const child = declNode.child(i);
if (child && child.type === "identifier") return child.text;
}
return null;
}
/**
* Extract parameter names from a `function_value_parameters` node. Each
* `parameter` child carries a leading `identifier` for the parameter name;
* the optional trailing `: <type>` annotation is ignored.
*/
function extractParams(declNode: TreeSitterNode): string[] {
const params: string[] = [];
const valueParams = findChild(declNode, "function_value_parameters");
if (!valueParams) return params;
for (const param of findChildren(valueParams, "parameter")) {
// The first `identifier` inside a parameter is its name.
const id = findChild(param, "identifier");
if (id) params.push(id.text);
}
return params;
}
/**
* Extract the return type text from a `function_declaration` by looking for
* the `:` separator and taking the next named child, which is the type node.
* Returns undefined for `Unit`-returning functions (no annotation present).
*/
function extractReturnType(declNode: TreeSitterNode): string | undefined {
// function_value_parameters comes before the optional `: <type>` block;
// walk children from after the parameters to find `:` followed by a type.
let sawParams = false;
for (let i = 0; i < declNode.childCount; i++) {
const child = declNode.child(i);
if (!child) continue;
if (child.type === "function_value_parameters") {
sawParams = true;
continue;
}
if (sawParams && child.type === ":") {
// The next named sibling is the type
for (let j = i + 1; j < declNode.childCount; j++) {
const next = declNode.child(j);
if (next && next.isNamed) return next.text;
}
}
}
return undefined;
}
/**
* Walk a `class_body` and collect functions + properties. Function entries
* are added to both the class's `methods` array and the top-level
* `functions` array (matching the GoExtractor / SwiftExtractor convention).
*/
function collectClassBody(
body: TreeSitterNode,
methods: string[],
properties: string[],
functions: StructuralAnalysis["functions"],
exports: StructuralAnalysis["exports"],
): void {
for (let i = 0; i < body.childCount; i++) {
const member = body.child(i);
if (!member) continue;
if (member.type === "function_declaration") {
const name = extractDeclarationName(member);
if (!name) continue;
methods.push(name);
functions.push({
name,
lineRange: [member.startPosition.row + 1, member.endPosition.row + 1],
params: extractParams(member),
returnType: extractReturnType(member),
});
if (isExported(member)) {
exports.push({ name, lineNumber: member.startPosition.row + 1 });
}
} else if (member.type === "property_declaration") {
const name = extractPropertyName(member);
if (name) properties.push(name);
if (name && isExported(member)) {
exports.push({ name, lineNumber: member.startPosition.row + 1 });
}
} else if (member.type === "object_declaration") {
// Nested companion-object / object members are surfaced as a single
// synthetic property pointing at the inner object's name — enough for
// the graph builder to keep the relationship without exploding scope.
const name = extractDeclarationName(member);
if (name) properties.push(name);
}
}
}
/**
* Extract the property name from a `property_declaration`. The name lives
* inside the `variable_declaration` child as its `identifier`.
*/
function extractPropertyName(propNode: TreeSitterNode): string | null {
const varDecl = findChild(propNode, "variable_declaration");
if (!varDecl) return null;
const id = findChild(varDecl, "identifier");
return id ? id.text : null;
}
/**
* Walk a `primary_constructor`'s `class_parameters` and surface every
* `val` / `var` parameter as a class property. Plain `parameter` entries
* (no val/var keyword) are constructor-only and are NOT properties they
* vanish after the constructor returns.
*/
function collectPrimaryConstructorProperties(
declNode: TreeSitterNode,
properties: string[],
): void {
const primary = findChild(declNode, "primary_constructor");
if (!primary) return;
const params = findChild(primary, "class_parameters");
if (!params) return;
for (const param of findChildren(params, "class_parameter")) {
// A class_parameter that starts with `val` or `var` is a property.
let isProperty = false;
for (let i = 0; i < param.childCount; i++) {
const child = param.child(i);
if (child && (child.type === "val" || child.type === "var")) {
isProperty = true;
break;
}
}
if (!isProperty) continue;
const id = findChild(param, "identifier");
if (id) properties.push(id.text);
}
}
/**
* Kotlin extractor for tree-sitter structural analysis and call graph
* extraction. Maps Kotlin's class / interface / object / data-class
* declarations to the project's shared `StructuralAnalysis.classes` array.
*/
export class KotlinExtractor implements LanguageExtractor {
readonly languageIds = ["kotlin"];
extractStructure(rootNode: TreeSitterNode): StructuralAnalysis {
const functions: StructuralAnalysis["functions"] = [];
const classes: StructuralAnalysis["classes"] = [];
const imports: StructuralAnalysis["imports"] = [];
const exports: StructuralAnalysis["exports"] = [];
for (let i = 0; i < rootNode.childCount; i++) {
const node = rootNode.child(i);
if (!node) continue;
switch (node.type) {
case "package_header":
// Package is metadata about this file, not a graph member. Skip.
break;
case "import":
this.extractImport(node, imports);
break;
case "function_declaration":
this.extractTopLevelFunction(node, functions, exports);
break;
case "class_declaration":
this.extractClassDeclaration(node, classes, functions, exports);
break;
case "object_declaration":
this.extractObjectDeclaration(node, classes, functions, exports);
break;
}
}
return { functions, classes, imports, exports };
}
extractCallGraph(rootNode: TreeSitterNode): CallGraphEntry[] {
const entries: CallGraphEntry[] = [];
const functionStack: string[] = [];
const walk = (node: TreeSitterNode) => {
let pushed = false;
if (node.type === "function_declaration") {
const name = extractDeclarationName(node);
if (name) {
functionStack.push(name);
pushed = true;
}
}
if (node.type === "call_expression" && functionStack.length > 0) {
const callee = this.extractCalleeName(node);
if (callee) {
entries.push({
caller: functionStack[functionStack.length - 1],
callee,
lineNumber: node.startPosition.row + 1,
});
}
}
for (let i = 0; i < node.childCount; i++) {
const child = node.child(i);
if (child) walk(child);
}
if (pushed) functionStack.pop();
};
walk(rootNode);
return entries;
}
// ---- Private helpers ----
private extractTopLevelFunction(
declNode: TreeSitterNode,
functions: StructuralAnalysis["functions"],
exports: StructuralAnalysis["exports"],
): void {
const name = extractDeclarationName(declNode);
if (!name) return;
functions.push({
name,
lineRange: [declNode.startPosition.row + 1, declNode.endPosition.row + 1],
params: extractParams(declNode),
returnType: extractReturnType(declNode),
});
if (isExported(declNode)) {
exports.push({ name, lineNumber: declNode.startPosition.row + 1 });
}
}
private extractClassDeclaration(
declNode: TreeSitterNode,
classes: StructuralAnalysis["classes"],
functions: StructuralAnalysis["functions"],
exports: StructuralAnalysis["exports"],
): void {
const name = extractDeclarationName(declNode);
if (!name) return;
const properties: string[] = [];
const methods: string[] = [];
// 1. Primary-constructor `val`/`var` parameters become properties.
collectPrimaryConstructorProperties(declNode, properties);
// 2. Body members (if any). Some Kotlin declarations have no body
// (e.g. `class Empty` or `data class Point(...)` without `{}`).
const body = findChild(declNode, "class_body");
if (body) {
collectClassBody(body, methods, properties, functions, exports);
}
classes.push({
name,
lineRange: [declNode.startPosition.row + 1, declNode.endPosition.row + 1],
methods,
properties,
});
if (isExported(declNode)) {
exports.push({ name, lineNumber: declNode.startPosition.row + 1 });
}
}
private extractObjectDeclaration(
declNode: TreeSitterNode,
classes: StructuralAnalysis["classes"],
functions: StructuralAnalysis["functions"],
exports: StructuralAnalysis["exports"],
): void {
const name = extractDeclarationName(declNode);
if (!name) return;
const properties: string[] = [];
const methods: string[] = [];
const body = findChild(declNode, "class_body");
if (body) {
collectClassBody(body, methods, properties, functions, exports);
}
classes.push({
name,
lineRange: [declNode.startPosition.row + 1, declNode.endPosition.row + 1],
methods,
properties,
});
if (isExported(declNode)) {
exports.push({ name, lineNumber: declNode.startPosition.row + 1 });
}
}
/**
* Extract a Kotlin import.
*
* The grammar gives us a single `qualified_identifier` child holding the
* dotted module path. Three trailing variants must be distinguished:
*
* - `import foo.bar.Baz` source="foo.bar.Baz", specifier="Baz"
* - `import foo.bar.*` source="foo.bar", specifier="*"
* - `import foo.bar.Baz as Quux` source="foo.bar.Baz", specifier="Quux"
*
* The grammar represents the wildcard as a trailing `*` token AFTER the
* qualified_identifier (which holds the dotted prefix). The alias
* appears as `as` + a top-level `identifier` sibling.
*/
private extractImport(
declNode: TreeSitterNode,
imports: StructuralAnalysis["imports"],
): void {
const qualified = findChild(declNode, "qualified_identifier");
if (!qualified) return;
const parts: string[] = [];
for (const id of findChildren(qualified, "identifier")) {
parts.push(id.text);
}
if (parts.length === 0) return;
const source = parts.join(".");
// Look for a sibling `*` (wildcard) or an `as` keyword + identifier
// after the qualified_identifier.
let specifier = parts[parts.length - 1];
let sawWildcardStar = false;
let sawAs = false;
for (let i = 0; i < declNode.childCount; i++) {
const child = declNode.child(i);
if (!child) continue;
if (child.type === "*") sawWildcardStar = true;
if (child.type === "as") sawAs = true;
else if (sawAs && child.type === "identifier") {
specifier = child.text;
sawAs = false;
}
}
if (sawWildcardStar) specifier = "*";
imports.push({
source,
specifiers: [specifier],
lineNumber: declNode.startPosition.row + 1,
});
}
/**
* Extract the callee name from a Kotlin `call_expression`. Two shapes:
*
* foo(...) first child is `identifier "foo"`
* target.method(...) first child is `navigation_expression` whose
* last `navigation_suffix > identifier` is the
* method name
*/
private extractCalleeName(callNode: TreeSitterNode): string | null {
const first = callNode.child(0);
if (!first) return null;
if (first.type === "identifier") return first.text;
if (first.type === "navigation_expression") {
// The Kotlin grammar flattens navigation: `x.foo` is
// navigation_expression { x, ".", identifier "foo" }
// The method name is the LAST `identifier` child of the navigation.
let lastIdentifier: string | null = null;
for (let i = 0; i < first.childCount; i++) {
const child = first.child(i);
if (child && child.type === "identifier") {
lastIdentifier = child.text;
}
}
return lastIdentifier;
}
return null;
}
}
@@ -58,6 +58,8 @@ function dataUrl(fileName: string, token: string | null): string {
};
const url = envMap[fileName];
if (url) return url;
const base = import.meta.env.BASE_URL || "/";
return `${base.endsWith("/") ? base : `${base}/`}${fileName}`;
}
const path = `/${fileName}`;
return token ? `${path}?token=${encodeURIComponent(token)}` : path;
@@ -13,7 +13,7 @@ function buildCopyText(issues: GraphIssue[]): string {
const lines = hasFatal
? [
"Some of these issues look like dashboard rendering bugs.",
"Please file an issue at github.com/Lum1104/Understand-Anything/issues with the text below.",
"Please file an issue at github.com/Egonex-AI/Understand-Anything/issues with the text below.",
"",
]
: [
@@ -129,6 +129,10 @@ Determine whether to run a full analysis or incremental update.
mkdir -p $PROJECT_ROOT/.understand-anything/intermediate
mkdir -p $PROJECT_ROOT/.understand-anything/tmp
```
3.1. **Purge stale trash dirs.** Phase 7 cleanup `mv`s scratch dirs into `.trash-<timestamp>/` rather than `rm -rf`ing them directly (see issue #301), so that destructive-action gates on hardened hosts don't trip on just-created paths. Reclaim the space here once the trash is older than 7 days — by this point any freshness-window check has long since stopped caring about those dirs:
```bash
find $PROJECT_ROOT/.understand-anything/ -maxdepth 1 -type d -name '.trash-*' -mtime +7 -exec rm -rf {} + 2>/dev/null || true
```
3.5. **Auto-update configuration:**
- If `--auto-update` is in `$ARGUMENTS`: write `{"autoUpdate": true}` to `$PROJECT_ROOT/.understand-anything/config.json`
- If `--no-auto-update` is in `$ARGUMENTS`: write `{"autoUpdate": false}` to `$PROJECT_ROOT/.understand-anything/config.json`
@@ -140,8 +144,10 @@ Determine whether to run a full analysis or incremental update.
- `chinese``zh`, `japanese``ja`, `korean``ko`, `english``en`, `spanish``es`, `french``fr`, `german``de`, `portuguese``pt`, `russian``ru`, `arabic``ar`, etc.
- Locale variants: `zh-TW`, `zh-HK`, `zh-CN`, `pt-BR`, etc. are preserved as-is.
- If `--language` is NOT specified:
- Check `$PROJECT_ROOT/.understand-anything/config.json` for an existing `outputLanguage` field. If present, use that.
- If no stored preference, default to `en` (English).
- **Stored preference wins.** If `$PROJECT_ROOT/.understand-anything/config.json` has an `outputLanguage` field, set `$OUTPUT_LANGUAGE` to it and skip the rest.
- **Otherwise detect (first run only).** Infer the predominant language of the user's conversation as an ISO 639-1 code (`$DETECTED_LANG`). If it is `en` or cannot be confidently determined, set `$OUTPUT_LANGUAGE=en` and proceed silently — no prompt (English users see no change).
- **If `$DETECTED_LANG` ≠ `en`, confirm once before analyzing:** tell the user you detected `<language>` and ask whether to generate all content in it; they press Enter/"yes" to accept, or type another language code/name to override (normalize via the friendly-name map above). If running non-interactively (no reply possible), skip the wait, use `$DETECTED_LANG`, and print a one-line notice instead of blocking.
- **Persist** the resolved `$OUTPUT_LANGUAGE` (including `en`) into `config.json` so it never re-prompts for this project.
- If `--language` IS specified:
- Update `$PROJECT_ROOT/.understand-anything/config.json` with the new language: merge `{"outputLanguage": "<lang>"}` into existing config.
- Store as `$OUTPUT_LANGUAGE` for use throughout all phases.
@@ -770,17 +776,20 @@ Report to the user: `[Phase 7/7] Saving knowledge graph...`
}
```
4. Clean up intermediate files, **preserving `scan-result.json`** so future incremental runs can skip Phase 1 SCAN (see issue #293):
4. Clean up intermediate files, **preserving `scan-result.json`** so future incremental runs can skip Phase 1 SCAN (see issue #293). We `mv` scratch dirs into a timestamped `.trash-*` instead of `rm -rf`ing them directly — this avoids tripping destructive-action gates on hardened hosts (e.g. freshness-window checks) that flag deleting directories created moments earlier (see issue #301). The delayed-purge step in Phase 0 reclaims the space once the trash is older than 7 days.
```bash
# Preserve scan-result.json — Phase 1's deterministic file inventory.
# Future incremental runs (Phase 2 compute-batches.mjs --changed-files=…)
# need this inventory; without it, Phase 1 must re-dispatch and pay ~157k
# tokens / ~158s per incremental run.
TRASH="$PROJECT_ROOT/.understand-anything/.trash-$(date +%s)"
mkdir -p "$TRASH"
INTER="$PROJECT_ROOT/.understand-anything/intermediate"
if [ -d "$INTER" ]; then
find "$INTER" -mindepth 1 -maxdepth 1 -not -name 'scan-result.json' -exec rm -rf {} +
# Move every entry except scan-result.json into the trash dir.
find "$INTER" -mindepth 1 -maxdepth 1 -not -name 'scan-result.json' -exec mv {} "$TRASH/" \; 2>/dev/null || true
fi
rm -rf $PROJECT_ROOT/.understand-anything/tmp
mv "$PROJECT_ROOT/.understand-anything/tmp" "$TRASH/" 2>/dev/null || true
```
5. Report a summary to the user containing:
@@ -13,10 +13,19 @@
*/
import { readFileSync, writeFileSync, existsSync, realpathSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { createRequire } from 'node:module';
/**
* Chunk size for parallel file I/O. Bounded so a 15k-file repo doesn't try
* to open every descriptor at once (would hit `EMFILE`) while still keeping
* libuv's worker-thread pool saturated. Empirically chosen to keep memory
* around tens of MB even when the average file is ~10 KB.
*/
const IO_PARALLELISM = 64;
const __filename = fileURLToPath(import.meta.url);
const PLUGIN_ROOT = resolve(dirname(__filename), '../..');
const require = createRequire(resolve(PLUGIN_ROOT, 'package.json'));
@@ -57,31 +66,55 @@ async function extractExports(projectRoot, codeFiles) {
}
const exportsByPath = new Map();
for (const file of codeFiles) {
const abs = join(projectRoot, file.path);
let content;
try {
content = readFileSync(abs, 'utf-8');
} catch (err) {
process.stderr.write(
`Warning: compute-batches: exports extraction failed for ${file.path} ` +
`(read error: ${err.message}) — symbols=[] in neighborMap — ` +
`cross-batch edges to this file limited to file-level\n`,
);
exportsByPath.set(file.path, []);
continue;
}
try {
const analysis = registry.analyzeFile(file.path, content);
const names = (analysis?.exports || []).map(e => e.name).filter(Boolean);
exportsByPath.set(file.path, names);
} catch (err) {
process.stderr.write(
`Warning: compute-batches: exports extraction failed for ${file.path} ` +
`(analyze error: ${err.message}) — symbols=[] in neighborMap — ` +
`cross-batch edges to this file limited to file-level\n`,
);
exportsByPath.set(file.path, []);
// I/O is parallelised in bounded chunks (libuv worker threads handle the
// disk reads concurrently) while the actual tree-sitter parse stays on
// the main thread, since web-tree-sitter is single-threaded WASM. For a
// 15k-file iOS repo (#226), the sequential `readFileSync` loop dominated;
// letting reads pipeline drops wall time roughly proportional to the
// share of the loop spent waiting on disk.
for (let start = 0; start < codeFiles.length; start += IO_PARALLELISM) {
const slice = codeFiles.slice(start, start + IO_PARALLELISM);
// Read every file in the slice concurrently. Errors per file are
// captured in-place so a single bad file does not abort the chunk.
const reads = await Promise.all(
slice.map(async (file) => {
const abs = join(projectRoot, file.path);
try {
const content = await readFile(abs, 'utf-8');
return { file, content, readError: null };
} catch (err) {
return { file, content: null, readError: err };
}
}),
);
// Serialise the CPU-bound tree-sitter work and the stderr warning emits
// so log order remains identical to the previous sequential loop. This
// also keeps existing fixture-comparison tests stable.
for (const { file, content, readError } of reads) {
if (readError) {
process.stderr.write(
`Warning: compute-batches: exports extraction failed for ${file.path} ` +
`(read error: ${readError.message}) — symbols=[] in neighborMap — ` +
`cross-batch edges to this file limited to file-level\n`,
);
exportsByPath.set(file.path, []);
continue;
}
try {
const analysis = registry.analyzeFile(file.path, content);
const names = (analysis?.exports || []).map(e => e.name).filter(Boolean);
exportsByPath.set(file.path, names);
} catch (err) {
process.stderr.write(
`Warning: compute-batches: exports extraction failed for ${file.path} ` +
`(analyze error: ${err.message}) — symbols=[] in neighborMap — ` +
`cross-batch edges to this file limited to file-level\n`,
);
exportsByPath.set(file.path, []);
}
}
}
return exportsByPath;
@@ -37,6 +37,29 @@ import { createRequire } from 'node:module';
import { dirname, resolve, join, posix } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
/**
* Read a list of files concurrently while preserving result order. Failures
* are returned in-place as `{ raw: null, err }` so callers can emit the same
* per-file warnings they did under the previous sequential `readFileSync`
* loops.
*
* `paths` is a list of `{ key, absPath }` pairs; `key` is whatever the caller
* wants to attach the result to (typically a project-relative POSIX path).
*/
async function readFilesParallel(paths) {
return Promise.all(
paths.map(async ({ key, absPath }) => {
try {
const raw = await readFile(absPath, 'utf-8');
return { key, raw, err: null };
} catch (err) {
return { key, raw: null, err };
}
}),
);
}
const __dirname = dirname(fileURLToPath(import.meta.url));
// skills/understand/ -> plugin root is two dirs up
@@ -180,20 +203,26 @@ function parseTsConfigText(raw) {
* where the stripper damaged a string literal containing `//`.
* 3. If both fail, warn and skip that tsconfig contributes no aliases.
*/
function loadTsConfigs(projectRoot, files) {
async function loadTsConfigs(projectRoot, files) {
const out = new Map();
const warnings = [];
// Collect the candidate paths in the original file order before reading,
// so warning emit order matches the previous sequential implementation.
const candidates = [];
for (const f of files) {
const p = toPosix(f.path);
const base = p.includes('/') ? p.slice(p.lastIndexOf('/') + 1) : p;
if (base !== 'tsconfig.json') continue;
const absPath = join(projectRoot, p);
if (!existsSync(absPath)) continue;
let raw;
try {
raw = readFileSync(absPath, 'utf-8');
} catch (err) {
process.stderr.write(
`Warning: extract-import-map: tsconfig.json at ${absPath} failed ` +
candidates.push({ key: p, absPath });
}
const reads = await readFilesParallel(candidates);
for (const { key: p, raw, err } of reads) {
if (err) {
// absPath isn't carried through the helper return shape; reconstruct it.
warnings.push(
`Warning: extract-import-map: tsconfig.json at ${join(projectRoot, p)} failed ` +
`to read (${err.message}) — path aliases from this config will ` +
`not be applied — relative imports unaffected\n`,
);
@@ -201,8 +230,8 @@ function loadTsConfigs(projectRoot, files) {
}
const parsed = parseTsConfigText(raw);
if (!parsed) {
process.stderr.write(
`Warning: extract-import-map: tsconfig.json at ${absPath} failed ` +
warnings.push(
`Warning: extract-import-map: tsconfig.json at ${join(projectRoot, p)} failed ` +
`to parse — path aliases from this config will not be applied ` +
`— relative imports unaffected\n`,
);
@@ -210,7 +239,7 @@ function loadTsConfigs(projectRoot, files) {
}
out.set(dirOf(p), parsed);
}
return out;
return { configs: out, warnings };
}
/**
@@ -237,20 +266,26 @@ function loadTsConfigs(projectRoot, files) {
* The resolver uses each module's prefix to translate
* `import "github.com/foo/bar/x"` into the project-internal `x/<file>.go`.
*/
function loadGoModules(projectRoot, files) {
async function loadGoModules(projectRoot, files) {
const out = new Map();
// loadGoModules currently emits no warnings (read failures are silently
// skipped — per-file resolvers surface "no ancestor go.mod" later), but
// the `{ data, warnings }` shape matches loadTsConfigs / loadPhpAutoloads
// so the concurrent caller in buildResolutionContext can drain them
// uniformly in canonical order.
const warnings = [];
const candidates = [];
for (const f of files) {
const p = toPosix(f.path);
const base = p.includes('/') ? p.slice(p.lastIndexOf('/') + 1) : p;
if (base !== 'go.mod') continue;
const absPath = join(projectRoot, p);
if (!existsSync(absPath)) continue;
let raw;
try {
raw = readFileSync(absPath, 'utf-8');
} catch {
continue;
}
candidates.push({ key: p, absPath });
}
const reads = await readFilesParallel(candidates);
for (const { key: p, raw, err } of reads) {
if (err) continue;
let moduleName = '';
for (const line of raw.split(/\r?\n/)) {
const trimmed = line.replace(/\/\/.*$/, '').trim();
@@ -261,7 +296,7 @@ function loadGoModules(projectRoot, files) {
if (!moduleName) continue;
out.set(dirOf(p), moduleName);
}
return out;
return { modules: out, warnings };
}
/**
@@ -306,10 +341,32 @@ function findNearestConfigDir(startDir, configMap) {
*
* Build once; pass everywhere.
*/
function buildResolutionContext(projectRoot, files) {
async function buildResolutionContext(projectRoot, files) {
const fileSet = new Set(files.map(f => toPosix(f.path)));
const tsConfigs = loadTsConfigs(projectRoot, files);
const goModules = loadGoModules(projectRoot, files);
// The three config-loader passes are independent and each does its own
// batched parallel I/O; run them concurrently so the wait for a slow
// tsconfig.json read doesn't block go.mod / composer.json scanning.
//
// Each loader BUFFERS warnings into a private array rather than writing
// them to stderr inline. If a loader streamed warnings directly during
// the concurrent passes, lines from independent loader families could
// interleave based on I/O timing — that would break the pre-PR
// deterministic order (ts → go → php) and make stderr-diff verification
// flaky. Drain the buffers in canonical order *after* Promise.all, so
// a fixture with `(malformed tsconfig.json, malformed composer.json)`
// always emits `tsconfig…\ncomposer…\n`, never the reverse.
const [tsResult, goResult, phpResult] = await Promise.all([
loadTsConfigs(projectRoot, files),
loadGoModules(projectRoot, files),
loadPhpAutoloads(projectRoot, files),
]);
for (const w of tsResult.warnings) process.stderr.write(w);
for (const w of goResult.warnings) process.stderr.write(w);
for (const w of phpResult.warnings) process.stderr.write(w);
const tsConfigs = tsResult.configs;
const goModules = goResult.modules;
const phpAutoloads = phpResult.autoloads;
// Index .go files by their parent directory so the Go resolver can
// expand a package-level import to all member .go files in O(1).
@@ -331,8 +388,6 @@ function buildResolutionContext(projectRoot, files) {
const kotlinIndex = buildSuffixIndex(files, p => p.endsWith('.kt'));
const csIndex = buildSuffixIndex(files, p => p.endsWith('.cs'));
const phpAutoloads = loadPhpAutoloads(projectRoot, files);
return {
projectRoot,
fileSet,
@@ -369,15 +424,55 @@ const TS_EXT_PROBES = [
'/index.ts', '/index.tsx', '/index.js', '/index.jsx',
];
/**
* NodeNext / Node16 / Bundler-with-explicit-extensions ESM TypeScript convention:
* TypeScript does NOT rewrite import specifiers during compilation, so source
* files import their COMPILED specifier (`./config.js`) even when only
* `./config.ts` exists on disk. We map each compiled-output extension to the
* TS source extensions that could have produced it, in priority order.
*
* Without this rewrite, ESM-TS projects (which is now the default for any new
* TS project) end up with a near-edgeless knowledge graph because every
* project-internal import fails to resolve. (#294)
*/
const NODENEXT_REWRITES = {
'.js': ['.ts', '.tsx', '.js', '.jsx'],
'.jsx': ['.tsx', '.jsx'],
'.mjs': ['.mts', '.mjs', '.ts'],
'.cjs': ['.cts', '.cjs', '.ts'],
};
/**
* Try ext probes against the file set for the given base path. Returns the
* first matching project-relative path, or null. If the base path already has
* a code extension AND exists in the file set, returns it directly.
*
* For NodeNext-style imports (`./foo.js` where only `./foo.ts` exists), apply
* the source-extension rewrite see NODENEXT_REWRITES above.
*/
function probeWithExtensions(basePath, fileSet) {
if (!basePath) return null;
// Exact match (import already had an extension)
// Exact match (import already had an extension that resolves on disk)
if (fileSet.has(basePath)) return basePath;
// NodeNext rewrite: if the basePath ends with a compiled-output extension
// but no such file exists, try the corresponding source extensions. We do
// this BEFORE the legacy "append extensions" loop because for an import
// like `./foo.js`, appending `.ts` would produce `foo.js.ts` (always wrong)
// while the correct candidate is `foo.ts`.
for (const [outExt, srcExts] of Object.entries(NODENEXT_REWRITES)) {
if (!basePath.endsWith(outExt)) continue;
const stem = basePath.slice(0, -outExt.length);
for (const srcExt of srcExts) {
const candidate = stem + srcExt;
if (fileSet.has(candidate)) return candidate;
}
// The basePath had an explicit compiled extension — don't fall through
// to the "append extensions" loop, which would produce nonsense like
// `foo.js.ts`. If NodeNext rewrite didn't find anything, return null.
return null;
}
for (const ext of TS_EXT_PROBES) {
const candidate = basePath + ext;
if (fileSet.has(candidate)) return candidate;
@@ -1028,20 +1123,23 @@ function parseComposerAutoloadText(raw) {
* at the bad file and skips it. The rest of the project's PHP imports keep
* resolving via whichever composer.json files parsed cleanly.
*/
function loadPhpAutoloads(projectRoot, files) {
async function loadPhpAutoloads(projectRoot, files) {
const out = new Map();
const warnings = [];
const candidates = [];
for (const f of files) {
const p = toPosix(f.path);
const base = p.includes('/') ? p.slice(p.lastIndexOf('/') + 1) : p;
if (base !== 'composer.json') continue;
const absPath = join(projectRoot, p);
if (!existsSync(absPath)) continue;
let raw;
try {
raw = readFileSync(absPath, 'utf-8');
} catch (err) {
process.stderr.write(
`Warning: extract-import-map: composer.json at ${absPath} failed ` +
candidates.push({ key: p, absPath });
}
const reads = await readFilesParallel(candidates);
for (const { key: p, raw, err } of reads) {
if (err) {
warnings.push(
`Warning: extract-import-map: composer.json at ${join(projectRoot, p)} failed ` +
`to read (${err.message}) — PSR-4 namespace mapping from this ` +
`composer.json unavailable — PHP imports under this package ` +
`will not resolve\n`,
@@ -1050,8 +1148,8 @@ function loadPhpAutoloads(projectRoot, files) {
}
const parsed = parseComposerAutoloadText(raw);
if (parsed === null) {
process.stderr.write(
`Warning: extract-import-map: composer.json at ${absPath} failed ` +
warnings.push(
`Warning: extract-import-map: composer.json at ${join(projectRoot, p)} failed ` +
`to parse — PSR-4 namespace mapping unavailable — PHP imports ` +
`under this package will not resolve\n`,
);
@@ -1059,7 +1157,7 @@ function loadPhpAutoloads(projectRoot, files) {
}
out.set(dirOf(p), parsed);
}
return out;
return { autoloads: out, warnings };
}
/**
@@ -1421,8 +1519,10 @@ async function main() {
);
}
// Build resolution context (cached configs)
const ctx = buildResolutionContext(projectRoot, files);
// Build resolution context (cached configs). The loader pass for the
// tsconfig/go.mod/composer.json files inside is parallelised — see
// `buildResolutionContext`.
const ctx = await buildResolutionContext(projectRoot, files);
const importMap = {};
let filesWithImports = 0;
@@ -117,7 +117,7 @@ export function buildOnboardingGuide(graph: KnowledgeGraph): string {
// --- Footer ---
lines.push("---");
lines.push("");
lines.push(`*Generated by [Understand Anything](https://github.com/Lum1104/Understand-Anything) from knowledge graph v${graph.version}*`);
lines.push(`*Generated by [Understand Anything](https://github.com/Egonex-AI/Understand-Anything) from knowledge graph v${graph.version}*`);
lines.push("");
return lines.join("\n");