Plugin reply shape, preferences UI, cited-record status, and the lesson kind #166
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "scribe",
|
||||
"description": "Scribe for Claude Code: connects the scribe MCP server, adds the hooks that deliver live project state and relevant records at the right moment, ships the shared client-neutral Scribe skills (using-scribe, writing-plans, reporting-back, systematic-debugging, verification, brainstorming, reusing-code, shape-accounting), and syncs your saved Scribe Processes as skills (/scribe:sync).",
|
||||
"version": "2026.09.18.0436",
|
||||
"version": "2026.09.18.1551",
|
||||
"author": {
|
||||
"name": "Bryan Van Deusen"
|
||||
},
|
||||
|
||||
@@ -10,9 +10,25 @@ while you worked: they don't hold the files you read, the names you used or
|
||||
the order you did things in. A reply that follows *your* path is accurate and
|
||||
still unreadable to them. Shape it around **where the work stands**.
|
||||
|
||||
Pick the kind of reply first (the tables below), then fill its sections. The
|
||||
sections are what lets the operator find things at a glance, so keep them even
|
||||
when one is short — "**Needs you:** nothing" is an answer they were looking for.
|
||||
Pick the kind of reply first (the tables below). Its sections are **what to
|
||||
consider including, not a form to complete.**
|
||||
|
||||
Two kinds of section, and they behave differently:
|
||||
|
||||
- **A section that answers a standing question** — *does anything need me? what
|
||||
happens next?* — is always answered, even when the answer is nothing.
|
||||
"**Needs you:** nothing" is what they were looking for.
|
||||
- **A section that explains** — how it was done, why that way, what else you
|
||||
noticed — earns its place only when it changes what the operator does or
|
||||
decides. When it would not, leave it out: that detail belongs in the record's
|
||||
log, where it is available and not in the way.
|
||||
|
||||
**Write the shortest reply that carries the answer.** To someone reading
|
||||
quickly, length is not thoroughness — it is work handed back to them. A reply
|
||||
that fills every heading faithfully and runs a full screen is worse than four
|
||||
lines naming the two things that changed their position. Extra length has to be
|
||||
earned: a comparison they asked for, options that need laying side by side, a
|
||||
measurement whose numbers are the point.
|
||||
|
||||
## Every reply
|
||||
|
||||
@@ -33,6 +49,13 @@ when one is short — "**Needs you:** nothing" is an answer they were looking fo
|
||||
working. If a term has to appear, explain it once.
|
||||
- **Place the work in Scribe.** Name the task, issue or milestone it belongs to,
|
||||
by id *and* title (using-scribe: "Name the record, never just its number").
|
||||
- **A decision already made gets acted on, and the reply says what you did with
|
||||
it.** Once the operator has chosen, that is the input to the work, not a topic
|
||||
to revisit. If something you have since learned genuinely overturns the
|
||||
choice, say so once and plainly — name the new evidence and what it changes —
|
||||
and otherwise let the decision stand. Laying out the trade-offs of a settled
|
||||
question again reads as contradicting yourself rather than as being careful,
|
||||
and it costs the operator the decision twice.
|
||||
|
||||
## Take the placement from the record
|
||||
|
||||
@@ -137,6 +160,15 @@ Notes on each section:
|
||||
- **Needs you** — an action, an approval, a decision, or "nothing". If it's an
|
||||
action, give the reason with it. An approval you are holding for also gets
|
||||
its own **Approval requested** section, and this line points at it.
|
||||
|
||||
**Two tests, and it takes both: is this theirs to decide, and is work waiting
|
||||
on it?** A choice that is genuinely theirs — a priority, a trade-off only they
|
||||
can price, something they have to live with afterwards — belongs here. A
|
||||
question you could settle by reading something, by taking a measurement you
|
||||
already have access to, or by choosing the obvious default does not: that is
|
||||
work not yet done, and sending it moves your uncertainty onto them. Settle it,
|
||||
say which way you went and why, and leave them free to overrule you. This
|
||||
section is for what blocks them, not for what you are unsure about.
|
||||
- **Next** — from `placement.next`, or say the milestone is finished. If you
|
||||
found something you didn't fix, the offer to fix it goes here.
|
||||
|
||||
@@ -146,3 +178,10 @@ Read the reply as the operator will: someone who wasn't there, reading
|
||||
quickly. Can they tell **what was done, whether anything needs them, and what
|
||||
happens next** without asking a follow-up? If not, the sections are what's
|
||||
missing — not more detail.
|
||||
|
||||
Then read it once more for **what can go**. A section filled because it was in
|
||||
the table, reasoning supporting a conclusion nobody is going to dispute, a
|
||||
finding already written to the record — none of it changes what the operator
|
||||
does, so none of it belongs in the reply. Cutting is not hiding: the log holds
|
||||
it, and the reply stays readable. A reply that has been cut twice is the one
|
||||
they can act on.
|
||||
|
||||
@@ -170,6 +170,18 @@ TOPICS: tuple[Topic, ...] = (
|
||||
Topic("the operator's own reply shapes come first", "skill:reporting-back",
|
||||
("reply_preferences", 'content_type="rule"'),
|
||||
"the operator's own shapes come first"),
|
||||
# Milestone 409 step 7: the sections were being FILLED rather than chosen,
|
||||
# so a reply could satisfy every heading and still be unreadable. These
|
||||
# three are the discipline around the scaffold, not the scaffold itself.
|
||||
Topic("sections are chosen, not filled, and the reply is cut twice",
|
||||
"skill:reporting-back", ("not a form to complete", "what can go"),
|
||||
"write the shortest reply that carries the answer"),
|
||||
Topic("a needs-you item is theirs to decide and blocks work",
|
||||
"skill:reporting-back", ("is this theirs to decide",),
|
||||
"this section is for what blocks them, not for what you are unsure about"),
|
||||
Topic("a settled decision is acted on, not re-opened",
|
||||
"skill:reporting-back", ("already made",),
|
||||
"reads as contradicting yourself rather than as being careful"),
|
||||
# ── per-tool contracts and in-band behaviour — owned by the server ──
|
||||
Topic("closing a task cues the report", "docstrings", ("report_back",), "reporting this to the operator?"),
|
||||
Topic("a note that asserts a fact carries its check", "docstrings", ("verify_with", "expires_when"),
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
"""A reply's sections are chosen, not filled (#4153, milestone 409 step 7).
|
||||
|
||||
WHY THIS EXISTS
|
||||
|
||||
Step 6 measured the scaffold on live sessions and found the two halves
|
||||
disagreeing: **adherence passed and the read test failed.** Completion replies
|
||||
carried every section the table asks for — placement, what changed, what needs
|
||||
the operator, what comes next — and the operator still could not read them.
|
||||
|
||||
The cause was in the skill rather than in compliance with it. It said to pick a
|
||||
kind of reply "then fill its sections… keep them even when one is short", which
|
||||
is an instruction to complete a form. Nothing anywhere set a ceiling, and a
|
||||
section with a heading and nothing to say gets filled rather than dropped. So a
|
||||
faithful reply and an unreadable one were the same reply.
|
||||
|
||||
WHAT IS PINNED
|
||||
|
||||
The discipline, not the scaffold. The fifteen categories and their sections are
|
||||
unchanged and are not this file's subject:
|
||||
|
||||
1. Sections are chosen — a standing question is always answered, an
|
||||
explanation earns its place.
|
||||
2. The reply is as short as the answer allows, and gets a second pass for
|
||||
what can go.
|
||||
3. "Needs you" takes BOTH tests: theirs to decide, and blocking.
|
||||
4. A decision already made is acted on rather than re-argued.
|
||||
|
||||
RULE 167, AND WHY THERE IS NO ABSENCE CHECK HERE
|
||||
|
||||
The obvious guard — assert the skill no longer tells anyone to "fill" a
|
||||
section — is the trap snippet #3352 names. This skill legitimately discusses
|
||||
filling in order to warn against it ("not a form to complete", "a section
|
||||
filled because it was in the table"), so an absence check would fail on the
|
||||
corrected text: a false alarm about the very thing it protects.
|
||||
|
||||
So every assertion here is a PRESENCE check, and one is POSITIONAL —
|
||||
`test_the_needs_you_test_sits_with_the_needs_you_section` pins where the test
|
||||
lives, not merely that the words occur somewhere in the file. All four were
|
||||
falsified against the pre-#4153 text before being committed.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
SKILL = ROOT / "plugin/skills/reporting-back/SKILL.md"
|
||||
|
||||
|
||||
def _flat() -> str:
|
||||
"""Whitespace-flattened: the file is hard-wrapped, so phrases straddle lines."""
|
||||
return " ".join(SKILL.read_text().split()).lower()
|
||||
|
||||
|
||||
def test_sections_are_chosen_rather_than_completed():
|
||||
"""The framing the operator reacted to was "fill its sections"."""
|
||||
text = _flat()
|
||||
assert "not a form to complete" in text
|
||||
# Both halves of the distinction, or "choose" collapses back into "fill":
|
||||
# a standing question is answered even when the answer is nothing, an
|
||||
# explanation is dropped when it changes nothing.
|
||||
assert "always answered, even when the answer is nothing" in text
|
||||
assert "changes what the operator does or decides" in text
|
||||
|
||||
|
||||
def test_the_reply_carries_a_length_discipline():
|
||||
"""Without a ceiling, every section is an invitation to keep writing."""
|
||||
text = _flat()
|
||||
assert "write the shortest reply that carries the answer" in text
|
||||
# And the ceiling has named exceptions, so this cannot be read as
|
||||
# "always be terse" — a measurement the operator asked for still earns room.
|
||||
assert "extra length has to be earned" in text
|
||||
|
||||
|
||||
def test_the_reply_gets_a_second_pass_for_what_can_go():
|
||||
"""Cutting is a separate act from writing, and needs saying separately.
|
||||
|
||||
The pre-existing check asked whether anything was MISSING, which a bloated
|
||||
reply passes.
|
||||
"""
|
||||
text = _flat()
|
||||
assert "what can go" in text
|
||||
# Cutting must not read as withholding, or it will not be done.
|
||||
assert "cutting is not hiding" in text
|
||||
|
||||
|
||||
def test_the_needs_you_test_sits_with_the_needs_you_section():
|
||||
"""POSITIONAL. The test has to be where the section is defined.
|
||||
|
||||
A reader reaches this while writing that section; stated anywhere else it
|
||||
is a paragraph nobody is reading at the moment it applies. Asserted by
|
||||
offset rather than by presence, so moving it away fails here.
|
||||
"""
|
||||
raw = " ".join(SKILL.read_text().split())
|
||||
lowered = raw.lower()
|
||||
|
||||
needs_you = lowered.index("- **needs you** — an action")
|
||||
the_test = lowered.index("is this theirs to decide")
|
||||
next_bullet = lowered.index("- **next** —", needs_you)
|
||||
|
||||
assert needs_you < the_test < next_bullet, (
|
||||
"the needs-you test has moved out of the Needs you bullet; a reader "
|
||||
"writing that section will not meet it"
|
||||
)
|
||||
# Both halves are load-bearing: "theirs" alone still admits a question the
|
||||
# session could have answered, "blocking" alone admits one that is not
|
||||
# theirs to make.
|
||||
assert "work waiting on it" in lowered
|
||||
assert "not for what you are unsure about" in lowered
|
||||
|
||||
|
||||
def test_a_settled_decision_is_acted_on_rather_than_re_argued():
|
||||
"""Re-opening a decision reads as self-contradiction, not as diligence.
|
||||
|
||||
Phrased as a practice rather than a prohibition (rule 165): the instruction
|
||||
is what to DO with a decision, with the failure named after it.
|
||||
"""
|
||||
text = _flat()
|
||||
assert "a decision already made gets acted on" in text
|
||||
assert "reads as contradicting yourself rather than as being careful" in text
|
||||
# The escape hatch stays open, or this becomes a rule against ever
|
||||
# correcting anything — which is the opposite of what is wanted.
|
||||
assert "genuinely overturns the choice" in text
|
||||
|
||||
|
||||
def test_the_scaffold_itself_is_untouched():
|
||||
"""This step changed the discipline AROUND the sections, not the sections.
|
||||
|
||||
If a future edit deletes a category while tightening the prose, that is a
|
||||
change to #4009's subject and should not ride along silently here.
|
||||
"""
|
||||
text = _flat()
|
||||
for section in ("where this sits", "what now works", "how / why",
|
||||
"needs you", "next"):
|
||||
assert section in text, f"completion-report section {section!r} is gone"
|
||||
for kind in ("completion", "finding", "blocked / failed", "progress",
|
||||
"decision", "clarification", "handoff", "approval", "conflict"):
|
||||
assert kind in text, f"reply kind {kind!r} is gone"
|
||||
Reference in New Issue
Block a user