agentsclimarketplace

Test impact

Skill Borda/AI-Rig/plugins/codemap-py/claude-skills/test-impact

Identify which tests need rerunning after a code change — traces static call graph (function-level) or import graph (module-level) to find affected test files, then emits a ready-to-run pytest command. TRIGGER when: user asks which tests are affected by a change; phrases: "which tests are affected", "what tests cover this", "test impact of", "what tests to rerun".From its SKILL.md

Install
npx -y skills add Borda/AI-Rig --skill test-impact

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 24 stars24 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

8.3 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it

<objective>

Identifies minimal test set affected by changing a function or module. Uses codemap static analysis — no test execution needed. Result: ready-to-run pytest command covering only impacted tests.

Two input modes:

  • Function-level (module::symbol) — BFS over reverse call graph; finds every test calling changed function, directly or transitively. Includes tests mocking the symbol (mock_patches).
  • Module-level (bare module) — BFS over reverse import graph; finds every test importing module through any chain. Includes tests mocking any symbol in module.

not_covered: dynamic dispatch, hook callbacks, string-dispatch callers — same blind spot as fn-blast. Surface caveat, log gap.

NOT for: finding all callers of a function (use /codemap-py:query-code fn-blast <module::symbol>); querying module deps or blast radius (use /codemap-py:query-code); running/executing tests (identified here, not executed).

</objective> <inputs>
  • $ARGUMENTS: <qname> [--no-mocks]
    • qnamemodule::symbol (function-level) or bare dotted module (module-level)
    • --no-mocks — exclude mock-only test files (no call/import path)
    • Omitted → AskUserQuestion in Step 1
</inputs> <workflow>

Step 0 — Ensure index

# timeout: 10000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
_CM_PROJ=$(git rev-parse --show-toplevel 2>/dev/null | xargs basename 2>/dev/null || basename "$PWD")
_IDX="${CODEMAP_INDEX_DIR:-.cache/codemap}"
INDEX="${_IDX}/${_CM_PROJ}.json"

SQ=$(python3 "${CLAUDE_PLUGIN_ROOT:-plugins/codemap-py}/bin/locate_scan_query.py" 2>/dev/null)
[ -z "$SQ" ] && { echo "scan-query not found — install codemap-py plugin first"; exit 1; }
echo "$SQ" > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-ti-sq-${CSID}"

[ ! -f "$INDEX" ] && echo "No index found — will build via codemap-py:scan-codebase"

Auto-build opt-out via SCAN_NO_AUTOBUILD=1 (index used exactly as-is — no refresh, no full build); build wall-time echoed when it runs, keeps build cost separable from query cost.

If $INDEX not found:

  • SCAN_NO_AUTOBUILD=1 set → print ! codemap index missing and SCAN_NO_AUTOBUILD=1 — refusing to auto-build. Build it manually first: /codemap-py:scan-codebase and exit 1.
  • otherwise → run scan-index in the foreground (wait until it finishes) then continue. (Not the codemap-py:scan-codebase skill — it is disable-model-invocation:true, user-slash-only; build via the scan-index binary.)

If index already exists:

# timeout: 30000
_CM_PROJ=$(git rev-parse --show-toplevel 2>/dev/null | xargs basename 2>/dev/null || basename "$PWD")
_IDX="${CODEMAP_INDEX_DIR:-.cache/codemap}"
if [ "${SCAN_NO_AUTOBUILD:-0}" = "1" ]; then
    echo "[codemap] SCAN_NO_AUTOBUILD=1 — using existing index as-is (no refresh)"
else
    _CM_BUILD_T0=$(date +%s)
    # forward CODEMAP_INDEX_DIR; ensures scan-index writes to same path as INDEX
    CODEMAP_INDEX_DIR="${_IDX}" "${CLAUDE_PLUGIN_ROOT:-plugins/codemap-py}/bin/scan-index" --incremental \
        && echo "[codemap] index built in $(( $(date +%s) - _CM_BUILD_T0 ))s" \
        || printf "⚠ scan-index --incremental failed — index may be stale; continuing\n"
fi

After Skill() or incremental refresh, re-verify index still present:

# timeout: 5000
_CM_PROJ=$(git rev-parse --show-toplevel 2>/dev/null | xargs basename 2>/dev/null || basename "$PWD")
_IDX="${CODEMAP_INDEX_DIR:-.cache/codemap}"
INDEX="${_IDX}/${_CM_PROJ}.json"
[ -f "$INDEX" ] || { printf "! Index not found after refresh at %s — check CODEMAP_INDEX_DIR or re-run /codemap-py:scan-codebase\n" "$INDEX"; exit 1; }

