marketplace/bundles/plan-marshall/skills/build-pyproject/SKILL.md
Python/pyprojectx build operations — mypy type-checking, ruff linting, pytest with Cobertura coverage, and test-directory-based module discovery
npx skillsauth add cuioss/plan-marshall build-pyprojectInstall 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.
Python build execution via pyprojectx (./pw wrapper) with output parsing for mypy, ruff, and pytest. Wrapper resolution prefers the project wrapper (pw) and falls back to the system pwx when no checked-in wrapper is present. See build-api-reference.md § Wrapper Detection for the detection table.
See build-api-reference.md § Enforcement for shared rules.
All commands use python3 .plan/execute-script.py plan-marshall:build-pyproject:pyproject_build {command} {args}.
| Script | Purpose |
|--------|---------|
| pyproject_build.py | CLI dispatcher |
| _pyproject_execute.py | Execution config via factory (uses shared default_command_key_fn, default_build_command_fn) |
| _pyproject_cmd_parse.py | Multi-parser registry for mypy, ruff, pytest |
| _pyproject_cmd_discover.py | Module discovery via test directory detection |
Supports: run, parse, coverage-report, check-warnings, discover, resolve-test-scope.
See build-api-reference.md for the full subcommand API and availability matrix.
--command-args takes pyprojectx commands, e.g., "verify", "module-tests core", "quality-gate". Result includes wrapper field showing resolved executable path. Also accepts --env and --working-dir (see build-api-reference.md § run for the parameter definitions). A build carrying either flag is never daemon-routable — it falls back in-process under --execution-mode auto and fails loud under --execution-mode daemoncoverage.xml, htmlcov/coverage.xml. Generate with pytest --cov --cov-report=xmltest/ or tests/ subdirectories. Metadata from pyproject.toml via tomllib. Excludes .venv, venv, .tox, cache directories--plan-id) or a task-scoped footprint supplied directly via --changed-paths (comma-separated; the files a single task's change touched) — the latter supersedes the former when present. Both feed the same pure _test_scope_divergence.resolve_test_scope helper unchanged; only the footprint list differs. The derivation mirrors the bundle-derivation in phase-6-finalize/standards/pre-push-quality-gate.md (fnmatch each footprint entry against the build.map globs; marketplace/bundles/{bundle}/… → segment 2, test/{bundle}/… → segment 1). divergence_possible is true when the footprint spans more than one module or touches shared cross-module test infrastructure (script-shared/scripts/build/…, test/**/conftest.py); a single isolated module yields recommended_target = that module (scoped-equals-whole-tree by equivalence); a footprint that resolves to no module yields empty scoped_modules / recommended_target: None (a docs-only change ⇒ no pytest target). The whole-plan mode is consumed by the phase-6-finalize whole-tree module-tests divergence gate (mirroring the escalate-only-on-trigger discipline of the finalize-step-plugin-doctor reference behavior, PLAN-02); the task-scoped --changed-paths mode is consumed by the execute-task implementation profile per-task breakable-test gaterun --plan-id)When run is invoked with --plan-id <P>, every parsed issue from a failed build is auto-stored via the producer path (always-on — there is no separate --store-findings flag). Without --plan-id, the build parses and formats only (no finding storage). The pyproject-specific issue→finding-type routing is:
| Parsed category (Issue) | Finding type |
|---------------------------|--------------|
| test_failure, test_* | test-failure |
| categories containing lint or style (e.g., lint_error) | lint-issue |
| everything else (compile, type_error, dependency, plugin) | build-error |
Severity is mapped from Issue.severity: error → error, warning → warning. The finding's module carries the build tool name (python), rule carries the original parser category, and detail carries the full message plus any stack trace.
For the producer→store→consumer→gate flow including the producer-mismatch fidelity contract, see
ref-workflow-architecture/standards/findings-pipeline.md. This SKILL.md owns the per-tool issue→finding-type routing only.
Unlike Maven/Gradle (single parser) and npm (single-match registry), Python runs all matching parsers and combines results. This handles pyprojectx verify which runs mypy + ruff + pytest in sequence, producing mixed output in a single log file.
Directories with test/ or tests/ subdirectories. Searches one level deep from project root, plus root itself.
The canonical argparse surface for pyproject_build.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".
python3 .plan/execute-script.py plan-marshall:build-pyproject:pyproject_build run \
--command-args COMMAND_ARGS \
[--timeout SECONDS] [--mode {actionable,structured,errors}] [--format {toon,json}] \
[--env ENV] [--working-dir WORKING_DIR] \
(--project-dir PROJECT_DIR | --plan-id PLAN_ID)
--project-dir and --plan-id are mutually exclusive.
Two independent budgets — distinct, non-confusable failure envelopes. run is governed by two separate ceilings that do NOT extend one another:
--timeout (build-EXECUTION deadline). --timeout — falling back to the config default when omitted — is the subprocess execution deadline. An overrun surfaces as status: timeout.--plan-id slot (build-QUEUE concurrency limiter). When --plan-id is set, the build is additionally enrolled in the manage-locks build-queue concurrency limiter. Its slot-acquisition ceiling is a separate manage-locks knob (build.queue.max_retries × 60s, ~600s default) that --timeout does NOT extend. Under sustained saturation past that retry ceiling the build returns status: error / error: queue_saturated (exit 1) WITHOUT ever running the build — this envelope is DISTINGUISHABLE from a status: timeout execution timeout, never confusable with it. A status: timeout under --plan-id is therefore always an execution-deadline overrun, never a slot-acquisition timeout.Escape hatch. The --project-dir path (mutually exclusive with --plan-id) makes the queue slot a NO-OP passthrough, bypassing the concurrency limiter entirely. On a contended machine where a saturated queue is starving a build, switching from --plan-id to --project-dir is the documented way to bypass the queue and let the build run.
python3 .plan/execute-script.py plan-marshall:build-pyproject:pyproject_build parse \
--log LOG \
[--mode {default,errors,structured}] [--format {toon,json}] \
[--failures-detail] [--test TEST] \
(--project-dir PROJECT_DIR | --plan-id PLAN_ID)
--failures-detail slices the deduped per-signature traceback detail for ALL failing tests; --test TEST slices the traceback for one named failing test. Both are additive to the standard parse surface — with neither set, parse behaves exactly as before.
python3 .plan/execute-script.py plan-marshall:build-pyproject:pyproject_build coverage-report \
[--project-path PROJECT_PATH] [--report-path REPORT_PATH] [--threshold PERCENT] \
(--project-dir PROJECT_DIR | --plan-id PLAN_ID)
python3 .plan/execute-script.py plan-marshall:build-pyproject:pyproject_build check-warnings \
[--warnings WARNINGS] [--acceptable-warnings ACCEPTABLE_WARNINGS] \
(--project-dir PROJECT_DIR | --plan-id PLAN_ID)
python3 .plan/execute-script.py plan-marshall:build-pyproject:pyproject_build discover \
[--root ROOT] [--format {toon,json}]
python3 .plan/execute-script.py plan-marshall:build-pyproject:pyproject_build resolve-test-scope \
[--changed-paths CHANGED_PATHS] \
(--project-dir PROJECT_DIR | --plan-id PLAN_ID)
--project-dir and --plan-id are mutually exclusive; they resolve the build.map globs. The footprint comes from one of two sources: --changed-paths (a comma-separated task-scoped footprint — the files a single task's change touched) supersedes the whole-plan footprint when present; when it is absent --plan-id is required to resolve the live plan footprint (the --project-dir-only escape hatch cannot resolve a plan footprint on its own). Prints TOON: scoped_modules[], divergence_possible, recommended_target, whole_tree_available. Consumed by the phase-6-finalize whole-tree module-tests divergence gate (phase-6-finalize/standards/pre-push-quality-gate.md) with the whole-plan footprint, and by the execute-task implementation profile per-task breakable-test gate with a task-scoped --changed-paths footprint.
python3 .plan/execute-script.py plan-marshall:build-pyproject:pyproject_build run-config-key \
--command-args COMMAND_ARGS [--format {toon,json}]
build-api-reference.md — Shared subcommand API, error categories, issue routing, wrapper detectionbuild-execution.md — Execution contract and lifecyclestandards/pyproject-impl.md — Python/pyprojectx execution detailsdevelopment
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