Defect Shift-Left

SkillDev tools

Places every error detection at the earliest stage of the pipeline that is technically capable of catching it. Use when designing or auditing a CI/CD pipeline, choosing tooling, deciding where a check belongs, or asking "could this have been caught earlier?"

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Defect Shift-Left skill

What this skill tells your AI

The instructions your AI receives, as published by l-gevity/l-gevity-skills in .agents/skills/defect-shift-left/SKILL.md and read by ahel’s review.

Pipeline stages have a strict order. Every defect has an earliest stage at which it can be caught. Catching it later is always a regression.

Core Directives

  1. Prevent over detect. Make invalid states unrepresentable before adding a check.
  2. Earliest possible stage is mandatory. If a check can run at stage N, running it at N+1 is a regression.
  3. Replace same-scope duplicates. When shifting a check earlier, remove any later check that covers the same scope. Keep a later backstop only when it covers a broader or less-bypassable scope.
  4. Fail loud at the origin. Errors must surface where they originated.

Improvement Trio

  • defect-shift-left: move defect detection earlier.
  • push-out: move recurring operational work outward.
  • bring-down: move bespoke code down into reusable capability.

1. The Ladder

StageRankPhaseWhat runs here
00LanguageType system, syntax, language semantics
11DesignSpec, ADR, threat model, schema
22AuthoringLSP, in-editor lint, formatter
33Pre-commitFormat, fast lint, secret scan, commit-msg hook
44CompileCompiler, type-checker, codegen
55Build / Static analysisFull lint, depcheck, SAST, license, CVE, bundle, IaC, fitness functions
66Unit testLocal test runner, property tests
77Integration / ContractCI suite, contract tests, container builds
8a8Pre-deploy staticMigration dry-run, config-vs-env, capacity, IAM diff (deploy abortable)
8b9Deploy executionSmoke, health probes, slot readiness (rollback on failure)
910Canary / StagingPartial traffic, real env, perf regression
1011Production runtimeLive traffic, monitoring
1112Post-incidentForensics, RCA

Cost grows roughly geometrically with rank. The ladder is monotonic — later detection is never neutral. Use Rank for distance math; stage labels like 8a and 8b are names, not numbers.

Stages 8a and 8b are split because some defects only become detectable when target-environment state is available; pre-deploy can abort cheaply, deploy execution requires rollback.


2. Stage 0 — Make Invalid States Unrepresentable

Before adding any check at Stage ≥1, ask: can a type or schema make this defect unrepresentable? If yes, the check belongs at Stage 0.

TechniqueEliminates
Strong / branded typesType confusion, semantic mixing
Sum types + exhaustive matchingMissing case, silent fallthrough
Option / Result typesNull deref, silent failure
Refinement typesRange, off-by-one
Linear / affine typesUse-after-free, double-close
Schema-as-codeConfig drift, contract mismatch
Const / immutable defaultAccidental mutation, race
Strict compiler flags (strict, noUncheckedIndexedAccess, strictNullChecks, --strict)Whole defect classes without writing new types — flip a flag, the compiler enumerates the gaps

3. Defect Taxonomy → Earliest Stage

Stage vs rank. The Stage column is the label from §1; for distance math use the rank. Labels 07 equal their rank, then 8a→8, 8b→9, 9→10, 10→11, 11→12 — never subtract stage labels.

Defect classStageMechanism (fallback)
Type mismatch, null deref, semantic-type mixing0Type system
Missing case handling0Exhaustive sum types
Off-by-one / range0Refinement types (else 6: property test)
Use-after-free, race0Linear / borrow types (else 5: static analysis)
Generated code drift from schema0Codegen types (else 5: codegen drift check)
Contract / schema absent or ambiguous1Shared schema / spec
Authorization model gap1Threat model (else 7: security test)
Style, formatting, unused code, API misuse2LSP / editor (else 5: lint)
Banned API / unsafe pattern2LSP rule (else 5: lint)
Forbidden architectural dependency2Editor import rule (else 5: depcheck / lint)
Committed config violates schema2Editor schema hint (else 5: schema validation)
Secret in source3Pre-commit scanner (else 5: SAST)
Symbol resolution / missing import4Compiler
CVE in dependency5SCA audit
License incompatibility5License audit
Bundle / artifact regression5Bundle validator
Logic error in pure function6Unit test
Property violation across input space6Property test
Integration boundary mismatch7Contract test
Container / build reproducibility7CI image build
Performance regression (micro)7Benchmark (else 9: load test)
Migration vs current schema8aDry-run against prod DB
Irreversible migration8aReversibility check
Cross-service version skew8aVersion-matrix gate
Backwards-incompatible API change8aContract diff vs deployed
Missing / expired secret in target env8aSecret-store presence check
Undefined feature flag in target8aFlag-store consistency
Target-env config violates schema8aPre-deploy config / env validation
Capacity / quota exceeded8aResource projection
IAM permission expansion8aIAM diff
Cost / budget breach8aCost projection
Missing rollback artifact8aRegistry probe
Compliance approval missing8aPolicy gate
Artifact crashes on boot8bStartup smoke
Health probe never passes8bOrchestrator readiness gate
Target env unreachable dependency8bBoot connectivity check
Resource exhaustion under load9Load test
Real-world latency / SLO breach10Production monitoring

