# Jsonld Knowledge Graph

> Design and ship a companion JSON-LD knowledge graph (graph.jsonld) next to llms.txt for projects with stable concept-level structure. Encodes domain entities and relationships as schema.org triples for LLM citation. Use when project has matrix / hierarchy / phase-binding structure that prose alone leaves implicit, AND that structure is stable across releases. Defers llms.txt navigator wording to llms-txt-writer.

- Skill: `shimo4228/jsonld-knowledge-graph` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add shimo4228/jsonld-knowledge-graph`
- Raw SKILL.md: https://api.skillmd.com/api/skills/shimo4228/jsonld-knowledge-graph/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: shimo4228 (https://skillmd.com/u/shimo4228)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/shimo4228/jsonld-knowledge-graph

---


# JSON-LD Knowledge Graph

`graph.jsonld` を `llms.txt` の隣に置いて、project の concept-level architecture を schema.org JSON-LD triples として encode する skill。LLM が prose だけでは引き出しにくい entity 間の関係（matrix / hierarchy / phase-binding）を triple として citation 可能にする。

## When to Use

以下を **すべて** 満たす project に適用する:

- 安定した concept-level 構造を持つ
  - 例: matrix（4 Quadrants × N ADRs）、ordered hierarchy（prohibition-strength 3 levels）、phase-skill binding（6:6 bijective）、layered architecture（3 memory layers）
- 構造が **release を跨いで安定**（vX.Y.Z bump で entity が増減しない）
- LLM に当該関係を訊いた時に **prose だけでは答が出にくい / 誤答する** 経験的証拠がある
- すでに `llms.txt` + `llms-full.txt`（Answer.AI 標準）を持っている

## When NOT to Use

以下のいずれかに該当する場合は **適用しない**:

- 単一目的の linear project（matrix / hierarchy 構造がない）
- 内部構造が release ごとに churn する（graph が編集負債になる）
- `llms-full.txt` の prose Q&A で関係問題が既に十分答えられる
- 「LLM citation 向上を狙いたい」だけで具体的な entity 関係問題がない（GEO 目的なら llms-txt-writer の方が direct）

`graph.jsonld` は prose の代替ではなく **補完**。prose で表現しきれない構造があるとき、その構造をやっと拾える。

## file-level 構造との関係（正本）

`graph.jsonld` は **concept 層だけ** を保存する。file-level 構造（「X はどのファイルに住むか」「誰が誰を呼ぶか」）は保存せず、問いのたびにコードから導出する:

| | file-level（導出、保存しない） | graph.jsonld（保存） |
|---|---|---|
| **対象** | ファイル / モジュール / call 関係 | 概念 / エンティティ |
| **答える質問** | 「X はどのファイルに住んでいるか」「誰が Y を呼ぶか」 | 「X とは何か、X と Y はどう関係するか」 |
| **源** | コードそのもの — Claude Code の LSP tool（`workspaceSymbol` / `findReferences` / `incomingCalls`）、`grimp` 等の import グラフ | JSON-LD triples（手で書く） |
| **主読者** | 作業中の agent | AI search engine + LLM が entity を citation する時 |
| **trigger** | なし（都度計算） | concept / 関係の変化 |

graph にコードのノード（file path・module・LOC）を置かない。置くと第 2 の module map になり、ソース commit ごとに同期コストを払う鏡が育つ（contemplative-agent ADR-0102: 手書き module map 6 枚がソース 197 commit に対し 159 commit の同期を要し、読者は観測されなかった）。設計理由は ADR、パイプラインの段構成はそれを走らせる script の冒頭コメントが持つ。

### Drift 防止のための運用規約

- 新規 ADR / Concept / Quadrant 等を追加 → graph.jsonld にノード追加（`@id` は GitHub blob URL）
- ファイルパスが変わった → graph.jsonld の `@id` が blob URL ならそこだけ追従。他に更新する文書は無い
- 概念の semantics が変わった → graph.jsonld の description 更新

context-sync skill（Maintain phase 担当）がこの drift を audit する。詳細は `~/.claude/skills/context-sync/SKILL.md` 参照。

## Required Design Moves

実装で観察された 9 つの再利用可能な move。すべて適用が default、外す場合は理由を明示する。

### 1. Dual `@type` on every node

```json
{
  "@id": "...",
  "@type": ["ResearchLine", "ScholarlyArticle"]
}
```

custom namespace type（domain semantics を持つ）+ schema.org base type（AI search engine が citation target として認識）。両方つけることで、schema.org に親しい crawler も custom vocab に親しい LLM も同じノードを認識できる。

### 2. Language-tagged literals for bilingual

```json
"alternateName": [
  {"@value": "Four Business AI Quadrants", "@language": "en"},
  {"@value": "ビジネスAIの四象限", "@language": "ja"}
]
```

**1 file で en/ja 両対応**。`graph.ja.jsonld` を別ファイルにするのは禁止（同期負債が発生する）。Concept DOI / repo URL / `@id` は言語中立なので二重管理不要。

### 3. Schema absence で禁止関係を構造的に強制

「X と Y は **絶対に dependency 関係ではない**」を保証したい場合、`dependsOn` のような edge type を **そもそも `@context` に定義しない**。schema の vocabulary 自体が構造的 commitment になり、表現できない関係は表現されない。

例: 3 つの sibling research line が独立進化することを強制するため、共通 vocab には `dependsOn` edge type を **そもそも定義しない**。代わりに `siblingOf`（symmetric）と `derivesFrom`（historical / 一方向、ある line が別 line から実装的に派生した事実を encode する場合のみ使う）の 2 種類だけを定義する。

### 4. Cross-graph `@id` reuse で triple merge

複数の `graph.jsonld` ファイルが存在する場合（hub-and-spoke topology）、共有 entity の `@id` は **byte-identical** にする。例:

- 3 つの memory layers が 2 つの sibling line repo の graph に登場
- 両方とも `@id: "https://<owner>.github.io/<owner>/vocab#memory-layer/episode-log"` を使う（owner namespace は project ごとに置換）
- LLM がふたつの graph を crawl すると triple が自動 merge され、「同じ概念」として認識される

これは JSON-LD 1.1 の標準動作。pyld など conformant processor で実装可能。

### 5. Volatile state を schema レベルで除外

graph.jsonld に **以下の field を持たせない**:

- version 番号 (`version`, `versionNumber`, `vX.Y.Z`)
- count（ADR 数、test 数、ノード数）
- 流動的 enumeration（churning skill list, dynamic capability list）

schema に存在しない field は entity に乗らないので、routine release が graph に漏れない。`grep -E '"version"|"count"|v[0-9]+\.[0-9]+' graph.jsonld` が常に空を返すことを CI で検証可能。

### 6. Matrix encoding via paired edges

`A × B` matrix を encode する場合、片方向の edge だけでは不十分。**2 つの complementary edge** を使う:

- `appliesTo` (`ADR → Quadrant`): 各 ADR が適用される Quadrant 群
- `realizedBy` (`ProhibitionLevel → ADR`): 各 prohibition tier が実現される ADR

silence is signal: 「Quadrant 1, 2 (Script, Algorithmic Search) は AAP 適用外」は `appliesTo` edge の **不在** で表現する。`governanceTier: "out-of-scope"` プロパティで明示的にも stamp する。

### 7. Root `Dataset` node で graph self-description

`@graph` array の先頭に schema.org `Dataset` 型のルートノードを置く:

```json
{
  "@id": "https://github.com/owner/repo#knowledge-graph",
  "@type": ["Dataset", "CreativeWork"],
  "name": "Project Knowledge Graph",
  "description": "...",
  "isBasedOn": "https://github.com/owner/repo",
  "mainEntity": [<primary entity @ids>]
}
```

schema.org `Dataset` は Google AI Overviews / Perplexity が structured data として認識する。graph の **目的と entry points** を crawler に明示する役割。

### 8. Reading-order block で AI 入口導線を明示

`llms.txt` 冒頭に Graph-first reading order block を置く。詳細手順は llms-txt-writer skill 参照。要点:

- タイトル直下に `> AI agents should read graph.jsonld first` blockquote
- 直後に numbered "Recommended reading order" section
- Core documentation navigator の最上位に graph.jsonld

### 9. Reverse-link で hub-and-spoke 経路双方向化

hub-and-spoke topology の場合、各 line repo の README に **hub graph への逆リンク**を置く:

```markdown
For the canonical relationship map of <ecosystem>'s research ecosystem, see:
https://github.com/<owner>/<hub>/blob/main/graph.jsonld
```

LLM が個別 line repo から入ってきた場合でも hub graph へ戻れる。1 段目の探索後に broader context へ広げる経路。

**散文リンクだけでは不十分** — README 逆リンクは人間/プレーンテキスト向け。グラフ層でも各 spoke の **self-node に機械可読な上向き edge** を張る:

```json
// 各 line / supporting repo の self-node (ResearchLine or Dataset) に:
"isPartOf": "https://github.com/<owner>/<hub>"   // hub の canonical @id (bare-repo URL に統一)
```

- hub→spoke は `mainEntity` / `siblingOf`、spoke→hub は `isPartOf` で双方向化 (`isPartOf` の厳密な逆は `hasPart` だが、独立 triple として有効)
- target @id は **全 repo で同一の canonical hub node** に揃える (bare-repo `github.com/<owner>/<hub>` 推奨)。揃わないと merge 時に hub が複数ノードに割れて join 不能
- `isPartOf` は context に `{"@id":"https://schema.org/isPartOf","@type":"@id"}` を宣言 (無いと「IRI 文字列の literal 化」と同じく literal になる)。context を触れない content graph では inline `{"@id":"..."}` で node 参照を強制
- self-node を持たない content-only graph (Article 列挙のみ等) には最小 Dataset self-node を1個新設し、そこに `isPartOf` を張る
- **監査**: 新 repo 追加時、全 spoke の self-node が `isPartOf→hub` を持つか確認。実地では下向き (hub→spoke) だけ張られ、上向きが大半の spoke で抜けていた

## Schema Vocabulary 設計

新規 type / edge を導入する判断基準:

### 新規 node type を作る条件

- **closed / bounded set**: 「4 axioms」「3 memory layers」のように member が limited で stable
- **ordering invariant が必要**: `ProhibitionLevel` のように `level` integer で順序を保つ必要がある
- **distinct semantics**: `Axiom` は swappable preset の 1 instance、`Concept` は free-form term — 同じ `Concept` 扱いだと意味が混ざる

これに当てはまらない場合は、既存の `Concept` instance として encode する（4 code-LLM patterns のように、4 件しかなく新規 type 化に値しない場合）。

### 新規 edge type を作る条件

- **既存 edge では semantic が違う**: `extends` (EcosystemRepo → ResearchLine) と `appliesTo` (ADR → Quadrant) は別の関係。reuse すると混乱
- **matrix の片側を成す**: `appliesTo` + `realizedBy` のように、matrix encoding に必須

新規 edge は schema.org base vocabulary に該当があれば優先する（`url`, `identifier`, `citation`, `isBasedOn`, `mainEntity` 等）。custom vocab に追加するのは schema.org base で表現できない時のみ。

### `@id` namespace 設計

- ResearchLine: 既存 DOI URL を使う（`https://doi.org/10.5281/zenodo.NNNN`、**concept DOI** 必須、latest DOI は使わない）
- EcosystemRepo: GitHub repo URL（`https://github.com/owner/repo`）
- Concept: custom vocab namespace（`https://<owner>.github.io/<owner>/vocab#concept/<slug>` 等）
- ADR: GitHub blob URL（`https://github.com/owner/repo/blob/main/docs/adr/NNNN-slug.md`）

