marketplace/bundles/plan-marshall/skills/manage-build-server/SKILL.md
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)
npx skillsauth add cuioss/plan-marshall manage-build-serverInstall 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.
The operator's control surface for marshalld, the machine-global plan-marshall
build server. This skill is script-deterministic — every verb is a deterministic
executor script call, no LLM judgement. It owns two responsibilities: project
enrolment (the opt-in registry) and the daemon lifecycle (start/stop/drain/
status/install/upgrade). Build consumption (submit/wait/ping/preflight) lives in
the separate build-server-client skill — this skill never submits work.
marshalld is strictly opt-in: registration IS the enable signal. There is no
config knob and nothing git-tracked. A project is served by the daemon only after
an operator runs register here; an unregistered project's builds never touch the
daemon or its socket and behave byte-identically to a machine with no build server.
Base contract: See manage-contract.md for shared enforcement rules, TOON output format, and error-response patterns.
Execution mode: Run the control verbs via the executor; parse the TOON output for status / running and route accordingly.
Prohibited actions:
registry.json, the daemon socket, or the pidfile directly — every mutation goes through the script API so the registry's atomic write + audit-line invariant holds.marshall-steward carries a read-only status pointer only.Constraints:
skills[] (see the anti-laundering wall below).register / unregister mutate only registry.json (plus its audit line) — never source, never .plan/ plan state.register and unregister are the operator-interactivity wall for the build
server. Registration is a deliberate, human-driven enrolment action — so it lives
ONLY in this user-invocable control skill and is NEVER reachable from a dispatched
agent's skills[]. A plan cannot enrol itself onto the served set, and the daemon
never resolves what to run: it verifies every submit positionally against the
project's existing registration (interpreter, executor path inside the verified
tree, notation allowlist, argument schema) and refuses anything off-template. The
control surface (enrolment) and the consumption surface (build-server-client
submit/wait) are deliberately split across two skills so enrolment can never be
laundered through a build dispatch.
register populates each project's scope fields so a plain enrolment yields a
routable project rather than an inert empty-scope entry. When --container /
--notation are omitted, registration stores canonical defaults:
notation_allowlist — the routable build notations (Maven, Gradle, npm,
Python), derived from the single source of truth shared with the daemon's
build-routing seam, so a build tool that is routable is default-allowlisted
from the same edit.worktree_containers — the canonical worktree location every plan uses,
<root>/.plan/local/worktrees.Re-running register is the repair path for an already-registered project
whose scope is empty: it backfills the missing defaults without hand-editing
registry.json. Per-field precedence is explicit CLI value > existing non-empty
stored value > computed default, so re-registration is idempotent — it backfills
empty fields, preserves any deliberately-customised non-empty values, and lets an
explicit --container / --notation override both the stored value and the
default.
marshalld requires a POSIX runtime (Unix domain sockets, fork/setsid
double-forking, ppid==1 re-parenting). Supported platforms are macOS and Linux;
on Windows, plan-marshall runs exclusively inside WSL2 with the entire runtime
in-distro. One distro is one machine: each distro has its own ~/.plan-marshall/,
registry, and daemon, and wsl --shutdown / reboot / idle timeout stops the
daemon (a down status is routine on Windows — the init preflight re-asks). The
full statement lives in doc/user/installation.adoc § Prerequisites — see there,
not duplicated here.
All daemon state lives under the machine-global home root
(~/.plan-marshall/marshalld/, overridable via PLAN_MARSHALL_HOME), created
0700:
| Path | Contents |
|------|----------|
| socket | Unix domain socket (0600, owner-only) |
| daemon.pid | Running daemon pid |
| daemon.log | Daemon log (rotated to daemon.log.1 past a size cap) |
| registry.json | Machine-global project registry (0600) |
| registry-audit.log | Append-only registration audit |
| lifecycle-audit.log | Append-only start/stop/drain/install/upgrade audit |
| interaction-audit.log | Append-only per-request interaction audit (0600) |
| journal/ | Durable job specs, results, and ETA history |
| job-logs/ | Per-job captured build logs |
The interaction audit is a central append-only log — the natural third
sibling of registry-audit.log and lifecycle-audit.log — into which the daemon
writes exactly one attributed record for every request it dispatches
(ping / submit / wait). Each record carries per-project attribution
(project_root + plan_id + job_id) plus op / outcome / timestamp, and
never any secret-bearing spec field. Retention is bounded and GC'd on every daemon
start, parallel to the journal's bounded-retention model.
marshalld co-located with this control skill (the verified bundle / plugin-cache
version, never a project-local executor an attacker could tamper with — S5).
Refuses to launch a second daemon when one is already live (idempotent).SIGTERM, then escalate to SIGKILL after a grace
window, then remove the socket and pidfile. Use stop when the daemon is wedged.SIGTERM) and wait for the
daemon to exit on its own, never escalating to SIGKILL. A job still in flight is
recorded in the journal and replayed as killed on the next start — never
silently lost, never blind-resumed. Prefer drain for planned restarts.down with a named reason. Also reports whether the caller's
project is registered.Crash recovery. A crashed daemon leaves a stale socket and pidfile; the next
start liveness-probes the recorded pid and, finding it dead, cleans the stale
state and binds fresh. A daemon restart replays the journal: terminal results
survive, and any job that was in flight when the daemon died is marked killed
(never silently resumed). Log rotation is automatic — daemon.log rotates to
daemon.log.1 once it passes its size cap, so daemon logging never grows unbounded.
Script: plan-marshall:manage-build-server:manage_build_server
| Verb | Purpose |
|------|---------|
| register | Enrol a project in the machine-global registry (the enable signal) |
| unregister | Drop a project from the registry |
| start | Start the daemon detached, version-pinned |
| stop | Force-stop the daemon (SIGTERM then SIGKILL) |
| drain | Gracefully stop the daemon (no SIGKILL) |
| status | Report running version + binary path |
| install | Idempotent version-pinned start |
| upgrade | Drain then start the verified version |
| logs | Read-only, project-scoped view of the daemon's interaction-audit log |
Script: plan-marshall:manage-build-server:marshalld — the daemon binary,
launched by start (never invoked directly by an operator).
The canonical argparse surface for manage_build_server.py. The plugin-doctor
analyzer reads this section as source-of-truth for markdown notation occurrences.
python3 .plan/execute-script.py plan-marshall:manage-build-server:manage_build_server register \
[--root ROOT] [--container DIR] [--notation NOTATION]
--container and --notation are repeatable. --root defaults to the caller's
main checkout. When --container / --notation are omitted, registration
populates canonical default scope and re-running register backfills an empty
existing entry — see Default registration scope above.
python3 .plan/execute-script.py plan-marshall:manage-build-server:manage_build_server unregister \
[--root ROOT]
python3 .plan/execute-script.py plan-marshall:manage-build-server:manage_build_server start
python3 .plan/execute-script.py plan-marshall:manage-build-server:manage_build_server stop
python3 .plan/execute-script.py plan-marshall:manage-build-server:manage_build_server drain
python3 .plan/execute-script.py plan-marshall:manage-build-server:manage_build_server status
python3 .plan/execute-script.py plan-marshall:manage-build-server:manage_build_server install
python3 .plan/execute-script.py plan-marshall:manage-build-server:manage_build_server upgrade
python3 .plan/execute-script.py plan-marshall:manage-build-server:manage_build_server logs \
[--root ROOT] [--limit LIMIT]
Read-only inspection of the daemon's central interaction-audit.log, filtered to
the caller project (the derived project-scoped view) — it never mutates the log.
--root defaults to the caller's main checkout; --limit returns the N most
recent records (default 50), ordered oldest-first within the returned window
(so records[0] is the oldest of the window, not the newest). When the log is
absent or unreadable the verb returns an explicit empty records list with a
named reason (log_absent / log_unreadable; fails closed).
build-server-client — the build-consumption surface (submit/wait/ping/preflight); this skill never submits work.manage-locks — the machine-global build-queue slot substrate the daemon's scheduler coordinates against.marshall-steward — carries a read-only daemon-status pointer into this skill; no daemon logic lives there.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
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
testing
Epic-orchestration persona — the work identity for decomposing epics into workstreams and plans, tracking plan lifecycles, analyzing landings, and reconciling the persisted orchestrator ledger; orchestrates, never implements