mirror of
https://github.com/safishamsi/graphify.git
synced 2026-07-16 12:27:06 +00:00
watch: auto-rebuild graph on code changes without LLM, notify on doc/image changes
This commit is contained in:
@@ -69,7 +69,7 @@ When the user types `/graphify`, invoke the Skill tool with `skill: "graphify"`
|
||||
/graphify path "DigestAuth" "Response"
|
||||
/graphify explain "SwinTransformer"
|
||||
|
||||
/graphify ./raw --watch # auto-update graph whenever files change
|
||||
/graphify ./raw --watch # auto-sync graph as files change (code: instant, docs: notifies you)
|
||||
/graphify ./raw --wiki # build agent-crawlable wiki (index.md + article per community)
|
||||
/graphify ./raw --svg # export graph.svg
|
||||
/graphify ./raw --graphml # export graph.graphml (Gephi, yEd)
|
||||
@@ -96,6 +96,8 @@ Works with any mix of file types:
|
||||
|
||||
**Token benchmark** - printed automatically after every run. On a mixed corpus (Karpathy repos + papers + images): **71.5x** fewer tokens per query vs reading raw files.
|
||||
|
||||
**Auto-sync** (`--watch`) - run in a background terminal and the graph updates itself as your codebase changes. Code file saves trigger an instant rebuild (AST only, no LLM). Doc/image changes notify you to run `--update` for the LLM re-pass. Useful for agentic workflows where multiple agents are writing code in parallel - the graph stays current between waves automatically.
|
||||
|
||||
**Wiki** (`--wiki`) - Wikipedia-style markdown articles per community and god node, with an `index.md` entry point. Point any agent at `index.md` and it can navigate the knowledge base by reading files instead of parsing JSON.
|
||||
|
||||
Every edge is tagged `EXTRACTED`, `INFERRED`, or `AMBIGUOUS` - you always know what was found vs guessed.
|
||||
@@ -108,7 +110,7 @@ Every edge is tagged `EXTRACTED`, `INFERRED`, or `AMBIGUOUS` - you always know w
|
||||
| graphify source + Transformer paper | 4 | **5.4x** | [`worked/mixed-corpus/`](worked/mixed-corpus/) |
|
||||
| httpx (synthetic Python library) | 6 | ~1x | [`worked/httpx/`](worked/httpx/) |
|
||||
|
||||
Token reduction scales with corpus size. 6 files fits in a context window anyway — graph value there is structural clarity, not compression. At 52 files (code + papers + images) you get 71x+. Each `worked/` folder has the raw input files and the actual output (`GRAPH_REPORT.md`, `graph.json`) so you can run it yourself and verify the numbers.
|
||||
Token reduction scales with corpus size. 6 files fits in a context window anyway, so graph value there is structural clarity, not compression. At 52 files (code + papers + images) you get 71x+. Each `worked/` folder has the raw input files and the actual output (`GRAPH_REPORT.md`, `graph.json`) so you can run it yourself and verify the numbers.
|
||||
|
||||
## Tech stack
|
||||
|
||||
|
||||
+11
-4
@@ -23,7 +23,7 @@ Turn any folder of files into a navigable knowledge graph with community detecti
|
||||
/graphify <path> --neo4j # generate graphify-out/cypher.txt for Neo4j
|
||||
/graphify <path> --neo4j-push bolt://localhost:7687 # push directly to Neo4j
|
||||
/graphify <path> --mcp # start MCP stdio server for agent access
|
||||
/graphify <path> --watch # watch folder, notify when files change
|
||||
/graphify <path> --watch # watch folder, auto-rebuild on code changes (no LLM needed)
|
||||
/graphify add <url> # fetch URL, save to ./raw, update graph
|
||||
/graphify add <url> --author "Name" # tag who wrote it
|
||||
/graphify add <url> --contributor "Name" # tag who added it to the corpus
|
||||
@@ -1100,15 +1100,22 @@ Supported URL types (auto-detected):
|
||||
|
||||
## For --watch
|
||||
|
||||
Start a background watcher that monitors a folder and auto-reruns `--update` when files change.
|
||||
Start a background watcher that monitors a folder and auto-updates the graph when files change.
|
||||
|
||||
```bash
|
||||
python3 -m graphify.watch INPUT_PATH --debounce 3
|
||||
```
|
||||
|
||||
Replace INPUT_PATH with the folder to watch. Every time a supported file is added or modified, graphify waits `debounce` seconds (default 3) after the last change, then runs the `--update` pipeline automatically. Press Ctrl+C to stop.
|
||||
Replace INPUT_PATH with the folder to watch. Behavior depends on what changed:
|
||||
|
||||
For the personal inspo use case: leave this running in a terminal. Drop tweets, screenshots, papers, and notes into the folder throughout the day - the graph updates itself.
|
||||
- **Code files only (.py, .ts, .go, etc.):** re-runs AST extraction + rebuild + cluster immediately, no LLM needed. `graph.json` and `GRAPH_REPORT.md` are updated automatically.
|
||||
- **Docs, papers, or images:** writes a `graphify-out/needs_update` flag and prints a notification to run `/graphify --update` (LLM semantic re-extraction required).
|
||||
|
||||
Debounce (default 3s): waits until file activity stops before triggering, so a wave of parallel agent writes doesn't trigger a rebuild per file.
|
||||
|
||||
Press Ctrl+C to stop.
|
||||
|
||||
For agentic workflows: run `--watch` in a background terminal. Code changes from agent waves are picked up automatically between waves. If agents are also writing docs or notes, you'll need a manual `/graphify --update` after those waves.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+99
-5
@@ -1,5 +1,6 @@
|
||||
# monitor a folder and auto-trigger --update when files change
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
@@ -11,20 +12,100 @@ _WATCHED_EXTENSIONS = {
|
||||
".png", ".jpg", ".jpeg", ".webp", ".gif", ".svg",
|
||||
}
|
||||
|
||||
_CODE_EXTENSIONS = {
|
||||
".py", ".ts", ".js", ".go", ".rs", ".java", ".cpp", ".c", ".rb", ".swift", ".kt",
|
||||
".cs", ".scala", ".php", ".cc", ".cxx", ".hpp", ".h", ".kts",
|
||||
}
|
||||
|
||||
def _run_update(watch_path: Path) -> None:
|
||||
"""Write a flag file and print a notification when files change."""
|
||||
|
||||
def _rebuild_code(watch_path: Path) -> bool:
|
||||
"""Re-run AST extraction + build + cluster + report for code files. No LLM needed.
|
||||
|
||||
Returns True on success, False on error.
|
||||
"""
|
||||
try:
|
||||
from graphify.extract import collect_files, extract
|
||||
from graphify.build import build_from_json
|
||||
from graphify.cluster import cluster, score_all
|
||||
from graphify.analyze import god_nodes, surprising_connections, suggest_questions
|
||||
from graphify.report import generate
|
||||
from graphify.export import to_json
|
||||
|
||||
code_files = []
|
||||
for ext in _CODE_EXTENSIONS:
|
||||
code_files.extend(watch_path.rglob(f"*{ext}"))
|
||||
code_files = [
|
||||
f for f in code_files
|
||||
if not any(part.startswith(".") for part in f.parts)
|
||||
and "graphify-out" not in f.parts
|
||||
and "__pycache__" not in f.parts
|
||||
]
|
||||
|
||||
if not code_files:
|
||||
print("[graphify watch] No code files found - nothing to rebuild.")
|
||||
return False
|
||||
|
||||
result = extract(code_files)
|
||||
|
||||
detection = {
|
||||
"files": {"code": [str(f) for f in code_files], "document": [], "paper": [], "image": []},
|
||||
"total_files": len(code_files),
|
||||
"total_words": sum(len(f.read_text(errors="ignore").split()) for f in code_files),
|
||||
}
|
||||
|
||||
G = build_from_json(result)
|
||||
communities = cluster(G)
|
||||
cohesion = score_all(G, communities)
|
||||
gods = god_nodes(G)
|
||||
surprises = surprising_connections(G, communities)
|
||||
labels = {cid: "Community " + str(cid) for cid in communities}
|
||||
questions = suggest_questions(G, communities, labels)
|
||||
|
||||
out = watch_path / "graphify-out"
|
||||
out.mkdir(exist_ok=True)
|
||||
|
||||
report = generate(G, communities, cohesion, labels, gods, surprises, detection,
|
||||
{"input": 0, "output": 0}, str(watch_path), suggested_questions=questions)
|
||||
(out / "GRAPH_REPORT.md").write_text(report)
|
||||
to_json(G, communities, str(out / "graph.json"))
|
||||
|
||||
# clear stale needs_update flag if present
|
||||
flag = out / "needs_update"
|
||||
if flag.exists():
|
||||
flag.unlink()
|
||||
|
||||
print(f"[graphify watch] Rebuilt: {G.number_of_nodes()} nodes, "
|
||||
f"{G.number_of_edges()} edges, {len(communities)} communities")
|
||||
print(f"[graphify watch] graph.json and GRAPH_REPORT.md updated in {out}")
|
||||
return True
|
||||
|
||||
except Exception as exc:
|
||||
print(f"[graphify watch] Rebuild failed: {exc}")
|
||||
return False
|
||||
|
||||
|
||||
def _notify_only(watch_path: Path) -> None:
|
||||
"""Write a flag file and print a notification (fallback for non-code-only corpora)."""
|
||||
flag = watch_path / "graphify-out" / "needs_update"
|
||||
flag.parent.mkdir(parents=True, exist_ok=True)
|
||||
flag.write_text("1")
|
||||
print(f"\n[graphify watch] New or changed files detected in {watch_path}")
|
||||
print("[graphify watch] Non-code files changed - semantic re-extraction requires LLM.")
|
||||
print("[graphify watch] Run `/graphify --update` in Claude Code to update the graph.")
|
||||
print(f"[graphify watch] Flag written to {flag}")
|
||||
|
||||
|
||||
def _has_non_code(changed_paths: list[Path]) -> bool:
|
||||
return any(p.suffix.lower() not in _CODE_EXTENSIONS for p in changed_paths)
|
||||
|
||||
|
||||
def watch(watch_path: Path, debounce: float = 3.0) -> None:
|
||||
"""
|
||||
Watch watch_path for new or modified files and re-run graphify --update.
|
||||
Watch watch_path for new or modified files and auto-update the graph.
|
||||
|
||||
For code-only changes: re-runs AST extraction + rebuild immediately (no LLM).
|
||||
For doc/paper/image changes: writes a needs_update flag and notifies the user
|
||||
to run /graphify --update (LLM extraction required).
|
||||
|
||||
debounce: seconds to wait after the last change before triggering (avoids
|
||||
running on every keystroke when many files are saved at once).
|
||||
@@ -37,6 +118,7 @@ def watch(watch_path: Path, debounce: float = 3.0) -> None:
|
||||
|
||||
last_trigger: float = 0.0
|
||||
pending: bool = False
|
||||
changed: list[Path] = []
|
||||
|
||||
class Handler(FileSystemEventHandler):
|
||||
def on_any_event(self, event):
|
||||
@@ -48,8 +130,12 @@ def watch(watch_path: Path, debounce: float = 3.0) -> None:
|
||||
return
|
||||
if any(part.startswith(".") for part in path.parts):
|
||||
return
|
||||
if "graphify-out" in path.parts:
|
||||
return
|
||||
last_trigger = time.monotonic()
|
||||
pending = True
|
||||
if path not in changed:
|
||||
changed.append(path)
|
||||
|
||||
handler = Handler()
|
||||
observer = Observer()
|
||||
@@ -57,14 +143,22 @@ def watch(watch_path: Path, debounce: float = 3.0) -> None:
|
||||
observer.start()
|
||||
|
||||
print(f"[graphify watch] Watching {watch_path.resolve()} - press Ctrl+C to stop")
|
||||
print(f"[graphify watch] Debounce: {debounce}s - will update {debounce}s after last change")
|
||||
print(f"[graphify watch] Code changes rebuild graph automatically. "
|
||||
f"Doc/image changes require /graphify --update.")
|
||||
print(f"[graphify watch] Debounce: {debounce}s")
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(0.5)
|
||||
if pending and (time.monotonic() - last_trigger) >= debounce:
|
||||
pending = False
|
||||
_run_update(watch_path)
|
||||
batch = list(changed)
|
||||
changed.clear()
|
||||
print(f"\n[graphify watch] {len(batch)} file(s) changed")
|
||||
if _has_non_code(batch):
|
||||
_notify_only(watch_path)
|
||||
else:
|
||||
_rebuild_code(watch_path)
|
||||
except KeyboardInterrupt:
|
||||
print("\n[graphify watch] Stopped.")
|
||||
finally:
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "graphifyy"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
description = "Claude Code skill - turn any folder of code, docs, papers, images, or tweets into a queryable knowledge graph"
|
||||
readme = "README.md"
|
||||
license = { text = "MIT" }
|
||||
|
||||
+19
-132
@@ -20,11 +20,10 @@ Turn any folder of files into a navigable knowledge graph with community detecti
|
||||
/graphify <path> --html # (HTML is generated by default - this flag is a no-op)
|
||||
/graphify <path> --svg # also export graph.svg (embeds in Notion, GitHub)
|
||||
/graphify <path> --graphml # export graph.graphml (Gephi, yEd)
|
||||
/graphify <path> --wiki # export agent-crawlable wiki (index.md + article per community + god nodes)
|
||||
/graphify <path> --neo4j # generate graphify-out/cypher.txt for Neo4j
|
||||
/graphify <path> --neo4j-push bolt://localhost:7687 # push directly to Neo4j
|
||||
/graphify <path> --mcp # start MCP stdio server for agent access
|
||||
/graphify <path> --watch # watch folder, notify when files change
|
||||
/graphify <path> --watch # watch folder, auto-rebuild on code changes (no LLM needed)
|
||||
/graphify add <url> # fetch URL, save to ./raw, update graph
|
||||
/graphify add <url> --author "Name" # tag who wrote it
|
||||
/graphify add <url> --contributor "Name" # tag who added it to the corpus
|
||||
@@ -523,42 +522,7 @@ print('graph.graphml written - open in Gephi, yEd, or any GraphML tool')
|
||||
"
|
||||
```
|
||||
|
||||
### Step 7d - Wiki export (only if --wiki flag)
|
||||
|
||||
Generates a Wikipedia-style markdown wiki: one article per community, one per god node, plus an `index.md` entry point for agents to start from. Inspired by the Farzapedia pattern — structure the knowledge so an agent can navigate it like a file system it understands.
|
||||
|
||||
```bash
|
||||
python3 -c "
|
||||
import json
|
||||
from graphify.build import build_from_json
|
||||
from graphify.analyze import god_nodes
|
||||
from graphify.wiki import to_wiki
|
||||
from pathlib import Path
|
||||
|
||||
extraction = json.loads(Path('.graphify_extract.json').read_text())
|
||||
analysis = json.loads(Path('.graphify_analysis.json').read_text())
|
||||
labels_raw = json.loads(Path('.graphify_labels.json').read_text()) if Path('.graphify_labels.json').exists() else {}
|
||||
|
||||
G = build_from_json(extraction)
|
||||
communities = {int(k): v for k, v in analysis['communities'].items()}
|
||||
cohesion = {int(k): v for k, v in analysis['cohesion'].items()}
|
||||
labels = {int(k): v for k, v in labels_raw.items()}
|
||||
gods = god_nodes(G, top_n=20)
|
||||
|
||||
n = to_wiki(G, communities, 'graphify-out/wiki', community_labels=labels or None, cohesion=cohesion, god_nodes_data=gods)
|
||||
print(f'Wiki: {n} articles written to graphify-out/wiki/')
|
||||
print('Start at graphify-out/wiki/index.md')
|
||||
"
|
||||
```
|
||||
|
||||
The wiki contains:
|
||||
- `index.md` — catalog of all communities and god nodes; agent entry point
|
||||
- `<CommunityName>.md` — key concepts, cross-community links, source files, audit trail
|
||||
- `<GodNodeLabel>.md` — all connections grouped by relation type, community membership
|
||||
|
||||
To use with an agent: point it at `index.md` and tell it to navigate the wiki to answer questions about the corpus. Works with Claude Code, Claude Desktop, or any agent that can read markdown files.
|
||||
|
||||
### Step 7e - MCP server (only if --mcp flag)
|
||||
### Step 7d - MCP server (only if --mcp flag)
|
||||
|
||||
```bash
|
||||
python3 -m graphify.serve graphify-out/graph.json
|
||||
@@ -641,25 +605,18 @@ rm -f graphify-out/.needs_update 2>/dev/null || true
|
||||
|
||||
Tell the user:
|
||||
```
|
||||
Graph complete. Outputs are in graphify-out/ inside the directory you ran this on.
|
||||
Graph complete. Outputs are in a hidden folder called graphify-out/ inside the directory you ran this on.
|
||||
|
||||
The folder is hidden (dot prefix) so it won't show in Finder or a normal ls.
|
||||
To see it:
|
||||
Mac/Linux: ls -la graphify-out/
|
||||
VS Code: the Explorer panel shows hidden files by default
|
||||
Finder: Cmd+Shift+. to toggle hidden files
|
||||
|
||||
What's inside:
|
||||
graphify-out/obsidian/ - open as a vault in Obsidian (File > Open Vault)
|
||||
graphify-out/graph.html - interactive graph, open in any browser
|
||||
graphify-out/GRAPH_REPORT.md - full audit report
|
||||
graphify-out/graph.json - raw graph data
|
||||
|
||||
What you can do next:
|
||||
/graphify <path> --wiki build a Wikipedia-style wiki agents can navigate (index.md + articles)
|
||||
/graphify <path> --update re-extract only new/changed files, merge into existing graph
|
||||
/graphify <path> --watch auto-update graph whenever files change
|
||||
/graphify add <url> fetch a URL and add it to the corpus
|
||||
/graphify query "<question>" BFS search of the graph
|
||||
/graphify path "ConceptA" "ConceptB" shortest path between two concepts
|
||||
/graphify explain "<node>" plain-language explanation of any node
|
||||
/graphify <path> --mcp start MCP server so other agents can query the graph live
|
||||
/graphify <path> --neo4j export Cypher for Neo4j import
|
||||
/graphify <path> --graphml export GraphML for Gephi/yEd
|
||||
graphify-out/obsidian/ - open this folder as a vault in Obsidian (File > Open Vault)
|
||||
graphify-out/GRAPH_REPORT.md - full audit report, also readable here in Claude
|
||||
graphify-out/graph.json - persistent graph, query it later with /graphify query "..."
|
||||
|
||||
Full path: PATH_TO_DIR/graphify-out/
|
||||
```
|
||||
@@ -730,34 +687,6 @@ print(f'Merged: {G_existing.number_of_nodes()} nodes, {G_existing.number_of_edge
|
||||
|
||||
Then run Steps 4–8 on the merged graph as normal.
|
||||
|
||||
After Step 8, if `graphify-out/wiki/` already exists, regenerate the wiki automatically:
|
||||
|
||||
```bash
|
||||
python3 -c "
|
||||
import json
|
||||
from graphify.build import build_from_json
|
||||
from graphify.analyze import god_nodes
|
||||
from graphify.wiki import to_wiki
|
||||
from pathlib import Path
|
||||
|
||||
if not Path('graphify-out/wiki').exists():
|
||||
raise SystemExit(0) # wiki was never built, skip
|
||||
|
||||
extraction = json.loads(Path('.graphify_extract.json').read_text())
|
||||
analysis = json.loads(Path('.graphify_analysis.json').read_text())
|
||||
labels_raw = json.loads(Path('.graphify_labels.json').read_text()) if Path('.graphify_labels.json').exists() else {}
|
||||
|
||||
G = build_from_json(extraction)
|
||||
communities = {int(k): v for k, v in analysis['communities'].items()}
|
||||
cohesion = {int(k): v for k, v in analysis['cohesion'].items()}
|
||||
labels = {int(k): v for k, v in labels_raw.items()}
|
||||
gods = god_nodes(G, top_n=20)
|
||||
|
||||
n = to_wiki(G, communities, 'graphify-out/wiki', community_labels=labels or None, cohesion=cohesion, god_nodes_data=gods)
|
||||
print(f'Wiki updated: {n} articles in graphify-out/wiki/')
|
||||
"
|
||||
```
|
||||
|
||||
After Step 4, show the graph diff:
|
||||
|
||||
```bash
|
||||
@@ -1171,64 +1100,22 @@ Supported URL types (auto-detected):
|
||||
|
||||
## For --watch
|
||||
|
||||
Start a background watcher that monitors a folder and auto-reruns `--update` when files change.
|
||||
Start a background watcher that monitors a folder and auto-updates the graph when files change.
|
||||
|
||||
```bash
|
||||
python3 -m graphify.watch INPUT_PATH --debounce 3
|
||||
```
|
||||
|
||||
Replace INPUT_PATH with the folder to watch. Every time a supported file is added or modified, graphify waits `debounce` seconds (default 3) after the last change, then runs the `--update` pipeline automatically. Press Ctrl+C to stop.
|
||||
Replace INPUT_PATH with the folder to watch. Behavior depends on what changed:
|
||||
|
||||
For the personal inspo use case: leave this running in a terminal. Drop tweets, screenshots, papers, and notes into the folder throughout the day - the graph updates itself.
|
||||
- **Code files only (.py, .ts, .go, etc.):** re-runs AST extraction + rebuild + cluster immediately, no LLM needed. `graph.json` and `GRAPH_REPORT.md` are updated automatically.
|
||||
- **Docs, papers, or images:** writes a `graphify-out/needs_update` flag and prints a notification to run `/graphify --update` (LLM semantic re-extraction required).
|
||||
|
||||
---
|
||||
Debounce (default 3s): waits until file activity stops before triggering, so a wave of parallel agent writes doesn't trigger a rebuild per file.
|
||||
|
||||
## Answering Follow-up Questions After the Pipeline
|
||||
Press Ctrl+C to stop.
|
||||
|
||||
**After the pipeline completes, ALL follow-up questions about the corpus MUST be answered from the graph — not by re-reading files or re-exploring the directory.**
|
||||
|
||||
Do NOT use Glob, Grep, Read, Bash, or the Explore agent to answer questions about the corpus content. The graph already has the information. Re-exploring the directory defeats the entire purpose of graphify and wastes time.
|
||||
|
||||
**If `graphify-out/wiki/index.md` exists, use the wiki — it is more readable than raw JSON.**
|
||||
|
||||
Start at `index.md`, read the relevant community article(s), then drill into god node articles as needed. This is faster and more accurate than parsing graph.json because the articles are already structured for agent consumption.
|
||||
|
||||
If the wiki does not exist, load and query `graphify-out/graph.json` directly:
|
||||
|
||||
```python
|
||||
import json
|
||||
from pathlib import Path
|
||||
from networkx.readwrite import json_graph
|
||||
import networkx as nx
|
||||
|
||||
G = json_graph.node_link_graph(json.loads(Path("graphify-out/graph.json").read_text()), edges="links")
|
||||
```
|
||||
|
||||
Then answer using graph data:
|
||||
- **"What X are in this repo?"** → filter nodes by `file_type`, `label`, `source_file`, or node attributes
|
||||
- **"How does X work?"** → find matching nodes, get their neighbors and edge relations
|
||||
- **"What calls Y?"** → traverse edges with `relation == "calls"` pointing to Y
|
||||
- **"What are the main themes?"** → read community labels from the GRAPH_REPORT.md or node `community` attributes
|
||||
- **"Find verbs / functions / classes / etc."** → filter `G.nodes(data=True)` by label patterns
|
||||
|
||||
Example — finding all verbs (action concepts) in a codebase:
|
||||
```python
|
||||
from collections import Counter
|
||||
|
||||
# Node labels are plain names like "run", "render", "resolve" — no "def"/"fn" prefix
|
||||
# Extract the first word of each function label (e.g. "load_graph" → "load")
|
||||
verb_counts = Counter()
|
||||
for _, d in G.nodes(data=True):
|
||||
if d.get("file_type") == "code":
|
||||
first_word = d.get("label", "").split("_")[0].split(".")[0].lower()
|
||||
if first_word and first_word.isalpha():
|
||||
verb_counts[first_word] += 1
|
||||
|
||||
for verb, count in verb_counts.most_common(20):
|
||||
print(f"{count:>4}x {verb}")
|
||||
```
|
||||
|
||||
**The only exception:** if the user explicitly asks you to look at a raw file (e.g., "show me the contents of X"), you may read that specific file. But for any analytical question, use the graph.
|
||||
For agentic workflows: run `--watch` in a background terminal. Code changes from agent waves are picked up automatically between waves. If agents are also writing docs or notes, you'll need a manual `/graphify --update` after those waves.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+9
-9
@@ -3,26 +3,26 @@ import time
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from graphify.watch import _run_update, _WATCHED_EXTENSIONS
|
||||
from graphify.watch import _notify_only, _WATCHED_EXTENSIONS
|
||||
|
||||
|
||||
# --- _run_update ---
|
||||
# --- _notify_only ---
|
||||
|
||||
def test_run_update_creates_flag(tmp_path):
|
||||
_run_update(tmp_path)
|
||||
def test_notify_only_creates_flag(tmp_path):
|
||||
_notify_only(tmp_path)
|
||||
flag = tmp_path / "graphify-out" / "needs_update"
|
||||
assert flag.exists()
|
||||
assert flag.read_text() == "1"
|
||||
|
||||
def test_run_update_creates_flag_dir(tmp_path):
|
||||
def test_notify_only_creates_flag_dir(tmp_path):
|
||||
# graphify-out dir does not exist yet
|
||||
assert not (tmp_path / "graphify-out").exists()
|
||||
_run_update(tmp_path)
|
||||
_notify_only(tmp_path)
|
||||
assert (tmp_path / "graphify-out").is_dir()
|
||||
|
||||
def test_run_update_idempotent(tmp_path):
|
||||
_run_update(tmp_path)
|
||||
_run_update(tmp_path)
|
||||
def test_notify_only_idempotent(tmp_path):
|
||||
_notify_only(tmp_path)
|
||||
_notify_only(tmp_path)
|
||||
flag = tmp_path / "graphify-out" / "needs_update"
|
||||
assert flag.read_text() == "1"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user