DiviOps harness primer
Cross-cutting conventions every DiviOps target-coverage skill (divi-5-builder, diviops-scf, diviops-fluentcart, future slices) relies on. Read this when you're about to issue a diviops_* MCP call and want to know what the success / failure envelope looks like, how to preview a write, or which capability gate decides whether a tool is even registered for this session.
This skill carries no tool-surface documentation — tools live with the coverage slice that owns them.
Response envelope
Every diviops_* tool returns the same envelope:
// Success
{ "ok": true, "data": <payload> }
// Failure
{ "ok": false, "error": { "code": "<code>", "message": "<human>", "hint": "<optional>", "data": <optional structured detail> } }
The envelope is uniform across every namespace (page_*, module_*, preset_*, variable_*, global_color_*, global_font_*, tb_*, template_*, library_*, canvas_*, meta_*, schema_*, validate_*, render_*, scf_*, section_*). Callers branch on error.code for failure paths; the error.data field carries structured detail when the failure mode needs more than a code + message.
Standard error codes
not_found 404 Target ID does not resolve
invalid_input 400 Schema violation, malformed args
validation_failed 400 validate_blocks-detected shape error
conflict 409 Uniqueness collision (delete-with-references, default-preset delete, name collision, …)
capability_missing 412 Plugin version below required for this tool
forbidden 403 Row-level WordPress permission denied
wp_error 500 Underlying WordPress error
divi_error 500 Divi-specific error (block parser, validator, …)
capability_missing is the handshake-layer signal — the plugin on this site doesn't carry the capability flag the tool requires. Distinct from forbidden (the WP user lacks the capability on this row).
Namespace-prefixed error codes
The convention is <namespace>.<reason> for codes that are scoped to a specific namespace and carry namespace-specific structured detail. Two flavors:
- Gate codes —
<ns>.not_configured,<ns>.capability_missing. Pre-execution rejection by env-var configuration or handshake state. - Runtime codes —
<ns>.command_failed,<ns>.bucket_mismatch,<ns>.fluid_generation_failed,<ns>.customizer_default_immutable, etc. Failures that happen once the tool has begun executing.
The split matters because gate failures are environment problems (config missing, capability not advertised) — the user fixes them site-side. Runtime failures are operation problems (wp-cli exited non-zero, input shape was syntactically valid but algorithmically impossible, target row exists but is protected) — the user fixes them per-call.
Codifying <ns>.not_configured (gate) + <ns>.command_failed (runtime) as distinct codes — rather than collapsing onto wp_error or capability_missing — is the discipline. Callers branch on whether they need to surface a setup hint or a per-call recovery hint.
Capability handshake
On MCP session start the server pings the plugin and receives a handshake payload that names which target plugins are present, which modules the user has activated, and which per-tool capabilities the plugin advertises. The server uses this to (a) gate tool registration so unsupported tools don't appear at all, and (b) gate coverage-slice skill activation so the right slice (Divi page authoring vs SCF vs FluentCart vs …) routes.
Three layers must align for a coverage slice's tools to be live in the session:
- Target presence — the target plugin (Divi 5, SCF, FluentCart, …) is installed on the WP site. Detected via
class_exists()or similar; reflected in the handshake'savailable_targets. - Module activation — the user enabled the target's module in WP admin's Modules settings page. Reflected in the handshake's
active_modules. - Project preference — the user has not opted out of this slice for the current project on their machine. Persisted client-side; storage shape resolves during Phase B implementation.
If any layer is false, the slice declines activation even when the user's prompt matches its description. The harness primer skill (this one) does NOT have layer 1 or 2 gating — the primer's contract content is cross-cutting and load-bearing for every coverage slice's correctness, so it activates whenever any DiviOps task is detected.
The handshake does NOT carry license state. License is implicit: if the skill files are on disk, the user got them through the licensed distribution channel; if the Pro plugin is installed and active, the user got it through the licensed download.
dry_run plan shape
Every mutating tool accepts an optional dry_run: boolean (default false). Read tools don't — they're already side-effect-free.
When dry_run: true, the tool does not mutate state. The success envelope carries a uniform plan:
{
"ok": true,
"data": {
"dry_run": true,
"plan": {
"summary": "<one-line human description>",
"changes": [
{ "kind": "<namespace>.<verb>", "target": "<resource identifier>", "before": <value|null>, "after": <value|null> }
],
"warnings": [ "<optional caveat>" ]
}
}
}
Apply mode (dry_run omitted or false) keeps each tool's namespace-specific success-payload shape unchanged.
Exceptions — two passthrough surfaces do not accept dry_run:
diviops_meta_wp_cli— raw passthrough; use explicit read-only commands instead.diviops_scf_import— SCF's upstreamwp scf json importlacks a--dry-runflag; usediviops_scf_sync --dry_runfor SCF-on-disk previews.
diviops_scf_sync flows dry_run through to wp-cli's --dry-run flag — the resulting preview is wp-cli's plain-text summary, NOT the standardized data.plan = { summary, changes[] } shape above. This divergence is by design; plan-shape standardization for wp-cli passthroughs is tracked separately.
Legacy plans on preset_cleanup, preset_reassign, preset_delete, preset_set_default, page_trash, page_update_status, tb_template_trash, canvas_duplicate, and the module_* / section_* mutating tools keep their route-specific summary shape inside the envelope's data slot — they're inside the envelope but not yet conformant to the unified plan shape above. Per-tool descriptions document the route-specific shape.
Idempotency conventions
A tool is idempotent when running it twice produces the same observable state as running it once. DiviOps tools document idempotency per-tool; the conventions:
_meta.idempotent: trueon the success payload signals a write whose repeat call would be a no-op (or a side-effect-equivalent re-apply). Documented per-tool in the tool's MCP description.annotations.idempotentHint: trueon the tool registration is the lighter signal — declares the tool is intended idempotent without machine-checked guarantees. Useful for callers that want to retry safely without re-confirming.- Destructive ops on already-destructed targets return
ok: truewithdata.already_<state>: true, NOT409 conflict.page_trashon an already-trashed page returns{ ok: true, data: { already_trashed: true } };tb_template_trashdefault mode is similarly silent-success on already-clean. The signal is preserved in thealready_<state>flag for callers that need it. Repeat-safe semantics matter for AI agent retries. - Same-status no-op updates return
ok: truewithdata.noop: true.page_update_statusagainst a post already in the target status, for example. force=trueoverride — for*_deletetools whose default mode refuses on live-reference conflicts (variable_delete,global_color_delete,global_font_delete,preset_delete), passingforce=trueclears the conflict and proceeds. Orphan refs remain; run the corresponding `