T-DPG3-default-check-composition-skip-writes-doctor
State: closed/done · Impact: medium · Complexity: medium
Ship check’s fixed default composition, the skip-and-report/--strict
behavior for an unresolved leaf inside any composition, the revised
“vocabulary name resolves to nothing → error” CLI behavior, and doctor’s
new warning for a write-capable verb used where read-only is expected.
check has no built-in composition today (its old self-referential
[{run: 'check'}] became [] in T-4OUZ). There is no skip-and-report
behavior for an unresolved {run: <name>} step reached via splicing — it
silently contributes zero steps. sdlc run <vocabulary-name> with nothing
configured or detected has no distinct “not configured, nothing detected”
message. doctor has no check for a hooks: reference to a write-capable
verb.
Proposed
Section titled “Proposed”check’s built-in entry becomes [{run: 'fmt-check'}, {run: 'lint'}, {run: 'typecheck'}, {run: 'test'}] (layer: 'builtin', never 'detected' — see
decision.md’s “Default check composition”). A non-top-level unresolved
{run: <name>} step is recorded {status: 'skipped', reason: 'not configured'} instead of silently vanishing; --strict turns a skip into a
failure. sdlc run <vocabulary-name> with nothing configured or detected
prints “not configured, nothing detected” (exit non-zero), distinct from
“no workflow named 'doctor
warns when a hooks:/documented-CI reference names a writes: true
vocabulary name.
Approach
Section titled “Approach”lib/config/workflows.ts:check’s built-in entry becomes[{run: 'fmt-check'}, {run: 'lint'}, {run: 'typecheck'}, {run: 'test'}](layer: 'builtin', not'detected'—checkis excluded fromT-VE84’sdetectVocabularyentirely: never resolved by searching for a runner task literally namedcheck).test-int/test-e2eare deliberately not part of the default — a project adds them viaprepend/appendor a fullworkflows.checkoverride.step-runner.ts’srunWorkflow(or whereverT-4OUZputrun:splicing): when a non-top-level{run: <name>}step (reached via splicing, not the name the caller originally asked to run) resolves tolayer: 'none', record{status: 'skipped', reason: 'not configured'}for that step instead of contributing zero steps silently. Add astrictoption (CLI--strictonsdlc run/workflow run, and an optionalworkflows.<name>.strictschema field as a per-project default) that turns a'skipped'outcome into a failure for that run.lib/services/workflow/ops/run.ts: thelayer === 'none'check (T-4OUZ) gets two message variants:name ∈ VOCABULARY_NAMES→ “not configured, nothing detected” (exit non-zero); otherwise → “no workflow named ''” (exit non-zero, unchanged wording). lib/services/project/ops/doctor.ts: new check — for everyhooks:step and every documented CI-context reference whoserun:target is a vocabulary name withVOCABULARY_METADATA[name].writes === true, emit a warning naming the hook and the write-capable verb, suggesting the read-only counterpart (fmt→fmt-check,lint-fix→lint).lib/services/config/ops/explain.ts(T-MVK5’s rendering): a'detected'layer’sdetectedViaprints as e.g. “detected via cargo (synthesizedcargo clippy)” / “detected via node-scripts (declared scriptlint).”
| Area | Note |
|---|---|
solutions/ontological/lib/config/workflows.ts | check’s builtin composition; strict schema field |
solutions/ontological/lib/services/orchestrator/step-runner.ts | Skip-and-report for a non-top-level unresolved run:; --strict |
solutions/ontological/lib/services/workflow/ops/run.ts | --strict flag; two-variant 'none' message |
solutions/ontological/lib/services/project/ops/doctor.ts | New writes-in-read-only-context warning |
solutions/ontological/lib/services/config/ops/explain.ts | Render detectedVia |
solutions/ontological/lib/config/workflows.test.ts, step-runner.test.ts, doctor.test.ts | New cases |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: A Rust-only crate (no
package.json/tsconfig.json) runningsdlc run checksucceeds, withtypecheckreportedskipped: not configuredandfmt-check/lint/testactually running via their cargo-detected commands. - AC-2: The same run with
--strictfails, namingtypecheckas the reason. - AC-3:
sdlc run buildin a directory with noCargo.toml,package.json, moon/just/mise task, prints “not configured, nothing detected” and exits non-zero;sdlc run banana(never a real name) prints “no workflow named ‘banana’” and exits non-zero — different message, same exit behavior. - AC-4:
sdlc run judgein a project that never configured it still exits 0 with zero steps (lifecycle name, unchanged). - AC-5:
hooks.pre-commit: [{run: 'fmt'}]makessdlc project doctorwarn thatpre-commitruns a write-capable verb;hooks.pre-commit: [{run: 'fmt-check'}]does not. - AC-6:
sdlc config explain workflows.lintin a project with noworkflows.lintentry but a detectedpackage.jsonlintscript prints thedetectedVialine, not a bare command with no source.
Out of scope
Section titled “Out of scope”- Detecting
test-int/test-e2edefaults automatically (they are project-declared only — no synthesized convention exists for “the integration tier” the waycargo testdoes for the fast tier).
Dependencies
Section titled “Dependencies”T-VE84 (needs detectVocabulary/VOCABULARY_METADATA to exist);
T-4KYN (hooks resolution, for the doctor warning to inspect a hook’s
run: targets); T-MVK5 (config-explain/doctor plumbing this extends
rather than duplicates).
Discovery context
Section titled “Discovery context”Drawn from D-LSLH-collapse-verbs-workflows-chain-hooks’s “Default check
composition” and “Skip semantics” sections.
Depends on
Section titled “Depends on”T-VE84, T-4KYN, T-MVK5