CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 15s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / integration (push) Successful in 1m17s
CI & Build / Python tests (push) Successful in 1m59s
CI & Build / Build & push image (push) Successful in 23s
The reply shapes are server product now, delivered at their moments, so the skill stops restating them. Gone from it: the "Every reply" list, the per-kind tables, and "The operator's own shapes come first" (preferences arrive beside the shape on the same moments). It opens by saying where the shapes come from (list_reply_shapes, the delivered core's header) and keeps the reasoning: sections chosen not filled, a settled decision acted on, placement from the record, who decides what, the assumptions an option carries, the completion report worked in full, and the second pass. 13.5k to 10.7k characters. The core gains the Finding kind the skill's table carried (2,150 of 2,200). Tests follow the content: the kind and Approval-row pins move to the shapes, a new test holds the worked example and the completion shape to the same sections, and the guidance-ownership registry reads the delivered shapes as a surface, owning the preference-wins and length topics there. using-scribe points at the delivery and list_reply_shapes. #5496. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
143 lines
6.2 KiB
Python
143 lines
6.2 KiB
Python
"""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"
|
|
# The kinds live in the delivered shapes since milestone 500 step 4; the
|
|
# skill keeps the reasoning and the worked completion report.
|
|
from scribe.services import reply_shapes
|
|
|
|
shapes = " ".join(s.title + " " + s.text for s in reply_shapes.SHAPES.values()).lower()
|
|
for kind in ("completion", "finding", "blocked", "progress",
|
|
"decision", "clarification", "handoff", "approval", "conflict"):
|
|
assert kind in shapes, f"reply kind {kind!r} is gone"
|