fix(hooks): after-write dedups its channel files; prior-art tests follow the extractor and skip list into scribe_defs.sh (#2901)

Run 4240: two pre-write hook tests pinned the skip case and scribe_defs()
inside scribe_prior_art.sh, which moved to the shared library; the after-write
test saw the same derive key appended once per changed file. Keep each token
once (sort -u after the appends) and point the pins at the library.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 13:38:06 -04:00
co-authored by Claude Fable 5
parent 5925335ca0
commit b88225eeb3
2 changed files with 16 additions and 5 deletions
+5
View File
@@ -187,6 +187,11 @@ while IFS= read -r rel_path; do
printf '%s' "$body" | jq -r '((.note_ids // []) - (.sync_note_ids // []))[]?' 2>/dev/null >> "$idfile" || true printf '%s' "$body" | jq -r '((.note_ids // []) - (.sync_note_ids // []))[]?' 2>/dev/null >> "$idfile" || true
printf '%s' "$body" | jq -r '(.sync_note_ids // [])[]?' 2>/dev/null >> "$syncfile" || true printf '%s' "$body" | jq -r '(.sync_note_ids // [])[]?' 2>/dev/null >> "$syncfile" || true
printf '%s' "$body" | jq -r '(.derive_keys // [])[]?' 2>/dev/null >> "$derivefile" || true printf '%s' "$body" | jq -r '(.derive_keys // [])[]?' 2>/dev/null >> "$derivefile" || true
# Several files in one call may name the same family: keep each
# token once, so the next request's exclude list stays exact.
for f in "$idfile" "$syncfile" "$derivefile"; do
[ -s "$f" ] && { sort -u -o "$f" "$f" 2>/dev/null || true; }
done
fi fi
fi fi
fi fi
+11 -5
View File
@@ -853,14 +853,18 @@ def test_hook_exits_silently_when_unconfigured():
def test_hook_skips_prose_and_data_files(): def test_hook_skips_prose_and_data_files():
"""No round-trip for a markdown edit — the server would return nothing anyway.""" """No round-trip for a markdown edit — the server would return nothing
src = HOOK.read_text() anyway. The list lives in the shared library (#2901) and the hook asks it."""
skip = re.search(r"case \"\$file_path\" in\n(.*?)esac", src, re.S) lib = (PLUGIN / "hooks" / "scribe_defs.sh").read_text()
assert skip, "expected an extension skip list" skip = re.search(r"scribe_skip_path\(\) \{\n case \"\$1\" in\n(.*?)esac", lib, re.S)
assert skip, "expected an extension skip list in scribe_defs.sh"
for ext in ("*.md", "*.json", "*.lock", "*.png"): for ext in ("*.md", "*.json", "*.lock", "*.png"):
assert ext in skip.group(1) assert ext in skip.group(1)
# Config formats are deliberately NOT skipped — a workflow file is reusable. # Config formats are deliberately NOT skipped — a workflow file is reusable.
assert "*.yml" not in skip.group(1) assert "*.yml" not in skip.group(1)
src = HOOK.read_text()
assert 'scribe_skip_path "$file_path" && exit 0' in src
assert '/scribe_defs.sh"' in src # sourced, not copied
def test_plugin_version_bumped_with_the_hook(): def test_plugin_version_bumped_with_the_hook():
@@ -1150,7 +1154,9 @@ def test_hook_names_the_shapes_being_written():
that changes a body, not a signature — the definition enclosing the edit, that changes a body, not a signature — the definition enclosing the edit,
found by walking the target file upward from the edited lines.""" found by walking the target file upward from the edited lines."""
src = HOOK.read_text() src = HOOK.read_text()
assert "scribe_defs()" in src # one extractor, two consumers lib = (PLUGIN / "hooks" / "scribe_defs.sh").read_text()
assert "scribe_defs()" in lib # one extractor, shared (#2901)
assert "scribe_defs()" not in src # ...not a second copy here
assert ".tool_input.old_string" in src # the Edit's anchor assert ".tool_input.old_string" in src # the Edit's anchor
assert "| tac | scribe_defs | head -1" in src # nearest definition above assert "| tac | scribe_defs | head -1" in src # nearest definition above
# The ledger feed sends NAMES, never bodies, and stays on the one GET. # The ledger feed sends NAMES, never bodies, and stays on the one GET.