T-T879-task-work-uses-per-project-quality-checks
Status: closed/done · Impact: high · Complexity: medium
Auto-generated from a /sdlc:task-work post-mortem. Review and
promote to ready before picking up.
/sdlc:task-work hard-codes just full-check / just ci as the quality
gates it expects to run, which only fits projects that use the just task
runner. This repo (and presumably many SDLC consumers) has no justfile —
its real gates are validate_frontmatter.py, audit_entities.py, and
run_evals.py. Today the orchestrator has to silently substitute; a strict
reading of the skill would either fail or skip checks entirely. Closing this
gap lets task-work run unmodified across projects with different runners.
Cited by T-J2CW-add-epic-entity-task-depends-on-dependencies and
previously by T-3A6G-implement-entities-migrate.
From the originating post-mortem:
The skill expects
just full-check/just cifor quality checks (/sdlc:task-workstep 7). This repo has nojustfile— checks here arevalidate_frontmatter.py,audit_entities.py, andrun_evals.py. Worth parameterizing the skill’s check commands per-project or making the skill discover them.
And from T-3A6G-implement-entities-migrate’s post-mortem:
task-work’s Step 4 worktree init (mise trust && just setup-worktree) and Step 7’s quality checks (just full-check,just ci) are templated from a different project that hasjust; this repo has neither. Orchestrator substitutedcheck_entities.py+ the two eval runners. Fix:task-workshould declare its quality-check verbs in a per-project config (e.g..claude/sdlc-config.yaml) or probe forJustfileand substitute.
Skill: plugin/skills/task-work/SKILL.md Step 4 (worktree init) and Step 7
(quality checks).
Proposed
Section titled “Proposed”/sdlc:task-work reads a per-project config at sdlc.yaml in the project
root (pinned location — kept top-level so it sits next to README/CLAUDE.md,
not buried under .claude/). The config declares the quality-check commands
as a list of shell verbs:
quality_checks: - just full-check - just ciThree user-facing pieces ship together, built on top of three shared compositional primitives (see Approach for the primitives):
task-workreadssdlc.yamlin Step 4 (worktree init) and Step 7 (quality checks). If the file is absent, the skill warns visibly and runs no checks (no silent fallback)./sdlc:setupcreatessdlc.yamlwith project-appropriate defaults when it doesn’t exist, alongside the per-typedocs/planning/directory creation it already does. Idempotent — re-running setup leaves an existingsdlc.yamluntouched.- A new skill
/sdlc:find-quality-checkshelps a user populate or update thequality_checks:list. It probes the project for recognized runners (Justfile, package.json scripts, Makefile, presence ofplugin/skills/entities-audit/tests/run_evals.py-style harnesses) and proposes commands via AskUserQuestion. Can be invoked any time — at project bootstrap or when adding new gates. (Skill name finalized during implementation; the earlier draft floatedconfigure-quality-checksbutfind-quality-checksbetter matches the probe-and-suggest verb.)
Approach
Section titled “Approach”This ships as three reusable compositional units plus the consumers
that wire them together. The units are deliberately small and unopinionated
so other SDLC skills (future CI hooks, pre-PR gates, per-project
diagnostics) can pick them up without coupling to task-work.
Compositional units (build these first)
Section titled “Compositional units (build these first)”-
Deterministic runner-detection script —
plugin/scripts/detect_quality_runners.py. Probes a project root for known runner signals (Justfile,package.jsonscripts, Makefile, Cargo workspaces, common Python check entrypoints likevalidate_frontmatter.py/audit_entities.py/run_evals.py, etc.) and emits a structured JSON report on stdout describing what’s present and what shell verbs would invoke each. No LLM, no prompts — pure file/grep introspection. Best-effort: missing runners are simply absent from the report, never an error. Reusable by anything that needs to ask “what runs in this project.” -
/sdlc:find-quality-checksLLM skill. Wraps the detection script and uses model judgment to propose the right quality-check commands for the project: picking the right subset of detected verbs, ordering them, splitting fast vs. full gates, and asking the user via AskUserQuestion when the detector’s output is ambiguous. Writes the approved list back tosdlc.yaml. This is the user-facing “configure quality checks” experience. -
Deterministic executor —
plugin/scripts/run_quality_checks.py. Takes a list of shell verbs (typically fromsdlc.yaml’squality_checks:, but accepts an explicit list too) and runs them sequentially, reporting per-command success/failure. Three output modes selectable by flag, so it composes cleanly:--line(default): one-line deterministic stdout marker suitable for use in composition (OK <passed>/<total>on success;FAIL <first-failed-cmd>on failure).--json: full per-command status, stdout, stderr, duration. For machine consumers (orchestrator, dashboards).--log: human-readable streamed output, for interactive runs. Non-zero exit on any failed verb. Reusable bytask-work, future pre-commit/pre-PR skills, and ad-hoc invocation.
Consumers (wire the units in)
Section titled “Consumers (wire the units in)”-
Define the
sdlc.yamlshape. Today:quality_checks: [str]+ (reserved)worktree_init: [str]. Documented as a flat schema doc atplugin/conventions/sdlc-yaml.mdrather than a new entity underplugin/entities/sdlc-config/—sdlc.yamlis operational config without a lifecycle, so theentities/machinery would be overweight. -
Update
plugin/skills/task-work/SKILL.mdStep 7 (quality checks) to read<project-root>/sdlc.yamland invoke the executor (unit 3) rather than shelling out to hard-codedjustverbs. Absent file => warn + skip. Present file with empty list => warn + skip. Present file with non-empty list => run via the executor.task-worknever shells out directly in Step 7; it always goes through the executor so behavior stays consistent across consumers. Step 4’s worktree init verbs (mise trust && just setup-worktree) are left as-is in this PR — theworktree_init:slot in sdlc.yaml is reserved but unused; see Out of scope below. -
Extend
/sdlc:setup(and its backingplugin/scripts/setup_planning.py) to createsdlc.yamlwhen absent. Starter file contains commented-out examples and an emptyquality_checks:list. Idempotent — existing files left untouched. Accept an optional flag (e.g.--detect) to invoke the detection script (unit 1) and pre-populate proposed verbs as commented-out suggestions, so the user still has to opt in. -
Add
plugin/skills/find-quality-checks/SKILL.md(the LLM skill, unit 2) plus any backing script. The skill: read existingsdlc.yamlif any, call the detection script, present suggested commands via AskUserQuestion, write the updated list back. -
Optional: add fixture-based tests covering the
sdlc.yamlreader (missing file, empty list, malformed YAML, good cases), the detection script’s report shape across a few canned project layouts, and the executor’s three output modes against pass/fail mixes.
Files to touch
Section titled “Files to touch”Compositional units:
plugin/scripts/detect_quality_runners.py(new) — deterministic runner-detection script (unit 1). Emits JSON.plugin/scripts/run_quality_checks.py(new) — deterministic executor (unit 3). Supports--line/--json/--logoutput modes.plugin/skills/find-quality-checks/SKILL.md(new) — LLM skill (unit 2) that composes the detection script + AskUserQuestion to populatesdlc.yaml.plugin/conventions/sdlc-yaml.md(new) — flat schema doc describing the file shape, execution semantics, and reserved-key list.sdlc.yaml(new, at project root) — this repo’s actual gates, consumed by the executor in Step 7.
Consumers:
plugin/skills/task-work/SKILL.md— Step 7 stops hard-codingjustverbs; reads<project-root>/sdlc.yamland invokesrun_quality_checks.py. Step 4’s worktree-init verbs are deferred to a follow-up PR (see Out of scope).plugin/skills/setup/SKILL.mdandplugin/scripts/setup_planning.py— createsdlc.yamlwith a documented starter when absent; accept--detectto pre-populate fromdetect_quality_runners.pyoutput as commented suggestions.
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: Running
/sdlc:task-workin this repo (nojustfile) executesvalidate_frontmatter.py,audit_entities.py, andrun_evals.pyas its quality gates without any silent substitution by the orchestrator — sourced fromsdlc.yamlat the project root. - AC-2: Running
/sdlc:task-workin a project whosesdlc.yamldeclaresjust full-check/just cicontinues to work (no regression for the original shape). - AC-3: A project without
sdlc.yaml(or with an emptyquality_checks:) gets a clear visible warning, not a silent pass. - AC-4:
/sdlc:setupcreatessdlc.yamlat the project root when absent. Re-running setup with the file present leaves it untouched. - AC-5:
/sdlc:find-quality-checksprobes a real project, suggests appropriate commands, and writes them toquality_checks:insdlc.yaml. Idempotent. - AC-6 (absorbed from
T-FC8W-task-work-graceful-no-justfile-fallback): the
skill emits a single clear log line per skipped phase (e.g.
Step 7: no quality_checks configured in sdlc.yaml — skipping) rather than running a phantomjust …and failing noisily. - AC-7:
detect_quality_runners.py(unit 1) is independently invokable and emits valid JSON describing detected runners — usable without going through any skill. - AC-8:
run_quality_checks.py(unit 3) is independently invokable with an explicit list of verbs and supports all three output modes (--line,--json,--log). Non-zero exit on any failed verb.task-workuses it for all quality-check execution; no direct shell-outs remain in Step 7.
Out of scope
Section titled “Out of scope”- Designing a full project-config schema covering things other than quality
checks (everything beyond
quality_checks:is later). - Wiring
worktree_init:in sdlc.yaml. The schema reserves the key but this PR does not consume it;/sdlc:task-workStep 4 still uses the hard-codedmise trust && just setup-worktreeline. Switching Step 4 to read sdlc.yaml is a follow-up — it has the same shape as the Step 7 change but enough additional surface area (idempotency of the init verbs across re-runs, what to do when the verbs don’t exist on the user’s PATH) that it’s worth landing on its own. - Migrating other SDLC skills off hard-coded
justverbs (their post-mortems can spawn their own follow-ups).
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:task-work post-mortem of T-J2CW-add-epic-entity-task-depends-on-dependencies on 2026-05-19.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-20. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
plugin/scripts/run_quality_checks.py --config sdlc.yaml --linereturnsOK 5/5against the project’s gates (audit_entities.py + four run_evals.py harnesses), sourced from sdlc.yaml with no orchestrator substitution. - AC-2: agent-manual — implementation sub-agent verified a tmpdir fixture containing a
justshim andsdlc.yaml: [just full-check, just ci]; executor runs both via shell=True and reportsOK 2/2. Not re-verified here;subprocess.run(verb, shell=True, cwd=project_root)handles multi-word verbs uniformly. - AC-3: auto — missing config emits
config file not found: <path>on stderr, exit 2; emptyquality_checks: []emitswarning: no quality_checks configuredon stderr, exit 2. Step 7 of task-work documents both cases as warn-and-skip with no silent fallback. - AC-4: agent-manual — sub-agent verified
setup_planning.py’swrite_sdlc_yamlcreates the file when absent with a documented header, leaves existing files untouched (printsexists:), and--detectpre-populates commented-out runner suggestions. - AC-5: deferred-user —
/sdlc:find-quality-checksis a SKILL.md procedure (probe via detector → AskUserQuestion multiSelect with prior choices pre-selected → write back). Cannot be exercised end-to-end from sub-agent context; please run it interactively to confirm the idempotent re-write loop. - AC-6 (absorbed clear-log-line): auto — the executor emits
warning: no quality_checks configured in <path>on stderr (empty list) andconfig file not found: <path>on stderr (missing file), both with exit 2. task-work Step 7 surfaces these warn-and-skip cases explicitly with no phantomjust …invocation. The exact “Step 7:… — skipping” wording from the AC example isn’t used verbatim; the substance (single clear line per skipped phase, no phantom runner) is satisfied. Spot-check the user-facing message wording if you want byte-level fidelity to the absorbed example. - AC-7: auto —
plugin/scripts/detect_quality_runners.py --project-root .returns valid JSON describing 19python-scriptrunners in this repo. Independently invokable. - AC-8: auto — three modes exercised directly:
--line(OK 2/2/FAIL <cmd>),--json(per-command{command, exit_code, duration_ms, stdout, stderr}pluspassed/total),--log(streamed[N/total] <cmd>prefix). Mixed pass/fail returns non-zero. task-work Step 7 invokes the executor exclusively at line 213; no direct shell-outs remain.
What worked
Section titled “What worked”- Phasing the implementation into 7 atomic commits (one per logical unit) kept the diff easy to review and made each step independently reversible.
- Pre-deciding the script-placement question (
plugin/scripts/vs co-located under a skill) and the schema-doc location (flat convention doc, not a new entity underplugin/entities/) in the sub-agent brief eliminated bikeshedding during implementation. - Eat-your-own-dogfood — running the new executor against the project’s own
sdlc.yamlas the final quality gate — producedOK 5/5and validated the AC-1 contract directly. - Sub-agent honored
plugin/skills/CLAUDE.mdconventions (no piped gating commands, no duplicated prose across SKILL.md files) without being reminded mid-implementation.
Friction and automation gaps
Section titled “Friction and automation gaps”- Two spec-drift fragments survived sub-agent implementation:
(final name TBD)in Approach unit 2 and(or final name)in AC-5 — both referenced the abandonedconfigure-quality-checksname. Only surfaced when I grepped the spec post-implementation. Fix: the implementation-sub-agent brief should include “search the task spec for any TBD / ‘or final name’ / ‘(pick one)’ phrases and resolve them” as an explicit Phase 7 sub-step. Better still: /sdlc:task-ensure-ready could flag those phrases as readiness warnings before the implementation gate so the spec is name-clean from the start. → T-KG6Y-task-ensure-ready-flags-spec-placeholders - This task modifies the very skill (
plugin/skills/task-work/SKILL.md) that drove its execution. Worked fine because the runtime version is loaded from the global plugin path, not the worktree — but there’s no mechanism that surfaces this to the orchestrator at task pickup. Fix: /sdlc:task-work could detect when a task’s## Files to touchlist includes its own SKILL.md and emit a one-line note at Step 1 explaining the runtime/worktree split, so the operator doesn’t worry about the recursion mid-flight. → T-FWZH-task-work-detects-self-modifying-skill - The detector finds runners by file probe but cannot judge which constitute “the project’s quality
gates” —
quality_checks:curation is inherently a judgment call. This is the failure mode that motivated/sdlc:find-quality-checks(the interactive curator) and is documented design, not a gap. Noting for the aggregate signal. - Step 4 worktree-init (
mise trust && just setup-worktree) is still hard-coded; the spec’s## Out of scopedocuments this as the obvious next follow-up. Reserving theworktree_init:key insdlc.yamlwithout consuming it splits the surface area cleanly between this PR and the next. → T-K7FR-task-work-worktree-init-language-agnostic
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-KG6Y-task-ensure-ready-flags-spec-placeholders — readiness gate flags TBD / ‘final name’ /
(pick one)/ unfilled<...>placeholders in the spec body (created) - T-FWZH-task-work-detects-self-modifying-skill — Step 1 emits a runtime/worktree note when a task touches its own SKILL.md (created)
- T-K7FR-task-work-worktree-init-language-agnostic — Step 4 worktree-init becomes project-aware
instead of hard-coded
just setup-worktree(linked)