T-1PII-shared-sdlc-yaml-loader-lib
Status: closed/obsoleted · Impact: low · Complexity: small
Auto-generated from a /sdlc:task-work post-mortem. Review and
promote to open/ready before picking up.
Two consumers (plugin/scripts/count_inflight_tasks.py and
plugin/scripts/run_quality_checks.py) now route sdlc.yaml
through the shared validator before reading keys, but each
subprocesses plugin/validators/validate_sdlc_yaml.py to do so —
robust, but adds ~real-world subprocess cost per invocation and
duplicates the schema-path / load logic. A small shared library
under plugin/lib/ would let both consumers import
load_and_validate(sdlc_yaml_path) directly.
| Location | Role today |
|---|---|
plugin/validators/validate_sdlc_yaml.py | exposes |
Both consumers settle for subprocess.run([validator, …]) which | <migrated from v2 — no role recorded> |
There is no plugin/lib/ directory today; shared helpers live | <migrated from v2 — no role recorded> |
Proposed
Section titled “Proposed”A new plugin/lib/sdlc_yaml.py (regular Python module, not a uv-
script) exposing:
default_schema_path() -> Pathload_schema(path: Path) -> dictload_yaml(path: Path) -> Anyvalidate_loaded(data, schema) -> list[str]validate_file(path, schema=None) -> tuple[bool, list[str]]load_and_validate(path) -> tuple[dict | None, list[str]]— the convenience entry point: returns(data, [])on pass,(None, errors)on fail.
The validator CLI (plugin/validators/validate_sdlc_yaml.py) becomes
a thin wrapper around this module. Both consumers (and any future
ones) from plugin.lib.sdlc_yaml import load_and_validate (or
equivalent importlib boilerplate for non-package imports) instead of
subprocessing.
Approach
Section titled “Approach”- Extract the pure-Python functions from
plugin/validators/validate_sdlc_yaml.pyintoplugin/lib/sdlc_yaml.py. Keep the CLI script and re-export from the new module so external callers (CI, humans) see no change. - Refactor
count_inflight_tasks.pyandrun_quality_checks.pyto import the helper directly. Decide on the import strategy (importlib.util.spec_from_file_locationis the precedent elsewhere in the plugin; adopt that). - Confirm the per-tick cost saved is real on the orchestrator hot
path (which calls
count_inflight_tasks.pyper tick).
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/sdlc_yaml.py | new | the shared module. |
plugin/validators/validate_sdlc_yaml.py | modify | slim down to a CLI |
plugin/scripts/count_inflight_tasks.py | modify | replace subprocess |
plugin/scripts/run_quality_checks.py | modify | replace subprocess |
plugin/validators/tests/test_validate_sdlc_yaml.py | modify | extend or |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
plugin/lib/sdlc_yaml.pyexists and exposesload_and_validate,validate_loaded,default_schema_path. - AC-2:
count_inflight_tasks.pyandrun_quality_checks.pyno longer invoke the validator as a subprocess (verify withcommand grep -n "subprocess.*validate_sdlc_yaml" plugin/scripts/). - AC-3: All existing tests
(
plugin/validators/tests/test_validate_sdlc_yaml.pyand any new module-level tests) pass. - AC-4: The validator CLI still passes a smoke run against the
project’s own
sdlc.yaml.
Out of scope
Section titled “Out of scope”- Generalising to a
plugin/lib/for other shared helpers. This task creates exactly one module; broader refactors (e.g. moving_schema_patterns.py) belong in their own task. - Packaging the plugin as an installable Python package. The plugin is a flat checkout; this task respects that and uses importlib for the cross-script import.
Dependencies
Section titled “Dependencies”- none — this is a follow-up clean-up after T-J1M3-sdlc-yaml-json-schema-and-validator shipped the subprocess-based version.
Discovery context
Section titled “Discovery context”Spawned by /sdlc:task-work post-mortem of
T-J1M3-sdlc-yaml-json-schema-and-validator on 2026-05-21.
The originating task settled for subprocess.run to call the
validator from sibling scripts because every script in this plugin
is a PEP-723 uv-script and a clean cross-script import surface
doesn’t exist. This task adds one.