feat(prior-art): edit-time record-sync nudge — the sync class (#2708)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 24s
CI & Build / integration (push) Successful in 26s
CI & Build / Python tests (push) Successful in 56s
CI & Build / Build & push image (push) Successful in 43s

A snippet recorded AT the exact file being edited is not a reuse
suggestion — it IS the record of the file being changed. The write-path
hint now renders those as their own SYNC class: 'snippet #N records this
file — updating the record is part of the edit (update_snippet /
verify_snippet)'. Nearby and semantic hits stay the reuse menu.

The two classes dedup on separate per-session channels (exclude_ids vs
exclude_sync_ids, .ids vs .sync.ids in the hook), so a reuse hint shown
early in a session can no longer silence the record-sync nudge when the
recorded file itself is edited later. Sync surfacing is measured under
its own note_usage source (write_path_sync) — its pull-through rate is
the scoreboard for whether edit-time sync actually happens, per decision
#2707 (no forge connection; records stay current in the session that has
the context). Plugin 0.1.31.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 20:05:01 -04:00
co-authored by Claude Fable 5
parent 765635bbf2
commit 3162332a13
9 changed files with 290 additions and 69 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "scribe",
"description": "Scribe system-of-record for Claude Code: MCP tools over your notes/tasks/projects/rules, a session-start push channel that surfaces your always-on rules + active-project context, process-skills (writing-plans, systematic-debugging, verification, brainstorming, reusing-code), and your saved Scribe Processes auto-surfaced as skills (/scribe:sync). Replaces superpowers + file-memory with one app-backed plugin.",
"version": "0.1.30",
"version": "0.1.31",
"author": { "name": "Bryan Van Deusen" },
"mcpServers": {
"scribe": {
+3
View File
@@ -50,6 +50,9 @@ On install you'll be asked for:
(`hooks/scribe_prior_art.sh`) → `GET /api/plugin/prior-art`. Returns
`additionalContext` with **no** permission decision, so it can inform the write
but never stop it; silent when nothing is recorded, which is most of the time.
Two framings: a REUSE menu (similar/nearby records), and a SYNC nudge when a
snippet records the exact file being edited — "updating the record is part of
the edit" — each with its own once-per-session dedup.
Toggle in **Settings → Knowledge auto-inject**.
- `skills/` → the universal process-skills, surfaced by description match.
- `hooks/scribe_sync_processes.sh` (a 2nd SessionStart hook) + the `/scribe:sync`
+30 -4
View File
@@ -8,6 +8,12 @@
# that path or in its directory, plus snippets resembling the code about to be
# written. Titles + ids only, never bodies.
#
# The answer comes in two framings (#2708). A snippet recorded AT the exact
# file being edited is the SYNC class — "you are editing the recorded file;
# updating the record is part of the edit" — which is how records stay current
# on an instance with no forge connection (decision #2707). Everything else is
# the REUSE menu. The two dedup separately (see the state files below).
#
# NEVER BLOCKS. It returns `additionalContext` with no `permissionDecision`, so
# the write proceeds untouched and Claude sees the note beside the tool result.
# Any failure — unconfigured, unreachable, malformed — exits 0 in silence. A
@@ -194,31 +200,51 @@ fi
# surface shows a given snippet at most once per session, but they don't silence
# each other: a title that flew past in a prompt menu twenty turns ago is
# exactly what should reappear at the moment the duplicate is being written.
#
# TWO channels, not one (#2708). The server answers in two classes — REUSE
# ("something similar/nearby is recorded") and SYNC ("a snippet records the
# exact file being edited — updating the record is part of the edit"). They
# dedup separately: a reuse hint shown early in the session must not suppress
# the sync nudge when the recorded file itself is edited later.
state_dir="${TMPDIR:-/tmp}/scribe-priorart"
mkdir -p "$state_dir" 2>/dev/null || true
idfile=""
syncfile=""
exclude_q=""
sync_exclude_q=""
if [ -n "$session_id" ]; then
safe_sid=$(printf '%s' "$session_id" | tr -c 'A-Za-z0-9._-' '_')
idfile="$state_dir/${safe_sid}.ids"
syncfile="$state_dir/${safe_sid}.sync.ids"
if [ -f "$idfile" ]; then
seen=$(tr '\n' ',' < "$idfile" 2>/dev/null | sed 's/,$//')
[ -n "$seen" ] && exclude_q="&exclude_ids=${seen}"
fi
if [ -f "$syncfile" ]; then
sync_seen=$(tr '\n' ',' < "$syncfile" 2>/dev/null | sed 's/,$//')
[ -n "$sync_seen" ] && sync_exclude_q="&exclude_sync_ids=${sync_seen}"
fi
fi
# `|| true`, not `|| exit 0`: an unreachable instance must not discard a local
# finding that needed no instance to produce.
body=$(curl -fsS --max-time 5 \
-H "Authorization: Bearer ${token}" \
"${url%/}/api/plugin/prior-art?path=${path_enc}&code=${code_enc}${repo_q}${exclude_q}" 2>/dev/null) || body=""
"${url%/}/api/plugin/prior-art?path=${path_enc}&code=${code_enc}${repo_q}${exclude_q}${sync_exclude_q}" 2>/dev/null) || body=""
context=""
if [ -n "$body" ]; then
context=$(printf '%s' "$body" | jq -r '.context // empty' 2>/dev/null) || context=""
# Remember what was surfaced so it isn't shown again this session.
if [ -n "$idfile" ] && [ -n "$context" ]; then
printf '%s' "$body" | jq -r '.note_ids[]? // empty' 2>/dev/null >> "$idfile" || true
# Remember what was surfaced so it isn't shown again this session — each
# class into its own channel: sync ids (snippets recording the edited file)
# to the sync file, everything else to the reuse file.
if [ -n "$context" ]; then
if [ -n "$idfile" ]; then
printf '%s' "$body" | jq -r '((.note_ids // []) - (.sync_note_ids // []))[]?' 2>/dev/null >> "$idfile" || true
fi
if [ -n "$syncfile" ]; then
printf '%s' "$body" | jq -r '(.sync_note_ids // [])[]?' 2>/dev/null >> "$syncfile" || true
fi
fi
fi
+8
View File
@@ -41,6 +41,14 @@ through recall/auto-inject; this skill is the active reflex around that.
it before you go any further. Either it's the helper you were about to
duplicate — reuse it and drop yours — or it isn't, and the record needs the new
location adding. Both are cheaper now than after the duplicate settles in.
- **A `[records this file]` hint is a duty, not a menu.** When the hint says a
snippet records the very file you're editing, the record's freshness is now
YOUR edit's responsibility: if the edit changes the recorded shape,
`update_snippet(id, code=…)` with the new form as part of the same task; if
it doesn't, `verify_snippet(id, status="ok", commit_sha=…)` costs one call
and re-stamps the record as checked. Scribe never reads the repo — this
moment, in the session that has the context, is the only place the record
gets kept true.
## The first time a shape is built — record it