-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdrift-check.sh
More file actions
executable file
·185 lines (164 loc) · 6.54 KB
/
Copy pathdrift-check.sh
File metadata and controls
executable file
·185 lines (164 loc) · 6.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env bash
# drift-check.sh — Detect staleness in the knowledge harness
#
# Run at session start/resume. Outputs warnings for:
# FRESHNESS:
# 1. Stale graphify graph (>7 days behind latest commit)
# 2. Learnings files not updated despite recent activity
# 3. bd memory bloat (>60 memories)
# 4. Consolidation overdue (>7 days)
# CONSISTENCY (cross-reference integrity, not just age):
# 5. Learnings file exists but is not indexed in index.md (discovery gap)
# 6. index.md references a learnings file that does not exist (dangling)
# 7. Asymmetric cross-ref: A's "See also:" lists B, but B does not list A back
#
# Environment:
# HARNESS_REFS — path to the knowledge home's references/ dir
# (default: $HOME/.agent-knowledge/references)
# HARNESS_REPOS — space-separated list of repo paths to check graphs for
# DRIFT_GRAPH_DAYS — graph staleness threshold in days (default: 7)
# DRIFT_LEARN_DAYS — learnings staleness threshold in days (default: 30)
# DRIFT_MEMORY_MAX — memory count threshold (default: 60)
# DRIFT_CONSOL_DAYS — consolidation overdue threshold in days (default: 7)
#
# Exit codes: 0 = clean, 1 = warnings found (non-blocking)
set -o pipefail
REFS_DIR="${HARNESS_REFS:-$HOME/.agent-knowledge/references}"
REPOS="${HARNESS_REPOS:-$(pwd)}"
GRAPH_DAYS="${DRIFT_GRAPH_DAYS:-7}"
LEARN_DAYS="${DRIFT_LEARN_DAYS:-30}"
MEMORY_MAX="${DRIFT_MEMORY_MAX:-60}"
CONSOL_DAYS="${DRIFT_CONSOL_DAYS:-7}"
WARNINGS=()
# --- Helper: get file mtime as epoch (macOS + Linux) ---
file_mtime() {
if [ "$(uname)" = "Darwin" ]; then
/usr/bin/stat -f %m "$1" 2>/dev/null
else
stat -c %Y "$1" 2>/dev/null
fi
}
# --- 1. Graphify freshness ---
check_graph_freshness() {
local repo_dir="$1"
local repo_name
repo_name="$(basename "$repo_dir")"
local graph="$repo_dir/graphify-out/graph.json"
if [ ! -f "$graph" ]; then
return
fi
local graph_mtime
graph_mtime=$(file_mtime "$graph")
local now
now=$(date +%s)
local graph_age_days=$(( (now - graph_mtime) / 86400 ))
if [ "$graph_age_days" -gt "$GRAPH_DAYS" ]; then
local commits_since
commits_since=$(cd "$repo_dir" && git log --oneline --since="@${graph_mtime}" 2>/dev/null | wc -l | tr -d ' ')
if [ "${commits_since:-0}" -gt 5 ]; then
WARNINGS+=("DRIFT: $repo_name graph is ${graph_age_days}d old with $commits_since commits since last build. Run: cd $repo_dir && graphify . --update")
fi
fi
}
# --- 2. Learnings staleness ---
check_learnings_freshness() {
if [ ! -d "$REFS_DIR" ]; then return; fi
local oldest_file=""
local oldest_days=0
local now
now=$(date +%s)
for f in "$REFS_DIR"/learnings-*.md; do
[ -f "$f" ] || continue
local mtime
mtime=$(file_mtime "$f")
local age_days=$(( (now - mtime) / 86400 ))
if [ "$age_days" -gt "$oldest_days" ]; then
oldest_days=$age_days
oldest_file="$(basename "$f")"
fi
done
if [ "$oldest_days" -gt "$LEARN_DAYS" ]; then
WARNINGS+=("DRIFT: $oldest_file not updated in ${oldest_days}d — may have stale entries")
fi
}
# --- 3. Memory bloat ---
check_memory_count() {
local count
count=$(bd memories 2>/dev/null | grep -c "^### " 2>/dev/null || true)
count="${count##*$'\n'}"
count="${count:-0}"
if [ "$count" -gt "$MEMORY_MAX" ]; then
WARNINGS+=("DRIFT: $count bd memories (threshold: $MEMORY_MAX). Consider running /consolidate")
fi
}
# --- 4. Consolidation overdue ---
check_consolidation_freshness() {
local last_consol
# Anchor to the canonical `meta/last-consolidation` memory — `bd memories consolidation`
# fuzzy-matches ANY memory containing "consolidation", so a bare date|head -1 grabs the
# first date from an unrelated memory. Pin to the key's content line.
last_consol=$(bd memories consolidation 2>/dev/null | grep -A1 "meta/last-consolidation" | grep -oE "[0-9]{4}-[0-9]{2}-[0-9]{2}" | head -1 || echo "")
if [ -z "$last_consol" ]; then
WARNINGS+=("DRIFT: No consolidation ever recorded. Run /consolidate")
return
fi
local consol_epoch
consol_epoch=$(python3 -c "from datetime import datetime; print(int(datetime.strptime('$last_consol','%Y-%m-%d').timestamp()))" 2>/dev/null || echo "0")
local now
now=$(date +%s)
local days_since=$(( (now - consol_epoch) / 86400 ))
if [ "$days_since" -gt "$CONSOL_DAYS" ]; then
WARNINGS+=("DRIFT: Last consolidation was ${days_since}d ago ($last_consol). Consider /consolidate")
fi
}
# --- 5-7. Reference consistency (index integrity + cross-ref symmetry) ---
check_reference_consistency() {
local index="$REFS_DIR/index.md"
[ -d "$REFS_DIR" ] || return
[ -f "$index" ] || { WARNINGS+=("DRIFT: index.md missing at $index"); return; }
# 5. Every learnings-*.md on disk is referenced in index.md
for f in "$REFS_DIR"/learnings-*.md; do
[ -f "$f" ] || continue
local base; base="$(basename "$f")"
grep -q "$base" "$index" || WARNINGS+=("DRIFT: $base exists but is not indexed in index.md (discovery gap)")
done
# 6. Every learnings-*.md named in index.md exists on disk
local ref
for ref in $(grep -oE 'learnings-[a-z0-9-]+\.md' "$index" | sort -u); do
[ -f "$REFS_DIR/$ref" ] || WARNINGS+=("DRIFT: index.md references $ref but the file does not exist (dangling)")
done
# 7. "See also:" cross-ref symmetry — if A lists B, B should list A
for f in "$REFS_DIR"/learnings-*.md; do
[ -f "$f" ] || continue
local a; a="$(basename "$f")"
local seealso; seealso="$(grep -m1 -i 'See also:' "$f")"
[ -n "$seealso" ] || continue
local b
for b in $(printf '%s' "$seealso" | grep -oE 'learnings-[a-z0-9-]+\.md' | sort -u); do
[ "$b" = "$a" ] && continue
[ -f "$REFS_DIR/$b" ] || continue
grep -m1 -i 'See also:' "$REFS_DIR/$b" | grep -q "$a" \
|| WARNINGS+=("DRIFT: cross-ref asymmetry — $a lists $b, but $b's 'See also:' omits $a")
done
done
}
# --- Run all checks ---
for repo in $REPOS; do
[ -d "$repo" ] && check_graph_freshness "$repo"
done
check_learnings_freshness
check_memory_count
check_consolidation_freshness
check_reference_consistency
# --- Output ---
if [ ${#WARNINGS[@]} -eq 0 ]; then
echo "Harness health: all clear"
exit 0
else
echo "## Harness Drift Warnings (${#WARNINGS[@]})"
echo ""
for w in "${WARNINGS[@]}"; do
echo "- $w"
done
exit 1
fi