namespace は dereferenceable である必要なし（典型的な "private vocab" pattern）。重要なのは **byte-identical re-use** が cross-graph で実現できること。

## Cross-graph @id Discipline

hub-and-spoke topology で複数 graph を持つ場合の規約:

1. **Hub graph に登場する entity の `@id` を canonical** とする
2. 各 line graph で同じ entity を参照する時は、**copy-paste で `@id` を再利用**（手入力しない、typo 防止）
3. 新規 entity を追加する時は hub graph 側にまず登録してから line graph で参照
4. `@id` を変更する場合は、すべての graph を同時に更新（cross-graph grep で漏れ確認）

verification:

```bash
# Hub graph の primary entity @id を抽出
jq -r '.["@graph"][] | select(.["@type"][] | contains("ResearchLine")) | .["@id"]' hub/graph.jsonld

# 各 line graph でも同じ @id が登場することを確認
grep -h "doi.org/10.5281/zenodo" */graph.jsonld | sort -u
```

## Bilingual Strategy

JSON-LD 1.1 の language-tagged literal を使い、**1 file で en/ja を保持**:

```json
{
  "alternateName": [
    {"@value": "Agent Knowledge Cycle", "@language": "en"},
    {"@value": "エージェント知識サイクル", "@language": "ja"}
  ]
}
```

