Skip to content
Back to skills

Qe Test Impact Analyzer

ASecurity

Tier-2 specialist — answers "given this diff, which tests must I run?" Consumes git diff, the estate capability graph (changed-since/BlastRadius over endpoint + affordance nodes), call-graph signal, and historical coverage from the DomainStore ledger to rank existing scenarios by probability of catching a regression in the change. The answer to the #1 question every CI conversation has: "why did we run all these tests for a one-line change?" Use when: test impact analysis, TIA, selective tes...

  • 9 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 18, 2026
ai-agentspythongobashsqlnodetestinggitapi

Works with

  • cli
  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 18, 2026

npx -y skills add mikeparcewski/wicked-garden --skill qe-test-impact-analyzer --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Qe Test Impact Analyzer?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Qe Test Impact Analyzer
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mikeparcewski-qe-test-impact-analyzer/badge)](https://www.skillsdirectory.com/skills/mikeparcewski-qe-test-impact-analyzer)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: wicked-garden-qe-test-impact-analyzer
description: |
  Tier-2 specialist — answers "given this diff, which tests must I run?"
  Consumes git diff, the estate capability graph (changed-since/BlastRadius
  over endpoint + affordance nodes), call-graph signal, and historical
  coverage from the DomainStore ledger to rank existing scenarios by
  probability of catching a regression in the change. The answer to the #1 question every CI
  conversation has: "why did we run all these tests for a one-line change?"

  Use when: test impact analysis, TIA, selective testing, "which tests
  should I run", "affected tests for this diff", CI test selection, PR-scoped
  test runs, smart test selection.

  <example>
  Context: A PR touched {WT_LIB}/gate.mjs and agents/acceptance-test-reviewer.md.
  user: "Which tests are affected by this diff?"
  <commentary>Use test-impact-analyzer — it grepped the diff, ran
  call-graph discovery to find dependent scenarios, queried the ledger for
  historical coverage, and ranked the top 20 affected scenarios by
  impact × exposure.</commentary>
  </example>
metadata:
  role: worker
---

# Test Impact Analyzer

You answer "which tests catch this diff?" with evidence. You do not run
tests yourself — you rank the existing scenario set so a CI system or a
developer can run the top N with high confidence that a regression in the
diff will be caught. The #1 goal: "why did we run all 2000 tests for a
one-line change?" gets a crisp answer — "because you didn't ask me; the
top 40 would have caught it at 1/50th the cost."

## Runtime
Script-backed steps use the `wicked-garden` launcher: `wicked-garden run <plugin-root-relative path, e.g. scripts/…> [args]`. It is on PATH after `npm i -g wicked-garden`; otherwise use `npx wicked-garden run …`; inside a wicked-crew run it is `"$WICKED_GARDEN_ROOT/scripts/wicked-garden"`.
If none of these is available, or Python 3 is missing, skip the script-backed step, say so, and follow the manual alternative where one is given next to it — never invent the script's output.
Relative paths in this skill are relative to the directory that contains this SKILL.md.

## 1. Inputs

- **Diff reference** — defaults to `main`. Accepts `HEAD~N`, a branch name,
  or a specific SHA. The agent runs `git diff --name-only <ref>` and
  `git diff --stat <ref>` to discover changed files and their churn.
- **Scenario registry** — all active scenarios from DomainStore
  (`SELECT id, name, source_path, body FROM scenarios WHERE deleted = 0`).
  Read via `sqlite3 .wicked-qe/wicked-qe.db` using bound params
  (scenarios are keyed by project_id; agent discovers project_id from
  `.wicked-qe/config.json`).
- **Coverage history** — the ledger's historical coverage signal. For each
  scenario, which files did its runs touch? Captured under
  `evidence/<run-id>/coverage.json` when an executor writes it. Older runs
  may not have it — that's fine; the ranker degrades gracefully.
- **Estate capability graph (preferred graph signal)** — when the target
  repo is indexed in wicked-estate, its TH-15 extractor rule packs mint
  capability nodes that carry their declaring file:
  `.wicked-estate-extractors/endpoints.toml` (wicked-crew) turns fastify
  route registrations into `http-endpoint` nodes (kind `other:endpoint`,
  ids like `endpoint:get:/api/v1/runs/:id/gate`), and
  `.wicked-estate-extractors/affordances.toml` (wicked-studio) turns
  `data-testid` declarations into `ui-affordance` nodes (kind
  `other:affordance`, ids like `affordance:run-card`, dynamic testids
  normalised to `affordance:seat-signin-*` exactly as the committed
  testid-inventory.json does). One query answers "which capabilities
  changed":

  ```bash
  # capability nodes in the diff = entries whose kind is
  # {"other":"endpoint"} or {"other":"affordance"}
  wicked-estate changed-since "${DIFF_REF}" --json \
    > "${EVIDENCE_DIR}/changed-symbols.json"
  ```

  The reverse question — "who serves this capability" — is BlastRadius:
  `wicked-estate blast-radius "/runs/:id/gate"` (or an affordance label like
  `"action-preview"`) returns the declaring file(s). Probe availability
  first, fail-open:
  `wicked-garden run scripts/_estate_client.py health`
  (db resolution mirrors the client: `WICKED_ESTATE_DB` >
  `./.wicked-estate/graph.db` > the binary's default). **Honest
  degradation, same rule as the campaign action's Lens 1:** estate
  unreachable or the repo unindexed → contribute ZERO capability signal,
  drop the term, renormalize, and say so in the ranking reasons — never
  silently substitute grep output as graph signal.
- **Call-graph (optional)** — if `tree-sitter`, `ast-grep`, or language
  servers (`tsserver`, `pyright`) are on PATH, the agent builds a local
  reverse-dependency map for the diff. Best-effort; the agent falls back
  to path-prefix matching when unavailable.

Refuse if none of these are present: no git, no scenarios in ledger, and
no ability to read the repo. Return `ERR_INSUFFICIENT_SIGNAL`.

## 2. Discovery + ranking

```bash
# 1. Discover the diff. `|| true` keeps the pipeline alive even when the
# ref is missing — the empty-result check on the next line is the single
# source of truth for "no changes".
DIFF_REF="${1:-main}"
CHANGED_FILES="$(git diff --name-only "${DIFF_REF}"...HEAD 2>/dev/null || true)"
if [ -z "${CHANGED_FILES}" ]; then
  echo "ERR_EMPTY_DIFF: no changes vs ${DIFF_REF}"; exit 3
fi
echo "${CHANGED_FILES}" > "${EVIDENCE_DIR}/changed-files.txt"

# 2. Churn per file (line count of the diff).
git diff --stat "${DIFF_REF}"...HEAD > "${EVIDENCE_DIR}/diff-stat.txt"

# 3. Scenario registry from DomainStore (parameter-bound, not interpolated).
sqlite3 -json ".wicked-qe/wicked-qe.db" <<EOF > "${EVIDENCE_DIR}/scenarios.json"
SELECT id, name, source_path, body
FROM scenarios
WHERE deleted = 0
  AND project_id = (SELECT id FROM projects WHERE name = :project);
EOF

# 4. Historical coverage — files each scenario's most-recent passing run touched.
sqlite3 -json ".wicked-qe/wicked-qe.db" <<EOF > "${EVIDENCE_DIR}/coverage-history.json"
.parameter set :window 30
SELECT s.id AS scenario_id, r.evidence_path
FROM scenarios s
JOIN runs r ON r.scenario_id = s.id
JOIN verdicts v ON v.run_id = r.id
WHERE v.verdict IN ('PASS', 'CONDITIONAL')
  AND r.started_at >= datetime('now', '-' || :window || ' days')
  AND s.deleted = 0
ORDER BY r.started_at DESC;
EOF

# 5. For each scenario with a recent PASS, read evidence/<run-id>/coverage.json
# (if present) and build the file-touched set.

# 6. Changed capabilities (estate-indexed repos only; skip honestly otherwise).
# Filter changed-symbols.json down to the capability node kinds and record
# {id, label, file} rows — the "which capabilities changed" answer that
# scenario selection keys on.
python3 - "$EVIDENCE_DIR" <<'PY'
import json, sys, os
ev = sys.argv[1]
path = os.path.join(ev, "changed-symbols.json")
caps = []
if os.path.exists(path):
    for row in json.load(open(path)):
        kind = row.get("kind")
        tag = kind.get("other") if isinstance(kind, dict) else None
        if tag in ("endpoint", "affordance"):
            caps.append({"kind": tag, "label": row.get("name"),
                         "file": row.get("file", "")})
json.dump(caps, open(os.path.join(ev, "changed-capabilities.json"), "w"),
          indent=2)
PY
```

## 3. Scoring — impact × exposure

Each scenario gets a score in `[0, 1]`:

```
score = 0.50 * direct_file_overlap        # scenario touched a changed file
      + 0.25 * graph_reach                # max(capability_overlap, call_graph_reach)
      + 0.15 * path_prefix_similarity     # same package / folder as the diff
      + 0.10 * recent_flake_penalty       # 1.0 if scenario has flaked this week, else 0.5
```

`graph_reach = max(capability_overlap, call_graph_reach)`:

- **capability_overlap** (estate term, preferred) — 1.0 when the scenario
  references a capability in `changed-capabilities.json`: an API scenario
  naming an endpoint path (`/api/v1/runs/:id/gate`) whose `endpoint:` node
  is in the changed set, or a browser scenario whose body / bound
  deterministic spec drives a `data-testid` whose `affordance:` node is in
  the changed set (match dynamic nodes like `seat-signin-*` by glob).
  Extract candidate references by scanning the scenario body and spec for
  `data-testid` selectors and `/api/…` paths — the references are data the
  scenario already carries, only the CHANGED set comes from the graph.
- **call_graph_reach** (local fallback) — the pre-estate signal from
  tree-sitter / ast-grep / language servers, unchanged.

Neither available: drop the term and renormalize (see Failure modes).

Weights are tunable via `.wicked-qe/config.json`'s `tia.weights` map;
they default to the above. Agent reads the config once and respects
overrides but logs the effective weights into the evidence file.

## 4. Evidence output

Write under `.wicked-qe/evidence/<run_id>/`:

| File                         | kind          | notes                                                          |
|------------------------------|---------------|----------------------------------------------------------------|
| `changed-files.txt`          | `log`         | One path per line                                              |
| `diff-stat.txt`              | `log`         | `git diff --stat` output                                       |
| `changed-symbols.json`       | `misc`        | `wicked-estate changed-since --json` (estate-indexed repos)    |
| `changed-capabilities.json`  | `misc`        | Endpoint/affordance nodes in the diff — "which capabilities changed" (empty array when estate degraded) |
| `scenarios.json`             | `misc`        | Scenario registry snapshot at analysis time                    |
| `coverage-history.json`      | `coverage`    | Per-scenario evidence-path lookups                             |
| `impact-ranking.json`        | `misc`        | Ranked scenarios with score + reasons[]                        |
| `impact-summary.md`          | `log`         | Human summary: top N, reasoning, tail (N+1 .. total)           |
| `impact-coverage-gap.md`     | `log`         | Files in the diff with zero scenario coverage (the real risk)  |

`impact-ranking.json` shape:

```json
[
  {
    "scenario_id": "uuid",
    "scenario_name": "auth-login-valid-credentials",
    "score": 0.87,
    "reasons": [
      "direct_overlap: {WT_LIB}/auth.mjs (scenario ran this file)",
      "capability: endpoint:post:/api/v1/open changed (packages/crew/src/api/routes.ts in diff); scenario drives POST /api/v1/open",
      "call_graph: acceptance-test-writer transitively imports {WT_LIB}/auth.mjs",
      "path_prefix: both under lib/"
    ],
    "last_verdict": "PASS",
    "last_run_at": "2026-04-18T09:13:02Z"
  }
]
```

## 5. DomainStore writes

Through the `wicked-ledger` DomainStore (`import { createDomainStore } from "wicked-ledger"`):

```js
// Record the impact analysis as a task with the ranked list so CI can
// consume it via wicked-garden-qe insight "most impactful tests for HEAD".
store.create("tasks", {
  project_id: PROJECT_ID,
  title: `TIA for ${DIFF_REF}...HEAD — top ${TOP_N} scenarios to run`,
  status: "open",
  assignee_skill: "test-impact-analyzer:consume",
  body: JSON.stringify({
    diff_ref: DIFF_REF,
    changed_files: CHANGED_FILES,
    ranked_scenarios: rankedList.slice(0, TOP_N),
    coverage_gap: uncoveredFiles,
    confidence: "high" | "medium" | "low",
  }),
});
```

Also emit (optional, fire-and-forget via `wicked-ledger`'s `emitBusEvent`):

```
wicked.test.impact.computed  payload: { diff_ref, top_n, scenario_count, coverage_gap_count }
```

No `verdicts` row — TIA is advisory, not an adjudication. The caller runs
the top N and the scenario-executor / acceptance pipeline produces verdicts.

## 6. Failure modes

- `ERR_EMPTY_DIFF` — `git diff --name-only <ref>...HEAD` returned nothing. Exit 3.
- `ERR_NO_SCENARIOS` — ledger has zero active scenarios for this project. Exit 3
  with remediation: "author scenarios first via wicked-garden-qe author".
- `ERR_INSUFFICIENT_SIGNAL` — neither git nor ledger is readable. Exit 3.
- Stale coverage: if all scenarios' coverage files are > 90 days old, set
  confidence: "low" and print a loud warning — but still produce a ranking.
  Path-prefix similarity is the fallback; better than nothing.
- Estate degraded (binary missing, db unindexed, `changed-since` non-zero
  exit): write `changed-capabilities.json` as `[]`, note "estate: degraded"
  in the ranking reasons, and let `graph_reach` fall back to
  `call_graph_reach` alone. Never substitute grep output as capability
  signal (same honesty rule as the campaign action's Lens 1).
- Call-graph tool ALSO missing: note in the ranking reasons that graph
  signal was skipped entirely. `score` drops the `0.25 * graph_reach`
  term, renormalized.

## 7. Integration

- Command `wicked-garden-qe execute --selective --since <ref>` should dispatch
  this agent, read `impact-ranking.json`, and run the top-N (default 40).
  Flag `--selective-confidence-floor 0.3` filters by score.
- `wicked-garden-qe insight "most impactful tests for HEAD"` answers from the
  most recent `tasks` row with `assignee_skill: test-impact-analyzer:consume`.
- The agent is strictly read-only for source code. Bash usage is scoped to
  `git`, `sqlite3`, and optional call-graph tools.

## References

- `wicked-ledger` domain-store — table allowlist, parameter binding
- `wicked-ledger` oracle-queries — query catalog
- the `wicked-garden-qe` skill's `refs/execute.md` — `--selective` flag
- the `wicked-garden-qe` skill's `refs/campaign.md` — Lens 1 shares the same
  estate capability inventory (TH-7); this skill's `changed-capabilities.json`
  is the change-scoped slice of it
- wicked-estate `docs/extractor-sdk.md` Part 2 — the ExtraEdgeExtractor rule
  format behind the TH-15 packs (`.wicked-estate-extractors/endpoints.toml`
  in wicked-crew, `.wicked-estate-extractors/affordances.toml` in
  wicked-studio)
- `wicked-garden-qe-coverage-archaeologist` — sibling; covers the inverse (untested code), not affected tests

## Helper resolution (`{WT_LIB}`)

`{WT_LIB}` is the plugin's own qe helper directory — the helper modules ship
in-catalog (`scripts/qe/lib/`, ported from the retired wicked-testing package <!-- historical -->
in Phase 6c). Resolve it (cross-platform):

```bash
WT_LIB="$(wicked-garden path scripts/qe/lib)"
```

## wicked-ledger resolution

`wicked-ledger` (npm) is the QE data layer — DomainStore, oracle queries, and
manifest building. Import-style snippets above must run where the package
resolves: the target project's `node_modules` (`npm i --no-save wicked-ledger`)
or any directory with it installed. The pinned range lives in
`.claude-plugin/plugin.json` → `wicked_ledger_version`.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.