Preferences — a rule kind that Scribe keeps up to date (#3849 steps 1–3) #150
@@ -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": "2026.09.10.0221",
|
||||
"version": "2026.09.11.0319",
|
||||
"author": {
|
||||
"name": "Bryan Van Deusen"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ of record (notes, tasks, projects, milestones, rules) reachable through the
|
||||
for the operator's work, and as your own working memory across sessions.
|
||||
|
||||
**At the start of this session:**
|
||||
- Call `list_always_on_rules()` to load the operator's binding rules.
|
||||
- Call `list_always_on_rules()` to load the operator's standing rules.
|
||||
- If the working repo maps to a Scribe project (check `list_repo_bindings`),
|
||||
call `enter_project(<id>)` to load that project's rules, open tasks, and
|
||||
recent notes in one shot.
|
||||
@@ -21,6 +21,12 @@ for the operator's work, and as your own working memory across sessions.
|
||||
compaction — call `list_always_on_rules()` (and `enter_project()` when a
|
||||
project is in scope) BEFORE acting. When a loaded 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**
|
||||
must be followed — ignoring it breaks something or crosses a boundary. A
|
||||
**preference** is how the operator wants work done: worth following for
|
||||
consistency, not a defect to miss. Injected lines name the kind in their
|
||||
opening words. A preference is also yours to keep current when they correct
|
||||
you (`update_preference`); a rule waits for them.
|
||||
- **What you loaded is not all of the rules.** Only the always-on tier arrives
|
||||
that way; conditional rules are RETRIEVED, and one you were never handed
|
||||
binds exactly as hard. So before a consequential act, `search` for a rule
|
||||
|
||||
@@ -58,9 +58,27 @@ Two constraints on *how* that's achieved:
|
||||
|
||||
2. **Standing rules are binding — and the ones you were handed are not all of
|
||||
them.** Load the resident set via `list_always_on_rules()` at session start
|
||||
(see "Do this first"); treat every one as binding. Pull a rule's full
|
||||
statement with `get_rule(id)` when it's about to bite. When a project is in
|
||||
scope, `enter_project(id)` also returns its applicable rules.
|
||||
(see "Do this first"). Pull a record's full statement with `get_rule(id)`
|
||||
when it's about to bite. When a project is in scope, `enter_project(id)`
|
||||
also returns its applicable rules.
|
||||
|
||||
**`kind` says how much force a record carries, and it is never something to
|
||||
infer.** A **rule** must be followed: ignoring it breaks something or
|
||||
crosses a boundary. A **preference** records how the operator wants work
|
||||
done, and ignoring it costs consistency rather than correctness. Both are
|
||||
worth following and both arrive the same way; only one is a mistake to
|
||||
miss. An injected line names which in its opening words — *"Standing rule
|
||||
that may apply…"* against *"Preference that may apply…"* — and every
|
||||
payload carries `kind` outright.
|
||||
|
||||
**A preference is the one record you keep current yourself.** When the
|
||||
operator corrects you, or the preference on file no longer matches how they
|
||||
actually want something done, `update_preference` — that is expected, not a
|
||||
liberty, and it wants the task or note that taught the change. Say in the
|
||||
same turn that you did it, so they can disagree while it is in front of
|
||||
them. A rule waits for the operator instead: `create_rule` proposes and
|
||||
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
|
||||
|
||||
@@ -88,8 +88,9 @@ Hierarchy: Project -> Milestone -> Task/Note. The map, by purpose:
|
||||
active project_id to stay in scope.
|
||||
- WHERE work happens: Systems. Tag records with system_ids as you write;
|
||||
create_system when the area is unmodelled.
|
||||
- HOW: rules bind. list_always_on_rules() at start; before a consequential
|
||||
act, search(content_type="rule") — the resident set is not all of them.
|
||||
- HOW: rules bind; preferences guide. list_always_on_rules() at start;
|
||||
before a consequential act, search(content_type="rule") — the resident
|
||||
set is not all of them.
|
||||
- UI: the project's design system is binding — resolve_design_system /
|
||||
get_design_system_stylesheet before hand-writing a value.
|
||||
- REUSE: search snippets before writing a helper; record what you build with
|
||||
|
||||
@@ -252,3 +252,50 @@ def test_no_surface_names_the_push_without_stating_the_pull():
|
||||
f"the bridge — it can be absent without saying so. Name it if it helps, "
|
||||
f"but say to call {PULL}() regardless."
|
||||
)
|
||||
|
||||
|
||||
# ── force: a surface that says rules bind must say what does not ────────
|
||||
#
|
||||
# Added with the preference kind (milestone 399). Before it, "rules bind" was
|
||||
# the whole truth and every surface said so flatly. It is now half of one, and
|
||||
# the half that is missing is the dangerous half to omit: a session reading
|
||||
# only "rules bind" and then receiving a preference has been told, by the most
|
||||
# authoritative surface it has, to treat it as binding.
|
||||
#
|
||||
# That failure is silent in both directions. Treating a preference as a rule
|
||||
# produces a session that refuses to proceed over something the operator only
|
||||
# preferred; and it removes the reason preferences exist, which is that they
|
||||
# can be brought up to date rather than obeyed.
|
||||
#
|
||||
# Same bargain as every test in this file: STRUCTURE, not wording. A surface
|
||||
# passes by mentioning the other kind at all, so the prose stays free.
|
||||
#
|
||||
# PINNED ON THE CLAIM, NOT THE WORD "bind". A bare substring also matches
|
||||
# `bind_repo`, `list_repo_bindings` and the DNS-rebinding comment in
|
||||
# server.py — so it would one day fail a skill that mentions repo binding and
|
||||
# has nothing to do with force, which is rule 167's named failure: a guard
|
||||
# raising a false alarm about the very thing it protects. These phrases are
|
||||
# the ones that actually assert bindingness to a reader.
|
||||
BINDING_CLAIMS = (
|
||||
"rules bind",
|
||||
"binding rules",
|
||||
"rules are binding",
|
||||
"treat every one as binding",
|
||||
)
|
||||
OTHER_KIND = "preference"
|
||||
|
||||
|
||||
def test_a_surface_claiming_rules_bind_also_names_what_does_not():
|
||||
offenders = [
|
||||
label for label, text in _all_surfaces()
|
||||
if any(c in text.lower() for c in BINDING_CLAIMS)
|
||||
and OTHER_KIND not in text.lower()
|
||||
]
|
||||
assert not offenders, (
|
||||
f"these surfaces tell a session that rules bind and never mention "
|
||||
f"preferences: {offenders}. A preference arrives through the same arms "
|
||||
f"and renders in the same line shape, so a surface that describes only "
|
||||
f"the binding kind is read as covering both — and the session treats "
|
||||
f"'how the operator likes this done' as something it may not proceed "
|
||||
f"past. Name the other kind, however briefly."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user