Skip to content

feat: hub score visualization, HTML export, and Louvain community detection#118

Merged
magic5644 merged 29 commits into
mainfrom
feat/hub-score-visual
Jul 2, 2026
Merged

feat: hub score visualization, HTML export, and Louvain community detection#118
magic5644 merged 29 commits into
mainfrom
feat/hub-score-visual

Conversation

@magic5644

Copy link
Copy Markdown
Owner

Summary

  • F1 — Hub score visual indicator on ReactFlow file nodes (4 border-width tiers, 0–100 normalized)
  • F2GraphNodeMetadata foundation: hubScore, fileExtension, loc computed by NodeMetadataBuilder (Spider as single source of truth)
  • F3graph-it export --format html + /export in REPL: standalone HTML with vis.js 10.1.0 inlined, community colors, htmlEscape on all injected values (Règle 10)
  • F4 — Louvain phase-1 community detection: communityId in GraphNodeMetadata, palette 12 colors WCAG AA, legend in ReactFlow (representative node label), community colors in HTML export

Key decisions

  • NodeMetadataBuilder is the single enrichment point (hubScore + communityId), called by GraphViewService and ExportHtmlCommand
  • COMMUNITY_PALETTE in shared/, communityColor() webview-only (Règle 05)
  • communityId: 0 = isolated node, 1+ = cluster, absent = not yet computed
  • vis-network 10.1.0 in dependencies (runtime require.resolve, not bundled — Règle 02 safe)
  • .vscodeignore: CLAUDE.md and .claude/** excluded from .vsix

Test plan

  • npm test — 2179 pass, 12 skipped
  • npm run check:types — clean
  • npm run package:verify — zero .map files, .vsix 19.5 MB
  • node bin/graph-it export --format html --output graph.html — open in browser, verify community colors + legend
  • REPL: graph-it/export --output graph.html

File nodes now display border thickness proportional to their import
count (hub score 0-100, normalized over visible nodes). Four tiers:
2px (<20), 3px (>=20), 4px (>=50), 5px (>=80). High-score nodes also
render bold (>=50) and larger font (>=80, non-root only).

Priority order preserved: selection > cycle > root > hub score.
External dashed borders coexist with hub tiers. React.memo comparator
updated to include hubScore to ensure re-renders on score changes.

Coverage: FileNode.tsx 97%, buildGraph.ts 96%, 251 tests passing.
Introduces GraphNodeMetadata {hubScore, loc?, fileExtension?} as an
optional Record on GraphData, keyed by normalizePath. NodeMetadataBuilder
computes hubScore [0-1] from parentCounts in the analyzer layer, making
Spider the single source of truth per ADR-F2-01.

WikiGenerator reads externalNodeMetadata.hubScore when available, falling
back to its DB-computed value. GraphViewService populates nodeMetadata
after parentCounts. buildGraph.ts reads nodeMetadata with a fallback to
computeHubScores for backward compat when nodeMetadata is absent.
MCP types.ts adds GraphNodeMetadataSchema and GraphDataSchema Zod schemas.

477 tests passing, coverage: NodeMetadataBuilder 100%, buildGraph 96%,
mcp/types.ts 86%.
Adds `graph-it export --format html [--output path.html]` that produces
a self-contained HTML file with vis-network 10.1.0 inlined (~628KB).

- HtmlExporter: htmlEscape on all injected values (Règle 10), safeVisSource
  guard against </script> sequences, hub score colour tiers
- ExportHtmlCommand: parseArgs for --output, normalizePath on all keys
- formatter.ts: "html" added to CliOutputFormat + CliError guard
- .vscodeignore: exclude CLAUDE.md and .claude/** from .vsix
- ADR-F3-01/02: vis.js inlining strategy and dependency position
Detects file clusters via a greedy Louvain phase-1 algorithm (pure Node.js,
no external dependency). communityId 0 = isolated, 1+ = cluster index.

- LouvainDetector: deterministic phase-1, 100-pass cap, contiguous 1-indexed
- communityPalette: 12-color WCAG AA palette in shared/
- communityColor: webview-only util, transparent for isolated/absent nodes
- CommunityLegend: overlay bottom-left, label = highest-hubScore node per cluster
- FileNode: background tinted by community (13% alpha, Règle 04 compliant)
- HtmlExporter: communityId → vis.js node color (palette priority, hubScore fallback)
- ExportHtmlCommand: calls computeNodeMetadata to populate communityId for export
- GraphProvider test: relaxed to objectContaining for nodeMetadata additions
- ADR-F4-01: documents Louvain-native vs graphology decision
@socket-security

socket-security Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedhappy-dom@​20.10.6661008895100
Added@​testing-library/​react@​16.3.210010010087100
Addedvis-network@​10.1.010010010090100

View full report

@socket-security

socket-security Bot commented Jun 30, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm happy-dom is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/happy-dom@20.10.6

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/happy-dom@20.10.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

magic5644 added 23 commits June 30, 2026 09:57
…export routing

- CommunityLegend: clearer header ("Import clusters" + subtitle), tooltip
  updated to "Cluster N — N clusters total"
- HtmlExporter: buildCommunityLegend() injects a fixed bottom-left overlay
  with cluster colors and representative node labels into exported HTML
- REPL: /export now only routes to HTML export when --format html is explicit,
  preserving existing post-result export (JSON/Markdown/Mermaid)
- Remove stale eslint-disable directive in HtmlExporter.ts
…xtension

Spider.workspaceRoot getter → GraphViewService passes it to computeNodeMetadata,
fixing the "all files same cluster" regression in the extension webview.
NodeMetadataBuilder test now passes explicit workspaceRoot='/project'.
Replaces workspacePrefixLen (broken when short files reduce minLen/cap)
with exact relative path from workspaceRoot — same logic as PathCommunityDetector.
Falls back to workspacePrefixLen when workspaceRoot absent (tests unchanged).
When state.workspaceRoot differs from runtime.workspaceRoot (user ran /path ./src/),
use it as the default scope for /export. Falls back to state.lastFile if set.
ExportHtmlCommand wraps path.resolve(outputPath) with normalizePath → forward
slashes on Windows. Test assertions use normalizePath consistently.
…port

changelog.md: v1.11.0 entry (PathCommunityDetector, HTML vis.js export, /export REPL, bug fixes)
docs/CLI.md: add /export to REPL slash commands table
README.md: add /export to REPL list + graph-it export to CLI table
computeNodeMetadata called after spider.crawl — same path-based community
detection as VS Code extension and CLI. NodeInfo gains hubScore + communityId fields.
- buildNodeInfo: 4 nouveaux cas — metadata undefined (pas de hubScore/communityId),
  metadata peuplée (hubScore + communityId présents), clés partielles, lookup normalisé (Règle 03)
- executeCrawlDependencyGraph: 2 nouveaux cas — hubScore présent sur chaque nœud,
  communityId distinct entre nœuds dans des répertoires différents
…munity fields

- graph.ts: pass graphData.nodes (normalized) to buildNodeInfo so metadata
  keys match nodeMetadata index on Windows (Règle 03, Sophie BLOCK fix)
- changelog.md + docs/CLI.md: document hubScore/communityId on crawl_dependency_graph nodes
…r/src/, etc.)

PathCommunityDetector now strips the common dir prefix shared by ALL input files
before applying UMBRELLA skip. This prevents projects where all sources live under
a non-umbrella container dir (e.g. 'vue/') from collapsing to a single cluster.

Algorithm: path.relative(workspaceRoot) → strip common prefix (capped at minLen-1)
→ skip UMBRELLA dir → domain = first remaining dir segment.

ReactFlowGraph.tsx communityLabel uses the same two-stage stripping (absolute prefix
then relative common prefix) for correct legend labels.

Fixes: all-same-cluster bug when workspace root is the monorepo root (app-bobbee case).
Divergent top-level roots (vue/src/ + backend/) collapsed to one cluster each
because common-prefix stripping is empty when roots diverge, and container dirs
like 'vue' are not umbrella dirs.

New rule: skip up to and INCLUDING the last umbrella dir (src, tests, lib...),
domain = next segment. Handles vue/src/views -> 'views', packages/app/src/store
-> 'store'. Applied in PathCommunityDetector (analyzer) and ReactFlowGraph legend
(webview) with matching logic.

Adds domainFromDirParts export + 3 tests (divergent roots, deeply nested).
/export executed correctly but was missing from SLASH_COMMANDS, so it never
appeared in the Tab-completion suggestion list.
New VS Code setting graph-it-live.showCommunities (boolean, default true).
When false, hides the community legend and node background tint on the
file graph. Applies live via onDidChangeConfiguration (no reload).

Wired end-to-end: config -> ProviderStateManager -> GraphProvider
(_sendGraphUpdate initial + enriched messages) -> ShowGraphMessage ->
App.tsx state -> ReactFlowGraph prop -> buildGraph -> FileNodeData tint gate.
Property tests write temp_*.py into the committed fixtures dir and unlink
in a per-test finally, which is not crash-safe: a killed run orphans the
file (observed a 0-byte temp_mixed_types_*.py). Add beforeAll/afterAll
sweep (fs.readdir + unlink, cross-OS, no shell) and an anchored .gitignore
guard so orphans never get committed.
Comment thread package.json

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SocketSecurity ignore npm/happy-dom@20.10.6

@magic5644 magic5644 merged commit 3d51a2d into main Jul 2, 2026
11 checks passed
@magic5644 magic5644 deleted the feat/hub-score-visual branch July 2, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant