Files
FabledScribe/plugin/skills/using-scribe/SKILL.md
T
bvandeusenandClaude Opus 5 7f974d9749
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 46s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m44s
CI & Build / Build & push image (push) Successful in 23s
feat(mcp): enter_project becomes a small primer: goal, recent work, open work, vocabulary (#4045)
The handshake carried the whole project record, every milestone's plan, full
rule text, the notes most recently edited and ~9k of design guidance. For
project 2 that was ~222k characters, past what an MCP client accepts as a tool
result. Each category was walked through with the operator and sized to what a
session needs on arrival; each names the call that has the rest.

- project: id, title, status and the full goal (session start's "full goal"
  pointer still lands here). get_project keeps the whole record.
- milestone_summary: the 5 most recently touched milestones, any status, most
  recent first, without plans. Summaries gain last_touched_at: the later of
  the milestone's own edit and its newest step update, from the query that
  already counts steps. milestone_summary_omitted counts the rest and points
  to list_milestones. get_project and list_milestones list every milestone,
  also without plans.
- open_tasks: the 10 most recently touched open tasks, with or without a
  milestone, each naming its milestone. list_notes gains sort="touched"
  (the later of updated_at and the newest work-log), because a log doesn't
  bump updated_at.
- recent_notes: dropped. Retrieval surfaces notes by relevance, and
  get_recent covers recency.
- systems: id and name.
- design_system: summary plus guidance_call. get_design_system gains
  resolved_guidance, the chain-merged prose; its own guidance field is only
  the departures, so session start's old pointer to it led to a fragment.
  The session start pointer and using-scribe's "Building UI" section now
  name resolved_guidance.
- rules: rules_payload(brief=True) gives project_rules as id and title plus
  subscribed_rulebooks, and records only what it shows. Retrieval delivers
  rules in full and ignores subscriptions (#4052). Other callers unchanged.
- pattern_coverage, inception and systems_bootstrap: unchanged.

Clients: the plugin's using-scribe skill, the compaction notice and session
start are updated here; the REST project summary only gains last_touched_at.
Plugin version minted.

Tests: a size ceiling on the handshake for a large project; milestone and
task selection and naming; brief rules; resolved_guidance; the session
start pointer; and a real-Postgres test that a work-log touches its task and
a step update touches its milestone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
2026-09-14 22:04:38 -04:00

18 KiB


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 Scribe-first reflex. You hold none of the operator's rules: they arrive by retrieval when your work matches one, and search(content_type="rule") is how you ask before a consequential act. Call enter_project when a repo/project is in scope. Then recall before acting, update over duplicate, plan in Scribe not in files.

Using Scribe

Scribe 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 repo maps to a Scribe project (you're in a known repo, or list_repo_bindings shows a binding), 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.

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.

Scribe holds these functions — keep one copy

Scribe 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 Scribe instead of also writing them locally: codify rules with create_rule / create_project_rule, capture durable knowledge as Scribe notes, and keep plans in Scribe milestones (via start_planning). One copy, in Scribe; let any existing local memory shrink as Scribe takes over.

Two constraints on how that's achieved:

  • Steer behaviour; leave the client's own settings as they are. Scribe works alongside a client's built-in memory at its defaults. You replace memory's functions by doing the work in Scribe, 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 Scribe-shaped hole is acceptable. If Scribe 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 Scribe (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.

  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: before a consequential act, search(content_type="rule") on what you are about to do, and pull a record's full statement with get_rule(id) when it is about to bite. When a project is in scope, enter_project(id) also returns the rules bound to its areas.

    kind says how much force a record carries, and it is never something to infer. A rule must be followed: ignoring it breaks something or crosses a boundary. A preference records how the operator wants work done, and ignoring it costs consistency rather than correctness. Both are worth following and both arrive the same way; only one is a mistake to miss. An injected line names which in its opening words — "Standing rule that may apply…" against "Preference that may apply…" — and every payload carries kind outright.

    A preference is the one record you keep current yourself. When the operator corrects you, or the preference on file no longer matches how they actually want something done, update_preference — that is expected, not a liberty, and it wants the task or note that taught the change. Say in the same turn that you did it, so they can disagree while it is in front of them. A rule waits for the operator instead: create_rule proposes and asks. If what you learned is that something MUST be done a certain way, that is a rule to propose, not a preference to harden in place.

    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 Scribe. 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.

  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.

  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.

  8. Name the record, never just its number. Whenever you refer to a Scribe 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. Scribe'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 Scribe 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.

  10. 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.

  1. 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. The reporting-back skill holds the shape for each kind of reply: completions, findings, decisions, handoffs, "where are we".

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.

Starting a project: decide what it inherits

A project's inheritance is a decision, not a default. Before create_project, ask the operator the three inception questions and pass the answers — never create a project bare by default:

  • which rulebooks to subscribe (list_rulebooks shows them; default: none — a rulebook binds a project only when it opts in) → subscribe_rulebooks=[...]
  • 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

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" (inherit-all) and do not ask; any project can be re-decided. The rules/design-system/Systems tools still work one at a time — inception is the moment they are decided together, and the record of why.

Where a new rule goes

When codifying a rule, pick its home by who it should bind — and keep shared homes general:

  • Rulebook (create_rule + subscribe_project_to_rulebook) — a reusable, themed module of general rules that binds the projects which opt in (e.g. a review checklist → every service). Themed, but project-agnostic.
  • Project rule (create_project_rule) — anything specific to one project (its files, paths, quirks).

There used to be a third home — an always_on rulebook that bound every project automatically. It is gone: subscription is the only reach a rulebook has. Names one project's specifics → project rule; anything a category of projects shares → rulebook. Never put project-specific detail in a rulebook — it leaks to every other project that subscribes.

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.

First ask whether it's a rule at all. A rule is prose you have to remember and apply; Scribe'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.

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 Scribe process-skills

Scribe 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.