rename(547): the plugin is grimoire — manifest, MCP key, hooks, env, skills
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m2s
CI & Build / Python tests (push) Failing after 1m26s
CI & Build / Build & push image (push) Skipped

Step 3 of the Scribe -> Grimoire rename.

- Plugin `grimoire` in marketplace `grimoire-plugin`, MCP server key
  `grimoire`, so tools reach a session as mcp__plugin_grimoire_grimoire__*.
- hooks/scribe_* -> hooks/grimoire_*, their shell functions and the TMPDIR
  ledgers with them; SCRIBE_URL / SCRIBE_TOKEN / SCRIBE_HOOK_DIR /
  SCRIBE_LEDGER_DIRS -> GRIMOIRE_*; the project marker is `.grimoire`.
- skills/using-scribe -> using-grimoire, /grimoire:sync, stored processes
  arrive as grimoire-proc-<slug>; moment_actions names them the same way.
- Migration 0124 rewrites stored skill names (rule_moments,
  rule_moment_judgments, moment_mappings) from scribe:/scribe-proc- to
  grimoire:/grimoire-proc-, so a rule mounted on a skill keeps firing.
- Settings' install commands and MCP-only defaults, the docs and the
  tests follow.
- scripts/check_plugin.py drops the smoke event for a report-check hook
  that has no file in plugin/hooks.

