feat: mirror tag-vocabulary summary and note-type validation

Ports two changes from the knowledge-base vault this skeleton is extracted
from, keeping the shared scripts byte-identical across the pair:

- `graph_summary` reports `by_tag` and `tag_count`, sized by `topTags`, and
  drops the `by_type`/`by_status` breakdowns. An MCP-only agent could not
  otherwise discover the tag spellings that `graph_search_nodes` matches
  verbatim.
- `generate-graph.py --validate` now enforces a controlled vocabulary for a
  note's `type`, which is where the drift signal `by_type` used to give now
  lives. `AGENT_QUERY_GUIDE.md`, both skills and the-knowing-one quote the
  enforced enum.

`tests/test_generate_graph.py` is new; `make test` is 50 tests. Regenerating
the graph also drops a stale `GLOBAL_AGENTS` node for a file this skeleton
does not carry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Manuel Ganter
2026-09-14 11:37:02 +02:00
co-authored by Claude Opus 5
parent 638cbe6b39
commit 1cb6369282
10 changed files with 316 additions and 52 deletions
+9 -3
View File
@@ -23,7 +23,9 @@ Schema:
- `title` (string): note title
- `path` (string): vault-relative markdown path
- `repo` (string): repo scope (`tenant-controller`, `controller-util`, `global`, ...)
- `type` (string): note classification (`guideline`, `policy`, `project`, etc.)
- `type` (string): note classification from a controlled vocabulary —
`hub|project|area|guideline|policy|feature|roadmap|decision|note|meta|reference`;
absent frontmatter means `note`, and `--validate` fails on anything else
- `status` (string): lifecycle state (`active`, `draft`, `deprecated`)
- `tags` (string array): topic tags
- `last_reviewed` (string): `YYYY-MM-DD`
@@ -64,6 +66,10 @@ Examples with `jq`:
# list repos
jq -r '.nodes[].repo' index/graph.json | sort -u
# list the tag vocabulary, most frequent first (tags are matched verbatim)
jq -r '[.nodes[].tags[]?] | group_by(.) | map({tag: .[0], count: length})
| sort_by(-.count)[] | "\(.count)\t\(.tag)"' index/graph.json
# list top linked notes
jq -r '.nodes | sort_by(-(.inbound_count // 0))[] | "\(.inbound_count // 0)\t\(.id)\t\(.path)"' index/graph.json
@@ -96,8 +102,8 @@ For `jg`, use the same filters if your local `jg` is jq-compatible.
Validation:
```bash
# validate: non-zero exit on unresolved links, edge types outside the
# vocabulary, OR any remaining legacy [[wiki links]]
# validate: non-zero exit on unresolved links, edge types or note types
# outside their vocabularies, OR any remaining legacy [[wiki links]]
python3 scripts/generate-graph.py --validate
# regenerate graph and validate