mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-06-22 10:58:03 +08:00
Merge branch 'main' into feat/add-kiro-platform-support
This commit is contained in:
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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,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/",
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
patreon: Lum1104
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
@@ -12,5 +12,8 @@ __pycache__/
|
||||
.worktrees/
|
||||
homepage/public/demo/
|
||||
.private/
|
||||
__pycache__/
|
||||
.venv/
|
||||
venv/
|
||||
*.pyc
|
||||
*.pyo
|
||||
Thumbs.db
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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 →</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 © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||
MIT License © Yuxiang Lin and Infinite Universe, Inc.
|
||||
</p>
|
||||
|
||||
+20
-19
@@ -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 →</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 © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||
MIT License © Yuxiang Lin and Infinite Universe, Inc.
|
||||
</p>
|
||||
|
||||
+20
-19
@@ -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 コミュニティに参加 →</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
|
||||
```
|
||||
|
||||
**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 @@ 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-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` は除きます(これらはローカルの一時ファイルです)。
|
||||
|
||||
@@ -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 © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||
MIT License © Yuxiang Lin and Infinite Universe, Inc.
|
||||
</p>
|
||||
|
||||
+20
-19
@@ -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 커뮤니티 참여하기 →</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 라이선스 © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||
MIT License © Yuxiang Lin and Infinite Universe, Inc.
|
||||
</p>
|
||||
|
||||
+20
-19
@@ -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 →</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 © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||
MIT License © Yuxiang Lin and Infinite Universe, Inc.
|
||||
</p>
|
||||
|
||||
+20
-19
@@ -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 →</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ı © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||
MIT License © Yuxiang Lin and Infinite Universe, Inc.
|
||||
</p>
|
||||
|
||||
+20
-19
@@ -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 社区 →</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
|
||||
```
|
||||
|
||||
**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 @@ 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-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 许可证 © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||
MIT License © Yuxiang Lin and Infinite Universe, Inc.
|
||||
</p>
|
||||
|
||||
+20
-19
@@ -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 社群 →</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
|
||||
```
|
||||
|
||||
**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 @@ 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-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 授權條款 © <a href="https://github.com/Lum1104">Lum1104</a>
|
||||
MIT License © Yuxiang Lin and Infinite Universe, Inc.
|
||||
</p>
|
||||
|
||||
+51
@@ -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 142–144). |
|
||||
| `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 ~148–151) 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 142–144) 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
@@ -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 |
|
||||
|
||||
@@ -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">·</span>
|
||||
<a href={discordUrl} target="_blank" rel="noopener noreferrer">Discord</a>
|
||||
<span class="footer-sep">·</span>
|
||||
<a href="/demo/">Live Demo</a>
|
||||
<span class="footer-sep">·</span>
|
||||
<a href={`mailto:${contactEmail}`}>Contact</a>
|
||||
<span class="footer-sep">·</span>
|
||||
<a href={`${githubUrl}/blob/main/LICENSE`} target="_blank" rel="noopener noreferrer">MIT License</a>
|
||||
</div>
|
||||
<p class="footer-note">Graphs that teach. Built for AI coding assistants.</p>
|
||||
<p class="footer-note">An open-source project from Egonex. Originally created by Lum1104.</p>
|
||||
<p class="footer-note">© 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) {
|
||||
|
||||
@@ -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> 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">·</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 →</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">·</span>
|
||||
<span class="hero-enterprise-email">lum@understand-anything.com</span>
|
||||
<span class="hero-enterprise-arrow">→</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">·</span>
|
||||
<span class="hero-company-site">egonex.ai</span>
|
||||
<span class="hero-company-arrow">→</span>
|
||||
</a>
|
||||
|
||||
<a href="https://lum.is-a.dev/" target="_blank" rel="noopener noreferrer" class="hero-author anim anim-5">
|
||||
<span class="hero-author-label">Author</span>
|
||||
<span class="hero-author-divider">·</span>
|
||||
<span class="hero-author-handle">Lum1104</span>
|
||||
<span class="hero-author-arrow">→</span>
|
||||
</a>
|
||||
|
||||
<details class="hero-subscribe anim anim-6">
|
||||
<summary class="hero-subscribe-toggle">
|
||||
<span>Subscribe to release updates</span>
|
||||
<svg class="hero-subscribe-chevron" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</summary>
|
||||
<div class="hero-subscribe-frame">
|
||||
<p class="hero-subscribe-hint">No spam, unsubscribe anytime</p>
|
||||
<iframe
|
||||
src="https://yuxianglin.substack.com/embed"
|
||||
title="Subscribe to release updates"
|
||||
width="480"
|
||||
height="150"
|
||||
loading="lazy"
|
||||
scrolling="no"
|
||||
frameborder="0"
|
||||
></iframe>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Generated
+24
@@ -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,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",
|
||||
|
||||
+364
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user