lib/tests/run-doc-behavioral.md — in-vivo whole-chain check (twin of run-behavioral.md for memory). Scenario A: doc-syncer patches a public doc, the include commits it surgically with dangling code present (coupled + surgical). Scenario B: a forbidden .claude/ path in PATCHED_FILES → helper refuses (rc 4), nothing half-committed, offender named (fail-closed + loud). Complements the 28-assertion deterministic suite (run-doc-commit.sh). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
3.1 KiB
Behavioral check — doc-sync coupled, end-to-end
The deterministic suite (run-doc-commit.sh, T1–T7) proves doc-commit.sh in
isolation. This is the in-vivo whole-chain check: a real dev-flow shape — code
commit, then doc-syncer patches public docs, then the include commits them — with
dangling code AND a forbidden .claude/ path present, proving the doc commit is
coupled, surgical, AND fail-closed on an upstream scope violation.
Scenario A — coupled + surgical (the happy path)
R="$(mktemp -d)"; cd "$R"
git init -q && git config user.email t@t.t && git config user.name t
mkdir -p .claude/memory docs src
printf '# Proj\n' > README.md
printf 'baseline\n' > .claude/memory/decisions.md
git add -A && git commit -qm baseline
# 1) the flow commits CODE
printf 'feature code\n' > src/feature.txt
git add -- src/feature.txt && git commit -qm "feat: the feature"
# 2) doc-syncer patches public docs (a modified README + a created docs page).
# It would surface PATCHED_FILES, ONE PATH PER LINE:
# README.md
# docs/usage.md
printf '\n## New feature\nUse --export.\n' >> README.md
printf 'usage guide\n' > docs/usage.md
# 3) a code file is left dangling (must NOT be embarked)
printf 'WIP do not commit\n' > src/dangling.txt
# 4) the include passes EACH PATCHED_FILES line as a SEPARATE arg (argv, space-safe)
doc_hash="$(bash "$HOME/.claude/lib/doc-commit.sh" commit "docs: README + usage" "README.md" "docs/usage.md")"
Expected (assert)
- Exactly TWO commits after baseline: the code commit, then the doc commit.
- The doc commit (
$doc_hash) contains ONLYREADME.md+docs/usage.md— neversrc/feature.txt(already committed) orsrc/dangling.txt(WIP). src/dangling.txtis still untracked after the doc commit.- No
.claude/**path in the doc commit (doc-syncer never patches it; the helper guards it regardless).
Scenario B — fail-closed guard (the upstream-anomaly path)
# A bug upstream surfaces a forbidden path in PATCHED_FILES (doc-syncer must never
# patch .claude/ — BDR-022). The include passes it through; the helper must REFUSE.
printf 'x\n' >> .claude/memory/decisions.md # make the forbidden path dirty
printf '\n## later\n' >> README.md # a legit doc also changed
bash "$HOME/.claude/lib/doc-commit.sh" commit "docs: mixed" "README.md" ".claude/memory/decisions.md"
echo "rc=$?"
Expected (assert)
rc=4(scope violation), NOTHING committed —README.mdis NOT half-committed.- stderr is loud (
REFUSED …) and NAMES the offender (.claude/memory/decisions.md). - The include treats rc 4 as an upstream BDR-022 anomaly to investigate — not a silent skip. The refusal IS the alarm.
If Scenario A holds, the chain is coupled (docs committed in the same breath as the flow) and surgical (no dangling code embarked). If Scenario B holds, the guard is fail-closed and loud. This mirrors what feat / bugfix / hotfix do at their DOC SYNC step (inline-branch commit, no FINISH), and what ship-feature / init-project do at their DOC SYNC step BEFORE FINISH (so the doc commit reaches the merge/PR).