create-battle-plan 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/.claude/commands/good-morning.md +13 -0
- package/template/.claude/commands/wrap-up.md +36 -0
- package/template/CLAUDE.md +135 -240
- package/template/events-archive.yml +5 -0
- package/template/events.yml +5 -0
- package/template/tools/events/add.js +64 -0
- package/template/tools/events/archive.js +55 -0
- package/template/tools/events/due-for-gate.js +31 -0
- package/template/tools/events/lib/events.js +221 -0
- package/template/tools/events/migrate-from-csv.js +90 -0
- package/template/tools/events/upcoming.js +33 -0
- package/template/tools/tasks/render-today.js +25 -10
- package/template/tools/verify-cascade.sh +289 -7
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# verify-cascade.sh — Full verification of the Battle Plan cascade system.
|
|
3
|
-
# Checks: dates, metrics, staleness, battle plan freshness
|
|
3
|
+
# Checks: dates, metrics, staleness, battle plan freshness, qualitative
|
|
4
|
+
# claim drift, on-disk file presence, amended-doc UPDATE discipline,
|
|
5
|
+
# chronological-doc dated-heading discipline.
|
|
4
6
|
# Usage: tools/verify-cascade.sh
|
|
5
|
-
# Exit 0 if clean, exit 1 if
|
|
7
|
+
# Exit 0 if clean (or warnings only), exit 1 if errors found.
|
|
6
8
|
|
|
7
9
|
set -euo pipefail
|
|
8
10
|
|
|
@@ -14,6 +16,26 @@ TODAY=$(date +%Y-%m-%d)
|
|
|
14
16
|
WARNINGS=0
|
|
15
17
|
ERRORS=0
|
|
16
18
|
|
|
19
|
+
# Shared find-exclusion list. These paths/filenames are excluded from
|
|
20
|
+
# every check because they are autogenerated, archive-only, or carry
|
|
21
|
+
# their own freshness semantics that don't follow the cascade rules:
|
|
22
|
+
# - examples/ → template scaffolding shown to users
|
|
23
|
+
# - superpowers/ → plugin-skill content; not cascade docs
|
|
24
|
+
# - archive/ → frozen-in-time historical snapshots
|
|
25
|
+
# - social/ → drafts of outbound posts; cascade-irrelevant
|
|
26
|
+
# - today-archive/ → daily flushed task surfaces
|
|
27
|
+
# - today.md → autogenerated by tools/tasks/render-today.js
|
|
28
|
+
# - CLAUDE.md → system instructions, not a cascade doc
|
|
29
|
+
FIND_EXCLUDES=(
|
|
30
|
+
-not -path "*/examples/*"
|
|
31
|
+
-not -path "*/superpowers/*"
|
|
32
|
+
-not -path "*/archive/*"
|
|
33
|
+
-not -path "*/social/*"
|
|
34
|
+
-not -path "*/today-archive/*"
|
|
35
|
+
-not -name "today.md"
|
|
36
|
+
-not -name "CLAUDE.md"
|
|
37
|
+
)
|
|
38
|
+
|
|
17
39
|
echo "=== Battle Plan Verification ==="
|
|
18
40
|
echo "Date: $TODAY"
|
|
19
41
|
echo ""
|
|
@@ -38,7 +60,7 @@ while IFS= read -r doc; do
|
|
|
38
60
|
echo "WARNING: No Last Updated line in $doc"
|
|
39
61
|
WARNINGS=$((WARNINGS + 1))
|
|
40
62
|
fi
|
|
41
|
-
done < <(find "$DOCS_DIR" -name "*.md"
|
|
63
|
+
done < <(find "$DOCS_DIR" -name "*.md" "${FIND_EXCLUDES[@]}" 2>/dev/null)
|
|
42
64
|
|
|
43
65
|
# --- Check 2: Metrics consistency ---
|
|
44
66
|
echo ""
|
|
@@ -73,7 +95,7 @@ if [ -f "$BATTLE_PLAN" ]; then
|
|
|
73
95
|
echo "WARNING: $doc ($doc_date) is newer than battle plan ($bp_date)"
|
|
74
96
|
WARNINGS=$((WARNINGS + 1))
|
|
75
97
|
fi
|
|
76
|
-
done < <(find "$DOCS_DIR" -name "*.md"
|
|
98
|
+
done < <(find "$DOCS_DIR" -name "*.md" "${FIND_EXCLUDES[@]}" 2>/dev/null)
|
|
77
99
|
else
|
|
78
100
|
echo "WARNING: Battle plan not found at $BATTLE_PLAN"
|
|
79
101
|
WARNINGS=$((WARNINGS + 1))
|
|
@@ -96,7 +118,7 @@ while IFS= read -r doc; do
|
|
|
96
118
|
echo "WARNING: No TL;DR in $doc"
|
|
97
119
|
WARNINGS=$((WARNINGS + 1))
|
|
98
120
|
fi
|
|
99
|
-
done < <(find "$DOCS_DIR" -name "*.md"
|
|
121
|
+
done < <(find "$DOCS_DIR" -name "*.md" "${FIND_EXCLUDES[@]}" 2>/dev/null)
|
|
100
122
|
|
|
101
123
|
# --- Check 5: Stale inline references ---
|
|
102
124
|
echo ""
|
|
@@ -119,7 +141,7 @@ while IFS= read -r doc; do
|
|
|
119
141
|
# Skip metrics.yml references (handled by check-metrics)
|
|
120
142
|
[[ "$ref" == *"metrics.yml"* ]] && continue
|
|
121
143
|
|
|
122
|
-
ref_path=$(find "$DOCS_DIR" -name "$ref_file" -not -path "*/examples/*" 2>/dev/null | head -1)
|
|
144
|
+
ref_path=$(find "$DOCS_DIR" -name "$ref_file" -not -path "*/examples/*" -not -path "*/superpowers/*" 2>/dev/null | head -1)
|
|
123
145
|
if [ -z "$ref_path" ]; then
|
|
124
146
|
echo "WARNING: Referenced file $ref_file not found (from $doc)"
|
|
125
147
|
WARNINGS=$((WARNINGS + 1))
|
|
@@ -134,7 +156,7 @@ while IFS= read -r doc; do
|
|
|
134
156
|
WARNINGS=$((WARNINGS + 1))
|
|
135
157
|
fi
|
|
136
158
|
done < <(grep -oE '\(→ [^)]+\)' "$doc" 2>/dev/null || true)
|
|
137
|
-
done < <(find "$DOCS_DIR" -name "*.md"
|
|
159
|
+
done < <(find "$DOCS_DIR" -name "*.md" "${FIND_EXCLUDES[@]}" 2>/dev/null)
|
|
138
160
|
|
|
139
161
|
# --- Check 6: today.md freshness (task subsystem) ---
|
|
140
162
|
echo ""
|
|
@@ -142,16 +164,276 @@ echo "--- Check 6: today.md Freshness ---"
|
|
|
142
164
|
|
|
143
165
|
TASKS_YML="$REPO_ROOT/tasks.yml"
|
|
144
166
|
TODAY_MD="$DOCS_DIR/today.md"
|
|
167
|
+
|
|
145
168
|
if [ -f "$TASKS_YML" ] && [ -f "$TODAY_MD" ]; then
|
|
146
169
|
if [ "$TASKS_YML" -nt "$TODAY_MD" ]; then
|
|
147
170
|
echo "WARNING: tasks.yml is newer than docs/today.md — run \`node tools/tasks/render-today.js\`"
|
|
148
171
|
WARNINGS=$((WARNINGS + 1))
|
|
172
|
+
else
|
|
173
|
+
echo "today.md is fresh relative to tasks.yml"
|
|
149
174
|
fi
|
|
150
175
|
elif [ -f "$TASKS_YML" ] && [ ! -f "$TODAY_MD" ]; then
|
|
151
176
|
echo "WARNING: tasks.yml exists but docs/today.md does not — run \`node tools/tasks/render-today.js\`"
|
|
152
177
|
WARNINGS=$((WARNINGS + 1))
|
|
153
178
|
fi
|
|
154
179
|
|
|
180
|
+
# --- Check 7: Qualitative wrappers near metric links ---
|
|
181
|
+
#
|
|
182
|
+
# Surfaces phrases that DEPEND on the linked metric's current value
|
|
183
|
+
# ("exceeded N", "target hit ✓", "Nx better than baseline", etc.).
|
|
184
|
+
# When the metric value changes — which can happen automatically via
|
|
185
|
+
# sync-metrics — these wrapper phrases can become factually wrong
|
|
186
|
+
# without the metric substitution itself looking suspicious.
|
|
187
|
+
#
|
|
188
|
+
# Example failure mode: a doc once said "we've now exceeded our 40-DM
|
|
189
|
+
# target [**42**](metrics.yml#outreach_sent)". A week later the metric
|
|
190
|
+
# updates to 38 (e.g. dead leads recounted, derivation logic changed).
|
|
191
|
+
# The `[**38**]` substitution is silent; the word "exceeded" is now
|
|
192
|
+
# factually wrong but the link still resolves and looks fine.
|
|
193
|
+
#
|
|
194
|
+
# This is heuristic and informational: hits are WARNINGS, never ERRORS.
|
|
195
|
+
# Tune by editing the QUALITATIVE_REGEX below or excluding paths.
|
|
196
|
+
echo ""
|
|
197
|
+
echo "--- Check 7: Qualitative Wrappers Near Metric Links ---"
|
|
198
|
+
|
|
199
|
+
# Phrases that flag a metric-linked number as a fragile narrative claim.
|
|
200
|
+
# Each is matched on a line that also contains a `metrics.yml#` reference.
|
|
201
|
+
QUALITATIVE_REGEX='(exceeded|target (was|hit)|hit ✓|/[0-9]+ ✓|[0-9]+x better|[0-9]+x the rate|[0-9]+x higher|doubled|tripled|crossed [0-9]+|achieved|surpassed|outperformed)'
|
|
202
|
+
|
|
203
|
+
QW_FILES_FOUND=0
|
|
204
|
+
while IFS= read -r doc; do
|
|
205
|
+
[ -z "$doc" ] && continue
|
|
206
|
+
[[ "$doc" == "$DOCS_DIR/README.md" ]] && continue
|
|
207
|
+
|
|
208
|
+
# Pull lines that contain BOTH a metric link AND a qualitative wrapper.
|
|
209
|
+
# Using `grep -P` would be cleaner but BSD grep on macOS lacks -P, so we
|
|
210
|
+
# chain two extended-regex matches instead.
|
|
211
|
+
matches=$(grep -nE 'metrics\.yml#' "$doc" 2>/dev/null \
|
|
212
|
+
| grep -iE "$QUALITATIVE_REGEX" \
|
|
213
|
+
|| true)
|
|
214
|
+
|
|
215
|
+
if [ -n "$matches" ]; then
|
|
216
|
+
QW_FILES_FOUND=$((QW_FILES_FOUND + 1))
|
|
217
|
+
rel="${doc#$REPO_ROOT/}"
|
|
218
|
+
echo "WARNING: qualitative wrapper near metric link in $rel"
|
|
219
|
+
WARNINGS=$((WARNINGS + 1))
|
|
220
|
+
while IFS= read -r m; do
|
|
221
|
+
[ -z "$m" ] && continue
|
|
222
|
+
# Trim long lines for readability — keep first 200 chars.
|
|
223
|
+
lineno=$(echo "$m" | cut -d: -f1)
|
|
224
|
+
preview=$(echo "$m" | cut -d: -f2- | cut -c1-200)
|
|
225
|
+
echo " L$lineno: $preview"
|
|
226
|
+
done <<<"$matches"
|
|
227
|
+
fi
|
|
228
|
+
done < <(find "$DOCS_DIR" -name "*.md" "${FIND_EXCLUDES[@]}" 2>/dev/null)
|
|
229
|
+
|
|
230
|
+
if [ $QW_FILES_FOUND -eq 0 ]; then
|
|
231
|
+
echo "No qualitative wrappers found near metric links."
|
|
232
|
+
else
|
|
233
|
+
echo ""
|
|
234
|
+
echo " ↑ Re-validate each phrase against the current metric value."
|
|
235
|
+
echo " A wrapper that was true at write-time can become factually"
|
|
236
|
+
echo " wrong after sync-metrics updates the linked number."
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
# --- Check 8: Personal-surface files present on disk ---
|
|
240
|
+
#
|
|
241
|
+
# tasks.yml + events.yml + events-archive.yml are typically gitignored
|
|
242
|
+
# (they are your personal daily-task / events surface — not shared with
|
|
243
|
+
# collaborators, and not visible to a teammate's Claude session). They
|
|
244
|
+
# must still exist on disk for render-today.js / due-for-gate.js /
|
|
245
|
+
# /wrap-up to work.
|
|
246
|
+
#
|
|
247
|
+
# Failure mode this catches: a routine gitignore commit that uses
|
|
248
|
+
# `git rm <file>` instead of `git rm --cached <file>` deletes the
|
|
249
|
+
# on-disk copies. Nothing crashes — the scripts silently no-op when
|
|
250
|
+
# the files are missing: today.md renders empty, due-for-gate returns
|
|
251
|
+
# []. Fail loud here so the same mistake surfaces immediately next
|
|
252
|
+
# time instead of after several days of empty surfaces.
|
|
253
|
+
#
|
|
254
|
+
# Fresh installs: a file that's missing AND has never been tracked in
|
|
255
|
+
# git is treated as "not bootstrapped yet" rather than "lost" — silently
|
|
256
|
+
# noted, never errored. Run `tools/init-project.sh` (or just create the
|
|
257
|
+
# empty files) to bootstrap. The error only fires when a file was once
|
|
258
|
+
# tracked but is now gone from disk — that's the real recovery scenario.
|
|
259
|
+
echo ""
|
|
260
|
+
echo "--- Check 8: Personal-Surface Files On Disk ---"
|
|
261
|
+
|
|
262
|
+
CHECK8_MISSING=0
|
|
263
|
+
CHECK8_NOT_BOOTSTRAPPED=0
|
|
264
|
+
HAS_GIT=0
|
|
265
|
+
if git -C "$REPO_ROOT" rev-parse --git-dir >/dev/null 2>&1; then
|
|
266
|
+
HAS_GIT=1
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
for f in tasks.yml events.yml events-archive.yml; do
|
|
270
|
+
[ -f "$REPO_ROOT/$f" ] && continue
|
|
271
|
+
|
|
272
|
+
# Was this file ever tracked? If so, missing-on-disk is a real recovery
|
|
273
|
+
# scenario. If never tracked (or no git at all), treat as "not bootstrapped".
|
|
274
|
+
was_tracked=0
|
|
275
|
+
if [ $HAS_GIT -eq 1 ]; then
|
|
276
|
+
if [ -n "$(git -C "$REPO_ROOT" rev-list --all -- "$f" 2>/dev/null | head -1)" ]; then
|
|
277
|
+
was_tracked=1
|
|
278
|
+
fi
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
if [ $was_tracked -eq 1 ]; then
|
|
282
|
+
echo "ERROR: $f is missing on disk (but was previously tracked in git)."
|
|
283
|
+
echo " This file is typically gitignored but must exist locally. Likely cause:"
|
|
284
|
+
echo " someone ran 'git rm $f' (not 'git rm --cached $f') in a past"
|
|
285
|
+
echo " gitignore commit. Recover with:"
|
|
286
|
+
echo " git log --all --diff-filter=D -- $f # find the deletion commit <C>"
|
|
287
|
+
echo " git show <C>^:$f > $f # restore the last tracked version"
|
|
288
|
+
ERRORS=$((ERRORS + 1))
|
|
289
|
+
CHECK8_MISSING=$((CHECK8_MISSING + 1))
|
|
290
|
+
else
|
|
291
|
+
CHECK8_NOT_BOOTSTRAPPED=$((CHECK8_NOT_BOOTSTRAPPED + 1))
|
|
292
|
+
fi
|
|
293
|
+
done
|
|
294
|
+
|
|
295
|
+
if [ $CHECK8_MISSING -eq 0 ] && [ $CHECK8_NOT_BOOTSTRAPPED -eq 0 ]; then
|
|
296
|
+
echo "All personal-surface files present."
|
|
297
|
+
elif [ $CHECK8_MISSING -eq 0 ] && [ $CHECK8_NOT_BOOTSTRAPPED -gt 0 ]; then
|
|
298
|
+
echo "Note: $CHECK8_NOT_BOOTSTRAPPED personal-surface file(s) not yet bootstrapped (never tracked). Run \`tools/init-project.sh\` or create empty stubs to start using them."
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
# --- Check 9: Amended-doc UPDATE block discipline ---
|
|
302
|
+
#
|
|
303
|
+
# Amended-compression docs require every revision to be a
|
|
304
|
+
# `> **[UPDATE YYYY-MM-DD · Source: ...]**` block placed immediately
|
|
305
|
+
# above the claim it modifies. Without UPDATE blocks, /distill cannot
|
|
306
|
+
# tell what's new vs old when collapsing the doc, and the timeline of
|
|
307
|
+
# how a claim evolved is silently lost.
|
|
308
|
+
#
|
|
309
|
+
# Heuristic: if an amended doc was modified vs origin/main (or HEAD if
|
|
310
|
+
# origin/main is unavailable) and the diff added content lines but no
|
|
311
|
+
# new UPDATE block, warn. Allows some false positives (e.g. UPDATE
|
|
312
|
+
# block continuation lines, frontmatter touches) — tunable below.
|
|
313
|
+
#
|
|
314
|
+
# Skipped silently when there's no git history yet (fresh repo).
|
|
315
|
+
echo ""
|
|
316
|
+
echo "--- Check 9: Amended-Doc UPDATE Block Discipline ---"
|
|
317
|
+
|
|
318
|
+
# Pick the diff base. Prefer origin/main; fall back to HEAD.
|
|
319
|
+
DIFF_BASE=""
|
|
320
|
+
if git -C "$REPO_ROOT" rev-parse --verify --quiet origin/main >/dev/null 2>&1; then
|
|
321
|
+
DIFF_BASE="origin/main"
|
|
322
|
+
elif git -C "$REPO_ROOT" rev-parse --verify --quiet HEAD >/dev/null 2>&1; then
|
|
323
|
+
DIFF_BASE="HEAD"
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
if [ -z "$DIFF_BASE" ]; then
|
|
327
|
+
echo "No git base ref to diff against (no origin/main, no HEAD) — skipping Check 9."
|
|
328
|
+
else
|
|
329
|
+
CHECK9_HITS=0
|
|
330
|
+
while IFS= read -r doc; do
|
|
331
|
+
[ -z "$doc" ] && continue
|
|
332
|
+
[[ "$doc" == "$DOCS_DIR/README.md" ]] && continue
|
|
333
|
+
|
|
334
|
+
compression=$(grep '^\*\*Compression:\*\*' "$doc" | head -1 | sed 's/\*\*Compression:\*\* //' || true)
|
|
335
|
+
[[ "$compression" != "amended" ]] && continue
|
|
336
|
+
|
|
337
|
+
rel="${doc#$REPO_ROOT/}"
|
|
338
|
+
|
|
339
|
+
# Diff against base. Suppress errors if file is untracked.
|
|
340
|
+
diff_out=$(git -C "$REPO_ROOT" diff "$DIFF_BASE" -- "$rel" 2>/dev/null || true)
|
|
341
|
+
[ -z "$diff_out" ] && continue
|
|
342
|
+
|
|
343
|
+
# Count added content lines (excluding diff headers, frontmatter, blank lines).
|
|
344
|
+
# Frontmatter lines start with **Last Updated:**, **Status:**, etc.
|
|
345
|
+
added_content=$(echo "$diff_out" \
|
|
346
|
+
| grep -E '^\+[^+]' \
|
|
347
|
+
| grep -vE '^\+\s*$' \
|
|
348
|
+
| grep -vE '^\+\*\*(Last Updated|Status|Role|Compression|TL;DR):' \
|
|
349
|
+
| wc -l | tr -d ' ')
|
|
350
|
+
|
|
351
|
+
# Count added UPDATE blocks.
|
|
352
|
+
added_updates=$(echo "$diff_out" \
|
|
353
|
+
| grep -cE '^\+> \*\*\[UPDATE [0-9]{4}-[0-9]{2}-[0-9]{2}' \
|
|
354
|
+
|| true)
|
|
355
|
+
|
|
356
|
+
# Warn if content added but no UPDATE block added.
|
|
357
|
+
if [ "$added_content" -gt 0 ] && [ "$added_updates" -eq 0 ]; then
|
|
358
|
+
echo "WARNING: $rel — $added_content content line(s) added vs $DIFF_BASE but no new UPDATE block."
|
|
359
|
+
echo " Amended-compression docs require '> **[UPDATE YYYY-MM-DD · Source: ...]**' for every revision."
|
|
360
|
+
WARNINGS=$((WARNINGS + 1))
|
|
361
|
+
CHECK9_HITS=$((CHECK9_HITS + 1))
|
|
362
|
+
fi
|
|
363
|
+
done < <(find "$DOCS_DIR" -name "*.md" "${FIND_EXCLUDES[@]}" 2>/dev/null)
|
|
364
|
+
|
|
365
|
+
if [ $CHECK9_HITS -eq 0 ]; then
|
|
366
|
+
echo "All amended-doc edits have proper UPDATE blocks (or no amended docs modified)."
|
|
367
|
+
fi
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
# --- Check 10: Chronological-doc dated-heading discipline ---
|
|
371
|
+
#
|
|
372
|
+
# Chronological-compression docs require every new entry to start with
|
|
373
|
+
# a dated heading: `## YYYY-MM-DD — <title>`, `## Session N (YYYY-MM-DD)
|
|
374
|
+
# — <title>`, `### Day N — <weekday>`, etc. Undated entries get
|
|
375
|
+
# silently absorbed into the wrong era during /distill, scrambling the
|
|
376
|
+
# timeline of a doc that exists specifically to preserve a timeline.
|
|
377
|
+
#
|
|
378
|
+
# Heuristic: for each chronological doc modified vs the diff base, find
|
|
379
|
+
# every heading added in the diff (lines starting with +## or +###) and
|
|
380
|
+
# verify it matches one of the dated patterns. Standard fixed section
|
|
381
|
+
# headings (TL;DR, Status, Pipeline, etc.) are exempted via a second
|
|
382
|
+
# allowlist regex — extend FIXED_HEADING_REGEX below if your project
|
|
383
|
+
# introduces new fixed section names.
|
|
384
|
+
#
|
|
385
|
+
# Skipped silently when there's no git history yet (fresh repo).
|
|
386
|
+
echo ""
|
|
387
|
+
echo "--- Check 10: Chronological-Doc Dated Headings ---"
|
|
388
|
+
|
|
389
|
+
if [ -z "$DIFF_BASE" ]; then
|
|
390
|
+
echo "No git base ref to diff against — skipping Check 10."
|
|
391
|
+
else
|
|
392
|
+
# Allowed heading patterns (extended-regex):
|
|
393
|
+
# - "## 2026-05-21" or "## 2026-05-21 — ..."
|
|
394
|
+
# - "## Session 17" or "## Session 17 (2026-05-20) — ..."
|
|
395
|
+
# - "### Day 53" or "### Day 53 — ..."
|
|
396
|
+
DATED_HEADING_REGEX='^\+#{2,3} (([0-9]{4}-[0-9]{2}-[0-9]{2})|((Day|Session) [0-9]+))'
|
|
397
|
+
# Standard fixed headings used in battle-plan / hypotheses / insights
|
|
398
|
+
# docs. Extend this list if your project introduces new fixed sections.
|
|
399
|
+
FIXED_HEADING_REGEX='^\+#{2,4} (Daily Log|Sessions|Recent Sessions|TL;DR|Status|Pipeline|Key Metrics|Weekly plan|Contingency Plans|Documents This Plan Depends On|Rules for This Document|Next milestones|Scope locks|Role|Compression|Timeline|Goal|Notes|References|Cross-references|Validation protocol|Kill criteria|Hypothesis statement|Confidence|Impact)'
|
|
400
|
+
|
|
401
|
+
CHECK10_HITS=0
|
|
402
|
+
while IFS= read -r doc; do
|
|
403
|
+
[ -z "$doc" ] && continue
|
|
404
|
+
[[ "$doc" == "$DOCS_DIR/README.md" ]] && continue
|
|
405
|
+
|
|
406
|
+
compression=$(grep '^\*\*Compression:\*\*' "$doc" | head -1 | sed 's/\*\*Compression:\*\* //' || true)
|
|
407
|
+
[[ "$compression" != "chronological" ]] && continue
|
|
408
|
+
|
|
409
|
+
rel="${doc#$REPO_ROOT/}"
|
|
410
|
+
diff_out=$(git -C "$REPO_ROOT" diff "$DIFF_BASE" -- "$rel" 2>/dev/null || true)
|
|
411
|
+
[ -z "$diff_out" ] && continue
|
|
412
|
+
|
|
413
|
+
# Find added headings (## or ###) that match neither dated nor fixed patterns.
|
|
414
|
+
bad_headings=$(echo "$diff_out" \
|
|
415
|
+
| grep -E '^\+#{2,3} ' \
|
|
416
|
+
| grep -vE "$DATED_HEADING_REGEX" \
|
|
417
|
+
| grep -vE "$FIXED_HEADING_REGEX" \
|
|
418
|
+
|| true)
|
|
419
|
+
|
|
420
|
+
if [ -n "$bad_headings" ]; then
|
|
421
|
+
echo "WARNING: $rel — undated heading(s) added vs $DIFF_BASE:"
|
|
422
|
+
while IFS= read -r h; do
|
|
423
|
+
[ -z "$h" ] && continue
|
|
424
|
+
echo " ${h:1:200}"
|
|
425
|
+
done <<<"$bad_headings"
|
|
426
|
+
echo " Chronological-compression docs require '## YYYY-MM-DD — <title>' or '## Session N (YYYY-MM-DD) — <title>' or '### Day N — <weekday>'."
|
|
427
|
+
WARNINGS=$((WARNINGS + 1))
|
|
428
|
+
CHECK10_HITS=$((CHECK10_HITS + 1))
|
|
429
|
+
fi
|
|
430
|
+
done < <(find "$DOCS_DIR" -name "*.md" "${FIND_EXCLUDES[@]}" 2>/dev/null)
|
|
431
|
+
|
|
432
|
+
if [ $CHECK10_HITS -eq 0 ]; then
|
|
433
|
+
echo "All chronological-doc new headings are properly dated (or no chronological docs modified)."
|
|
434
|
+
fi
|
|
435
|
+
fi
|
|
436
|
+
|
|
155
437
|
# --- Summary ---
|
|
156
438
|
echo ""
|
|
157
439
|
echo "========================="
|