`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
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
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
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>
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>
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>
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>
- is_test_path: collapse 7 per-language conditional blocks into a
data-driven _TEST_NAME_PATTERNS table; JS/TS infix stays inline
- production_candidates: extract _join + module-level _add_unique to
drop the nested closure and the repeated trailing-slash idiom
- Drop dead _TEST_DIR_SEGMENTS constant and the local _splitext
reimplementation; use os.path.splitext
- link_tests: drop the impossible-malformed-tags guard, tighten the
docstring, change edge description to "Path-based pairing
(deterministic)", drop redundant break comment
- Trim Step 5b inline block that duplicated the module-level header
- Convert file-analyzer Note from blockquote to bold paragraph to
match surrounding prompt style
Tests: split the strip-edges test from the unrelated-edges-survive
test, add empty-input and missing-filePath cases, pin sibling-before-
walkup and sibling-before-mirror priority order, drop brittle report
text assertion. 36 tests, all passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Now that the merge script produces tested_by edges deterministically
from path conventions, the LLM should not emit them — its direction is
unreliable across batches and any emitted edges are stripped on merge.
- Remove tested_by row from file-analyzer's edge table.
- Add a note pointing to the deterministic linker.
- Document the new behaviour in the merge section of SKILL.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove bin/ from DEFAULT_IGNORE_PATTERNS (Node/Ruby CLI launchers use bin/)
- .NET users can add bin/ to .understandignore manually
- Fix project-scanner Step 2.5 to re-filter from original file list when
.understandignore exists, ensuring ! negation correctly overrides defaults
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Python scripts to merge knowledge graphs (closes#70) and move
mechanical normalization out of LLM context into deterministic scripts
with diagnostic reporting. Convert all agent definitions from dispatch
templates to self-contained system prompts to prevent instruction loss.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous wording ("MUST have sub-nodes") implied every code file
must produce function nodes, which is wrong for files without significant
functions. Now correctly states: if significant functions exist in the
script output, you must create nodes for them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prompt templates (file-analyzer, project-scanner, architecture-analyzer,
tour-builder, graph-reviewer) were being compressed by the orchestrator
when dispatched as subagent prompts, causing function/class extraction
to be silently skipped. Moving them to agents/ ensures the framework
loads the full prompt without compression.
- Move 5 prompt templates from skills/understand/ to agents/
- Update SKILL.md to reference agent definitions instead of templates
- Set all agent models to `inherit` for cross-platform compatibility
- Update CLAUDE.md to reflect new agent model policy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LLMs systematically abbreviate node types (e.g. "func" instead of
"function") and edge types (e.g. "extends" instead of "inherits"),
causing dashboard validation failures. This combines two fixes:
Option A: Rename the ambiguous `func:` ID prefix to `function:` across
all prompts, source code, tests, and example data so LLMs see consistent
naming. Also fix `relates_to` ghost edge type in django.md.
Option B: Add NODE_TYPE_ALIASES and EDGE_TYPE_ALIASES normalization maps
in schema.ts that transparently correct common abbreviations before Zod
validation, as a runtime safety net.
Closes#36
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Edge weights, node ID prefix conventions, and tag formatting were listed
in both Check 1 (Schema Validation — Critical) and Check 7 (Quality —
Warning). A deterministic script following check headings would classify
these as warnings, potentially approving invalid graphs. Remove the
duplicates from Check 7 since Check 1 already covers them as critical.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The exclusion rule conditionally kept .sh files for bash-first projects,
but the source-file whitelist and language mapping table had no .sh entry,
causing retained shell files to be dropped or untyped. Add .sh/.bash to
the whitelist and language table (mapped to 'bash'), and remove the
conditional exclusion rule.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Duplicate node IDs were listed under Check 6 (Warning) but classified as
critical in the Severity Classification section. A script following check
headings would classify duplicates as warnings, letting invalid graphs
pass review. Move duplicate-ID check to its own Check 5 (Critical) and
renumber subsequent checks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Redesign all 5 agent prompts with a "Phase 1 — Script" + "Phase 2 — LLM
Reasoning" structure. Agents now write and execute scripts for deterministic
work (validation, file discovery, structure extraction, graph analysis)
before applying LLM judgment for semantic tasks (summaries, tags, pedagogy).
Key changes per agent:
- graph-reviewer: script performs all 6 validation checks deterministically
- project-scanner: script handles file discovery, language detection, line counting
- file-analyzer: script extracts functions/classes/imports via regex patterns
- architecture-analyzer: script computes import adjacency, inter-group frequency
- tour-builder: script calculates fan-in, BFS traversal, cluster detection
Design safeguards:
- scriptCompleted sentinel field guards against partial script output
- Batch-indexed temp paths prevent collision in concurrent file-analyzer agents
- Explicit "trust the script" directives prevent LLM from re-reading raw data
- Up to 2 script retries on failure
- Explicit instructions to strip intermediate fields from final output
- Bash tool added to all agent tool lists
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove ChatPanel and @anthropic-ai/sdk dependency (redundant with /understand-chat)
- Replace Monaco editor with styled summary code viewer
- New graph-first layout: 75% graph + 360px right sidebar
- Dark luxury aesthetic: deep blacks, gold/amber accents, DM Serif Display typography
- Add ProjectOverview component for sidebar default state
- Learn persona now shows tour panel directly in sidebar
- Add schema validation on graph load with error banner
- Defensive null checks in store for tour methods
- Agent pipeline: write intermediate results to disk instead of context
- Agent models: sonnet for simple tasks, opus for complex (no haiku)
- Prompt-engineer all 5 agent prompts and SKILL.md
- Auto-trigger /understand-dashboard after /understand completes
- Add dashboard screenshot to README
- Bump version to 1.0.1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move packages/{core,dashboard,skill} into understand-anything-plugin/ to
conform to the Claude Code plugin format. Add .claude-plugin/marketplace.json
for plugin discovery. Update workspace config and docs accordingly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>