Commit Graph

345 Commits

  • Merge pull request #419 from chengyongru/feat/nanobot-support
    feat(install): add Nanobot platform support
  • Merge pull request #347 from tirth8205/feat/kotlin-extractor
    feat(core): add Kotlin structural analysis via tree-sitter
  • Merge pull request #359 from tirth8205/fix/import-resolver-nodenext-rewrite
    fix(extract-import-map): apply NodeNext .js→.ts rewrite (#294)
  • Merge pull request #387 from tirth8205/fix/phase7-cleanup-mv-trash
    fix(skill): use mv-to-trash + delayed purge for Phase 7 cleanup (#301)
  • Merge pull request #346 from tirth8205/perf/understand-pipeline
    perf(understand): parallelise file I/O in compute-batches + extract-import-map (#76)
  • fix(skill): use mv-to-trash + delayed purge for Phase 7 cleanup (#301)
    Phase 7's `rm -rf` of the just-created `intermediate/` and `tmp/` dirs
    trips destructive-action gates on hardened hosts (e.g. freshness-window
    checks that flag deleting paths created moments earlier). Move them into
    a timestamped `.trash-<epoch>/` instead; Phase 0 reclaims the space once
    the trash is older than 7 days, well past any freshness window. Behavior
    on normal hosts is unchanged — disk usage is identical after the next
    run's purge.
    
    Closes #301
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  • fix(extract-import-map): preserve deterministic stderr order across concurrent loaders
    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>
  • feat(understand): auto-detect conversation language on first run
    When /understand runs with no --language flag and no stored outputLanguage,
    step 3.6 now infers the conversation language and — only when it is non-English
    — confirms once before generating, then persists the choice to config.json.
    English conversations keep the exact same silent `en` path; --language flag and
    stored config still take priority. README documents the behavior; version
    bumped 2.7.5 -> 2.7.6 across all five manifests (user-visible behavior change).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
  • 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>
  • fix(extract-import-map): apply NodeNext .js→.ts rewrite (#294)
    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
  • feat(core): add Kotlin structural analysis via tree-sitter
    Wires Kotlin into the existing tree-sitter pipeline so .kt and .kts
    files now produce functions, classes, data classes, sealed classes,
    interfaces, objects, imports, exports, and call-graph edges — matching
    the behavior of the other language extractors.
    
    ## Why @tree-sitter-grammars/tree-sitter-kotlin
    
    The standard `tree-sitter-kotlin` (v0.3.8) ships only native bindings.
    The new `@tree-sitter-grammars/tree-sitter-kotlin@1.1.0` ships a
    prebuilt `.wasm` (loads cleanly with `web-tree-sitter@^0.26.6`,
    nodeTypeCount=289, parses class_declaration / function_declaration as
    expected). Same shape that PR1 used for Swift, just a different
    publisher because the repomix WASM bundle does not include Kotlin.
    
    `@tree-sitter-grammars` is the official tree-sitter org's GitHub
    account, so this is the canonical upstream WASM source for Kotlin.
    
    ## Notes for reviewers
    
    - `kotlinConfig` already existed as a stub (no `treeSitter` field), so
      Android / JVM / Gradle codebases currently produce no structural
      edges between `.kt` files. This PR adds the `treeSitter` field; the
      existing plugin loader picks it up unchanged.
    - **Visibility rule differs from Swift**: Kotlin's default visibility
      is `public`, so the extractor treats *every* declaration with no
      modifier as exported. Only an explicit `private` opts out. `internal`
      and `protected` remain exported in the project-graph sense because
      they are still resolvable from other files (within the module / via
      inheritance).
    - `class_declaration` in tree-sitter-kotlin is overloaded for class,
      data class, sealed class, and interface (distinguished by the keyword
      child and `modifiers > class_modifier`). The extractor handles all
      four uniformly.
    - `object_declaration` is a separate node type (Kotlin singletons) —
      treated as a class-like entry with its own `name` and members.
    - Primary-constructor parameters marked `val` / `var` are surfaced as
      class properties; plain `parameter`s without `val/var` are
      constructor-only and are NOT counted as properties (matching Kotlin
      semantics).
    - Import handling distinguishes the three forms: plain dotted
      (`import a.b.C`), wildcard (`import a.b.*` → specifier `"*"`), and
      aliased (`import a.b.C as Foo` → specifier `"Foo"`).
    
    ## Verification
    
    - `pnpm lint` clean
    - `pnpm --filter @understand-anything/core build` clean
    - `pnpm --filter @understand-anything/skill build` clean
    - `pnpm --filter @understand-anything/core test`: **692/692** (+22 new
      Kotlin tests, matching the bar set by go-extractor.test.ts /
      swift-extractor.test.ts)
    - `pnpm test`: 196/196 (no regressions)
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  • perf(understand): parallelise file I/O in compute-batches + extract-import-map (#76)
    The /understand pipeline reads every code file twice during analysis:
    once in compute-batches (`extractExports` for the cross-batch neighbour
    map) and once again in extract-import-map (per-language config loaders).
    Both sites used sequential `readFileSync` loops, so on the iOS repo in
    issue #226 (~15k files) the disk-read time was effectively serialised
    behind a single libuv thread while the rest of the pool sat idle.
    
    ## Changes
    
    - `extractExports` now batches files into `IO_PARALLELISM = 64` slices
      and issues all `readFile` calls in each slice through `Promise.all`,
      letting libuv's worker-thread pool overlap disk reads. The
      tree-sitter parse stays on the main thread because `web-tree-sitter`
      is single-threaded WASM — pipelining the I/O while parses run is
      where the wall-time savings come from.
    - `loadTsConfigs`, `loadGoModules`, `loadPhpAutoloads` and
      `buildResolutionContext` switch to async / `Promise.all` for the
      same reason. `buildResolutionContext` also runs the three loader
      passes concurrently (`Promise.all([...])`) since they're independent.
    - A small `readFilesParallel(paths)` helper is added at the top of
      `extract-import-map.mjs` so the three loaders share the same
      error-preserving shape.
    
    ## Why behavior stays identical
    
    - Each loader collects its candidate paths in `files[]` order *before*
      issuing reads, then iterates `reads` in the same order to emit
      warnings + populate output maps. So stderr order and the final map
      contents are byte-identical to the previous sequential loops.
    - `extractExports` collects per-file errors in-place in the
      `Promise.all` callbacks and emits warnings during the post-read
      serial loop, again in chunk order — so warning text and order match
      the previous implementation.
    - Tree-sitter parsing is unchanged: parses still run serially on the
      main thread, just with reads pipelined alongside.
    
    ## What's NOT in this PR
    
    - `buildFingerprintStore` and `analyzeChanges` in `core/fingerprint.ts`
      have the same sequential pattern. They're left alone here because
      they're part of the public `@understand-anything/core` API; making
      them async would be a breaking change worth its own discussion.
      Internal-only `.mjs` scripts are safe to refactor without API churn.
    - No change to scan-project: most of its sync I/O is `statSync`
      (metadata, not content) plus a handful of small `.gitignore` /
      `.understandignore` reads. The parallelism win is marginal there.
    
    ## Verification
    
    - `pnpm lint` clean
    - `pnpm --filter @understand-anything/core build` clean
    - `pnpm --filter @understand-anything/skill build` clean
    - `pnpm test`: 196/196 — including
      `test_compute_batches.test.mjs` (19 tests) and
      `test_extract_import_map.test.mjs` (40 tests), which exercise both
      changed pipelines end-to-end with fixture projects. No output
      diff vs main.
    
    Refs #76
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  • Merge pull request #227 from ZebangCheng/fix/tsconfig-path-leading-dot-slash
    fix(extract-import-map): normalize tsconfig path-alias candidates with leading "./" (#214)
  • fix(scan-project): preserve non-ASCII path bytes via git ls-files -z
    `enumerateViaGit` ran `git ls-files -co --exclude-standard` (newline-separated
    output) and then `split('\n').map(trim)` on the result. Without `-z`,
    `git ls-files` C-escapes any byte outside the locale's "safe" set and wraps
    the path in double quotes — for example, a directory named `30. 🏗️ docs/`
    comes back as `"30. \360\237\217\227\357\270\217 docs/"`. Downstream
    consumers then can't round-trip those octal-quoted strings to real disk
    paths, so every file under such directories is silently dropped from the
    scan.
    
    This is particularly biting on Windows (where the issue surfaces even with
    UTF-8 locale settings) and for any project that uses emoji, accented
    characters, or CJK codepoints in directory names — which is increasingly
    common in design/spec/journal trees.
    
    The fix is to use `-z` (NUL-terminated output), the same approach git
    itself documents for downstream consumers (e.g. `xargs -0`). NUL-separated
    chunks are raw bytes, so every codepoint round-trips back to its real disk
    path on every platform. Split on `\0` instead of `\n`; drop the now-
    unnecessary `.trim()`.
    
    Verified on a real project with emoji-prefixed directory names:
    
      bare `git ls-files`:
        "30. \360\237\217\227\357\270\217\360\237\247\231\342\200\215..."
    
      `git ls-files -z`:
        30. 🏗️🧙‍♂️🔮 BD-CCSP/01. Demo's/DEMO--...
    
    Discovered during a multi-agent scan of an Atlas Intelligence spoke repo;
    ~33 design-intent files in `30. 🏗️ BD-{app}/` directories were silently
    dropped per scan. Full report: atlas-intelligence-io/fleet-feedback#491.
    
    Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
  • Merge pull request #200 from AsimRaza10/fix/agent-model-omit-inherit
    fix(agents): omit `model: inherit` so non-Claude tools don't see a bad model id
  • Merge pull request #204 from Lum1104/feat/semantic-batching-and-output-chunking
    fix(#159): semantic batching + bundled importMap + Phase 1 speedup
  • fix(agents): omit model: inherit so non-Claude tools don't see a bad model id
    `model: inherit` is a Claude Code-specific keyword that means "use the
    parent session's model." Other tools that read the same agent frontmatter
    (opencode, codex, etc.) don't understand it and instead try to use
    `inherit` as a literal model id, which the configured provider rejects.
    
    Reproduction (from #167): opencode + deepseek runs `/understand`, the
    project-scanner subagent dispatches with `model: inherit`, deepseek
    returns `ProviderModelNotFoundError`, and the pipeline halts on every
    subagent dispatch.
    
    With the field omitted, each platform falls back to its own configured
    default:
    - Claude Code: user's default subagent model
    - opencode / codex / etc.: globally configured model
    
    Note for Claude Code Opus users: subagents will no longer auto-inherit
    the Opus session model. If you want the previous behavior, set your
    default subagent model globally — that single setting now controls all
    nine agents.
    
    Closes #167
  • Merge pull request #186 from AsimRaza10/fix/tailwind-source-detection
    fix(dashboard): explicit @source for Tailwind v4 (fixes #179)
  • fix(ux): add progress reporting to /understand pipeline
    Adds phase status lines, batch progress with total count, and phase
    completion confirmations to the skill definition. Users now see
    [Phase N/7] headers and Batch X/N during analysis instead of
    unnumbered batch lines with no context.
    
    Fixes #182
  • fix(dashboard): add explicit @source for Tailwind v4 detection
    Tailwind v4's default source detection walks the nearest .git and
    collects tracked files via git ls-files. When the dashboard sources
    sit inside a gitignored subtree of an ancestor repo (e.g. the default
    marketplace install path ~/.claude/plugins/cache/, which is ignored by
    ~/.claude/.gitignore), detection returns 0 files and the Oxide engine
    skips all utility generation — the dashboard renders unstyled.
    
    Adding explicit @source directives is the supported Tailwind v4 escape
    hatch and is a no-op for installs where automatic detection works.
    
    Verified: built CSS bundle jumps from ~9 KB to ~55 KB and utility
    classes (.flex, .grid, .absolute, .w-full, .h-full) are present.
    
    Fixes #179
  • Merge pull request #171 from Derrick-xn/docs/update-skill-graph-reference
    docs(skills): update graph structure references
  • chore(dependencies): update astro and vite versions in package.json and pnpm-lock.yaml
    Bumps astro to version 6.3.7 and vite to version 6.4.2 across relevant package files to ensure compatibility and access to the latest features.
  • Merge pull request #161 from okwn/contrib/understand-anything/eslint-tooling
    chore: add ESLint tooling with TypeScript support
  • chore(lint): switch to recommended baseline, fix errors, wire into CI
    - typescript-eslint preset: strict -> recommended for a usable first-pass
      baseline (per PR discussion); ratchet up in a follow-up.
    - Drop the projectService/parserOptions block. Neither `recommended` nor
      `strict` is type-aware, so it was unused; removing it also avoids the
      pnpm-workspace tsconfig-resolution failure mode flagged in review.
    - Add Node + browser globals via the `globals` package so .mjs scripts and
      the dashboard stop hitting `no-undef`.
    - Expand ignores: built bundles (**/public/**), Astro generated (.astro/),
      and .private/ (eval scratch). Cuts 2400+ errors in vendored output.
    - Allow `_`-prefixed unused vars/args/caught errors; skip irregular
      whitespace inside comments (json-parser intentionally embeds ZWSP-escaped
      block-comment examples in JSDoc).
    - Fix the residual 13 genuine errors: drop dead imports/vars, replace
      two `as any[]` in schema.ts with `Array<Record<string, unknown>>`,
      drop unused destructure in change-classifier, drop unused catch binding
      in extract-structure.mjs.
    - Add EOF newline to eslint.config.mjs.
    - Refresh pnpm-lock.yaml.
    - Add `pnpm lint` step to .github/workflows/ci.yml so the tooling
      actually enforces something.
    
    pnpm lint now exits 0 locally; 33+13 test files / 1445 tests still pass.
  • fix: correct GitHub URL in onboarding guide footer
    The generated onboarding markdown linked to a nonexistent repository
    (anthropics/understand-anything) instead of the actual project URL
    (Lum1104/Understand-Anything).
  • fix(skills/understand): canonicalize isCli paths so symlinked SKILL_DIR runs main()
    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
  • refactor(onboarding): theme tokens, lifted state, a11y
    - 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.
  • fix(onboarding): include class/function in node-type description
    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.
  • fix(onboarding): wire to i18n + add missing ua-fade-in keyframes
    - 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)
  • 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
  • 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 #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)