Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Project Knowledge Graph

A locally-run web app that stores, manages, and visualizes the Granite Switch project knowledge graph — Business Goals → Requirements → ADRs → Components → Plans, plus Issues, Experiments, and Insights that connect across them.

It has two consumers:

  • A human browsing the graph in a browser (polished Cytoscape.js visualization with multiple layouts, filtering, hover previews, and a click-to-pin detail panel).
  • A Claude skill reading and writing the graph over a local HTTP API.

The stack is intentionally simple: a FastAPI backend, a buildless React + Cytoscape.js frontend loaded from CDN (no Node/npm required), and a single graph.json file for storage so the data is easy to inspect, diff, and version.

Run it (one command)

The app reads its data from a subject repo — you must tell it which repo:

bash knowledge-graph/run.sh --repo /path/to/subject-repo

(Or set the repo via env: REPO=/path/to/subject-repo bash knowledge-graph/run.sh.)

This creates a local venv, installs fastapi/uvicorn/pydantic, and starts the server. Then open:

http://localhost:8081

Use a different port with PORT=9000 bash knowledge-graph/run.sh --repo ….

The backend serves both the API and the web UI on the same port, so there is a single command for the whole app.

If --repo is missing, or the repo has no .claude/spec directory, the app fails immediately with a clear error rather than starting empty:

[knowledge-graph] ERROR: no subject repo given.
  usage: bash run.sh --repo /path/to/subject-repo   (or set REPO=…)

Where data is stored — files are the source of truth

The graph's node files live inside the subject repo, under .claude/spec/<type-dir>/<id>.md (YAML frontmatter for structured fields + outbound links, markdown body for the description):

<subject-repo>/.claude/spec/
├── business-goals/   BG-01.md …
├── requirements/     REQ-001.md …
├── adrs/             ADR-001.md …
├── components/       CMP-001.md …      ← Component files
├── plans/            PLAN-1.md …
├── issues/           ISSUE-8.md …
├── experiments/      …
└── insights/         INSIGHT-1.md …

The app resolves this as <repo-path>/.claude/spec/<type>/, where <repo-path> comes from --repo (surfaced to the backend as the KG_REPO env var). For tests you can point the backend straight at a spec dir with KG_DATA_DIR.

The app only reads these files; skills and users edit them. The editing flow is: add / edit / delete a file, then trigger a reload so the running app re-reads it into the in-memory graph:

# reload everything
curl -s -X POST http://localhost:8081/api/reload

# reload just one type (detects deletions within it)
curl -s -X POST http://localhost:8081/api/reload -d '{"types":["CMP"]}'

# reload specific files (paths relative to the repo's .claude/spec/)
curl -s -X POST http://localhost:8081/api/reload -d '{"paths":["components/CMP-005.md"]}'

The response reports exactly what changed and any per-file problems: { "added": [...], "updated": [...], "removed": [...], "unchanged": [...], "errors": [{path, error}], "total": N }. A malformed or schema-invalid file appears in errors and is skipped — it never crashes the graph. The .md files are the single source of truth; the in-memory graph is rebuilt from them on startup and on every reload.

The graph

Nodes are rendered as dark "blueprint plates" with a hairline border and a left colour tab in their type colour. Labels are ID-first: at a glance each plate shows its mono ID (CMP-005), and the full title fades in once you zoom past ~0.78 (the canvas hint bottom-left tracks this). At 74 nodes a wrapped title is unreadable when the graph is fitted to the window, so the ID — short and unique — is what stays legible. BG lanes always shows titles, since its columns are wide.

Zoom-to-fit is clamped to a legibility floor (0.52) and re-centred on the densest region: fitting the raw bounding box let a few loosely connected outliers shrink the core until nothing could be read.

Edges are directed and labeled with the relationship (supports, addresses, implements, tracks, informs, relates-to, plus the two component-to-component edges contains and depends-on). Component edges are drawn visually distinct: contains (hierarchy) is a solid cyan line with a filled arrow; depends-on is a dashed cyan line. These are the only edges that carry a meaningful subtype.

