feat(ci): add pre-commit FactId validation hook (#393)
Grep-based pre-commit check validating fact_id references in content YAML against canonical knowledge catalogs. Runs in advisory mode when catalogs are stubs (exit 0), switches to enforcing mode once populated (exit 1 on unknown fact_ids with file:line output). - tooling/check-fact-ids: core validation script (<2s runtime) - .config/hooks/pre-commit: hook dispatcher for modular checks - Makefile: check-fact-ids + setup-hooks targets, wired into setup - docs/DEVOPS.md: content validation and pre-commit hooks sections Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
# Validate fact_id references in content YAML against canonical knowledge catalogs.
|
||||
# Part of pre-commit checks (ticket #393). Grep-based, targets <2s runtime.
|
||||
#
|
||||
# Modes:
|
||||
# Advisory — when knowledge catalogs have no fact definitions yet (exit 0)
|
||||
# Enforcing — when catalogs are populated; fails on unknown fact_ids (exit 1)
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
KNOWLEDGE_DIR="$REPO_ROOT/content/global/knowledge"
|
||||
CONTENT_DIR="$REPO_ROOT/content/campaigns"
|
||||
|
||||
# --- Extract canonical fact_ids from knowledge catalogs ---
|
||||
# Matches YAML lines like: fact_id: some_value or fact_id: "some_value"
|
||||
# Excludes entity-attributes.yaml (different schema: attribute keys, not fact_ids)
|
||||
CANONICAL_IDS=""
|
||||
if [ -d "$KNOWLEDGE_DIR" ]; then
|
||||
CANONICAL_IDS=$(
|
||||
grep -rh 'fact_id:\s*' "$KNOWLEDGE_DIR" \
|
||||
--include='*.yaml' \
|
||||
--exclude='entity-attributes.yaml' \
|
||||
| sed 's/.*fact_id:\s*//' \
|
||||
| sed 's/\s*#.*//' \
|
||||
| sed "s/^[\"']\(.*\)[\"']$/\1/" \
|
||||
| sort -u \
|
||||
|| true
|
||||
)
|
||||
fi
|
||||
|
||||
CANONICAL_COUNT=$(echo "$CANONICAL_IDS" | grep -c '\S' || true)
|
||||
|
||||
# --- Extract referenced fact_ids from campaign content ---
|
||||
# Covers monologue prerequisites.facts[].fact_id and dialogue knowledge_grant.fact_id
|
||||
REFERENCED=""
|
||||
if [ -d "$CONTENT_DIR" ]; then
|
||||
REFERENCED=$(
|
||||
grep -rh 'fact_id:\s*' "$CONTENT_DIR" \
|
||||
--include='*.yaml' \
|
||||
| sed 's/.*fact_id:\s*//' \
|
||||
| sed 's/\s*#.*//' \
|
||||
| sed "s/^[\"']\(.*\)[\"']$/\1/" \
|
||||
|| true
|
||||
)
|
||||
fi
|
||||
|
||||
REFERENCED_UNIQUE=$(echo "$REFERENCED" | sort -u | grep '\S' || true)
|
||||
REF_COUNT=$(echo "$REFERENCED_UNIQUE" | grep -c '\S' || true)
|
||||
|
||||
# --- Compare ---
|
||||
if [ "$CANONICAL_COUNT" -eq 0 ]; then
|
||||
echo "check-fact-ids: WARNING — no canonical fact_ids in knowledge catalogs"
|
||||
echo " Catalogs not yet populated. Check is advisory only."
|
||||
if [ "$REF_COUNT" -gt 0 ]; then
|
||||
echo " $REF_COUNT unique fact_ids referenced in content:"
|
||||
echo "$REFERENCED_UNIQUE" | sed 's/^/ /'
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Enforcing mode: catalogs have definitions
|
||||
ERRORS=0
|
||||
while IFS= read -r ref_id; do
|
||||
[ -z "$ref_id" ] && continue
|
||||
if ! echo "$CANONICAL_IDS" | grep -qxF "$ref_id"; then
|
||||
echo "ERROR: unknown fact_id '$ref_id' — not in knowledge catalogs"
|
||||
grep -rn "fact_id:\s*$ref_id" "$CONTENT_DIR" --include='*.yaml' \
|
||||
| sed "s|$REPO_ROOT/||" \
|
||||
| sed 's/^/ /'
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done <<< "$REFERENCED_UNIQUE"
|
||||
|
||||
if [ "$ERRORS" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "check-fact-ids: FAILED — $ERRORS unknown fact_id(s)"
|
||||
echo " Canonical fact_ids defined in: content/global/knowledge/*.yaml"
|
||||
echo " Run 'make check-fact-ids' to recheck."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "check-fact-ids: OK — $REF_COUNT references validated against $CANONICAL_COUNT canonical facts"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user