@exelalfanso/puppets-orchestration 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/architect.md +29 -0
- package/.agents/backend.md +33 -0
- package/.agents/debugger.md +29 -0
- package/.agents/explorer.md +29 -0
- package/.agents/frontend.md +33 -0
- package/.agents/orchestrator.md +35 -0
- package/.agents/reviewer.md +42 -0
- package/.agents/tester.md +29 -0
- package/.agents/worker-skills.md +24 -0
- package/AGENTS.md +33 -0
- package/README.md +150 -0
- package/bin/puppets.mjs +152 -0
- package/docs/README.md +64 -0
- package/docs/architecture/decisions/README.md +36 -0
- package/docs/architecture/overview.md +41 -0
- package/docs/engineering/conventions.md +30 -0
- package/docs/engineering/project-orchestration.md +118 -0
- package/docs/engineering/testing-strategy.md +41 -0
- package/docs/javascript-typescript-stack.md +59 -0
- package/docs/product/PRD.md +56 -0
- package/docs/requirements/SRS.md +35 -0
- package/package.json +20 -0
- package/skills/README.md +12 -0
- package/skills/be/backend-architect/SKILL.md +333 -0
- package/skills/be/backend-development-feature-development/SKILL.md +180 -0
- package/skills/be/backend-security-coder/SKILL.md +156 -0
- package/skills/be/nodejs-backend-patterns/SKILL.md +35 -0
- package/skills/be/nodejs-backend-patterns/resources/implementation-playbook.md +1019 -0
- package/skills/fe/frontend-design/LICENSE.txt +177 -0
- package/skills/fe/frontend-design/SKILL.md +71 -0
- package/skills/fe/web-design-guidelines/SKILL.md +39 -0
- package/skills/qa/ai-qa-review/SKILL.md +357 -0
- package/skills/qa/ai-qa-review/references/mutation-testing.md +38 -0
- package/skills/qa/ai-qa-review/references/smell-examples.md +232 -0
- package/skills/qa/ai-qa-review/references/testability-refactors.md +97 -0
- package/skills/qa/coverage-analysis/SKILL.md +308 -0
- package/skills/qa/coverage-analysis/references/ci-gating.md +132 -0
- package/skills/qa/coverage-analysis/references/mutation-testing.md +29 -0
- package/skills/qa/coverage-analysis/references/tool-config.md +195 -0
- package/skills/qa/qa-project-context/SKILL.md +289 -0
- package/skills/qa/qa-project-context/references/examples.md +237 -0
- package/skills/qa/release-readiness/SKILL.md +443 -0
- package/skills/qa/release-readiness/references/communication-templates.md +38 -0
- package/skills/qa/release-readiness/references/rollout-automation.md +47 -0
- package/skills/qa/risk-based-testing/SKILL.md +337 -0
- package/skills/qa/risk-based-testing/references/examples.md +81 -0
- package/skills/react/vercel-composition-patterns/AGENTS.md +946 -0
- package/skills/react/vercel-composition-patterns/README.md +60 -0
- package/skills/react/vercel-composition-patterns/SKILL.md +89 -0
- package/skills/react/vercel-composition-patterns/rules/_sections.md +29 -0
- package/skills/react/vercel-composition-patterns/rules/_template.md +24 -0
- package/skills/react/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
- package/skills/react/vercel-composition-patterns/rules/architecture-compound-components.md +112 -0
- package/skills/react/vercel-composition-patterns/rules/patterns-children-over-render-props.md +87 -0
- package/skills/react/vercel-composition-patterns/rules/patterns-explicit-variants.md +100 -0
- package/skills/react/vercel-composition-patterns/rules/react19-no-forwardref.md +42 -0
- package/skills/react/vercel-composition-patterns/rules/state-context-interface.md +191 -0
- package/skills/react/vercel-composition-patterns/rules/state-decouple-implementation.md +113 -0
- package/skills/react/vercel-composition-patterns/rules/state-lift-state.md +125 -0
- package/skills/react/vercel-react-best-practices/AGENTS.md +3810 -0
- package/skills/react/vercel-react-best-practices/README.md +123 -0
- package/skills/react/vercel-react-best-practices/SKILL.md +149 -0
- package/skills/react/vercel-react-best-practices/rules/_sections.md +46 -0
- package/skills/react/vercel-react-best-practices/rules/_template.md +28 -0
- package/skills/react/vercel-react-best-practices/rules/advanced-effect-event-deps.md +56 -0
- package/skills/react/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/skills/react/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/skills/react/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/skills/react/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/skills/react/vercel-react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
- package/skills/react/vercel-react-best-practices/rules/async-defer-await.md +82 -0
- package/skills/react/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/skills/react/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/skills/react/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/skills/react/vercel-react-best-practices/rules/bundle-analyzable-paths.md +63 -0
- package/skills/react/vercel-react-best-practices/rules/bundle-barrel-imports.md +60 -0
- package/skills/react/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/skills/react/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/skills/react/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/skills/react/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/skills/react/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/skills/react/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/skills/react/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/skills/react/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/skills/react/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/skills/react/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/skills/react/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/skills/react/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/skills/react/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/skills/react/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/skills/react/vercel-react-best-practices/rules/js-flatmap-filter.md +60 -0
- package/skills/react/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/skills/react/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/skills/react/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/skills/react/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/skills/react/vercel-react-best-practices/rules/js-request-idle-callback.md +105 -0
- package/skills/react/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/skills/react/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/skills/react/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/skills/react/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/skills/react/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/skills/react/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/skills/react/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/skills/react/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/skills/react/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/skills/react/vercel-react-best-practices/rules/rendering-resource-hints.md +85 -0
- package/skills/react/vercel-react-best-practices/rules/rendering-script-defer-async.md +68 -0
- package/skills/react/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/skills/react/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-no-inline-components.md +82 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-use-deferred-value.md +59 -0
- package/skills/react/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/skills/react/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/skills/react/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/skills/react/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/skills/react/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/skills/react/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/skills/react/vercel-react-best-practices/rules/server-hoist-static-io.md +149 -0
- package/skills/react/vercel-react-best-practices/rules/server-no-shared-module-state.md +50 -0
- package/skills/react/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/skills/react/vercel-react-best-practices/rules/server-parallel-nested-fetching.md +34 -0
- package/skills/react/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/skills/vue/create-adaptable-composable/SKILL.md +76 -0
- package/skills/vue/vue-best-practices/SKILL.md +154 -0
- package/skills/vue/vue-best-practices/references/animation-class-based-technique.md +254 -0
- package/skills/vue/vue-best-practices/references/animation-state-driven-technique.md +291 -0
- package/skills/vue/vue-best-practices/references/component-async.md +97 -0
- package/skills/vue/vue-best-practices/references/component-data-flow.md +307 -0
- package/skills/vue/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
- package/skills/vue/vue-best-practices/references/component-keep-alive.md +137 -0
- package/skills/vue/vue-best-practices/references/component-slots.md +216 -0
- package/skills/vue/vue-best-practices/references/component-suspense.md +228 -0
- package/skills/vue/vue-best-practices/references/component-teleport.md +108 -0
- package/skills/vue/vue-best-practices/references/component-transition-group.md +128 -0
- package/skills/vue/vue-best-practices/references/component-transition.md +125 -0
- package/skills/vue/vue-best-practices/references/composables.md +290 -0
- package/skills/vue/vue-best-practices/references/directives.md +162 -0
- package/skills/vue/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/skills/vue/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
- package/skills/vue/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
- package/skills/vue/vue-best-practices/references/plugins.md +166 -0
- package/skills/vue/vue-best-practices/references/reactivity.md +344 -0
- package/skills/vue/vue-best-practices/references/render-functions.md +201 -0
- package/skills/vue/vue-best-practices/references/sfc.md +310 -0
- package/skills/vue/vue-best-practices/references/state-management.md +135 -0
- package/skills/vue/vue-best-practices/references/updated-hook-performance.md +187 -0
- package/skills/vue/vue-debug-guides/SKILL.md +202 -0
- package/skills/vue/vue-debug-guides/reference/animation-key-for-rerender.md +160 -0
- package/skills/vue/vue-debug-guides/reference/animation-transitiongroup-performance.md +241 -0
- package/skills/vue/vue-debug-guides/reference/async-component-error-handling.md +115 -0
- package/skills/vue/vue-debug-guides/reference/async-component-keepalive-ref-issue.md +112 -0
- package/skills/vue/vue-debug-guides/reference/async-component-suspense-control.md +84 -0
- package/skills/vue/vue-debug-guides/reference/async-component-vue-router.md +109 -0
- package/skills/vue/vue-debug-guides/reference/attrs-event-listener-merging.md +205 -0
- package/skills/vue/vue-debug-guides/reference/checkbox-true-false-value-form-submission.md +118 -0
- package/skills/vue/vue-debug-guides/reference/cleanup-side-effects.md +172 -0
- package/skills/vue/vue-debug-guides/reference/click-events-on-components.md +180 -0
- package/skills/vue/vue-debug-guides/reference/component-naming-conflicts.md +159 -0
- package/skills/vue/vue-debug-guides/reference/component-ref-requires-defineexpose.md +176 -0
- package/skills/vue/vue-debug-guides/reference/composable-avoid-hidden-side-effects.md +208 -0
- package/skills/vue/vue-debug-guides/reference/composable-call-location-restrictions.md +141 -0
- package/skills/vue/vue-debug-guides/reference/composable-naming-return-pattern.md +139 -0
- package/skills/vue/vue-debug-guides/reference/composable-tovalue-inside-watcheffect.md +182 -0
- package/skills/vue/vue-debug-guides/reference/composition-api-not-functional-programming.md +120 -0
- package/skills/vue/vue-debug-guides/reference/composition-api-script-setup-async-context.md +203 -0
- package/skills/vue/vue-debug-guides/reference/composition-api-vs-react-hooks-differences.md +156 -0
- package/skills/vue/vue-debug-guides/reference/computed-array-mutation.md +148 -0
- package/skills/vue/vue-debug-guides/reference/computed-conditional-dependencies.md +147 -0
- package/skills/vue/vue-debug-guides/reference/computed-no-parameters.md +159 -0
- package/skills/vue/vue-debug-guides/reference/computed-no-side-effects.md +107 -0
- package/skills/vue/vue-debug-guides/reference/computed-return-value-readonly.md +160 -0
- package/skills/vue/vue-debug-guides/reference/configure-app-before-mount.md +89 -0
- package/skills/vue/vue-debug-guides/reference/declare-emits-for-documentation.md +212 -0
- package/skills/vue/vue-debug-guides/reference/define-expose-before-await.md +192 -0
- package/skills/vue/vue-debug-guides/reference/define-model-default-value-sync.md +139 -0
- package/skills/vue/vue-debug-guides/reference/defineEmits-must-be-top-level.md +164 -0
- package/skills/vue/vue-debug-guides/reference/defineEmits-no-runtime-and-type-mixed.md +170 -0
- package/skills/vue/vue-debug-guides/reference/definemodel-object-mutation-no-emit.md +148 -0
- package/skills/vue/vue-debug-guides/reference/dom-update-timing-nexttick.md +90 -0
- package/skills/vue/vue-debug-guides/reference/dynamic-argument-constraints.md +146 -0
- package/skills/vue/vue-debug-guides/reference/dynamic-component-registration-vite.md +147 -0
- package/skills/vue/vue-debug-guides/reference/event-modifier-order-matters.md +101 -0
- package/skills/vue/vue-debug-guides/reference/exact-modifier-for-precise-shortcuts.md +155 -0
- package/skills/vue/vue-debug-guides/reference/fallthrough-attrs-overwrite-vue3.md +159 -0
- package/skills/vue/vue-debug-guides/reference/in-dom-template-parsing-caveats.md +149 -0
- package/skills/vue/vue-debug-guides/reference/inheritattrs-false-for-wrapper-components.md +230 -0
- package/skills/vue/vue-debug-guides/reference/keepalive-router-nested-double-mount.md +222 -0
- package/skills/vue/vue-debug-guides/reference/keepalive-transition-memory-leak.md +144 -0
- package/skills/vue/vue-debug-guides/reference/keyup-modifier-timing.md +137 -0
- package/skills/vue/vue-debug-guides/reference/lifecycle-dom-access-timing.md +216 -0
- package/skills/vue/vue-debug-guides/reference/lifecycle-hooks-synchronous-registration.md +156 -0
- package/skills/vue/vue-debug-guides/reference/lifecycle-ssr-awareness.md +184 -0
- package/skills/vue/vue-debug-guides/reference/local-components-not-in-descendants.md +151 -0
- package/skills/vue/vue-debug-guides/reference/mount-return-value.md +88 -0
- package/skills/vue/vue-debug-guides/reference/multi-root-component-class-attrs.md +93 -0
- package/skills/vue/vue-debug-guides/reference/native-event-collision-with-emits.md +162 -0
- package/skills/vue/vue-debug-guides/reference/no-passive-with-prevent.md +141 -0
- package/skills/vue/vue-debug-guides/reference/no-v-if-with-v-for.md +136 -0
- package/skills/vue/vue-debug-guides/reference/perf-computed-object-stability.md +157 -0
- package/skills/vue/vue-debug-guides/reference/perf-props-stability-update-optimization.md +140 -0
- package/skills/vue/vue-debug-guides/reference/plugin-global-properties-sparingly.md +109 -0
- package/skills/vue/vue-debug-guides/reference/plugin-install-before-mount.md +124 -0
- package/skills/vue/vue-debug-guides/reference/plugin-prefer-provide-inject-over-global-properties.md +120 -0
- package/skills/vue/vue-debug-guides/reference/plugin-typescript-type-augmentation.md +157 -0
- package/skills/vue/vue-debug-guides/reference/prop-defineprops-scope-limitation.md +161 -0
- package/skills/vue/vue-debug-guides/reference/provide-inject-debugging-challenges.md +203 -0
- package/skills/vue/vue-debug-guides/reference/provide-inject-default-value-factory.md +244 -0
- package/skills/vue/vue-debug-guides/reference/provide-inject-reactivity-not-automatic.md +226 -0
- package/skills/vue/vue-debug-guides/reference/provide-inject-synchronous-setup.md +235 -0
- package/skills/vue/vue-debug-guides/reference/reactive-destructuring.md +89 -0
- package/skills/vue/vue-debug-guides/reference/reactivity-debugging-hooks.md +132 -0
- package/skills/vue/vue-debug-guides/reference/reactivity-markraw-for-non-reactive.md +149 -0
- package/skills/vue/vue-debug-guides/reference/reactivity-proxy-identity-hazard.md +96 -0
- package/skills/vue/vue-debug-guides/reference/reactivity-same-tick-batching.md +166 -0
- package/skills/vue/vue-debug-guides/reference/ref-value-access.md +61 -0
- package/skills/vue/vue-debug-guides/reference/refs-in-collections-need-value.md +81 -0
- package/skills/vue/vue-debug-guides/reference/render-function-avoid-internal-vnode-properties.md +151 -0
- package/skills/vue/vue-debug-guides/reference/render-function-vnodes-must-be-unique.md +133 -0
- package/skills/vue/vue-debug-guides/reference/rendering-render-function-h-import-vue3.md +148 -0
- package/skills/vue/vue-debug-guides/reference/rendering-render-function-return-from-setup.md +148 -0
- package/skills/vue/vue-debug-guides/reference/rendering-render-function-slots-as-functions.md +168 -0
- package/skills/vue/vue-debug-guides/reference/rendering-resolve-component-for-string-names.md +231 -0
- package/skills/vue/vue-debug-guides/reference/select-initial-value-ios-bug.md +91 -0
- package/skills/vue/vue-debug-guides/reference/self-referencing-component-name.md +157 -0
- package/skills/vue/vue-debug-guides/reference/sfc-named-exports-forbidden.md +184 -0
- package/skills/vue/vue-debug-guides/reference/sfc-scoped-css-child-component-styling.md +156 -0
- package/skills/vue/vue-debug-guides/reference/sfc-scoped-css-dynamic-content.md +193 -0
- package/skills/vue/vue-debug-guides/reference/sfc-scoped-css-slot-content.md +242 -0
- package/skills/vue/vue-debug-guides/reference/sfc-script-setup-reactivity.md +195 -0
- package/skills/vue/vue-debug-guides/reference/slot-forwarding-to-child-components.md +143 -0
- package/skills/vue/vue-debug-guides/reference/slot-implicit-default-content.md +155 -0
- package/skills/vue/vue-debug-guides/reference/slot-name-reserved-prop.md +109 -0
- package/skills/vue/vue-debug-guides/reference/slot-named-scoped-explicit-default.md +95 -0
- package/skills/vue/vue-debug-guides/reference/slot-render-scope-parent-only.md +135 -0
- package/skills/vue/vue-debug-guides/reference/slot-v-slot-on-components-or-templates-only.md +122 -0
- package/skills/vue/vue-debug-guides/reference/ssr-hydration-mismatch-causes.md +280 -0
- package/skills/vue/vue-debug-guides/reference/ssr-platform-specific-apis.md +256 -0
- package/skills/vue/vue-debug-guides/reference/state-ssr-cross-request-pollution.md +276 -0
- package/skills/vue/vue-debug-guides/reference/suspense-no-builtin-error-handling.md +127 -0
- package/skills/vue/vue-debug-guides/reference/suspense-ssr-hydration-issues.md +159 -0
- package/skills/vue/vue-debug-guides/reference/tailwind-dynamic-class-generation.md +144 -0
- package/skills/vue/vue-debug-guides/reference/teleport-scoped-styles-limitation.md +191 -0
- package/skills/vue/vue-debug-guides/reference/teleport-ssr-hydration.md +152 -0
- package/skills/vue/vue-debug-guides/reference/teleport-target-must-exist.md +113 -0
- package/skills/vue/vue-debug-guides/reference/template-expressions-restrictions.md +114 -0
- package/skills/vue/vue-debug-guides/reference/template-functions-no-side-effects.md +187 -0
- package/skills/vue/vue-debug-guides/reference/template-ref-null-with-v-if.md +123 -0
- package/skills/vue/vue-debug-guides/reference/template-ref-unwrapping-top-level.md +104 -0
- package/skills/vue/vue-debug-guides/reference/template-ref-v-for-order.md +172 -0
- package/skills/vue/vue-debug-guides/reference/textarea-no-interpolation.md +72 -0
- package/skills/vue/vue-debug-guides/reference/transition-group-flip-inline-elements.md +152 -0
- package/skills/vue/vue-debug-guides/reference/transition-group-move-animation-position-absolute.md +130 -0
- package/skills/vue/vue-debug-guides/reference/transition-group-no-default-wrapper-vue3.md +152 -0
- package/skills/vue/vue-debug-guides/reference/transition-js-hooks-done-callback.md +251 -0
- package/skills/vue/vue-debug-guides/reference/transition-nested-duration.md +182 -0
- package/skills/vue/vue-debug-guides/reference/transition-reusable-scoped-style.md +245 -0
- package/skills/vue/vue-debug-guides/reference/transition-router-view-appear.md +193 -0
- package/skills/vue/vue-debug-guides/reference/transition-type-when-mixed.md +172 -0
- package/skills/vue/vue-debug-guides/reference/transition-unmount-hook-timing.md +149 -0
- package/skills/vue/vue-debug-guides/reference/ts-defineprops-boolean-default-false.md +225 -0
- package/skills/vue/vue-debug-guides/reference/ts-defineprops-imported-types-limitations.md +281 -0
- package/skills/vue/vue-debug-guides/reference/ts-event-handler-explicit-typing.md +213 -0
- package/skills/vue/vue-debug-guides/reference/ts-reactive-no-generic-argument.md +196 -0
- package/skills/vue/vue-debug-guides/reference/ts-shallowref-for-dynamic-components.md +218 -0
- package/skills/vue/vue-debug-guides/reference/ts-template-ref-null-handling.md +249 -0
- package/skills/vue/vue-debug-guides/reference/ts-template-type-casting.md +214 -0
- package/skills/vue/vue-debug-guides/reference/ts-withdefaults-mutable-factory-function.md +171 -0
- package/skills/vue/vue-debug-guides/reference/undeclared-emits-double-firing.md +195 -0
- package/skills/vue/vue-debug-guides/reference/use-template-ref-vue35.md +158 -0
- package/skills/vue/vue-debug-guides/reference/v-else-must-follow-v-if.md +136 -0
- package/skills/vue/vue-debug-guides/reference/v-for-component-props.md +95 -0
- package/skills/vue/vue-debug-guides/reference/v-for-computed-reverse-sort.md +86 -0
- package/skills/vue/vue-debug-guides/reference/v-for-key-attribute.md +90 -0
- package/skills/vue/vue-debug-guides/reference/v-for-range-starts-at-one.md +66 -0
- package/skills/vue/vue-debug-guides/reference/v-if-null-check-order.md +171 -0
- package/skills/vue/vue-debug-guides/reference/v-model-ignores-html-attributes.md +83 -0
- package/skills/vue/vue-debug-guides/reference/v-model-ime-composition.md +83 -0
- package/skills/vue/vue-debug-guides/reference/v-model-number-modifier-behavior.md +124 -0
- package/skills/vue/vue-debug-guides/reference/v-show-template-limitation.md +124 -0
- package/skills/vue/vue-debug-guides/reference/watch-async-cleanup.md +180 -0
- package/skills/vue/vue-debug-guides/reference/watch-async-creation-memory-leak.md +176 -0
- package/skills/vue/vue-debug-guides/reference/watch-deep-same-object-reference.md +165 -0
- package/skills/vue/vue-debug-guides/reference/watch-flush-timing.md +189 -0
- package/skills/vue/vue-debug-guides/reference/watch-reactive-property-getter.md +108 -0
- package/skills/vue/vue-debug-guides/reference/watcheffect-async-dependency-tracking.md +173 -0
- package/skills/vue/vue-debug-guides/reference/watcheffect-flush-post-for-refs.md +176 -0
- package/skills/vue/vue-jsx-best-practices/SKILL.md +12 -0
- package/skills/vue/vue-jsx-best-practices/reference/render-function-jsx-vue-vs-react.md +141 -0
- package/skills/vue/vue-options-api-best-practices/SKILL.md +23 -0
- package/skills/vue/vue-options-api-best-practices/reference/no-arrow-functions-in-lifecycle-hooks.md +95 -0
- package/skills/vue/vue-options-api-best-practices/reference/no-arrow-functions-in-methods.md +68 -0
- package/skills/vue/vue-options-api-best-practices/reference/stateful-methods-lifecycle.md +61 -0
- package/skills/vue/vue-options-api-best-practices/reference/ts-options-api-arrow-functions-validators.md +141 -0
- package/skills/vue/vue-options-api-best-practices/reference/ts-options-api-computed-return-types.md +192 -0
- package/skills/vue/vue-options-api-best-practices/reference/ts-options-api-proptype-complex-types.md +212 -0
- package/skills/vue/vue-options-api-best-practices/reference/ts-options-api-provide-inject-limitations.md +135 -0
- package/skills/vue/vue-options-api-best-practices/reference/ts-options-api-type-event-handlers.md +202 -0
- package/skills/vue/vue-options-api-best-practices/reference/ts-options-api-use-definecomponent.md +172 -0
- package/skills/vue/vue-options-api-best-practices/reference/ts-strict-mode-options-api.md +197 -0
- package/skills/vue/vue-pinia-best-practices/SKILL.md +21 -0
- package/skills/vue/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
- package/skills/vue/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
- package/skills/vue/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
- package/skills/vue/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
- package/skills/vue/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
- package/skills/vue/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
- package/skills/vue/vue-router-best-practices/SKILL.md +23 -0
- package/skills/vue/vue-router-best-practices/reference/router-beforeenter-no-param-trigger.md +167 -0
- package/skills/vue/vue-router-best-practices/reference/router-beforerouteenter-no-this.md +176 -0
- package/skills/vue/vue-router-best-practices/reference/router-guard-async-await-pattern.md +227 -0
- package/skills/vue/vue-router-best-practices/reference/router-navigation-guard-infinite-loop.md +187 -0
- package/skills/vue/vue-router-best-practices/reference/router-navigation-guard-next-deprecated.md +150 -0
- package/skills/vue/vue-router-best-practices/reference/router-param-change-no-lifecycle.md +181 -0
- package/skills/vue/vue-router-best-practices/reference/router-simple-routing-cleanup.md +209 -0
- package/skills/vue/vue-router-best-practices/reference/router-use-vue-router-for-production.md +183 -0
- package/skills/vue/vue-testing-best-practices/SKILL.md +29 -0
- package/skills/vue/vue-testing-best-practices/reference/async-component-testing.md +163 -0
- package/skills/vue/vue-testing-best-practices/reference/teleport-testing-complexity.md +158 -0
- package/skills/vue/vue-testing-best-practices/reference/testing-async-await-flushpromises.md +175 -0
- package/skills/vue/vue-testing-best-practices/reference/testing-browser-vs-node-runners.md +208 -0
- package/skills/vue/vue-testing-best-practices/reference/testing-component-blackbox-approach.md +144 -0
- package/skills/vue/vue-testing-best-practices/reference/testing-composables-helper-wrapper.md +238 -0
- package/skills/vue/vue-testing-best-practices/reference/testing-e2e-playwright-recommended.md +242 -0
- package/skills/vue/vue-testing-best-practices/reference/testing-no-snapshot-only.md +197 -0
- package/skills/vue/vue-testing-best-practices/reference/testing-pinia-store-setup.md +228 -0
- package/skills/vue/vue-testing-best-practices/reference/testing-suspense-async-components.md +229 -0
- package/skills/vue/vue-testing-best-practices/reference/testing-vitest-recommended-for-vue.md +204 -0
- package/skills/worker/README.md +10 -0
- package/skills/worker/caveman/README.md +48 -0
- package/skills/worker/caveman/SKILL.md +88 -0
- package/skills/worker/ponytail/SKILL.md +120 -0
- package/skills-lock.json +131 -0
- package/templates/state.md +16 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Mutation Testing — the objective backstop for qualitative smells
|
|
2
|
+
|
|
3
|
+
Coverage tells you a line *ran*. Mutation testing tells you whether the test would have *caught a bug* in that line. The runner introduces small faults (mutants) — flips a `>` to `>=`, returns `null`, deletes a statement — and reruns the suite. A mutant the tests still pass on "survived": no assertion constrained that behavior. The mutation score is killed / total.
|
|
4
|
+
|
|
5
|
+
Use it to convert two SKILL smells from eye-judgement into a measurable gate:
|
|
6
|
+
|
|
7
|
+
- **Closed AI loop** (tests mirror implementation). Tests that just echo what the agent wrote rarely kill mutants — they assert the produced value, not the contract. A low score on AI-authored tests is the signal.
|
|
8
|
+
- **Weak / redundant assertions** (`toBeTruthy`, duplicate assertions). Surviving mutants point at exactly the assertions that don't pin behavior down.
|
|
9
|
+
|
|
10
|
+
## Tools (verified current, mid-2026)
|
|
11
|
+
|
|
12
|
+
| Stack | Runner | Command |
|
|
13
|
+
|-------|--------|---------|
|
|
14
|
+
| JS / TS | StrykerJS (supports Jest, Vitest, Mocha) | `npx stryker run` |
|
|
15
|
+
| Java | PIT (pitest) | `mvn org.pitest:pitest-maven:mutationCoverage` |
|
|
16
|
+
| Rust | cargo-mutants | `cargo mutants` |
|
|
17
|
+
| Python | mutmut | `mutmut run` |
|
|
18
|
+
|
|
19
|
+
## Thresholds (2026 guidance)
|
|
20
|
+
|
|
21
|
+
- AI-generated tests scoring **< 60%** mutation score signal tests not independent of the implementation — treat as the Closed-AI-loop / weak-assertion smell and require human-authored boundary tests before trusting the suite.
|
|
22
|
+
- Hand-tuned production code: 70% on critical paths, 50% standard, 30% experimental. A score above 80% is a strong test-quality indicator.
|
|
23
|
+
|
|
24
|
+
## Minimal StrykerJS config
|
|
25
|
+
|
|
26
|
+
```jsonc
|
|
27
|
+
// stryker.config.json
|
|
28
|
+
{
|
|
29
|
+
"testRunner": "vitest",
|
|
30
|
+
"coverageAnalysis": "perTest",
|
|
31
|
+
"mutate": ["src/**/*.ts", "!src/**/*.test.ts"],
|
|
32
|
+
"thresholds": { "high": 80, "low": 60, "break": 60 }
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`"break": 60` fails CI under 60% — wire it as a quality gate parallel to the test runner, the same way ESLint catches static smells. Scope it to changed files in PR review (`--mutate "$(git diff --name-only main...HEAD | grep '\.ts$' | tr '\n' ',')"`) so a full-suite mutation run (slow) only happens in scheduled audits.
|
|
37
|
+
|
|
38
|
+
Surviving mutants double as a test-generation feedback loop: feed them back to the agent and ask it to write the assertion that kills each one.
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Test Smell Examples (SMELL → FIX)
|
|
2
|
+
|
|
3
|
+
Runnable before/after code for the test smells catalogued in `SKILL.md`. The smell descriptions, detection cues, and review actions live in the SKILL; this file holds the full code so the SKILL loads lean.
|
|
4
|
+
|
|
5
|
+
## Reliability Smells
|
|
6
|
+
|
|
7
|
+
### Sleep-Based Waiting
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// SMELL: Slow on fast machines, flaky on slow ones
|
|
11
|
+
it('should show notification after save', async () => {
|
|
12
|
+
await page.click('#save');
|
|
13
|
+
await page.waitForTimeout(3000);
|
|
14
|
+
expect(await page.isVisible('.notification')).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// FIX: Wait for the specific condition
|
|
18
|
+
it('should show notification after save', async () => {
|
|
19
|
+
await page.getByRole('button', { name: 'Save' }).click();
|
|
20
|
+
await expect(page.getByRole('alert')).toBeVisible();
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Order Dependency
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// SMELL: Test B depends on Test A's side effects
|
|
28
|
+
describe('user management', () => {
|
|
29
|
+
it('A: should create a user', async () => {
|
|
30
|
+
await api.post('/users', { name: 'Alice' });
|
|
31
|
+
});
|
|
32
|
+
it('B: should list users', async () => {
|
|
33
|
+
const users = await api.get('/users');
|
|
34
|
+
expect(users).toContainEqual({ name: 'Alice' }); // Fails if A didn't run first
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// FIX: Each test sets up its own state
|
|
39
|
+
describe('user management', () => {
|
|
40
|
+
it('should create a user', async () => {
|
|
41
|
+
const response = await api.post('/users', { name: 'Alice' });
|
|
42
|
+
expect(response.status).toBe(201);
|
|
43
|
+
});
|
|
44
|
+
it('should list users including recently created', async () => {
|
|
45
|
+
await api.post('/users', { name: 'Bob' }); // Own setup
|
|
46
|
+
const users = await api.get('/users');
|
|
47
|
+
expect(users).toContainEqual({ name: 'Bob' });
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### External Service Coupling
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// SMELL: Fails when Stripe is down, rate-limited, or returns different data
|
|
56
|
+
it('should process payment', async () => {
|
|
57
|
+
const result = await stripe.charges.create({ amount: 2000, currency: 'usd' });
|
|
58
|
+
expect(result.status).toBe('succeeded');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// FIX: Mock the boundary
|
|
62
|
+
it('should process payment', async () => {
|
|
63
|
+
const mockStripe = { charges: { create: vi.fn().mockResolvedValue({ status: 'succeeded' }) } };
|
|
64
|
+
const service = new PaymentService(mockStripe);
|
|
65
|
+
const result = await service.charge(2000, 'usd');
|
|
66
|
+
expect(result.status).toBe('succeeded');
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Diagnostic Smells
|
|
71
|
+
|
|
72
|
+
### Weak Assertion Messages
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// SMELL: Failure message: "Expected false to be true" -- useless
|
|
76
|
+
it('should validate the form', () => {
|
|
77
|
+
expect(isValid(form)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// FIX: Use specific assertions that produce clear failure messages
|
|
81
|
+
it('should accept form with valid email and non-empty name', () => {
|
|
82
|
+
const result = validate(form);
|
|
83
|
+
expect(result.isValid).toBe(true);
|
|
84
|
+
expect(result.errors).toEqual([]);
|
|
85
|
+
// Failure: "Expected errors to equal [] but received [{ field: 'email', message: 'invalid format' }]"
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Multiple Failure Causes Per Test
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// SMELL: If this fails, is it the creation, the update, or the deletion?
|
|
93
|
+
it('should handle user lifecycle', async () => {
|
|
94
|
+
const user = await service.create({ name: 'Alice' });
|
|
95
|
+
expect(user.id).toBeDefined();
|
|
96
|
+
|
|
97
|
+
await service.update(user.id, { name: 'Bob' });
|
|
98
|
+
const updated = await service.get(user.id);
|
|
99
|
+
expect(updated.name).toBe('Bob');
|
|
100
|
+
|
|
101
|
+
await service.delete(user.id);
|
|
102
|
+
await expect(service.get(user.id)).rejects.toThrow(NotFoundError);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// FIX: One behavior per test
|
|
106
|
+
it('should create user with generated id', async () => {
|
|
107
|
+
const user = await service.create({ name: 'Alice' });
|
|
108
|
+
expect(user.id).toBeDefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should update user name', async () => {
|
|
112
|
+
const user = await service.create({ name: 'Alice' });
|
|
113
|
+
await service.update(user.id, { name: 'Bob' });
|
|
114
|
+
expect((await service.get(user.id)).name).toBe('Bob');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should delete user so they cannot be retrieved', async () => {
|
|
118
|
+
const user = await service.create({ name: 'Alice' });
|
|
119
|
+
await service.delete(user.id);
|
|
120
|
+
await expect(service.get(user.id)).rejects.toThrow(NotFoundError);
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Design Smells
|
|
125
|
+
|
|
126
|
+
### Conditional Test Logic
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// SMELL: Test logic that can take different paths is itself untested
|
|
130
|
+
it('should handle all user roles', () => {
|
|
131
|
+
for (const role of ['admin', 'user', 'guest']) {
|
|
132
|
+
const result = getPermissions(role);
|
|
133
|
+
if (role === 'admin') {
|
|
134
|
+
expect(result).toContain('delete');
|
|
135
|
+
} else if (role === 'user') {
|
|
136
|
+
expect(result).toContain('read');
|
|
137
|
+
expect(result).not.toContain('delete');
|
|
138
|
+
} else {
|
|
139
|
+
expect(result).toEqual(['read']);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// FIX: Use parameterized tests (test.each / it.each)
|
|
145
|
+
it.each([
|
|
146
|
+
['admin', ['read', 'write', 'delete']],
|
|
147
|
+
['user', ['read', 'write']],
|
|
148
|
+
['guest', ['read']],
|
|
149
|
+
])('role "%s" should have permissions %j', (role, expected) => {
|
|
150
|
+
expect(getPermissions(role)).toEqual(expected);
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Giant Fixtures
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// SMELL: Every test pays the setup cost for data it doesn't use
|
|
158
|
+
beforeEach(async () => {
|
|
159
|
+
await createUser(pool, { id: 'u1', role: 'admin' });
|
|
160
|
+
await createUser(pool, { id: 'u2', role: 'user' });
|
|
161
|
+
await createUser(pool, { id: 'u3', role: 'guest' });
|
|
162
|
+
await createProduct(pool, { id: 'p1', stock: 100 });
|
|
163
|
+
await createProduct(pool, { id: 'p2', stock: 0 });
|
|
164
|
+
await createOrder(pool, { id: 'o1', userId: 'u2' });
|
|
165
|
+
await createOrder(pool, { id: 'o2', userId: 'u2' });
|
|
166
|
+
// ... 15 more objects
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// FIX: Each test creates only what it needs
|
|
170
|
+
it('should prevent guest from deleting products', async () => {
|
|
171
|
+
const guest = await createUser(pool, { role: 'guest' });
|
|
172
|
+
const product = await createProduct(pool, { stock: 50 });
|
|
173
|
+
await expect(productService.delete(product.id, guest.id)).rejects.toThrow(ForbiddenError);
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Over-Mocking
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// SMELL: Mocking the thing you are testing
|
|
181
|
+
it('should format price', () => {
|
|
182
|
+
const mockFormatter = vi.fn().mockReturnValue('$29.99');
|
|
183
|
+
const result = mockFormatter(29.99);
|
|
184
|
+
expect(mockFormatter).toHaveBeenCalledWith(29.99);
|
|
185
|
+
expect(result).toBe('$29.99');
|
|
186
|
+
// This test verifies nothing about the real formatPrice function
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// FIX: Only mock external boundaries (network, DB, filesystem, time)
|
|
190
|
+
it('should format price with currency symbol', () => {
|
|
191
|
+
expect(formatPrice(29.99, 'USD')).toBe('$29.99');
|
|
192
|
+
expect(formatPrice(29.99, 'EUR')).toBe('29,99 EUR');
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Coverage Smells
|
|
197
|
+
|
|
198
|
+
### Happy Path Only
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// SMELL: What happens with invalid input? Empty input? Null? Boundary values?
|
|
202
|
+
describe('calculateDiscount', () => {
|
|
203
|
+
it('should apply 10% discount', () => {
|
|
204
|
+
expect(calculateDiscount(100, 0.1)).toBe(90);
|
|
205
|
+
});
|
|
206
|
+
it('should apply 20% discount', () => {
|
|
207
|
+
expect(calculateDiscount(200, 0.2)).toBe(160);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// FIX: Add boundary, negative, and error cases
|
|
212
|
+
describe('calculateDiscount', () => {
|
|
213
|
+
it('should apply percentage discount', () => {
|
|
214
|
+
expect(calculateDiscount(100, 0.1)).toBe(90);
|
|
215
|
+
});
|
|
216
|
+
it('should handle zero discount', () => {
|
|
217
|
+
expect(calculateDiscount(100, 0)).toBe(100);
|
|
218
|
+
});
|
|
219
|
+
it('should handle 100% discount', () => {
|
|
220
|
+
expect(calculateDiscount(100, 1.0)).toBe(0);
|
|
221
|
+
});
|
|
222
|
+
it('should reject negative discount', () => {
|
|
223
|
+
expect(() => calculateDiscount(100, -0.1)).toThrow('Discount must be between 0 and 1');
|
|
224
|
+
});
|
|
225
|
+
it('should reject discount over 100%', () => {
|
|
226
|
+
expect(() => calculateDiscount(100, 1.5)).toThrow('Discount must be between 0 and 1');
|
|
227
|
+
});
|
|
228
|
+
it('should handle zero price', () => {
|
|
229
|
+
expect(calculateDiscount(0, 0.1)).toBe(0);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Testability Refactors
|
|
2
|
+
|
|
3
|
+
Before/after code for the testability problems described under "Testability Analysis" in `SKILL.md`. The flags and what-to-look-for cues live in the SKILL; the refactor code lives here.
|
|
4
|
+
|
|
5
|
+
## Dependency Injection
|
|
6
|
+
|
|
7
|
+
Flag classes that instantiate dependencies directly (`new PostgresDatabase()`, `new StripeClient()` inside methods). Suggest constructor injection so tests can substitute mocks/fakes.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// HARD TO TEST // TESTABLE
|
|
11
|
+
class OrderService { class OrderService {
|
|
12
|
+
async create(data: OrderInput) { constructor(
|
|
13
|
+
const db = new PostgresDB(); private readonly db: Database,
|
|
14
|
+
const email = new SendGrid(); private readonly email: EmailClient,
|
|
15
|
+
} ) {}
|
|
16
|
+
} }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Side Effect Isolation
|
|
20
|
+
|
|
21
|
+
Flag functions that mix pure calculation with I/O (email, logging, analytics). Extract the calculation as a pure function, then call it from the side-effectful orchestrator. The pure half becomes trivially unit-testable.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// HARD TO TEST: calculation and side effects fused
|
|
25
|
+
async function checkout(cart: Cart, userId: string) {
|
|
26
|
+
let total = 0;
|
|
27
|
+
for (const item of cart.items) total += item.price * item.qty;
|
|
28
|
+
if (cart.coupon) total *= 1 - cart.coupon.rate;
|
|
29
|
+
await db.orders.insert({ userId, total }); // I/O
|
|
30
|
+
await email.send(userId, `You paid ${total}`); // I/O
|
|
31
|
+
return total;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// TESTABLE: pure calculation extracted
|
|
35
|
+
function calcTotal(cart: Cart): number {
|
|
36
|
+
const subtotal = cart.items.reduce((s, i) => s + i.price * i.qty, 0);
|
|
37
|
+
return cart.coupon ? subtotal * (1 - cart.coupon.rate) : subtotal;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function checkout(cart: Cart, userId: string) {
|
|
41
|
+
const total = calcTotal(cart); // unit-test this directly
|
|
42
|
+
await db.orders.insert({ userId, total });
|
|
43
|
+
await email.send(userId, `You paid ${total}`);
|
|
44
|
+
return total;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`calcTotal` now tests with plain inputs and outputs — no DB, no email, no boundary values hidden behind I/O.
|
|
49
|
+
|
|
50
|
+
## Pure Function Extraction
|
|
51
|
+
|
|
52
|
+
Look for validation, transformation, and business rules buried inside request handlers. If logic is inline in `app.post('/api/orders', ...)`, it cannot be unit-tested without spinning up an HTTP server. Extract it as a standalone function.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// HARD TO TEST: rule logic trapped inside the route handler
|
|
56
|
+
app.post('/api/orders', async (req, res) => {
|
|
57
|
+
if (!req.body.items?.length) return res.status(400).json({ error: 'empty' });
|
|
58
|
+
const weight = req.body.items.reduce((w, i) => w + i.weight * i.qty, 0);
|
|
59
|
+
const shipping = weight > 20 ? 15 : weight > 5 ? 8 : 4; // business rule
|
|
60
|
+
res.json({ shipping });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// TESTABLE: rule is a pure function, handler is a thin adapter
|
|
64
|
+
export function shippingFor(items: Item[]): number {
|
|
65
|
+
const weight = items.reduce((w, i) => w + i.weight * i.qty, 0);
|
|
66
|
+
return weight > 20 ? 15 : weight > 5 ? 8 : 4;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
app.post('/api/orders', async (req, res) => {
|
|
70
|
+
if (!req.body.items?.length) return res.status(400).json({ error: 'empty' });
|
|
71
|
+
res.json({ shipping: shippingFor(req.body.items) });
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`shippingFor` is now testable with `it.each` across the 5/20 boundaries — no Express, no supertest.
|
|
76
|
+
|
|
77
|
+
## Interface Segregation
|
|
78
|
+
|
|
79
|
+
Flag classes that depend on broad interfaces (entire `PrismaClient`) when they only use 2-3 methods. Define a narrow interface with only the methods actually used, making test doubles trivial to implement.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// HARD TO TEST: must mock all of PrismaClient to fake one query
|
|
83
|
+
class UserLookup {
|
|
84
|
+
constructor(private prisma: PrismaClient) {}
|
|
85
|
+
byEmail(email: string) { return this.prisma.user.findUnique({ where: { email } }); }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// TESTABLE: narrow port, one-line fake
|
|
89
|
+
interface UserReader {
|
|
90
|
+
findUnique(args: { where: { email: string } }): Promise<User | null>;
|
|
91
|
+
}
|
|
92
|
+
class UserLookup {
|
|
93
|
+
constructor(private users: UserReader) {}
|
|
94
|
+
byEmail(email: string) { return this.users.findUnique({ where: { email } }); }
|
|
95
|
+
}
|
|
96
|
+
// test: new UserLookup({ findUnique: async () => ({ id: '1', email }) })
|
|
97
|
+
```
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coverage-analysis
|
|
3
|
+
description: >-
|
|
4
|
+
Measure and improve test coverage meaningfully. Covers Istanbul/V8/coverage.py
|
|
5
|
+
configuration, coverage gap analysis by risk, coverage-as-ratchet in CI (never let
|
|
6
|
+
it decrease), PR coverage diff checks, mutation testing for assertion quality, and
|
|
7
|
+
distinguishing meaningful from vanity coverage. Use when: "code coverage," "coverage
|
|
8
|
+
gap," "Istanbul," "coverage threshold," "coverage report," "branch coverage."
|
|
9
|
+
Not for: writing the tests that raise coverage — use unit-testing; coverage as a
|
|
10
|
+
tracked KPI trend over time — use qa-metrics.
|
|
11
|
+
Related: unit-testing, ci-cd-integration, qa-metrics, ai-qa-review.
|
|
12
|
+
license: MIT
|
|
13
|
+
metadata:
|
|
14
|
+
author: kindlmann
|
|
15
|
+
version: "2.0"
|
|
16
|
+
category: metrics
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<objective>
|
|
20
|
+
A suite at 90% line coverage with assertion-free tests catches zero bugs — line coverage
|
|
21
|
+
proves code ran, not that a regression would be caught. This skill measures the right
|
|
22
|
+
things (branch coverage, mutation score, critical-path coverage), gates them in CI with a
|
|
23
|
+
ratchet so coverage can only go up, and surfaces gaps by risk instead of chasing a vanity
|
|
24
|
+
number. It prevents the classic failure: a green coverage badge over a test suite that
|
|
25
|
+
never asserts anything meaningful, while the payment module sits at 30%.
|
|
26
|
+
</objective>
|
|
27
|
+
|
|
28
|
+
## Quick Route
|
|
29
|
+
|
|
30
|
+
| Situation | Go to |
|
|
31
|
+
|-----------|-------|
|
|
32
|
+
| Pick and configure a coverage provider | Coverage Tools → `references/tool-config.md` |
|
|
33
|
+
| Decide where to write tests next | Gap Analysis |
|
|
34
|
+
| Stop coverage from regressing in CI | Coverage as CI Gate → Ratchet Pattern |
|
|
35
|
+
| Show per-PR coverage to reviewers | Coverage as CI Gate → PR Diff |
|
|
36
|
+
| Tests run code but don't assert | Mutation Testing |
|
|
37
|
+
| Decide what to exclude / what target to set | Meaningful vs Vanity Coverage |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Discovery Questions
|
|
42
|
+
|
|
43
|
+
Check `.agents/qa-project-context.md` first — if it exists, use it and skip anything already answered there. Then:
|
|
44
|
+
|
|
45
|
+
1. **What test runner and coverage tooling is configured?** Check for `vitest.config.*` (coverage block), `jest.config.*` (coverageProvider), `.nycrc`, `c8` in scripts, or `[tool.coverage]` in `pyproject.toml`. The runner decides the install — Vitest pulls `@vitest/coverage-v8`, not c8 (see Coverage Tools).
|
|
46
|
+
2. **What is the current coverage level?** Run the existing coverage command and note line, branch, and function percentages. This is the baseline for the ratchet.
|
|
47
|
+
3. **Is coverage gated in CI?** Check GitHub Actions / GitLab CI for `--coverage`, `coverageThreshold`, `fail_under`, or `--cov-fail-under`. No gate means coverage is decorative.
|
|
48
|
+
4. **What is the target, and who set it?** A target without rationale ("the VP said 80%") leads to gaming. Targets should reflect risk tolerance and codebase maturity, not a round number.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Core Principles
|
|
53
|
+
|
|
54
|
+
**1. Coverage measures breadth, not depth.** A line being executed does not mean it is tested correctly. `expect(true).toBe(true)` executes the function but asserts nothing. Coverage tells you what code ran, not whether the tests would catch a bug — that is what mutation testing measures.
|
|
55
|
+
|
|
56
|
+
**2. Branch coverage matters more than line coverage.** A ternary `condition ? a : b` on one line counts as fully covered in line coverage even if only one branch ran. Line coverage does not guarantee branch coverage. Gate on branches, not just lines:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
function discount(price: number, isPremium: boolean): number {
|
|
60
|
+
return isPremium ? price * 0.8 : price;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Line coverage: 100% (the line executed). Branch coverage: 50% (only the true branch ran).
|
|
64
|
+
expect(discount(100, true)).toBe(80);
|
|
65
|
+
|
|
66
|
+
// Fix — assert BOTH paths so branch coverage reaches 100%:
|
|
67
|
+
expect(discount(100, true)).toBe(80);
|
|
68
|
+
expect(discount(100, false)).toBe(100);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**3. Ratchet pattern: never decrease, only increase.** Record current coverage as the minimum threshold. Every PR must meet or exceed it. Coverage climbs over time without forcing an artificial target up front.
|
|
72
|
+
|
|
73
|
+
**4. Focus on gaps by risk, not on the number.** A project at 85% is not automatically "better" than one at 75%. What matters is whether the untested slice contains payment, auth, or data-integrity logic. Analyze gaps by risk.
|
|
74
|
+
|
|
75
|
+
**5. New code has a higher bar than legacy code.** Require 90%+ on new code in PRs even if the project sits at 65%. This stops coverage decay without demanding a rewrite of legacy code.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Coverage Tools
|
|
80
|
+
|
|
81
|
+
Pick the provider by **test runner first**, then by how cleanly it maps to your build output.
|
|
82
|
+
|
|
83
|
+
| Runner / context | Install | Provider |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| **Vitest** | `@vitest/coverage-v8` (default) or `@vitest/coverage-istanbul` | `coverage.provider: 'v8'` / `'istanbul'` |
|
|
86
|
+
| **Jest** | bundled (`coverageProvider: 'v8'` or `'babel'`) | V8 or Istanbul/babel |
|
|
87
|
+
| **Non-Vitest Node** (`node:test`, plain mocha) | `c8` CLI | V8 via `c8 <command>` |
|
|
88
|
+
| **Legacy Istanbul CLI** | `nyc` | Istanbul instrumentation |
|
|
89
|
+
| **Python** | `pytest-cov` (wraps coverage.py) | coverage.py |
|
|
90
|
+
|
|
91
|
+
Two engines underneath:
|
|
92
|
+
|
|
93
|
+
- **V8 coverage** — built into the V8 engine, so it does not instrument source: faster, no Babel transform. For Vitest, install `@vitest/coverage-v8` (NOT `c8` — that is the standalone CLI for non-Vitest runners). For a plain `node:test` or mocha project, `c8` is the CLI wrapper around the same V8 data. Default for new Node/Vitest projects.
|
|
94
|
+
- **Istanbul** — instruments source code; slower but maps more reliably through transpilers and bundlers. Switch to it (`@vitest/coverage-istanbul`, or `nyc`) when V8 maps poorly. **Symptom that V8 maps poorly:** reported uncovered lines land on blank lines, closing braces, or decorators, or whole covered functions show as red — that means the source map is misattributing lines (common with certain TS bundlers / SWC configs). When you see that, flip to the Istanbul provider.
|
|
95
|
+
|
|
96
|
+
> **Node baseline:** `c8` 11.x and `nyc` 18.x are current. c8 11 still supports Node >=12; nyc 18 requires **Node 20 || >= 22**. If you must stay on Node 18, pin `nyc@^17` (c8 11 runs fine on Node 18). New projects should standardize on Node 20+.
|
|
97
|
+
|
|
98
|
+
See `references/tool-config.md` for the full provider configs (Vitest `coverage` block, `.nycrc.json`, Jest `coverageThreshold`, `pyproject.toml` / `.coveragerc.toml`), install commands, and run invocations.
|
|
99
|
+
|
|
100
|
+
### Merging coverage across test types
|
|
101
|
+
|
|
102
|
+
Unit, integration, and E2E runs each produce partial coverage. Combine them so a line covered only by an integration test isn't reported as a gap:
|
|
103
|
+
|
|
104
|
+
- **Vitest** — run as multiple projects/configs and let Vitest merge, or merge `coverage-final.json` outputs.
|
|
105
|
+
- **nyc** — `nyc merge .nyc_output merged.json && nyc report -t merged` combines `.json` files from separate runs.
|
|
106
|
+
- **coverage.py** — `coverage combine` after running each suite with `coverage run -p`.
|
|
107
|
+
|
|
108
|
+
Merge first, gate on the merged total. Don't gate each suite's coverage in isolation.
|
|
109
|
+
|
|
110
|
+
### Coverage Report Types
|
|
111
|
+
|
|
112
|
+
| Reporter | Output | Use Case |
|
|
113
|
+
|----------|--------|----------|
|
|
114
|
+
| `text` | Terminal table | Quick local check |
|
|
115
|
+
| `html` | Interactive HTML | Detailed local analysis, clicking through files |
|
|
116
|
+
| `lcov` | `lcov.info` file | SonarQube, Codecov, Coveralls integration |
|
|
117
|
+
| `json-summary` | `coverage-summary.json` | CI scripts, PR comments, dashboard metrics |
|
|
118
|
+
| `cobertura` | `cobertura-coverage.xml` | GitLab CI coverage visualization |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Gap Analysis
|
|
123
|
+
|
|
124
|
+
Coverage reports show which lines and branches did not run. Not all gaps are equal — prioritize by risk.
|
|
125
|
+
|
|
126
|
+
**Step 1: Generate the report.**
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npm run test:coverage
|
|
130
|
+
# Open coverage/index.html in a browser
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Step 2: Sort files by uncovered lines.** Parse `coverage-summary.json`, sort files by `(total - covered)` descending, focus on the top 20. A small script that reads the JSON summary and outputs file / line% / branch% / uncovered-count makes this repeatable.
|
|
134
|
+
|
|
135
|
+
**Step 3: Map gaps to risk.**
|
|
136
|
+
|
|
137
|
+
| Gap Location | Risk Level | Action |
|
|
138
|
+
|-------------|-----------|--------|
|
|
139
|
+
| Payment processing | Critical | Write tests immediately |
|
|
140
|
+
| Auth/permissions | Critical | Write tests immediately |
|
|
141
|
+
| Data validation | High | Add to next sprint |
|
|
142
|
+
| Error handling paths | High | Add to next sprint |
|
|
143
|
+
| Utility functions | Medium | Cover when modifying |
|
|
144
|
+
| UI formatting | Low | Skip unless regression-prone |
|
|
145
|
+
| Generated code | None | Exclude from coverage |
|
|
146
|
+
|
|
147
|
+
Include branch coverage in the sort, not just lines — a file at 100% line / 50% branch hides untested paths a line-only sort would rank as "done."
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Coverage as CI Gate
|
|
152
|
+
|
|
153
|
+
### Threshold Configuration
|
|
154
|
+
|
|
155
|
+
Set a **global threshold** as the project minimum, then layer **per-directory thresholds** stricter for critical code (payments, auth) than for utilities. Vitest uses glob keys under `thresholds` (e.g. `"src/payments/**": { lines: 95, branches: 90 }`); Jest uses path keys under `coverageThreshold`. Both support per-path overrides.
|
|
156
|
+
|
|
157
|
+
See `references/ci-gating.md` for the global, per-directory, and Jest per-file threshold config.
|
|
158
|
+
|
|
159
|
+
### Ratchet Pattern
|
|
160
|
+
|
|
161
|
+
Never let coverage decrease. Record the current level as the minimum and floor it upward when coverage improves. A ratchet script reads `coverage-summary.json`, compares each metric against a committed `.coverage-ratchet.json`, fails the build on any regression, and updates the baseline when coverage improves.
|
|
162
|
+
|
|
163
|
+
Commit `.coverage-ratchet.json` (e.g. `{ "lines": 82, "branches": 78, ... }`). In CI, run the ratchet script after tests. On main-branch merges, auto-commit the updated ratchet file if coverage improved.
|
|
164
|
+
|
|
165
|
+
See `references/ci-gating.md` for the full `coverage-ratchet.ts` script.
|
|
166
|
+
|
|
167
|
+
### PR Diff Coverage Gate
|
|
168
|
+
|
|
169
|
+
Require new code in a PR to meet a higher threshold (e.g. 90%) than the project baseline. In CI, use `git diff --name-only origin/main...HEAD` to identify changed files, then check their coverage from `coverage-summary.json`. Fail the pipeline if changed-file coverage falls below the threshold. This stops decay without rewriting legacy code.
|
|
170
|
+
|
|
171
|
+
Surface the diff to reviewers with `davelosert/vitest-coverage-report-action@v2` (reads the JSON summary) or `marocchino/sticky-pull-request-comment@v2` with a script that filters to changed files. See `references/ci-gating.md` for the full PR workflow.
|
|
172
|
+
|
|
173
|
+
**Hosted alternatives:** **Codecov**, **Coveralls**, and **Trunk Coverage** ship first-class differential PR coverage with merge-blocking gates and inline annotations. Most teams prefer these over hand-rolled diff scripts — pick one if you don't already have a coverage host. Codecov + GitHub: `codecov/codecov-action@v5` reads `lcov.info` and posts a PR diff comment automatically.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Mutation Testing
|
|
178
|
+
|
|
179
|
+
Mutation testing measures *assertion quality*, not just code execution. It mutates your source (flips a `>` to `>=`, deletes a line) and checks whether a test fails. A surviving mutant means a real bug your tests would miss. With **Stryker JS v9.6+** and **Vitest 4.1+** the cost is low enough to run on PR-changed files; **mutmut 3.x** covers Python.
|
|
180
|
+
|
|
181
|
+
### Targeting
|
|
182
|
+
|
|
183
|
+
Mutation testing is expensive on whole codebases — run it **incrementally**. Stryker's `incremental: true` (JSON cache) plus `--mutate` scoped to the git diff re-mutates only touched files; mutmut similarly mutates per path. Restrict to:
|
|
184
|
+
|
|
185
|
+
- Pure business logic (validators, calculators, transformers)
|
|
186
|
+
- Critical paths (payment, auth, data integrity)
|
|
187
|
+
- Code with high line coverage but suspect assertions (branch coverage > 90% but few assertion variants)
|
|
188
|
+
|
|
189
|
+
Skip UI rendering, glue code, and generated code.
|
|
190
|
+
|
|
191
|
+
See `references/mutation-testing.md` for the Stryker config (`stryker.config.json`, incremental, run on changed files only) and the mutmut invocation.
|
|
192
|
+
|
|
193
|
+
### Reading the score
|
|
194
|
+
|
|
195
|
+
A mutation score of 80% means 80% of injected bugs were caught. Lower than your coverage % is normal — many mutants land in untested branches the coverage report already flagged. The interesting signal is **high coverage + low mutation score**: code executes but assertions don't constrain it.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Meaningful vs Vanity Coverage
|
|
200
|
+
|
|
201
|
+
### Why 100% Coverage Is Usually Wrong
|
|
202
|
+
|
|
203
|
+
100% requires testing every branch of every line, including:
|
|
204
|
+
- Error handling for impossible states
|
|
205
|
+
- Default cases in exhaustive switches
|
|
206
|
+
- Framework lifecycle methods never called directly
|
|
207
|
+
- Defensive checks against corrupted data
|
|
208
|
+
|
|
209
|
+
Tests written to hit 100% are often trivial, brittle, and catch no real bugs.
|
|
210
|
+
|
|
211
|
+
### Diminishing Returns
|
|
212
|
+
|
|
213
|
+
| Coverage Range | Value | Effort |
|
|
214
|
+
|---------------|-------|--------|
|
|
215
|
+
| 0% to 60% | High — main paths, obvious regressions | Low |
|
|
216
|
+
| 60% to 80% | Medium — error paths, edge cases | Medium |
|
|
217
|
+
| 80% to 90% | Lower — unusual combinations, defensive code | High |
|
|
218
|
+
| 90% to 100% | Minimal — unreachable code, framework internals | Very high |
|
|
219
|
+
|
|
220
|
+
The sweet spot is **75–85%** for most projects. Critical paths (payments, auth) aim higher (**90%+**). Set the global threshold in the sweet spot and per-directory thresholds at 90%+ for payment/auth.
|
|
221
|
+
|
|
222
|
+
### What NOT to Cover
|
|
223
|
+
|
|
224
|
+
Exclude these — they inflate the denominator without adding value. Document each exclusion's justification in a CONTRIBUTING/coverage note so the exclude list can't quietly hide real gaps.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// vitest.config.ts / jest.config.js — exclude patterns
|
|
228
|
+
exclude: [
|
|
229
|
+
"**/*.d.ts", // Type definitions
|
|
230
|
+
"**/index.ts", // Barrel exports (re-exports only)
|
|
231
|
+
"**/*.stories.{ts,tsx}", // Storybook stories
|
|
232
|
+
"**/generated/**", // Auto-generated code (GraphQL, Prisma)
|
|
233
|
+
"**/migrations/**", // Database migrations
|
|
234
|
+
"**/__mocks__/**", // Test mocks
|
|
235
|
+
"**/types/**", // Type-only modules
|
|
236
|
+
]
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Quality Indicators Beyond Percentage
|
|
240
|
+
|
|
241
|
+
| Indicator | What It Measures | How to Get It |
|
|
242
|
+
|-----------|-----------------|---------------|
|
|
243
|
+
| **Mutation score** | Would tests catch a real bug? | Stryker / mutmut |
|
|
244
|
+
| **Branch coverage** | Are all conditional paths tested? | V8/Istanbul with branch reporting |
|
|
245
|
+
| **Critical path coverage** | Are payment/auth/data flows fully covered? | Per-directory thresholds |
|
|
246
|
+
| **Defect escape rate** | Do production bugs occur in tested code? | Post-incident analysis |
|
|
247
|
+
| **Coverage delta** | Is coverage improving or declining? | Ratchet pattern tracking |
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Anti-Patterns
|
|
252
|
+
|
|
253
|
+
### 1. Treating coverage as proof of quality
|
|
254
|
+
"We have 90% coverage so we're well-tested" is dangerous. Coverage says code executed, not that behavior was verified. **Fix:** pair the percentage with a mutation score on critical modules; a test with no assertions should drop the mutation score even at 100% line coverage.
|
|
255
|
+
|
|
256
|
+
### 2. Excluding files to inflate numbers
|
|
257
|
+
Adding hard-to-test files (error handlers, integration modules) to the exclude list hides the most important gaps. **Fix:** only exclude genuinely untestable code — generated files, type definitions, barrel exports — and justify each exclusion in writing.
|
|
258
|
+
|
|
259
|
+
### 3. Writing trivial tests to hit targets
|
|
260
|
+
`it("should exist", () => expect(MyClass).toBeDefined())` adds coverage without value. **Fix:** every test verifies a behavior that, if broken, affects users; mutation testing flags these no-op tests.
|
|
261
|
+
|
|
262
|
+
### 4. Global threshold without per-module analysis
|
|
263
|
+
An 80% global threshold passes even if payments sits at 30%, as long as utilities inflate the average. **Fix:** per-directory thresholds at 90%+ for payment/auth.
|
|
264
|
+
|
|
265
|
+
### 5. Coverage threshold set once, never adjusted
|
|
266
|
+
A team stuck at 78% for six months isn't improving. **Fix:** the ratchet floors upward automatically; review it quarterly and investigate stagnation.
|
|
267
|
+
|
|
268
|
+
### 6. Ignoring branch coverage
|
|
269
|
+
Line coverage reports 100% on `const r = cond ? a : b` even if one branch never runs. **Fix:** always report and gate on `branches` alongside `lines`.
|
|
270
|
+
|
|
271
|
+
### 7. Coverage from E2E tests only
|
|
272
|
+
A single E2E test touches 60% of the codebase without testing any edge case — broad and shallow. **Fix:** measure unit/integration coverage separately from E2E and gate on the unit/integration total; E2E coverage is a bonus signal, not the gate.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Verification
|
|
277
|
+
|
|
278
|
+
Prove the gate actually fails on regression — a green build with no enforcement is worthless.
|
|
279
|
+
|
|
280
|
+
1. **Threshold fires:** temporarily lower one committed metric below current (or delete one passing test), run `npm run test:coverage` (or `pytest --cov=src --cov-fail-under=80`), and confirm a **non-zero exit code**. Restore afterward.
|
|
281
|
+
2. **Ratchet fires:** with `.coverage-ratchet.json` committed, drop a test so coverage regresses, run the ratchet script, and confirm it prints `FAIL: ... coverage dropped` and exits 1.
|
|
282
|
+
3. **Branch gate is on:** confirm the report shows a `branches` column and that `branches` appears in the threshold config — not just `lines`.
|
|
283
|
+
4. **PR diff renders:** open a draft PR touching one source file and confirm the coverage-diff comment posts with the changed file's numbers.
|
|
284
|
+
|
|
285
|
+
If step 1 exits 0 after you lowered coverage, the gate is not wired — fix that before claiming Done.
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Done When
|
|
290
|
+
|
|
291
|
+
- Coverage runs automatically in CI on every push (no manual step to generate the report).
|
|
292
|
+
- Coverage threshold is enforced in CI: the build exits non-zero when line **or branch** coverage drops below the defined minimum (verified per Verification step 1).
|
|
293
|
+
- Coverage report is published as a CI artifact (HTML + `json-summary`) and a per-PR coverage delta posts to PR comments.
|
|
294
|
+
- The coverage config contains an exclude list and per-directory thresholds (90%+ for payments/auth), and a CONTRIBUTING/coverage doc lists each exclusion's justification.
|
|
295
|
+
- Ratchet is wired: `.coverage-ratchet.json` is committed, the ratchet script runs in CI, and the build fails on regression from the recorded baseline (verified per Verification step 2).
|
|
296
|
+
|
|
297
|
+
## Reference Files (in `references/`)
|
|
298
|
+
|
|
299
|
+
- **tool-config.md** — Full provider configs for Vitest (`@vitest/coverage-v8` / `-istanbul`), the c8 CLI for non-Vitest runners, nyc, Jest, and coverage.py, with install and run commands.
|
|
300
|
+
- **ci-gating.md** — PR coverage-diff workflow, global/per-directory/per-file thresholds, and the `coverage-ratchet.ts` script.
|
|
301
|
+
- **mutation-testing.md** — Stryker (`stryker.config.json`, incremental) and mutmut configuration for measuring assertion quality.
|
|
302
|
+
|
|
303
|
+
## Related Skills
|
|
304
|
+
|
|
305
|
+
- **unit-testing** — Writing the tests that raise coverage: mocking strategies, framework-specific config, and Vitest `coverage.changed` for changed-files-only coverage in CI. Go there to author tests; this skill measures and gates them.
|
|
306
|
+
- **ci-cd-integration** — Pipeline wiring for coverage gates, artifact storage, and PR comments.
|
|
307
|
+
- **qa-metrics** — Coverage as a tracked KPI trend over time alongside mutation score and defect escape rate. Go there for dashboards and trends; this skill is per-repo configuration and gating.
|
|
308
|
+
- **ai-qa-review** — AI-assisted identification of undertested paths and Vitest browser mode for component coverage parity.
|