Skip to content

Commit 9eaa6a4

Browse files
Clean up repo metadata for Rootly fork
- Point pyproject.toml URLs and description to Rootly-AI-Labs - Rewrite ARCHITECTURE.md and SECURITY.md for Rootly pipeline - Add Rootly copyright to LICENSE, keep upstream credit - Add Rootly context to AGENTS.md - Remove upstream demo data (worked/) - Remove redundant README sections
1 parent b267021 commit 9eaa6a4

36 files changed

Lines changed: 57 additions & 12787 deletions

AGENTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
# graphify
1+
# rootly-graphify
22

3-
This project has a graphify knowledge graph at graphify-out/.
3+
This project turns Rootly incident data into a knowledge graph. The Rootly corpus lives in `graphify-rootly-data/` and the graph output in `graphify-out/`.
44

55
**Always-on rules:**
6-
- Before answering architecture or codebase questions, read `graphify-out/GRAPH_REPORT.md` for god nodes and community structure
6+
- Before answering incident or architecture questions, read `graphify-out/GRAPH_REPORT.md` for god nodes and community structure
77
- If `graphify-out/wiki/index.md` exists, navigate it instead of reading raw files
88
- After modifying code files, run: `python -c "from graphify.watch import _rebuild_code; from pathlib import Path; _rebuild_code(Path('.'))"` to keep the graph current
99

ARCHITECTURE.md

Lines changed: 37 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,67 @@
11
# Architecture
22

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).
44

55
## Pipeline
66

77
```
8-
detect() extract()build_graph() → cluster() → analyze() → report() → export()
8+
Rootly API rootly_exportrootly_runner → cluster() → analyze() → report() → export()
99
```
1010

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-
1311
## Module responsibilities
1412

1513
| Module | Function | Input → Output |
1614
|--------|----------|----------------|
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 |
2017
| `cluster.py` | `cluster(G)` | graph → graph with `community` attr on each node |
2118
| `analyze.py` | `analyze(G)` | graph → analysis dict (god nodes, surprises, questions) |
2219
| `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 |
4621

47-
`validate.py` enforces this schema before `build_graph()` consumes it.
22+
## Rootly collection (`rootly_export.py`)
4823

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
5031

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`)
5633

57-
## Adding a new language extractor
34+
Reads incident JSON and creates a typed graph:
5835

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)
6442

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
6650

67-
All external input passes through `graphify/security.py` before use:
51+
## Confidence labels
6852

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 |
7358

74-
See `SECURITY.md` for the full threat model.
59+
## Optional deep enrichment
7560

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.
7762

78-
One test file per module under `tests/`. Run with:
63+
## Testing
7964

8065
```bash
8166
pytest tests/ -q
8267
```
83-
84-
All tests are pure unit tests - no network calls, no file system side effects outside `tmp_path`.

LICENSE

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
MIT License
22

3-
Copyright (c) 2026 Safi Shamsi
3+
Copyright (c) 2026 Rootly, Inc.
4+
Copyright (c) 2026 Safi Shamsi (graphify)
45

56
Permission is hereby granted, free of charge, to any person obtaining a copy
67
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,6 @@ run graphify on graphify-rootly-data --mode deep
7777
```
7878

7979

80-
---
81-
82-
### Step 3: Open the current graph output
83-
84-
After semantic enrichment, use the top-level output in `graphify-out/graph.html`. The current generic visualization already includes the maintained filters and visuals, so no separate re-apply command is needed.
85-
8680
---
8781

8882
## What you can explore
@@ -195,6 +189,3 @@ graphify rootly --api-key-env ROOTLY_API_KEY --days 30 --mode standard
195189
/graphify graphify-rootly-data --update
196190
```
197191

