Commit 3b573df
* fix(guardrails): hold a wrapped destructive confirm to the args it previewed
The gate's `bind` parameter exists to fold confirmation-relevant arguments
into the token's identity, and the integration wrappers were not passing it.
The only thing tying a confirm to its preview was `target` — the first of
id/post_id/attachment_id in the input — so on any partner tool taking an id
plus a payload, every other argument was unbound and the delegate closed over
the *confirm* call's input. Preview {post_id: 12, description: "approved"},
confirm {post_id: 12, description: "something else"}, and the second one ran.
The user approved one change and got another, which is the two-step confirm
doing the opposite of its job.
Now `bind` is a sha256 of the whole argument set minus confirm_token, key
sorted so a client reordering its JSON doesn't break a legitimate confirm.
`target` stays the bare id, so the log line is still a readable "#12" rather
than a hash. The target key list is widened past the original three and is
filterable — a key missing from it now costs legibility, never safety.
Also here: denial_reason() had no branch for a capability denial, the one
refusal whose fix is a different WordPress account rather than anything in
the Saddle dashboard. It fell through to the generic paragraph, which lists
three fixes and not that one. permission() now records the capability each
ability asks for as it registers — one line, and it covers free, Pro and the
wrappers alike, because all three build their callback there.
Closes #89
Refs #63
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GkZr73cqaSesHRDG89Yy8S
* feat(tools): offer only the tools this credential can actually call
A fresh install sits at the read tier — that is the second non-negotiable —
and then hands the agent all 66 free tools, 30 of which are refused on every
single call. With Pro and the first-party integrations on, the same list runs
to about 160. Every one of those unusable entries costs a full JSON schema in
the agent's context window, and buys a guaranteed refusal.
tools/list is now filtered to what the current credential could execute:
the effective tier (already clamped by any OAuth scope), the owner's per-tool
switches, and the capability the connected account holds. Both transports —
and on a WordPress.org install the built-in one is the only transport there
is, since the adapter stopped shipping in that zip.
The filter runs at DISPATCH, never at registration. adapter_tool_names() is
built during mcp_adapter_init, inside rest_api_init and before authentication;
filtering there would read the wrong tier and force early user resolution on
every REST request site-wide. So the adapter server is still registered with
the complete set and mcp_adapter_tools_list narrows the response.
Pause is deliberately excluded. It denies everything at call time anyway, and
emptying the list would make every connected client reconnect on resume. The
instructions lead with the pause warning instead.
Filtering costs an agent the ability to say "that tool exists, you just have
not enabled it", so that is bought back explicitly: the context now says how
many tools are withheld and by which gate, and get-site-info reports the same
counts. Two sentences instead of thirty schemas, and it is the half a user
can act on.
Along the way: is_callable_now() resolves the ability before reading the
capability registry, because wp_get_abilities() is what lazily fires
wp_abilities_api_init and fills it — reading it first silently skips the
check on the first call of a request. Pinned by a test.
The POT was regenerated and had drifted well past this change: it was missing
context-bundle, list-templates, get-template and the block-theme
bootstrap-design-system strings entirely — an entire feature's worth of
msgids, which is the exact failure CLAUDE.md warns about.
Refs #89
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GkZr73cqaSesHRDG89Yy8S
* fix(context): point at the tool that replaced five, and stop stuttering
Three things the context was getting wrong, all of them cheap to fix and
paid on every single session.
**It never mentioned context-bundle.** The bundle exists precisely to
collapse get-design-system + list-block-types + list-block-patterns +
list-section-recipes + list-templates into one call, and the steering was
still describing that five-call sequence — so every session paid the calls
the bundle was built to save. The design section now leads with ORIENT
FIRST, and closes the loop the playbook teaches: verify-page, then actually
open the preview.
**Saddle_Context_Bundle::summary_lines() was dead code.** Computed,
budgeted, and documented in its own docblock as riding the system context
"so a session starts oriented before it calls anything at all" — and called
by nothing since the bundle shipped. Now hooked, under its own heading, with
its self-referential prefix and its "call context-bundle" tail dropped: the
context supplies both and would otherwise say them twice.
**Recent changes stuttered.** A real site was spending six of its fifteen
lines on six consecutive saves of one post, which tells an agent nothing the
first line didn't. Consecutive entries on the same action and target now fold
into one line with a count, keeping the NEWEST summary — the current title,
not the draft one. Only consecutive runs fold, so editing A, then B, then A
again still reads as three steps. saddle/recall-changes stays unfolded; it is
the audit view.
Refs #91
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GkZr73cqaSesHRDG89Yy8S
* feat(playbook): ship the playbooks on every site that can use them
**build-page was gated on wp_is_block_theme().** The real question is not
"block theme?" but "does something else own these pages?" — a classic theme
still edits its posts and pages in the block editor, and it is the one kind of
site with no Site Editor to fall back on. So the sites most in need of a
playbook were precisely the ones getting none.
The disqualifier is a FOREIGN builder: its pages are markup inside the content
and the native block tools refuse them, so a Gutenberg playbook there is worse
than saying nothing. Divi with Saddle Pro is not foreign — Pro declares it
through saddle_native_builders and bundles its own playbook, which shadows by
name. That native/foreign split already existed inside Saddle_Context as
private logic; it is now two named methods, so the playbook and the context
answer the question the same way instead of twice.
Step 2 adapts: on a classic theme there are no template parts to read, so it
sends the agent to get-blocks on an existing page rather than after
get-template, which would be refused.
**And a second playbook, fix-page.** The repair loop was one bullet inside
build-page step 6, and it is the flow an agent hits most often — usually out
of order. It now has room to say why the order is the order (structural first
because everything else is measured against a tree that is about to change,
then "ignored" because styling that never landed looks identical to styling
that did), and to be blunt about the trap that damages pages: addresses are
positional, so one structural edit shifts every address after it. Make one
change, re-read, then make the next.
Four skills tests had assumed the classic test site had no built-ins at all —
which was true only because of the gate this commit removes. They now measure
what they mean (owner-installed skills), and there are five new cases covering
the gate in both directions and the classic-theme step 2.
Refs #93
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GkZr73cqaSesHRDG89Yy8S
* docs: commit the agent guide rewrite, and name the four pillars
Two things, both about the guide rather than the code.
**The 2026-08-12 rewrite was never committed.** It has been sitting in the
working tree since that session — the re-derived CLAUDE.md, the ROADMAP.md
split out of it (a dated roadmap inside a file that loads on every task goes
stale and misleads mid-session), and AGENTS.md turned into a symlink so the
two never drift. Recovered as-is; none of it is my wording.
**Plus a new section: the four pillars.** Context, guardrails, tools, and the
system — what a connected agent gets, where each part lives, and the seams
between them. The work in this branch kept turning up features that had landed
*beside* a pillar rather than in one: a bundle summary computed and never
served, steering that described the five calls the bundle replaced, a tool list
that ignored the tier, a gate parameter that existed and was not passed. Naming
the four is how the next feature lands in one.
One correction to the roadmap while committing it: item 1, "site-editor reads",
has already shipped — list-templates, get-template, get-global-styles and
list-saved-patterns are all registered in includes/abilities/site-editor.php.
The rest of that file still needs re-deriving against the tree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GkZr73cqaSesHRDG89Yy8S
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 3aff80d commit 3b573df
8 files changed
Lines changed: 829 additions & 426 deletions
File tree
- includes
- languages
- tests
This file was deleted.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
182 | 182 | | |
183 | 183 | | |
184 | 184 | | |
185 | | - | |
186 | | - | |
187 | | - | |
188 | | - | |
189 | | - | |
190 | | - | |
191 | | - | |
192 | | - | |
193 | | - | |
194 | | - | |
195 | | - | |
196 | | - | |
| 185 | + | |
| 186 | + | |
197 | 187 | | |
198 | 188 | | |
199 | 189 | | |
| |||
269 | 259 | | |
270 | 260 | | |
271 | 261 | | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
272 | 303 | | |
273 | 304 | | |
274 | 305 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
34 | 42 | | |
35 | 43 | | |
36 | 44 | | |
37 | 45 | | |
38 | 46 | | |
39 | 47 | | |
40 | | - | |
| 48 | + | |
41 | 49 | | |
42 | 50 | | |
43 | 51 | | |
44 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
45 | 61 | | |
46 | 62 | | |
47 | 63 | | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
48 | 108 | | |
49 | 109 | | |
50 | 110 | | |
| |||
54 | 114 | | |
55 | 115 | | |
56 | 116 | | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
57 | 131 | | |
58 | 132 | | |
59 | 133 | | |
60 | 134 | | |
61 | 135 | | |
62 | 136 | | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
63 | 141 | | |
64 | 142 | | |
65 | 143 | | |
| |||
68 | 146 | | |
69 | 147 | | |
70 | 148 | | |
71 | | - | |
| 149 | + | |
72 | 150 | | |
73 | 151 | | |
74 | 152 | | |
| |||
0 commit comments