4. Audit Protocol

  1. Inventory every check and the stage it runs at, including manual reviews, advisory warnings, and runtime asserts.
  2. Classify each by defect class (§3).
  3. Look up the earliest possible stage and its rank (§1).
  4. Compute rank distance = current rank − earliest rank.
  5. Prioritize by rank distance × frequency × blast radius.
  6. Move the check to the earliest feasible stage.
  7. Gate it. A correct-stage check that does not block is still a detection gap.
  8. Remove later same-scope duplicates once the earlier gate is proven. Keep only broader or less-bypassable backstops.
  9. Audit every escaped defect: find its earliest possible stage and place a gate there.
SituationAction
Proposed = earliest possibleProceed
Proposed > earliest, earlier feasible nowReject — implement at the earlier stage
Proposed > earliest, earlier requires effortDocument gap as technical debt; schedule shift
No check; defects only found in productionCritical — work backward from Stage 10
Check requires target-env stateStage 8a is earliest — do not push to Stage 10
Check exists but does not blockPromote to blocking gate or remove as theatre
Later check covers same scope as earlier checkRemove later duplicate after proof
Later check covers broader / unbypassable scopeKeep as backstop; record distinct scope

Emit one coder-facing row per gap:

Defect classCurrent stage (rank)Earliest stage (rank)Rank distanceMechanismDecisionOwner/checkVerificationNext action

If a gap remains, state: "Detection Gap: defect class catchable at Stage [X] (rank [Xr]), currently at Stage [Y] (rank [Yr]). Mechanism: [...]."


5. Anti-Patterns

PatternActual / earliest
Runtime check for type errorsStage 10 / Stage 0
CI formatting check with no editor supportStage 5 / Stage 2
Linter only in CIStage 5 / Stage 2 + Stage 5
Code review as primary defect filterManual / Stage 2–5
Production monitor for known-bad inputStage 10 / Stage 0
Compile errors hidden behind dynamic typesStage 6+ / Stage 0
Manual deployment checklistManual / Stage 5 or 8a
Documentation as the contractStage 7+ / Stage 1
Deploy-and-pray monitoringStage 10 / Stage 8a
Migration applied without dry-runStage 8b–10 / Stage 8a
Secrets / config validated only at runtimeStage 10 / Stage 8a
Manual rollback on deploy failureStage 10 / Stage 8b
No canary, full traffic on new artifactStage 10 / Stage 9

These three do not detect late — they suppress a defect rather than move it earlier, so they have no "earliest stage":

  • Retry as error handling — masks a Stage 10 failure indefinitely instead of surfacing it.
  • Catch-and-log silent failure — swallows the error, violating "fail loud at the origin" (Directive 4).
  • Warnings nobody reads — detection with no gate; see §6.4.

6. Common Shift Patterns

Recurring moves that shift a defect class from a later stage to an earlier one. Recognise them; apply them deliberately.

6.1 Untyped → strict-typed source

ShiftsType errors, null deref, registry-shape drift, silent undefined from bracket access
FromStage 6+ (unit test) or Stage 10 (production)
ToStage 0 (type system)

Convert source to a language with a checking compiler (JS → TS, Python → typed Python under mypy/pyright, Ruby → RBS/Sorbet). Then progressively enable the strictest flags — strict, noUncheckedIndexedAccess, strictNullChecks — and retire every @ts-nocheck / # type: ignore escape hatch. Each flag flip is its own shift: the compiler enumerates the defects, you fix them in batches.

The shift completes only when the strict typecheck is a blocking gate at both pre-commit (fast feedback on staged files) and CI (full-repo backstop). A typecheck nobody runs is theatre — see §6.4.

6.2 ADR → executable architectural rule

ShiftsForbidden imports, layering violations, banned API usage, accidental cross-module coupling
FromStage 1 (design doc) or Stage 7+ (code review)
ToStage 2 (editor rule) + Stage 5 (blocking static analysis)

Architectural rules expressed in prose are advice; rules expressed in lint config are enforcement. eslint-plugin-boundaries, import/no-restricted-paths, dependency-cruiser, ArchUnit (JVM), and import-linter (Python) — all turn an ADR sentence into editor feedback and a build failure.

The recipe: encode each architectural decision as a rule that fails the build when violated. The ADR document remains as rationale; the lint config is the enforcement.

For the encoding pattern see architecture-as-code, with concrete implementations in architecture-as-code-javascript or architecture-as-code-python. For first principles see architecture-guidelines; for the topology rationale this enforces, see morphogenetic-architecture.