### やってはいけないこと

- `graph.ja.jsonld` を別ファイルにする → 同期負債発生
- 一部の literal だけ多言語化する → 一貫性が崩れて LLM 解釈に揺れ
- `@language` を省略する → デフォルト言語が不明、crawler の解釈が undefined

### やるべきこと

- ResearchLine `name` と `alternateName` は必ず両言語
- Concept / Axiom / Quadrant など concept-level node は `alternateName` で両言語
- description は en 単独でもよい（prose は llms-full.txt が正本、graph は entity 識別が主目的）
- ADR title は en 単独でよい（GitHub の英語 prose が正本）

## Companion File Wiring

graph.jsonld を作っただけでは crawler に見つからない。`llms.txt` / `llms-full.txt` / README に wiring が必要:

| ファイル | 何を追加 |
|---|---|
| `llms.txt` | (1) 冒頭に Graph-first reading order blockquote と numbered section、(2) Core documentation navigator の最上位に graph.jsonld entry |
| `llms-full.txt` | 末尾に question-form H2（"How do X and Y relate as a graph?"）+ graph.jsonld への link + 3 つ程度の load-bearing design choice 説明 |
| `README.md` (人間向け) | 冒頭に `<details><summary>AI-facing reading order</summary>` 折りたたみ block。維持している language mirror があれば同 block を mirror にも入れる |
| `README.{lang}.md` (追加 mirror がある場合) | summary tag と intro 行のみ localize、bullet list は paths なので en 共通でよい。mirror を維持するか（traffic-data 基準）の判断は `llms-txt-writer` の Companion JSON-LD Graph セクションが正本 |
| hub-and-spoke の line README | hub graph への reverse-link を上記 block 内に追加 |

