diff --git a/README.md b/README.md
index d68f341..3bdb4db 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,7 @@
+
@@ -183,7 +184,7 @@ Understand-Anything works across multiple AI coding platforms.
/plugin install understand-anything
```
-### One-line install (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI)
+### One-line install (Codex / OpenCode / OpenClaw / Antigravity / Gemini CLI / Pi Agent / Vibe CLI / VS Code Copilot / Hermes / Cline / KIMI CLI / Trae)
**macOS / Linux:**
```bash
@@ -199,7 +200,7 @@ iwr -useb https://raw.githubusercontent.com/Lum1104/Understand-Anything/main/ins
The installer clones the repo to `~/.understand-anything/repo` and creates the right symlinks for the chosen platform. Restart your CLI/IDE afterwards.
-- Supported `` values: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`
+- Supported `` values: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `trae`
- Update later: `./install.sh --update`
- Uninstall: `./install.sh --uninstall `
@@ -239,6 +240,7 @@ copilot plugin install Lum1104/Understand-Anything:understand-anything-plugin
| Hermes | ✅ Supported | `install.sh hermes` |
| Cline | ✅ Supported | `install.sh cline` |
| KIMI CLI | ✅ Supported | `install.sh kimi` |
+| Trae | ✅ Supported | `install.sh trae` |
---
diff --git a/READMEs/README.es-ES.md b/READMEs/README.es-ES.md
index 27884f9..542d83b 100644
--- a/READMEs/README.es-ES.md
+++ b/READMEs/README.es-ES.md
@@ -205,6 +205,8 @@ 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í.
+
### VS Code + GitHub Copilot
VS Code con GitHub Copilot (v1.108+) detecta automáticamente el plugin a través de `.copilot-plugin/plugin.json` cuando se clona este repositorio. No requiere instalación manual: simplemente clona y abre en VS Code.
diff --git a/READMEs/README.ja-JP.md b/READMEs/README.ja-JP.md
index 78fe62c..cbe8a43 100644
--- a/READMEs/README.ja-JP.md
+++ b/READMEs/README.ja-JP.md
@@ -206,6 +206,8 @@ 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` を貼り付けて追加します。
+
### VS Code + GitHub Copilot
GitHub Copilot拡張機能(v1.108+)をインストールしたVS Codeは、`.copilot-plugin/plugin.json` 経由でプラグインを自動検出します。クローンしてVS Codeで開くだけで、手動インストールは不要です。
diff --git a/READMEs/README.ko-KR.md b/READMEs/README.ko-KR.md
index 13c4aef..2e51742 100644
--- a/READMEs/README.ko-KR.md
+++ b/READMEs/README.ko-KR.md
@@ -205,6 +205,8 @@ 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`를 붙여넣은 뒤 추가하세요.
+
### VS Code + GitHub Copilot
GitHub Copilot(v1.108+)이 설치된 VS Code는 `.copilot-plugin/plugin.json`을 통해 플러그인을 자동으로 인식합니다. 수동 설치가 필요 없습니다. 클론 후 VS Code에서 열기만 하면 됩니다.
diff --git a/READMEs/README.ru-RU.md b/READMEs/README.ru-RU.md
index 67182fb..3b11cb3 100644
--- a/READMEs/README.ru-RU.md
+++ b/READMEs/README.ru-RU.md
@@ -206,6 +206,8 @@ 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` в поле поиска и добавьте оттуда.
+
### VS Code + GitHub Copilot
VS Code с GitHub Copilot (v1.108+) автоматически обнаруживает плагин через `.copilot-plugin/plugin.json` при клонировании этого репозитория. Ручная установка не требуется — просто склонируйте и откройте в VS Code.
diff --git a/READMEs/README.tr-TR.md b/READMEs/README.tr-TR.md
index e03b993..7f3168d 100644
--- a/READMEs/README.tr-TR.md
+++ b/READMEs/README.tr-TR.md
@@ -206,6 +206,8 @@ 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.
+
### VS Code + GitHub Copilot
GitHub Copilot uzantısı (v1.108+) yüklü VS Code, `.copilot-plugin/plugin.json` aracılığıyla eklentiyi otomatik keşfeder. Manuel kurulum gerekmez — sadece klonla ve VS Code'da aç.
diff --git a/READMEs/README.zh-CN.md b/READMEs/README.zh-CN.md
index 17b6e91..87fedc6 100644
--- a/READMEs/README.zh-CN.md
+++ b/READMEs/README.zh-CN.md
@@ -205,6 +205,8 @@ 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` 并添加。
+
### VS Code + GitHub Copilot
安装 GitHub Copilot 扩展(v1.108+)后,VS Code 会通过 `.copilot-plugin/plugin.json` 自动发现插件,克隆后直接在 VS Code 中打开即可,无需手动安装。
diff --git a/READMEs/README.zh-TW.md b/READMEs/README.zh-TW.md
index 1d8c297..0b4ef96 100644
--- a/READMEs/README.zh-TW.md
+++ b/READMEs/README.zh-TW.md
@@ -205,6 +205,8 @@ 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` 並新增。
+
### VS Code + GitHub Copilot
安裝 GitHub Copilot 擴充功能(v1.108+)後,VS Code 會透過 `.copilot-plugin/plugin.json` 自動發現外掛程式,複製後直接在 VS Code 中開啟即可,無需手動安裝。
diff --git a/install.ps1 b/install.ps1
index 4eb6ece..1a4b4df 100644
--- a/install.ps1
+++ b/install.ps1
@@ -38,6 +38,7 @@ $Platforms = [ordered]@{
hermes = @{ Target = (Join-Path $HOME '.hermes\skills'); Style = 'folder' }
cline = @{ Target = (Join-Path $HOME '.cline\skills'); Style = 'folder' }
kimi = @{ Target = (Join-Path $HOME '.kimi\skills'); Style = 'folder' }
+ trae = @{ Target = (Join-Path $HOME '.trae\skills'); Style = 'per-skill' }
}
function Show-Usage {
diff --git a/install.sh b/install.sh
index b23f8ae..8ff4293 100755
--- a/install.sh
+++ b/install.sh
@@ -39,6 +39,7 @@ vscode|$HOME/.copilot/skills|per-skill
hermes|$HOME/.hermes/skills|folder
cline|$HOME/.cline/skills|folder
kimi|$HOME/.kimi/skills|folder
+trae|$HOME/.trae/skills|per-skill
EOF
}
diff --git a/tests/skill/understand/test_extract_import_map.test.mjs b/tests/skill/understand/test_extract_import_map.test.mjs
index e25a644..8a7e019 100644
--- a/tests/skill/understand/test_extract_import_map.test.mjs
+++ b/tests/skill/understand/test_extract_import_map.test.mjs
@@ -238,6 +238,111 @@ describe('extract-import-map.mjs — TypeScript / JavaScript resolver', () => {
);
});
+ // ── Issue #214: tsconfig path-alias targets with leading "./" ───────────
+ // create-next-app ships `"@/*": ["./*"]` as the default. With a root
+ // tsconfig the candidate would stay as "./lib/thing" while ctx.fileSet
+ // stores normalized "lib/thing", silently dropping every cross-module
+ // import edge. Three originally broken cases plus one regression guard
+ // for the already working `["*"]` form.
+
+ it('resolves tsconfig paths with leading "./" target and no baseUrl (#214)', () => {
+ projectRoot = setupTree({
+ 'tsconfig.json': JSON.stringify({
+ compilerOptions: {
+ paths: { '@/*': ['./*'] },
+ },
+ }),
+ 'src/app.ts': `import { x } from '@/lib/thing';\nconst _ = x;\n`,
+ 'lib/thing.ts': `export const x = 1;\n`,
+ });
+
+ const result = runScript(projectRoot, {
+ projectRoot,
+ files: [
+ { path: 'tsconfig.json', language: 'json', fileCategory: 'config' },
+ { path: 'src/app.ts', language: 'typescript', fileCategory: 'code' },
+ { path: 'lib/thing.ts', language: 'typescript', fileCategory: 'code' },
+ ],
+ });
+
+ expect(result.status).toBe(0);
+ expect(result.output.importMap['src/app.ts']).toContain('lib/thing.ts');
+ });
+
+ it('resolves tsconfig paths with leading "./" target and baseUrl "." (#214)', () => {
+ projectRoot = setupTree({
+ 'tsconfig.json': JSON.stringify({
+ compilerOptions: {
+ baseUrl: '.',
+ paths: { '@/*': ['./*'] },
+ },
+ }),
+ 'src/app.ts': `import { x } from '@/lib/thing';\nconst _ = x;\n`,
+ 'lib/thing.ts': `export const x = 1;\n`,
+ });
+
+ const result = runScript(projectRoot, {
+ projectRoot,
+ files: [
+ { path: 'tsconfig.json', language: 'json', fileCategory: 'config' },
+ { path: 'src/app.ts', language: 'typescript', fileCategory: 'code' },
+ { path: 'lib/thing.ts', language: 'typescript', fileCategory: 'code' },
+ ],
+ });
+
+ expect(result.status).toBe(0);
+ expect(result.output.importMap['src/app.ts']).toContain('lib/thing.ts');
+ });
+
+ it('resolves tsconfig paths with leading "./" target and baseUrl "src" (#214)', () => {
+ projectRoot = setupTree({
+ 'tsconfig.json': JSON.stringify({
+ compilerOptions: {
+ baseUrl: 'src',
+ paths: { '@/*': ['./*'] },
+ },
+ }),
+ 'src/app.ts': `import { x } from '@/thing';\nconst _ = x;\n`,
+ 'src/thing.ts': `export const x = 1;\n`,
+ });
+
+ const result = runScript(projectRoot, {
+ projectRoot,
+ files: [
+ { path: 'tsconfig.json', language: 'json', fileCategory: 'config' },
+ { path: 'src/app.ts', language: 'typescript', fileCategory: 'code' },
+ { path: 'src/thing.ts', language: 'typescript', fileCategory: 'code' },
+ ],
+ });
+
+ expect(result.status).toBe(0);
+ expect(result.output.importMap['src/app.ts']).toContain('src/thing.ts');
+ });
+
+ it('keeps resolving tsconfig paths with bare "*" target (#214 regression guard)', () => {
+ projectRoot = setupTree({
+ 'tsconfig.json': JSON.stringify({
+ compilerOptions: {
+ paths: { '@/*': ['*'] },
+ },
+ }),
+ 'src/app.ts': `import { x } from '@/lib/thing';\nconst _ = x;\n`,
+ 'lib/thing.ts': `export const x = 1;\n`,
+ });
+
+ const result = runScript(projectRoot, {
+ projectRoot,
+ files: [
+ { path: 'tsconfig.json', language: 'json', fileCategory: 'config' },
+ { path: 'src/app.ts', language: 'typescript', fileCategory: 'code' },
+ { path: 'lib/thing.ts', language: 'typescript', fileCategory: 'code' },
+ ],
+ });
+
+ 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
diff --git a/understand-anything-plugin/skills/understand/extract-import-map.mjs b/understand-anything-plugin/skills/understand/extract-import-map.mjs
index 42dd7d4..f472464 100644
--- a/understand-anything-plugin/skills/understand/extract-import-map.mjs
+++ b/understand-anything-plugin/skills/understand/extract-import-map.mjs
@@ -470,9 +470,18 @@ export function resolveTsJsImport(rawImport, file, ctx) {
const relativeToConfig = normalizedBase
? posix.join(normalizedBase, mapped)
: mapped;
- const candidate = tsConfigDir
- ? posix.join(tsConfigDir, relativeToConfig)
- : relativeToConfig;
+ // posix.normalize strips a leading "./" left over when both
+ // tsConfigDir and normalizedBase are empty (root tsconfig with
+ // `"@/*": ["./*"]`, the create-next-app default). Without this the
+ // candidate stays as "./foo" while ctx.fileSet stores "foo", and
+ // probeWithExtensions silently drops every cross-module edge.
+ const candidate = posix.normalize(
+ tsConfigDir
+ ? posix.join(tsConfigDir, relativeToConfig)
+ : relativeToConfig,
+ );
+ // Defensive: tsconfig targets shouldn't escape the project root.
+ if (candidate.startsWith('..')) continue;
const probed = probeWithExtensions(candidate, ctx.fileSet);
if (probed) return probed;
}
diff --git a/understand-anything-plugin/skills/understand/scan-project.mjs b/understand-anything-plugin/skills/understand/scan-project.mjs
index 553a82e..83ccc7d 100644
--- a/understand-anything-plugin/skills/understand/scan-project.mjs
+++ b/understand-anything-plugin/skills/understand/scan-project.mjs
@@ -464,17 +464,25 @@ function toPosix(p) {
* so the ignore filter has to do more work in the fallback path.
*/
function enumerateViaGit(projectRoot) {
- const result = spawnSync('git', ['ls-files', '-co', '--exclude-standard'], {
+ // -z = NUL-terminated output. Without it, `git ls-files` C-escapes non-ASCII
+ // bytes in path names — paths containing emoji, accented characters, CJK
+ // codepoints, etc. come back quoted with octal escapes (e.g.
+ // `"30. \360\237\217\227 BD-CCER/file.md"` for a path containing 🏗️).
+ // Those quoted-escaped strings then fail to round-trip back to real disk
+ // paths in downstream consumers, so files in such directories are silently
+ // dropped from the scan. The -z form emits raw bytes between NUL separators,
+ // preserving every codepoint as-is. This is the same approach git itself
+ // uses for `--null` everywhere downstream (xargs -0, etc.).
+ const result = spawnSync('git', ['ls-files', '-z', '-co', '--exclude-standard'], {
cwd: projectRoot,
encoding: 'utf-8',
maxBuffer: 256 * 1024 * 1024, // 256MB — huge monorepos can produce >10MB of paths
});
if (result.status !== 0 || !result.stdout) return null;
- // Each line is one path, project-relative, already POSIX on all platforms
- // because git emits forward slashes regardless of OS.
+ // Each NUL-separated chunk is one path, project-relative, already POSIX on
+ // all platforms because git emits forward slashes regardless of OS.
return result.stdout
- .split('\n')
- .map(s => s.trim())
+ .split('\0')
.filter(Boolean)
.map(toPosix);
}