- name
- devkg-sparql
- description
- Query the Dev Knowledge Graph via SPARQL instead of grepping raw session files. Use this when asked about technologies, relationships between tools, session history, where a topic was discussed, or cross-platform knowledge. Prefer provenance-first SPARQL (message + session) over label-only CONTAINS or grep.
- user-invocable
- true
- allowed-tools
- ["Bash(curl:*)","Bash(jq:*)"]
# DevKG SPARQL Query Skill
## CRITICAL CONTEXT-SAFETY RULE
**NEVER READ LARGE SPARQL RESULTS, SESSION FILES, JSONL, LOGS, OR GENERATED ARTIFACTS ALL AT ONCE.** Always add `LIMIT`, select only needed variables, inspect counts first, and summarize. Never dump huge result sets, ID lists, raw JSON, or transcript content into chat.
Query the developer knowledge graph at `http://localhost:3030/devkg/sparql` via SPARQL. This graph contains extracted knowledge triples, entities, Wikidata links, and session metadata from **Claude Code, pi, Codex, Cursor, ChatGPT, DeepSeek, Grok, and Warp** sessions.
**Content limit:** `sioc:content` on messages is capped at ~2000 characters at ingest. SPARQL is enough to **locate sessions and reason lightly** from triples + snippets. For full quotes or deep thread reconstruction, normalize `hasSourceFile` and read the JSONL only when needed.
## Retrieval Strategy (read this first)
| User intent | Do this first | Do NOT start with |
|-------------|---------------|-------------------|
| "Where / which sessions discussed X?" | **Template 5** (topic + intent + provenance) | Label-only Template 6, or grep |
| "What do we know about technology X?" | Template 1 (entity + provenance) | Grep |
| "How does X relate to Y?" | Template 2 | Grep |
| "Find the exact message wording" | Template 5 or 9 → then JSONL only if snippet truncated | Grepping all projects |
**Default for session-discovery questions:** multi-signal filter (topic **and** intent terms) on **both** triple labels and `sioc:content`, always joining `extractedFrom` / `extractedInSession`, ordered by `DESC(?created)`, with `LIMIT`.
When Fuseki returns provenance hits, **do not** fall back to grep. Grep only if Fuseki is down or returns 0 rows after a provenance query.
## Execution Pattern
Always use this pattern (POST, URL-encoded query, JSON output). Include Fuseki auth when required:
```bash
curl -s -X POST 'http://localhost:3030/devkg/sparql' \
-u admin:admin \
-H 'Accept: application/sparql-results+json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "query=YOUR_SPARQL_HERE" \
| jq -r '.results.bindings[] | [.var1.value, .var2.value] | @tsv'
```
Adjust the `jq` expression to match your SELECT variables. Use `@tsv` for compact tabular output. Always `LIMIT` results.
For multi-line queries, use double quotes around the `--data-urlencode` value and escape inner quotes:
```bash
curl -s -X POST 'http://localhost:3030/devkg/sparql' \
-u admin:admin \
-H 'Accept: application/sparql-results+json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "query=PREFIX devkg: <http://devkg.local/ontology#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT DISTINCT ?label WHERE {
?e a devkg:Entity ; rdfs:label ?label .
FILTER(LANG(?label) = \"\")
} LIMIT 10" \
| jq -r '.results.bindings[] | .label.value'
```
## Fallback Rule
If Fuseki is unreachable (curl fails or times out) **or** a provenance query (Template 5/8) returns 0 results, then fall back to grep-based session search:
```bash
grep -rli "keyword" ~/.claude/projects ~/.pi/agent/sessions ~/.cursor/projects 2>/dev/null | head -20
```
Then read matching JSONL files with bounded Python extraction. Only use this as a last resort.
## Resolving `hasSourceFile` to Disk (and Pruned Sources)
`hasSourceFile` is NOT always a real absolute path. Normalize before any `Read`:
| `hasSourceFile` prefix | Real on-disk location |
|---|---|
| `/Users/...` | absolute path — use as-is |
| `/claude-sessions/<munged>/<file>` | `~/.claude/projects/<munged>/<file>` |
| `/pi-sessions/<munged>/<file>` | `~/.pi/agent/sessions/<munged>/<file>` |
| `/codex-sessions/<path>` | `~/.codex/sessions/<path>` |
| `/cursor-sessions/projects/<slug>/...` | `~/.cursor/projects/<slug>/...` |
```bash
resolve_session_path() {
local sf="$1" p=""
case "$sf" in
/Users/*) p="$sf" ;;
/claude-sessions/*) p="$HOME/.claude/projects/${sf#/claude-sessions/}" ;;
/pi-sessions/*) p="$HOME/.pi/agent/sessions/${sf#/pi-sessions/}" ;;
/codex-sessions/*) p="$HOME/.codex/sessions/${sf#/codex-sessions/}" ;;
/cursor-sessions/projects/*) p="$HOME/.cursor/projects/${sf#/cursor-sessions/projects/}" ;;
*) p="$sf" ;;
esac
local stem="${p%.jsonl}"
if [ -f "$p" ]; then echo "FILE:$p"; return; fi
if [ -f "$stem" ]; then echo "FILE:$stem"; return; fi
if [ -d "$stem/subagents" ]; then echo "SUBAGENTS:$stem/subagents"; return; fi
if [ -d "$p/subagents" ]; then echo "SUBAGENTS:$p/subagents"; return; fi
echo "PRUNED:$p"
}
```
**If the path is `PRUNED`**, do NOT grep the filesystem. Re-query KnowledgeTriples for that session via `extractedInSession` and reconstruct from labels + any remaining `sioc:content`.
## Result Formatting
Present SPARQL results as markdown tables. Never dump raw JSON to the user.
## Prefixes (copy into every query)
```sparql
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX prov: <http://www.w3.org/ns/prov#>
PREFIX sioc: <http://rdfs.org/sioc/ns#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX devkg: <http://devkg.local/ontology#>
PREFIX data: <http://devkg.local/data/>
PREFIX wd: <http://www.wikidata.org/entity/>
```
## Ontology Cheat Sheet
### Classes
| Class | Parent | Description |
|-------|--------|-------------|
| `devkg:Session` | `prov:Activity`, `sioc:Forum` | A working session (conversation) |
| `devkg:Message` | `sioc:Post`, `prov:Entity` | A message in a session |
| `devkg:UserMessage` | `devkg:Message` | Human message |
| `devkg:AssistantMessage` | `devkg:Message` | AI message |
| `devkg:ToolCall` | `prov:Activity` | Legacy tool invocation nodes (may be absent in new ingests — do not rely on them) |
| `devkg:ToolResult` | `prov:Entity` | Legacy tool output (may be absent in new ingests) |
| `devkg:CodeArtifact` | `prov:Entity`, `schema:SoftwareSourceCode` | Code file/snippet |
| `devkg:Entity` | `prov:Entity` | Extracted technical concept |
| `devkg:KnowledgeTriple` | — | Reified triple (subject→predicate→object) with provenance |
| `devkg:Project` | `prov:Entity` | A development project |
| `devkg:Developer` | `prov:Agent` | Human developer |
| `devkg:AIModel` | `prov:Agent` | AI model (Claude, GPT, etc.) |
| `devkg:Topic` | `skos:Concept` | Knowledge topic |
### Structural Predicates (Session/Message graph)
| Predicate | Domain → Range | Notes |
|-----------|---------------|-------|
| `devkg:usedInSession` | Message/ToolCall → Session | Links content to its session |
| `devkg:hasParentMessage` | Message → Message | Thread structure |
| `devkg:mentionsTopic` | Message → Topic | Topic tagging |
| `devkg:invokedTool` | AssistantMessage → ToolCall | Tool usage |
| `devkg:hasToolResult` | ToolCall → ToolResult | Tool output |
| `devkg:producedArtifact` | Activity → CodeArtifact | Code generation |
| `devkg:belongsToProject` | Session → Project | Project membership |
| `devkg:extractedFrom` | KnowledgeTriple → Message | Triple provenance |
| `devkg:extractedInSession` | KnowledgeTriple → Session | Triple provenance |
| `devkg:tripleSubject` | KnowledgeTriple → Entity | Reified subject |
| `devkg:tripleObject` | KnowledgeTriple → Entity | Reified object |
| `devkg:triplePredicateLabel` | KnowledgeTriple → xsd:string | Predicate name |
### Key Datatype Properties
| Property | On | Value |
|----------|----|-------|
| `sioc:content` | Message | Message text (truncated ~2000 chars at ingest) |
| `rdfs:label` | Entity/Session/Project | Display name |
| `dcterms:created` | Session/Message | ISO datetime |
| `devkg:hasSourcePlatform` | Session | `claude-code`, `pi-coding-agent`, `codex`, `cursor`, `chatgpt`, `deepseek`, `grok`, `warp` |
| `devkg:hasSourceFile` | Session | Logical path to raw source — normalize before `Read` (see "Resolving `hasSourceFile` to Disk") |
| `devkg:hasToolName` | ToolCall | Legacy — prefer KnowledgeTriples + message content for discovery |
| `devkg:hasWorkingDirectory` | Session | Project directory path |
| `owl:sameAs` | Entity | Wikidata URI (e.g., `wd:Q28865`) |
### Knowledge Predicates (24 total)
These connect `devkg:Entity` to `devkg:Entity` via direct edges AND are stored as `devkg:triplePredicateLabel` strings on reified `devkg:KnowledgeTriple` nodes:
`uses`, `dependsOn`, `enables`, `isPartOf`, `hasPart`, `implements`, `extends`, `alternativeTo`, `solves`, `produces`, `configures`, `composesWith`, `provides`, `requires`, `isTypeOf`, `builtWith`, `deployedOn`, `storesIn`, `queriedWith`, `integratesWith`, `broader`, `narrower`, `relatedTo`, `servesAs`
## Query Templates
### 1. Entity Lookup — "What do we know about X?"
Returns all relationships (outbound + inbound) for an entity, with source file and content snippet for provenance. Use `CONTAINS` for fuzzy matching.
```sparql
SELECT DISTINCT ?direction ?predicate ?otherLabel ?sourceFile ?platform
(SUBSTR(?content, 1, 150) AS ?snippet) WHERE {
{
?triple a devkg:KnowledgeTriple ;
devkg:tripleSubject ?s ;
devkg:triplePredicateLabel ?predicate ;
devkg:tripleObject ?o ;
devkg:extractedFrom ?msg ;
devkg:extractedInSession ?session .
?s rdfs:label ?sLabel .
?o rdfs:label ?otherLabel .
FILTER(CONTAINS(LCASE(STR(?sLabel)), "ENTITY_LOWER"))
BIND("outbound" AS ?direction)
} UNION {
?triple a devkg:KnowledgeTriple ;
devkg:tripleSubject ?o ;
devkg:triplePredicateLabel ?predicate ;
devkg:tripleObject ?obj ;
devkg:extractedFrom ?msg ;
devkg:extractedInSession ?session .
?obj rdfs:label ?oLabel .
?o rdfs:label ?otherLabel .
FILTER(CONTAINS(LCASE(STR(?oLabel)), "ENTITY_LOWER"))
BIND("inbound" AS ?direction)
}
OPTIONAL { ?session devkg:hasSourceFile ?sourceFile }
OPTIONAL { ?session devkg:hasSourcePlatform ?platform }
OPTIONAL { ?msg sioc:content ?content }
}
ORDER BY ?direction ?predicate
```
Replace `ENTITY_LOWER` with the lowercase entity name (e.g., `neo4j`, `opentelemetry`).
The `sourceFile` column is a logical path to the original JSONL/JSON file — normalize it with `resolve_session_path` (see "Resolving `hasSourceFile` to Disk") before `Read`; if it resolves to `PRUNED`, reconstruct from the triples instead.
### 2. Entity-to-Entity — "How does X relate to Y?"
```sparql
SELECT DISTINCT ?predicate ?sourceSnippet WHERE {
?triple a devkg:KnowledgeTriple ;
devkg:tripleSubject ?s ;
devkg:triplePredicateLabel ?predicate ;
devkg:tripleObject ?o ;
devkg:extractedFrom ?msg .
?s rdfs:label ?sLabel .
?o rdfs:label ?oLabel .
OPTIONAL { ?msg sioc:content ?c . BIND(SUBSTR(?c, 1, 150) AS ?sourceSnippet) }
FILTER(
CONTAINS(LCASE(STR(?sLabel)), "ENTITY_X") &&
CONTAINS(LCASE(STR(?oLabel)), "ENTITY_Y")
)
}
```
### 3. Predicate Search — "What uses/enables/solves X?"
```sparql
SELECT DISTINCT ?subjectLabel ?objectLabel WHERE {
?triple a devkg:KnowledgeTriple ;
devkg:tripleSubject ?s ;
devkg:triplePredicateLabel "PREDICATE" ;
Ver no GitHub