diff --git a/README.md b/README.md index d68f341..3bdb4db 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Gemini CLI OpenCode Vibe CLI + Trae Homepage Live Demo

@@ -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); }