Layouts (switch in the top bar):

  • Force (gravity — the default, best for an overview of the full graph).
  • Architecture — components are the anchors. Root components spread across the top; components a parent contains are nested below it; each component's linked nodes (ADRs, REQs, issues, insights) cluster in a grid directly beneath it. Nodes linked to no component sit in an "unassigned" cluster at the far right. Answers "what is the system made of, and what touched each part."
  • BG lanes — a table-style view: one horizontal swimlane per Business Goal; within each lane that goal's chain reads left→right in fixed sub-columns (BG | REQ | COMPONENT | ADR | PLAN | Issues). Rectangles are wide and spread edge-to-edge across the window (and widen further when you collapse the detail panel); node size stays fixed and readable, and the view scrolls vertically to reach lower lanes rather than shrinking to fit. Groups all requirements of the same goal together — best for reading a large graph.
  • Hierarchy (breadth-first), Circle, Grid.

Filtering (left panel, all combinable, one-click reset): free-text search on title/ID, node-type toggles, and — after selecting a node — "Direct neighbors" or "Connected subgraph". The type toggles double as the legend — each row is a swatch in the type's colour plus its node count, so there is no separate floating legend over the canvas. Types with zero nodes stay listed (the schema still defines them) but render hollow and dimmed. The node/link readout top-right turns amber whenever a filter is actually narrowing the view. The subgraph is directed lineage: it shows only nodes on a directed path through the selected node — its ancestors up the pipeline (the goals it serves) and its descendants down (what builds on it), not the whole connected blob.

Details: hover a node for a quick callout (it follows the cursor and flips side near a window edge); hovering also lights that node's edges amber and shows their relationship labels, so you can trace connections without pinning. Click to pin the full record (all fields + links in and out) in the right panel for copying — a pinned node gets a wide halo so it stays findable in a dense field, and its own edges keep their labels. The detail panel collapses (the button) to give the graph the full window width; a ‹ Details button brings it back.

Look & feel: the UI is a dark "blueprint instrument" — a two-tier grid substrate with corner registration marks, hairline rules instead of stacked cards, Archivo for UI text and IBM Plex Mono for identifiers/readouts. The eight node-type colours (served by the API) are the only saturated colour; amber is the single UI accent, used for state rather than decoration. Motion is one staggered load-in and short transitions, and it all collapses under prefers-reduced-motion.

Node types & schema

Every node has id, type, title, description, and links (a list of { "target": "<id>", "rel": "<label>" }). Type-specific fields:

Type Required (beyond title) Optional May link to
BG (Business Goal) priority (HARD|Directional) stakeholders[]
REQ (Requirement) level (MUST|SHOULD|MAY), priority (MVP|Post-MVP|Optional), state (Satisfied|Partial|Not satisfied) satisfied_when BG
ADR status (Proposed|Accepted|Rejected|Superseded|Deprecated) confidence (High|Medium|Low), date, rationale, consequences CMP
CMP (Component) name, responsibility, code_location status (active|planned|deprecated) ADR, REQ, CMP (contains / depends-on)
PLAN status (Draft|Approved|In progress|Done|Abandoned) ADR, REQ
ISSUE number, url, state (open|closed) REQ, ADR, CMP
EXPERIMENT results_summary, outcome (success|failure|inconclusive) ADR
INSIGHT any type

The API is the authoritative source — GET /schema (or GET /schema/<type>, e.g. GET /schema/component) returns this exactly, including allowed link targets and the default relationship label per type. IDs follow the per-type prefix (BG-01, REQ-001, ADR-001, CMP-001, PLAN-1, ISSUE-1, EXP-1, INSIGHT-1); each <id>.md file is named by its id.

Components (CMP)

A Component is a high-level building block of the system (the composer, the switch layer, the per-backend decoders, the Triton kernel, …). Components describe current structure; ADRs record decisions — keep the two distinct. There should be only ~a dozen for the whole system, not one per class.

Component files live in data/components/, one file per component named by id:

---
id: CMP-005
type: CMP
name: vLLM Backend Decoder            # required; used as the node title
responsibility: The fast-inference GraniteSwitch model for vLLM …   # required
code_location: granite-switch/src/granite_switch/vllm/…             # required
status: active                        # optional: active | planned | deprecated
links:
- target: CMP-006
  rel: contains                       # hierarchy: this component contains CMP-006
- target: CMP-001
  rel: depends-on                     # dependency
- target: ADR-021                     # ADRs that shape this component
  rel: depends-on
- target: REQ-020                     # requirements it satisfies
  rel: relates-to
