# Note-network checks for rowcleaner. Tools: make, git, grep, sed, wc, sort.
# All four run with `make check`.

HAND   := AGENTS.md
# A note is any context.md in a folder; no list to keep in sync.
NOTES  := $(wildcard */context.md)
JOURNAL := $(wildcard journal/*.md)
DOCS    = $(HAND) README.md $(NOTES) $(JOURNAL) $(wildcard queue/*.md)

# What an agent has to read before it can start: the handbook, the queued work,
# and the note for the folder actually in play.
BOOT        ?= $(HAND) $(firstword $(wildcard queue/*.md)) src/context.md
# Budget for that set, in estimated tokens (bytes / 4).
BOOT_BUDGET ?= 1600

.PHONY: check check-links check-journal check-stale bootmap

check: check-links check-journal check-stale bootmap

# 1. Every relative link resolves from the linking file's own directory, and every
#    note appears in the handbook index.
check-links:
	@fail=0; \
	for f in $(DOCS); do \
		d=$$(dirname "$$f"); \
		for l in $$(grep -oE '\]\([^)]+\.md\)' "$$f" | sed 's/^](//; s/)$$//'); do \
			case "$$l" in http*) continue ;; esac; \
			[ -e "$$d/$$l" ] || { echo "broken link: $$f -> $$l" >&2; fail=1; }; \
		done; \
	done; \
	for n in $(NOTES); do \
		[ -e "$$n" ] || { echo "note listed but missing: $$n" >&2; fail=1; }; \
		grep -qF "$$n" $(HAND) || { echo "note missing from handbook index: $$n" >&2; fail=1; }; \
	done; \
	[ $$fail -eq 0 ] && echo "links ok ($$(echo $(DOCS) | wc -w) files, $$(echo $(NOTES) | wc -w) notes indexed)"

# 2. Every journal entry carries the four slots, so a summary cannot drop them silently.
#    Entries written before the rule existed are listed once here; that list only shrinks.
SLOTS := Decision Rejected_alternative Invariant Verify_command
JOURNAL_EXEMPT := journal/2026-10-19_crlf-blank-rows.md
check-journal:
	@fail=0; \
	for f in $(filter-out $(JOURNAL_EXEMPT),$(JOURNAL)); do \
		for s in $(SLOTS); do \
			n=$$(echo "$$s" | tr '_' ' '); \
			grep -qE "^## $$n$$" "$$f" || { echo "$$f: missing slot '$$n'" >&2; fail=1; }; \
		done; \
	done; \
	[ $$fail -eq 0 ] && echo "journal ok"

# 3. A note is stale when tracked code in its own folder was committed after the note was.
check-stale:
	@fail=0; \
	for n in $(NOTES); do \
		d=$$(dirname "$$n"); \
		nt=$$(git log -1 --format=%ct -- "$$n" 2>/dev/null); \
		if [ -z "$$nt" ]; then echo "$$n: unknown (never committed)"; continue; fi; \
		ct=$$(for f in $$(git ls-files "$$d" | grep -v '\.md$$'); do git log -1 --format=%ct -- "$$f"; done | sort -nr | head -1); \
		if [ -z "$$ct" ]; then echo "$$n: ok (no committed code in $$d)"; \
		elif [ "$$ct" -gt "$$nt" ]; then \
			echo "$$n: STALE - code in $$d moved $$(( (ct - nt) / 86400 )) days after the note" >&2; fail=1; \
		else echo "$$n: ok"; fi; \
	done; exit $$fail

# 4. Boot map: the index an agent reads instead of guessing, plus what it costs.
bootmap:
	@echo "background notes, by size:"; \
	for n in $(NOTES); do printf '  %5s words  %s\n' "$$(wc -w < "$$n")" "$$n"; done | sort -r -n; \
	b=$$(cat $(BOOT) | wc -c); \
	est=$$((b / 4)); \
	echo "cold-boot set: $$b bytes, about $$est tokens (budget $(BOOT_BUDGET))"; \
	[ $$est -le $(BOOT_BUDGET) ] || { echo "FAIL: boot path over budget" >&2; exit 1; }
