feat: CLI trace/impact commands with rich tree visualization#10
Conversation
- ingest now supports .db output — saves graph to SQLite instead of JSON - trace <FQN> --db --depth: downstream flow tracing with cycle detection - impact <FQN> --db --depth: upstream impact analysis with cycle detection - both commands render Rich tree output with node type, FQN, file:line Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix incorrect FQN in test_cli_full_workflow (was :assist:0, should be :assist) - Fix tmp_path type annotation (Path, not str) - Translate Russian comments to English, fix 'expeccted' typo - Add tests for --version, JSON output, DB output, empty dir warning, nonexistent path, db-not-found, node-not-found for trace/impact, callee/caller tree rendering, Unresolved label, Unknown Caller label, cycle detection, depth-limit early return - Add pipeline tests: FileNotFoundError, NotADirectoryError, unknown extension skip, excluded dirs, unparseable UTF-8 file - Add pragma: no cover on __main__ entrypoint line in cli.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces SQLite database storage support to the ingestion pipeline, along with two new CLI commands, trace and impact, for analyzing downstream execution flows and upstream callers. It also adds comprehensive unit tests for these new features. The review feedback suggests improving resource safety by using context managers for database connections instead of manual connect/disconnect calls, and optimizing performance by replacing N+1 query patterns in the recursive tree builders with batch node retrieval.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
- Replace manual connect/disconnect with `with SQLiteStore(...) as store:` in ingest, trace, and impact — prevents connection leaks on exceptions - Replace per-edge store.get_node() calls in build_trace_tree and build_impact_tree with a single store.get_nodes() batch fetch per level Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request enhances the CGIS CLI by adding support for SQLite database storage during ingestion, alongside two new commands: trace (for downstream execution flow tracing) and impact (for upstream impact analysis). It also introduces comprehensive unit tests for the CLI and the ingestion pipeline. The review feedback suggests optimizing the recursive tree-building functions (build_trace_tree and build_impact_tree) by skipping recursive calls for unresolved nodes or unknown callers, which avoids redundant database queries and improves performance.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Unresolved targets and unknown callers have no entries in the nodes table, so querying their edges is a guaranteed no-op. Guard with early continue to avoid the wasted DB roundtrip and clarify intent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request adds support for SQLite storage in the ingest command and introduces two new CLI commands: trace (for downstream execution flow tracing) and impact (for upstream impact analysis), complete with cycle detection and depth limits. Comprehensive unit tests have also been added for both the CLI and the ingestion pipeline. The reviewer recommended passing overwrite=True when saving the graph to SQLite to ensure stale data from previous runs is cleared.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…aph data Without overwrite, re-running ingest on an existing DB keeps nodes and edges for deleted or renamed symbols. JSON output is always a full rewrite; SQLite should behave the same way. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
…amespace helper dagre.layout() is fully synchronous; no I/O occurs, so async/await added unnecessary microtask overhead. Also extracts the duplicated namespace extraction pattern into a private getNamespace() helper. Suggested by gemini-code-assist round-3 review (PR #122 findings #9, #10). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…122) * chore(ui): add CANVAS_MAX_WIDTH and ISLAND_GAP layout constants * feat(ui): add IslandLayoutEngine — 3-phase bin-packing island layout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ui): add IslandContainerNode and NAMESPACE_COLORS * feat(ui): wire IslandLayoutEngine, remove layout.ts Replace layoutGraph (dagre monolith) with IslandLayoutEngine.run() in useLayoutComputation and useFlowNavigation; register islandContainer nodeType in GraphShell; add layout/index.ts re-export shim; delete layout.ts and its now-obsolete layout.test.ts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(guardian): pass --repo to gh pr view before checkout gh CLI has no git context before actions/checkout runs. GITHUB_REPOSITORY provides the repo slug without needing a local clone. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ui): correct island container padding and header offset - bbox width now uses only horizontal padding (FILE_CONTAINER_PADDING * 2) - bbox height adds FILE_HEADER_HEIGHT + FILE_HEADER_GAP for the label badge - nodes normalised to start at (pad, pad+header) so they don't collide with header - container in run() placed at offset directly, no double-padding Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(layout): add run() tests for IslandLayoutEngine + defer _expandedFiles Five new unit tests covering: container-per-namespace, id prefix, islandNamespace data propagation, cross-edge passthrough, and positive bbox dimensions. Added TODO(PR4) comment on _expandedFiles explaining it is reserved for the ego-subgraph overlay (issue #30). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(metrics): broaden findings_total regex to match any model category label The previous regex required exact category names (Logic Bug, Test Coverage, Type Safety, Ontology). If the model used a different label the count came back as 0. Now matches any **[ at line start — the only place that pattern appears in the guardian output format. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Revert "fix(metrics): broaden findings_total regex to match any model category label" This reverts commit e46bec4. * fix(layout): apply 4 gemini-code-assist findings 1. [HIGH] Dagre center→top-left: subtract NODE_WIDTH/2 and NODE_HEIGHT/2 from dagre coordinates so nodes render at their correct position in React Flow (dagre returns centers, React Flow expects top-left corners). 2. [MEDIUM] Container z-order: push islandContainer node before its children so React Flow renders it in the background, not on top. 3. [MEDIUM] Sort stability: replace area sort with alphabetical namespace sort so island positions don't shift when nodes are expanded/collapsed. 4. [MEDIUM] Dangling edges: skip edges where source or target is absent from this.nodes to prevent React Flow warnings on missing node refs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(guardian): add Library Contract as priority-2 review category Adds language-agnostic "Library boundary contracts" heuristic at priority (2), between logic correctness and test coverage. Instructs the reviewer to trace data transformations across library calls and flag convention mismatches: units/coordinate systems, ordering assumptions, mutation ownership. Applies to both Python (e.g. naive vs tz-aware datetimes, row/col vs x/y) and TypeScript (e.g. center-based vs top-left coordinate systems). Outcome of pair-review session with gemini-code-assist on PR #122, where the Dagre center→top-left bug was missed by guardian but caught by gemini. The root cause: guardian had no prompt category for library contract violations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(layout): use LAYOUT_DIRECTION constant in dagre graph config Was hardcoding 'TB' while constants.ts exports LAYOUT_DIRECTION = 'LR'. Caught by guardian's new Library Contract category. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(layout): apply 4 gemini-code-assist round-2 findings - Defensive null check on dagre pos before accessing x/y - Replace O(N) bboxes.find() with result.bbox from islandResults map - Remove redundant `as object` cast when spreading node.data - Remove redundant `as Record<string, string>` cast in IslandContainerNode (NAMESPACE_COLORS already typed in theme.ts) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(ui): make IslandLayoutEngine.run() synchronous, extract getNamespace helper dagre.layout() is fully synchronous; no I/O occurs, so async/await added unnecessary microtask overhead. Also extracts the duplicated namespace extraction pattern into a private getNamespace() helper. Suggested by gemini-code-assist round-3 review (PR #122 findings #9, #10). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ui): add TODO(PR3) for fileContainer wrapping, defensive position fallback - Documents missing fileContainer wrapping logic (regression from old layout.ts): expanded FILE nodes must emit type:'fileContainer' nodes with React Flow parentNode relationships. FileContainerNode component already exists. Tracked as PR3 work item. - Guards dagre fallback path against undefined node.position with optional chaining (node.position?.x ?? 0) to prevent TypeError on uninitialized nodes. Findings from gemini-code-assist round-4 review (PR #122). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Initialize project using Create React App
* chore: upgrade reactflow v11 to @xyflow/react v12
- Replace reactflow with @xyflow/react for React 19 support
- Update import paths in App.js to @xyflow/react
* feat: core graph modules - execution flow, layout, file-based grouping
- flow.js: bidirectional DFS with depth control
- layout.js: dagre-based TB layout with group container positioning
- grouping.js: file-path based grouping with custom group nodes
- graph.json: 68 internal nodes, 297 edges
* feat: interactive graph debugger UI with dark theme
- Dark theme with colorMode='dark' and fitView
- Custom GroupNode component for file containers
- Execution flow view (click node to trace call graph)
- Depth slider, minimap, hover tooltips
- Show/hide external nodes toggle
- External nodes (122) for broken edges
- Stats panel showing node/edge counts
- Non-clickable group nodes with pointer-events:none
* fix: update tests for new UI and add ResizeObserver mock
- App.test.js: match 'Depth' label instead of 'learn react'
- setupTests.js: add ResizeObserver polyfill for ReactFlow v12
* fix: remove parentNode/extent, use absolute positioning with overlap-safe group packing
- Replace ReactFlow parentNode/extent with metadata-only groupId
- Children keep dagre absolute positions (no relative repositioning)
- Groups positioned from children bounding boxes
- Greedy vertical packing prevents group overlaps
- CLASS nodes centered in their groups
* fix: re-check all groups after shifting down to prevent vertical overlaps
* feat: export graph as PNG/SVG with html-to-image
- Add ⬇ PNG and ⬇ SVG buttons in control panel
- Export captures full graph viewport with dark background
- Filters out minimap, controls, and panels from export
- Uses html-to-image@1.11.11 (pinned for compatibility)
* fix: increase PNG export quality with pixelRatio: 2
* fix: optimize export - target viewport only, pixelRatio 1.5
* fix: restore pixelRatio 2 for PNG export
* fix: bugs + code cleanup
Bugs:
- Add .catch() to export promises (prevents silent failures)
- Cache addExternalNodes in ref (compute once, not per-click)
- Conditional fitView via needsFit state (no re-fit on hover)
Code cleanup:
- Remove unused useMemo, useReactFlow imports
- Remove redundant 'const g = enriched' alias
- Delete dead graphUtils.js module
- Update index.html title/description from CRA defaults
* feat: tests + search/legend/error-boundary
Tests (23 total):
- grouping.test.js: 6 tests for groupByClass
- flow.test.js: 8 tests for buildExecutionFlow
- layout.test.js: 7 tests for layoutGraph
Features:
- Node search: filter by name, type, or file path
- Legend panel: shows color coding for node types
- ErrorBoundary: catches crashes with retry button
* fix: suppress ResizeObserver loop error from ReactFlow
* fix: suppress ResizeObserver console.error from ReactFlow
* fix: debounce ResizeObserver to prevent loop error
* revert: remove ResizeObserver wrapper that caused nodes to disappear
* chore: migrate deps from CRA to Vite + vitest
* chore: clean up CRA-specific config (eslintConfig, browserslist, web-vitals)
* chore: add Vite config with React plugin and vitest
* chore: move index.html to root for Vite
* chore: remove public/index.html (moved to root)
* chore: rename entry files for Vite, remove React imports
* chore: update .gitignore for bun
* chore: remove package-lock.json, use bun.lock
* docs: update commands for bun
* fix: untrack bun.lock, update manifest.json for Graph Debugger
* chore: cleanup old .js files, add dist/ to gitignore
* docs: add BACKLOG.md with 43 backlog items
* fix: use imperative fitView instead of broken declarative pattern
* chore: move test libs to devDependencies, remove unused user-event
* docs: mark critical bugs as resolved in BACKLOG.md
* fix: wrap App in ReactFlowProvider for useReactFlow hook
* feat: disable external nodes by default
* docs: update backlog status
* refactor: rename extractClass → extractGroupKey
* refactor: rename groupByClass → groupByFile
* refactor: extract NODE_WIDTH to shared constants
* style: remove duplicate margin:0 from index.css
* docs: mark quick wins as resolved in backlog
* feat: add ARIA labels to interactive elements
* feat: add Zoom to Fit button in control panel
* feat: add depth presets (Immediate/Near/Call tree)
* feat: add keyboard shortcuts (Esc/F//)
* feat: add search highlighting with box-shadow glow
* feat: add loading indicator during layout computation
* feat: replace fixed tooltip with cursor-following tooltip
* feat: add back-to-root navigation in flow view
* docs: mark UX items as resolved in backlog
* perf: move graph.json to public/, load via fetch() (-160KB bundle)
* perf: optimize edge validation O(n*m) → O(n+m) via Set
* perf: optimize edge dedup O(n²) → O(n) via Set
* perf: debounce search input (200ms)
* perf: memoize buildExecutionFlow results
* docs: mark performance items as resolved in backlog
* perf: wrap displayedNodes in useMemo
* refactor: move extractGroupKey to grouping.js
* refactor: move layout constants to shared constants.js
* style: remove unused .tooltip-panel CSS rule
* chore: install react-error-boundary
* refactor: replace ErrorBoundary with react-error-boundary
* style: remove !important from CSS, use style props
* docs: add JSDoc types for graph node/edge interfaces
* docs: mark code quality items as resolved in backlog
* chore: commit utils.js and updated graph.json
* TypeScript migration + decomposition fixes
- TS migration (colleague): all .tsx/.ts files, tsconfig.json, types
- CSS module files for each component (were missing imports)
- Fixed 6 post-merge issues:
- Removed duplicate useEffect in App.tsx
- Using mapNodeToReactFlow/mapEdgeToReactFlow in buildFullGraph
- Restored isLayouting state for layout indicator
- / keyboard shortcut focuses search input
- Enriched graph data passed to useFlowNavigation (no ref access during render)
- noUnusedLocals: true in tsconfig
- Fixed nodeMapper.ts: className → class (groupByFile expects n.class)
- Added missing mapEdgeToReactFlow to edgeMapper.ts
- Fixed ESLint @typescript-eslint/no-explicit-any in 3 files
- ControlPanel: collapsible (☰/✕ toggle), search wired up and working
- Removed storybook (deemed overkill)
* Fix remaining TS errors + Phase 3 unit tests
- GroupNode.tsx: cast data for typed label access
- flow.ts: make deduplicateEdges generic to preserve GraphEdge type
- grouping.ts: cast edges as Edge[] in return value
- layout.ts: use (node as any).groupId pattern, safe gp access
- test files: add type annotations, ! assertions, ts-nocheck for layout.test
- New tests (8 test files, 46 total tests):
- utils/nodeMapper.test.ts (8 tests)
- utils/edgeMapper.test.ts (4 tests)
- hooks/useSearch.test.ts (7 tests)
- hooks/useExport.test.ts (3 tests)
- Lint, tsc, tests all pass
* Fix bun lint + bun test DOM setup
- Remove redundant vitest imports from all 8 test files (globals: true)
- Move vi.mock to top level in useExport.test.ts (fix hoisting warning)
- Restructure useSearch tests: waitFor + real delays (cross-runner)
- Install happy-dom + bunfig.toml for bun native test runner DOM support
- bun lint: 0 errors, bun run test:run: 47/47 pass, bun test: 46/47 pass
* Phase 3: complete all tests (74 tests, 10 files)
- useFlowNavigation unit test (5 cases: API shape, group skip, click flow, clearCache)
- ControlPanel render test (16 cases: toggle, depth, presets, search, export, flow mode)
- Integration test extended: legend + stats panel + fetch mock fixed (ok: true)
- Lint + tsc clean, 74/74 tests pass
* docs: CSS Modules migration spec
* docs: CSS Modules migration implementation plan
* feat(css): add tokens.css, shared.module.css, GroupNode.module.css
* feat(css): migrate ControlPanel to CSS Modules
* feat(css): migrate LoadingOverlay to CSS Modules
* feat(css): migrate StatsPanel to CSS Modules
* feat(css): migrate LegendPanel to CSS Modules
* feat(css): migrate NodeTooltip to CSS Modules
* feat(css): migrate App to CSS Modules
* feat(css): migrate GroupNode to CSS Modules
* feat: add VITE_GRAPH_DATA_URL env var with /graph.json fallback
* chore: bump html-to-image to latest
* docs: update AGENTS.md + BACKLOG.md for env vars + html-to-image bump
* feat: add bundle analyzer (rollup-plugin-visualizer)
* feat: match UI with new graph format (FILE nodes, namespace, edge colors)
- types.ts: add FILE, NodeNamespace; remove EXTERNAL type
- theme.ts: add EDGE_COLORS, FILE colors, update LEGEND_ITEMS
- nodeMapper.ts: namespace-based dimming, FILE support
- edgeMapper.ts: color by edge type (CALLS/IMPORTS/EXTENDS/CONTAINS)
- flow.ts: traverse CALLS + IMPORTS + CONTAINS edges
- App.tsx: remove addExternalNodes, filter by namespace
- grouping.ts: simplify extractGroupKey (remove EXTERNAL check)
- All tests updated: 80/80 pass, tsc + lint clean
* refactor: remove groupByFile, add structure edges toggle
- Remove grouping.ts, GroupNode.tsx, GroupNode.module.css
- Simplify layout.ts — no more group/CLASS handling
- Simplify App.tsx — no groupByFile, no GroupNode, no nodeTypes
- Add showStructure state with toggle in ControlPanel
- buildExecutionFlow accepts allowedEdgeTypes parameter
- Full graph filters edges by showStructure (CALLS-only when OFF)
- Flow view uses allowedEdgeTypes for DFS traversal
- Remove GROUP_* constants from constants.ts
- Update tests: 9 files, 70/70 pass, lint + tsc clean
* feat: compact UI, collapsible FILE blocks, per-edge-type toggles, smooth animations
- Remove depth slider (hardcoded to 3 in useFlowNavigation)
- Remove CONTAINS from toggleable edge types (always structural)
- Compact 2-row ControlPanel (no header, flex-wrap)
- Per-edge-type toggle buttons with colored indicator dots
- FILE collapsible blocks with ▶/▼ prefixes (1 level CONTAINS children)
- CONTAINS edges from expanded FILE nodes always visible
- Two-column LegendPanel (Nodes | Edges)
- Smooth node position transitions (transform 0.3s ease)
- fitView with duration: 250ms on all calls
- nodesDraggable=false to prevent drag conflicts
* refactor: extract collapse, generalize DFS, remove dead code
- Extract getCollapsedView to src/collapse.ts (pure function, no hook deps)
- Generalize DFS in flow.ts: single dfs() handles both outgoing and incoming via resolveNext callback (eliminates ~30 lines of duplication)
- Remove unused src/components/ErrorFallback.tsx (inline version in App.tsx already handles this)
- Remove unused src/types.js (legacy JSDoc typedefs, no imports)
- Remove unused CSS classes: .btn-icon, .btn-toggle, .btn-preset from shared.module.css
* docs: add Phase 6 visual refinements to BACKLOG.md
* feat: edge aggregation, useGraphFilter hook, context highlighting
- aggregateEdges: bundles duplicate CALLS between same source/target
- useGraphFilter: extracted filter pipeline from App.tsx (-50 LOC)
- applyContextHighlight: dims non-connected nodes/edges on hover
- CSS transitions for smooth opacity changes
- 12 new test files, 86 total passing
Phase 6 items: edge aggregation + filter extraction + context highlight
* docs: file containers design spec
* feat: add file container constants
* feat: container wrap in layout.ts with {nodes,edges} return type
* feat: update hook callers + mocks for new layoutGraph return type
* feat: FileContainerNode component with CSS and tests
* feat: register FileContainerNode in ReactFlow nodeTypes, remove old group CSS
* fix: remove double border in expanded containers, soften container colors
* fix: restore CALLS edges in full graph view; feat: CLASS expansion via DECLARES edges
* Revert "fix: restore CALLS edges in full graph view; feat: CLASS expansion via DECLARES edges"
This reverts commit 5957e01.
* fix: restore CALLS edges in full graph view
* fix: add stale-async guard to useGraphFilter, test external node filtering
* fix: expand all FILE containers by default, children inside containers with CALLS visible
* fix: set data.isExpanded in layoutGraph so FileContainerNode shows correct arrow
* feat: FILE nodes collapsed by default; revert getCollapsedView to FILE-level view when nothing expanded
* fix: include transitive descendants in FILE container bounding box and overlap shift; add LAYOUT_DIRECTION constant
* fix: recursive descendants in getCollapsedView; add arrow markers to edges
* docs: update BACKLOG.md with Phase 7, known bugs, and migration plan
* docs: add UI redesign design spec — multi-repo graph explorer
4-PR plan: zustand foundation, IslandLayoutEngine, multi-namespace
ingestion, ego-subgraph overlay (issue #30).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: add 4-part UI redesign implementation plans
PR1: zustand foundation + bug fixes
PR2: IslandLayoutEngine (bin-packing)
PR3: multi-repo namespace + cgis serve
PR4: ego-subgraph overlay (issue #30)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(ui): add zustand dependency
* feat(ui): add zustand store with graph/layout/filter/flow/ui slices
Implements useGraphStore with 5 slices covering graph data ingestion,
layout state, edge/namespace filtering, flow navigation cache, and
UI view modes. All 8 unit tests pass via TDD.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add GraphProvider — loads graph.json into zustand store
* fix(ui): stable edge IDs and markerEnd in both view mappers
- Replace index-based IDs (e-${index}, flow-${index}) with content-hash stable IDs (source→target:type)
to maintain React key stability when filtering edges
- Add missing markerEnd with ArrowClosed to mapEdgeToFlowView for arrow heads in flow view
- Update markerEnd in mapEdgeToReactFlow to include stroke color for visual consistency
- Update existing tests to match new ID format
- Add comprehensive TDD test suite in __tests__/edgeMapper.test.ts
Fixes both bugs from Task 4.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): move ALL_EDGE_TYPES to module constant, add EdgeTypeName type
* feat(ui): add useLayoutComputation — replaces useGraphFilter side-effect
* fix(ui): useFlowNavigation reads from store, graphVersion invalidates cache
Rewrites useFlowNavigation to read rawNodes/rawEdges/graphVersion from
the zustand store and call setFlow/setViewMode directly, eliminating
the callback-chain from App.tsx. Adds graphVersion to the cache key so
cached flow data is never served after a graph reload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add GraphShell — thin orchestrator replacing App.tsx
Assembles ReactFlow canvas with all hooks (useLayoutComputation,
useFlowNavigation, useSearch, useExport, useKeyboardShortcuts),
context highlight, and panels (ControlPanel, StatsPanel, LegendPanel,
LoadingOverlay, NodeTooltip) — adapts store Set/EdgeTypeName types to
ControlPanel's string[] contract at the call site.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): wire GraphProvider into app entry point
* chore(ui): remove App.tsx, useGraphFetch, useGraphFilter — replaced by store+GraphShell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): resolve lint errors in new PR1 files
Replace React.* type references with named imports and suppress
necessary any casts in files that interface with untyped collapse/flow
modules, fixing all 17 eslint errors on feat/ui-pr1-foundation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): resolve SonarQube alerts — redundant casts, void operator, readonly props, zero fraction
* test(self-parsing): update ts graph assertions for PR1 architecture
Remove deleted files (App.tsx, useGraphFilter, useGraphFetch) and
add the new PR1 components (GraphProvider, useLayoutComputation,
GraphShell, useGraphStore) to file and function node assertions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): remove unused EdgeTypeName import, fix useCallback wrapper
SonarQube: remove unused `EdgeTypeName` import from GraphShell.tsx.
Also fixes the eslint react-hooks/use-memo rule violation by wrapping
toggleEdgeType in an inline arrow function instead of casting it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(ui): remove superpowers plans from repo, ignore docs/superpowers/
Planning artifacts don't belong in the repo. Files were tracked despite
.gitignore having only `.superpowers/` (hidden dir) — the actual path
`docs/superpowers/` has no dot prefix so the rule didn't match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(ui): remove brainstorming session log, ignore docs/session-*.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): IslandLayoutEngine — per-namespace cluster layout (PR 2/4) (#122)
* chore(ui): add CANVAS_MAX_WIDTH and ISLAND_GAP layout constants
* feat(ui): add IslandLayoutEngine — 3-phase bin-packing island layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add IslandContainerNode and NAMESPACE_COLORS
* feat(ui): wire IslandLayoutEngine, remove layout.ts
Replace layoutGraph (dagre monolith) with IslandLayoutEngine.run() in
useLayoutComputation and useFlowNavigation; register islandContainer
nodeType in GraphShell; add layout/index.ts re-export shim; delete
layout.ts and its now-obsolete layout.test.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(guardian): pass --repo to gh pr view before checkout
gh CLI has no git context before actions/checkout runs.
GITHUB_REPOSITORY provides the repo slug without needing a local clone.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): correct island container padding and header offset
- bbox width now uses only horizontal padding (FILE_CONTAINER_PADDING * 2)
- bbox height adds FILE_HEADER_HEIGHT + FILE_HEADER_GAP for the label badge
- nodes normalised to start at (pad, pad+header) so they don't collide with header
- container in run() placed at offset directly, no double-padding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(layout): add run() tests for IslandLayoutEngine + defer _expandedFiles
Five new unit tests covering: container-per-namespace, id prefix, islandNamespace
data propagation, cross-edge passthrough, and positive bbox dimensions.
Added TODO(PR4) comment on _expandedFiles explaining it is reserved for the
ego-subgraph overlay (issue #30).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(metrics): broaden findings_total regex to match any model category label
The previous regex required exact category names (Logic Bug, Test Coverage,
Type Safety, Ontology). If the model used a different label the count came
back as 0. Now matches any **[ at line start — the only place that pattern
appears in the guardian output format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Revert "fix(metrics): broaden findings_total regex to match any model category label"
This reverts commit e46bec4.
* fix(layout): apply 4 gemini-code-assist findings
1. [HIGH] Dagre center→top-left: subtract NODE_WIDTH/2 and NODE_HEIGHT/2
from dagre coordinates so nodes render at their correct position in
React Flow (dagre returns centers, React Flow expects top-left corners).
2. [MEDIUM] Container z-order: push islandContainer node before its
children so React Flow renders it in the background, not on top.
3. [MEDIUM] Sort stability: replace area sort with alphabetical namespace
sort so island positions don't shift when nodes are expanded/collapsed.
4. [MEDIUM] Dangling edges: skip edges where source or target is absent
from this.nodes to prevent React Flow warnings on missing node refs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(guardian): add Library Contract as priority-2 review category
Adds language-agnostic "Library boundary contracts" heuristic at priority (2),
between logic correctness and test coverage. Instructs the reviewer to trace
data transformations across library calls and flag convention mismatches:
units/coordinate systems, ordering assumptions, mutation ownership.
Applies to both Python (e.g. naive vs tz-aware datetimes, row/col vs x/y)
and TypeScript (e.g. center-based vs top-left coordinate systems).
Outcome of pair-review session with gemini-code-assist on PR #122, where
the Dagre center→top-left bug was missed by guardian but caught by gemini.
The root cause: guardian had no prompt category for library contract violations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(layout): use LAYOUT_DIRECTION constant in dagre graph config
Was hardcoding 'TB' while constants.ts exports LAYOUT_DIRECTION = 'LR'.
Caught by guardian's new Library Contract category.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(layout): apply 4 gemini-code-assist round-2 findings
- Defensive null check on dagre pos before accessing x/y
- Replace O(N) bboxes.find() with result.bbox from islandResults map
- Remove redundant `as object` cast when spreading node.data
- Remove redundant `as Record<string, string>` cast in IslandContainerNode
(NAMESPACE_COLORS already typed in theme.ts)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ui): make IslandLayoutEngine.run() synchronous, extract getNamespace helper
dagre.layout() is fully synchronous; no I/O occurs, so async/await
added unnecessary microtask overhead. Also extracts the duplicated
namespace extraction pattern into a private getNamespace() helper.
Suggested by gemini-code-assist round-3 review (PR #122 findings #9, #10).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): add TODO(PR3) for fileContainer wrapping, defensive position fallback
- Documents missing fileContainer wrapping logic (regression from old layout.ts):
expanded FILE nodes must emit type:'fileContainer' nodes with React Flow
parentNode relationships. FileContainerNode component already exists.
Tracked as PR3 work item.
- Guards dagre fallback path against undefined node.position with optional
chaining (node.position?.x ?? 0) to prevent TypeError on uninitialized nodes.
Findings from gemini-code-assist round-4 review (PR #122).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): fileContainer wrapping for expanded FILE nodes (PR 3/4) (#127)
* feat(ui): implement fileContainer wrapping for expanded FILE nodes (PR 3/4)
Expended FILE nodes are now transformed into fileContainer background
wrapper nodes sized to contain their children. Containers are z-ordered
before content nodes so React Flow renders them as backgrounds.
CONTAINS edges from expanded files are filtered out — the relationship
is already conveyed visually by the container.
Ports logic from the deleted layout.ts into IslandLayoutEngine.applyFileContainers().
FileContainerNode component and GraphShell registration were already in place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(ui): clarify background-node design choice in applyFileContainers
The TODO(PR3) comment described a parentNode/extent approach that was not
taken. Documents why: containers are non-draggable visual-only backgrounds,
so absolute coordinates are simpler and sufficient — same pattern as islandContainer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): allow fileContainer header clicks, use endsWith('Container') filter
- Remove pointerEvents:'none' and selectable:false from fileContainer so
users can click the header to collapse an expanded FILE node. islandContainer
keeps pointerEvents:'none' as it is purely decorative with no interactions.
- Replace explicit type union with endsWith('Container') for z-order sort,
making it robust to future container types without code changes.
Findings from gemini-code-assist review of PR #127.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): spread node.style, explicit container types, default expandedFiles param
- Spread node.style when creating fileContainer to preserve any existing
styles on the original FILE node instead of discarding them.
- Revert endsWith('Container') back to explicit type union — explicit is
more predictable and prevents accidental z-order matches if a future
non-background node type happens to end in 'Container'.
- Add default value run(expandedFiles = new Set()) to guard against
callers that omit the argument.
Findings from gemini-code-assist round-2 review of PR #127.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): fix expand — add groupId to non-FILE nodes in mapNodeToReactFlow
Child nodes (CLASS, FUNCTION, METHOD) were missing groupId in their data,
so getCollapsedView could not find them when a FILE was expanded. This fix
derives groupId from file_path using filePathToNodeId(), matching the
Python extractor's file_path_to_module_fqn() convention.
Also adds allowedHosts to vite dev server for remote access via
monitoring.zae.life and codebrain.zae.life.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(ui): move allowedHosts to VITE_ALLOWED_HOSTS env var
Hardcoded domains moved to .env.development; vite.config.js reads
VITE_ALLOWED_HOSTS (comma-separated) with empty fallback.
.env.example documents the variable (commented out).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): use loadEnv to read VITE_ALLOWED_HOSTS from .env files
process.env doesn't load .env.development automatically in vite.config.js —
switched to loadEnv(mode, cwd) so VITE_ALLOWED_HOSTS is correctly resolved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): dark theme, scrollbars, legend position, animation speed, FILE color
- index.css: html/body overflow:hidden (removes scrollbars) + dark bg fallback
- GraphShell: colorMode="dark" on ReactFlow (fixes white pane background)
- App.module.css: node opacity/transform transitions 0.2s→0.5s (smoother hover & expand)
- theme.ts: FILE node #1c2128→#1a2535, border #444c56→#4a6a8a (steel-blue, less black)
- LegendPanel: bottom-right with marginRight:210px (clears minimap, no Controls overlap)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): move legend to top-right below stats panel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): fix flow view, double border, and opacity transition
- Store raw GraphNode[]/GraphEdge[] in useGraphStore alongside React Flow
nodes/edges so useFlowNavigation can pass typed data to buildExecutionFlow
(previously rawNodes held RF nodes, causing n.name/n.namespace=undefined)
- Clear inherited FILE node styles in applyFileContainers to remove double
border on expanded fileContainer (was spreading ...node.style with inline
border/background/boxShadow)
- Smooth opacity transition on hover: 0.7s ease-in-out for nodes and edges
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): keep fileContainer children visible on hover
When hovering an expanded FILE container, children were dimming out
because only the container ID was in the connected set. Now:
- hovering a fileContainer → all child nodes (groupId matches) stay opaque
- hovering a child node → its container + siblings stay opaque
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): draggable FILE nodes and fix handle warnings
- Remove draggable:false from fileContainer so expanded FILE nodes can
be dragged; add onNodesChange to persist positions in controlled mode
- handleNodesChange fans out fileContainer drags to all child nodes by
computing delta and pushing extra position changes for each child
- FileContainerNode always renders handles (opacity:0 when expanded)
to stop RF "Couldn't create edge for target handle id: null" warnings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(health): node health scoring + heatmap color toggle (#130)
* refactor(query): extract tarjan_scc to shared _scc.py
Move build_adjacency, _pop_scc, _tarjan_step, and tarjan_scc out of
analyzer.py into a new cgis/query/_scc.py module so future consumers
(e.g. HealthScorer) can reuse them without importing from the analyzer.
Update test import to reference the new location.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(query): fix stale _tarjan_scc comment and alias clarity
* feat(health): add HealthScorer with fan_in, fan_out, depth, in_cycle
Implements per-node health metrics computed from behavioral/structural
edge analysis: fan counts (CALLS/IMPORTS/REFERENCES/USES), BFS depth
from FILE nodes via CONTAINS/DECLARES, and cycle detection via Tarjan
SCC on IMPORTS edges. Existing node metadata is preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(health): propagate in_cycle to all structural descendants, not just direct children
Replace the single-hop file_ancestor scan in enrich() with a new BFS
_compute_file_ancestors() method that walks ALL structural edges transitively,
so CLASS and METHOD nodes nested inside a cyclic FILE correctly inherit in_cycle=True.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(cli): enrich JSON graph output with health metrics
Wire HealthScorer into _write_graph_output's JSON branch so every
exported node carries fan_in, fan_out, depth, and in_cycle in its
metadata. Add integration test that ingests src/cgis and asserts
the four health keys are present on every node.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): pass node metadata through to RF node data
* feat(ui): add heatmap color scale and applyHeatmapColors utility
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): heatmap color mode toggle wired end-to-end
Add colorMode ('type'|'health') to store, wire applyHeatmapColors into
useLayoutComputation, and surface a toggle button in ControlPanel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): guard filePathToNodeId against undefined file_path
* fix(ui): grid-layout isolated nodes instead of dagre rank-0 column
* feat(ui): edge thickness by confidence, islands by FQN prefix, TypeScript graph
* fix(ui): increase fitView padding and allow deeper zoom-out (minZoom=0.02)
* fix: apply guardian review fixes for health scoring and island layout
- _compute_cycles now includes NodeType.MODULE in addition to FILE so
module-level import cycles are correctly detected
- _compute_file_ancestors seeds BFS from both FILE and MODULE roots so
descendants of MODULE nodes inherit the correct cycle flag
- IslandLayoutEngine.layoutIsland uses maxX (not maxX-minX) for grid
offsetX so isolated nodes never overlap connected nodes after normalization
- gridOffsetY aligns isolated node grid to minY of the dagre block
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>



Summary
trace <FQN>command — downstream flow tracing rendered as a Rich tree with cycle detectionimpact <FQN>command — upstream impact analysis rendered as a Rich tree with cycle detectioningestto support.dboutput (saves graph to SQLite directly, in addition to existing JSON output)Test plan
uv run pytest)cli.py— 100% coveragepipeline.py— 100% coverageingest→trace→impact🤖 Generated with Claude Code