Download core/tools/graph.sh from playn4/playnfrontierAI: direct link, hf CLI and curl.
- Browser
- Download file 5.14 kB
-
https://huggingface.co/playn4/playnfrontierAI/resolve/main/core/tools/graph.sh
- Command line
-
hf download hf://playn4/playnfrontierAI/core/tools/graph.sh
-
curl -L -o graph.sh https://huggingface.co/playn4/playnfrontierAI/resolve/main/core/tools/graph.sh
5.14 kB
| # OMN graph β canon link-graph emit + structural lint. | |
| # Nodes: facts/, decisions/, projects/ + watch/learnings/backlog/inbox.md. | |
| # Edges: [[wiki-links]] (resolved by basename anywhere in core, same rule as | |
| # check.sh) + supersedes: frontmatter. Report-only (exit 0) β a close.sh hook | |
| # may promote the warns to a gate later (the human's call). | |
| set -u | |
| CORE="$(cd "$(dirname "$0")/.." && pwd)" | |
| OUT="$CORE/graph.json" | |
| FAIL=0 | |
| warn() { echo " !! $*"; FAIL=$((FAIL+1)); } | |
| note() { echo " - $*"; } | |
| meta() { | |
| awk -v f="$2" 'BEGIN{n=0} | |
| /^---$/ {n++; next} | |
| n==1 && $1==f":" {sub(/^[^:]+: /,""); print; exit}' "$1" | |
| } | |
| days_since() { | |
| local s | |
| s=$(date -d "$1" +%s 2>/dev/null) || { echo 0; return; } | |
| echo $(( ($(date +%s) - s) / 86400 )) | |
| } | |
| # stale_p <file> <kind> β kind: facts|decisions|projects|top (top-level working | |
| # files are never stale here; check.sh gates them via mtime, not frontmatter) | |
| stale_p() { | |
| case "$2" in | |
| facts|decisions|projects) | |
| local iv; iv=$(meta "$1" check); iv=${iv%d}; [ -n "$iv" ] || iv=7 | |
| [ "$(days_since "$(meta "$1" updated)")" -gt "$iv" ] ;; | |
| *) return 1 ;; | |
| esac | |
| } | |
| # resolve <ref> β path relative to core, or "" (card.md never a target) | |
| resolve() { | |
| local hit | |
| hit=$(find "$CORE" -name "${1##*/}.md" ! -name card.md 2>/dev/null | sort | head -1) | |
| [ -n "$hit" ] && printf '%s' "${hit#"$CORE"/}" | |
| } | |
| TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT | |
| NODES="$TMP/nodes"; EDGES="$TMP/edges"; LINKS="$TMP/links" | |
| touch "$NODES" "$EDGES" "$LINKS" | |
| # 1. Nodes | |
| for f in "$CORE"/facts/*.md "$CORE"/decisions/*.md "$CORE"/projects/*.md \ | |
| "$CORE"/watch.md "$CORE"/learnings.md "$CORE"/backlog.md "$CORE"/inbox.md \ | |
| "$CORE"/README.md; do | |
| [ -f "$f" ] || continue | |
| id="${f#"$CORE"/}"; dir=${id%%/*}; [ "$dir" = "$id" ] && dir=top | |
| case "$(meta "$f" status)" in accepted|live|active|superseded|"") ;; *) continue ;; esac | |
| st=$(meta "$f" status); [ -n "$st" ] || st="-" | |
| ti=$(meta "$f" title); [ -n "$ti" ] || ti=$(grep -m1 '^# ' "$f" | sed 's/^# //') | |
| esc=$(printf '%s' "$ti" | sed 's/\\/\\\\/g; s/"/\\"/g') | |
| if stale_p "$f" "$dir"; then stale=true; else stale=false; fi | |
| printf '%s\t%s\t%s\t%s\n' "$id" "$st" "$stale" "$esc" >> "$NODES" | |
| done | |
| # 2. Edges β [[wiki-links]] anywhere in node files + supersedes: frontmatter | |
| while IFS=$'\t' read -r id _; do | |
| f="$CORE/$id" | |
| grep -oE '\[\[[^]]+\]\]' "$f" 2>/dev/null | sed -E 's/^\[\[//; s/\]\]$//' | sort -u | | |
| while read -r ref; do | |
| [ -z "$ref" ] && continue | |
| tgt=$(resolve "$ref") | |
| if [ -z "$tgt" ]; then printf 'DEAD\t%s\t%s\n' "$id" "$ref" >> "$LINKS" | |
| elif [ "$tgt" = "$id" ]; then continue | |
| else printf '%s\t%s\twikilink\n' "$id" "$tgt" >> "$EDGES"; fi | |
| done | |
| sup=$(meta "$f" supersedes) | |
| [ -z "$sup" ] && continue | |
| printf '%s\n' "$sup" | grep -oE '\[\[[^]]+\]\]' | sed -E 's/^\[\[//; s/\]\]$//' | | |
| while read -r ref; do | |
| tgt=$(resolve "$ref") | |
| if [ -z "$tgt" ]; then printf 'DEAD\t%s\t%s\n' "$id" "$ref" >> "$LINKS" | |
| else printf '%s\t%s\tsupersedes\n' "$id" "$tgt" >> "$EDGES"; fi | |
| done | |
| done < "$NODES" | |
| # dedup edges per (from,to), supersedes wins over wikilink | |
| sort -t$'\t' -k1,1 -k2,2 -k3,3 "$EDGES" | awk -F'\t' '!seen[$1 FS $2]++' > "$TMP/edges.u" | |
| # 3. Lint | |
| echo "=== OMN graph ($(date +%F)) ===" | |
| while IFS=$'\t' read -r _ src ref; do warn "dead link [[$ref]] in $src"; done < "$LINKS" | |
| # stale neighbors: live source -> stale target | |
| while IFS=$'\t' read -r from to kind; do | |
| tf="$CORE/$to"; tdir=${to%%/*}; [ "$tdir" = "$to" ] && tdir=top | |
| if stale_p "$tf" "$tdir"; then | |
| sf="$CORE/$from"; sdir=${from%%/*}; [ "$sdir" = "$from" ] && sdir=top | |
| stale_p "$sf" "$sdir" || warn "stale neighbor: $from ($kind) -> $to is STALE β update or tombstone $to" | |
| fi | |
| done < "$TMP/edges.u" | |
| # orphans + god nodes (in-canon degree) | |
| awk -F'\t' '{d[$1]++; d[$2]++} END{for (k in d) print k}' "$TMP/edges.u" > "$TMP/linked" | |
| awk -F'\t' '{c[$2]++} END{for (k in c) print c[k], k}' "$TMP/edges.u" | sort -rn | head -3 > "$TMP/gods" | |
| comm -23 <(cut -f1 "$NODES" | sort) <(sort "$TMP/linked") > "$TMP/orphans" | |
| if [ -s "$TMP/orphans" ]; then | |
| note "orphans (no canon edges): $(paste -sd' ' "$TMP/orphans")" | |
| else | |
| note "orphans: none" | |
| fi | |
| if [ -s "$TMP/gods" ]; then | |
| note "god nodes (most-linked): $(awk '{printf "%s (%d)%s", $2, $1, (NR<3?", ":"")}' "$TMP/gods")" | |
| fi | |
| printf ' - graph.json written (%d nodes, %d edges)\n' \ | |
| "$(wc -l < "$NODES")" "$(wc -l < "$TMP/edges.u")" | |
| # 4. Emit graph.json | |
| { | |
| printf '{\n "generated": "%s",\n' "$(date '+%F %T')" | |
| printf ' "nodes": [\n' | |
| first=1 | |
| while IFS=$'\t' read -r id st stale ti; do | |
| [ $first -eq 1 ] || printf ',\n'; first=0 | |
| printf ' {"id": "%s", "status": "%s", "stale": %s, "title": "%s"}' "$id" "$st" "$stale" "$ti" | |
| done < "$NODES" | |
| printf '\n ],\n "edges": [\n' | |
| first=1 | |
| while IFS=$'\t' read -r from to kind; do | |
| [ $first -eq 1 ] || printf ',\n'; first=0 | |
| printf ' {"from": "%s", "to": "%s", "kind": "%s"}' "$from" "$to" "$kind" | |
| done < "$TMP/edges.u" | |
| printf '\n ]\n}\n' | |
| } > "$OUT" | |
| [ "$FAIL" -eq 0 ] && echo "PASS" || echo "REPORT: $FAIL finding(s) β report-only, exit 0" | |
| exit 0 | |