| name | repo-scanning |
| description | Deterministic repo analysis — language/framework detection, structure mapping, domain discovery, health checks, and import graph integration |
| triggers | {"files":["src/lib/scanner.js","src/lib/source-exts.js","src/lib/path-resolver.js","src/lib/parsers/typescript.js","src/lib/parsers/python.js","src/lib/frameworks/nextjs.js","src/commands/scan.js","tests/scanner.test.js"],"keywords":["scanRepo","detectLanguages","detectFrameworks","detectDomains","detectEntryPoints","health check","SOURCE_EXTS","SKIP_DIR_NAMES"]} |
You are working on aspens' repo scanning system — a fully deterministic analyzer (no LLM calls) that detects languages, frameworks, structure, domains, entry points, size, and health issues for any repository.
Domain purpose
Scanning is the foundation every other command builds on. scanRepo() must produce stable, reproducible results from any repo on disk — even a freshly-cloned one with no manifests parsed yet — so doc init, doc sync, doc impact, and doc graph can decide what to generate, which target (Claude / Codex) is appropriate, and which domains warrant skills. Determinism is the contract: the same repo at the same commit must always produce the same scan.
Key Concepts
- scanRepo() return shape:
{ path, name, languages[], frameworks[], structure, domains[], entryPoints[], hasClaudeConfig, hasClaudeMd, hasCodexConfig, hasAgentsMd, repoType, size, health } — order matters: repoType and health depend on prior fields
- Multi-target detection: Scanner checks for both
.claude dir + AGENTS.md (Claude Code) and .codex dir + AGENTS.md (Codex CLI) to inform target selection during doc init
- Detection via marker files: Languages detected by presence of files like
package.json, go.mod, Cargo.toml — not by scanning source extensions
- Framework detection: JS/TS from
package.json deps, Python from requirements.txt/pyproject.toml/Pipfile, Go from go.mod contents, Ruby from Gemfile
- Domain detection: Scans dirs under source root + repo root, skips
SKIP_DIR_NAMES set (structural/build/IDE/.NET/Java/Rust build dirs), requires at least one source file via collectModules()
- extraDomains: User-specified domains merged via
mergeExtraDomains() — marked with userSpecified: true, resolved against source root then repo root
- Source root: First match of
src, app, lib, server, pages via findSourceRoot(); for nested-project layouts (e.g. ~/apps/MyApp/MyApp/MyApp.csproj), if repo root has exactly one non-skip child with a project manifest, that child is promoted as the source root and excluded from domain scanning at repo root to avoid double-counting
- Size estimation: Lines estimated at ~40 bytes/line from
stat.size, walk capped at depth 5, skips bin/obj/target build output alongside node_modules/dist/etc.
- Graph is opt-out:
scanCommand builds graph by default (options.graph !== false); errors are caught and only logged with --verbose
- Health checks are language-aware:
.gitignore checks for missing node_modules/, __pycache__/, target/, virtualenv dirs, and uncommitted .env files are gated on detected languages
Critical Rules
SOURCE_EXTS (in src/lib/source-exts.js): .py, .ts, .js, .tsx, .jsx, .mjs, .cjs, .rb, .go, .rs, .java, .kt, .kts, .cs, .fs, .fsx, .swift, .php, .ex, .exs — adding a language requires updating this set AND the detectLanguages indicators. Import graph / hub / cluster detection remains JS/TS/Python-only; other languages get domain discovery but a minimal atlas.
SKIP_DIR_NAMES: Includes src, app, bin, obj, dist, target, node_modules, etc. — skipped in domain detection. bin/obj/target added to avoid .NET/Java/Rust build artifacts.
BOILERPLATE_STEMS: __init__, index, mod are excluded from module collection — don't add real module names here
- TypeScript implies JavaScript: TS detection in
detectLanguages() automatically adds JS to the languages array
- Graph failure is non-fatal:
buildRepoGraph errors in scanCommand() are caught and silently ignored unless --verbose
- Tests use real filesystem fixtures, not mocks — create fixtures with
createFixture(name, files) pattern, always clean up
detectEntryPoints is exported and reused by graph-builder.js — changing its signature breaks the graph builder
es-module-lexer must be initialized: parseJsImports() awaits init before calling parse(). The lexer can fail on JSX-heavy files; the regex fallback in parsers/typescript.js is intentional graceful degradation, not dead code.
- Python parser skips SCREAMING_SNAKE constants by design — they produced false positives in code-map; do not re-add them.
- Next.js entry points feed the import-graph priority ranker —
app/, pages/, and middleware/instrumentation files are roots Next.js runs implicitly with no static importer.
Last Updated: 2026-05-11