Step 1 — Parse arguments

Extract QNAME and NO_MOCKS flag from $ARGUMENTS.

# timeout: 5000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
_CM_PROJ=$(git rev-parse --show-toplevel 2>/dev/null | xargs basename 2>/dev/null || basename "$PWD")
ARGS="${ARGUMENTS:-}"
QNAME=$(echo "$ARGS" | awk '{print $1}')
MOCKS_FLAG=$(echo "$ARGS" | grep -q -- "--no-mocks" && echo "--no-mocks" || echo "")
echo "$QNAME" > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-ti-qname-${CSID}"
echo "$MOCKS_FLAG" > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-ti-mocks-${CSID}"

If $ARGUMENTS empty → AskUserQuestion: "Which function or module changed?" Options: (a) Enter module::symbol for function-level · (b) Enter bare module name for module-level · (c) Cancel — exit without running test-impact analysis. After the user answers, set QNAME from the answer and write it to the tmpfile before proceeding to Step 2:

# timeout: 5000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
_CM_PROJ=$(git rev-parse --show-toplevel 2>/dev/null | xargs basename 2>/dev/null || basename "$PWD")
QNAME="<answer from AskUserQuestion>"
echo "$QNAME" > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-ti-qname-${CSID}"

Multi-symbol guard: $ARGUMENTS may contain multiple space-separated tokens (e.g. mypackage.auth::validate mypackage.auth::parse). awk '{print $1}' silently truncates to first. If $ARGUMENTS has more than one token after stripping --no-mocks, print ⚠ test-impact accepts one symbol at a time — using first token only: $QNAME. Run separately for each remaining symbol.

Step 2 — Run test-impact query

# timeout: 10000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
_CM_PROJ=$(git rev-parse --show-toplevel 2>/dev/null | xargs basename 2>/dev/null || basename "$PWD")
IFS= read -r QNAME < "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-ti-qname-${CSID}" 2>/dev/null || QNAME=""
IFS= read -r MOCKS_FLAG < "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-ti-mocks-${CSID}" 2>/dev/null || MOCKS_FLAG=""
IFS= read -r SQ < "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-ti-sq-${CSID}" 2>/dev/null || SQ=""
RESULT=$("$SQ" test-impact "$QNAME" $MOCKS_FLAG 2>/dev/null)
NOT_COVERED=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps(d.get('index',{}).get('not_covered',[])))" 2>/dev/null || echo "[]")
HINT=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('index',{}).get('hint',''))" 2>/dev/null || echo "")
TOTAL=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('test_files',[])))" 2>/dev/null || echo "0")
PYTEST_CMD=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('pytest_cmd',''))" 2>/dev/null || echo "")
echo "$NOT_COVERED" > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-ti-not-covered-${CSID}"
echo "$HINT"        > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-ti-hint-${CSID}"
echo "$TOTAL"       > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-ti-total-${CSID}"
echo "$PYTEST_CMD"  > "${TMPDIR:-/tmp}/codemap-${_CM_PROJ}-ti-pytest-cmd-${CSID}"

Parse JSON output from $RESULT:

  • test_files — list of test file paths
  • pytest_cmd — ready-to-run command
  • via_call / via_mock — breakdown of how tests were found
  • index.not_covered — surface as caveat if non-empty
  • index.hint — include as suggestion

haiku JSON parse guard: scan-query JSON output may be prefixed/suffixed with log/warning lines under haiku model. Always extract JSON via python3 -c "import sys,json; ..." piping stdin — never assume raw output valid JSON. Parsing fails (ValueError/JSONDecodeError) → print ! scan-query returned non-JSON output — try /codemap-py:scan-codebase to rebuild index and exit 1.

Step 3 — Output

When total == 0: report "No tests found via static analysis. Try full suite or check with grep -rn <symbol_name> tests/."

When total > 0:

## Test impact: <qname>

**Affected tests** (<total> files, <via_call> via call/import graph, <via_mock> via mocks):
<test_files as bullet list>

**Run:**

<pytest_cmd>


<if not_covered non-empty>
**Caveat:** dynamic-dispatch / hook-callback callers are not in the static graph — <hint>.
</if>

Output routing: if total >= 5 write to .temp/output-test-impact-<branch>-<YYYY-MM-DD>.md.

</workflow>

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.