Files
cgalo5758 a5a4b9c591 Gate archives on notebook citations and add the harvest skill
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.
2026-09-19 19:47:15 -05:00

40 lines
1.5 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Refuse to archive an OpenSpec change while tracked text still cites the
# notebook.
#
# Archiving is the moment a change's files freeze (status/MAINTAINING.md,
# "Where things live": archives are not rewritten), so a proposal or design
# that still names a path under status/explorations/ has to be harvested
# before the move, not after. Any harness that runs shell commands calls
# this with the command it is about to run: it exits 0 at once for anything
# that is not an archive, and for an archive (an `mv` into
# openspec/changes/archive/, or `openspec archive`) it runs
# scripts/notebook-citations.sh and exits 1 with the hits and the
# instruction when there are any. The rule and the message live here; a
# harness adapter only extracts the command and relays the exit:
# .claude/hooks/archive-gate.sh for Claude Code, .opencode/plugin/
# archive-gate.ts for opencode.
#
# Usage: ./scripts/archive-gate.sh '<shell command about to run>'
set -euo pipefail
cd "$(dirname "$0")/.."
cmd=${1:-}
move='(^|[;&|[:space:]])(git[[:space:]]+)?mv[[:space:]].*openspec/changes/archive'
archive='(^|[;&|[:space:]])openspec[[:space:]]+archive([[:space:]]|$)'
if ! [[ $cmd =~ $move || $cmd =~ $archive ]]; then
exit 0
fi
if hits=$(./scripts/notebook-citations.sh 2>&1); then
exit 0
fi
{
echo "archive-gate: not archiving while tracked text cites the notebook."
echo "$hits"
echo
echo "Harvest first (the harvest skill; status/MAINTAINING.md, \"Landing a change\"): state each fact in its tracked home, then archive."
} >&2
exit 1