詳細な wording は `~/.claude/skills/llms-txt-writer/SKILL.md` の Companion JSON-LD Graph セクション参照。

## Verification Workflow

graph.jsonld を作成・編集したら以下を実行:

```bash
# 1. JSON 構文
python3 -m json.tool < graph.jsonld > /dev/null

# 2. JSON-LD expansion + N-Quads triple count
uvx --quiet --from pyld python3 -c "
from pyld import jsonld
import json
doc = json.load(open('graph.jsonld'))
expanded = jsonld.expand(doc)
nquads = jsonld.to_rdf(doc, {'format': 'application/n-quads'})
lines = [l for l in nquads.strip().split(chr(10)) if l]
print(f'{len(expanded)} nodes / {len(lines)} triples')
"

# 3. Volatile state 検出（empty が期待値）
grep -E '"version"|"versionNumber"|"adrCount"|"testCount"|v[0-9]+\.[0-9]+' graph.jsonld

# 4. Reading-order presence in llms.txt
head -30 llms.txt | grep -c "Recommended reading order\|graph.jsonld"

# 5. Concept DOI 整合（latest ではなく concept DOI が @id に使われている）
# (manual: 各 ResearchLine @id を Zenodo で開いて parent record であることを確認)
```

### Context pitfalls — edge が静かに消える 2 パターン

production graph 6 本の監査 (2026-06) で実際に発生した。いずれも JSON は valid のまま、
triple count も変わらないため、上記 step 1-2 では検出できない:

