marketplace/bundles/plan-marshall/skills/platform-runtime/SKILL.md
Platform abstraction layer routing operations to Claude Code or OpenCode implementations
npx skillsauth add cuioss/plan-marshall platform-runtimeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Script-based platform abstraction that routes 24 goal-based operations to the correct target implementation. Follows the tools-integration-ci pattern: one router script, target-specific provider classes, static routing via marshal.json.
Execution mode: Invoke scripts via executor notation; parse TOON output for status and route accordingly.
Prohibited actions:
platform_runtime.pyruntime.target in marshal.jsonref-toon-format parser moduleConstraints:
python3 .plan/execute-script.py plan-marshall:platform-runtime:platform_runtime <operation> [args...]session render-title takes no arguments; all resolution is internalno-op responses are not errors; the calling skill must continuestandards/contract.md for per-operation TOON schemasTwenty-four operations covering the full platform lifecycle:
| Operation | Purpose |
|-----------|---------|
| project initial-setup | One-time project setup: create .plan/, seed marshal.json, install platform hook |
| project install-hook | Install the terminal-title hook bundle; with the orthogonal --enforcement opt-in, install ONLY the PreToolUse enforcement hook entry without touching the terminal-title wiring |
| layout skill-roots | Resolve the ordered project-local skill root directories for the active target |
| layout bundle-cache-root | Resolve the deployed-bundle cache root directories for the active target |
| session capture | Persist current session id via manage-status; no-op on OpenCode |
| permission configure | Write raw permission list to platform settings |
| permission analyze | Read-only audit of permission hygiene, redundancy, and missing-steps |
| permission fix | Apply hygienic fixes: normalize, add, remove, ensure, consolidate |
| permission ensure-wildcards | Add marketplace bundle wildcard permissions |
| permission ensure-steps | Add missing skill permissions for marshal.json phase steps |
| permission web-analyze | Read-only analysis of WebFetch/webfetch domain permissions |
| permission web-apply | Add or remove web domain permissions |
| session render-title | Emit OSC title sequence from writer artifact; no-op on OpenCode |
| session push-title-token | Parse the store selector (--plan-id for the plan store, or --store orchestrator --slug {slug} for the orchestrator store) plus optional --icon, then bind and settle the title state for the next render event to deliver (Claude); no-op on OpenCode. This seam binds and persists — it does not repaint: the hook-written terminalSequence envelope is the sole delivery channel and is event-driven, so delivery is deferred to the next render. Shared by the manage-status phase-state-write drive seam, the lock/build state writers, and the marshall-orchestrator per-verb call, whose bind_orchestrator side effect is the load-bearing reason the seam exists. Two no-op reasons: no_title_state and feature_inactive; the return carries no pushed and no delivery field |
| session bind | Bind the running session to --plan-id (last-driven-wins) so render-title / resolve-plan resolve it; no-op on OpenCode |
| session resolve-plan | Read the running session's bound plan id (the read side of session bind); no-op on OpenCode |
| session doctor | Visit every directory under the session-cache root, report plan-bound-by-multiple-sessions conflicts, stale slots, and orphan directories that yield no live slot, and (with --fix) GC the stale slots plus prune the orphan directories; no-op on OpenCode |
| session teardown | Activation-gated end-of-session retire: drop the session's binding. Releasing the binding is the whole of the teardown — no title reset is written, because a reset can only be delivered on the render channel. Fired only by SessionStart:clear, the sole release point; the archive path deliberately does not call it. A project with no terminal-title wiring reports active: false / reason: feature_inactive and is left untouched. No arguments; no-op on OpenCode |
| session reload-directive | Resolve + surface the harness-appropriate post-upgrade reload directive (Claude: /reload-plugins plus the monitor caveat); no-op (full-restart alternative) on OpenCode. RESOLVES + SURFACES only — a script cannot type a harness slash command |
| metrics capture | Record token consumption for a planning phase |
| metrics normalized-tokens | Resolve normalized transcript token totals for the active target |
| subagent dispatch | Return platform-specific subagent invocation parameters |
| wait for | Hold a bounded wait until a concrete, pollable observable (--observable names a kind from a closed set; build-job today) reaches a terminal state, and return a normalized succeeded/failed/timed_out/killed/pending outcome. The observable is never an opaque condition descriptor — a runtime subprocess cannot evaluate one. Bound exhaustion yields outcome: pending with terminal: false, never an implicit pass; no-op on OpenCode, whose runtime holds no wait channel |
| health-check | Verify platform integration |
See standards/contract.md for per-operation TOON schemas (success, error, no-op paths).
The health-check --checks display surface inspects each terminal-title render entry plus a dedicated PreToolUse:enforcement present/MISSING label for the orthogonal enforcement hook, so a partial or absent enforcement install is diagnosable and repairable independently of the terminal-title wiring.
Static Routing Pattern: marshal.json stores runtime.target; router dispatches to target class.
marshal.json Scripts
runtime.target: claude ──────────────────► claude_runtime.py
runtime.target: opencode ─────────────────► opencode_runtime.py
Router (platform_runtime.py) reads runtime.target, looks up target class in registry, and dispatches. Registry is extensible — adding a new target adds a class and a registry entry.
Standard invocation (post-bootstrap):
python3 .plan/execute-script.py plan-marshall:platform-runtime:platform_runtime <operation> [args...]
Bootstrap invocation (Steps 1–3, before executor exists):
During marshall-steward Steps 1–3 the executor does not yet exist. Use the glob-path bootstrap directly. After Step 4 (Generate Executor), switch to executor notation for all subsequent calls.
Every operation returns:
status: success | error | no-op
operation: <name>
result: <any> (success only)
error: <string> (error only)
message: <string> (error only)
reason: <string> (no-op only)
alternative: <string> (no-op only)
Full per-operation schemas: standards/contract.md
No-op policy and caller obligations: standards/no-op-policy.md
| Code | Meaning |
|------|---------|
| invalid_scope | Scope argument not project or global |
| invalid_check | permission analyze --checks contains unknown check name |
| marshal_not_found | .plan/marshal.json missing |
| prompt_not_found | subagent dispatch prompt file not found |
| unknown_target | runtime.target not in registry |
| hook_not_configured | SessionStart hook missing; $CLAUDE_CODE_SESSION_ID unset |
| invalid_settings | Settings file malformed (JSON parse error); fail-closed before write — permission configure/fix/ensure-wildcards/ensure-steps/web-apply |
| invalid_marshal | .plan/marshal.json malformed (parse error); fail-closed instead of zero-step audit — permission analyze/ensure-steps |
| unsupported_observable | wait for --observable names a kind outside the closed set |
| invalid_bound | wait for --bound-seconds is not positive |
| unknown_reference | wait for --reference names no instance of the observable kind |
| observable_unreachable | The observable's inspection channel could not be reached; no outcome is implied |
| unexpected_observable_status | The observable reported an out-of-vocabulary status; no outcome is inferred |
When a target returns no-op:
status is no-op, not errorreason explains why the operation is not supportedalternative suggests what the caller can do insteadAll scripts comply with:
tools-script-executor — executor notation, standardized error format, environment variables (PLAN_DIR_NAME, PM_MARKETPLACE_ROOT), exit codes (0 success, 1 invalid params, 2 runtime error)ref-toon-format — TOON generated and parsed via toon_parser.py from the ref-toon-format skill; no ad-hoc parsingPlatform-runtime operations satisfy: "Would this differ between Claude Code and OpenCode?"
| In scope | Belongs elsewhere |
|----------|-------------------|
| Platform hooks, session IDs | manage-status (plan state) |
| Settings/permissions files | tools-integration-ci (CI/PR) |
| Terminal title rendering | manage-architecture (architecture data) |
| Platform-specific subagent invocation | manage-metrics (metrics storage) |
| Platform health verification | tools-script-executor (executor regeneration) |
| Post-upgrade reload directive (harness-appropriate) | marshall-steward (upgrade flow that consumes the directive) |
The session render-title and session push-title-token operations are the
resolve + emit layer of the terminal-title three-way split. They resolve
session → plan, read the title state from status.json (live first, archived
fallback), call the pure manage-terminal-title composer, and — on the render
path — emit per platform (OSC / statusLine / web sessionTitle). status.json is
the single source of persisted title state — there is no title-body.txt
artifact. See manage-terminal-title/standards/terminal-title-architecture.md for
the canonical end-to-end architecture: state (manage-status), composer
(manage-terminal-title), resolve+emit (platform-runtime), session-plan
binding, output channels, platform abstraction, and the glyph + icon vocabulary.
A conditional PreToolUse enforcement hook deterministically blocks four mechanically-checkable hard-rule violation families, but ONLY when the call originates inside a plan-marshall plan context — failing open everywhere else. It is implemented by three sibling scripts:
pretooluse_gate.py — the shared, pure-function module that is the SINGLE
home of the PreToolUse payload-field knowledge and the Signal1 OR Signal2
fail-open context-gate predicate. Imported by both leaves below; owns no rule
matchers.claude_pretooluse_capture.py — the observe-only leaf that validates the
shared gate's field names against real payloads before enforcement is armed.claude_pretooluse_hook.py — the enforcement leaf that imports the shared
gate and adds only the four rule families plus the permissionDecision: deny
envelope.The enforcement hook is installed on demand via the orthogonal
project install-hook --enforcement path (independent of the terminal-title
bundle), surfaces a dedicated PreToolUse:enforcement present/MISSING label on
the health-check --checks display diagnostic, and is registered through the
marshall-steward Configuration → Enforcement Hook menu
(../marshall-steward/references/menu-enforcement-hook.md).
The context gate, the four rule families with their redirect reasons, the
fail-open / best-effort-no-raise contract, and the capture-validates-the-gate
dependency chain are documented in
standards/pretooluse-enforcement.md —
the canonical reference; the rule list is not restated here.
development
Domain-owned OpenRewrite log-line finding parser for the java-cui domain — parses the
development
Domain-owned OpenRewrite marker detection for the java-cui domain — scans Java/Kotlin sources for cui-rewrite TODO markers, categorizes them by recipe, and fails the gate on any detected marker
development
Operator control surface for the marshalld build server — enrol/drop a project in the machine-global registry (the opt-in enable signal and anti-laundering wall), manage the daemon lifecycle (start, stop, drain, status, install, upgrade) version-pinned to the verified bundle copy, and inspect the daemon's per-project interaction-audit log (read-only)
tools
The tiny build-consumption client for the marshalld build server — submit a build job, bounded long-poll for its result, ping the daemon identity, and preflight registry-plus-liveness in one call; consumption only, never provisioning or enrolment