fix(#4022): instruction surfaces stop describing the always-on tier milestone 394 removed
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 47s
CI & Build / TypeScript typecheck (push) Successful in 58s
CI & Build / Python tests (push) Successful in 1m41s
CI & Build / Build & push image (push) Successful in 28s

Skill bodies and tool docstrings still taught the deleted model: using-scribe
said always-on rules "arrive whether or not you ask" and that SessionStart may
inject a rule index; create_project, decide_project_inception and enter_project
said an undecided project gets "every always-on rulebook"; create_rule pointed
standards at "the always-on one"; the verification sweeps and
retrieval_telemetry listed always-on paths and a live preload.

Every passage now describes the current model: every rule is retrieved, a
rulebook binds only by subscription, an undecided project inherits nothing,
and the preload survives only in telemetry rows older than 394.

Also repairs three sentences left half-replaced by the 394 edits: the static
context's "If you have not loaded the no rule has arrived", create_rule's
"an A subscribed rulebook", and create_project's doubled
subscribe_rulebooks entry. Plugin version minted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-14 10:39:27 -04:00
co-authored by Claude Opus 5
parent 7239e3c479
commit 6c1fd28179
7 changed files with 46 additions and 49 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 active-project context, process-skills (writing-plans, reporting-back, 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": "2026.09.14.1411",
"version": "2026.09.14.1438",
"author": {
"name": "Bryan Van Deusen"
},
+3 -3
View File
@@ -17,9 +17,9 @@ for the operator's work, and as your own working memory across sessions.
commit / push, or any other hard-to-reverse or outward-facing action, the
operator's Scribe rules decide what to do — NOT generic conventions baked
into the harness or your defaults (e.g. "branch before committing," "open a
feature branch per task," "push to a fork"). If you have not loaded the
no rule has arrived for the act in front of you, `search(content_type=
"rule")` BEFORE acting rather than falling back on a default habit. When a
feature branch per task," "push to a fork"). If no rule has arrived for the
act in front of you, `search(content_type="rule")` BEFORE acting rather than
falling back on a default habit. When a
retrieved rule and a default habit disagree, the rule wins; if no rule
speaks to it, ask rather than assume.
- **Rules bind; preferences do not.** A record's `kind` says which. A **rule**
+12 -14
View File
@@ -27,10 +27,9 @@ If the working repo maps to a Scribe project (you're in a known repo, or
project plus the rules bound to the areas it works in, open tasks, and recent
notes in one shot.
Do this actively. A SessionStart hook *may* also inject a rule index, but treat
that as a bonus, not a precondition: it can be absent (e.g. when the instance is
unreachable, or the token didn't reach the hook), so the reliable path is this
explicit pull. Rules loaded this way are **binding** for the session.
Do this actively. Nothing is handed to a session up front to stand in for it —
rules arrive by retrieval, when your work or the operator's message matches
one — so asking and entering the project are the reliable path.
## Scribe holds these functions — don't keep a second copy
@@ -88,17 +87,16 @@ Two constraints on *how* that's achieved:
asks. If what you learned is that something MUST be done a certain way,
that is a rule to propose, not a preference to harden in place.
Rules come in two tiers. **Always-on** rules are delivered — they arrive
whether or not you ask. **Conditional** rules are RETRIEVED, and one binds
just as hard for never having been handed to you. So before a consequential
act, `search(content_type="rule")` on what you are about to do. An empty
loaded set is not evidence that no rule applies; it is only evidence that
none was pushed, and those are different claims.
Every rule is RETRIEVED: one reaches you when a command, the code you are
writing or the operator's message resembles what it is about, and a rule
binds just as hard for never having been handed to you. So before a
consequential act, `search(content_type="rule")` on what you are about to
do. An empty session is not evidence that no rule applies; it is only
evidence that nothing has matched yet, and those are different claims.
The tier split exists because delivery does not scale: every resident rule
costs tokens in every session forever, so a rulebook that grows past a few
dozen either stops growing or stops fitting. Retrieval is what lets the
rulebook keep growing — but retrieval only fires if something asks.
Retrieval is what lets a rulebook keep growing — a rule costs nothing in a
session it has nothing to do with — but retrieval only fires if something
asks.
**Ask hardest where you feel most certain.** Rules about which TOOL to reach
for — use the forge's MCP client rather than curling its API, don't stand up
+2 -2
View File
@@ -384,8 +384,8 @@ async def notes_due_for_verification(
thing there is. 0 = no age filter.
project_id: narrow to one project. 0 = every project. Unlike the rules
sweep, this filter is safe: a note belongs to at most one project
outright, with none of the subscription and always-on paths that
would make a project filter UNDER-report a rule.
outright, with none of the subscription paths that would make a
project filter UNDER-report a rule.
never_only: only notes nobody has ever verified.
"""
uid = current_user_id()
+11 -12
View File
@@ -83,7 +83,7 @@ async def enter_project(project_id: int) -> dict:
`inception` (milestone 297) appears ONLY when the project is yours and
nobody has decided what it inherits: it carries the current defaults
(which always-on rulebooks bind, design system, Systems), what to ask the
(the rulebooks it could subscribe to, design system, Systems), what to ask the
operator — once — and the decide_project_inception call that answers it;
it repeats on every enter until a decision is recorded.
@@ -149,7 +149,7 @@ async def enter_project(project_id: int) -> dict:
)
# The inception ask (milestone 297): a project nobody has decided on
# inherits its defaults silently — always-on rulebooks, no design system,
# inherits nothing, silently — no rulebook subscriptions, no design system,
# no Systems. Owner-only (deciding is the owner's), and only until a
# decision is recorded; the key is ABSENT otherwise (#2483).
inception_ask = None
@@ -288,8 +288,8 @@ async def create_project(
before calling, ask the operator the four inception questions and pass
the answers; a project created without any of them is UNDECIDED and
enter_project will ask until decide_project_inception records it.
Defaults if nobody decides: every always-on rulebook binds, nothing is
subscribed, no design system, no Systems.
Defaults if nobody decides: no rulebook subscriptions, no design system,
no Systems.
Args:
title: Project name (required).
@@ -297,11 +297,10 @@ async def create_project(
goal: The desired outcome or definition of done for the project.
status: one of active (default), paused, completed, archived.
color: Optional hex colour for the project card (e.g. "#6366f1").
subscribe_rulebooks: rulebook ids this project opts into. Since
milestone 394 subscription is the only way a rulebook binds a
project, so there is no automatic tier left to decline. Was
NOT inherit ([] = inherit them all). list_rulebooks shows which are
subscribe_rulebooks: rulebook ids to subscribe (the non-always-on ones).
subscribe_rulebooks: rulebook ids this project opts into.
Subscription is the only way a rulebook binds a project, so a
rulebook left out simply does not apply. list_rulebooks shows
which exist.
design_system_id: the design system this project's UI is built from
(list_design_systems); -1 = explicitly none; 0 = not stated.
seed_systems: true mints the standard starter Systems (CI & Release,
@@ -351,9 +350,9 @@ async def decide_project_inception(
unsubscribe_project_from_rulebook to undo one), replaces the design
system, and never re-seeds Systems a project already has.
Args: as create_project's inception args. Passing nothing records an
inherit-all decision (every always-on rulebook binds, no subscriptions,
no design system, no seed) — a valid answer, stated.
Args: as create_project's inception args. Passing nothing records a
decision to take nothing (no subscriptions, no design system, no seed) —
a valid answer, stated.
"""
uid = current_user_id()
choices = _inception_choices(
+5 -6
View File
@@ -308,13 +308,12 @@ async def create_rule(
and let the answer stand; re-raising a declined proposal argues a rule
into existence, which is the thing this whole loop exists to prevent.
A rulebook rule is shared by every project that gets the rulebook: an
A subscribed rulebook binds the
projects that opt in. So a rulebook rule must read as a general standard —
A rulebook rule is shared by every project subscribed to the rulebook, so
it must read as a general standard —
never pin it to one project's files, paths, or quirks. For a rule that
applies to a single project only, use create_project_rule instead (no
rulebook+topic ceremony). If it's a standard a CATEGORY of projects shares,
put it in a themed subscribed rulebook, not the always-on one.
put it in a rulebook for that category and subscribe those projects to it.
Write it general WITHOUT hedging for the exceptions. A project that needs
to strengthen, narrow or replace this rule writes its own and links it
@@ -1048,8 +1047,8 @@ async def rules_due_for_verification(
never_only: only rules nobody has ever verified.
NOT filterable by project, deliberately: a project reaches rules through
project scope, subscriptions, always-on rulebooks and exclusions, and a
filter that missed one of those paths would UNDER-report — which is the
project scope and rulebook subscriptions, and a filter that missed one of
those paths would UNDER-report — which is the
exact failure this whole surface exists to prevent. Read the whole list.
"""
uid = current_user_id()
+12 -11
View File
@@ -280,19 +280,20 @@ It is an UPPER BOUND per surface: a pull records the door it came
`surfaced` VS `ambient` IS THE READING THAT MATTERS HERE. `surfaced` counts
rules a ranker chose — today only the write-path arm — and those are claims
a pull can settle. `ambient` counts BULK DELIVERIES: the SessionStart
preload and the `rules_payload` surfaces
(`enter_project`, `get_project`, `get_milestone`, `start_planning`,
`get_task`), which hand over the whole applicable set at once with nobody
choosing anything. A large `ambient` says the resident set is big and
arrives often — never that it is useful, and never that it is read.
a pull can settle. `ambient` counts BULK DELIVERIES: the `rules_payload`
surfaces (`enter_project`, `get_project`, `get_milestone`,
`start_planning`, `get_task`), which hand over the whole applicable set at
once with nobody choosing anything — plus, in rows older than milestone
394, the SessionStart preload it removed. A large `ambient` says a bulk set
is big and arrives often — never that it is useful, and never that it is
read.
`pull_through` therefore divides by `surfaced` alone. Fold the preload in
and growing the always-on set would depress the arm's measured precision
while trimming it would flatter it, for reasons having nothing to do with
the arm. To judge the PRELOAD instead, compare `ambient` against pulls of
those same rules over time: a resident set surfaced thousands of times and
opened never is the dead-weight signal, one tier up.
and growing a bulk set would depress the arm's measured precision while
trimming it would flatter it, for reasons having nothing to do with the
arm. To judge a BULK surface instead, compare `ambient` against pulls of
those same rules over time: a set surfaced thousands of times and opened
never is the dead-weight signal, one level up.
Read it against `sources["write_path_rule"]`. That arm was once believed
never to decline — the reading that scoped #3311 — but it was the arm's