1. **IRI 文字列の literal 化**: `@vocab` があっても、context で `@type: "@id"` 強制の
   ない property（schema.org の `author` / `creator` 等）に IRI を文字列で渡すと literal
   になり、node への edge にならない。Person node が graph に居ても誰からも参照されない。
   - 対策: IRI 参照は常に `{"@id": "https://..."}` オブジェクト形式で書く
2. **未定義 key の無言ドロップ**: `@vocab` なしの明示マッピング型 context では、context に
   無い key（`sameAs` 等）が expansion で警告なく消える。
   - 対策: graph に新しい property を導入する前に context マッピングの存在を確認する

検出は同梱の lint script が決定論的に行う（上記 step 1-3 の機械チェックも包含するので、編集後はこれ 1 コマンドでよい）:

```bash
# 6. graph lint — JSON validity / expansion / DROPPED-KEY / URL-LITERAL / VOLATILE を一括検査
#    exit 0 = clean, 1 = findings, 2 = fatal。複数ファイル可
uv run --with pyld python3 ~/.claude/skills/jsonld-knowledge-graph/scripts/graph_lint.py graph.jsonld

# locator 系 property（url / license / contentUrl）は literal URL を許容。変更する場合:
#   --allow-literal-url url,license,contentUrl,codeRepository

# 複数 graph を渡すと cross-file の NAME-DRIFT も検査する
# （同一 IRI に複数の異なる en name → 検出は構造的・決定論的。どれを正とするかの
#   解決だけが judgment なので、lint は報告のみ。表記揺れの正解は alternateName に置く）
uv run --with pyld python3 ~/.claude/skills/jsonld-knowledge-graph/scripts/graph_lint.py hub/graph.jsonld line1/graph.jsonld line2/graph.jsonld
#   --skip-name-drift で省略可
```

修正の定石: 値の書き換えではなく **@context への coercion 追加** で直す（`"sameAs": {"@id": "https://schema.org/sameAs", "@type": "@id"}` を足せば既存の文字列値がそのまま IRI として解釈される。diff が context の数行で済む）。

#### なぜ自作 lint か（external research, 2026-06）

標準ツールを調査した上での Build 判定。再調査不要:

