@wootsup/yt-builder-mcp 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/bin/yt-builder-mcp.js +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.js +1 -1
- package/dist/catalog/tool-catalog-markdown.d.ts.map +1 -1
- package/dist/catalog/tool-catalog-markdown.js +13 -1
- package/dist/catalog/tool-catalog-markdown.js.map +1 -1
- package/dist/cli/doctor-command.d.ts.map +1 -1
- package/dist/cli/doctor-command.js +22 -2
- package/dist/cli/doctor-command.js.map +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/clients/index.d.ts +1 -1
- package/dist/clients/index.js +1 -1
- package/dist/diagnostics/ca-reexec.js +2 -2
- package/dist/diagnostics/diagnose-network-error.d.ts +1 -1
- package/dist/diagnostics/doctor.d.ts +34 -2
- package/dist/diagnostics/doctor.d.ts.map +1 -1
- package/dist/diagnostics/doctor.js +63 -2
- package/dist/diagnostics/doctor.js.map +1 -1
- package/dist/diagnostics/startup-self-probe.js +1 -1
- package/dist/gateway/advanced-read-tool.d.ts.map +1 -1
- package/dist/gateway/advanced-read-tool.js +14 -2
- package/dist/gateway/advanced-read-tool.js.map +1 -1
- package/dist/gateway/advanced-tool/discovery.d.ts.map +1 -1
- package/dist/gateway/advanced-tool/discovery.js +12 -1
- package/dist/gateway/advanced-tool/discovery.js.map +1 -1
- package/dist/gateway/advanced-tool/domains.d.ts.map +1 -1
- package/dist/gateway/advanced-tool/domains.js +5 -0
- package/dist/gateway/advanced-tool/domains.js.map +1 -1
- package/dist/gateway/advanced-tool/execute.d.ts +7 -1
- package/dist/gateway/advanced-tool/execute.d.ts.map +1 -1
- package/dist/gateway/advanced-tool/execute.js +58 -4
- package/dist/gateway/advanced-tool/execute.js.map +1 -1
- package/dist/gateway/essentials.d.ts +4 -4
- package/dist/gateway/essentials.d.ts.map +1 -1
- package/dist/gateway/essentials.js +12 -7
- package/dist/gateway/essentials.js.map +1 -1
- package/dist/install-skill.js +1 -1
- package/dist/net/extra-headers.d.ts +1 -1
- package/dist/net/extra-headers.js +1 -1
- package/dist/net/internal-host.js +2 -2
- package/dist/net/proxy-dispatcher.js +1 -1
- package/dist/net/proxy-dispatcher.js.map +1 -1
- package/dist/net/site-basic-auth.d.ts +2 -2
- package/dist/net/site-basic-auth.js +2 -2
- package/dist/proxy/bridge.d.ts +2 -2
- package/dist/proxy/bridge.js +3 -3
- package/dist/proxy/bridge.js.map +1 -1
- package/dist/server.d.ts +3 -3
- package/dist/server.js +3 -3
- package/dist/setup-cli.d.ts +2 -2
- package/dist/setup-cli.js +2 -2
- package/dist/setup-cli.js.map +1 -1
- package/dist/setup-npx-spec.d.ts +1 -1
- package/dist/setup-npx-spec.js +1 -1
- package/dist/setup-wizard-handshake.js +1 -1
- package/dist/setup-wizard-types.d.ts +1 -1
- package/dist/setup-wizard.js +3 -3
- package/dist/setup-wizard.js.map +1 -1
- package/dist/sites/client-pool.d.ts +1 -1
- package/dist/sites/client-pool.js +1 -1
- package/dist/sites/tools/sites-list.d.ts.map +1 -1
- package/dist/sites/tools/sites-list.js +26 -4
- package/dist/sites/tools/sites-list.js.map +1 -1
- package/dist/sites/tools/sites-test.js +1 -1
- package/dist/sites/tools/sites-test.js.map +1 -1
- package/dist/sites/tools/use-site.js +2 -2
- package/dist/sites/tools/use-site.js.map +1 -1
- package/dist/tools/budgeted-table.d.ts +21 -0
- package/dist/tools/budgeted-table.d.ts.map +1 -0
- package/dist/tools/budgeted-table.js +137 -0
- package/dist/tools/budgeted-table.js.map +1 -0
- package/dist/tools/elements/builders.d.ts.map +1 -1
- package/dist/tools/elements/builders.js +102 -40
- package/dist/tools/elements/builders.js.map +1 -1
- package/dist/tools/elements/handlers-write.d.ts +21 -0
- package/dist/tools/elements/handlers-write.d.ts.map +1 -1
- package/dist/tools/elements/handlers-write.js +231 -11
- package/dist/tools/elements/handlers-write.js.map +1 -1
- package/dist/tools/elements/handlers.d.ts +20 -12
- package/dist/tools/elements/handlers.d.ts.map +1 -1
- package/dist/tools/elements/handlers.js +77 -26
- package/dist/tools/elements/handlers.js.map +1 -1
- package/dist/tools/elements/schema-validation.d.ts +147 -7
- package/dist/tools/elements/schema-validation.d.ts.map +1 -1
- package/dist/tools/elements/schema-validation.js +508 -28
- package/dist/tools/elements/schema-validation.js.map +1 -1
- package/dist/tools/format/elements-format.d.ts.map +1 -1
- package/dist/tools/format/elements-format.js +56 -12
- package/dist/tools/format/elements-format.js.map +1 -1
- package/dist/tools/format/health-format.d.ts +16 -0
- package/dist/tools/format/health-format.d.ts.map +1 -1
- package/dist/tools/format/health-format.js +9 -0
- package/dist/tools/format/health-format.js.map +1 -1
- package/dist/tools/format/inspection-format.d.ts +7 -0
- package/dist/tools/format/inspection-format.d.ts.map +1 -1
- package/dist/tools/format/inspection-format.js +18 -0
- package/dist/tools/format/inspection-format.js.map +1 -1
- package/dist/tools/format/pages-format.d.ts +1 -1
- package/dist/tools/format/pages-format.d.ts.map +1 -1
- package/dist/tools/format/pages-format.js +7 -0
- package/dist/tools/format/pages-format.js.map +1 -1
- package/dist/tools/format/sources-format.d.ts +12 -5
- package/dist/tools/format/sources-format.d.ts.map +1 -1
- package/dist/tools/format/sources-format.js +31 -6
- package/dist/tools/format/sources-format.js.map +1 -1
- package/dist/tools/health.d.ts.map +1 -1
- package/dist/tools/health.js +85 -11
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +7 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inspection.d.ts +34 -0
- package/dist/tools/inspection.d.ts.map +1 -1
- package/dist/tools/inspection.js +666 -62
- package/dist/tools/inspection.js.map +1 -1
- package/dist/tools/layout-flatten.d.ts +7 -1
- package/dist/tools/layout-flatten.d.ts.map +1 -1
- package/dist/tools/layout-flatten.js +24 -13
- package/dist/tools/layout-flatten.js.map +1 -1
- package/dist/tools/library.d.ts +29 -2
- package/dist/tools/library.d.ts.map +1 -1
- package/dist/tools/library.js +247 -34
- package/dist/tools/library.js.map +1 -1
- package/dist/tools/local-content/builders.js +14 -14
- package/dist/tools/local-content/builders.js.map +1 -1
- package/dist/tools/local-content/handlers.d.ts +3 -1
- package/dist/tools/local-content/handlers.d.ts.map +1 -1
- package/dist/tools/local-content/handlers.js +42 -9
- package/dist/tools/local-content/handlers.js.map +1 -1
- package/dist/tools/local-content/schemas.d.ts +9 -4
- package/dist/tools/local-content/schemas.d.ts.map +1 -1
- package/dist/tools/local-content/schemas.js +14 -6
- package/dist/tools/local-content/schemas.js.map +1 -1
- package/dist/tools/multi-items/builders.d.ts.map +1 -1
- package/dist/tools/multi-items/builders.js +11 -10
- package/dist/tools/multi-items/builders.js.map +1 -1
- package/dist/tools/navigation/builders.d.ts +18 -0
- package/dist/tools/navigation/builders.d.ts.map +1 -0
- package/dist/tools/navigation/builders.js +47 -0
- package/dist/tools/navigation/builders.js.map +1 -0
- package/dist/tools/navigation/handlers.d.ts +30 -0
- package/dist/tools/navigation/handlers.d.ts.map +1 -0
- package/dist/tools/navigation/handlers.js +41 -0
- package/dist/tools/navigation/handlers.js.map +1 -0
- package/dist/tools/navigation/index.d.ts +15 -0
- package/dist/tools/navigation/index.d.ts.map +1 -0
- package/dist/tools/navigation/index.js +14 -0
- package/dist/tools/navigation/index.js.map +1 -0
- package/dist/tools/navigation/schemas.d.ts +46 -0
- package/dist/tools/navigation/schemas.d.ts.map +1 -0
- package/dist/tools/navigation/schemas.js +41 -0
- package/dist/tools/navigation/schemas.js.map +1 -0
- package/dist/tools/pages/builders.d.ts.map +1 -1
- package/dist/tools/pages/builders.js +213 -81
- package/dist/tools/pages/builders.js.map +1 -1
- package/dist/tools/pages/handlers-audit.d.ts +112 -3
- package/dist/tools/pages/handlers-audit.d.ts.map +1 -1
- package/dist/tools/pages/handlers-audit.js +623 -7
- package/dist/tools/pages/handlers-audit.js.map +1 -1
- package/dist/tools/pages/handlers-read.d.ts +51 -8
- package/dist/tools/pages/handlers-read.d.ts.map +1 -1
- package/dist/tools/pages/handlers-read.js +348 -69
- package/dist/tools/pages/handlers-read.js.map +1 -1
- package/dist/tools/pages/handlers-write.d.ts +7 -3
- package/dist/tools/pages/handlers-write.d.ts.map +1 -1
- package/dist/tools/pages/handlers-write.js +69 -25
- package/dist/tools/pages/handlers-write.js.map +1 -1
- package/dist/tools/pages/header-transparency.d.ts +64 -0
- package/dist/tools/pages/header-transparency.d.ts.map +1 -0
- package/dist/tools/pages/header-transparency.js +119 -0
- package/dist/tools/pages/header-transparency.js.map +1 -0
- package/dist/tools/pages/schemas.d.ts +284 -13
- package/dist/tools/pages/schemas.d.ts.map +1 -1
- package/dist/tools/pages/schemas.js +434 -26
- package/dist/tools/pages/schemas.js.map +1 -1
- package/dist/tools/pages/target.d.ts +162 -0
- package/dist/tools/pages/target.d.ts.map +1 -0
- package/dist/tools/pages/target.js +239 -0
- package/dist/tools/pages/target.js.map +1 -0
- package/dist/tools/response-budget.d.ts +77 -0
- package/dist/tools/response-budget.d.ts.map +1 -0
- package/dist/tools/response-budget.js +134 -0
- package/dist/tools/response-budget.js.map +1 -0
- package/dist/tools/shared-schemas.d.ts +45 -0
- package/dist/tools/shared-schemas.d.ts.map +1 -1
- package/dist/tools/shared-schemas.js +50 -0
- package/dist/tools/shared-schemas.js.map +1 -1
- package/dist/tools/sources/builders.d.ts.map +1 -1
- package/dist/tools/sources/builders.js +55 -39
- package/dist/tools/sources/builders.js.map +1 -1
- package/dist/tools/sources/handlers-bind.d.ts +10 -11
- package/dist/tools/sources/handlers-bind.d.ts.map +1 -1
- package/dist/tools/sources/handlers-bind.js +110 -9
- package/dist/tools/sources/handlers-bind.js.map +1 -1
- package/dist/tools/sources/handlers.d.ts +4 -2
- package/dist/tools/sources/handlers.d.ts.map +1 -1
- package/dist/tools/sources/handlers.js +11 -7
- package/dist/tools/sources/handlers.js.map +1 -1
- package/dist/tools/sparse-fields.d.ts +61 -8
- package/dist/tools/sparse-fields.d.ts.map +1 -1
- package/dist/tools/sparse-fields.js +124 -12
- package/dist/tools/sparse-fields.js.map +1 -1
- package/dist/tools/sublayout/builders.d.ts.map +1 -1
- package/dist/tools/sublayout/builders.js +1 -2
- package/dist/tools/sublayout/builders.js.map +1 -1
- package/dist/tools/tool-builder/results.d.ts +19 -6
- package/dist/tools/tool-builder/results.d.ts.map +1 -1
- package/dist/tools/tool-builder/results.js +133 -6
- package/dist/tools/tool-builder/results.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +2 -2
- package/skills/yt-builder-mcp/SKILL.md +1713 -72
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @license MIT
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
|
+
import { INCLUDE_META_SCHEMA } from '../shared-schemas.js';
|
|
10
11
|
import { MAX_CHARS } from '../sparse-fields.js';
|
|
11
12
|
export const PAGES_LIST_OUTPUT_SCHEMA = z.object({
|
|
12
13
|
// F-Frontend-URL (2026-05-25): each row carries `frontend_url`,
|
|
@@ -35,7 +36,7 @@ export const PAGES_LIST_OUTPUT_SCHEMA = z.object({
|
|
|
35
36
|
// field projection + available_fields discovery surface it.
|
|
36
37
|
items: z.array(z.record(z.string(), z.unknown())),
|
|
37
38
|
total: z.number(),
|
|
38
|
-
// B2 (
|
|
39
|
+
// B2 (customer 2026-06-25): pagination. `total` is the (filtered) count;
|
|
39
40
|
// `returned` is how many rows this page carries; `next_cursor` (present
|
|
40
41
|
// only when more remain) is passed back as `cursor` for the next page.
|
|
41
42
|
returned: z.number().optional(),
|
|
@@ -61,7 +62,7 @@ export const SCHEMA_OUTPUT_SCHEMA = z.object({
|
|
|
61
62
|
// row for back-compat but is optional so callers reading the schema
|
|
62
63
|
// know `rel_path` is the canonical projection column.
|
|
63
64
|
//
|
|
64
|
-
// G-B (
|
|
65
|
+
// G-B (customer 2026-06-26): every row-field is now `.optional()` +
|
|
65
66
|
// `.passthrough()` so a `fields[]` projection (which drops the unrequested
|
|
66
67
|
// keys) still validates — exactly the relaxation `element_list`'s output
|
|
67
68
|
// schema uses. The default (no fields) full row still carries all keys.
|
|
@@ -96,16 +97,36 @@ export const ETAG_OUTPUT_SCHEMA = z.object({
|
|
|
96
97
|
// the RESULT-LEVEL by `withSiteMeta`, NEVER into `structuredContent`
|
|
97
98
|
// (see results.ts W12-R2 commentary) — this schema therefore validates
|
|
98
99
|
// only the inner template-summary shape.
|
|
100
|
+
//
|
|
101
|
+
// T-C5 (mega-push plan 2026-07-24-02, customer P1.5): `template_summary` is now
|
|
102
|
+
// BIMODAL — a single-`template_id` call returns these fields at the TOP LEVEL
|
|
103
|
+
// (unchanged wire shape, back-compat preserved); a `template_ids` BATCH call
|
|
104
|
+
// instead returns `{summaries: {<id>: <that SAME per-template shape>},
|
|
105
|
+
// not_found: [...]}` and omits every top-level field below. The 7 fields that
|
|
106
|
+
// used to be REQUIRED (template_id/counts_by_type/bound_count/max_depth/total/
|
|
107
|
+
// named_sections/etag) are now `.optional()` too, SPECIFICALLY so one strict
|
|
108
|
+
// Zod object can validate BOTH response variants without a `z.union()` — the
|
|
109
|
+
// codebase's established alternative for a bimodal MCP response is dropping
|
|
110
|
+
// `outputSchema` entirely (see page_get_layout / page_dynamize in
|
|
111
|
+
// builders.ts), which would lose validation for the (overwhelmingly common)
|
|
112
|
+
// single-template case too. Declaring every possible field of BOTH shapes as
|
|
113
|
+
// optional on one object keeps `additionalProperties:false` catching
|
|
114
|
+
// genuinely unexpected keys while tolerating "this variant's fields are
|
|
115
|
+
// absent" — the SAME relaxation `resolved_target`/`FINDING` already use
|
|
116
|
+
// elsewhere in this file for the identical reason (different response
|
|
117
|
+
// variants omit different keys).
|
|
99
118
|
export const TEMPLATE_SUMMARY_OUTPUT_SCHEMA = z.object({
|
|
100
|
-
template_id: z.string(),
|
|
101
|
-
counts_by_type: z.record(z.string(), z.number().int().nonnegative()),
|
|
102
|
-
bound_count: z.number().int().nonnegative(),
|
|
103
|
-
max_depth: z.number().int().nonnegative(),
|
|
104
|
-
total: z.number().int().nonnegative(),
|
|
105
|
-
named_sections: z
|
|
119
|
+
template_id: z.string().optional(),
|
|
120
|
+
counts_by_type: z.record(z.string(), z.number().int().nonnegative()).optional(),
|
|
121
|
+
bound_count: z.number().int().nonnegative().optional(),
|
|
122
|
+
max_depth: z.number().int().nonnegative().optional(),
|
|
123
|
+
total: z.number().int().nonnegative().optional(),
|
|
124
|
+
named_sections: z
|
|
125
|
+
.array(z.object({
|
|
106
126
|
path: z.string(),
|
|
107
127
|
name: z.string(),
|
|
108
|
-
}))
|
|
128
|
+
}))
|
|
129
|
+
.optional(),
|
|
109
130
|
// pages_audit impl plan Task 7 (2026-07-19): additive enrichment —
|
|
110
131
|
// heading-level census (h1-h6 + div — `div` counts `description_list`
|
|
111
132
|
// elements, the one type whose title_element defaults to div),
|
|
@@ -114,9 +135,54 @@ export const TEMPLATE_SUMMARY_OUTPUT_SCHEMA = z.object({
|
|
|
114
135
|
// a NEW MCP client talking to an OLD (pre-1.10.0) host plugin — which
|
|
115
136
|
// doesn't emit these fields yet — still validates.
|
|
116
137
|
heading_levels: z.record(z.string(), z.number().int().nonnegative()).optional(),
|
|
138
|
+
// T-A3 (mega-push plan 2026-07-24-02): a static, always-present sibling
|
|
139
|
+
// stating which sources feed heading_levels above — html/text/code
|
|
140
|
+
// raw-HTML elements count (T-A1 widened the shared LayoutAuditExtractor
|
|
141
|
+
// pass to read them), module/widget output does NOT (no content field to
|
|
142
|
+
// parse). `.optional()` for the same pre-1.10.0-host back-compat reason
|
|
143
|
+
// as heading_levels itself.
|
|
144
|
+
heading_levels_note: z.string().optional(),
|
|
117
145
|
images_missing_alt: z.number().int().nonnegative().optional(),
|
|
118
146
|
disabled_elements: z.number().int().nonnegative().optional(),
|
|
119
|
-
|
|
147
|
+
// F191 (2026-06-12) source-transparency flags the PHP summary() has emitted
|
|
148
|
+
// since June but this schema never declared, so a strict SDK parse silently
|
|
149
|
+
// stripped them. `already_dynamized` = the layout already binds an API Mapper
|
|
150
|
+
// source (demo/clone vs pristine theme); `is_theme_template` = a real YOOtheme
|
|
151
|
+
// theme template vs a per-page/per-article layout. `.optional()` for the same
|
|
152
|
+
// pre-1.10.0-host back-compat reason as the fields above.
|
|
153
|
+
already_dynamized: z.boolean().optional(),
|
|
154
|
+
is_theme_template: z.boolean().optional(),
|
|
155
|
+
etag: z.string().optional(),
|
|
156
|
+
// Task 6 (Wave 1 audit, F4, 2026-07-24): one entry per content-bearing
|
|
157
|
+
// node (structural wrappers section/row/column/fragment/layout excluded).
|
|
158
|
+
// Disabled/render-dead nodes ARE included, unlike disabled_elements above:
|
|
159
|
+
// this is the field that says WHICH node and WHY, not just a count.
|
|
160
|
+
// T-E2 (mega-push plan 2026-07-24-02, Thomas-Entscheid C): OPT-IN now —
|
|
161
|
+
// absent unless the caller passed `include_nodes:true` (previously
|
|
162
|
+
// unconditional). `.optional()` covers BOTH that opt-out-by-default case
|
|
163
|
+
// AND the pre-1.10.0-host back-compat reason the three fields above share.
|
|
164
|
+
content_nodes: z
|
|
165
|
+
.array(z.object({
|
|
166
|
+
path: z.string(),
|
|
167
|
+
type: z.string(),
|
|
168
|
+
status: z.boolean(),
|
|
169
|
+
effective_visible: z.boolean(),
|
|
170
|
+
hidden_on_mobile: z.boolean(),
|
|
171
|
+
headings: z.array(z.string()),
|
|
172
|
+
alts: z.array(z.string()),
|
|
173
|
+
}))
|
|
174
|
+
.optional(),
|
|
175
|
+
// T-C5: page 2+ of content_nodes when `include_nodes:true` + `limit` were
|
|
176
|
+
// supplied. Present ONLY while more content_nodes rows remain (mirrors
|
|
177
|
+
// pages_list / element_list — never an always-present nullable field).
|
|
178
|
+
next_cursor: z.string().optional(),
|
|
179
|
+
// T-C5 (customer P1.5) batch shape — present ONLY when the caller passed
|
|
180
|
+
// `template_ids` instead of `template_id`. One entry per id that resolved
|
|
181
|
+
// (SAME per-template shape as the single-mode top-level fields above,
|
|
182
|
+
// left as an open record here rather than nesting the whole shape a
|
|
183
|
+
// second time); `not_found` lists ids that did not.
|
|
184
|
+
summaries: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),
|
|
185
|
+
not_found: z.array(z.string()).optional(),
|
|
120
186
|
});
|
|
121
187
|
// ─── C1 — pages_create + pages_delete (v1.2) ──────────────────────────
|
|
122
188
|
//
|
|
@@ -350,9 +416,11 @@ export const PAGE_DYNAMIZE_INPUT = {
|
|
|
350
416
|
.string()
|
|
351
417
|
.min(1)
|
|
352
418
|
.optional()
|
|
353
|
-
.describe('SINGLE-block path: the
|
|
354
|
-
'
|
|
355
|
-
'
|
|
419
|
+
.describe('SINGLE-block path: the LIST source the one iterating column query is rewritten ' +
|
|
420
|
+
'to. Accepts ANY list source — a published API Mapper flow ' +
|
|
421
|
+
'(apiMapperFlow…List, e.g. "apiMapperFlowAbcList") OR a native source/relation ' +
|
|
422
|
+
'(e.g. "posts.customPosts", "articles"). Provide THIS or `source_map` — exactly ' +
|
|
423
|
+
'one. Use yootheme_builder_sources_list to discover.'),
|
|
356
424
|
// F201 (multi-block): a section with SEVERAL source blocks (Played / Next /
|
|
357
425
|
// Later) dynamizes in ONE call. Each entry keys the EXISTING iterating
|
|
358
426
|
// column query (`match`) to its flow list source (`source_name`) + its own
|
|
@@ -437,6 +505,14 @@ export const PAGE_DYNAMIZE_INPUT = {
|
|
|
437
505
|
.optional()
|
|
438
506
|
.describe('Optimistic-lock ETag from yootheme_builder_get_etag. Optional; when provided, ' +
|
|
439
507
|
'412 on conflict. Recommended for collaborative edits.'),
|
|
508
|
+
// SOURCES_BINDING-27: preflight the dynamize without creating a page.
|
|
509
|
+
dry_run: z
|
|
510
|
+
.boolean()
|
|
511
|
+
.optional()
|
|
512
|
+
.describe('Preflight ONLY. When true, compute the dynamize and return {summary, ' +
|
|
513
|
+
'would_change, dry_run:true} WITHOUT creating a page — nothing persists ' +
|
|
514
|
+
'(etag == new_etag). Inspect summary.queries_swapped / unmatched_map_keys ' +
|
|
515
|
+
'before the real call.'),
|
|
440
516
|
};
|
|
441
517
|
/**
|
|
442
518
|
* Wire shape for `yootheme_builder_pages_delete` on success.
|
|
@@ -464,13 +540,97 @@ export const PAGES_DELETE_OUTPUT_SCHEMA = z
|
|
|
464
540
|
})
|
|
465
541
|
.refine((v) => typeof v.page_id === 'number' || typeof v.article_id === 'number', { message: 'response must contain either page_id (WP) or article_id (Joomla)' });
|
|
466
542
|
// ─── G-A (Track-C URL→layout) — pages_resolve ───────────────────────
|
|
543
|
+
// ─── A-6 (single-page audit plan, 2026-07-22-02) — semantic `target` ──────
|
|
544
|
+
//
|
|
545
|
+
// The shared addressing object BOTH `pages_resolve` and `pages_audit` accept.
|
|
546
|
+
// It is deliberately PERMISSIVE (every member optional, no `.refine`): the
|
|
547
|
+
// exactly-one-of contract is enforced in the HANDLERS as a typed
|
|
548
|
+
// `too_many_targets` result. F205 (`gateway/advanced-read-tool.ts`): an
|
|
549
|
+
// SDK-level zod rejection surfaces as a raw -32602 and bypasses every piece of
|
|
550
|
+
// typed steering the tool provides.
|
|
551
|
+
//
|
|
552
|
+
// The VOCABULARY lives in these field `.describe()` strings on purpose — the
|
|
553
|
+
// tool descriptions are pinned at MAX_DESCRIPTION_LENGTH=600 by
|
|
554
|
+
// `tests/gateway/description-length.test.ts` and pages_resolve already sits at
|
|
555
|
+
// 577, so there is no room there. Field describes carry no length pin.
|
|
556
|
+
/**
|
|
557
|
+
* The five addressing keys, in the order they are documented + put on the
|
|
558
|
+
* wire. SSOT for both the `matched_by` echo enum and the runtime iteration in
|
|
559
|
+
* `./target.ts`.
|
|
560
|
+
*/
|
|
561
|
+
export const TARGET_KEYS = ['template_id', 'post_id', 'url', 'special', 'title'];
|
|
562
|
+
/**
|
|
563
|
+
* The semantic-special vocabulary, byte-identical to PHP `SpecialTargetMap`'s
|
|
564
|
+
* key set. `date` is IN the vocabulary but unsupported on both platforms — it
|
|
565
|
+
* resolves to a clean `special_not_supported` error rather than "unknown
|
|
566
|
+
* special", which is why it is listed here.
|
|
567
|
+
*/
|
|
568
|
+
export const SPECIAL_TARGET_TOKENS = [
|
|
569
|
+
'home',
|
|
570
|
+
'front-page',
|
|
571
|
+
'blog',
|
|
572
|
+
'search',
|
|
573
|
+
'404',
|
|
574
|
+
'category',
|
|
575
|
+
'tag',
|
|
576
|
+
'author',
|
|
577
|
+
'archive',
|
|
578
|
+
'date',
|
|
579
|
+
];
|
|
580
|
+
export const PAGES_TARGET = z
|
|
581
|
+
.object({
|
|
582
|
+
template_id: z
|
|
583
|
+
.string()
|
|
584
|
+
.min(1)
|
|
585
|
+
.optional()
|
|
586
|
+
.describe('An exact template id (e.g. "page:4816", "article:12", "helpArchiveTpl").'),
|
|
587
|
+
post_id: z
|
|
588
|
+
.union([z.number().int().positive(), z.string().min(1)])
|
|
589
|
+
.optional()
|
|
590
|
+
.describe('The numeric WordPress post / Joomla article id (e.g. 4816).'),
|
|
591
|
+
url: z
|
|
592
|
+
.string()
|
|
593
|
+
.min(1)
|
|
594
|
+
.optional()
|
|
595
|
+
.describe('A public frontend URL (e.g. "https://site.com/schedule-demo/").'),
|
|
596
|
+
special: z
|
|
597
|
+
.enum(SPECIAL_TARGET_TOKENS)
|
|
598
|
+
.optional()
|
|
599
|
+
.describe('A semantic page role: "home" / "front-page" / "blog" (the configured ' +
|
|
600
|
+
'front page or posts page), "search", "404", "category", "tag", ' +
|
|
601
|
+
'"author" (Joomla: the Contact template), "archive" (the post ' +
|
|
602
|
+
'archive). "date" is recognised but not supported on either platform ' +
|
|
603
|
+
'yet and returns special_not_supported. DISCOVERY_AUDIT-17: a special ' +
|
|
604
|
+
'resolves ONLY when the site has a theme template assigned to that role, ' +
|
|
605
|
+
'so search/tag/author/archive availability is SITE-SPECIFIC — a role with ' +
|
|
606
|
+
'no template returns special_not_resolvable (e.g. "archive" resolves on ' +
|
|
607
|
+
'one site but not another).'),
|
|
608
|
+
title: z
|
|
609
|
+
.string()
|
|
610
|
+
.min(1)
|
|
611
|
+
.optional()
|
|
612
|
+
.describe('Case-insensitive substring of the page NAME/label (e.g. "SunWest"). ' +
|
|
613
|
+
'Zero matches → title_no_match; two or more → title_ambiguous with a ' +
|
|
614
|
+
'candidate list to choose from.'),
|
|
615
|
+
})
|
|
616
|
+
.describe('Address exactly ONE page. Supply exactly one of template_id / post_id / url / ' +
|
|
617
|
+
'special / title — supplying two returns a typed too_many_targets error, ' +
|
|
618
|
+
'supplying none runs the unscoped default.');
|
|
467
619
|
export const PAGES_RESOLVE_INPUT = {
|
|
620
|
+
// A-6: OPTIONAL since a target-only resolve must be expressible. The
|
|
621
|
+
// exactly-one-of(url, target) contract lives in `handlePagesResolve`
|
|
622
|
+
// (typed too_many_targets / missing_url), not in the schema.
|
|
468
623
|
url: z
|
|
469
624
|
.string()
|
|
470
625
|
.min(1)
|
|
626
|
+
.optional()
|
|
471
627
|
.describe('A public frontend URL of the page/article (e.g. ' +
|
|
472
628
|
'"https://site.com/schedule-demo/"). Resolved to the per-page ' +
|
|
473
|
-
'template_id that renders it — paste a URL, get a layout-addressable id.'
|
|
629
|
+
'template_id that renders it — paste a URL, get a layout-addressable id. ' +
|
|
630
|
+
'Omit when addressing the page via `target` instead.'),
|
|
631
|
+
target: PAGES_TARGET.optional().describe('Resolve a page WITHOUT knowing its URL — by template_id, post_id, url, a ' +
|
|
632
|
+
'semantic special ("home", "blog", "404", "search", "category", "tag", ' +
|
|
633
|
+
'"author", "archive") or a title substring. Mutually exclusive with `url`.'),
|
|
474
634
|
};
|
|
475
635
|
export const PAGES_RESOLVE_OUTPUT_SCHEMA = z.object({
|
|
476
636
|
// The read-tool-addressable id every read tool accepts as `template_id`
|
|
@@ -484,17 +644,27 @@ export const PAGES_RESOLVE_OUTPUT_SCHEMA = z.object({
|
|
|
484
644
|
// Content type — `get_post_type`/archive post-type/taxonomy on WP,
|
|
485
645
|
// "article"/"category" on Joomla.
|
|
486
646
|
type: z.string(),
|
|
647
|
+
// The page's human display name. Load-bearing for the fuzzy addresses: a
|
|
648
|
+
// `special`/`title` resolve is only checkable if the caller can see WHICH
|
|
649
|
+
// page it landed on without a second round-trip. `.optional()` because a
|
|
650
|
+
// host predating the label fill omits it and this is a STRICT object.
|
|
651
|
+
label: z.string().optional(),
|
|
487
652
|
// Canonical permalink (null when the platform cannot re-derive one).
|
|
488
653
|
frontend_url: z.string().nullable(),
|
|
489
654
|
// G-A2: the matched YT type/condition for a theme template
|
|
490
|
-
// (e.g. "archive-
|
|
655
|
+
// (e.g. "archive-example_help", "taxonomy-category_faq"), else null for a
|
|
491
656
|
// per-page layout.
|
|
492
657
|
context: z.string().nullable(),
|
|
493
658
|
// G-A2: which kind was resolved — a per-page layout vs. the governing theme
|
|
494
659
|
// template — so the agent knows it reached the right surface.
|
|
495
660
|
resolved_via: z.enum(['per_page', 'theme_template']),
|
|
661
|
+
// A-6: WHICH branch of the `target` object matched, echoed so the caller can
|
|
662
|
+
// tell the user how a fuzzy address (a title substring, a semantic special)
|
|
663
|
+
// was interpreted. `.optional()` is load-bearing: a `url` resolve and every
|
|
664
|
+
// host predating A-5 omit it, and this is a STRICT object.
|
|
665
|
+
matched_by: z.enum(TARGET_KEYS).optional(),
|
|
496
666
|
});
|
|
497
|
-
// ─── F5 + N5 (
|
|
667
|
+
// ─── F5 + N5 (customer HANDOFF-4/5) — render_preview ──────────────────
|
|
498
668
|
//
|
|
499
669
|
// The input shape (`template_id` OR `url`, exactly one — mirroring
|
|
500
670
|
// page_get_layout's addressing) is assembled in `builders.ts` from the shared
|
|
@@ -516,7 +686,7 @@ export const PAGES_RESOLVE_OUTPUT_SCHEMA = z.object({
|
|
|
516
686
|
* - cache_purge_hint: the constant operator steer (purge Rocket/CDN after save).
|
|
517
687
|
* - template_id: echoed back (the resolved id the render ran on).
|
|
518
688
|
* - matched: OPTIONAL — present ONLY when the caller passed `expect`; did the
|
|
519
|
-
* rendered HTML contain that substring (
|
|
689
|
+
* rendered HTML contain that substring (customer HANDOFF-6 #4).
|
|
520
690
|
* - snippet: OPTIONAL — present ONLY when the caller passed `expect`; a
|
|
521
691
|
* ~200-char window around the first match (or null when not
|
|
522
692
|
* matched / the render threw). NULLABLE so the no-match case
|
|
@@ -529,13 +699,64 @@ export const RENDER_PREVIEW_OUTPUT_SCHEMA = z.object({
|
|
|
529
699
|
error: z.string().optional(),
|
|
530
700
|
cache_purge_hint: z.string(),
|
|
531
701
|
template_id: z.string(),
|
|
532
|
-
// F5 + N5 /
|
|
702
|
+
// F5 + N5 / customer HANDOFF-6 #4 — the optional `expect` substring check.
|
|
533
703
|
// Both keys are emitted ONLY when the caller passed `expect`; declared here
|
|
534
704
|
// (strict ZodObject → additionalProperties:false) so a strict host does not
|
|
535
705
|
// -32602-reject the `expect` variant. `snippet` is nullable (null on a miss).
|
|
536
706
|
matched: z.boolean().optional(),
|
|
537
707
|
snippet: z.string().nullable().optional(),
|
|
538
708
|
});
|
|
709
|
+
// ─── T-A5 (heading_outline tool, mega-push plan 2026-07-24-02) ─────────────
|
|
710
|
+
//
|
|
711
|
+
// A real, doc-order H1-H6 outline of ONE template, each heading attributed to
|
|
712
|
+
// its SOURCE element (`element_type`/`path`), plus the alt-status of every
|
|
713
|
+
// image (including images living inside `html`/`text`/`code` raw markup).
|
|
714
|
+
// Read-only STRUCTURE inspection — no findings/severity (that is
|
|
715
|
+
// `pages_audit`'s job). `template_id`-only addressing (no `post_id`/`url`
|
|
716
|
+
// sugar): resolve via `yootheme_builder_pages_resolve` first, like the other
|
|
717
|
+
// per-page tools that need a bare template_id.
|
|
718
|
+
//
|
|
719
|
+
// The INPUT shape is assembled directly in `builders.ts` (template_id + a
|
|
720
|
+
// local `mode` enum), mirroring render_preview/template_summary — only the
|
|
721
|
+
// STRICT output schema lives here.
|
|
722
|
+
/**
|
|
723
|
+
* Wire shape for `yootheme_builder_heading_outline` on success.
|
|
724
|
+
*
|
|
725
|
+
* STRICT (additionalProperties:false default ZodObject) — every field the
|
|
726
|
+
* handler puts in structuredContent MUST be declared here, or a strict MCP
|
|
727
|
+
* host -32602-rejects the SUCCESS.
|
|
728
|
+
*
|
|
729
|
+
* - outline: every H1-H6 heading, in TRUE document order, each carrying its
|
|
730
|
+
* TEXT and the source element that carries it (`element_type`/`path`).
|
|
731
|
+
* - images: every image this pass sees (populated OR empty alt) — `alt` is
|
|
732
|
+
* null for a missing/empty alt, the raw string otherwise; `is_generic_alt`
|
|
733
|
+
* flags an exact match against the same generic-phrase list pages_audit's
|
|
734
|
+
* `generic-alt-text` check uses ("logo", "image", "photo", ...).
|
|
735
|
+
* - h1_count / has_level_skip / images_missing_alt: derived flags —
|
|
736
|
+
* has_level_skip mirrors pages_audit's `heading-skip` check EXACTLY (a
|
|
737
|
+
* level more than one above the highest seen so far, including a
|
|
738
|
+
* document that OPENS below h1).
|
|
739
|
+
*/
|
|
740
|
+
export const HEADING_OUTLINE_OUTPUT_SCHEMA = z.object({
|
|
741
|
+
template_id: z.string(),
|
|
742
|
+
mode: z.enum(['builder', 'rendered']),
|
|
743
|
+
etag: z.string(),
|
|
744
|
+
outline: z.array(z.object({
|
|
745
|
+
level: z.string(),
|
|
746
|
+
text: z.string(),
|
|
747
|
+
element_type: z.string(),
|
|
748
|
+
path: z.string(),
|
|
749
|
+
})),
|
|
750
|
+
images: z.array(z.object({
|
|
751
|
+
alt: z.string().nullable(),
|
|
752
|
+
is_generic_alt: z.boolean(),
|
|
753
|
+
element_type: z.string(),
|
|
754
|
+
path: z.string(),
|
|
755
|
+
})),
|
|
756
|
+
h1_count: z.number().int().nonnegative(),
|
|
757
|
+
has_level_skip: z.boolean(),
|
|
758
|
+
images_missing_alt: z.number().int().nonnegative(),
|
|
759
|
+
});
|
|
539
760
|
// ─── Task 6 (pages_audit design, 2026-07-19) — shared FINDING/QUICK_CHECK ──
|
|
540
761
|
//
|
|
541
762
|
// Defined ONCE here — referenced by `pages_audit`'s own output schema AND
|
|
@@ -543,21 +764,94 @@ export const RENDER_PREVIEW_OUTPUT_SCHEMA = z.object({
|
|
|
543
764
|
// the finding shape has exactly one source of truth across all three
|
|
544
765
|
// surfaces (design §5-§7). Mirrors the PHP wire shape verbatim
|
|
545
766
|
// (`PageAuditService::audit()` / `AuditCheck`'s partial-finding contract).
|
|
767
|
+
/**
|
|
768
|
+
* The human-locatable address of ONE audit hit (B-3/B-5) — mirrors the PHP
|
|
769
|
+
* `FindingLocator::toArray()` wire shape. `path` stays the machine write
|
|
770
|
+
* address; `breadcrumb`/`element_type`/`element_name` are what a HUMAN needs
|
|
771
|
+
* to find the element in the customizer. Per the PHP null-key policy the
|
|
772
|
+
* optional members are OMITTED rather than sent as null.
|
|
773
|
+
*
|
|
774
|
+
* FAIL-SOFT BY CONSTRUCTION. `pages[]` rows used to ride through
|
|
775
|
+
* `.catchall(z.unknown())` untyped; now that `locators` is a typed array, a
|
|
776
|
+
* mismatch is no longer stripped — the MCP SDK THROWS `McpError`
|
|
777
|
+
* (InvalidParams) on an outputSchema violation, which fails the WHOLE audit
|
|
778
|
+
* call. So the optional members are `.nullish()` (an explicit `null` from a
|
|
779
|
+
* host that does not honour the omit-nulls policy must not be fatal) and the
|
|
780
|
+
* object carries a `.catchall`, so a field a future host version adds can
|
|
781
|
+
* never hard-fail an otherwise perfectly good audit.
|
|
782
|
+
*
|
|
783
|
+
* `repeat` is the one deliberately STRICT member: PHP emits it clamped to
|
|
784
|
+
* `>= 1` (`repeat: 0` is meaningless — a hit that renders zero times is not a
|
|
785
|
+
* hit), so `min(1)` states the real contract rather than tolerating a value
|
|
786
|
+
* that could only mean a producer bug.
|
|
787
|
+
*/
|
|
788
|
+
export const FINDING_LOCATOR = z
|
|
789
|
+
.object({
|
|
790
|
+
path: z.string(),
|
|
791
|
+
breadcrumb: z.string(),
|
|
792
|
+
element_type: z.string(),
|
|
793
|
+
element_name: z.string().nullish(),
|
|
794
|
+
heading_source: z.string().nullish(),
|
|
795
|
+
content_snippet: z.string().nullish(),
|
|
796
|
+
repeat: z.number().int().min(1),
|
|
797
|
+
prop: z.string().nullish(),
|
|
798
|
+
name: z.string().nullish(),
|
|
799
|
+
})
|
|
800
|
+
.catchall(z.unknown());
|
|
546
801
|
/**
|
|
547
802
|
* One audit finding. `pages` carries per-page mini-evidence whose KEYS vary
|
|
548
803
|
* by check code (`first_headline_path`, `alt_paths`, `cluster_id`,
|
|
549
804
|
* `iterated`, `disabled_elements`, `elements_content`, ...) — `.catchall`
|
|
550
805
|
* keeps every row's base `{id, label}` typed while tolerating the
|
|
551
806
|
* check-specific extras without a per-code union.
|
|
807
|
+
*
|
|
808
|
+
* The ADDITIVE B-3/B-5 keys (`locators`, `hits_truncated`,
|
|
809
|
+
* `locators_stripped`, `builder_url`) are declared EXPLICITLY for
|
|
810
|
+
* self-documentation — the `.catchall` already tolerated them, but a named
|
|
811
|
+
* member states the contract. All stay `.optional()`: a host plugin predating
|
|
812
|
+
* B-3 emits none of them.
|
|
813
|
+
*
|
|
814
|
+
* `frontend_url` (T-B1, 2026-07-24) rides alongside `builder_url` on EVERY
|
|
815
|
+
* page entry now, not just the nine element-located "hit" checks — a
|
|
816
|
+
* theme/archive template with no public route legitimately omits it (never
|
|
817
|
+
* emitted as `null`), same convention as `builder_url`.
|
|
818
|
+
*
|
|
819
|
+
* `hits_truncated` and `locators_stripped` mean DIFFERENT things and are never
|
|
820
|
+
* interchangeable: the former says the upstream `hit_cap` dropped hits (so the
|
|
821
|
+
* raw `paths`/`alt_paths`/`skips`/`dead_anchors` arrays are incomplete), the
|
|
822
|
+
* latter says the transport's shrink-to-fit shed locator PROSE while leaving
|
|
823
|
+
* those arrays complete.
|
|
824
|
+
*
|
|
825
|
+
* FORWARD-COMPAT `.catchall`, for the same reason `FINDING_LOCATOR` carries one:
|
|
826
|
+
* the handler forwards each finding VERBATIM, and a strict object generates
|
|
827
|
+
* `additionalProperties:false`, so ONE additive field on the PHP side (an
|
|
828
|
+
* explicit hit total, a new evidence key) would make a strict host -32602-reject
|
|
829
|
+
* an otherwise perfect audit. Every declared member is still validated; only
|
|
830
|
+
* UNDECLARED ones ride through untyped.
|
|
552
831
|
*/
|
|
553
832
|
export const FINDING = z.object({
|
|
554
833
|
code: z.string(),
|
|
555
|
-
category: z.enum(['seo', 'structure', 'content', 'links']),
|
|
834
|
+
category: z.enum(['seo', 'structure', 'content', 'links', 'design']),
|
|
556
835
|
severity: z.enum(['critical', 'warning', 'info']),
|
|
557
836
|
title: z.string(),
|
|
558
837
|
count: z.number().int().nonnegative(),
|
|
559
838
|
pages: z.array(z
|
|
560
|
-
.object({
|
|
839
|
+
.object({
|
|
840
|
+
id: z.string(),
|
|
841
|
+
label: z.string(),
|
|
842
|
+
locators: z.array(FINDING_LOCATOR).optional(),
|
|
843
|
+
// NAME-2: `count` is ambiguous ACROSS levels on this wire — it
|
|
844
|
+
// means PAGES on the finding and HITS on a page entry. The host
|
|
845
|
+
// therefore emits an unambiguous `hits` alias next to a page
|
|
846
|
+
// entry's `count` whenever that count really is a hit tally.
|
|
847
|
+
// Declared (not merely tolerated via `.catchall`) so the field
|
|
848
|
+
// is part of the contract and `hitCountOf` may prefer it.
|
|
849
|
+
hits: z.number().int().nonnegative().optional(),
|
|
850
|
+
hits_truncated: z.boolean().optional(),
|
|
851
|
+
locators_stripped: z.boolean().optional(),
|
|
852
|
+
builder_url: z.string().nullable().optional(),
|
|
853
|
+
frontend_url: z.string().nullable().optional(),
|
|
854
|
+
})
|
|
561
855
|
.catchall(z.unknown())),
|
|
562
856
|
pages_truncated: z.boolean(),
|
|
563
857
|
// Machine-executable remediation (design §5): absent for findings with
|
|
@@ -569,7 +863,7 @@ export const FINDING = z.object({
|
|
|
569
863
|
note: z.string(),
|
|
570
864
|
})
|
|
571
865
|
.optional(),
|
|
572
|
-
});
|
|
866
|
+
}).catchall(z.unknown());
|
|
573
867
|
/**
|
|
574
868
|
* The advisory block `page_publish` (default on) / `page_save`
|
|
575
869
|
* (opt-in via `quick_check:true`) attach (Task 8). Design §7: `{clean:true}`
|
|
@@ -586,6 +880,35 @@ export const QUICK_CHECK = z.object({
|
|
|
586
880
|
* spread in separately by `builders.ts` (mirrors PAGES_RESOLVE_INPUT).
|
|
587
881
|
*/
|
|
588
882
|
export const PAGES_AUDIT_INPUT = {
|
|
883
|
+
// T-A2 (mega-push plan 2026-07-24-02): opt-in rendered-DOM audit. Default
|
|
884
|
+
// omitted = "builder", byte-identical to the pre-T-A2 sweep (reads the
|
|
885
|
+
// saved layout JSON tree; no `mode`/`render_scan`/`render_errors` keys on
|
|
886
|
+
// the response).
|
|
887
|
+
mode: z
|
|
888
|
+
.enum(['builder', 'rendered'])
|
|
889
|
+
.optional()
|
|
890
|
+
.describe('Audit source. "builder" (default) reads the saved layout tree: fast, but blind to ' +
|
|
891
|
+
'module/widget output and anything a plugin injects only at render time. ' +
|
|
892
|
+
'"rendered" renders each page first (via the SAME engine render_preview uses) and ' +
|
|
893
|
+
'audits the ACTUAL DOM: a curated subset of checks (headings/alt/anchor-text/' +
|
|
894
|
+
'dead-anchor/design; NOT thin-content/duplicate-layout/empty-page, which need the ' +
|
|
895
|
+
'builder tree). This is the MOST valuable mode for the "design" category, since a ' +
|
|
896
|
+
'plugin form (Formidable/ACF) only exists at render time. Findings carry no builder ' +
|
|
897
|
+
'element path in this mode (empty breadcrumb/' +
|
|
898
|
+
'element_type by design, there is no builder node to point at); locate the hit by ' +
|
|
899
|
+
'its content_snippet or the page\'s builder_url/frontend_url instead. A SITE-wide ' +
|
|
900
|
+
'rendered sweep is capped (rendering is expensive); the response\'s render_scan ' +
|
|
901
|
+
'{attempted, rendered, capped} always says so honestly, and render_errors lists any ' +
|
|
902
|
+
'page whose render failed (excluded from findings, never reported as a false clean).'),
|
|
903
|
+
// A-6: scope the whole sweep to ONE page. Omit for the site-wide default.
|
|
904
|
+
// Site-wide checks that compare pages against EACH OTHER (duplicate-layout,
|
|
905
|
+
// title-collision) cannot run on a single page and self-filter out; the
|
|
906
|
+
// response says so in its `note`.
|
|
907
|
+
target: PAGES_TARGET.optional().describe('Audit ONE page instead of the whole site — by template_id, post_id, url, a ' +
|
|
908
|
+
'semantic special ("home", "blog", "404", "search", "category", "tag", ' +
|
|
909
|
+
'"author", "archive") or a title substring. The response then carries ' +
|
|
910
|
+
'scope:"single" + resolved_target (which page was actually audited) and ' +
|
|
911
|
+
'per-hit locators. Cannot be combined with `types`.'),
|
|
589
912
|
types: z
|
|
590
913
|
.array(z.string().min(1))
|
|
591
914
|
.optional()
|
|
@@ -593,12 +916,28 @@ export const PAGES_AUDIT_INPUT = {
|
|
|
593
916
|
'same `type` values yootheme_builder_pages_list rows carry). Default: every ' +
|
|
594
917
|
'scanned template/page is included.'),
|
|
595
918
|
checks: z
|
|
596
|
-
.array(z.enum(['seo', 'structure', 'content', 'links']))
|
|
919
|
+
.array(z.enum(['seo', 'structure', 'content', 'links', 'design']))
|
|
597
920
|
.optional()
|
|
598
|
-
.describe('Run only these check categories (default: all
|
|
921
|
+
.describe('Run only these check categories (default: all five). "seo" = title-collision, ' +
|
|
599
922
|
'non-pretty-url, stale-content. "structure" = empty-page, missing-h1, ' +
|
|
600
|
-
'multiple-h1, no-headings, heading-skip
|
|
601
|
-
'
|
|
923
|
+
'multiple-h1, no-headings, heading-skip, heading-hidden-on-mobile (hidden at ' +
|
|
924
|
+
'the mobile-first UIkit breakpoint; Google indexes mobile-first, so this is ' +
|
|
925
|
+
'effectively invisible for ranking), long-heading. "content" = ' +
|
|
926
|
+
'images-missing-alt, generic-alt-text, thin-content, duplicate-layout. ' +
|
|
927
|
+
'"links" = dead-anchor, anchor-text-quality. "design" = design-missing-uikit-class, ' +
|
|
928
|
+
'design-foreign-class, design-inline-style: markup breaking out of the UIkit ' +
|
|
929
|
+
'design system (e.g. a plugin form submit button with no uk-button); most ' +
|
|
930
|
+
'valuable in mode:"rendered", where a plugin\'s own output actually exists — ' +
|
|
931
|
+
'plus header-contrast-risk (warning): the first section pulls the navigation ' +
|
|
932
|
+
'over itself (header_transparent) in a colour the theme\'s own --uk-inverse ' +
|
|
933
|
+
'declaration contradicts, with no image or video hiding the background that ' +
|
|
934
|
+
'declaration describes; and header-contrast-unverifiable (info): the same ' +
|
|
935
|
+
'transparent header over a hero image/video, whatever the colour — the medium ' +
|
|
936
|
+
'hides that background, so no colour is verifiable from the props, none is ' +
|
|
937
|
+
'recommended, and readability is only verifiable by looking at the rendered ' +
|
|
938
|
+
'page. Both are builder-mode only (section props do not exist ' +
|
|
939
|
+
'in a rendered document). Headings and image alt are also read from raw HTML ' +
|
|
940
|
+
'(html/text element content), not just builder props.'),
|
|
602
941
|
thin_content_threshold: z
|
|
603
942
|
.number()
|
|
604
943
|
.int()
|
|
@@ -613,7 +952,17 @@ export const PAGES_AUDIT_INPUT = {
|
|
|
613
952
|
.optional()
|
|
614
953
|
.describe('Months since a page\'s `modified_at` after which it is flagged `stale-content`. ' +
|
|
615
954
|
'Pages with an empty modified_at are never flagged. Default: 24.'),
|
|
616
|
-
|
|
955
|
+
// PERF-4 / PROD-2: on THIS tool `max_chars` bounds BOTH legs — the shared
|
|
956
|
+
// describe's "structuredContent stays complete" is true everywhere else,
|
|
957
|
+
// but an audit's structured leg was measured at 349 KB (~89k tokens) for a
|
|
958
|
+
// site sweep, so it carries its own default ceiling
|
|
959
|
+
// ({@see STRUCTURED_BUDGET_CHARS}) that this argument raises or lowers.
|
|
960
|
+
max_chars: MAX_CHARS.describe('Optional cap (chars) on this result. Bounds BOTH the text leg and the ' +
|
|
961
|
+
'structuredContent payload (default: 48000 chars for structuredContent). ' +
|
|
962
|
+
'When the structured leg has to shrink, locator detail is shed before ' +
|
|
963
|
+
'evidence pages and the response carries structured_truncated:true. Raise ' +
|
|
964
|
+
'it for a complete pathological page, lower it on a tight context budget.'),
|
|
965
|
+
include_meta: INCLUDE_META_SCHEMA,
|
|
617
966
|
};
|
|
618
967
|
/**
|
|
619
968
|
* Wire shape for `yootheme_builder_pages_audit` (design §5). `scanned.by_type`
|
|
@@ -623,6 +972,34 @@ export const PAGES_AUDIT_INPUT = {
|
|
|
623
972
|
*/
|
|
624
973
|
export const PAGES_AUDIT_OUTPUT_SCHEMA = z.object({
|
|
625
974
|
etag: z.string(),
|
|
975
|
+
// B-5 — the three single-target keys. `.optional()` is LOAD-BEARING, not
|
|
976
|
+
// decoration: this is a strict object, and both a site sweep and every
|
|
977
|
+
// host predating single-target mode omit all three, so a non-optional
|
|
978
|
+
// member would make every such (perfectly valid) response fail validation.
|
|
979
|
+
scope: z.string().optional(),
|
|
980
|
+
note: z.string().optional(),
|
|
981
|
+
// Every member of `PageQuery::descriptorFromRow()` /
|
|
982
|
+
// `augmentResolverDescriptor()` must be declared here — the audit handler
|
|
983
|
+
// forwards `resolved_target` VERBATIM, and this object generates
|
|
984
|
+
// `additionalProperties:false`, so an undeclared key makes a strict host
|
|
985
|
+
// -32602-reject a SUCCESSFUL call (W12-R2). All eight are `.optional()`
|
|
986
|
+
// because a host predating single-target mode sends none of them.
|
|
987
|
+
resolved_target: z
|
|
988
|
+
.object({
|
|
989
|
+
template_id: z.string().optional(),
|
|
990
|
+
post_id: z.union([z.number(), z.string()]).nullable().optional(),
|
|
991
|
+
type: z.string().optional(),
|
|
992
|
+
label: z.string().optional(),
|
|
993
|
+
frontend_url: z.string().nullable().optional(),
|
|
994
|
+
// The matched YT type/condition for a theme template
|
|
995
|
+
// (e.g. "taxonomy-category_faq"); null for a per-page layout.
|
|
996
|
+
context: z.string().nullable().optional(),
|
|
997
|
+
// `per_page` | `theme_template` — kept a plain string rather than an
|
|
998
|
+
// enum so a newer host's additional kind is forwarded, not rejected.
|
|
999
|
+
resolved_via: z.string().optional(),
|
|
1000
|
+
matched_by: z.string().optional(),
|
|
1001
|
+
})
|
|
1002
|
+
.optional(),
|
|
626
1003
|
scanned: z.object({
|
|
627
1004
|
templates: z.number().int().nonnegative(),
|
|
628
1005
|
by_type: z.record(z.string(), z.number().int().nonnegative()),
|
|
@@ -634,5 +1011,36 @@ export const PAGES_AUDIT_OUTPUT_SCHEMA = z.object({
|
|
|
634
1011
|
}),
|
|
635
1012
|
findings: z.array(FINDING),
|
|
636
1013
|
disclaimer: z.string(),
|
|
1014
|
+
// PERF-4 / PROD-2 — set by the TS handler (never by a host) when the shed
|
|
1015
|
+
// ladder had to run to bring `structuredContent` under its char budget.
|
|
1016
|
+
// Its PRESENCE is the whole signal: a complete payload omits the key
|
|
1017
|
+
// entirely, so a reader can never mistake a shrunk audit for a full one.
|
|
1018
|
+
// Which entries lost what is flagged in place — `locators_stripped` on the
|
|
1019
|
+
// page rows whose locator prose went, `pages_truncated` on the findings
|
|
1020
|
+
// whose evidence pages were capped or dropped.
|
|
1021
|
+
structured_truncated: z.boolean().optional(),
|
|
1022
|
+
// T-A2 — present ONLY when `mode:"rendered"` was requested (absent on the
|
|
1023
|
+
// default builder sweep, matching `scope`'s own "absent means the old
|
|
1024
|
+
// behaviour" convention). `render_scan` is ALWAYS present alongside it,
|
|
1025
|
+
// even when the cap never engaged — Design-Nut 4's "never silent" rule:
|
|
1026
|
+
// a caller can always see attempted vs. actually-rendered.
|
|
1027
|
+
mode: z.enum(['rendered']).optional(),
|
|
1028
|
+
render_scan: z
|
|
1029
|
+
.object({
|
|
1030
|
+
attempted: z.number().int().nonnegative(),
|
|
1031
|
+
rendered: z.number().int().nonnegative(),
|
|
1032
|
+
capped: z.boolean(),
|
|
1033
|
+
})
|
|
1034
|
+
.optional(),
|
|
1035
|
+
// Present only when at least one template's render threw — excluded from
|
|
1036
|
+
// findings entirely (a render failure must never masquerade as a content
|
|
1037
|
+
// finding), surfaced here instead so nothing is silently dropped.
|
|
1038
|
+
render_errors: z
|
|
1039
|
+
.array(z.object({
|
|
1040
|
+
id: z.string(),
|
|
1041
|
+
label: z.string(),
|
|
1042
|
+
error: z.string(),
|
|
1043
|
+
}))
|
|
1044
|
+
.optional(),
|
|
637
1045
|
});
|
|
638
1046
|
//# sourceMappingURL=schemas.js.map
|