|
1 | 1 | # Architecture |
2 | 2 |
|
3 | | -graphify is a Claude Code skill backed by a Python library. The skill orchestrates the library; the library can be used standalone. |
| 3 | +rootly-graphify connects the Rootly API to the graphify knowledge graph pipeline. It has two phases: collection (Rootly API → local corpus) and graph analysis (corpus → knowledge graph). |
4 | 4 |
|
5 | 5 | ## Pipeline |
6 | 6 |
|
7 | 7 | ``` |
8 | | -detect() → extract() → build_graph() → cluster() → analyze() → report() → export() |
| 8 | +Rootly API → rootly_export → rootly_runner → cluster() → analyze() → report() → export() |
9 | 9 | ``` |
10 | 10 |
|
11 | | -Each stage is a single function in its own module. They communicate through plain Python dicts and NetworkX graphs - no shared state, no side effects outside `graphify-out/`. |
12 | | - |
13 | 11 | ## Module responsibilities |
14 | 12 |
|
15 | 13 | | Module | Function | Input → Output | |
16 | 14 | |--------|----------|----------------| |
17 | | -| `detect.py` | `collect_files(root)` | directory → `[Path]` filtered list | |
18 | | -| `extract.py` | `extract(path)` | file path → `{nodes, edges}` dict | |
19 | | -| `build.py` | `build_graph(extractions)` | list of extraction dicts → `nx.Graph` | |
| 15 | +| `rootly_export.py` | `run_rootly_export()` | Rootly API → local corpus (markdown + JSON) | |
| 16 | +| `rootly_runner.py` | `rootly_build_graph()` | corpus → `nx.Graph` with typed nodes and edges | |
20 | 17 | | `cluster.py` | `cluster(G)` | graph → graph with `community` attr on each node | |
21 | 18 | | `analyze.py` | `analyze(G)` | graph → analysis dict (god nodes, surprises, questions) | |
22 | 19 | | `report.py` | `render_report(G, analysis)` | graph + analysis → GRAPH_REPORT.md string | |
23 | | -| `export.py` | `export(G, out_dir, ...)` | graph → Obsidian vault, graph.json, graph.html, graph.svg | |
24 | | -| `ingest.py` | `ingest(url, ...)` | URL → file saved to corpus dir | |
25 | | -| `cache.py` | `check_semantic_cache / save_semantic_cache` | files → (cached, uncached) split | |
26 | | -| `security.py` | validation helpers | URL / path / label → validated or raises | |
27 | | -| `validate.py` | `validate_extraction(data)` | extraction dict → raises on schema errors | |
28 | | -| `serve.py` | `start_server(graph_path)` | graph file path → MCP stdio server | |
29 | | -| `watch.py` | `watch(root, flag_path)` | directory → writes flag file on change | |
30 | | -| `benchmark.py` | `run_benchmark(graph_path)` | graph file → corpus vs subgraph token comparison | |
31 | | - |
32 | | -## Extraction output schema |
33 | | - |
34 | | -Every extractor returns: |
35 | | - |
36 | | -```json |
37 | | -{ |
38 | | - "nodes": [ |
39 | | - {"id": "unique_string", "label": "human name", "source_file": "path", "source_location": "L42"} |
40 | | - ], |
41 | | - "edges": [ |
42 | | - {"source": "id_a", "target": "id_b", "relation": "calls|imports|uses|...", "confidence": "EXTRACTED|INFERRED|AMBIGUOUS"} |
43 | | - ] |
44 | | -} |
45 | | -``` |
| 20 | +| `export.py` | `export(G, out_dir, ...)` | graph → graph.json, graph.html | |
46 | 21 |
|
47 | | -`validate.py` enforces this schema before `build_graph()` consumes it. |
| 22 | +## Rootly collection (`rootly_export.py`) |
48 | 23 |
|
49 | | -## Confidence labels |
| 24 | +1. Validate API key (from `.env` or `--api-key-env`) |
| 25 | +2. Prompt for time window (7, 30, or 90 days) |
| 26 | +3. Fetch incidents whose `started_at` falls inside the window |
| 27 | +4. Fetch triggered alerts per incident via sub-resource endpoint |
| 28 | +5. Fetch all teams |
| 29 | +6. Write markdown + raw JSON to corpus directory |
| 30 | +7. Write `.graphifyignore` to exclude retrospectives from graph |
50 | 31 |
|
51 | | -| Label | Meaning | |
52 | | -|-------|---------| |
53 | | -| `EXTRACTED` | Relationship is explicitly stated in the source (e.g., an import statement, a direct call) | |
54 | | -| `INFERRED` | Relationship is a reasonable deduction (e.g., call-graph second pass, co-occurrence in context) | |
55 | | -| `AMBIGUOUS` | Relationship is uncertain; flagged for human review in GRAPH_REPORT.md | |
| 32 | +## Rootly graph build (`rootly_runner.py`) |
56 | 33 |
|
57 | | -## Adding a new language extractor |
| 34 | +Reads incident JSON and creates a typed graph: |
58 | 35 |
|
59 | | -1. Add a `extract_<lang>(path: Path) -> dict` function in `extract.py` following the existing pattern (tree-sitter parse → walk nodes → collect `nodes` and `edges` → call-graph second pass for INFERRED `calls` edges). |
60 | | -2. Register the file suffix in `extract()` dispatch and `collect_files()`. |
61 | | -3. Add the suffix to `CODE_EXTENSIONS` in `detect.py` and `_WATCHED_EXTENSIONS` in `watch.py`. |
62 | | -4. Add the tree-sitter package to `pyproject.toml` dependencies. |
63 | | -5. Add a fixture file to `tests/fixtures/` and tests to `tests/test_languages.py`. |
| 36 | +**Nodes:** |
| 37 | +- Incident (labeled by title, colored by severity) |
| 38 | +- Alert (linked to triggering incident) |
| 39 | +- Team (organizational unit) |
| 40 | +- Service (affected infrastructure) |
| 41 | +- Severity level (SEV0–SEV3) |
64 | 42 |
|
65 | | -## Security |
| 43 | +**Edges:** |
| 44 | +- `triggered` — alert → incident |
| 45 | +- `affects` — incident → service |
| 46 | +- `owns` — team → service |
| 47 | +- `responded_by` — incident → team |
| 48 | +- `has_severity` — incident → severity |
| 49 | +- `assigned_to_team` — incident → team |
66 | 50 |
|
67 | | -All external input passes through `graphify/security.py` before use: |
| 51 | +## Confidence labels |
68 | 52 |
|
69 | | -- URLs → `validate_url()` (http/https only) + `_NoFileRedirectHandler` (blocks file:// redirects) |
70 | | -- Fetched content → `safe_fetch()` / `safe_fetch_text()` (size cap, timeout) |
71 | | -- Graph file paths → `validate_graph_path()` (must resolve inside `graphify-out/`) |
72 | | -- Node labels → `sanitize_label()` (strips control chars, caps 256 chars, HTML-escapes) |
| 53 | +| Label | Meaning | |
| 54 | +|-------|---------| |
| 55 | +| `EXTRACTED` | Relationship directly from Rootly API data (severity, team assignment) | |
| 56 | +| `INFERRED` | Reasonable deduction (shared service, co-occurrence) | |
| 57 | +| `AMBIGUOUS` | Uncertain — flagged for review | |
73 | 58 |
|
74 | | -See `SECURITY.md` for the full threat model. |
| 59 | +## Optional deep enrichment |
75 | 60 |
|
76 | | -## Testing |
| 61 | +Running `/graphify ./corpus --mode deep` dispatches parallel subagents over the markdown files to infer cross-incident themes, rationale, and conceptual links. This adds `INFERRED` and `semantically_similar_to` edges on top of the deterministic graph. |
77 | 62 |
|
78 | | -One test file per module under `tests/`. Run with: |
| 63 | +## Testing |
79 | 64 |
|
80 | 65 | ```bash |
81 | 66 | pytest tests/ -q |
82 | 67 | ``` |
83 | | - |
84 | | -All tests are pure unit tests - no network calls, no file system side effects outside `tmp_path`. |
0 commit comments