- **[pySHACL](https://github.com/RDFLib/pySHACL)** (RDFLib): 活発に維持されている W3C 標準の RDF 検証器。ただし**展開後の RDF graph に対して動く**ため、DROPPED-KEY は原理的に検出できない（落ちた key は SHACL が見る前に消えている）。URL-LITERAL は `sh:nodeKind sh:IRI` で書けるが、predicate ごとの shapes ファイル維持が必要
- **[jsonld-lint](https://github.com/mattrglobal/jsonld-lint)** (Mattr): un-mapped term 検出（= DROPPED-KEY）を持つ唯一の専用 linter だったが **2025-09-26 に owner がアーカイブ**。採用不可
- **jsonld.js の safe mode**: 展開時に未定義 term をエラー化できるが JS 専用。Python 側の pyld に相当機能なし

つまり最も危険な DROPPED-KEY が標準ツールの死角にあり、VOLATILE はプロジェクト固有ポリシーなので、薄い自作 lint が正当。

**pySHACL への移行条件**: 「すべての ResearchLine ノードは author と sameAs を持つ」のような**クラス単位の必須制約**を宣言的に強制したくなった時点で、URL-LITERAL 検査を shapes.ttl + pyshacl に置き換え、DROPPED-KEY / VOLATILE のみ graph_lint.py に残す構成に移行する。

### Manual checks

- **JSON-LD playground**: https://json-ld.org/playground/ に paste して `@context` が解決し triple として展開されることを確認
- **schema.org validator**: https://validator.schema.org/ で `Dataset` / `ScholarlyArticle` 等の type が認識されることを確認
- **LLM citation probe**: graph push 後 1-2 週間（crawler refresh 待ち）、ChatGPT / Perplexity に「<project> の X と Y はどう関係しますか？graph.jsonld を参照してください」と質問し、graph が citation されるか確認

## Mirror Sync to Hugging Face Datasets

graph.jsonld を更新したら、Hugging Face Datasets 上の mirror にも同期する。HF は LLM training pipeline / knowledge-graph crawler の primary ingest source として機能する（HF dataset は Auto-converted to Parquet が走り、`pandas` / `Polars` / `Datasets` ライブラリから直接 load 可能になる）。

**正本は [`hf-sync`](../hf-sync/SKILL.md) skill にある**。`/hf-sync <Owner/dataset>` または `bash ~/.claude/skills/hf-sync/sync.sh <Owner/dataset>` を project root で実行すれば、`graph.jsonld` の structural check → `graph.jsonl` flatten → `hf upload` 2 ファイルが 1 コマンドで走る。Local の token を使うので CI auth setup は不要。

前提条件・repo mapping・token scope の扱いは **hf-sync に再掲しない**（正本の改名時にコピーだけ取り残された前例あり — `hf login` → `hf auth login`）。

`release-doi` skill の Phase 4 末尾（tag push + `gh release create` の後）で呼ぶのが標準フロー。ad-hoc resync にも同じ skill を使う。

**graph の `sameAs` は self-sovereign または earned な解決先のみ**（ORCID / DOI / 自アカウントの platform profile / 無関係な第三者が作成した record）。Wikidata QID は張らない（authorship-strategy ADR-0021 — host governance による一括削除の実測）。dead QID を検出したら purge する。

HF 側の `README.md` (dataset card) は graph 更新では同期しない。Dataset card は HF 用に customize されている（sibling dataset への link、mirror notice 等）ので、文面を変えたい場合は手動で `hf upload <Owner/dataset> README.md --repo-type dataset`。

## Maintenance Contract

graph.jsonld を **編集する trigger**:

- ecosystem repo の追加 / 退役 → `EcosystemRepo` ノード add/remove
- 新しい stable structural concept の登場 → `Concept` / `Axiom` / `Quadrant` ノード add + 関連 edges
- 新 research line の開始 → `ResearchLine` ノード + `siblingOf` edges
- Concept DOI 自体が移動（稀、Zenodo record restructuring 時のみ）

graph.jsonld を **編集してはいけない trigger**（routine release では触らない）:

- 任意 module の `vX.Y.Z` release
- ADR count, skill count, test count, version bump
- 内部 module の restructuring（file-level は保存していないので触る文書が無い。blob URL の `@id` だけ追従）
- model version bump（`qwen3.5:9b` から `qwen4:9b` 等）

schema に version / count / churning field を持たせていない限り、これらは graph に **そもそも encode できない** ので構造的に強制される。

## Reference Implementation

このパターンの canonical implementation pointer（具体 repo URL / DOI / vocab namespace）は本 skill 本文に含めず、同一ディレクトリの [`inspiration.md`](inspiration.md) に記録する（portability 確保）。pattern を新しい project に適用する場合、以下の構造目安が参考になる:

- **Hub-and-spoke 構成**: 1 つの hub graph が sibling line graph 群を `siblingOf` で繋ぐ
- **Hub graph 規模目安**: 3-5 ResearchLine + 10-20 EcosystemRepo + 5-10 Concept + 3-5 ExternalReference
- **Per-line graph 規模目安**: 1 ResearchLine + line 内部構造（matrix / hierarchy / phase-binding）を 20-40 nodes で encode
- **共通 vocab**: 全 graph が同じ namespace を使い、shared `@id` で triple merge を実現

単一 repo（hub なし）でも matrix / hierarchy 構造があれば適用可能。その場合 hub-related の sibling/derivesFrom 系 edge は不要。

## What This Skill Does NOT Do

- llms.txt / llms-full.txt の文章設計 — use `llms-txt-writer`
- Project doc role の overlap 検出 / 整理 — use `context-sync`
- file-level の module map の生成 — 作らない（LSP tool / grimp で都度導出。上の「file-level 構造との関係」）
- Articles / blog post の文体 設計 — use `writing-ecosystem`（`~/MyAI_Lab/zenn-content` 常駐）

## Related

- `llms-txt-writer` — llms.txt / llms-full.txt 本体の書き方、navigator 設計、GEO/AEO 最適化
- `context-sync` — graph.jsonld と prose docs の drift audit（Maintain phase）