---
Prose describing the component (the body is the node's description).

Component edges — the only edges in the graph with a meaningful subtype:

  • contains — hierarchy (e.g. a backend contains its switch/LoRA layer). Rendered as a solid cyan line.
  • depends-on — dependency (the default rel for a bare component→component link). Rendered as a dashed cyan line.

Allowed component links: → ADR, → REQ, → CMP (contains/depends-on). Issues and Insights may link to a component like any other node.

Files are validated on load: a component missing a required field, using a bad status, or linking to a disallowed target type is reported per-file in the reload response's errors (and server log) and skipped — it never takes the graph down.

Spec graph content

The subject repo's .claude/spec/ holds the real Granite Switch data: 6 Business Goals, 28 Requirements, 21 ADRs, 8 Components, 10 Issues, and Insights. The link spine is REQ supports BG, ADR addresses CMP (every ADR points to the component(s) it shapes), CMP depends-on ADR / CMP → REQ (a component's driving decisions and the requirements it satisfies), and ISSUE tracks …. Edit the files under .claude/spec/ and call POST /api/reload to see changes; there is no separate seed or JSON cache.

HTTP API reference

JSON in, JSON out. CORS is enabled for any localhost / 127.0.0.1 origin. Base URL below assumes port 8081.

GET /schema — all node-type schemas

curl http://localhost:8081/schema

GET /schema/{type} — one node-type's init format

curl http://localhost:8081/schema/REQ
curl http://localhost:8081/schema/component   # friendly names/dirs also resolve

GET /graph — full graph (nodes + edges)

curl http://localhost:8081/graph

POST /api/reload — re-read node files into the graph

Optional body scopes it ({"types":[…]} or {"paths":[…]}); no body reloads everything. Returns {added, updated, removed, unchanged, errors, total}.

curl -X POST http://localhost:8081/api/reload -d '{"types":["CMP"]}'

GET /nodes — list nodes (optionally by type)

curl "http://localhost:8081/nodes?type=ADR"

GET /nodes/{id} — one node with its links

curl http://localhost:8081/nodes/REQ-001

POST /nodes — create a node (validated against the schema)

Body is an init object matching the type's schema, including its links. Returns the created node (with its assigned id) and HTTP 201.

curl -X POST http://localhost:8081/nodes \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "REQ",
    "title": "Streaming token output",
    "description": "The server must stream tokens as they are generated.",
    "level": "SHOULD",
    "priority": "Post-MVP",
    "state": "Not satisfied",
    "links": [{"target": "BG-02", "rel": "supports"}]
  }'

Missing/invalid fields return a clear error:

curl -X POST http://localhost:8081/nodes \
  -H 'Content-Type: application/json' \
  -d '{"type":"REQ","title":"Broken"}'
# -> 400 {"detail":"Missing required field 'level' for type REQ."}

PATCH /nodes/{id} — update fields on an existing node

curl -X PATCH http://localhost:8081/nodes/REQ-010 \
  -H 'Content-Type: application/json' \
  -d '{"state": "Satisfied"}'

DELETE /nodes/{id} — remove a node and its edges (404 if missing)

curl -X DELETE http://localhost:8081/nodes/INSIGHT-1
# -> {"deleted":"INSIGHT-1"}   (also removes every edge touching it)

POST /links — add a link between two existing nodes

curl -X POST http://localhost:8081/links \
  -H 'Content-Type: application/json' \
  -d '{"source":"EXP-1","target":"ADR-004","rel":"informs"}'

DELETE /links — remove a link (404 if it doesn't exist)

curl -X DELETE http://localhost:8081/links \
  -H 'Content-Type: application/json' \
  -d '{"source":"EXP-1","target":"ADR-004"}'

Using it from Claude

A companion skill at .claude/skills/knowledge-graph/SKILL.md documents how a Claude skill creates, links, and queries nodes via this API. Note: .claude/ is gitignored in this repo, so the skill lives locally and is not committed — copy it elsewhere if you want to version it.

Notes

  • Single-user local tool; no auth. Writes to graph.json are atomic (temp-file + rename) but not hardened for heavy concurrent writers — fine for one human plus one skill.
  • No build step: the frontend loads React, Babel-standalone, and Cytoscape.js from CDN, so an internet connection is needed on first page load.

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages