Skip to content
16 changes: 11 additions & 5 deletions graphify/skills/agents/references/add-watch.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,29 @@ Load this when the user ran `/graphify add <url>` or passed `--watch`. Neither i
Fetch a URL and add it to the corpus, then update the graph.

```bash
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import sys
from graphify.ingest import ingest
from pathlib import Path

url = sys.argv[1]
author = sys.argv[2] if len(sys.argv) > 2 else ''
contributor = sys.argv[3] if len(sys.argv) > 3 else ''

try:
out = ingest('URL', Path('./raw'), author='AUTHOR', contributor='CONTRIBUTOR')
out = ingest(url, Path('./raw'), author=author, contributor=contributor)
print(f'Saved to {out}')
except ValueError as e:
print(f'error: {e}', file=sys.stderr)
sys.exit(1)
except RuntimeError as e:
print(f'error: {e}', file=sys.stderr)
sys.exit(1)
"
" 'URL' 'AUTHOR' 'CONTRIBUTOR'
```

Replace `URL` with the actual URL, `AUTHOR` with the user's name if provided, `CONTRIBUTOR` likewise. If the command exits with an error, tell the user what went wrong - do not silently continue. After a successful save, automatically run the `--update` pipeline on `./raw` to merge the new file into the existing graph.
Replace `URL` with the actual URL, `AUTHOR` with the user's name if provided, `CONTRIBUTOR` likewise. Each value is passed as a separate shell argument and read in Python via `sys.argv`, so user-supplied text never lands inside the evaluated Python source - even a URL containing single quotes, double quotes, backticks, or shell metacharacters is treated as a string value, not code. Single-quote each argument in the shell command; if a value contains a literal single quote, replace it with `'\''` (close-quote, escaped-quote, open-quote). If the command exits with an error, tell the user what went wrong - do not silently continue. After a successful save, automatically run the `--update` pipeline on `./raw` to merge the new file into the existing graph.

Supported URL types (auto-detected):
- YouTube / any video URL → audio downloaded via yt-dlp, transcribed to `.txt` on next run (requires `pip install 'graphifyy[video]'`)
Expand All @@ -41,7 +46,8 @@ Supported URL types (auto-detected):
Start a background watcher that monitors a folder and auto-updates the graph when files change.

```bash
$(cat graphify-out/.graphify_python) -m graphify.watch INPUT_PATH --debounce 3
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -m graphify.watch INPUT_PATH --debounce 3
```

Replace INPUT_PATH with the folder to watch. Behavior depends on what changed:
Expand Down
3 changes: 2 additions & 1 deletion graphify/skills/agents/references/exports.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ graphify export graphml
### Step 7d - MCP server (only if --mcp flag)

```bash
$(cat graphify-out/.graphify_python) -m graphify.serve graphify-out/graph.json
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -m graphify.serve graphify-out/graph.json
```

This starts a stdio MCP server that exposes tools: `query_graph`, `get_node`, `get_neighbors`, `get_community`, `god_nodes`, `graph_stats`, `shortest_path`. Add to Claude Desktop or any MCP-compatible agent orchestrator so other agents can query the graph live.
Expand Down
67 changes: 39 additions & 28 deletions graphify/skills/agents/references/query.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ Two traversal modes - choose based on the question:

First check the graph exists:
```bash
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
from pathlib import Path
if not Path('graphify-out/graph.json').exists():
print('ERROR: No graph found. Run /graphify <path> first to build the graph.')
Expand All @@ -28,7 +29,8 @@ Fix this **without inventing tokens** by expanding the query against the actual

1. Extract the token vocabulary from node labels:
```bash
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import json, re
from pathlib import Path
data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8'))
Expand Down Expand Up @@ -64,8 +66,8 @@ Build the **expanded query string** by joining the selected tokens with spaces.

Prefer the CLI when it is installed:
```bash
graphify query "QUESTION"
# or: graphify query "QUESTION" --dfs --budget 3000
graphify query 'QUESTION'
# or: graphify query 'QUESTION' --dfs --budget 3000
```

If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline:
Expand All @@ -77,7 +79,8 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal
5. If the graph lacks enough information, say so - do not hallucinate edges.

```bash
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import sys, json
from networkx.readwrite import json_graph
import networkx as nx
Expand All @@ -86,8 +89,8 @@ from pathlib import Path
data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8'))
G = json_graph.node_link_graph(data, edges='links')

