find-untested-sources
MANDATORY for static source-to-test pairing: find or list source files/modules without corresponding tests, or suggest test locations from repository structure. Invoke even for a tiny package; do not substitute manual globbing.
Install / Use
npx skills add dotnet/skills --skill find-untested-sourcesInstalls into whichever agent you are using.
SKILL.md
Installable skill definition
Quality Score
Category
Development & EngineeringSupported Platforms
Our assessment of find-untested-sources
find-untested-sources scores 87/100 on our quality scale, 886th of 2,398 Development & Engineering skills we index (top 37%).
Its SKILL.md is 13 KB long, well organised into 25 sections with 4 code examples: a thorough specification that gives an agent plenty to work with.
With 5,471 GitHub stars, it is one of the more widely adopted skills in the catalogue.
Maintenance, license and trust
- The repository was last updated 2 days ago, so find-untested-sources is actively maintained.
- It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
- Its trust signals score 100/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.
find-untested-sources compared with similar skills
All 4 of these similar skills score higher than find-untested-sources; compare them before choosing.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| find-untested-sources (this skill)by dotnet | 87 | 5.5k | 2d ago | SKILL.md |
| Agent-Reachby Panniantong | 100 | 85.6k | 11d ago | CLAUDE.md |
| headroomby headroomlabs-ai | 100 | 73.9k | today | CLAUDE.md |
| ai-job-searchby MadsLorentzen | 100 | 44.0k | 5d ago | CLAUDE.md |
| claude-howtoby luongnv89 | 100 | 41.7k | today | CLAUDE.md |
Frequently asked questions
- How do I install find-untested-sources?
- Run
npx skills add dotnet/skills --skill find-untested-sources. The install tabs above show the steps for each supported agent. - Which AI agents does find-untested-sources work with?
- It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
- Is find-untested-sources safe to use?
- It is MIT-licensed and scores 100/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
- Is find-untested-sources still maintained?
- The repository was last updated 2 days ago, so find-untested-sources is actively maintained.
Skill content
View source on GitHubname: find-untested-sources description: > MANDATORY for static source-to-test pairing: find or list source files/modules without corresponding tests, or suggest test locations from repository structure. Invoke even for a tiny package; do not substitute manual globbing. Uses Roslyn for C#/.NET and tree-sitter for Python, TS/JS, Go, Java, Rust, Ruby, Kotlin, Swift, PowerShell, and C++. DO NOT USE FOR: real line/branch/Cobertura data, coverage-backed test priorities, CRAP risk, or grading existing tests. license: MIT
Find Untested Sources
Purpose
Coverage tools answer "which lines were executed?" — they require a green build and a passing test run, which is minutes-to-tens-of-minutes on a real repo. The question this skill answers is different and much cheaper:
Which source files have no test file referencing any of their declared types/symbols?
That's the question an agent asks before writing a new test — and it can be answered statically in a few seconds by parsing source files, with no build, no dependency resolution, and no compilation. The output is a deterministic test-pairing map that lets the agent pick the next file to test without reading the entire codebase first.
Two engines — pick one
This skill ships two interchangeable analyzers with a compatible JSON contract:
| Engine | Script | Use when |
|--------|--------|----------|
| Roslyn (C#) | scripts/Find-UntestedSources.cs | The repo is .NET-only. Parses every .cs file with the Roslyn syntax API and does strict namespace disambiguation, so it is materially more accurate on duplicated short names like Settings or Context. |
| tree-sitter (polyglot) | scripts/find_untested_sources.py | The repo is not exclusively C#, or you want one tool across C#, Python, TypeScript/JavaScript, Go, Java, Rust, Ruby, Kotlin, Swift, PowerShell, and C++. |
For a .NET-only repository, prefer the Roslyn engine — its namespace-aware pairing beats the polyglot engine's identifier overlap.
Required workflow
- Use the narrowest repository or package root named by the caller. Do not scan a parent workspace when the request identifies a subdirectory.
- Execute the appropriate analyzer once. Do not replace analyzer execution with
manual globbing, filename matching, or visual inspection.
For polyglot analysis, pass
--include-testedwhen the answer must distinguish paired sources from unpaired sources. "Static pairing only" prohibits compiling the target repository and running its tests; it does not prohibit launching this skill's parse-only analyzer. State that distinction briefly when the caller also says "do not build." Treat analyzer dependencies as environment prerequisites: do not install packages, try the wrong engine, build the repository, or fall back to a manual scan when an analyzer invocation fails. Report the prerequisite failure instead. - Base the result on the analyzer's JSON. Preserve its paired/unpaired classification and suggested relative path; do not guess a different path.
- When the caller named a subdirectory, prefix analyzer-relative paths with that subdirectory so reported paths are workspace-relative.
- Report the requested result plus the static-pairing coverage caveat. Do not append build, package-install, test-run, or coverage commands. When paired sources exist, name their covering test files so the unpaired classification is auditable.
When to Use
- User asks "where should I add tests based on source pairing?", "which files have no tests?", "find unpaired source files", or "give me a static test gap list".
- Before invoking a test-generation agent, to produce a source-pairing worklist.
- After generating tests, to verify each new test file pairs to a source file.
- To enumerate "weakly paired" source files (only one referring test) for follow-up depth checks.
When Not to Use
- Line/branch coverage — use
coverage-analysis. - Priorities derived from real coverage data — use
coverage-analysis. - CRAP-score / risk hotspots — use
coverage-analysis. - Are existing tests strong? — use
test-gap-analysis(mutation reasoning) orassertion-quality.
Roslyn engine (C#)
Prerequisites
- .NET SDK that supports file-based apps (
dotnet run script.cs). Pinned in the repo'sglobal.json(SDK 11 preview or later). - No internet access required beyond the initial NuGet restore of
Microsoft.CodeAnalysis.CSharpon first run.
Usage
# From the skill folder
dotnet run scripts/Find-UntestedSources.cs -- <repo-root> [--top N]
# Save the report
dotnet run scripts/Find-UntestedSources.cs -- <repo-root> > pairing.json
# Iterate the untested list, highest-API-surface first
$report = Get-Content pairing.json | ConvertFrom-Json
$report.untested | Select-Object -First 10 source, decl_count, suggested_test_path
Diagnostics go to stderr; JSON goes to stdout.
Output schema
{
"repo": "<absolute path>",
"elapsed_ms": 8883,
"counts": {
"source_files": 3036,
"test_files": 867,
"untested_files": 1852,
"paired_files": 1184
},
"untested": [
{
"source": "src/Foo/Bar.cs",
"decl_count": 8, // # of type declarations in the file
"suggested_test_path": // mirror of source under a discovered test project
"tests/Foo.Tests/Bar/BarTests.cs"
}
],
"source_to_tests": {
"src/Foo/Baz.cs": [
"tests/Foo.Tests/BazTests.cs",
"tests/Foo.IntegrationTests/Scenarios/BazScenarios.cs"
]
}
}
How it works
- File discovery — recursive walk pruning
bin/,obj/,node_modules/,.git/,.vs/,packages/, and any dotted subdir. Skips generated files (.g.cs,.Designer.cs,.AssemblyInfo.cs). - Test vs source classification — walks up to the nearest
.csprojand marks it a test project if the project name ends in.Tests,.Test,.UnitTests,.IntegrationTests,.E2E,.EndToEnd,.Spec,.Specs, or the content referencesMicrosoft.NET.Test.Sdk,MSTest.Sdk,Microsoft.Testing.Platform,xunit,NUnit,TUnit, or<IsTestProject>true</IsTestProject>. - Source index (parallel) — parse each source file with
CSharpSyntaxTree.ParseText(syntax only, no compilation); record everyBaseTypeDeclarationSyntax/DelegateDeclarationSyntaxas(ShortName, EnclosingNamespace, FilePath). - Test scan (parallel) — parse each test file, collect
usingdirectives + enclosing namespace, walk everyIdentifierToken, look it up in the short-name index, and disambiguate strictly: an identifier is attributed only if the declaration's namespace matches one of the test file'susingdirectives, the enclosing namespace, or a prefix of them. This avoids noise where common names likeSettingsorContextmatch every project. - Pairing & suggestion — invert into
source → [tests]. Build a production-to-test project map from<ProjectReference>entries; for each untested source, mirror its in-project relative path under the referencing test project to suggest a path. - JSON emit — ordered by declaration count desc, then alphabetical.
Polyglot engine (tree-sitter)
Prerequisites
- Python 3.10+.
pip install tree-sitter-language-pack(single self-contained wheel that bundles parsers for 300+ languages and the high-levelprocess()API). No native build, no per-language grammar install.
Usage
# From the skill folder
python scripts/find_untested_sources.py <repo-root>
# Restrict to a language (repeatable)
python scripts/find_untested_sources.py <repo-root> --lang python --lang typescript
# Truncate the report (top 20 by declared API surface)
python scripts/find_untested_sources.py <repo-root> --limit-untested 20 > pairing.json
# Iterate, highest-API-surface first
$report = Get-Content pairing.json | ConvertFrom-Json
$report.untested_sources | Select-Object -First 10 path, declaration_count, suggested_test_path
Pass --include-tested to additionally emit tested_sources (omitted by
default to keep the payload small for LLM consumption). Diagnostics go to
stderr; JSON goes to stdout.
Output schema
{
"repo_root": "<absolute path>",
"summary": {
"source_files": 3138,
"test_files": 761,
"tested_source_files": 1419,
"untested_source_files": 1719,
"orphan_test_files": 15,
"languages": ["csharp"]
},
"untested_sources": [
{
"path": "src/Foo/Bar.cs",
"language": "csharp",
"declaration_count": 8,
"declarations": ["Bar", "BarOptions", "IBar", "..."],
"suggested_test_path": "src/Foo/BarTests.cs"
}
],
"orphan_tests": [
{ "path": "tests/SomeIntegrationTest.cs", "language": "csharp" }
]
}
How it works
-
File discovery — recursive walk pruning common build/vendor dirs (
bin,obj,node_modules,target,dist,build,vendor,__pycache__,.venv,.git, …) and generated files (.d.ts,.g.cs,.Designer.cs,_pb2.py,*.min.js,AssemblyInfo.cs, …). -
Language detection —
detect_language_from_pathmaps the extension to a supported language; unknown extensions are skipped. -
Test-vs-source classification — per-language path heuristics:
| Language | Test rule | |---|---| | Python | path contains
tests//test/; or filename starts withtest_or ends_test.py; orconftest.py. | | JS/TS/TSX | path contains__tests__,tests,test,spec,e2e; or filename contains.test./.spec.. | | Go | filename ends_test.go. | | Java | path containstest/tests; or filename endsTest.java/Tests.java. | | Rust | path containstests//benches/. | | C# | path containstests/; or project segment ends.Tests/.Test/.UnitTests/.IntegrationTests; or filename endsTests/Test. | | Ruby | path containsspec//test/; or filename ends_spec.rb/_test.rb. | | Kotlin | path containstest//tests//spec/; or filename endsTest.kt/Tests.kt/Spec.kt. | | Swift | path containstest//tests//uitests//integrationtests/(case-insensitive); or filename endsTest.swift/Tests.swift. | | PowerShell | path containstest//tests//pester/; or filename ends.Tests.ps1/.Test.ps1. | | C++ | path containstest//tests//testing/; or filename startstest_or ends_test.cpp/_tests.cpp. | -
Per-file extraction —
process(text, ProcessConfig(structure, imports, symbols))returns declared items, raw import statements, and a flat declared -name list. -
Pairing — for each test file, union import resolution (per language, e.g. Python
from pkg.mod import x→pkg/mod.py; Javaimport a.b.C;→a/b/C.java; C#usingis namespace-not-file, so a no-op) with identifier overlap (word-like tokens, length ≥ 4, matched against declared names). -
JSON emit —
untested_sourcesordered by declaration count descending.
Limitations (be honest with the agent)
Both engines are static, parse-only heuristics that trade a little accuracy for orders-of-magnitude lower cost than coverage. Known gaps:
- Reflection / DI-resolved types referenced only via a string name or container resolution won't be detected — the type's short name never appears in the test source.
- Extension methods invoked as instance methods (C#): the declaring static class is not named, so its file is not credited.
var, target-typednew(), pattern matching lose the type token; the file-level union usually still catches it through other references.- Short identifier names (polyglot, < 4 chars) are dropped to avoid noisy
pairings on names like
id,db,Tag. - Monorepo path aliases (TS path mapping, Java module-info) are not resolved; a suffix-mat
Truncated for display — read the full file on GitHub.
Related Skills
Agent-Reach
85.6kGive your AI agent eyes to see the entire internet. Read & search Twitter, Reddit, YouTube, GitHub, Bilibili, XiaoHongShu — one CLI, zero API fees.
headroom
73.9kCompress tool outputs, logs, files, and RAG chunks before they reach the LLM. 20% fewer tokens for coding agents, 60-95% fewer tokens for JSON, same answers. Library, proxy, MCP server.
ai-job-search
44.0kThe job search that runs on your machine. AI job application framework built on Claude Code: evaluate postings, tailor CVs, write cover letters, prep interviews. Fork it and own it.
claude-howto
41.7kA visual, example-driven guide to Claude Code — from basic concepts to advanced agents, with copy-paste templates that bring immediate value.
Languages
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
