Adds a Community section near the end of README (English + 7 localized
variants) and a CommunityVideo component on the homepage embedding the
YouTube walkthrough by Better Stack. Section invites future video / blog /
tutorial contributions to be featured here.
import.meta.url resolves through symlinks but pathToFileURL(process.argv[1])
preserves them, so extract-structure.mjs silently exited 0 without writing
output when invoked via the plugin's symlinked install path — the documented
Claude Code / Copilot CLI layout. Compare both sides via realpathSync and add
a post-write existence assertion plus caller-side guidance in the agent.
Closes#162
- Replace hardcoded hex with var(--color-*) and CJK font stacks with
var(--font-sans) / var(--font-heading) so the overlay tracks the theme
picker and uses the project's typography (DM Serif Display).
- Lift dismiss/visibility state to Dashboard (shouldShowOnboarding +
showOnboarding useState + dismissOnboarding callback). Gate the
Suspense mount with a boolean so the lazy chunk is only fetched on
first visit, matching the PathFinderModal / KeyboardShortcutsHelp
mount pattern.
- Add capture-phase Escape handler (stopPropagation prevents the global
shortcut chain from also firing) and role="dialog" / aria-modal /
aria-labelledby on the card for screen readers.
Schema (packages/core/src/schema.ts) defines node types: file, function,
class, module, concept, config, document, ... The welcome step body only
listed "file, concept, entity, claim", missing the most common code-side
types. Updated all 6 locales (en / zh / zh-TW / ja / ko / ru) to mention
file / class / function from code plus concept / entity / claim from the
knowledge wiki.
- Replace hardcoded Chinese strings with t.onboarding.* via useI18n
- Add `onboarding` namespace to en / zh / zh-TW / ja / ko / ru locales
- Inject @keyframes ua-fade-in (was referenced in inline style but
never defined, so the overlay popped in instead of fading)
Ships the fingerprints baseline fix (e7af9ae): every install since
2.7.0 had a broken Phase 7 step 2.5 that threw TypeError on the first
/understand run and left fingerprints.json empty/missing, which made
every subsequent auto-update escalate to FULL_UPDATE. This release
replaces the LLM-written script with a bundled build-fingerprints.mjs
and reorders Phase 7 to write fingerprints before meta.json.
Anyone upgrading from 2.7.0–2.7.2 should re-run /understand --full
to regenerate a valid baseline.
The Phase 7 step 2.5 code example in SKILL.md called
buildFingerprintStore() with 2 arguments, but the real signature
requires 4 (projectDir, filePaths, registry: PluginRegistry,
gitCommitHash: string). It also omitted the required
`await TreeSitterPlugin.init()`. Any LLM following the example
threw TypeError on `registry.analyzeFile()` and never produced a
baseline — which is why fingerprints.json never existed in a usable
form after a fresh /understand, and is the root cause behind
issue #152's "every auto-update escalates to FULL_UPDATE" cascade.
Replace the LLM-written script with a bundled `build-fingerprints.mjs`
that mirrors `extract-structure.mjs`: resolves @understand-anything/core
via createRequire, initializes TreeSitterPlugin + PluginRegistry
correctly, calls buildFingerprintStore with all four arguments, and
persists via saveFingerprints. Smoke-tested on this repo (3 files,
correct functions/classes/imports extracted).
Reorder Phase 7 so fingerprints are written BEFORE meta.json. If
fingerprint generation fails, the new step explicitly says to abort
Phase 7 — meta.json must not advance without a valid baseline, or
the next auto-update sees a fresh commit hash with no fingerprints
and classifies every file as STRUCTURAL.
Affects every install since 2.7.0 (when the broken example was
introduced). Users running /understand --full on 2.7.3+ will get
a usable fingerprints.json on the first try.
Ships two auto-update fixes:
- #153 (5304ff0): apply .understandignore in Phase 0 so user-excluded
paths don't inflate the structural-change count.
- #152 (dd8b724): LOAD-PATCH-SAVE template for Phase 3d fingerprints
merge, with guard against silent load failure.
Fixes#152. Phase 3d step 3 instructed the LLM to "merge with existing
fingerprints (keep unchanged files as-is)" but the prose was vague
enough that the LLM-written script frequently wrote only the freshly
re-analyzed batch entries to fingerprints.json, discarding every other
file's fingerprint. The next auto-update saw N-batch_size files with
no stored fingerprint → classified as STRUCTURAL → exceeded the 30-file
threshold → FULL_UPDATE permanently, burning hundreds of thousands of
tokens on every subsequent commit.
Replace the four-bullet description with an explicit LOAD-PATCH-SAVE
script template:
1. LOAD ALL existing entries from fingerprints.json (never skip).
2. PATCH or REMOVE each path in filesToReanalyze (inline deletion
handling so the spec doesn't need a separate deletedFiles list).
3. GUARD: if the file existed and was non-empty but loaded as {},
abort the write — silent load failure would otherwise clobber
every fingerprint.
4. SAVE the full dict back.
The reporter's dry-run showed this restores 81/97 files to COSMETIC
classification on their project (zero LLM tokens) instead of all 97
incorrectly forced into STRUCTURAL.
Note: a related ordering bug exists in skills/understand/SKILL.md
Phase 7 (meta.json written before fingerprints.json — silent failure
in step 2.5 leaves stale fingerprints). That's a separate fix in a
different file and is intentionally not bundled here.
Fixes#153. Phase 0 step 7 filters changed files to source extensions
only and never reads `.understandignore`, so files in user-excluded
paths (migrations, vendored code, tests) count as structural changes
and can spuriously escalate the action to FULL_UPDATE. The reporter
saw 50 → 38 structural files after applying their ignore patterns
(below the 30-file FULL_UPDATE threshold, ARCHITECTURE_UPDATE would
have sufficed).
Add step 9 that delegates to `createIgnoreFilter` from
`@understand-anything/core` via $CLAUDE_PLUGIN_ROOT. Same code path
as /understand's project-scanner Step 2.5, so the auto-update honors
the exact same patterns (hardcoded defaults + user .understandignore
files at both standard locations + `!` negation semantics).
If $CLAUDE_PLUGIN_ROOT can't be resolved, fail loud rather than
silently skipping — a silent skip reproduces the original bug.
Ships the fixes that landed on main after the 2.7.0 cut:
- #139 (f3ea1a3): understand-knowledge — Windows path separators in
wikilink resolution + omit empty `category` so KnowledgeMetaSchema's
`z.string().optional()` no longer drops every article node. Closes#151.
- #147 (fafb888): understand-domain — resolve $PLUGIN_ROOT at runtime
for symlink installs.
- f71bad5: understand — persist canonical edge direction during merge,
fixing the 153k auto-correction cascade. Closes#140.
Fixes#140. merge-batch-graphs.py already defaulted missing `direction`
to "forward" when building the dedup key, but the value was never written
back onto the edge — so the generated knowledge-graph.json shipped without
the field and the dashboard validator emitted one auto-correction per
edge (153k on the reporter's Go codebase).
Mirror the dashboard schema validator at packages/core/src/schema.ts:
lowercase the value, map "both"/"mutual" → "bidirectional", fall back to
"forward" for missing or invalid values, and persist the result onto the
edge before it enters edges_by_key. This also closes a latent dedup leak
where "Forward" and "forward" (or "both" and "bidirectional") would have
produced separate dedup keys.
A second pill mirroring the Enterprise one, linking to https://lum.is-a.dev/.
Same gold/amber styling, 0.6rem gap below the Enterprise pill so the two read
as one "contact" group, anim-5 so both pills enter together. Full mobile
breakpoint mirror.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Includes since 2.6.3: Hermes (#91), Cline (#116), KIMI CLI (#134)
platform support; dashboard ACCESS_TOKEN env override; README cleanup
(slogan rewrite, drop outdated overview gifs, move thanks to footer).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove invalid allowBuilds from pnpm-workspace.yaml (use onlyBuiltDependencies in root package.json)
- Use data-testid for search input selector (fixes / keyboard shortcut for non-English locales)
KIMI CLI scans ~/.kimi/skills/ (its brand path) for SKILL.md per the
official skill discovery spec, so distribution follows the same
folder-symlink pattern as Hermes / Cline / OpenClaw. install.sh and
install.ps1 register the `kimi` platform; README × 7 updated for the
one-line install title, <platform> values, and compatibility table.
Closes#134
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cline natively scans ~/.cline/skills/ for SKILL.md, so distribution
follows the same folder-symlink pattern as Hermes / OpenClaw /
Antigravity. install.sh and install.ps1 register the `cline` platform;
README × 7 (English + 6 locales) updated for the one-line install
title, <platform> values, and the compatibility table.
Closes#116
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Honor UNDERSTAND_ACCESS_TOKEN if set, falling back to the random 16-byte
hex token. Lets the dev token survive across server restarts so shared
dashboard URLs don't rot, and makes the auth path easier to script in
tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P1: MobileLayout was missing I18nProvider wrapper, causing useI18n
to throw error on mobile devices. Now both desktop and mobile
layouts are wrapped with I18nProvider.
P2: SKILL.md used 'language' key but Dashboard reads 'outputLanguage'.
Fixed config.json key name to match ProjectConfig type definition.
All tests passed:
- Core: 670 tests
- Dashboard: 42 tests
Adds --language parameter to /understand command to generate knowledge
graph content in user-specified language.
Changes:
- Update argument-hint and Options documentation in SKILL.md
- Add language parsing logic in Phase 0 (language normalization,
config persistence, LANGUAGE_DIRECTIVE template)
- Inject language directive into agent dispatch prompts for all
content-generating phases (Phase 1-5)
- Add language directive handling instructions in agent definitions
- Create locales/ directory with template files for:
- English (en.md) - default
- Chinese Simplified (zh.md)
- Chinese Traditional (zh-TW.md)
- Japanese (ja.md)
- Korean (ko.md)
Locale files provide language-specific guidance for:
- Tag naming conventions
- Summary writing style
- Technical term handling
- Layer name translations
Closes#141
- install.sh / install.ps1: register `hermes` platform (folder-style
symlink to ~/.hermes/skills/understand-anything/). No new directories
or plugin manifests needed — Hermes scans skills via os.walk(...,
followlinks=True) and reads SKILL.md frontmatter directly.
- README × 7 (English + 6 locales): one-line install title, `<platform>`
values, and compatibility table updated for both Hermes and Vibe CLI
(locale READMEs were missing Vibe).
- README × 7: drop top-of-page Star History Rank badge and the TIP-style
thanks blockquote; rewrite the slogan into a longer line about quietly
teaching how the pieces fit; move a single italic thanks line between
Star History and the MIT footer.
- README × 7: remove the two outdated overview-*.gif blocks. The
Features section already opens with a NOTE callout linking to the live
demo, which serves as the visual entry instead.
- Delete assets/overview-{structural,domain}.gif (8.1 MB of orphaned
binaries) from the working tree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Issue 1: setDetailLevel now resets showFunctionsInClassView so the fn
toggle doesn't resurrect when re-entering class view after a file-view
round-trip.
- Issue 2: detail-level toolbar (Files/+Classes/fn) now gated on
viewMode !== "domain" so it doesn't render in domain view where it has
no effect.
Add a 5-step modal that walks new users through the dashboard's core operations
on first visit. Auto-hides via localStorage after dismiss; can be force-shown
with `?onboard=force` for screenshots and demos.
## What it teaches
1. What the graph represents (entities/relations from code or wiki)
2. Three view buttons (Overview / Learn / Deep Dive) — each answers a different
question
3. Search + node click — find by name, click for details panel
4. Layer switch + Project Tour — drill into a category, or follow a guided
walkthrough
5. Hidden features (Filter / Export / Path / Theme) and Shift+? for keyboard
shortcuts
## Design
- Inline styles, no extra CSS file — easier to land in the existing structure
- Lazy-loaded via Suspense like the other modals (KeyboardShortcutsHelp,
PathFinderModal) so it ships in a separate chunk
- Architectural-minimalism dark palette consistent with the existing dashboard:
off-black surface, warm accent (#c8a882), Noto Serif SC headings, generous
whitespace
- localStorage key `ua-onboarding-dismissed-v1` — versioned so future content
changes can re-trigger
- Accessible: keyboard-navigable buttons, click-outside to close (without
remembering dismiss), explicit "不再显示" / "Skip" affordance
## Tested
- Windows 11 + Chrome via Playwright: 5 steps render, progress bar tracks,
prev/next/dismiss/finish all work, localStorage persists dismiss across
reloads, `?onboard=force` re-shows for testing
- No new dependencies (uses React 19 hooks already present)
- No changes to data flow, store, or other components — strictly additive
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four single-line fixes that make `/understand-knowledge` work end-to-end on Windows.
## Root causes
1. Path separator mismatch (3 occurrences in parse-knowledge-base.py)
- `str(rel.with_suffix(""))` returns backslash-separated stems on Windows
(e.g. `entities\foo`), while wikilinks always use forward slashes
(`[[entities/foo]]`).
- Result: name_map keys and article_ids hold `entities\foo`, while
`resolve_wikilink()` looks up `entities/foo` -> 100% miss.
- Tested on Windows 11 + Python 3.14: 151/151 wikilinks unresolved,
0 edges built from wikilinks.
Fix: use `rel.with_suffix("").as_posix()` in all three places
(lines 235, 316, 330 on main).
2. Null vs missing field (1 occurrence)
- `"category": category or None` writes `null` when category is empty.
- `KnowledgeMetaSchema.category` in packages/core/src/schema.ts is
`z.string().optional()`, which accepts `undefined`/missing but
rejects `null`.
- Result: every article node fails GraphNodeSchema validation in the
dashboard (`Invalid input: expected string, received null`),
all nodes get dropped, dashboard renders empty.
Fix: omit the field when empty using dict spread.
## After
Tested with a 27-article Karpathy wiki on Windows:
- before: 151 unresolved wikilinks, 0 edges, 0 nodes rendered in dashboard
- after: 0 unresolved, 110 wikilink edges + 23 LLM-implicit edges,
all 53 nodes (article/topic/entity/claim) render correctly
No behavior change on macOS/Linux: `as_posix()` is a no-op when the OS
already uses `/`, and dict spread produces the same key as the previous
truthy branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When /understand or /understand-domain runs from a CWD inside an
ephemeral git worktree (the default for parallel-agent / isolation
sessions), every output file goes to the worktree path. Claude Code
deletes the worktree on session end, taking knowledge-graph.json,
domain-graph.json, meta.json, intermediate batches and ~hundreds of K
of analysis tokens with it.
Resolve PROJECT_ROOT through a worktree check before any output:
compare git rev-parse --git-dir against --git-common-dir; in a normal
checkout (and in a submodule) they're the same path, in a worktree they
differ and parent(--git-common-dir) is the main repo root.
UNDERSTAND_NO_WORKTREE_REDIRECT=1 opts out for the rare per-worktree
case.
- skills/understand/SKILL.md Phase 0 step 1: add the redirect after
PROJECT_ROOT is set from $ARGUMENTS or CWD, so an explicit arg path
is also rescued from a worktree but can be opted out of.
- skills/understand-domain/SKILL.md: add an explicit Phase 0 (it
previously inferred "current project" implicitly), then thread
$PROJECT_ROOT through Phases 2-5 so subsequent steps honor the
redirect.
- New worktree-redirect.test.mjs: 5 vitest cases covering main repo,
worktree root, worktree subdir, opt-out env var, and non-git CWD.
Mirrors the bash snippet inline (no shared lib in this repo).
Submodule false-positive ruled out by probe — submodules see git-dir
== git-common-dir (both point at <super>/.git/modules/<name>).
Codex P2: link_tests Pass 1 dropped duplicate (production, test) pairs
purely by arrival order — when two batches both emitted a tested_by
edge for the same pair with different confidences (0.3 vs 0.9), the
edge that happened to iterate first won. The general Step 6 deduper
at line 762 mirrors `weight > existing.weight` semantics but it only
ever saw one of the duplicates, so it couldn't rescue the heavier one.
Refactor Pass 1 to mirror Step 6's weight comparison locally:
- Track `pair_to_idx` mapping each kept (prod, test) pair to its
slot in the compacted edges list. On a duplicate, look up the
existing kept edge and compare weights; if the new edge is
strictly heavier, swap (if needed) and replace the slot. Tie or
lighter → drop the new edge.
- Defer the swap operation until we know an edge will survive — no
point canonicalizing a doomed duplicate.
- Track surviving swap pairs in a separate `swapped_pairs` set so
the `swapped` counter reflects the FINAL output, not the wasted
work on edges that were later replaced. This means: replacing a
swapped edge with a heavier canonical one drops the swap from
the count; replacing a canonical edge with a heavier swapped one
adds it.
- Extract the swap-in-place mutation into `_swap_tested_by_in_place`
so it can be invoked from both code paths.
Five new unit tests cover all four weight-vs-direction combinations
plus a tie case (existing test_drops_duplicate_canonical_edges, which
still passes — tie → keep first, no swap counted).
microservices-demo regression check unchanged: 7 → 7 edges, 3 swapped,
0 dropped, 7 tagged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Strip-and-rederive (current PR behaviour) drops real coverage signal on
projects whose test layout doesn't match a naming convention. On the
Google microservices-demo the LLM had emitted 7 valid tested_by edges
(3 with inverted direction); the strip pass dropped them and the path-
convention rederive could only re-pair 4 of them. Net: 7 → 4 edges,
3 production files lost their tested signal.
Replace strip-and-rederive with two-pass swap-then-supplement:
Pass 1 — walk LLM tested_by edges. Canonical (production → test)
edges pass through unchanged. Inverted (test → production) edges are
flipped in place; description gets a `[direction corrected]` audit
marker. Edges with no recoverable meaning (test↔test, prod↔prod,
orphan endpoint, duplicate pair) are dropped.
Pass 2 — for tests not yet paired by Pass 1, walk path-convention
candidates and emit a fresh production → test edge for the first
match. Pairs already covered by Pass 1 are skipped.
Tagging is consolidated into a final pass over all canonical edges so
production nodes get the "tested" tag whether the edge came from
Pass 1 (canonical / swapped) or Pass 2 (supplement).
Multi-language audit of production_candidates revealed three real-world
gaps surfaced by re-checking microservices-demo and common project
layouts:
- JS/TS walk-out only handled `__tests__/`. Extended to also walk out
of `<dir>/test/`, `<dir>/spec/`, and `<dir>/tests/` (some JS/TS
projects use these instead of __tests__/).
- Python walk-out only handled top-level `tests/`. Added in-package
`<pkg>/tests/test_<name>.py` → `<pkg>/<name>.py` (Django app style
and any project that colocates tests with the package).
- C# only had sibling fallback. Added two new mirrors:
* `<svc>/tests/X.cs` ↔ `<svc>/X.cs` and `<svc>/src/.../X.cs`
(microservices-demo cartservice exact layout).
* `<App>.Tests/Foo/BarTests.cs` ↔ `<App>/Foo/Bar.cs`
(.NET sibling-project convention).
Go is intentionally not changed — the "one _test.go covers several
.go files in the same package" pattern is now solved by Pass 1
(swapping LLM edges), not by trying to invent multi-pair path heuristics.
The file-analyzer prompt is updated: the `tested_by` row is restored
in the schema table because we now use those edges as evidence (Pass 1
canonicalizes the direction). The note explains direction will be
auto-corrected so the LLM doesn't need to be defensive about it.
link_tests now returns a 4-tuple (added, dropped, tagged, swapped);
the merge_and_normalize report distinguishes "edges produced
(supplement)" from "edges flipped" from "edges dropped".
Real-world validation on microservices-demo:
before: 7 tested_by edges, 3 inverted, 0 tagged
after PR: 4 tested_by edges, 0 inverted, 4 tagged ← strip-and-rederive
this: 7 tested_by edges, 0 inverted, 7 tagged ← swap-then-supplement
Tests: 47 pass (was 37). New cases cover all swap branches, the
shippingservice "one test, many sources" regression, and each new
language pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add detailLevel state ("file" | "class") to separate architecture-level
file dependencies from code-structure class views. File view shows only
file nodes and file→file edges (imports/depends_on), eliminating ~85%
of nodes/edges that previously caused severe zoom/pan lag. Class view
adds class nodes via contains edges with an optional function toggle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a `headingFont` option to ThemeConfig that lets users switch
heading typography between Serif (default), Sans, and Mono via the
existing Theme Picker UI.
- New `--font-heading` CSS custom property (defaults to `--font-serif`)
- Theme engine applies the selected font on config change
- ThemePicker gets a "Heading Font" toggle section
- All 15 component files updated from `font-serif` to `font-heading`
- Selection persists in localStorage alongside other theme settings
- Backwards-compatible: existing configs without `headingFont` default to serif
Closes#120