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
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:
@@ -0,0 +1,349 @@
|
||||
---
|
||||
name: using-grimoire
|
||||
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 `grimoire` 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.
|
||||
@@ -0,0 +1,71 @@
|
||||
# When a record doesn't reach the moment it should
|
||||
|
||||
Part of the using-grimoire 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.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Moments — rules that arrive when the work reaches a point
|
||||
|
||||
Part of the using-grimoire 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.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Projects: binding a directory, and starting one
|
||||
|
||||
Part of the using-grimoire 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 `.grimoire` file naming the project:
|
||||
|
||||
{"instance": "https://grimoire.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.
|
||||
@@ -0,0 +1,213 @@
|
||||
# Writing a rule, a lesson, or a note that asserts a fact
|
||||
|
||||
Part of the using-grimoire 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.
|
||||
Reference in New Issue
Block a user