mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-25 06:55:43 +00:00
feat: always-on hooks and README updates for all platforms
This commit is contained in:
@@ -15,3 +15,7 @@ build/
|
||||
.graphify/
|
||||
graphify-out/
|
||||
.graphify_*.json
|
||||
.graphify_python
|
||||
.claude/
|
||||
skills/
|
||||
docs/superpowers/
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# Changelog
|
||||
|
||||
Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases)
|
||||
|
||||
## 0.3.0 (2026-04-06)
|
||||
|
||||
- Add: multi-platform support — Codex (`skill-codex.md`), OpenCode (`skill-opencode.md`), OpenClaw (`skill-claw.md`)
|
||||
- Add: `graphify install --platform <codex|opencode|claw>` routes skill to correct config directory
|
||||
- Add: `graphify codex install` / `opencode install` / `claw install` — writes AGENTS.md for always-on graph-first behaviour
|
||||
- Add: `graphify claude uninstall` / `codex uninstall` / `opencode uninstall` / `claw uninstall`
|
||||
- Add: MIT license
|
||||
- Fix: `build()` was silently dropping hyperedges when merging multiple extractions
|
||||
- Refactor: `extract.py` 2527 → 1588 lines — replaced 12 copy-pasted language extractors with `LanguageConfig` dataclass + `_extract_generic()`
|
||||
- Docs: clustering is graph-topology-based (no embeddings) — explained in README
|
||||
- Docs: all missing flags documented (`--cluster-only`, `--no-viz`, `--neo4j-push`, `query --dfs`, `query --budget`, `add --author`, `add --contributor`)
|
||||
|
||||
## 0.2.2 (2026-04-06)
|
||||
|
||||
- Add: `graphify claude install` — writes graphify section to local CLAUDE.md + PreToolUse hook in `.claude/settings.json`
|
||||
- Add: `graphify claude uninstall` — removes section and hook
|
||||
- Add: `graphify hook install` — installs post-commit and post-checkout git hooks (platform-agnostic)
|
||||
- Add: `graphify hook uninstall` / `hook status`
|
||||
- Add: `graphify benchmark` CLI command
|
||||
- Fix: node deduplication documented at all three layers
|
||||
|
||||
## 0.1.8 (2026-04-05)
|
||||
|
||||
- Fix: follow-up questions now check for wiki first (graphify-out/wiki/index.md) before falling back to graph.json
|
||||
|
||||
@@ -25,11 +25,13 @@ graphify-out/
|
||||
|
||||
graphify runs in two passes. First, a deterministic AST pass extracts structure from code files (classes, functions, imports, call graphs, docstrings, rationale comments) with no LLM needed. Second, Claude subagents run in parallel over docs, papers, and images to extract concepts, relationships, and design rationale. The results are merged into a NetworkX graph, clustered with Leiden community detection, and exported as interactive HTML, queryable JSON, and a plain-language audit report.
|
||||
|
||||
**Clustering is graph-topology-based — no embeddings.** Leiden finds communities by edge density. The semantic similarity edges that Claude extracts (`semantically_similar_to`, marked INFERRED) are already in the graph, so they influence community detection directly. The graph structure is the similarity signal — no separate embedding step or vector database needed.
|
||||
|
||||
Every relationship is tagged `EXTRACTED` (found directly in source), `INFERRED` (reasonable inference, with a confidence score), or `AMBIGUOUS` (flagged for review). You always know what was found vs guessed.
|
||||
|
||||
## Install
|
||||
|
||||
**Requires:** [Claude Code](https://claude.ai/code) and Python 3.10+
|
||||
**Requires:** Python 3.10+ and one of: [Claude Code](https://claude.ai/code), [Codex](https://openai.com/codex), [OpenCode](https://opencode.ai), or [OpenClaw](https://openclaw.ai)
|
||||
|
||||
```bash
|
||||
pip install graphifyy && graphify install
|
||||
@@ -54,23 +56,22 @@ Then open your AI coding assistant and type:
|
||||
/graphify .
|
||||
```
|
||||
|
||||
### Make Claude always use the graph (recommended)
|
||||
### Make your assistant always use the graph (recommended)
|
||||
|
||||
After building a graph, run this once in your project:
|
||||
|
||||
```bash
|
||||
graphify claude install
|
||||
```
|
||||
| Platform | Command |
|
||||
|----------|---------|
|
||||
| Claude Code | `graphify claude install` |
|
||||
| Codex | `graphify codex install` |
|
||||
| OpenCode | `graphify opencode install` |
|
||||
| OpenClaw | `graphify claw install` |
|
||||
|
||||
This does two things:
|
||||
**Claude Code** does two things: writes a `CLAUDE.md` section telling Claude to read `graphify-out/GRAPH_REPORT.md` before answering architecture questions, and installs a **PreToolUse hook** (`settings.json`) that fires before every Glob and Grep call. If a knowledge graph exists, Claude sees: _"graphify: Knowledge graph exists. Read GRAPH_REPORT.md for god nodes and community structure before searching raw files."_ — so Claude navigates via the graph instead of grepping through every file.
|
||||
|
||||
1. **CLAUDE.md rules** - tells Claude to read `graphify-out/GRAPH_REPORT.md` before answering architecture questions, and to rebuild the graph after editing code files.
|
||||
**Codex, OpenCode, OpenClaw** write the same rules to `AGENTS.md` in your project root. These platforms don't support PreToolUse hooks, so AGENTS.md is the always-on mechanism.
|
||||
|
||||
2. **PreToolUse hook** (`settings.json`) - fires automatically before every Glob and Grep call. If a knowledge graph exists, Claude sees: _"graphify: Knowledge graph exists. Read GRAPH_REPORT.md for god nodes and community structure before searching raw files."_ This means Claude navigates via the graph instead of grepping through every file - faster answers, fewer wasted tool calls, and responses grounded in the actual structure of your codebase rather than keyword matches.
|
||||
|
||||
Without this, Claude will grep raw files by default even when a graph exists. With it, the graph becomes the first thing Claude reaches for.
|
||||
|
||||
Uninstall with `graphify claude uninstall`.
|
||||
Uninstall with the matching uninstall command (e.g. `graphify claude uninstall`).
|
||||
|
||||
<details>
|
||||
<summary>Manual install (curl)</summary>
|
||||
@@ -97,12 +98,18 @@ When the user types `/graphify`, invoke the Skill tool with `skill: "graphify"`
|
||||
/graphify ./raw # run on a specific folder
|
||||
/graphify ./raw --mode deep # more aggressive INFERRED edge extraction
|
||||
/graphify ./raw --update # re-extract only changed files, merge into existing graph
|
||||
/graphify ./raw --cluster-only # rerun clustering on existing graph, no re-extraction
|
||||
/graphify ./raw --no-viz # skip HTML, just produce report + JSON
|
||||
/graphify ./raw --obsidian # also generate Obsidian vault (opt-in)
|
||||
|
||||
/graphify add https://arxiv.org/abs/1706.03762 # fetch a paper, save, update graph
|
||||
/graphify add https://x.com/karpathy/status/... # fetch a tweet
|
||||
/graphify add https://... --author "Name" # tag the original author
|
||||
/graphify add https://... --contributor "Name" # tag who added it to the corpus
|
||||
|
||||
/graphify query "what connects attention to the optimizer?"
|
||||
/graphify query "what connects attention to the optimizer?" --dfs # trace a specific path
|
||||
/graphify query "what connects attention to the optimizer?" --budget 1500 # cap at N tokens
|
||||
/graphify path "DigestAuth" "Response"
|
||||
/graphify explain "SwinTransformer"
|
||||
|
||||
@@ -111,10 +118,20 @@ When the user types `/graphify`, invoke the Skill tool with `skill: "graphify"`
|
||||
/graphify ./raw --svg # export graph.svg
|
||||
/graphify ./raw --graphml # export graph.graphml (Gephi, yEd)
|
||||
/graphify ./raw --neo4j # generate cypher.txt for Neo4j
|
||||
/graphify ./raw --neo4j-push bolt://localhost:7687 # push directly to a running Neo4j instance
|
||||
/graphify ./raw --mcp # start MCP stdio server
|
||||
|
||||
graphify hook install # git hooks - rebuilds graph on commit and branch switch
|
||||
graphify claude install # always-on: CLAUDE.md + PreToolUse hook for this project
|
||||
# git hooks - platform-agnostic, rebuild graph on commit and branch switch
|
||||
graphify hook install
|
||||
graphify hook uninstall
|
||||
graphify hook status
|
||||
|
||||
# always-on assistant instructions - platform-specific
|
||||
graphify claude install # CLAUDE.md + PreToolUse hook (Claude Code)
|
||||
graphify claude uninstall
|
||||
graphify codex install # AGENTS.md (Codex)
|
||||
graphify opencode install # AGENTS.md (OpenCode)
|
||||
graphify claw install # AGENTS.md (OpenClaw)
|
||||
```
|
||||
|
||||
Works with any mix of file types:
|
||||
@@ -162,11 +179,11 @@ Token reduction scales with corpus size. 6 files fits in a context window anyway
|
||||
|
||||
## Privacy
|
||||
|
||||
graphify sends file contents to the Claude API (Anthropic) for semantic extraction of docs, papers, and images. Code files are processed locally via tree-sitter AST — no file contents leave your machine for code. No telemetry, usage tracking, or analytics of any kind. The only network calls are to Anthropic's API during extraction, using your own API key via Claude Code.
|
||||
graphify sends file contents to your AI coding assistant's underlying model API for semantic extraction of docs, papers, and images — Anthropic (Claude Code), OpenAI (Codex), or whichever provider your platform uses. Code files are processed locally via tree-sitter AST — no file contents leave your machine for code. No telemetry, usage tracking, or analytics of any kind. The only network calls are to your platform's model API during extraction, using your own API key.
|
||||
|
||||
## Tech stack
|
||||
|
||||
NetworkX + Leiden (graspologic) + tree-sitter + Claude + vis.js. No Neo4j required, no server, runs entirely locally.
|
||||
NetworkX + Leiden (graspologic) + tree-sitter + vis.js. Semantic extraction via Claude (Claude Code), GPT-4 (Codex), or whichever model your platform runs. No Neo4j required, no server, runs entirely locally.
|
||||
|
||||
<details>
|
||||
<summary>Contributing</summary>
|
||||
|
||||
@@ -1,581 +0,0 @@
|
||||
# v3 Platform Compatibility Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add Codex, OpenCode, and OpenClaw platform support via platform-specific skill files and a `graphify install --platform X` flag.
|
||||
|
||||
**Architecture:** The only section that differs between platforms is Step B2 (semantic extraction subagent dispatch) in skill.md. Three new skill files are created — one per platform — each identical to skill.md except for that one section. The `install()` function in `__main__.py` gains a `--platform` flag that copies the right skill file to the right config directory.
|
||||
|
||||
**Tech Stack:** Python 3.10+, pathlib, shutil, argparse (no new deps)
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
| File | Action | Purpose |
|
||||
|------|--------|---------|
|
||||
| `graphify/skill.md` | Read-only | Source of truth — unchanged |
|
||||
| `graphify/skill-codex.md` | Create | Codex variant (spawn_agent + wait) |
|
||||
| `graphify/skill-opencode.md` | Create | OpenCode variant (@mention dispatch) |
|
||||
| `graphify/skill-claw.md` | Create | OpenClaw variant (sequential extraction) |
|
||||
| `graphify/__main__.py` | Modify | Add --platform flag to install() and main() |
|
||||
| `pyproject.toml` | Modify | Add 3 new skill files to package-data |
|
||||
| `tests/test_install.py` | Create | Platform routing tests |
|
||||
| `README.md` | Modify | Platform table + token efficiency clarification |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Create the v3 branch
|
||||
|
||||
**Files:** none (git only)
|
||||
|
||||
- [ ] **Step 1: Create and switch to v3 branch**
|
||||
|
||||
```bash
|
||||
cd /home/safi/graphify
|
||||
git checkout -b v3
|
||||
```
|
||||
|
||||
Expected: `Switched to a new branch 'v3'`
|
||||
|
||||
- [ ] **Step 2: Verify branch**
|
||||
|
||||
```bash
|
||||
git branch --show-current
|
||||
```
|
||||
|
||||
Expected: `v3`
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Create `skill-codex.md`
|
||||
|
||||
skill-codex.md is identical to skill.md with one change: Step B2 replaces `Agent` tool calls with `spawn_agent` + `wait` + `close_agent` calls.
|
||||
|
||||
**Files:**
|
||||
- Create: `graphify/skill-codex.md`
|
||||
|
||||
- [ ] **Step 1: Copy skill.md as the base**
|
||||
|
||||
```bash
|
||||
cp graphify/skill.md graphify/skill-codex.md
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Open `graphify/skill-codex.md` and replace the Step B2 section**
|
||||
|
||||
Find this block (starts at "**Step B2 - Dispatch ALL subagents in a single message**", ends before "**Step B3**"):
|
||||
|
||||
Replace the entire Step B2 section with:
|
||||
|
||||
```markdown
|
||||
**Step B2 - Dispatch ALL subagents in a single message (Codex)**
|
||||
|
||||
> **Codex platform:** This step uses `spawn_agent` + `wait` + `close_agent` instead of the Agent tool.
|
||||
> Requires `multi_agent = true` in `~/.codex/config.toml`. If you get an error about multi-agent support, ask the user to add that config line and restart Codex.
|
||||
|
||||
Call `spawn_agent` once per chunk — all in the same response so they run in parallel:
|
||||
|
||||
```
|
||||
spawn_agent(agent_type="worker", message="Your task is to perform the following. Follow the instructions below exactly.\n\n<agent-instructions>\nYou are a graphify extraction subagent. Read the files listed and extract a knowledge graph fragment.\nOutput ONLY valid JSON matching the schema below - no explanation, no markdown fences, no preamble.\n\nFiles (chunk CHUNK_NUM of TOTAL_CHUNKS):\nFILE_LIST\n\n[copy the extraction rules and JSON schema verbatim from the existing Step B2 content — it's already in the file from the cp step]\n</agent-instructions>\n\nExecute this now. Output ONLY the structured JSON response.")
|
||||
```
|
||||
|
||||
Collect all handles. Then for each handle:
|
||||
```
|
||||
result = wait(handle)
|
||||
close_agent(handle)
|
||||
```
|
||||
|
||||
Parse each result as JSON. Accumulate nodes/edges/hyperedges across all results into `.graphify_semantic_new.json`.
|
||||
|
||||
If `spawn_agent` is not available, tell the user: "Codex multi-agent support is not enabled. Add `multi_agent = true` under `[features]` in `~/.codex/config.toml` and restart Codex."
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify the file looks correct**
|
||||
|
||||
```bash
|
||||
grep -n "spawn_agent\|Step B2\|Step B3" graphify/skill-codex.md | head -20
|
||||
```
|
||||
|
||||
Expected: lines showing spawn_agent in B2 and Step B3 after it.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add graphify/skill-codex.md
|
||||
git commit -m "add skill-codex.md for Codex platform (spawn_agent parallel extraction)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Create `skill-opencode.md`
|
||||
|
||||
**Files:**
|
||||
- Create: `graphify/skill-opencode.md`
|
||||
|
||||
- [ ] **Step 1: Copy skill.md as the base**
|
||||
|
||||
```bash
|
||||
cp graphify/skill.md graphify/skill-opencode.md
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Open `graphify/skill-opencode.md` and replace the Step B2 section**
|
||||
|
||||
Replace the entire Step B2 section with:
|
||||
|
||||
```markdown
|
||||
**Step B2 - Dispatch ALL subagents in a single message (OpenCode)**
|
||||
|
||||
> **OpenCode platform:** This step uses OpenCode's `@mention` dispatch instead of the Agent tool.
|
||||
|
||||
Dispatch all chunks in a single response. Each `@mention` runs in parallel:
|
||||
|
||||
```
|
||||
@agent Chunk CHUNK_NUM of TOTAL_CHUNKS: You are a graphify extraction subagent. Read the files listed and extract a knowledge graph fragment. Output ONLY valid JSON matching the schema below.
|
||||
|
||||
Files:
|
||||
FILE_LIST
|
||||
|
||||
[copy the extraction rules and JSON schema verbatim from the existing Step B2 content — already in the file from the cp step]
|
||||
```
|
||||
|
||||
One `@mention` block per chunk. All in the same message — this is what makes them parallel.
|
||||
|
||||
Wait for all agents to return. Parse each response as JSON. Accumulate nodes/edges/hyperedges across all results into `.graphify_semantic_new.json`.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify the file looks correct**
|
||||
|
||||
```bash
|
||||
grep -n "@mention\|Step B2\|Step B3" graphify/skill-opencode.md | head -20
|
||||
```
|
||||
|
||||
Expected: lines showing @mention in B2 and Step B3 after it.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add graphify/skill-opencode.md
|
||||
git commit -m "add skill-opencode.md for OpenCode platform (@mention parallel extraction)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Create `skill-claw.md`
|
||||
|
||||
OpenClaw's agent support is MVP/incomplete so extraction is sequential — the orchestrating LLM reads each file and extracts directly.
|
||||
|
||||
**Files:**
|
||||
- Create: `graphify/skill-claw.md`
|
||||
|
||||
- [ ] **Step 1: Copy skill.md as the base**
|
||||
|
||||
```bash
|
||||
cp graphify/skill.md graphify/skill-claw.md
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Open `graphify/skill-claw.md` and replace the Step B2 section**
|
||||
|
||||
Replace the entire Step B2 section with:
|
||||
|
||||
```markdown
|
||||
**Step B2 - Sequential extraction (OpenClaw)**
|
||||
|
||||
> **OpenClaw platform:** OpenClaw's multi-agent support is still early. Extraction runs sequentially — you read each file yourself and extract directly. This is slower than parallel platforms but reliable.
|
||||
|
||||
Load files from `.graphify_uncached.txt`. For each file, one at a time:
|
||||
|
||||
1. Read the file contents
|
||||
2. Extract nodes, edges, and hyperedges following the same rules and schema as the parallel variant (see schema below)
|
||||
3. Accumulate results into a running JSON object
|
||||
|
||||
Apply all the same extraction rules:
|
||||
- EXTRACTED / INFERRED / AMBIGUOUS confidence with confidence_score on every edge
|
||||
- rationale_for nodes for design decisions and WHY comments
|
||||
- semantically_similar_to edges for cross-file conceptual links (non-obvious only)
|
||||
- hyperedges for groups of 3+ nodes (max 3 per file)
|
||||
- DEEP_MODE: more aggressive INFERRED edges if --mode deep was given
|
||||
|
||||
Schema (same as parallel variant):
|
||||
{"nodes":[{"id":"filestem_entityname","label":"Human Readable Name","file_type":"code|document|paper|image","source_file":"relative/path","source_location":null,"source_url":null,"captured_at":null,"author":null,"contributor":null}],"edges":[{"source":"node_id","target":"node_id","relation":"calls|implements|references|cites|conceptually_related_to|shares_data_with|semantically_similar_to|rationale_for","confidence":"EXTRACTED|INFERRED|AMBIGUOUS","confidence_score":1.0,"source_file":"relative/path","source_location":null,"weight":1.0}],"hyperedges":[{"id":"snake_case_id","label":"Human Readable Label","nodes":["node_id1","node_id2","node_id3"],"relation":"participate_in|implement|form","confidence":"EXTRACTED|INFERRED","confidence_score":0.75,"source_file":"relative/path"}],"input_tokens":0,"output_tokens":0}
|
||||
|
||||
After processing all files, write the accumulated result to `.graphify_semantic_new.json`.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Also remove the timing estimate block from Step B**
|
||||
|
||||
In skill-claw.md, find and remove this paragraph (it only applies to parallel dispatch):
|
||||
|
||||
```
|
||||
Before dispatching subagents, print a timing estimate:
|
||||
- Load `total_words` and file counts from `.graphify_detect.json`
|
||||
- Estimate agents needed: `ceil(uncached_non_code_files / 22)` (chunk size is 20-25)
|
||||
- Estimate time: ~45s per agent batch (they run in parallel, so total ≈ 45s × ceil(agents/parallel_limit))
|
||||
- Print: "Semantic extraction: ~N files → X agents, estimated ~Ys"
|
||||
```
|
||||
|
||||
Replace with:
|
||||
|
||||
```
|
||||
Print: "Semantic extraction: N files (sequential — OpenClaw platform)"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify**
|
||||
|
||||
```bash
|
||||
grep -n "sequential\|Step B2\|Step B3\|spawn_agent\|@mention" graphify/skill-claw.md | head -20
|
||||
```
|
||||
|
||||
Expected: "sequential" appears in B2, no spawn_agent or @mention.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add graphify/skill-claw.md
|
||||
git commit -m "add skill-claw.md for OpenClaw platform (sequential extraction)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Update `pyproject.toml` package-data
|
||||
|
||||
**Files:**
|
||||
- Modify: `pyproject.toml`
|
||||
|
||||
- [ ] **Step 1: Update package-data to include the three new skill files**
|
||||
|
||||
In `pyproject.toml`, find:
|
||||
|
||||
```toml
|
||||
[tool.setuptools.package-data]
|
||||
graphify = ["skill.md"]
|
||||
```
|
||||
|
||||
Replace with:
|
||||
|
||||
```toml
|
||||
[tool.setuptools.package-data]
|
||||
graphify = ["skill.md", "skill-codex.md", "skill-opencode.md", "skill-claw.md"]
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify**
|
||||
|
||||
```bash
|
||||
grep -A2 "package-data" pyproject.toml
|
||||
```
|
||||
|
||||
Expected: all four skill files listed.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add pyproject.toml
|
||||
git commit -m "include platform skill files in package-data"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Add `--platform` flag to install command
|
||||
|
||||
**Files:**
|
||||
- Modify: `graphify/__main__.py`
|
||||
|
||||
- [ ] **Step 1: Write the failing test first**
|
||||
|
||||
Create `tests/test_install.py`:
|
||||
|
||||
```python
|
||||
"""Tests for graphify install --platform routing."""
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
PLATFORMS = {
|
||||
"claude": ("skill.md", ".claude/skills/graphify/SKILL.md"),
|
||||
"codex": ("skill-codex.md", ".agents/skills/graphify/SKILL.md"),
|
||||
"opencode": ("skill-opencode.md", ".config/opencode/skills/graphify/SKILL.md"),
|
||||
"claw": ("skill-claw.md", ".claw/skills/graphify/SKILL.md"),
|
||||
}
|
||||
|
||||
|
||||
def test_install_default_uses_claude_skill(tmp_path):
|
||||
"""install() with no platform copies skill.md to ~/.claude/skills/graphify/SKILL.md"""
|
||||
from graphify.__main__ import install
|
||||
with patch("graphify.__main__.Path.home", return_value=tmp_path):
|
||||
install(platform="claude")
|
||||
dst = tmp_path / ".claude" / "skills" / "graphify" / "SKILL.md"
|
||||
assert dst.exists()
|
||||
|
||||
|
||||
def test_install_codex_copies_correct_file(tmp_path):
|
||||
from graphify.__main__ import install
|
||||
with patch("graphify.__main__.Path.home", return_value=tmp_path):
|
||||
install(platform="codex")
|
||||
dst = tmp_path / ".agents" / "skills" / "graphify" / "SKILL.md"
|
||||
assert dst.exists()
|
||||
|
||||
|
||||
def test_install_opencode_copies_correct_file(tmp_path):
|
||||
from graphify.__main__ import install
|
||||
with patch("graphify.__main__.Path.home", return_value=tmp_path):
|
||||
install(platform="opencode")
|
||||
dst = tmp_path / ".config" / "opencode" / "skills" / "graphify" / "SKILL.md"
|
||||
assert dst.exists()
|
||||
|
||||
|
||||
def test_install_claw_copies_correct_file(tmp_path):
|
||||
from graphify.__main__ import install
|
||||
with patch("graphify.__main__.Path.home", return_value=tmp_path):
|
||||
install(platform="claw")
|
||||
dst = tmp_path / ".claw" / "skills" / "graphify" / "SKILL.md"
|
||||
assert dst.exists()
|
||||
|
||||
|
||||
def test_install_unknown_platform_exits(tmp_path):
|
||||
from graphify.__main__ import install
|
||||
with patch("graphify.__main__.Path.home", return_value=tmp_path):
|
||||
with pytest.raises(SystemExit):
|
||||
install(platform="unknown")
|
||||
|
||||
|
||||
def test_all_skill_files_exist_in_package():
|
||||
"""Verify all platform skill files are present in the installed package."""
|
||||
import graphify
|
||||
pkg_dir = Path(graphify.__file__).parent
|
||||
for src_name, _ in PLATFORMS.values():
|
||||
skill_path = pkg_dir / src_name
|
||||
assert skill_path.exists(), f"Missing skill file: {src_name}"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the test to verify it fails**
|
||||
|
||||
```bash
|
||||
python -m pytest tests/test_install.py -v --tb=short 2>&1 | head -40
|
||||
```
|
||||
|
||||
Expected: FAIL — `install()` doesn't accept a `platform` argument yet.
|
||||
|
||||
- [ ] **Step 3: Update `install()` in `graphify/__main__.py`**
|
||||
|
||||
Replace the current `install()` function and add `_PLATFORM_CONFIG`:
|
||||
|
||||
```python
|
||||
_PLATFORM_CONFIG = {
|
||||
"claude": {
|
||||
"skill_file": "skill.md",
|
||||
"skill_dst": Path(".claude") / "skills" / "graphify" / "SKILL.md",
|
||||
"claude_md": True, # only Claude Code gets CLAUDE.md registration
|
||||
},
|
||||
"codex": {
|
||||
"skill_file": "skill-codex.md",
|
||||
"skill_dst": Path(".agents") / "skills" / "graphify" / "SKILL.md",
|
||||
"claude_md": False,
|
||||
},
|
||||
"opencode": {
|
||||
"skill_file": "skill-opencode.md",
|
||||
"skill_dst": Path(".config") / "opencode" / "skills" / "graphify" / "SKILL.md",
|
||||
"claude_md": False,
|
||||
},
|
||||
"claw": {
|
||||
"skill_file": "skill-claw.md",
|
||||
"skill_dst": Path(".claw") / "skills" / "graphify" / "SKILL.md",
|
||||
"claude_md": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def install(platform: str = "claude") -> None:
|
||||
if platform not in _PLATFORM_CONFIG:
|
||||
print(f"error: unknown platform '{platform}'. Choose from: {', '.join(_PLATFORM_CONFIG)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
cfg = _PLATFORM_CONFIG[platform]
|
||||
skill_src = Path(__file__).parent / cfg["skill_file"]
|
||||
if not skill_src.exists():
|
||||
print(f"error: {cfg['skill_file']} not found in package - reinstall graphify", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
skill_dst = Path.home() / cfg["skill_dst"]
|
||||
skill_dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy(skill_src, skill_dst)
|
||||
print(f" skill installed → {skill_dst}")
|
||||
|
||||
if cfg["claude_md"]:
|
||||
# Register in ~/.claude/CLAUDE.md (Claude Code only)
|
||||
claude_md = Path.home() / ".claude" / "CLAUDE.md"
|
||||
if claude_md.exists():
|
||||
content = claude_md.read_text()
|
||||
if "graphify" in content:
|
||||
print(f" CLAUDE.md → already registered (no change)")
|
||||
else:
|
||||
claude_md.write_text(content.rstrip() + _SKILL_REGISTRATION)
|
||||
print(f" CLAUDE.md → skill registered in {claude_md}")
|
||||
else:
|
||||
claude_md.parent.mkdir(parents=True, exist_ok=True)
|
||||
claude_md.write_text(_SKILL_REGISTRATION.lstrip())
|
||||
print(f" CLAUDE.md → created at {claude_md}")
|
||||
|
||||
print()
|
||||
print("Done. Open your AI coding assistant and type:")
|
||||
print()
|
||||
print(" /graphify .")
|
||||
print()
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Update `main()` to pass `--platform` to `install()`**
|
||||
|
||||
In `main()`, find the `if cmd == "install":` block:
|
||||
|
||||
```python
|
||||
if cmd == "install":
|
||||
install()
|
||||
```
|
||||
|
||||
Replace with:
|
||||
|
||||
```python
|
||||
if cmd == "install":
|
||||
platform = "claude"
|
||||
args = sys.argv[2:]
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if args[i].startswith("--platform="):
|
||||
platform = args[i].split("=", 1)[1]
|
||||
i += 1
|
||||
elif args[i] == "--platform" and i + 1 < len(args):
|
||||
platform = args[i + 1]
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
install(platform=platform)
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Update the help text in `main()`**
|
||||
|
||||
Find:
|
||||
```python
|
||||
print(" install copy skill to ~/.claude/skills/ and register in CLAUDE.md")
|
||||
```
|
||||
|
||||
Replace with:
|
||||
```python
|
||||
print(" install [--platform P] copy skill to platform config dir (claude|codex|opencode|claw)")
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Run tests to verify they pass**
|
||||
|
||||
```bash
|
||||
python -m pytest tests/test_install.py -v --tb=short
|
||||
```
|
||||
|
||||
Expected: all 6 tests PASS.
|
||||
|
||||
- [ ] **Step 7: Run the full test suite to check for regressions**
|
||||
|
||||
```bash
|
||||
python -m pytest tests/ -q --tb=short 2>&1 | tail -20
|
||||
```
|
||||
|
||||
Expected: existing tests still pass.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add graphify/__main__.py tests/test_install.py
|
||||
git commit -m "add --platform flag to graphify install (codex, opencode, claw)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Update README
|
||||
|
||||
**Files:**
|
||||
- Modify: `README.md`
|
||||
|
||||
- [ ] **Step 1: Add platform support table under the Install section**
|
||||
|
||||
After the `pip install graphifyy && graphify install` code block, add:
|
||||
|
||||
```markdown
|
||||
### Platform support
|
||||
|
||||
| Platform | Install command |
|
||||
|----------|----------------|
|
||||
| Claude Code | `graphify install` |
|
||||
| Codex | `graphify install --platform codex` |
|
||||
| OpenCode | `graphify install --platform opencode` |
|
||||
| OpenClaw | `graphify install --platform claw` |
|
||||
|
||||
Codex users also need `multi_agent = true` under `[features]` in `~/.codex/config.toml` for parallel extraction. OpenClaw uses sequential extraction (parallel agent support is still early on that platform).
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Clarify token efficiency — find the benchmark section**
|
||||
|
||||
Find the line:
|
||||
```
|
||||
**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.
|
||||
```
|
||||
|
||||
Replace with:
|
||||
```
|
||||
**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. The first run extracts and builds the graph (this costs tokens). Every subsequent query reads the compact graph instead of raw files — that's where the savings compound. The SHA256 cache means re-runs only re-process changed files.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify README renders correctly**
|
||||
|
||||
```bash
|
||||
grep -n "Platform support\|multi_agent\|first run extracts" README.md
|
||||
```
|
||||
|
||||
Expected: all three lines found.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add README.md
|
||||
git commit -m "add platform support table and clarify token efficiency in README"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Final verification
|
||||
|
||||
- [ ] **Step 1: Run the full test suite**
|
||||
|
||||
```bash
|
||||
python -m pytest tests/ -q --tb=short 2>&1 | tail -20
|
||||
```
|
||||
|
||||
Expected: all tests pass, no regressions.
|
||||
|
||||
- [ ] **Step 2: Verify all four skill files are present in the package**
|
||||
|
||||
```bash
|
||||
ls graphify/skill*.md
|
||||
```
|
||||
|
||||
Expected:
|
||||
```
|
||||
graphify/skill.md
|
||||
graphify/skill-codex.md
|
||||
graphify/skill-opencode.md
|
||||
graphify/skill-claw.md
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Smoke test each install path**
|
||||
|
||||
```bash
|
||||
python -m graphify.__main__ install --platform codex 2>&1 | head -5
|
||||
python -m graphify.__main__ install --platform opencode 2>&1 | head -5
|
||||
python -m graphify.__main__ install --platform claw 2>&1 | head -5
|
||||
python -m graphify.__main__ install --platform unknown 2>&1
|
||||
```
|
||||
|
||||
Expected: first three print "skill installed →", last prints "error: unknown platform".
|
||||
|
||||
- [ ] **Step 4: Push v3 branch**
|
||||
|
||||
```bash
|
||||
git push -u origin v3
|
||||
```
|
||||
@@ -1,92 +0,0 @@
|
||||
# v3 Platform Compatibility Design
|
||||
|
||||
**Date:** 2026-04-06
|
||||
**Status:** Approved
|
||||
|
||||
## Problem
|
||||
|
||||
graphify's `skill.md` uses the Claude Code `Agent` tool for parallel semantic extraction. Users on Codex, OpenCode, and OpenClaw cannot use the skill. v3 adds platform-specific skill files so graphify works natively on all four platforms.
|
||||
|
||||
## Scope
|
||||
|
||||
- Four platform-specific skill files (one already exists)
|
||||
- `graphify install --platform X` routing
|
||||
- README clarifications (token efficiency, platform table)
|
||||
- No always-on project hooks for non-Claude-Code platforms in v3 (deferred to v3.1)
|
||||
|
||||
## What Changes Per Platform
|
||||
|
||||
The semantic extraction step (Step 3B) is the **only** section that differs. AST extraction, merging, clustering, labelling, export, and benchmarking are identical across all platforms and live in the shared Python CLI.
|
||||
|
||||
| Platform | Extraction approach | Rationale |
|
||||
|----------|-------------------|-----------|
|
||||
| Claude Code | Parallel `Agent` tool calls | Current behavior, unchanged |
|
||||
| Codex | Parallel `spawn_agent` + `wait` + `close_agent` | Codex multi-agent API (`multi_agent = true` required) |
|
||||
| OpenCode | Parallel `@mention` dispatches | OpenCode's native subagent system |
|
||||
| OpenClaw | Sequential loop — orchestrator extracts each file itself | OpenClaw agent support is MVP/incomplete; sequential is reliable |
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
graphify/
|
||||
├── skill.md # Claude Code (unchanged)
|
||||
├── skill-codex.md # Codex — parallel via spawn_agent
|
||||
├── skill-opencode.md # OpenCode — parallel via @mention
|
||||
├── skill-claw.md # OpenClaw — sequential extraction
|
||||
```
|
||||
|
||||
All four files ship in the PyPI package via `pyproject.toml` `package-data`.
|
||||
|
||||
## Install Command
|
||||
|
||||
`graphify install` gains a `--platform` flag:
|
||||
|
||||
```
|
||||
graphify install # Claude Code → ~/.claude/skills/graphify/SKILL.md
|
||||
graphify install --platform codex # Codex → ~/.agents/skills/graphify/SKILL.md
|
||||
graphify install --platform opencode # OpenCode → ~/.config/opencode/skills/graphify/SKILL.md
|
||||
graphify install --platform claw # OpenClaw → ~/.claw/skills/graphify/SKILL.md
|
||||
```
|
||||
|
||||
Behaviour:
|
||||
- Creates target directory if it doesn't exist (same as current Claude Code install)
|
||||
- If the platform's root config directory doesn't exist, prints a warning and exits cleanly: `"Codex config directory not found — is Codex installed?"`
|
||||
- `--platform` is optional; default is `claude` (current behaviour preserved)
|
||||
|
||||
## Skill File Content
|
||||
|
||||
Each file follows the same structure as `skill.md`. The extraction step (Step 3B) is rewritten for the platform:
|
||||
|
||||
**Codex (`skill-codex.md`):**
|
||||
- For each uncached file, call `spawn_agent(agent_type="worker", message=<extraction prompt>)`
|
||||
- Collect all agent handles, call `wait()` on each, then `close_agent()`
|
||||
- Requires user to have `multi_agent = true` in `~/.codex/config.toml`; skill notes this requirement
|
||||
|
||||
**OpenCode (`skill-opencode.md`):**
|
||||
- For each uncached file, dispatch via `@mention` with the extraction prompt
|
||||
- Collect results as agents complete
|
||||
|
||||
**OpenClaw (`skill-claw.md`):**
|
||||
- Loop over uncached files sequentially
|
||||
- Orchestrating LLM reads each file and extracts concepts/relationships/edges directly
|
||||
- Slower than parallel platforms but reliable given OpenClaw's MVP agent status
|
||||
- A note in the skill explains why: "OpenClaw's multi-agent support is still early; sequential extraction ensures reliability"
|
||||
|
||||
## README Changes
|
||||
|
||||
1. Add "Platform support" table under the Install section
|
||||
2. Clarify token efficiency: *"First run extracts and builds the graph — subsequent queries read the compact graph instead of raw files. The 71.5x reduction applies per query, and the cache means re-runs only re-process changed files."*
|
||||
3. Note sequential extraction on OpenClaw with brief explanation
|
||||
|
||||
## Not In Scope (v3)
|
||||
|
||||
- `graphify codex install` / `graphify opencode install` (always-on project hooks for non-CC platforms) — deferred to v3.1
|
||||
- Gemini CLI support — not enough information yet
|
||||
- Copilot CLI support — not enough information yet
|
||||
|
||||
## Testing
|
||||
|
||||
- Unit tests in `tests/test_install.py`: verify `--platform X` routes to correct source file and target path
|
||||
- Package data test: assert all four skill files are present in the installed package
|
||||
- No execution tests for platform-specific extraction (requires live platform)
|
||||
- Evals before release: run each platform skill on a real corpus, verify graph output is equivalent
|
||||
+85
-5
@@ -107,6 +107,71 @@ Rules:
|
||||
|
||||
_CLAUDE_MD_MARKER = "## graphify"
|
||||
|
||||
# AGENTS.md section for Codex, OpenCode, and OpenClaw.
|
||||
# All three platforms read AGENTS.md in the project root for persistent instructions.
|
||||
_AGENTS_MD_SECTION = """\
|
||||
## graphify
|
||||
|
||||
This project has a graphify knowledge graph at graphify-out/.
|
||||
|
||||
Rules:
|
||||
- Before answering architecture or codebase questions, read graphify-out/GRAPH_REPORT.md for god nodes and community structure
|
||||
- If graphify-out/wiki/index.md exists, navigate it instead of reading raw files
|
||||
- After modifying code files in this session, run `python3 -c "from graphify.watch import _rebuild_code; from pathlib import Path; _rebuild_code(Path('.'))"` to keep the graph current
|
||||
"""
|
||||
|
||||
_AGENTS_MD_MARKER = "## graphify"
|
||||
|
||||
|
||||
def _agents_install(project_dir: Path, platform: str) -> None:
|
||||
"""Write the graphify section to the local AGENTS.md (Codex/OpenCode/OpenClaw)."""
|
||||
target = (project_dir or Path(".")) / "AGENTS.md"
|
||||
|
||||
if target.exists():
|
||||
content = target.read_text()
|
||||
if _AGENTS_MD_MARKER in content:
|
||||
print(f"graphify already configured in AGENTS.md")
|
||||
return
|
||||
new_content = content.rstrip() + "\n\n" + _AGENTS_MD_SECTION
|
||||
else:
|
||||
new_content = _AGENTS_MD_SECTION
|
||||
|
||||
target.write_text(new_content)
|
||||
print(f"graphify section written to {target.resolve()}")
|
||||
print()
|
||||
print(f"{platform.capitalize()} will now check the knowledge graph before answering")
|
||||
print("codebase questions and rebuild it after code changes.")
|
||||
print()
|
||||
print("Note: unlike Claude Code, there is no PreToolUse hook equivalent for")
|
||||
print(f"{platform.capitalize()} — the AGENTS.md rules are the always-on mechanism.")
|
||||
|
||||
|
||||
def _agents_uninstall(project_dir: Path) -> None:
|
||||
"""Remove the graphify section from the local AGENTS.md."""
|
||||
target = (project_dir or Path(".")) / "AGENTS.md"
|
||||
|
||||
if not target.exists():
|
||||
print("No AGENTS.md found in current directory - nothing to do")
|
||||
return
|
||||
|
||||
content = target.read_text()
|
||||
if _AGENTS_MD_MARKER not in content:
|
||||
print("graphify section not found in AGENTS.md - nothing to do")
|
||||
return
|
||||
|
||||
cleaned = re.sub(
|
||||
r"\n*## graphify\n.*?(?=\n## |\Z)",
|
||||
"",
|
||||
content,
|
||||
flags=re.DOTALL,
|
||||
).rstrip()
|
||||
if cleaned:
|
||||
target.write_text(cleaned + "\n")
|
||||
print(f"graphify section removed from {target.resolve()}")
|
||||
else:
|
||||
target.unlink()
|
||||
print(f"AGENTS.md was empty after removal - deleted {target.resolve()}")
|
||||
|
||||
|
||||
def claude_install(project_dir: Path | None = None) -> None:
|
||||
"""Write the graphify section to the local CLAUDE.md."""
|
||||
@@ -213,11 +278,17 @@ def main() -> None:
|
||||
print("Commands:")
|
||||
print(" install [--platform P] copy skill to platform config dir (claude|codex|opencode|claw)")
|
||||
print(" benchmark [graph.json] measure token reduction vs naive full-corpus approach")
|
||||
print(" hook install install post-commit git hook (auto-rebuilds graph on commit)")
|
||||
print(" hook uninstall remove post-commit git hook")
|
||||
print(" hook status check if hook is installed")
|
||||
print(" claude install write graphify section to local CLAUDE.md")
|
||||
print(" claude uninstall remove graphify section from local CLAUDE.md")
|
||||
print(" hook install install post-commit/post-checkout git hooks (all platforms)")
|
||||
print(" hook uninstall remove git hooks")
|
||||
print(" hook status check if git hooks are installed")
|
||||
print(" claude install write graphify section to CLAUDE.md + PreToolUse hook (Claude Code)")
|
||||
print(" claude uninstall remove graphify section from CLAUDE.md + PreToolUse hook")
|
||||
print(" codex install write graphify section to AGENTS.md (Codex)")
|
||||
print(" codex uninstall remove graphify section from AGENTS.md")
|
||||
print(" opencode install write graphify section to AGENTS.md (OpenCode)")
|
||||
print(" opencode uninstall remove graphify section from AGENTS.md")
|
||||
print(" claw install write graphify section to AGENTS.md (OpenClaw)")
|
||||
print(" claw uninstall remove graphify section from AGENTS.md")
|
||||
print()
|
||||
return
|
||||
|
||||
@@ -245,6 +316,15 @@ def main() -> None:
|
||||
else:
|
||||
print("Usage: graphify claude [install|uninstall]", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
elif cmd in ("codex", "opencode", "claw"):
|
||||
subcmd = sys.argv[2] if len(sys.argv) > 2 else ""
|
||||
if subcmd == "install":
|
||||
_agents_install(Path("."), cmd)
|
||||
elif subcmd == "uninstall":
|
||||
_agents_uninstall(Path("."))
|
||||
else:
|
||||
print(f"Usage: graphify {cmd} [install|uninstall]", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
elif cmd == "hook":
|
||||
from graphify.hooks import install as hook_install, uninstall as hook_uninstall, status as hook_status
|
||||
subcmd = sys.argv[2] if len(sys.argv) > 2 else ""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -83,3 +83,77 @@ def test_claude_install_registers_claude_md(tmp_path):
|
||||
def test_codex_install_does_not_write_claude_md(tmp_path):
|
||||
_install(tmp_path, "codex")
|
||||
assert not (tmp_path / ".claude" / "CLAUDE.md").exists()
|
||||
|
||||
|
||||
# --- always-on AGENTS.md install/uninstall tests ---
|
||||
|
||||
def _agents_install(tmp_path, platform):
|
||||
from graphify.__main__ import _agents_install as _install_fn
|
||||
_install_fn(tmp_path, platform)
|
||||
|
||||
|
||||
def _agents_uninstall(tmp_path):
|
||||
from graphify.__main__ import _agents_uninstall as _uninstall_fn
|
||||
_uninstall_fn(tmp_path)
|
||||
|
||||
|
||||
def test_codex_agents_install_writes_agents_md(tmp_path):
|
||||
_agents_install(tmp_path, "codex")
|
||||
agents_md = tmp_path / "AGENTS.md"
|
||||
assert agents_md.exists()
|
||||
assert "graphify" in agents_md.read_text()
|
||||
assert "GRAPH_REPORT.md" in agents_md.read_text()
|
||||
|
||||
|
||||
def test_opencode_agents_install_writes_agents_md(tmp_path):
|
||||
_agents_install(tmp_path, "opencode")
|
||||
assert (tmp_path / "AGENTS.md").exists()
|
||||
|
||||
|
||||
def test_claw_agents_install_writes_agents_md(tmp_path):
|
||||
_agents_install(tmp_path, "claw")
|
||||
assert (tmp_path / "AGENTS.md").exists()
|
||||
|
||||
|
||||
def test_agents_install_idempotent(tmp_path):
|
||||
"""Installing twice does not duplicate the section."""
|
||||
_agents_install(tmp_path, "codex")
|
||||
_agents_install(tmp_path, "codex")
|
||||
content = (tmp_path / "AGENTS.md").read_text()
|
||||
assert content.count("## graphify") == 1
|
||||
|
||||
|
||||
def test_agents_install_appends_to_existing(tmp_path):
|
||||
"""Installs into an existing AGENTS.md without overwriting other content."""
|
||||
agents_md = tmp_path / "AGENTS.md"
|
||||
agents_md.write_text("# Existing rules\n\nDo not break things.\n")
|
||||
_agents_install(tmp_path, "codex")
|
||||
content = agents_md.read_text()
|
||||
assert "Do not break things." in content
|
||||
assert "## graphify" in content
|
||||
|
||||
|
||||
def test_agents_uninstall_removes_section(tmp_path):
|
||||
_agents_install(tmp_path, "codex")
|
||||
_agents_uninstall(tmp_path)
|
||||
agents_md = tmp_path / "AGENTS.md"
|
||||
# File deleted when it only contained graphify section
|
||||
assert not agents_md.exists()
|
||||
|
||||
|
||||
def test_agents_uninstall_preserves_other_content(tmp_path):
|
||||
"""Uninstall keeps pre-existing content."""
|
||||
agents_md = tmp_path / "AGENTS.md"
|
||||
agents_md.write_text("# Existing rules\n\nDo not break things.\n")
|
||||
_agents_install(tmp_path, "codex")
|
||||
_agents_uninstall(tmp_path)
|
||||
assert agents_md.exists()
|
||||
content = agents_md.read_text()
|
||||
assert "Do not break things." in content
|
||||
assert "## graphify" not in content
|
||||
|
||||
|
||||
def test_agents_uninstall_no_op_when_not_installed(tmp_path, capsys):
|
||||
_agents_uninstall(tmp_path)
|
||||
out = capsys.readouterr().out
|
||||
assert "nothing to do" in out
|
||||
|
||||
Reference in New Issue
Block a user