A notebook's lessons survive only if they are written into a tracked home before the decision lands, so landing a change now has three pieces. The contract: a "Landing a change" section in MAINTAINING.md and a fifth item in the UI definition of done. The labor: a harvest skill that inventories the notebook, names each file's kind and routes findings to the owning docs page, reasoning to the change's design.md, problems to issues.md and reusable tools to a tracked home. The gate: scripts/notebook-citations.sh, a git grep over what git would commit for the notebook's directory shape, which make lint runs after the program's own lint, and scripts/archive-gate.sh, which refuses an mv into the archive or an openspec archive while the check reports hits. The gate is one script that any harness calls with the command it is about to run; Claude Code reaches it through a PreToolUse hook and opencode through a plugin. Both checks live in scripts/ rather than the lint subcommand because they are repository hygiene, not part of the shipped program. The generated openspec-archive-change skill is not edited; the post-archive reminder carries the harvest step instead.
48 lines
1.9 KiB
Bash
Executable File
48 lines
1.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Refuse tracked text that names a path under status/explorations/.
|
|
#
|
|
# status/explorations/ is a local lab notebook git ignores
|
|
# (status/MAINTAINING.md, "Where things live"), so a tracked file naming a
|
|
# path under it points at something another clone does not have. This check
|
|
# reads what git would commit (the index plus untracked files the ignore rules
|
|
# do not exclude, binaries skipped) for the notebook's directory shape,
|
|
# explorations/<topic>-<yyyy-mm>, and leaves out what MAINTAINING.md freezes
|
|
# (archived changes, status/archive, status/log), design/ (synced from
|
|
# upstream, never edited here) and the notebook itself. The <topic>-<yyyy-mm>
|
|
# template MAINTAINING.md and the skills use has no digits, so a description
|
|
# of the convention is not a hit.
|
|
#
|
|
# It lives here and not in internal/lint because it is repository hygiene,
|
|
# not part of the shipped program; `make lint` runs it after the program's
|
|
# own lint. Exits 1 with one file:line per hit, 0 on a clean tree.
|
|
#
|
|
# Usage: ./scripts/notebook-citations.sh [repo-root]
|
|
set -euo pipefail
|
|
cd "${1:-$(dirname "$0")/..}"
|
|
|
|
pattern='explorations/[a-z0-9]+(-[a-z0-9]+)*-20[0-9]{2}-[0-9]{2}'
|
|
hits=$(git grep -n -I -E --untracked "$pattern" -- . \
|
|
':!design' ':!openspec/changes/archive' ':!status/archive' \
|
|
':!status/explorations' ':!status/log' ':!test/screens' || true)
|
|
|
|
if [[ -z "$hits" ]]; then
|
|
echo "notebook-citations: ok" >&2
|
|
exit 0
|
|
fi
|
|
|
|
count=0
|
|
while IFS= read -r line; do
|
|
file=${line%%:*}
|
|
rest=${line#*:}
|
|
lineno=${rest%%:*}
|
|
text=${rest#*:}
|
|
cite=$(grep -oE "$pattern" <<<"$text" | head -1)
|
|
printf '%s:%s: [notebook-citation] cites the exploration notebook "%s", which a clone does not have; state the fact or cite its tracked home (status/MAINTAINING.md, "Where things live")\n' \
|
|
"$file" "$lineno" "$cite" >&2
|
|
count=$((count + 1))
|
|
done <<<"$hits"
|
|
echo >&2
|
|
echo "notebook-citations: $count hit(s)" >&2
|
|
exit 1
|