Preferences — a rule kind that Scribe keeps up to date (#3849 steps 1–3) #150
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "scribe",
|
"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.",
|
"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": {
|
"author": {
|
||||||
"name": "Bryan Van Deusen"
|
"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.
|
for the operator's work, and as your own working memory across sessions.
|
||||||
|
|
||||||
**At the start of this session:**
|
**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`),
|
- 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
|
call `enter_project(<id>)` to load that project's rules, open tasks, and
|
||||||
recent notes in one shot.
|
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
|
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
|
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.
|
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
|
- **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
|
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
|
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
|
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
|
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
|
(see "Do this first"). Pull a record's full statement with `get_rule(id)`
|
||||||
statement with `get_rule(id)` when it's about to bite. When a project is in
|
when it's about to bite. When a project is in scope, `enter_project(id)`
|
||||||
scope, `enter_project(id)` also returns its applicable rules.
|
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
|
Rules come in two tiers. **Always-on** rules are delivered — they arrive
|
||||||
whether or not you ask. **Conditional** rules are RETRIEVED, and one binds
|
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.
|
active project_id to stay in scope.
|
||||||
- WHERE work happens: Systems. Tag records with system_ids as you write;
|
- WHERE work happens: Systems. Tag records with system_ids as you write;
|
||||||
create_system when the area is unmodelled.
|
create_system when the area is unmodelled.
|
||||||
- HOW: rules bind. list_always_on_rules() at start; before a consequential
|
- HOW: rules bind; preferences guide. list_always_on_rules() at start;
|
||||||
act, search(content_type="rule") — the resident set is not all of them.
|
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 /
|
- UI: the project's design system is binding — resolve_design_system /
|
||||||
get_design_system_stylesheet before hand-writing a value.
|
get_design_system_stylesheet before hand-writing a value.
|
||||||
- REUSE: search snippets before writing a helper; record what you build with
|
- 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"the bridge — it can be absent without saying so. Name it if it helps, "
|
||||||
f"but say to call {PULL}() regardless."
|
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