question = 'QUESTION'
mode = 'MODE' # 'bfs' or 'dfs'
question = sys.argv[1]
mode = sys.argv[2] # 'bfs' or 'dfs'
terms = [t.lower() for t in question.split() if len(t) >= 3] # match the vocab threshold; keeps api/jwt/ios (#1392)

# Find best-matching start nodes
Expand Down Expand Up @@ -137,7 +140,10 @@ else:
frontier = next_frontier

# Token-budget aware output: rank by relevance, cut at budget (~4 chars/token)
token_budget = BUDGET # default 2000
try:
token_budget = int(sys.argv[3])
except (IndexError, ValueError):
token_budget = 2000
char_budget = token_budget * 4

# Score each node by term overlap for ranked output
Expand All @@ -160,18 +166,19 @@ output = '\n'.join(lines)
if len(output) > char_budget:
output = output[:char_budget] + f'\n... (truncated at ~{token_budget} token budget - use --budget N for more)'
print(output)
"
" 'QUESTION' 'MODE' 'BUDGET'
```

Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs`, and `BUDGET` with the token budget (default `2000`, or whatever `--budget N` specifies). Then answer based on the subgraph output above, using only what the graph contains.
Pass `QUESTION`, `MODE`, and `BUDGET` as single-quoted shell arguments after the Python source. Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs`, and `BUDGET` with the token budget (default `2000`, or whatever `--budget N` specifies). The single quotes keep the substitution injection-safe: any value containing a literal single quote must have it replaced with `'\''` (close-quote, escaped-quote, open-quote). Then answer based on the subgraph output above, using only what the graph contains.

After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node:

```bash
$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -m graphify save-result --question 'ORIGINAL_QUESTION' --answer 'ANSWER' --type query --nodes 'NODE1' 'NODE2'
```

Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` with the list of node labels you cited. This closes the feedback loop: the next `--update` will extract this Q&A as a node in the graph.
Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` with the list of node labels you cited. The single quotes around every user-supplied value (including each `--nodes` argument) make the substitution injection-safe: a question like `hello"; rm -rf /` or a node label containing whitespace, single quotes, or shell metacharacters cannot escape the argument. If `ORIGINAL_QUESTION`, `ANSWER`, or any node label contains a literal single quote, replace it with `'\''` (close-quote, escaped-quote, open-quote) - the escape applies to every substituted value, not just node labels. This closes the feedback loop: the next `--update` will extract this Q&A as a node in the graph.

**Work memory (self-improving loop).** Add an `--outcome` so future sessions learn from this one — append `--outcome useful|dead_end|corrected` to the `save-result` command (and `--correction "the right answer"` when correcting):

Expand All @@ -188,13 +195,14 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl
Find the shortest path between two named concepts in the graph. Prefer the CLI when installed:

```bash
graphify path "NODE_A" "NODE_B"
graphify path 'NODE_A' 'NODE_B'
```

If the CLI is unavailable, run it inline:

```bash
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import json, sys
import networkx as nx
from networkx.readwrite import json_graph
Expand All @@ -203,9 +211,6 @@ from pathlib import Path
data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8'))
G = json_graph.node_link_graph(data, edges='links')

a_term = 'NODE_A'
b_term = 'NODE_B'

def find_node(term):
term = term.lower()
scored = sorted(
Expand All @@ -215,6 +220,9 @@ def find_node(term):
)
return scored[0][1] if scored and scored[0][0] > 0 else None

a_term = sys.argv[1]
b_term = sys.argv[2]

src = find_node(a_term)
tgt = find_node(b_term)

Expand All @@ -238,15 +246,16 @@ except nx.NetworkXNoPath:
print(f'No path found between {a_term!r} and {b_term!r}')
except nx.NodeNotFound as e:
print(f'Node not found: {e}')
"
" 'NODE_A' 'NODE_B'
```

Replace `NODE_A` and `NODE_B` with the actual concept names from the user. Then explain the path in plain language - what each hop means, why it's significant.
Pass `NODE_A` and `NODE_B` as single-quoted shell arguments after the Python source. Replace them with the actual concept names from the user. The single quotes keep the substitution injection-safe: any value containing a literal single quote must have it replaced with `'\''` (close-quote, escaped-quote, open-quote). Then explain the path in plain language - what each hop means, why it's significant.

After writing the explanation, save it back:
After writing the explanation, save it back (single quotes around the user-supplied values keep the substitution injection-safe; replace any literal `'` in the substituted text with `'\''`):

```bash
$(cat graphify-out/.graphify_python) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -m graphify save-result --question 'Path from NODE_A to NODE_B' --answer 'ANSWER' --type path_query --nodes 'NODE_A' 'NODE_B'
```

---
Expand All @@ -256,13 +265,14 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr
Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed:

```bash
graphify explain "NODE_NAME"
graphify explain 'NODE_NAME'
```

If the CLI is unavailable, run it inline:

```bash
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import json, sys
import networkx as nx
from networkx.readwrite import json_graph
Expand All @@ -271,7 +281,7 @@ from pathlib import Path
data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8'))
G = json_graph.node_link_graph(data, edges='links')

term = 'NODE_NAME'
term = sys.argv[1]
term_lower = term.lower()

# Find best matching node
Expand Down Expand Up @@ -299,13 +309,14 @@ for neighbor in G.neighbors(nid):
conf = edge.get('confidence', '')
src_file = G.nodes[neighbor].get('source_file', '')
print(f' --{rel}--> {nlabel} [{conf}] ({src_file})')
"
" 'NODE_NAME'
```

Replace `NODE_NAME` with the concept the user asked about. Then write a 3-5 sentence explanation of what this node is, what it connects to, and why those connections are significant. Use the source locations as citations.
Pass `NODE_NAME` as a single-quoted shell argument after the Python source. Replace it with the concept the user asked about. The single quotes keep the substitution injection-safe: any value containing a literal single quote must have it replaced with `'\''` (close-quote, escaped-quote, open-quote). Then write a 3-5 sentence explanation of what this node is, what it connects to, and why those connections are significant. Use the source locations as citations.

After writing the explanation, save it back:
After writing the explanation, save it back (single quotes around the user-supplied values keep the substitution injection-safe; replace any literal `'` in the substituted text with `'\''`):

```bash
$(cat graphify-out/.graphify_python) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -m graphify save-result --question 'Explain NODE_NAME' --answer 'ANSWER' --type explain --nodes 'NODE_NAME'
```
9 changes: 7 additions & 2 deletions graphify/skills/agents/references/transcribe.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ Read the top god node labels from detect output or analysis, then compose a shor

```bash
export GRAPHIFY_WHISPER_MODEL=base # or whatever --whisper-model the user passed (must be exported)
export GRAPHIFY_WHISPER_PROMPT="<the one-sentence domain hint you composed in Step 1>"
$(cat graphify-out/.graphify_python) -c "
# Single quotes keep the prompt literal — no expansion of $, `, or \ inside it.
# If your domain hint itself contains a single quote, replace it with '\'' (close-quote, escaped-quote, open-quote)
# or use a here-doc / printf %q pattern; the export must still happen for the child Python process to inherit it.
GRAPHIFY_WHISPER_PROMPT='<the one-sentence domain hint you composed in Step 1>'
export GRAPHIFY_WHISPER_PROMPT
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import json, os, sys
from pathlib import Path
from graphify.transcribe import transcribe_all
Expand Down
18 changes: 12 additions & 6 deletions graphify/skills/agents/references/update.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Load this only when the user passed `--update` or `--cluster-only`. A first-time
Use when you've added or modified files since the last run. Only re-extracts changed files - saves tokens and time.