Kept on purpose: the secret-sealing label, and the names that let backups
written as Scribe restore and be counted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 10:52:55 -04:00
co-authored by Claude Opus 5.5
parent 0f5ef0be63
commit c5eeec471f
87 changed files with 1027 additions and 989 deletions
-349
View File
@@ -1,349 +0,0 @@
---
name: using-scribe
description: Use at the START of every session, and before answering anything about the operator's work or starting any task — establishes the Grimoire-first reflex. You hold none of the operator's rules: they arrive by retrieval when your work matches one, and what_might_apply is how you ask before a consequential act — it returns the wide net of candidates with no bar. Call enter_project when a repo/project is in scope. Then recall before acting, update over duplicate, plan in Grimoire not in files.
metadata:
moments: session.start
---
# Using Grimoire
Grimoire is the operator's self-hosted system of record (notes, tasks, issues,
projects, milestones, systems) and rulebook, reachable
through the bundled `scribe` MCP server. Its value is mostly in what it
**already holds** — so make reading it a reflex, not something you wait to be
asked for.
## Do this first (every session)
If the working directory maps to a Grimoire project, call `enter_project(id)` —
it returns the project's goal, the milestones and open tasks worked on most
recently, its Systems and the titles of its own rules in one shot.
Binding a directory that is not a git repo, or starting a new project, has
its own steps: read [projects.md](projects.md).
Then **ask before you act**: before anything hard to reverse or outward-facing,
search the rules for what you are about to do. Reflex 2 below is why asking,
not loading, is how the operator's rules reach you.
## Grimoire holds these functions — keep one copy
Grimoire is the home for the operator's **rules, recall, and planning** — the
jobs your client's own local memory files would otherwise do. Route those jobs
to Grimoire instead of also writing them locally: codify rules with
`create_rule` / `create_project_rule`, capture durable knowledge as Grimoire
notes, and keep plans in Grimoire milestones (via `start_planning`). One copy, in
Grimoire; let any existing local memory shrink as Grimoire takes over.
Two constraints on *how* that's achieved:
- **Steer behaviour; leave the client's own settings as they are.** Grimoire works
alongside a client's built-in memory at its defaults. You replace memory's
functions by *doing the work in Grimoire*, so there is no reason to ask the
operator to switch a built-in feature off — a setting they didn't knowingly
change is breakage they won't know to restore.
- **A Grimoire-shaped hole is acceptable.** If Grimoire is later removed, the
operator recovers context over time — that's fine. Local memory doesn't need
to be kept as a self-sufficient fallback.
## The reflex
1. **Recall before acting.** Before answering a question about the operator's
work, or starting a task, `search` Grimoire (and `list_tasks` / `list_notes`)
for related prior work — an existing task, decision, or note — instead of
re-deriving it or opening a duplicate. When a project is in scope, pass its
`project_id` so results stay scoped.
**Code tells you what a thing does, not what was wanted.** A limit, a
default or a fallback found by reading the work is a past session's choice
until a record says the operator made it. Before presenting one as a
safeguard or a known cost, read the `Rulings` on the System it belongs to;
when none speaks to it, call it unconfirmed rather than settled.
2. **Rules are binding, and they reach you by retrieval.** No call loads the
operator's rules and no standing set is handed to a session. A rule arrives
when what you are about to do resembles what it is about — a command, the
code you are writing, the operator's message — and on most turns none will.
That is the surface working: it is what lets a rulebook keep growing, since
a rule costs nothing in a session it has nothing to do with. A rule binds
just as hard for never having been handed to you.
So "no rule arrived" means "nothing matched", never "no rule exists" — an
empty session is not evidence of an empty rulebook. Retrieval fires when
something asks, so ask — and reach for the tool that fits the moment:
- **Before a consequential act**, and before handing work back because you
are unsure you may finish it: `what_might_apply("what you are about to
do")`. It returns up to fifty ranked candidates with NO bar. The arms that
push rules at you spend a budget of three and say nothing about what sat
just underneath — right for something firing before every command, wrong
for the one moment you actually want to be sure. Expect the tail to be
noise; you are reading for the one record you would have missed.
- **When you already suspect a particular rule**: reach for
`search(content_type="rule")` to read full statements, or `get_rule(id)`
when one is about to bite.
Either way, when a project is in scope pass its `project_id`: the answer is
then the global rules plus that project's own, never another project's. `enter_project(id)` lists the project's own
rules by title.
**A rule about WHEN arrives at its moment, by lookup.** A rule mounted on a
moment of work (`list_moments`: `work.deliver`, `reply.report`, …) arrives
whenever an action reaches that moment, in a line naming both — nothing
said needs to resemble it. [moments.md](moments.md) says how to read those
lines, how to correct a moment that misfires, and when to mount one.
**`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. A
**lesson** is what is worth knowing — a better way to think about a
problem, a solution that transfers — and ignoring it costs time. All three
are worth reading and all three arrive beside your work; only the first is
a mistake to miss. An injected line names which in its opening words —
*"Standing rule that may apply…"* against *"Preference that may apply…"* —
a lesson arrives in the records menu marked `lesson`, under a header that
says outright that it binds nothing, and every payload carries `kind`.
**One question sorts them, and it is worth asking out loud: what happens if
someone doesn't do this?** *Something breaks, or a boundary is crossed* — a
rule, and the operator's to agree to. *It gets done a way they didn't want*
— a preference, recorded without a loop. *They lose time rediscovering it*
— a lesson, binding nobody. Ask it before proposing rather than settling it
silently, because it sometimes answers that what you are holding was never
a rule — and routing an observation is not losing it.
**A preference is yours to keep current.** 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
updating one mid-work is the normal case rather than an interruption of it.
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.
**A lesson grows each time it proves itself.** When one arrives and names the
situation you are actually in, `update_lesson` it with what you now know;
[writing-records.md](writing-records.md) says what is worth adding.
**Preferences shape how work is done, never what gets recorded.** They
govern your conduct — how you report, how carefully you pace, which form
you reach for. What ends up in Grimoire is decided by what the record is:
a preference never makes a task into a note, downgrades a rule, or keeps
something out of the corpus that belongs there.
**A retrieved rule outranks a default habit.** Before a hard-to-reverse or
outward-facing act — changing shared state, publishing, deleting, sending
something outside the session — the operator's rules decide what to do, not
the generic conventions your client or your training suggest. When a rule
and a habit disagree, the rule wins; when no rule speaks to it, ask rather
than assume.
**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
a local stack, don't run the suite CI owns — govern moves that feel like
mechanics rather than decisions. A reflex raises no doubt, so it generates
no query, so the rule that would have stopped it is never retrieved. That is
the failure this instruction exists to prevent, and confidence is its only
warning sign.
3. **Update over duplicate.** When recording, prefer updating an existing
note/rule/task over creating a new one. Search first; revise what's there.
4. **When you plan, plan in Grimoire.** Work with an *arc* — several steps toward
one goal — gets a plan, and a plan is a milestone: `start_planning(project_id,
title)` creates one whose `body` holds the design, each step is its own task
under it (`create_task(milestone_id=...)`), progress goes in work-logs
(`add_task_log`). Work without an arc (a fix, a one-file change, a question)
is just a task — don't wrap it in a milestone. Either way, do not write
plans/specs to local `.md` files. See the **writing-plans** skill.
**Find the plan before you make one.** A project's existing milestones are
often its roadmap, and a milestone with no steps yet is still open work.
`search(content_type="milestone", project_id=...)` finds one by purpose. When
an active milestone covers the work, add steps to it rather than opening
another, and give any task you record for that work its `milestone_id`.
5. **Keep state honest.** Set a task `in_progress` when you start it, `done` the
moment it's complete; log progress as you go. Always log when you
**complete** a task and when you **hit or discover a problem**, so a change
of direction is on the record and not only the successes.
**Hand off before this session's context stops existing.** A compaction, a
`/clear`, the operator wrapping up for the day — each is the last moment the
reasoning behind the work lives anywhere but here. Log on the task you were
holding where it stands, what you tried and ruled out, and the next move:
write down what the next session needs, because it arrives with Grimoire's
record and nothing else. Moving a task to `in_progress` or logging on it also
claims it for this session — that claim is what hands the work back to you
after a compaction, and it ends on its own when you stop, so there is nothing
to release by hand.
6. **Fixes are issues, not work-logs.** When you fix a problem — even one solved
in passing — record it as its own issue (`create_task(kind="issue")`) with
symptom → root cause → fix, optionally linked to the task it arose from
(`arose_from_id`) and the subsystem it touches (`system_ids`). Don't bury a
fix as a work-log line on whatever task happened to be open.
7. **Tag records to Systems.** `enter_project` lists the project's Systems —
its named subsystems/areas. When you create or meaningfully update a record,
ask which areas it is *about* and pass `system_ids`. The test: would someone
investigating that subsystem want this record in the pile
`list_system_records` returns? If the area has no System yet, create one
(`create_system`: name + a one-paragraph charter) — an area that plainly
exists deserves naming the moment two records would share it; don't wait to
be asked. Cross-cutting records — audits, sweeps, reviews — take *several*
tags and are the prime discovery moment: a pass that walks the subsystems
has just enumerated the vocabulary, so mint the Systems it names as it
names them. Create liberally — `create_system` is duplicate-gated, and that
gate (plus reviewing the existing list) is the guardrail against sprawl,
not restraint. Only a record genuinely about no particular area goes
untagged.
Every read and write of a project record shows its `systems`. An untagged
one carries a `systems_hint` question instead — on creates, updates and
work-logs alike. Treat it as the tagging question asked at the moment of
work: tag the record, create the missing System, or deliberately leave it.
A System also names its files: `path_patterns`, globs from the repo root.
When the work you are tagging touched files its System's patterns don't
cover, and they plainly belong to that area, add them with `update_system`
— which files are which area is your call, as naming the area was.
8. **Name the record, never just its number.** Whenever you refer to a Grimoire
record — in a message to the operator, a commit message, a task body, a
work-log — write the id *and* its title: `#3244 "the staleness signal"`,
`milestone 323 "rule versioning"`. Not `#3244`.
You have the record open; the operator does not. A bare id reads as
complete to you and as homework to them — they have to look it up to know
what their own conversation is about, or guess. Grimoire's own duplicate gate
already writes `id 412: "debounce helper"` for exactly this reason; match
it everywhere else.
The first mention in a message carries the title; later mentions of the
same record can use the bare id. If you don't know the title, look it up
before citing the number — an id you can't name is one you haven't checked.
This matters most in the places read later by someone with even less
context than the operator has now: commit messages, task bodies, and any
record that cites another.
**An id exists only once a create call returns it.** Never write the id you
expect a record to get — every session and user draws from one sequence,
so the number goes to whoever creates next, and Grimoire refuses a body that
cites an id not yet assigned. Records that must cite each other are created
together — `create_records` or `start_planning(steps=...)` — with
`{{ref:N}}` where the Nth record's id belongs.
9. **State updates in place; chronicles don't.** A dev-log records what
*happened* — write it once, never rewrite it. A durable finding (how a
subsystem works, a measured number) lives in that System's **reference
note** ("«System» — reference"), which you UPDATE as facts change — safe,
because every meaningful edit is snapshotted and the version history is the
changelog. The dev-log then `[[links]]` the reference note instead of
restating state. When a new record outright *corrects* an older one (a
re-measurement, a reversed decision), pass the old id in `supersedes` so the
stale record is demoted and labelled rather than left competing.
**What the operator decided an area must do is a ruling, and it lives on
that System** — a `Rulings` section in its description, written the turn
they decide. A quote in a work log reaches the next session only if a
search happens to match it; the description arrives with every record
filed there. [writing-records.md](writing-records.md) has the two tests and
the form.
10. **A note that asserts a fact about someone else's software can carry its
own check** (`verify_with`, `expires_when`), swept by
`notes_due_for_verification`. Most notes should not: read
[writing-records.md](writing-records.md) before filling either field.
11. **Report back in a shape the operator can read.** They were not there while
you worked, so organise the reply around **where the work stands**, not the
order you did things in: which task or milestone it belongs to, what now
works, what needs them, and what comes next. Take the placement from the
`placement` block that `create_task` / `update_task` return — the milestone,
step N of M, the next open step — rather than from memory. Grimoire delivers
the shape for each kind of reply when it is due — the core with each turn,
the completion report when a task closes, the asks when you put a question —
and `list_reply_shapes` has them all; the `reporting-back` skill holds the
reasoning behind them and a completion report worked in full.
## Stay inside the active project's scope
Once a project is in scope — you called `enter_project`, or the working repo is
bound — confine the session to it:
- **Pass that `project_id` to every read** (`search`, `list_tasks`,
`list_notes`). An unscoped read bleeds every other project's work into your
context.
- **Only reference or offer work on the in-scope project.** Don't surface,
suggest, or start work on other projects unless the operator explicitly widens
scope.
- If something clearly belongs to a *different* project, say so and **ask before
switching** — never silently operate cross-project.
## Read these when the moment comes
Each is part of this skill, kept in its own file because it matters at one
moment rather than on every turn:
- [projects.md](projects.md) — a directory with no project; starting a project
(`decide_project_inception` when `enter_project` returns `inception`).
- [writing-records.md](writing-records.md) — before writing a rule, preference
or lesson (where it goes, its `when_to_apply`, what already covers the
moment), when the operator decides how an area must behave (a ruling), and
before giving a note a check.
- [missed-retrieval.md](missed-retrieval.md) — a rule that missed the moment it
governed, or keeps arriving where it doesn't apply.
- [moments.md](moments.md) — a line that says a rule arrived *at* a moment, a
reply held for one read, an action that reached the wrong moment or none,
and a line proposing a mount.
## You are the judge of what the record says
Grimoire's shape exists to support how you work, so you are the judge of record
for the work itself: what a shape is, which canon a thing belongs to, whether
a finding holds, whether something is done. `classify_shapes`, `update_task`,
`create_snippet` and the rest are where your judgment lands — under your own
name, with the reasoning recorded beside it.
**Judging is attended, and that is the whole distinction.** You judge by
reading the evidence and writing down why. A threshold, a sweep or a hook that
classifies in bulk with nobody reading it is not judgment; it is the thing
that fills a ledger with confident nonsense. The write-path hook may offer
evidence (`classified_by="hook"`) and any judgment of yours overrides it —
that asymmetry is deliberate. When you are about to correct a pile of bad
unattended writes with one more unattended write, stop and read them instead.
**A finding you surface and do not judge is a finding dropped.** Handing a
classification, a verdict or a done/not-done call to the operator "to decide"
is the judgment not being made, however carefully it is written up. Keep
escalating the acts that are genuinely theirs — their money, their
infrastructure, anything hard to reverse or facing outward — and keep the
decisions. A hard call is still yours; an irreversible act is still theirs.
**A record that is wrong stays wrong until something rewrites it.** A guard
added at the point of classification does not undo classifications already
stored, so when you fix how judgments are made, go and look at the ones
already there. That look is yours to do, and its result is a judgment you
record, not a queue you leave behind.
## Building UI: the project's design system binds
`enter_project` names the project's `design_system` when it has one. Before
writing UI, read its guidance: `get_design_system(id)` returns it as
`resolved_guidance`, **chain-merged** — the house style it inherits plus its own
departures from it. Treat it the way you treat a rule.
Before writing a colour, size, radius, weight or duration by hand, reach for a
token: `resolve_design_system(id)` for the values, or
`get_design_system_stylesheet(id)` for the rendered sheet. A literal is a value
stated outside the system, so it can never follow a palette change — and
nothing will tell you it drifted.
## Other Grimoire process-skills
Grimoire also ships focused process-skills — writing-plans, reporting-back,
systematic debugging, verification, and brainstorming. Reach for the matching one when its
situation arises, the same way you reach for this skill.
@@ -1,71 +0,0 @@
# When a record doesn't reach the moment it should
Part of the using-scribe skill. Read it when a rule should have governed a
moment and never arrived, when one arrives on every turn and never applies,
or before touching a retrieval floor.
Retrieval misjudging is ordinary, and it is fixable — but only by whoever
notices. **Either direction counts:** a rule that should have governed a moment
and never arrived, and a rule that arrives on every turn and never applies. So
does **either noticer**: the operator saying *"that should have fired"*, and you
noticing it yourself — you reached for a rule nobody offered you, or you were
handed the same rule five times and set it aside five times.
**First ask whether it missed a WHEN or a WHAT.** A rule about a point in the
work — it governs delivering, finishing, verifying, reporting, whatever the
work is about — is not a trigger to reword: no wording resembles every piece
of work that reaches that point. Mount it on the moment instead
(`update_rule(moments=[...])`, from `list_moments`); a mount is a lookup, and
it arrives every time the moment happens. When it is already mounted and still
did not arrive, the action did not reach the moment on this install — offer
`map_action` for that action. The reverse — a mounted rule arriving where it
does not apply — is a mount or a mapping that is wrong: take the moment off
the rule, or `unmap_action` the action that reached it. Each of these
changes what the operator's sessions receive, so offer it in one line and make
it on their yes. When it is not plain enough to offer yet, `rule_misfired`
records it, and repeated reports become the offer. The skill's moments
reference has the detail. A rule about a subject missed its WHAT, and the rest of this page is
for that.
**Take it to the record first and the dial second.** A rule's `when_to_apply`
IS the text its similarity score is computed against, so when a rule misses a
moment it governs, the overwhelmingly likely cause is that its trigger does not
describe that moment in the words a session actually produces. Rewording one
trigger changes one rule's reach. Moving a floor changes what every record on
that surface does, and a floor cannot tell a badly-worded trigger from a
genuinely distant record — so one lowered to rescue a single rule admits
everything else that was sitting in the same band.
1. **Read the refused records.** `retrieval_telemetry(days=N,
near_miss_samples=5)` names by id what each surface refused and by how much.
Open them with `get_rule` / `get_note`. This is the step that carries the
answer: the statistic says a record was close, and only the record says
whether it was *right*.
2. **Fix the trigger.** `update_rule(when_to_apply=...)`, written as the
symptom — what the session was doing or saying at the moment it needed this
rule — not the situation the rule belongs to. Then check that it worked:
`what_might_apply("the moment, in the operator's own words")` and read where
the rule now ranks. The change is measurable, so measure it, and say the
before and after when you report it.
3. **Then consider the dial.** `retrieval_surfaces` shows what is in force per
arm and whether the number is still calibrated; `tune_retrieval` moves it.
`reason` is required and has to say what you read, because it is what lets
the operator disagree with a number they did not choose.
**A budget is judged by what sits past it, and an open rate cannot judge
it.** Every menu line carries its matched passage, so a record left
unopened may have been unrelated, enough as shown, or already in context.
`menus_to_review` re-runs a sample of logged menus to a depth past the
budget; judge each line from what is shown with `judge_menu`, then read
`retrieval_telemetry`'s `judged` block. If the lines past the cut are
mostly `on_point`, the budget is costing hits; mostly `unrelated`, it is
doing its job.
Reaching for `tune_retrieval` before opening a single record is the wrong move,
and it is the one that feels efficient. Worked example, measured on this
install: a rule granting a routine push scored 0.6515 and ranked 5th for the
moment it governed, behind three rules that *restrained* the same act. Every
percentile said "lower the floor" — and lowering it would have delivered those
three restraints and still not the rule. Rewriting the trigger to lead with the
symptom moved the same rule to 1st at 0.7130, ahead of all three. Only then was
the floor worth touching.
-87
View File
@@ -1,87 +0,0 @@
# Moments — rules that arrive when the work reaches a point
Part of the using-scribe skill. Read it when a line says a rule arrived *at* a
moment, when a reply is held for one read, when an action reached the wrong
moment or none, when a mounted rule arrived and did not apply, and when a line
proposes mounting or unmounting a rule.
## What a moment is
Most rules reach you by resemblance: what you are doing looks like what the
rule is about. A rule about WHEN — finishing, delivering, verifying,
reporting — resembles nothing said at that point, so resemblance misses it.
Such a rule is **mounted** on the moments of work it belongs to, and arrives
by lookup whenever an action reaches one. `list_moments` names them, each
with what is happening at it and the kinds of action that typically reach it:
`work.deliver` when work is sent beyond the place it was made, `work.finish`
when a piece of work is declared done, `reply.report` at the reply that ends a
turn, and so on. A named procedure is its own moment, `skill.<name>`, reached when it is
loaded.
Which ACTIONS reach a moment is the install's: one operator delivers with a
push, another with a deploy script. Shipped defaults cover the common ones,
and each install corrects them for itself.
## Reading a line that arrived at a moment
The line names the moment and the action that reached it — *"at work.deliver,
reached by `git push`"* — and the rule, with `get_rule(N)` to read it. Read it
as you would any rule that arrived beside your work: it binds just as hard,
and it came because of what you are doing now, not because of what you said.
The reply that ends a turn is a moment too. When a rule mounted at
`reply.report` has not been opened this session, the reply may be held once
with its name: open it, then send the reply — unchanged, if it already does
what the rule asks. The same rule is never held twice.
## When the moment is wrong — correct it in the session
A moment can fire on an action that is not that moment here, or an action can
plainly be a moment and fire nothing. Either way the fix is one call, and it
belongs in the session that noticed, not on a settings page:
- **An action reached the wrong moment** — the line names a moment that is not
what you did: offer `unmap_action(tool, moment, match, reason)`.
- **An action was a moment and nothing arrived** — the operator ships with
their own script, and nothing mounted on `work.deliver` came:
offer `map_action(tool, moment, match, reason)`.
Offer it in one line, the way the operator would say it ("that deploy script
is a deliver and nothing fired — map it?"), and make it on their yes. The
correction lasts for every later session on the install; `list_moments` shows
what each action reaches now.
## When a mounted rule arrives and does not apply
A mount delivers its rule at every occurrence of the moment, so a mount that
is wrong is noise every time — and a rule you read and set aside leaves no
trace anyone else can see. When a rule arrived *at* a moment, you read it,
and it has nothing to do with what you were doing there, say so with
`rule_misfired(rule_id, moment, why, reached_by)`: one call, nothing asked of
the operator. Name the action the line said reached the moment, and say what
you were doing, because the reports are what the operator decides from —
whether the rule does not belong at the moment, or one action is reaching it
that is not that moment here.
Reports gather across days. Once a rule has them on three, the response
carries a line asking you to offer the fix, and the operator sees it under
Settings › Moments too: `judge_rule_moments` `reject` takes the rule off,
`unmap_action` stops the action reaching the moment, and `confirm` keeps the
mount and ends the question. A rule that applied — even one you were already
following — is not a misfire, and a rule that arrived by its words rather
than a mount is fixed through its trigger instead.
## When a line proposes a mount
A rule that keeps being opened just after the same moment, across several
sessions, probably belongs on that moment. A line says so, naming the rule,
the moment and how often. It is a question for the operator, not a change you
make: offer it in one line, and record their answer with
`judge_rule_moments` — `confirm` mounts the rule, `reject` with their reason
stops the question being asked again.
The same tool answers proposals from a pass over the rules
(`rules_to_mount`, `propose_rule_moments`, `rule_moment_proposals`): a pass
proposes, and only the operator's yes mounts. Writing a NEW rule is different
— its moments are part of writing it, and the
skill's guide to writing records says how.
-41
View File
@@ -1,41 +0,0 @@
# Projects: binding a directory, and starting one
Part of the using-scribe skill. Read it when a working directory maps to no
project, or when a new project is about to be created.
## Binding a directory to a project
**A directory does not have to be a git repo to have a project.** A repo is
bound by its remote (`list_repo_bindings` shows the bindings). Anything else —
a notes folder, a server's config directory, a scratch directory — is bound by
a `.scribe` file naming the project:
{"instance": "https://scribe.example.com", "project_id": 2, "project": "Homelab"}
`instance` is what makes the id trustworthy. An id means nothing on its own —
it is a different project on every Grimoire — so a marker that has travelled to
another instance is ignored rather than followed to the wrong project. A bare
`2` also works when writing the file by hand. When work plainly belongs to a
project and the directory names none, offer to write the marker;
`list_projects` has the id.
## Starting a project: decide what it inherits
A project's inheritance is a **decision, not a default**. Before
`create_project`, ask the operator the two inception questions and pass the
answers — never create a project bare by default:
- which **design system** its UI is built from (`list_design_systems`; or
none) → `design_system_id=<id | -1>`
- whether to **seed the standard starter Systems** so records can be tagged
from day one → `seed_systems=true|false`
Rules are not an inception question: a global rule already applies to every
project, and a project's own rules are written on it as they come up.
If `enter_project` returns an `inception` key, the project was never decided
(it inherits its defaults silently): raise that ask once, with the defaults it
carries, then `decide_project_inception(project_id, …)`. Existing projects
were stamped "legacy" and do not ask; any project can be re-decided. The
design-system and Systems tools still work one at a time — inception is the
moment they are decided together, and the record of why.
@@ -1,213 +0,0 @@
# Writing a rule, a lesson, or a note that asserts a fact
Part of the using-scribe skill. Read it before `create_rule`,
`create_project_rule`, `create_preference`, `create_process` or
`create_lesson`; when a lesson
arrives that names the situation you are actually in; when the operator
decides how some area of the work must behave; and before filling
`verify_with` or `expires_when` on a note.
## Contents
- Where a new rule goes — its home, its trigger, what already covers the moment
- When it applies — the moments a rule, a preference or a process is for
- A ruling goes on the System it governs
- A lesson grows each time it proves itself
- A lesson names the rule it is an instance of
- A note that asserts a fact can carry its own check
## Where a new rule goes
A rule has one of two homes, and the home IS its reach:
- **Global** — in a rulebook (`create_rule` into a topic). It applies in every
project, and reaches a session wherever the work matches it. A rulebook is a
*themed* grouping of general rules (e.g. a review checklist), not a list of
projects it binds — there is no subscribing a project to one.
- **Project** (`create_project_rule`) — anything specific to one project (its
files, paths, quirks). It reaches only that project's sessions.
Names one project's specifics → project rule; a standard that holds wherever
the kind of work it describes happens → global. Never put project-specific
detail in a rulebook — it would reach every other project. A project that
departs from a global rule writes its own and links it with
`relate_rules(kind="overrides")`, which says why. A rule that turns out to be
in the wrong home — a project rule that holds everywhere, a global one only a
single project needs — moves with `move_rule`, which keeps its id, history,
areas and edges. Propose the move and make it on a yes.
**Whichever home it gets, a rule needs `when_to_apply`.** It is the only thing
that decides whether the rule is ever seen: nothing is preloaded, so a rule
with no trigger is not a quiet rule, it is an unreachable one. Write the moment
in the words a session actually produces — the command, the error, the
half-formed ask — not the category it belongs to.
**Then ask WHEN it applies, as well as what it is about.** A trigger is
matched by resemblance, and a rule about a point in the work — finishing,
delivering, verifying, reporting, asking — resembles nothing said at that
point. Give such a rule its moments as you write it: `list_moments` names
them, and `moments=[...]` on `create_rule`, `create_project_rule` or
`create_preference` mounts it, so it arrives whenever an action reaches one.
Keep the trigger anyway: it is the net for the moments nobody mapped. A rule
about a subject — a library, a file, a style — has no moment; it is reached
by meaning, and leaving `moments` empty is the answer, not an omission. A rule
often has both: a point in the work, and the words a session uses at it.
A stored **process** says the same about itself: `create_process(moments=…)`
names the moments the procedure is for, so loading it reaches them and the
rules mounted there arrive with it. A rule that only applies inside one
procedure mounts on that procedure's own moment, `skill.<name>`.
**Before writing one, ask what already covers that moment.**
`what_might_apply("the moment you are about to write a record for")` — fifty
candidates and no bar, so an existing record cannot hide under a threshold the
way it can from `search`. When something already covers the moment, the reach
is that record: improve its trigger or its statement rather than standing a
second one beside it. Two records describing the same moment compete in one
ranked list against one budget, and the slot they take from each other is the
third candidate that would have said something different. Two records may
legitimately share a moment and say *different* things — a rule for what must
happen, a preference for how to report it. What this catches is the same thing
said twice at two strengths, which is worse than either alone: a session that
retrieves the softer copy has been told that binding guidance is optional.
**First ask whether it's a rule at all.** A rule is prose you have to remember
and apply; Grimoire's other entities are structure a tool can resolve and check.
Visual standards belong in a **design system**, not a rulebook — a token can be
inherited, resolved per mode, rendered to a stylesheet and diffed against code,
and none of that survives being written as a rule. A repeatable procedure is a
**process**; reusable code is a **snippet**. Reach for a rule when the thing
really is a standing instruction about how to work.
Then ask what force it carries, by the question SKILL.md's reflex 2 asks: what
happens if someone doesn't do this? A standing instruction that merely costs
consistency is a **preference** (`create_preference`), and a transferable
insight that costs time is a **lesson** (`create_lesson`), keyed to the
situation it applies to so a later session meets it there. Both are first-class
outcomes of noticing something, not what's left when a rule proposal fails.
## A ruling goes on the System it governs
A **ruling** is the operator's decision about what the work itself does in one
area: how it behaves, what it must never do, which way a trade-off goes. It is
not a rule. A rule governs how *you* work and reaches you when your work
resembles it; a ruling governs what the *thing being built* does, and it has to
reach every session working in that area, whatever words that session is using.
Two tests, and it needs both:
- **The operator decided it.** They said it, or approved it when it was put to
them as its own question. Approving an option is not approving every
assumption the option carried — only what it named.
- **A later change could plausibly undo it.** "Failed work is retried until it
succeeds" qualifies. The name of the table that tracks the retries does not;
that is implementation, and it lives in the code and the logs.
A choice a session made on its own is not a ruling, however sound. Keeping the
two apart is the point: once code embodies a session's choice, it reads exactly
like the operator's intent, and the next session builds on it as if it were.
**Where it goes: a `Rulings` section at the end of the System's description**,
one line each — the statement, who decided, when, and the record it came from:
Rulings
- Failed work is retried until it succeeds; no attempt limit. (Operator, 2026-09-19, #1234; restated 2026-10-02, #1290)
The description arrives with every record filed under that System, so a ruling
there reaches each session working in the area without having to win a search.
Once the System names its files (`path_patterns`), its rulings also arrive with
the first command or edit in a session that touches them.
A quote inside a work log does not: it surfaces only when a query happens to
match it, and the passage that matches is usually the prose around it — often a
session's *reading* of the ruling rather than the ruling.
**When: in the turn the operator decides, before building on it.**
`get_system(id)`, then `update_system(id, description=...)` with the whole
description — the field is replaced, not appended to. Also when the operator
corrects work that departed from something they had already said: that ruling
existed and did not reach the work, and writing it where it will is half the
fix. No System fits? The area is probably unnamed — create it.
**It holds what is in force, not its history.** When a ruling is overturned,
change or remove its line and cite the record that changed it; the work log
keeps the history. If the charter above the section contradicts a ruling —
usually a charter written before the decision — fix that sentence in the same
edit. A stale charter line is a ruling nobody made, and it is read as one.
## A lesson grows each time it proves itself
When one arrives and the situation it names is the one you are actually in, you
are the single reader placed to tell whether its trigger is keyed right and
whether its claim covers what you are seeing. `update_lesson` takes what you
now know: another incident added to what taught it, the claim stated more
exactly, or — the edit worth most — a trigger re-keyed to the situation that
really fired. A lesson nobody reaches is seldom wrong; far more often it is
waiting in a situation nobody is in. One claim that has met the same failure
four times is worth more than four claims that each met it once, so when a
near-duplicate create hands back an existing id, that is the record to grow.
## A lesson names the rule it is an instance of
A lesson records one situation; a rule records the binding choice for a class
of them. Linking the two is what lets a rule be reached through the situations
that keep proving it — moments that resemble why it was written more closely
than its own wording does. A lesson never becomes a rule. It points at one.
The writer is best placed to say which rule, so answer while writing, in
`create_lesson` itself or in the `update_lesson` right after it:
- **`rule_ids=[…]`** — the rule or preference whose situation this is an
instance of.
- **`no_rule="why"`** — no rule governs it, in a line. That is a full answer,
not a gap: lessons that stand alone and keep landing in one situation are
what a missing rule looks like, and the reason is what lets the answer be
re-judged once a rule exists.
A lesson created with neither comes back with `rule_candidates`, the rules it
most resembles, each with its trigger. Read them against the situation the
lesson describes, not its topic — a rule about the same subsystem that governs
a different moment is not its rule — and give one of the two answers.
`list_lessons(unjudged=true)` gathers the ones still open.
## A note that asserts a fact can carry its own check
**A few notes assert a FACT, and those can carry their own check.**
Supersession only fires once somebody has read a note and disagreed — which
is the case where it was already believed. A note asserting something about
*someone else's* software — what a service does on a duplicate upload, how a
forge numbers its CI runs, what an updater compares — can instead carry
`verify_with` (how to check it) and `expires_when` (the STATE that ends it:
"when the forge numbers runs per workflow", never "in six months").
`notes_due_for_verification` lists them least-recently-confirmed first, with
never-checked at the top; `mark_note_verified` records what you found, and
`still_true=False` deliberately writes nothing — a note whose check failed
is wrong rather than in a state worth recording, so it keeps its place.
**The test is one question: could this note become false without anyone
editing it?** If no, leave both fields empty. That is the normal case, and
an empty `verify_with` is the positive marker for "this is a decision, there
is nothing to go and check" — not an unfinished record. The sweep is only
worth reading while almost nothing is on it, so a check added out of
tidiness costs the whole surface, not just that note.
**The sharper form of the same test: is the thing this note describes yours
to change?** If yes it is a decision — editing your own software is how it
changes, and you will know you did it. Measured against a real corpus, every
note that earned a check was about somebody ELSE's software: a signing
service, a forge, a hub, an SDK, a model, a dependency set.
**Three that look like candidates and are not:**
- **Resume pointers and "current state" notes.** They go stale fastest of
anything, which is exactly why they tempt — but the cure is to update or
delete them, not to schedule a check. A sweep full of pointers is a sweep
nobody reads.
- **Measurements of your own system.** They go false because you changed
something, and you knew. A measurement earns a check only when what it
measures is outside your control.
- **A decision that RESTS on somebody else's behaviour.** The decision is
still a decision. Put the check on the note asserting the fact, and link
the decision to it.
Not for tasks — a task's decay is its status, and a done issue records what
happened rather than asserting something that can go false. Not for snippets
either: `verify_snippet` compares the recorded location and code against the
repo, which is richer and already wired to drift detection.