6.3 Hand-validated boundary → schema-as-code

ShiftsConfig drift, API contract mismatch, malformed input, doc-vs-reality skew
FromStage 6+ (hand-rolled if-chain validators) or Stage 10 (runtime parse errors, prose-as-contract)
ToStage 0 (codegen), Stage 2 (editor), Stage 5 (build), Stage 8a (deploy) — one artifact, multiple rungs

Schemas are the highest-leverage shift in this catalogue because the same artifact powers checks at every stage that can read it:

  • Stage 0 — codegen produces static types: JSON Schema → json-schema-to-typescript; OpenAPI → server / client stubs; Protobuf → typed clients; XSD → C# / Java classes.
  • Stage 2 — editor schemas drive autocomplete and inline validation for hand-edited files ($schema in JSON, xsi:schemaLocation in XML, YAML language server hints).
  • Stage 5 — CI validates committed files against the schema (ajv, xmllint, spectral for OpenAPI, buf lint for Protobuf).
  • Stage 8a — pre-deploy gate rejects config that does not match the schema before it reaches a running service.
  • Boundary runtime — schema-bridged TS libraries (zod, typebox, io-ts, valibot) make the schema the single source: static type plus runtime validator generated from one declaration. Use at every external input boundary (HTTP body, env vars, message payload).

Catalogue: JSON Schema (configs, package.json), OpenAPI (HTTP), gRPC / Protobuf (service-to-service), GraphQL SDL, AsyncAPI (events), Avro (streaming), XSD (XML / SOAP).

The win is not "we validate" — it is "validation comes from a single artifact that fans out to every appropriate stage." Two hand-written sources checking the same shape are the same-scope duplication §Directive 3 forbids; one schema is the antidote.

6.4 Optional check → blocking gate

ShiftsThe check itself, from advisory to enforced
FromStage where the check exists but does not block
ToSame stage, now a gate

The most common shift-left failure is having the right check at the right stage and not making it block. A typecheck run as a manual npm run command has zero shift-left value relative to no typecheck at all. Audit:

  • Pre-commit hook fails → does it block the commit, or just print?
  • CI job fails → does branch protection require it before merge?
  • Lint warning → is the rule severity error or warn?
  • Coverage drop → does it fail the build, or land in a report nobody opens?

6.5 Scope-justified backstops

§Directive 3 allows later backstops when two layers run the same check on different scopes:

  • Pre-commit — staged files only, fast, narrow, bypassable with --no-verify.
  • CI — full repo, slow, complete, un-bypassable behind branch protection.

Both are warranted: different blast radii (single commit vs. branch), different bypass costs. Layering pays when the earlier layer is faster and bypassable — the later layer is the un-bypassable backstop, not a duplicate.


7. Stack-Aware Tooling Survey

Use this only when the user asks for tooling recommendations or implementation options. A plain shift-left audit stops at the missing category.

  1. Detect the stack: manifests, lockfiles, scripts, test runners, CI/CD files, IaC/deploy config, hook runners, and editor config. Record present and absent signals.
  2. Map gaps to categories: name the stage, defect class, and missing tool category. Do not jump straight to products.
StageTool category to look for
0Type system / compiler strictness flags / schema-as-code library
1ADR template, schema registry, threat-model artifact
2LSP, editor lint integration, formatter-on-save
3Hook runner, secret scanner, commit-message linter
4Compiler / type-checker invoked in build
5Linter, dependency auditor, SAST, license checker, IaC scanner
6Unit test runner, property-test library, coverage gate
7Integration / contract test harness, container build verifier
8aMigration dry-run, config validator, IAM diff, cost projector
8bSmoke-test runner, health-probe spec, orchestrator readiness gate
9Canary controller, load generator, perf-regression gate
10Runtime monitoring, error tracker, SLO alerting
11Incident-record system, RCA template
  1. Find specific options only on request: search the detected ecosystem, filter for stack compatibility, prefer tools already present in the stack, and cite each option with a source URL and release/currency signal.

Produce one row per gap:

StageDefect class at riskDetected stack signalCandidate tool categorySpecific options (cited)Effort

Do not propose a tool without naming the stage it staffs and the defect class it catches. A tool that does not map to a rung on §1 has no place in the output.

8. See also

  • architecture-as-code — the codified-architecture pattern this skill names in §6.2.
  • architecture-guidelines — first-principles rules whose violations this skill places on the ladder.
  • ci-cd-reliability-architecture — pipeline rules that staff Stages 5–10.
  • push-out — move recurring operational work out of human/manual execution into durable systems.
  • bring-down — move bespoke or duplicated code down into reusable capability.
  • continuous-improvement — how to promote a recurring escaped-defect into a permanent gate (Directive 1).

Signals

GitHub stars
43
Forks
9
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
defect-shift-left
Source
github.com/l-gevity/l-gevity-skills