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
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:
@@ -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": {
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user