```bash
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import sys, json
from graphify.detect import detect_incremental, save_manifest
from pathlib import Path
Expand All @@ -30,7 +31,8 @@ if new_total > 0:
Then populate `.graphify_detect.json` so Steps 3A–6 (which read it unconditionally) see the right state for an incremental run. `files` carries the changed subset (drives Step 3A AST + Step 3B0 cache check on only what changed); `all_files` carries the full corpus for any step that needs corpus-wide context:

```bash
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import json
from pathlib import Path
r = json.loads(Path('graphify-out/.graphify_incremental.json').read_text(encoding=\"utf-8\"))
Expand All @@ -48,7 +50,8 @@ Path('graphify-out/.graphify_detect.json').write_text(json.dumps({
If new files exist, first check whether all changed files are code files:

```bash
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import json
from pathlib import Path

Expand All @@ -71,7 +74,8 @@ If no new files exist (only deletions), create an empty extraction so the merge
```bash
if [ ! -f graphify-out/.graphify_extract.json ]; then
echo '[graphify update] Only deletions -- creating empty extraction for merge.'
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import json
from pathlib import Path
Path('graphify-out/.graphify_extract.json').write_text(json.dumps({'nodes':[],'edges':[],'hyperedges':[],'input_tokens':0,'output_tokens':0}), encoding='utf-8')
Expand All @@ -83,7 +87,8 @@ fi
Then:

```bash
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import json
from pathlib import Path
from graphify.build import build_merge
Expand Down Expand Up @@ -170,7 +175,8 @@ Then run Steps 4–8 on the merged graph as normal.
After Step 4, show the graph diff:

```bash
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import json
from graphify.analyze import graph_diff
from graphify.build import build_from_json
Expand Down
16 changes: 11 additions & 5 deletions graphify/skills/amp/references/add-watch.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,29 @@ Load this when the user ran `/graphify add <url>` or passed `--watch`. Neither i
Fetch a URL and add it to the corpus, then update the graph.

```bash
$(cat graphify-out/.graphify_python) -c "
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -c "
import sys
from graphify.ingest import ingest
from pathlib import Path

url = sys.argv[1]
author = sys.argv[2] if len(sys.argv) > 2 else ''
contributor = sys.argv[3] if len(sys.argv) > 3 else ''

try:
out = ingest('URL', Path('./raw'), author='AUTHOR', contributor='CONTRIBUTOR')
out = ingest(url, Path('./raw'), author=author, contributor=contributor)
print(f'Saved to {out}')
except ValueError as e:
print(f'error: {e}', file=sys.stderr)
sys.exit(1)
except RuntimeError as e:
print(f'error: {e}', file=sys.stderr)
sys.exit(1)
"
" 'URL' 'AUTHOR' 'CONTRIBUTOR'
```

Replace `URL` with the actual URL, `AUTHOR` with the user's name if provided, `CONTRIBUTOR` likewise. If the command exits with an error, tell the user what went wrong - do not silently continue. After a successful save, automatically run the `--update` pipeline on `./raw` to merge the new file into the existing graph.
Replace `URL` with the actual URL, `AUTHOR` with the user's name if provided, `CONTRIBUTOR` likewise. Each value is passed as a separate shell argument and read in Python via `sys.argv`, so user-supplied text never lands inside the evaluated Python source - even a URL containing single quotes, double quotes, backticks, or shell metacharacters is treated as a string value, not code. Single-quote each argument in the shell command; if a value contains a literal single quote, replace it with `'\''` (close-quote, escaped-quote, open-quote). If the command exits with an error, tell the user what went wrong - do not silently continue. After a successful save, automatically run the `--update` pipeline on `./raw` to merge the new file into the existing graph.

Supported URL types (auto-detected):
- YouTube / any video URL → audio downloaded via yt-dlp, transcribed to `.txt` on next run (requires `pip install 'graphifyy[video]'`)
Expand All @@ -41,7 +46,8 @@ Supported URL types (auto-detected):
Start a background watcher that monitors a folder and auto-updates the graph when files change.

```bash
$(cat graphify-out/.graphify_python) -m graphify.watch INPUT_PATH --debounce 3
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -m graphify.watch INPUT_PATH --debounce 3
```

Replace INPUT_PATH with the folder to watch. Behavior depends on what changed:
Expand Down
3 changes: 2 additions & 1 deletion graphify/skills/amp/references/exports.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ graphify export graphml
### Step 7d - MCP server (only if --mcp flag)

```bash
$(cat graphify-out/.graphify_python) -m graphify.serve graphify-out/graph.json
readarray -t GFY_PYTHON < graphify-out/.graphify_python
"${GFY_PYTHON[@]}" -m graphify.serve graphify-out/graph.json
```

This starts a stdio MCP server that exposes tools: `query_graph`, `get_node`, `get_neighbors`, `get_community`, `god_nodes`, `graph_stats`, `shortest_path`. Add to Claude Desktop or any MCP-compatible agent orchestrator so other agents can query the graph live.
Expand Down
Loading
Loading