Project configuration wizard for planning system. Manages executor generation, health checks, executor/config staleness signaling, build systems, skill domains, and the upgrade verb for one-flow post-change reconciliation.
Resources
3Install
npx skillscat add cuioss/plan-marshall/marshall-steward Install via the SkillsCat registry.
Marshall Steward Skill
Project configuration wizard for the planning system.
Usage
/marshall-steward # Interactive menu or first-run wizard
/marshall-steward --wizard # Force first-run wizard
/marshall-steward upgrade # Post-change reconciliation — asks before each stage
/marshall-steward upgrade integrate=true # Post-change reconciliation — runs all four stages end-to-endBanner
At command start, emit the following banner verbatim to the user:
[ MARSHALL STEWARD ]
configure · verify · maintainEnforcement
Execution mode: Run scripts exactly as documented; return to Main Menu after each operation.
Prohibited actions:
- Do not invent alternative menu structures or options
- Do not end without returning to menu (unless Quit)
- Do not summarize what you are about to do instead of doing it
- Do not improvise script execution; run exactly as documented
Constraints:
- Bootstrap scripts use direct Python paths with glob
- All other scripts use
python3 .plan/execute-script.py {notation} ... - After any operation completes, return to Main Menu
- Only exit when user selects "Quit"
What This Skill Provides
Wizard Mode: Sequential setup for new projects (executor generation, marshal.json init, build detection, skill domains)
Menu Mode: Interactive maintenance for returning users (regenerate executor, health check, configuration)
Scripts
Own Scripts (bootstrap-capable, run before executor exists)
| Script | Notation | Purpose |
|---|---|---|
| determine_mode | plan-marshall:marshall-steward:determine_mode |
Determine wizard vs menu mode; also exposes check-working-prefixes (project.working_prefixes presence/drift) and check-staleness (health-menu executor/config staleness preflight) |
| gitignore_setup | plan-marshall:marshall-steward:gitignore_setup |
Configure .gitignore for .plan/ |
| upgrade | plan-marshall:marshall-steward:upgrade |
Emit the four-stage upgrade verb plan (pure function of (integrate, project_kind)); also exposes migrate-bot-lists, the idempotent one-shot auto-map of the retired enabled_bots knob onto required_bots / optional_bots driven as the Stage-2 migrate-bot-lists sub-step |
| cache_freshness | plan-marshall:marshall-steward:cache_freshness |
Fail-closed three-valued plugin-cache freshness verdict (fresh|stale|unknown) driving the consumer Stage-1 cache-freshness-check sub-step |
| cache_retention | plan-marshall:marshall-steward:cache_retention |
Union-keep plugin-cache retention sweep (dry run unless --apply) driving the Stage-1 cache-retention-sweep sub-step behind the cache-retention-prune nested gate |
| bootstrap_plugin | (direct Python call) | Detect plugin root, cache in .plan/local/marshall-state.toon |
Delegated Scripts (require executor)
| Script | Notation | Purpose |
|---|---|---|
| generate-executor | plan-marshall:tools-script-executor:generate_executor |
Executor generation. Both surfaces (wizard Step 4 and maintenance "Regenerate Executor") detect whether they are running inside a git worktree (path under .plan/local/worktrees/) and, when so, pass --marketplace-root <worktree-absolute-path> so the generated executor's script mappings resolve against the worktree's marketplace/bundles/ instead of the main checkout or the plugin cache. |
| manage-config | plan-marshall:manage-config:manage-config |
Project-level marshal.json CRUD |
| run_config | plan-marshall:manage-run-config:run_config |
Clean temp, logs, archived-plans, memory |
| ci_health | plan-marshall:tools-integration-ci:ci_health |
CI provider detection |
| permission_doctor | plan-marshall:tools-permission-doctor:permission_doctor |
Permission analysis |
| permission_fix | plan-marshall:tools-permission-fix:permission_fix |
Permission fixes |
| extension_discovery | plan-marshall:extension-api:extension_discovery |
Extension config defaults |
| credentials | plan-marshall:manage-providers:credentials |
External tool provider management |
Prerequisites
The /marshall-steward command must locate bootstrap_plugin.py and detect the plugin root before loading this skill. bootstrap_plugin.py is the single deterministic resolver for every other bootstrap script path — locate it once, then route all post-get-root path lookups through its resolve verb instead of hand-globbing each script.
Locate
bootstrap_plugin.py(the one unavoidable glob). Resolve its path with theGlobtool against the layout-agnostic pattern**/marshall-steward/scripts/bootstrap_plugin.pyand capture the first match as${BOOTSTRAP}. The recursive**prefix matches both deploy layouts — the flattarget/claude/plan-marshall/skills/…tree and the versioned cache…/plan-marshall/{version}/skills/…tree — without a hand-placed*version level.Detect the plugin root and cache it:
python3 "${BOOTSTRAP}" get-rootRead
plugin_rootfrom the TOON output and set${PLUGIN_ROOT}to it. The plugin root is cached in.plan/local/marshall-state.toonfor subsequent calls.Resolve every other bootstrap script path through
bootstrap_plugin resolve— version-aware, layout-agnostic. For any scriptX.pyunder a bundle, run:python3 "${BOOTSTRAP}" resolve --bundle plan-marshall --path skills/{skill}/scripts/X.pyand read
resolved_pathfrom the TOON. Never hand-glob a${PLUGIN_ROOT}/plan-marshall/*/skills/…pattern to find a post-get-rootscript —resolvealready iterates the version dirs deterministically.
Step 1: Determine Mode
Determine whether to run wizard or menu based on existing files.
BOOTSTRAP: Since execute-script.py may not exist yet, use a DIRECT Python call. Resolve the script path deterministically via bootstrap_plugin resolve (${BOOTSTRAP} was located in Prerequisites) and read resolved_path from the TOON as {DETERMINE_MODE}:
python3 "${BOOTSTRAP}" resolve --bundle plan-marshall --path skills/marshall-steward/scripts/determine_mode.pyThen invoke the resolved script directly:
python3 "{DETERMINE_MODE}" modeOutput (TOON):
mode wizard
reason executor_missingMode Routing
| mode | reason | Action |
|---|---|---|
wizard |
executor_missing |
Load: Read references/wizard-flow.md → Execute wizard |
wizard |
marshal_missing |
Load: Read references/wizard-flow.md → Execute wizard |
menu |
both_exist |
Show Main Menu below |
Check for --wizard Flag
If --wizard flag provided, force wizard regardless of determine_mode result:
Read references/wizard-flow.mdExecute the wizard flow from that file.
Check for upgrade Verb
If the invocation carries the upgrade verb argument (optionally withintegrate=true), bypass both the mode routing and the Main Menu entirely and
run the upgrade flow directly:
Read references/upgrade-flow.mdExecute the upgrade flow from that file, passing the integrate value
(true when integrate=true was given, otherwise false). Follow that
reference's end-of-flow behavior when it completes.
Interactive Menu (Returning User)
Display menu when both executor and marshal.json exist.
Main Menu
The Main Menu has 6 options, which exceeds the AskUserQuestion 4-option cap. It is presented as a paginated menu following the "More actions..." pattern documented in plan-marshall/workflow/planning.md (§ Action: list): each page presents at most 4 options, and every non-final page reserves its 4th slot for a "More..." continuation that triggers the next page's AskUserQuestion.
Page 1 — first 3 options plus the "More..." continuation:
AskUserQuestion:
question: "What would you like to do?"
header: "Main Menu"
options:
- label: "1. Maintenance"
description: "Regenerate executor, clean logs"
- label: "2. Health Check"
description: "Verify setup, diagnose issues"
- label: "3. Configuration"
description: "Build systems, skill domains"
- label: "More..."
description: "Show remaining Main Menu options"
multiSelect: falsePage 2 — shown only when the user selects "More..." on Page 1 — the remaining options:
AskUserQuestion:
question: "What would you like to do?"
header: "Main Menu (continued)"
options:
- label: "4. Effort"
description: "Configure per-role model levels (variant routing)"
- label: "5. Upgrade"
description: "Post-change reconciliation: regenerate, reconcile, verify, land"
- label: "6. Quit"
description: "Exit plan-marshall"
multiSelect: falseMenu Routing
| User Selection | Action |
|---|---|
| "1. Maintenance" | Load: Read references/menu-maintenance.md → Execute |
| "2. Health Check" | Load: Read references/menu-healthcheck.md → Execute |
| "3. Configuration" | Load: Read references/menu-configuration.md → Execute |
| "More..." | Present Main Menu Page 2 AskUserQuestion |
| "4. Effort" | Load: Read standards/effort-menu.md → Execute |
| "5. Upgrade" | Load: Read references/upgrade-flow.md → Execute |
| "6. Quit" | Output "Good bye!" → STOP |
After any menu option completes, return to Main Menu Page 1 (except Quit).
Deferred Loading Pattern
This skill uses progressive disclosure to minimize context usage:
- Core skill loads: ~150 lines (this file - routing logic only)
- On wizard mode: Load
references/wizard-flow.md(~250 lines) - On menu selection: Load only the selected reference (~100-150 lines)
How to Load a Reference
When routing indicates to load a reference:
Read references/{file}.mdThen execute the workflow described in that file. Each reference file is loaded in full when its menu path is chosen — only one reference is active at a time.
Available References
| Reference | Purpose | Load When |
|---|---|---|
wizard-flow.md |
First-run wizard steps 1-15 (bootstrap 1-4, configuration 5 onwards) | mode=wizard or --wizard flag |
provider-setup.md |
Provider discovery/activation, CI detection, credential setup (extracted from wizard-flow.md) | Linked from wizard-flow.md (provider/CI/credential steps) |
architecture-setup.md |
Extension defaults, module discovery, build commands, Maven profiles, LLM analysis + architecture_refresh tier knobs (extracted from wizard-flow.md) | Linked from wizard-flow.md Step 8 |
build-map-setup.md |
Build-map seed/read workflow — build.map file-to-build contract, write-once seed, menu re-seed operation |
Linked from wizard-flow.md Step 8b and menu-configuration.md (Project Structure) |
skill-domains-setup.md |
Skill-domain configuration, profile activation, execute-task/recipe registration (extracted from wizard-flow.md) | Linked from wizard-flow.md Step 9 |
menu-maintenance.md |
Regenerate executor, cleanup. The cleanup operation routes to the Action: cleanup workflow — see `../plan-marshall/workflow/planning.md` § "Action: cleanup", the sole authority for what that pass does. |
Menu option 1 |
menu-healthcheck.md |
Verify setup, diagnose issues | Menu option 2 |
menu-configuration.md |
Build systems, skill domains, architecture refresh tier knobs | Menu option 3 |
standards/effort-menu.md |
Per-phase effort configuration (Effort submenu) | Menu option 4 |
menu-recipes.md |
Built-in recipes available in the wizard | Linked from menu-configuration.md |
menu-terminal-title.md |
Two-action sub-menu: install render-hook wiring; override active-plan for the current session | Linked from menu-configuration.md (Terminal Title) |
menu-enforcement-hook.md |
Detect→confirm→install sub-menu for the conditional PreToolUse enforcement hook (orthogonal --enforcement install) |
Linked from menu-configuration.md (Enforcement Hook) |
merge-queue-setup.md |
Idempotent probe→ask→configure provisioning of the platform merge queue (GitHub merge queue / GitLab merge train) via the ci repo merge-queue verbs |
Linked from wizard-flow.md Step 13.5 and menu-configuration.md (Merge Queue) |
landing-cycle.md |
End-of-run landing cycle: detect uncommitted plan-marshall artifact diff → offer to commit → push → skip-bot-review-labelled plan-less PR → merge-queue-aware merge → switch-to-main → pull; base-branch-conditional branch selection + bot skip-label honoring matrix |
Linked from the "End-of-Run Landing Cycle" hook (menu-mode Quit path + wizard-flow.md end) |
upgrade-flow.md |
Post-change upgrade verb: four-stage reconciliation driven by the project-kind-aware upgrade.py plan stage plan (the meta/consumer stage matrix — meta regenerates the target tree + executor and verifies with preflight + content-drift; consumer regenerates the executor only and verifies with preflight only), honoring each stage's per-stage gate dispositions and sub_steps |
Main Menu option 5, or the upgrade early verb check |
error-handling.md |
Error types and recovery | On error conditions |
Build Server Status (read-only pointer)
The Health Check may surface the machine-global marshalld build server's status
by running manage-build-server status and reporting the returned running /version / registered fields to the operator:
python3 .plan/execute-script.py plan-marshall:manage-build-server:manage_build_server statusThis is a read-only pointer only. Steward carries NO daemon lifecycle logic —
enrolment (register / unregister) and control (start / stop / drain /install / upgrade) live exclusively in the user-invocable manage-build-server
control skill. When the operator wants to start, stop, or enrol, direct them to/manage-build-server; steward never mutates registry or daemon state.
Phase 6 Finalize Step Seeding
The wizard seeds phase-6-finalize.steps in marshal.json from the
default-on built-in finalize-step set discovered viaextension_discovery.find_implementors — the SOLE finalize-step
discovery path. Membership, execution order, and default-seed inclusion
are declared in each step doc's frontmatter (implements: ...ext-point-finalize-step,order, default_on: true), NOT a hand-maintained constant list; see
`extension-api/standards/ext-point-finalize-step.md`.
The seed intentionally covers only steps that are sensible defaults for
any plan-marshall consumer (pre-push-quality-gate, finalize-step-simplify,
finalize-step-security-audit, push, create-pr, ci-verify, automated-review,
sonar-roundtrip, lessons-capture, finalize-step-preference-emitter, branch-cleanup,
record-metrics, finalize-step-print-phase-breakdown, archive-plan), ordered
by their declared order. pre-push-quality-gate is a built-in default
like the rest; its activation is derived from build.map — it activates
whenever the live footprint touches a glob registered in the build_map.
Those globs are tree-derived from each extension's classify_globs()
vocabulary (complete-by-construction over the real tree), not author-shipped
static literals.
Steps that are meta-project-only — e.g. running the multi-target
generator and pushing the host plugin cache — are NOT default-on built-ins.
They live as project-local skills under.claude/skills/finalize-step-{name}/SKILL.md in the meta-project that
needs them (discovered as project:finalize-step-{name} with default_on: false),
and that meta-project's marshal.json registers them explicitly. Consumer
projects don't see them and don't have them seeded.
Missing-default detection. When the wizard runs against an existing
project, determine_mode.py compares the existingmarshal.json::plan["phase-6-finalize"]["steps"] array against the
discovered default-on built-in set (via extension_discovery.find_implementors).
Any built-in step missing from the project's array is surfaced asmissing_default_finalize_steps so the wizard can prompt the user to add
it. This protects existing projects from quietly missing newly-added
consumer-applicable defaults when their marshal.json predates the additions.
First-run lane materialization. At the end of the wizard (Step 16),sync-defaults deep-merges the full default finalize step-set into marshal.json
before the step-sort, so the seeded pipeline becomes fully explicit — a
newly-materialized default_on: false step arrives lane: off, growing the step
count while leaving the effective running set unchanged (opt-in preserved). See
`references/wizard-flow.md` Step 16 for the
materialize-then-sort sequencing.
Review-Bot Participation (required_bots / optional_bots)
The plan-marshall:automatic-review step classifies review bots into two lists —required_bots (silence is a failure; gates the completeness quorum) andoptional_bots (silence is tolerable; never gates). A bot in NEITHER list is warned about but
still ingested. The semantics, the ask posture, and the failure taxonomy are owned by
`../automatic-review/standards/bot-participation-contract.md`;
the knob storage shape and the three-valued provenance are documented in
`../manage-config/standards/data-model.md`.
Wizard question. Both lists ship EMPTY, so the wizard MUST ask rather than assume. Build the
option set from the live registry — bot_registry.bot_kinds() — never a hardcoded bot list, so a
newly-registered bot appears automatically. Ask in two passes (required first, then optional over
the bots not already chosen as required):
AskUserQuestion:
question: "Which review bots MUST participate? A required bot's silence blocks the merge gate."
header: "Required bots"
options: # one per bot_registry.bot_kinds(), plus the none escape
- label: "{bot_kind}"
description: "Require {bot_kind} to review before the step may complete"
- label: "None"
description: "No bot is required — record an explicit empty answer"
multiSelect: trueAskUserQuestion:
question: "Which remaining bots MAY participate? An optional bot's silence never blocks."
header: "Optional bots"
options: # bot_registry.bot_kinds() minus the required selections
- label: "{bot_kind}"
description: "Ingest and report {bot_kind}, but never gate on it"
- label: "None"
description: "No optional bots — record an explicit empty answer"
multiSelect: trueRecord an explicit answer even when the operator selects "None". Write the (possibly empty)
value AND set bot_lists_provenance to answered:
python3 .plan/execute-script.py plan-marshall:manage-config:manage-config plan phase-6-finalize step set \
--step-id plan-marshall:automatic-review --param required_bots --value "{csv_or_empty}"python3 .plan/execute-script.py plan-marshall:manage-config:manage-config plan phase-6-finalize step set \
--step-id plan-marshall:automatic-review --param bot_lists_provenance --value answeredRecording answered is load-bearing: it is what keeps an operator's deliberate "no required bots"
distinguishable from a never_asked key the wizard has simply not reached yet. Never collapse the
two — a never-asked key is re-asked, an answered-empty one is not.
migrate-bot-lists (upgrade Stage 2). A project whose marshal.json still carries the retiredenabled_bots key is auto-mapped by the migrate-bot-lists sub_step of Stage 2
(reconcile-config) in the upgrade flow:
python3 .plan/execute-script.py plan-marshall:marshall-steward:upgrade migrate-bot-listsIt seeds required_bots from the legacy value VERBATIM (every bot on the old list was awaited, and
awaiting is exactly what required means), leaves optional_bots empty, removes enabled_bots,
and records provenance migrated. When the operator has ALREADY answered either new key, their
answer WINS — only the stale legacy key is removed and both values are reported. The verb is
idempotent and self-disarming: once the legacy key is gone it is a no-op success, so re-runningupgrade is always safe.
When the wizard asks the question above on a project that was just migrated, pre-fill the migrated
value as the default selection so the operator confirms or adjusts a real starting point rather
than re-deriving it. Read the current value first:
python3 .plan/execute-script.py plan-marshall:manage-config:manage-config plan phase-6-finalize step get \
--step-id plan-marshall:automatic-reviewBlocking-Finding Classification (fixed rule — no wizard seed)
The blocking-finding gate is governed by a fixed, hardcoded actionable-vs-knowledge rule in plan-marshall/scripts/_invariants.py — there is no per-phase configuration partition, no marshal.json key, and no wizard seed step. ACTIONABLE types (build-error, test-failure, lint-issue, sonar-issue, qgate, pr-comment) block when pending at a guarded boundary; KNOWLEDGE types (insight, tip, best-practice, improvement) never block. The wizard does not write any blocking-finding configuration. See `plan-marshall:plan-marshall/references/phase-handshake.md` § pending_findings_blocking_count resolution for the full rule.
Branch-Naming Surfacing (project.working_prefixes)
project.working_prefixes holds the canonical closed set of allowed
working-branch prefixes as the transparent, operator-editable source of truth inmarshal.json. It is seeded from DEFAULT_PROJECT['working_prefixes'] (defined
in manage-config/scripts/_config_defaults.py) on init and back-filled into an
existing marshal.json by sync-defaults. The default value is:
| Key | Default |
|---|---|
working_prefixes |
["feature/", "fix/", "chore/"] |
The docs/ prefix is explicitly retired and must not be re-admitted — it is not
CI-triggered, so a docs/-prefixed branch makes its PR structurally unmergeable
(see CLAUDE.md "Branch Naming"). The CI push-trigger allowlist is owned by.github/workflows/python-verify.yml (not mirrored here); a structural test
(test_branch_prefix_allowlist.py) asserts every working_prefix is covered by
a workflow push trigger.
Missing-default / drift detection. When the wizard runs against an existing
project, determine_mode.py check-working-prefixes compares the livemarshal.json::project["working_prefixes"] list againstDEFAULT_PROJECT['working_prefixes']. It surfaces missing when the key is
entirely absent, or a drift signal when a default entry is missing, so the wizard
can prompt the user to add or update it. This protects projects whosemarshal.json predates the key, since sync-defaults is not auto-run in the
interactive menu flow.
Idempotent and non-clobbering. The detection performs no writes. An
operator's customized list — including a superset that adds prefixes beyond
the defaults — is returned as ok and never flagged or overwritten; only
genuine absence or a missing default entry is surfaced.
Wizard step (runs against an existing project to surface presence/drift):
python3 .plan/execute-script.py plan-marshall:marshall-steward:determine_mode \
check-working-prefixesOutput (TOON) when the list is present and current (or operator-customized):
status okWhen the working_prefixes key is absent:
status missing
detail absent
missing_keys working_prefixesWhen the key is present but a default entry has drifted out (e.g. chore/
dropped):
status missing
detail drift
missing_keys working_prefixesRe-Run Remediation Pass
When the steward runs in menu mode (both the executor and marshal.json
already exist — an already-initialized project re-run/upgrade), it performs a
six-step remediation pass at menu-mode entry, before the Main Menu — a
sibling entry-time surface to "Branch-Naming Surfacing" and the missing-default
finalize-step detection above. The pass repairs config drift that accumulated in
projects initialized before the relevant fixes landed. The pass is not gated by
any version check; it runs unconditionally on every menu-mode entry and is
idempotent (an already-clean project is left byte-stable).
Steps (a), (b), (d), (e), and (f) are deterministic script calls that are silent
by default — they run unconditionally and leave an already-normalized project
unchanged. They surface nothing to the user EXCEPT for the documented warning
conditions of steps (a), (d), and (e): step (a)'s unrecognized_keys warning whennormalize-keys finds a top-level key it cannot order, step (d)'s session-restart
warning when it regenerates the executor, and step (e)'s detect/warn advisory when
the reconcile could not see the current config seed. Step (c) is an LLM-driven Y/NAskUserQuestion gate that consumes a deterministic diff, mirroring the existing
entry-time check-working-prefixes / missing-default surfacing.
(a) Normalize marshal.json top-level key order (silent on a canonical file,
unconditional). Re-write marshal.json with the canonical save_config key order.
Pre-fix projects accumulated a non-canonical top-level key order; this re-orders
them without touching values:
python3 .plan/execute-script.py plan-marshall:manage-config:manage-config normalize-keysSee the manage-config Canonical invocations (normalize-keys) for the verb
shape. The call is idempotent — an already-canonical file is left byte-stable.
When it returns status: warning with a non-empty unrecognized_keys, surface
that list to the operator: those top-level keys are absent from the canonical order
and were preserved but appended out of position (a stray or consumer-added block),
which normalize-keys cannot place and does not drop.
(b) Consolidate duplicate managed .gitignore blocks (silent,
unconditional). Run the .gitignore setup script via the executor/bootstrap.
Pre-fix projects accumulated multiple # Planning system (managed by /marshall-steward) managed-block headers (one per re-run); the consolidation
pass merges them into a single managed block preserving the union of rules, and
leaves an already-single-block file unchanged (status: unchanged):
python3 .plan/execute-script.py plan-marshall:marshall-steward:gitignore_setupSee the gitignore_setup Canonical invocations entry for the verb shape. The
consolidation runs on every invocation; a clean file is byte-stable.
(c) build.map drift gate (read-only diff + interactive Y/N gate). Compute
the drift between the persisted build.map and the live-tree derivation, then
gate any re-seed behind an AskUserQuestion so deliberate hand-edits are never
clobbered:
python3 .plan/execute-script.py plan-marshall:manage-config:manage-config build-map driftSee the manage-config Canonical invocations (build-map drift) for the verb
shape. The verb is read-only — it never mutates marshal.json. It returnsin_sync plus the per-domain added_globs / removed_globs diff.
in_sync: true→ no drift; continue to the Main Menu silently (no prompt).in_sync: false→ display the added/removed-glob diff to the user, then
prompt:AskUserQuestion: question: "The persisted build.map differs from the live-tree derivation. Re-seed it?" header: "build.map drift" options: - label: "Yes, re-seed" description: "Overwrite build.map with the live derivation" - label: "No, leave as-is" description: "Keep the persisted build.map (preserves deliberate hand-edits)" multiSelect: falseYes → re-seed via the force path:
python3 .plan/execute-script.py plan-marshall:manage-config:manage-config build-map seed --forceNo → leave the persisted
build.mapuntouched.
(d) Refresh the generated executor via generate_executor preflight (silent,
unconditional). Run the executor-freshness preflight FIRST, before thesync-defaults reconcile below. When the executor's embedded MARSHALL_VERSION
is older than the installed manifest's executor_changed_at_version, this
regenerates the executor in place; otherwise it is a no-op reportingexecutor_action: fresh. Sequencing it ahead of sync-defaults is load-bearing:
a version-stale executor resolves manage-config sync-defaults to a stale_cmd_sync_defaults.py, making the reconcile a silent no-op that never sees the
current config seed. Regenerating the executor first guarantees the subsequent
sync-defaults call — a fresh subprocess through .plan/execute-script.py —
resolves through the current-version script:
python3 .plan/execute-script.py plan-marshall:tools-script-executor:generate_executor preflightSafety follows the same generate_executor preflight rules described in
"Executor & Config Staleness Signaling" below (ADR-002: the executor is
per-tree derived state, never a user decision). When preflight reportsexecutor_action: regenerated, the "Session Reload Directive After Executor /
Agent Changes" guardrail below applies — surface the target-resolved session-
reload directive because the emitted agent set may have changed.
(e) Refresh provisioning stamps via sync-defaults (silent, unconditional).
Run the config deep-merge reconcile. It back-fills any missing default keys AND
re-stamps the system.provisioned_version / system.config_seed_fingerprint
provisioning fields, so a config-seed change made after the project was
initialized is reflected in marshal.json. This is the config-reconcile step
the check-staleness preflight advises the user to run when it reportsmarshal_status: stale:
python3 .plan/execute-script.py plan-marshall:manage-config:manage-config sync-defaultsSee the manage-config sync-defaults command for the deep-merge + re-stamp
contract. The call is idempotent — an already-current config is left byte-stable
(it persists only when the merge added a key or a stamp changed). The refreshedsystem.provisioned_version is what determine_mode check-staleness compares
against the installed dist-manifest.json's config_changed_at_version.
Detect/warn after (e) — because step (d) already guaranteed a current-version
executor, a sync-defaults that reports added_count: 0 while the config is
still stale is now anomalous rather than expected. Immediately after step (e),
compare its added_count against a fresh determine_mode check-stalenessmarshal_status.
First gate on call success: the comparison below is evaluated ONLY when BOTH thesync-defaults call AND the fresh check-staleness call returnstatus: success. A non-success status from either call must surface the failure
to the user and skip the clean-pass path entirely — never infer success frommarshal_status: fresh alone, because an error path does not guarantee a
well-formed added_count field. Only once both calls have returnedstatus: success do the three cases below apply, evaluated in order — themarshal_status: unknown gate is checked FIRST and short-circuits, so an
unresolvable-manifest verdict is never swept into themarshal_status: fresh → continue silently branch even when added_count > 0:
marshal_status: unknown(evaluated first, regardless ofadded_count) →
the installeddist-manifest.jsoncould not be resolved, so version-based
staleness cannot be determined — the preflight failed CLOSED. Surface the
cannot-determine warning (echo the preflightwarningfield) telling the user
freshness could not be substantiated, and advise verifying the install / a
manual executor regeneration (Maintenance → Regenerate Executor) followed by a
fresh/marshall-stewardmenu-mode entry. Do NOT report a clean silent pass in
this case — anunknownverdict is not afreshverdict.added_count: 0ANDmarshal_status: stale→ surface a warning telling the
user the reconcile could not see the current config seed even after the
executor-freshness preflight, and advise a manual executor regeneration
(Maintenance → Regenerate Executor) followed by a fresh/marshall-steward
menu-mode entry. Do NOT report a clean pass in this case.added_count > 0, ORmarshal_status: fresh→ the normal path; continue
silently to the Main Menu, preserving the idempotent silent-on-clean behavior
of steps (a)–(e).
(f) Sort phase-6-finalize.steps into frontmatter order (silent,
unconditional). Re-sort the on-disk plan.phase-6-finalize.steps keyed-map into
ascending frontmatter order. sync-defaults (step (e)) deep-merges any
newly-added finalize step by appending it, so the operator-visible marshal.json
drifts out of frontmatter order over time; this step restores the canonical order
on disk, reusing the manifest composer's sort choke-point (no duplicated order
table). It is sequenced LAST — after step (e)'s sync-defaults may have appended
a step — so it corrects any freshly-appended step, and its potential reorder diff
is picked up by the End-of-Run Landing Cycle's uncommitted-artifact detection:
python3 .plan/execute-script.py plan-marshall:manage-config:manage-config steps-sortSee the manage-config Canonical invocations (steps-sort) for the verb shape.
The call is idempotent — an already-sorted map is left byte-stable (it persists
only when the key order actually changed), and per-step values are preserved
byte-identically. phase-5-execute.verification_steps is out of scope.
After the six steps settle, proceed to the Main Menu.
End-of-Run Landing Cycle
A steward run can leave uncommitted changes to plan-marshall artifacts — the
Re-Run Remediation Pass alone may rewrite marshal.json (steps (a) normalize-keys,
(e) sync-defaults, (f) steps-sort), and interactive configuration edits touch it
too. The End-of-Run Landing Cycle is a single, uniform end-of-run hook that
offers to land those changes so a steward pass does not silently leave the working
tree dirty.
Uniform firing point. The hook fires at the natural END of every steward
mode:
- Menu mode — on the "Quit" path (Main Menu option 5), AFTER "Good bye!" is
emitted and BEFORE the skill stops. - Wizard mode — at the end of the wizard flow (see
`references/wizard-flow.md`), after the final
configuration step completes.
Trigger. The hook runs the landing-cycle procedure only when the working tree
carries an uncommitted diff — the Step 1 check is a plain whole-treegit -C {repo_root} status --porcelain that is NOT path-filtered; with no diff it
is a silent no-op and the run ends normally. In practice the changes a steward run
leaves uncommitted are always to tracked plan-marshall artifacts, but the check
itself is unscoped over the whole working tree. The full procedure — diff detection, the land/leave AskUserQuestion
gate, base-branch-conditional branch selection (create chore/{slug} on a base
branch; confirm reuse of a non-base working branch), commit → push →skip-bot-review-labelled plan-less PR → merge-queue-aware merge → switch to
the base branch → pull, and the bot skip-label honoring matrix — is documented in
`references/landing-cycle.md`. Load and execute that
reference when the hook fires.
Executor & Config Staleness Signaling
The steward surfaces executor/config staleness against the installeddist-manifest.json (emitted by the target generator) through the deterministicgenerate_executor preflight verb, wrapped by determine_mode check-staleness
so the Health Check menu can run it as one of its checks. The verb applies two
asymmetric ownership rules:
- The executor is safe derived state (ADR-002). When the executor's embedded
MARSHALL_VERSIONis older than the manifest'sexecutor_changed_at_version,
the verb regenerates the executor in place and reportsexecutor_action: regenerated; otherwiseexecutor_action: fresh.
Regeneration is safe because the executor is per-tree derived state, never a
user decision. marshal.jsonholds user decisions and is never auto-mutated. Config-seed
staleness (system.provisioned_versionolder than the manifest'sconfig_changed_at_version) is reported advisory-only asmarshal_status: stale; the steward routes the user to a config reconcile
rather than silently rewriting their config.
A fresh install with no manifest resolves both changed_at values to the empty
sentinel, so nothing is stale and the verb is a no-op reporting fresh.
Health-menu entry. The Health Check menu runs the staleness preflight via:
python3 .plan/execute-script.py plan-marshall:marshall-steward:determine_mode check-stalenessOutput (TOON) when the executor and config are both current:
status success
executor_action fresh
marshal_status fresh
installed_version <version>
executor_version <version>
marshal_version <version>When marshal_status is stale, advise the user to run a steward config
reconcile — the Re-Run Remediation Pass steps (d)-(e) above refresh the
executor and provisioning stamps (see those steps and their detect/warn
conditional for the sequencing rationale and warning mechanics), so
re-entering /marshall-steward in menu mode normally clears the advisory. The
exception is the detect/warn path (added_count: 0 AND marshal_status: stale),
where the reconcile could not see the current config seed even after the
executor-freshness preflight — there, a manual executor regeneration
(Maintenance → Regenerate Executor) is required before a fresh menu-mode entry
clears it. When executor_action is regenerated, surface the session-reload
directive (see "Session Reload Directive After Executor / Agent Changes" below)
because the emitted agent set may have changed.
Session Reload Directive After Executor / Agent Changes
CRITICAL — Reload the session's plugin set before dispatching against
newly-emitted agents or notations. Claude Code's agent registry is
session-pinned at session start: it scans the plugin cache exactly
once when the session boots and never re-scans mid-session. Any
steward operation that materially alters the agent set — executor
regeneration that adds new notations, a/sync-plugin-cacherun that
emits newexecution-context-{level}variants from the
dynamic-level executor extension point — produces files the
already-running session cannot see. Dispatching against a freshly
emitted variant from the same session fails withAgent type 'plan-marshall:execution-context-{level}' not foundeven
though the file exists on disk in the cache.Operational guardrail: after running steward operations from the
Maintenance menu (Regenerate Executor) or the wizard's executor-
generation step that regenerated the executor OR changed the agent set,
resolve the harness-appropriate reload directive through the
platform-runtime seam and surface it verbatim to the user:python3 .plan/execute-script.py plan-marshall:platform-runtime:platform_runtime session reload-directiveOn Claude the directive is
/reload-plugins, which refreshes the
session-pinned registry live — only registered monitors force a full
session restart, and plan-marshall registers none. On OpenCode the seam
returns ano-opwhose alternative is a full session restart. The WHY
rationale (registry is session-pinned at startup) is unchanged and is
documented at the sister surfaces —/sync-plugin-cache,variant_emitter.py, andext-point-dynamic-level-executor.md— and
MUST stay convergent across all four surfaces.
Artifact Landing Cycle
When a plan's deliverables touch marshall-steward-owned artifacts — executor regeneration (.plan/execute-script.py), marshal.json migrations, or the plugin-cache sync — those changes already commit to the governing plan's feature branch and ship as part of its normal phase-6-finalize PR. Whether they ride that PR or split into their own is decided by the same project-wide pr_strategy policy every PR-opening surface consults. Call the decision verb with the landing cycle's changed-file count and branch on its verdict:
python3 .plan/execute-script.py plan-marshall:manage-config:manage-config project pr-decision \
--changed-files NRide the plan's finalize PR on decision: ride; split into a separate PR on decision: split. Reference the verb by its canonical invocation (see manage-config Canonical invocations → project pr-decision) rather than restating the ceiling comparison. This documents already-implicit behaviour — steward artifacts already land on the plan's feature branch — now decided through the verb. For the ad-hoc (non-plan) counterpart of this rule, see `persona-plan-marshall-agent` agent-behavior-rules.md § "Ad-hoc changes still get the full PR flow".
Architecture Refresh Tier Knobs
The wizard and the maintenance Configuration submenu both expose two architecture_refresh tier knobs that drive the phase-6-finalize architecture-refresh step. The canonical schema, defaults, and value contract are owned by plan-marshall:manage-run-config (see manage-run-config/standards/run-config-standard.md and the architecture-refresh get-tier-0/get-tier-1/set-tier-0/set-tier-1 subcommands documented in manage-run-config/SKILL.md).
| Knob | Subcommand | Default | Allowed values |
|---|---|---|---|
architecture_refresh.tier_0 |
manage-run-config architecture-refresh set-tier-0 --value {value} |
enabled |
enabled, disabled |
architecture_refresh.tier_1 |
manage-run-config architecture-refresh set-tier-1 --value {value} |
prompt |
prompt, auto, disabled |
Surfaces inside this skill:
| Surface | Reference | Section |
|---|---|---|
| First-run wizard | references/architecture-setup.md |
Architecture Refresh Tier Knobs (reached via wizard-flow.md Step 8) |
| Maintenance menu (returning users) | references/architecture-setup.md |
Architecture Refresh Tier Knobs (reached via Configuration → Full Reconfigure, which re-runs the wizard from Step 5 onwards) |
Both surfaces share the same architecture-setup.md tier-knob question set, and both delegate persistence to the manage-run-config architecture-refresh set-tier-* subcommands — this skill never edits run-config.json directly.
Built-In Recipes
The steward exposes the following built-in recipes (registered via provides_recipes() in plan-marshall-plugin/extension.py). Recipes are loaded by phase-3-outline when a plan's status metadata sets plan_source=recipe and recipe_key=<key>.
| Recipe key | Recipe skill | Default change_type | Scope |
|---|---|---|---|
refactor-to-profile-standards |
plan-marshall:recipe-refactor-to-profile-standards |
tech_debt |
codebase_wide |
lesson_cleanup |
plan-marshall:recipe-lesson-cleanup |
derived from lesson kind (see below) | single_lesson |
lesson_cleanup derived change_type:
| Lesson kind | change_type |
|---|---|
bug |
bug_fix |
improvement |
enhancement |
anti-pattern |
tech_debt |
The lesson_cleanup recipe is auto-suggested by phase-1-init Step 5c when source == lesson and the lesson body is doc-shaped (no code-touching fences, no code-action verbs as primary subject). See references/menu-recipes.md for the wizard-facing description and marketplace/bundles/plan-marshall/skills/recipe-lesson-cleanup/SKILL.md for the recipe contract.
Note:
shared-doc-check.mdcontent has been inlined intowizard-flow.mdandmenu-maintenance.md. For TOON output format, seeplan-marshall:ref-toon-format.
Error Handling
If an error occurs during execution:
Read references/error-handling.mdApply the recovery guidance for the specific error type.
Canonical invocations
The canonical argparse surface for the six entry-point scripts this skill registers: determine_mode.py, bootstrap_plugin.py, gitignore_setup.py, upgrade.py, cache_freshness.py, and cache_retention.py. The plugin-doctor analyzer (_analyze_manage_invocation.py) reads this section as source-of-truth for the manage-invocation-invalid and missing-canonical-block rules. Consuming docs xref this section by name instead of restating the command inline. See `pm-plugin-development:plugin-script-architecture` cross-skill-integration.md § "Script invocation in documentation".
determine_mode — mode
python3 .plan/execute-script.py plan-marshall:marshall-steward:determine_mode mode [--plan-dir PLAN_DIR]determine_mode — check-docs
python3 .plan/execute-script.py plan-marshall:marshall-steward:determine_mode check-docs [--project-root PROJECT_ROOT]determine_mode — fix-docs
python3 .plan/execute-script.py plan-marshall:marshall-steward:determine_mode fix-docs [--project-root PROJECT_ROOT]determine_mode — check-structure
python3 .plan/execute-script.py plan-marshall:marshall-steward:determine_mode check-structure [--plan-dir PLAN_DIR]determine_mode — check-missing-finalize-steps
python3 .plan/execute-script.py plan-marshall:marshall-steward:determine_mode check-missing-finalize-steps [--plan-dir PLAN_DIR]determine_mode — check-staleness
python3 .plan/execute-script.py plan-marshall:marshall-steward:determine_mode check-stalenessbootstrap_plugin — get-root
python3 .plan/execute-script.py plan-marshall:marshall-steward:bootstrap_plugin get-root [--refresh]bootstrap_plugin — resolve
python3 .plan/execute-script.py plan-marshall:marshall-steward:bootstrap_plugin resolve --bundle BUNDLE --path PATHgitignore_setup
python3 .plan/execute-script.py plan-marshall:marshall-steward:gitignore_setup [--project-root PROJECT_ROOT] [--dry-run]upgrade — plan
python3 .plan/execute-script.py plan-marshall:marshall-steward:upgrade plan [--integrate {true|false}] [--project-kind {auto|meta|consumer}]cache_freshness — check
python3 .plan/execute-script.py plan-marshall:marshall-steward:cache_freshness check [--cache-root CACHE_ROOT]cache_retention — sweep
python3 .plan/execute-script.py plan-marshall:marshall-steward:cache_retention sweep [--apply] [--cache-root CACHE_ROOT] [--project-root PROJECT_ROOT]