On top of the placeholder fix: step 4 now launches the self-contained
viewer tarball pinned to the installed plugin version via npx — no
pnpm install, no core build, no Vite. The hardened install/build/vite
path becomes the fallback for offline runs or versions whose release
predates the viewer asset (< v2.9.0). Hardening test extended to cover
the quoted, version-pinned fast-path invocation.
Verified end-to-end against the real v2.9.0 release asset.
New zero-dependency package understand-anything-viewer: a small Node
http server with the prebuilt dashboard embedded, replicating the dev
server's endpoints and security model (127.0.0.1 bind, one-time access
token on every data endpoint, graph filePath sanitisation, file-content
allowlist / 1MB cap / binary rejection, .ua with legacy
.understand-anything fallback). npm-packed and attached to each GitHub
release as understand-anything-viewer.tgz, so teammates without Claude
Code open a committed graph with:
npx https://github.com/Egonex-AI/Understand-Anything/releases/latest/download/understand-anything-viewer.tgz <project>
No npm registry publishing involved. End-to-end tests spawn the real
server and cover the token gate, sanitisation, allowlist, traversal
guards, and both data-directory layouts.
Every bundled script now honors the shared rule (.understand-anything/
wins when it already exists, .ua/ otherwise): scripts importing
@understand-anything/core use the exported resolveUaDir; standalone
figma/dev scripts inline a two-line helper; Python merge/parse scripts
inline resolve_ua_dir mirroring core. extract-domain-context skips both
directory names when walking, and eslint ignores both. Tests cover
fresh-project .ua, legacy .understand-anything, and legacy-wins-when-
both for each script family.
Two narrow Karpathy-parser path-resolution gaps degraded valid wikis:
Index.md/Log.md were only detected in lowercase — detect_format,
parse_wiki's index/log selection, and merge-knowledge-graph's
project-name detection now resolve known infra filenames
case-insensitively within their directory (exact lowercase still wins
when both casings exist; no recursive fuzzy matching).
Root index files that link with the article-root prefix included
([[wiki/concepts/Index]] when wiki/ is the detected article root) never
matched article ids relative to that root, so category membership
missed and every node landed in the Other layer. Category lookup and
wikilink resolution now try such targets both as written and with the
root prefix stripped; links outside the article root stay unresolved.
Fixes#342
A batch file that parses cleanly but adds nothing to the merge is
indistinguishable from a silent partial merge (the #484 report: a
79-node part vanished from one run and was only caught because the
Phase 3 LLM reviewer happened to notice). Emit a Warning: on stderr per
empty file and re-emit the list in the phase report so the data-loss
signal survives to the review step, without hard-failing legitimate
empty batches.
Fixes#484
merge-subdomain-graphs.py silently dropped contains_flow/flow_step/
cross_domain edges whenever an endpoint node wasn't in the merged set,
and the drop detail lived only in the in-process report — since
subdomain graph files are cleaned up after assembly, those hierarchy
edges were gone permanently with no trace.
Structural drops now emit Warning: lines in the merge report, every
dropped edge is persisted to .understand-anything/merge-report.json,
and the next merge run re-injects previously dropped structural edges
so they resolve once the missing endpoint's subdomain has arrived.
Fixes#529
install.sh and the README already list `vibe` as a supported install target, but the PowerShell installer did not include it in `$Platforms`. As a result, `install.ps1 vibe` would fail with `Unknown platform: vibe`, and the interactive Windows installer menu would not show Vibe CLI.
Add `vibe` to the PowerShell platform table, keep the order aligned with install.sh, and add a regression test that checks installer platform ids, link styles, target directories, and README references stay in sync.
The domain-context scanner and a merge test read files via
Path.read_text(errors="replace") with no explicit encoding. pathlib's
read_text/write_text fall back to locale.getpreferredencoding(False) when
encoding is omitted; on Windows, absent Python UTF-8 mode (PEP 540), that is
the ANSI codepage (cp1252 / cp936 / cp932), not UTF-8.
extract-domain-context.py (lines 126, 211, 270, 315, 418) reads source files,
.gitignore, and metadata, then writes domain-context.json. On Windows the
reads decode UTF-8 source against the wrong codepage, silently mojibaking CJK
comments, accented identifiers, em-dashes, and translated README text into the
context fed to the domain-analyzer agent. errors="replace" does not catch
this: legacy codepages decode nearly every byte to a *wrong* character rather
than raising, so corruption is silent.
test_merge_batch_graphs.py (lines 986, 1105) reads assembled-graph.json back
for assertions; merge-batch-graphs.py writes it with ensure_ascii=False, so it
can contain raw non-ASCII that the unencoded read would mojibake on Windows.
Both now pass encoding="utf-8", matching every other Python script in the repo
(merge-batch-graphs.py, merge-subdomain-graphs.py, parse-knowledge-base.py,
merge-knowledge-graph.py). No behavior change on Linux/macOS, where the locale
default is already UTF-8.
Relates to the Windows-compat reports #262, #340.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses the regression flagged by ZebangCheng on #346: under the
parallelised `buildResolutionContext`, `loadTsConfigs` /
`loadGoModules` / `loadPhpAutoloads` ran concurrently but each wrote
warnings to stderr inline as it iterated read results, so a fixture
with both a malformed `tsconfig.json` and a malformed `composer.json`
could emit `composer, tsconfig` instead of the pre-PR `tsconfig,
composer` depending on I/O timing.
Each loader now buffers its warnings into a returned array and the
caller drains them in canonical order (tsconfig → go → php) after
`Promise.all`, restoring byte-identical stderr output. Added a
regression test that fixtures both malformed configs and asserts the
tsconfig warning precedes the composer warning in stderr.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Fixes the silent near-edgeless-graph regression on any modern ESM
TypeScript project. Reported in #294 with full repro + root-cause
analysis.
### Why this matters
Under `moduleResolution: NodeNext` (or `Node16` / `Bundler` with
explicit extensions — the default for new TS-ESM projects since 2023),
TypeScript does NOT rewrite import specifiers during compilation:
// src/index.ts — real, idiomatic NodeNext source
import { x } from './config.js'; // on disk: config.ts
Before this fix, `probeWithExtensions` only tried APPENDING extensions
to the import specifier:
'./config.js' → not in fileSet
'./config.js.ts', './config.js.tsx', './config.js.js', ... → all miss
→ returns null → edge dropped at merge as dangling
Net result on the reporter's repro: a knowledge graph with hundreds of
file nodes and almost no `imports` edges between them — silently
removing exactly the dependency structure the graph is meant to show.
### Fix
New `NODENEXT_REWRITES` table maps each compiled-output extension to
the TypeScript source extensions that could have produced it:
.js → [.ts, .tsx, .js, .jsx]
.jsx → [.tsx, .jsx]
.mjs → [.mts, .mjs, .ts]
.cjs → [.cts, .cjs, .ts]
`probeWithExtensions` now applies the rewrite when the import already
ends with one of these extensions and no such file exists on disk. The
rewrite runs BEFORE the legacy append-extensions loop — otherwise
`./foo.js` would generate the nonsense candidate `foo.js.ts` and the
append loop would never reach the actual `foo.ts`.
### Disambiguation
If both `config.ts` and `config.js` exist on disk (rare, but possible
during a partial migration), `import './config.js'` still resolves to
the .js — that's an exact-disk match and what NodeNext compilation
actually does. The rewrite only kicks in when the .js doesn't exist.
### Tests
6 new tests in `test_extract_import_map.test.mjs`:
- The main #294 case (`.js → .ts`)
- `.jsx → .tsx` and `.mjs → .mts` rewrites
- Disambiguation when both `.ts` and `.js` exist on disk
- Pure-JS projects still work (real `.js → .js` imports)
- Historical no-extension probes unaffected
- Missing files still return null (rewrite can't invent targets)
Total: 202 tests passing (was 196).
Closes#294