Commit Graph

509 Commits

  • chore(release): bump version to 2.7.3
    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.
  • fix(skills/understand): bundle build-fingerprints.mjs and reorder Phase 7
    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.
  • chore(release): bump version to 2.7.2
    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.
  • fix(hooks/auto-update): make fingerprints merge unambiguous in Phase 3d
    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.
  • fix(hooks/auto-update): apply .understandignore exclusions in Phase 0
    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.
  • chore(release): bump version to 2.7.1
    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.
  • fix(skills/understand): persist canonical edge direction during merge
    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.
  • Merge pull request #139 from nieao/fix/windows-compat
    fix(understand-knowledge): Windows path + zod schema null compatibility
  • Merge pull request #147 from rustanacexd/fix/understand-domain-plugin-root
    fix(skills/understand-domain): resolve plugin root for agent prompt loading (#146)
  • fix(skills/understand-domain): resolve plugin root at runtime for symlink installs
    Fixes #146. Ports the $PLUGIN_ROOT resolution pattern from understand/SKILL.md
    to understand-domain/SKILL.md, including:
    - Symlink resolution for ~/.agents/skills/understand-domain
    - Copilot fallback for ~/.copilot/skills/understand-domain
    - Detailed error diagnostics listing all checked paths
    - Phase 4 agent prompt path now uses $PLUGIN_ROOT/agents/domain-analyzer.md
  • docs(homepage): add author homepage link below enterprise contact
    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>
  • chore(release): bump version to 2.7.0
    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>
  • Merge pull request #145 from zhushen12580/fix/code-review-feedback
    docs: Add --language parameter documentation to all READMEs
  • Merge pull request #142 from zhushen12580/feature/language-parameter
    feat: Add --language parameter for localized content generation
  • fix: Address code review feedback for PR #142
    - 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)
  • feat(install): add KIMI CLI platform support (#134)
    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>
  • feat(install): add Cline platform support (#116)
    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>
  • feat(dashboard): allow ACCESS_TOKEN override via env var
    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>
  • fix: Wrap MobileLayout with I18nProvider; use outputLanguage key in config
    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
  • feat(dashboard): Add i18n support for localized UI text
    - Add outputLanguage field to ProjectConfig type
    - Create /config.json endpoint in vite.config.ts
    - Build locale files for 5 languages (en, zh, zh-TW, ja, ko)
    - Add I18nProvider context and useI18n hook
    - Update 5 components (ProjectOverview, NodeInfo, FileExplorer, FilterPanel, PersonaSelector)
    - Dashboard reads language from config.json and displays localized UI
    
    All tests passed:
    - Core: 670 tests
    - Dashboard: 42 tests
  • feat: Add --language parameter for localized content generation
    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
  • feat(install): add Hermes platform; refresh README (#91)
    - 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>
  • Merge pull request #138 from voidborne-d/fix/worktree-paths
    fix(skills): redirect PROJECT_ROOT out of git worktrees (closes #133)
  • Merge pull request #132 from Xingkai98/feat/dashboard-file-class-views
    feat(dashboard): add file/class dual-view toggle to reduce graph clutter
  • fix(dashboard): reset fn toggle on view switch; hide detail toolbar in domain view
    - 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.
  • Merge pull request #124 from tipich/fix/windows-pnpm10-compat
    fix(skill): make /understand work on Windows + pnpm 10
  • feat(dashboard): first-visit onboarding overlay
    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>
  • fix(understand-knowledge): Windows path + zod schema null compatibility
    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>
  • fix(skills): redirect PROJECT_ROOT out of git worktrees (#133)
    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>).
  • Merge pull request #122 from Lum1104/feat/issue-113-tested-by-coverage
    feat: deterministic tested_by edges + dashboard badge (#113)
  • fix(merge): keep max-weight tested_by edge in Pass 1 dedup (#113)
    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>
  • feat(merge): swap-then-supplement tested_by linker (#113)
    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>
  • feat(dashboard): add file/class dual-view toggle to reduce graph clutter
    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>
  • Merge pull request #127 from 3ng7n33r/main
    Add mistral vibe cli to install script
  • feat: customizable heading font via theme settings (#121)
    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
  • fix(merge): recover imports edges file-analyzer batches drop
    A controlled-experiment audit on a 1240-file Python project (opensre)
    showed that 27.2% of resolved-internal imports never made it from
    project-scanner's `importMap` into the final knowledge graph. Of the
    404 source files with internal imports, 91 ended up with ZERO imports
    edges in the graph despite their `file:` node being present (consistent
    with main-session orchestrator dropping the entry from `batchImportData`
    during batch construction), and 104 had partial coverage (consistent
    with file-analyzer agent dropping rows during edge enumeration).
    GitHub issue #128 reported the same failure mode at 16-21% on a Go
    monorepo.
    
    The fix has two layers:
    
    1. `merge-batch-graphs.py` now runs a deterministic recovery pass
       after merge: for every `(source, target)` in scan-result.json's
       `importMap` whose source `file:` node exists in the assembled graph
       and whose target `file:` node also exists, emit an `imports` edge
       if the batches didn't already. Recovered edges are tagged
       `recoveredFromImportMap: true` so downstream consumers can audit
       which edges came from the deterministic source vs. agent emission.
       The merge report logs the recovered count plus how many importMap
       entries were skipped because their source/target had no graph node.
    
    2. `file-analyzer.md` rewrites the imports edge rule to demand 1:1
       emission with a self-check: "the number of `imports` edges in your
       output MUST equal `sum(batchImportData[file].length)` across the
       batch's code files". This drives the agent to enumerate every row
       instead of summarizing — recovery should report 0 when this works.
    
    Tests: +6 cases covering the recovery path — drops, no-double-emit,
    missing source/target nodes, missing scan-result.json (incremental
    update), and self-import suppression. 770 passing (was 764).
    
    Bumps version to 2.6.3 across the five tracked manifests.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  • fix(skill): make /understand work on Windows + pnpm 10
    Two bugs blocked /understand from running properly:
    
    1. extract-structure.mjs:34,37 — `await import()` was called with raw
       absolute paths returned by `require.resolve()` and `path.resolve()`.
       On Windows those start with "C:\..." which Node 24's ESM loader
       parses as URL scheme "C:" and rejects with
       ERR_UNSUPPORTED_ESM_URL_SCHEME. Wrap both with `pathToFileURL().href`
       so the loader receives a proper file:// URL.
    
    2. tree-sitter native build scripts were skipped at install time
       because pnpm 10 blocks postinstall scripts by default. Added the
       parser packages plus esbuild and sharp to `pnpm.onlyBuiltDependencies`
       so they compile during `pnpm install` and `analyzeFile` / fingerprint
       generation actually work.
    
    Without these, file-analyzer subagents fall back to direct file reads
    and the importMap stays empty across all batches, which forces
    assemble-reviewer to grep-recover hundreds of cross-batch edges by
    hand. They also block the incremental update path entirely because
    fingerprint generation crashes inside `buildFingerprintStore`.
    
    Verified end-to-end on a 1190-file Laravel codebase: tree-sitter PHP
    parses Booking.php into 18 typed functions, fingerprint baseline
    builds in 1.56s, change detection correctly classifies cosmetic
    vs structural diffs.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  • fix(pipeline): close 12 sources of silent data loss in graph extraction
    A deep audit of the project-scanner → file-analyzer → merge pipeline
    turned up a wide range of silent data-loss bugs. Each one alone is
    small; together they were producing graphs with very few import edges,
    missing sub-file nodes for non-code formats, and inconsistent metrics.
    
    Root-cause fixes (high impact):
    
    - project-scanner.md: extend import-pattern table to resolve absolute
      imports for Python (`from a.b.c import x`), TS/JS (tsconfig.json
      paths/baseUrl aliases), Java/Kotlin (`com.foo.Bar` ↔ file paths),
      Ruby (`require 'foo/bar'` load-path), PHP (composer PSR-4 namespaces),
      and C/C++ (`#include` headers). Was relative-only, which produced
      empty importMap entries for the majority of real projects.
    - project-scanner.md: add `.ps1`, `.bat`, `.cmd`, `.jsonc` to language
      table; require non-null `language` field with an explicit fallback.
    - file-analyzer.md: document `sections`, `definitions`, `services`,
      `endpoints`, `steps`, `resources` in the extraction-output schema and
      spell out the sub-file node-creation rules per category. Was missing,
      so per-table / endpoint / resource nodes were never created from
      SQL / OpenAPI / Terraform / K8s / Dockerfile parser output.
    - file-analyzer.md: add explicit source-reading fallback rules for
      PowerShell, Batch, Bash, Swift, Kotlin (no tree-sitter coverage).
    - yaml-parser: declare `kubernetes`, `docker-compose`, `github-actions`,
      `openapi` languages so files the language-registry tags with those
      ids actually get section extraction. Recognize quoted top-level keys
      (e.g. `"on":` in GitHub Actions). Emit one section per entry for
      array-root YAML documents.
    - json-parser: declare `json-schema`, `openapi`; add `stripJsoncSyntax`
      helper that removes line / block comments and trailing commas before
      parse so `.jsonc` files (wrangler, tsconfig with comments) parse cleanly.
    - shell-parser: declare `jenkinsfile`. Tighten function-detection regex
      to require a reachable `{` brace so `name() echo hi` and patterns
      appearing inside heredocs are no longer false-positives.
    - markdown-parser: track fenced-code-block state and skip headings
      inside ``` / ~~~ blocks (`# install` shell comments were being
      emitted as level-1 sections).
    - merge-batch-graphs.py: add `article`, `entity`, `topic`, `claim`,
      `source` to VALID_NODE_PREFIXES and TYPE_TO_PREFIX so knowledge-base
      node types stop being flagged unknown / coerced to `file:`. Add
      `direction` to the edge dedup key so `forward` and `bidirectional`
      variants of the same (src, tgt, type) don't overwrite each other.
      Use a placeholder in bare-id fallback when `filePath` is missing on
      function/class nodes so unrelated `parse()` functions don't merge.
    - typescript-extractor: actually compute `isDefault` for default
      exports (was always emitted as `false` from buildResult).
    - extract-structure.mjs: match `wc -l` semantics for `totalLines` so
      the scanner's `sizeLines` and the extractor's `totalLines` agree on
      POSIX text files. Filter the parser-imports fallback to relative-only
      so `importCount` semantics stay *internal-import* whether the scanner
      resolved them or not. Drop unused `isCode` local.
    
    Tests: +19 cases covering JSONC parsing, markdown fenced-code skip,
    YAML quoted-keys / array-root, shell function false-positives,
    extract-structure import fallback semantics + totalLines off-by-one.
    764 passing (was 745).
    
    Bumps version to 2.6.2 across the five tracked manifests.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  • fix(file-analyzer): preserve language and fall back when imports unresolved
    Two bugs surfaced when analyzing Python projects that use absolute imports:
    
    - The dispatch prompt (SKILL.md) and file-analyzer agent omitted the
      per-file `language` field, so `extract-structure.mjs` received null and
      passed it through to the graph.
    - `extract-structure.mjs` used `if (importPaths)` to decide whether to
      trust pre-resolved imports. Empty arrays are truthy, so files where the
      project scanner could not resolve any imports (e.g. Python absolute
      imports) clobbered the parser's import count with 0, never falling
      back to tree-sitter's own analysis.
    
    Bumps plugin version to 2.6.1 across the five tracked manifests and adds
    unit tests for `buildResult` covering language pass-through and the
    importCount fallback paths. To make the script testable, `buildResult` is
    now exported and the CLI invocation is guarded so importing the module
    no longer triggers `main()`.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  • fix(merge): coerce malformed tags before adding "tested" (#113)
    Codex flagged that prod_node.setdefault("tags", []) returns the existing
    value when the key is present, so a raw LLM batch with tags=None or
    tags="some string" would crash the whole merge on the next "tested" not
    in tags membership check.
    
    The TypeScript autoFixGraph normalizer that handles this case runs
    downstream of merge-batch-graphs.py, not before it, so the Python side
    has to defend itself. Coerce non-list tags to a fresh [] before the
    membership/append.
    
    Regression test exercises None / comma-string / single-string / int /
    dict inputs.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  • Merge pull request #117 from DenisBalan/patch-1
    Fix dashboard URL format in vite.config.ts
  • Merge pull request #123 from Lum1104/feat/unified-install-script
    feat(install): unify per-platform installers into install.sh / install.ps1
  • fix(install): address Codex review — guard reparse deletes and robust uninstall
    - install.ps1 (P1): refuse to delete real files/directories; only remove
      paths that are actual junctions/symlinks. Closes a data-loss path where
      Cmd-Uninstall would Remove-Item -Recurse a pre-existing user directory at
      ~/.understand-anything-plugin (Cmd-Install correctly skipped touching it,
      but uninstall did not). Cmd-Uninstall and Unlink-Skills now both go
      through Remove-Reparse, which uses DirectoryInfo.Delete() so a junction's
      target is never followed.
    
    - install.sh (P2): --uninstall no longer exits early when the checkout has
      been deleted. The per-skill unlinker falls back to scanning the target
      dir for stale symlinks pointing into the plugin tree, so users can clean
      up after manually rm -rf'ing the checkout.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>