198-
---
199-
200-
Cloned from [graphify](https://github.com/safishamsi/graphify)

SECURITY.md

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
# Security Policy
22

3-
## Supported Versions
4-
5-
| Version | Supported |
6-
|---------|-----------|
7-
| 0.3.x | Yes |
8-
| < 0.3 | No |
9-
103
## Reporting a Vulnerability
114

125
**Do not open a public GitHub issue for security vulnerabilities.**
@@ -22,32 +15,20 @@ We will acknowledge receipt within 48 hours and aim to release a fix within 7 da
2215

2316
## Security Model
2417

25-
graphify is a **local development tool**. It runs as a Claude Code skill and optionally as a local MCP stdio server. It makes no network calls during graph analysis - only during `ingest` (explicit URL fetch by the user).
18+
rootly-graphify is a **local development tool**. It fetches data from the Rootly API during collection, then all graph analysis runs locally with no further network calls.
2619

2720
### Threat Surface
2821

2922
| Vector | Mitigation |
3023
|--------|-----------|
31-
| SSRF via URL fetch | `security.validate_url()` allows only `http` and `https` schemes, blocks private/loopback/link-local IPs, and blocks cloud metadata endpoints. Redirect targets are re-validated. All fetch paths including tweet oEmbed go through `safe_fetch()`. |
32-
| Oversized downloads | `safe_fetch()` streams responses and aborts at 50 MB. `safe_fetch_text()` aborts at 10 MB. |
33-
| Non-2xx HTTP responses | `safe_fetch()` raises `HTTPError` on non-2xx status codes - error pages are not silently treated as content. |
34-
| Path traversal in MCP server | `security.validate_graph_path()` resolves paths and requires them to be inside `graphify-out/`. Also requires the `graphify-out/` directory to exist. |
35-
| XSS in graph HTML output | `security.sanitize_label()` strips control characters, caps at 256 chars, and HTML-escapes all node labels and edge titles before pyvis embeds them. |
36-
| Prompt injection via node labels | `sanitize_label()` also applied to MCP text output - node labels from user-controlled source files cannot break the text format returned to agents. |
37-
| YAML frontmatter injection | `_yaml_str()` escapes backslashes, double quotes, and newlines before embedding user-controlled strings (webpage titles, query questions) in YAML frontmatter. |
38-
| Encoding crashes on source files | All tree-sitter byte slices decoded with `errors="replace"` - non-UTF-8 source files degrade gracefully instead of crashing extraction. |
39-
| Symlink traversal | `os.walk(..., followlinks=False)` is explicit throughout `detect.py`. |
40-
| Corrupted graph.json | `_load_graph()` in `serve.py` wraps `json.JSONDecodeError` and prints a clear recovery message instead of crashing. |
41-
42-
### What graphify does NOT do
43-
44-
- Does not run a network listener (MCP server communicates over stdio only)
45-
- Does not execute code from source files (tree-sitter parses ASTs - no eval/exec)
46-
- Does not use `shell=True` in any subprocess call
47-
- Does not store credentials or API keys
24+
| API key exposure | Key read from `.env` or environment variable, masked in logs, never written to graph output |
25+
| XSS in graph HTML output | `security.sanitize_label()` strips control characters, caps at 256 chars, and HTML-escapes all node labels and edge titles |
26+
| Path traversal in MCP server | `security.validate_graph_path()` resolves paths and requires them to be inside `graphify-out/` |
27+
| Encoding crashes on source files | All tree-sitter byte slices decoded with `errors="replace"` |
4828

49-
### Optional network calls
29+
### What rootly-graphify does NOT do
5030

51-
- `ingest` subcommand: fetches URLs explicitly provided by the user
52-
- PDF extraction: reads local files only (pypdf does not make network calls)
53-
- watch mode: local filesystem events only (watchdog does not make network calls)
31+
- Does not store your API key in any output file
32+
- Does not execute code from source files (tree-sitter parses ASTs — no eval/exec)
33+
- Does not use `shell=True` in any subprocess call
34+
- Does not make network calls during graph analysis — only during the initial Rootly API fetch

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ build-backend = "setuptools.build_meta"
55
[project]
66
name = "graphifyy"
77
version = "0.3.6"
8-
description = "AI coding assistant skill (Claude Code, Codex, OpenCode, OpenClaw) - turn any folder of code, docs, papers, or images into a queryable knowledge graph"
8+
description = "Turn Rootly incidents, alerts, and teams into a queryable knowledge graph"
99
readme = "README.md"
1010
license = { file = "LICENSE" }
11-
keywords = ["claude", "claude-code", "codex", "opencode", "knowledge-graph", "rag", "graphrag", "obsidian", "community-detection", "tree-sitter", "leiden", "llm"]
11+
keywords = ["rootly", "incidents", "knowledge-graph", "claude-code", "codex", "graphrag", "community-detection", "incident-management", "on-call", "sre"]
1212
requires-python = ">=3.10"
1313
dependencies = [
1414
"networkx",
@@ -34,9 +34,9 @@ dependencies = [
3434
]
3535

3636
[project.urls]
37-
Homepage = "https://github.com/safishamsi/graphify"
38-
Repository = "https://github.com/safishamsi/graphify"
39-
Issues = "https://github.com/safishamsi/graphify/issues"
37+
Homepage = "https://github.com/Rootly-AI-Labs/rootly-graphify-importer"
38+
Repository = "https://github.com/Rootly-AI-Labs/rootly-graphify-importer"
39+
Issues = "https://github.com/Rootly-AI-Labs/rootly-graphify-importer/issues"
4040

4141
[project.optional-dependencies]
4242
mcp = ["mcp"]

worked/example/README.md

Lines changed: 0 additions & 56 deletions
This file was deleted.

worked/example/raw/api.py

Lines changed: 0 additions & 78 deletions
This file was deleted.

0 commit comments

Comments
 (0)