Merge branch 'main' into fix/import-resolver-nodenext-rewrite

Resolve conflict in tests/skill/understand/test_extract_import_map.test.mjs
by keeping both new test groups — they cover independent fixes that should
coexist:
  - upstream #214: tsconfig path-alias targets with leading "./"
  - this PR  #294: NodeNext .js → .ts rewrite for ESM TypeScript imports

The extract-import-map.mjs script auto-merged cleanly; both fixes are
already present in the merged source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tirth Kanani
2026-06-01 10:25:37 +01:00
Unverified
13 changed files with 150 additions and 10 deletions
+4 -2
View File
@@ -24,6 +24,7 @@
<a href="#gemini-cli"><img src="https://img.shields.io/badge/Gemini_CLI-4285F4" alt="Gemini CLI" /></a>
<a href="#opencode"><img src="https://img.shields.io/badge/OpenCode-38bdf8" alt="OpenCode" /></a>
<a href="#mistral-vibe-cli"><img src="https://img.shields.io/badge/Vibe_CLI-7c3aed" alt="Vibe CLI" /></a>
<a href="#trae"><img src="https://img.shields.io/badge/Trae-7e22ce" alt="Trae" /></a>
<a href="https://understand-anything.com"><img src="https://img.shields.io/badge/Homepage-d4a574" alt="Homepage" /></a>
<a href="https://understand-anything.com/demo/"><img src="https://img.shields.io/badge/Live_Demo-00c853" alt="Live Demo" /></a>
</p>
@@ -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 `<platform>` values: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`
- Supported `<platform>` values: `gemini`, `codex`, `opencode`, `pi`, `openclaw`, `antigravity`, `vibe`, `vscode`, `hermes`, `cline`, `kimi`, `trae`
- Update later: `./install.sh --update`
- Uninstall: `./install.sh --uninstall <platform>`
@@ -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` |
---
+2
View File
@@ -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.
+2
View File
@@ -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で開くだけで、手動インストールは不要です。
+2
View File
@@ -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에서 열기만 하면 됩니다.
+2
View File
@@ -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.
+2
View File
@@ -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ç.
+2
View File
@@ -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 中打开即可,无需手动安装。
+2
View File
@@ -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 中開啟即可,無需手動安裝。
+1
View File
@@ -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 {
+1
View File
@@ -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
}
@@ -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
@@ -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;
}
@@ -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);
}