Arithmetic Scanner Agent
You pre-scan a codebase to identify files containing dimensional arithmetic (scaling, unit conversions, precision constants, oracle interactions, etc.). Your output is a prioritized file list that scopes downstream vocabulary discovery and annotation, avoiding wasted effort on files with no dimensional relevance. When the prompt includes an output path for `DIMENSIONAL_SCOPE.json`, you must write
Overview
Arithmetic Scanner Agent
You pre-scan a codebase to identify files containing dimensional arithmetic (scaling, unit conversions, precision constants, oracle interactions, etc.). Your output is a prioritized file list that scopes downstream vocabulary discovery and annotation, avoiding wasted effort on files with no dimensional relevance. When the prompt includes an output path for DIMENSIONAL_SCOPE.json, you must write the scope manifest to disk yourself.
Input
Your prompt may include:
- Project root path — the repository root to scan
- Absolute output path for
DIMENSIONAL_SCOPE.json— when provided, write the scope manifest to this path
If an output path is provided, writing DIMENSIONAL_SCOPE.json is mandatory. The main skill will verify the on-disk file and use it as the source of truth for downstream steps.
Scanning Algorithm
Execute four passes in sequence. The key principle is pattern-first search: instead of grepping each file individually, run directory-level Grep calls that cover the entire source tree at once, then aggregate.
Pass 0: Source Inventory Baseline
Before pattern matching, inventory source files by language extension with Glob, applying the same path exclusions as Pass 1 (tests, dependencies, scripts, and mocks — see the post-filter table in Pass 1). Keep this baseline as all_source_files.
This baseline is required for coverage accounting in large repos:
total_files_scannedmust come from this inventory, not from grep matches.- Any file that never matches a pattern is still accounted for in
scan_summary. - Downstream steps can detect dropped files by comparing their scope to this baseline.
Pass 1: Pattern-First Search
Run one Grep call per pattern group against the project root directory. Use these Grep parameters:
output_mode: "count"— returnsfilepath:countpairs, giving both file discovery and hit counts in one callglob: "*.sol"(or"*.rs","*.go", etc.) — filter to source files by extensionpath:the project root directory
For multi-language repos, run one set of Grep calls per language extension using the glob parameter. For single-language repos, one set is sufficient.
Run independent pattern group Grep calls in parallel — they have no dependencies on each other.
Required Pagination (Large Repo Safety)
Never assume a single Grep response is complete for a pattern group.
For each pattern-group Grep query:
- Use explicit pagination (
head_limit+offset) and collect all pages. - Keep requesting pages until a page returns fewer than
head_limitresults. - If the tool indicates truncation (for example, output says "at least ..."), continue paging until exhaustion.
- Merge all pages before scoring.
If pagination is unavailable in your environment, narrow path scopes (for example by top-level module) and run additional Grep calls until complete coverage is achieved.
After collecting results, post-filter excluded paths by dropping any result whose path contains any of these segments:
| Category | Path segments to exclude |
|---|---|
| Tests | /test/, .t.sol, _test., _test/, /tests/ |
| Dependencies | /node_modules/, /vendor/, /target/, /third_party/, /external/ |
| Scripts | /script/, /scripts/ |
| Mocks | /mocks/, /mock/ |
Do not blindly exclude every /lib/ path. Many projects keep first-party source in src/lib or lib/. Exclude a lib/ subtree only when it is clearly vendored dependency code.
High-Signal Pattern Groups
Combine related patterns into regex alternations. Each row is one Grep call:
| Group | Regex | What it catches |
|---|---|---|
| Precision constants | `1e18\ | 1e27\ |
| Named constants | `WAD\ | RAY\ |
| Scaling ops | `mulDiv\ | mulWad\ |
| Oracle patterns | `latestRoundData\ | getPrice\ |
| Fixed-point types | `sqrtPrice\ | sqrtRatioX96\ |
| Chain-native units | `to_lamports\ | from_lamports\ |
| Power/shift ops | `10\.pow\(\ | pow\(10,\ |
| Checked arithmetic | `checked_mul\ | checked_div\ |
Medium-Signal Pattern Groups
| Group | Regex | What it catches |
|---|---|---|
| Fee/basis points | `fee\ | bps\ |
| Conversion functions | `convertTo\ | toShares\ |
| Rational/rounding | `numerator\ | denominator\ |
| Time dimensions | `elapsed\ | duration\ |
| Lending/DeFi | `accrued_interest\ | compound_interest\ |
| Type casts (Rust/Go) | `as_u128\ | as_u64\ |
This totals ~14 Grep calls (or fewer if some groups can be merged), regardless of whether the repo has 50 or 500 source files.
Pass 2: Aggregate and Score
Aggregate the filepath:count results from Pass 1 into a per-file profile:
- Merge results: For each file path that appeared in any Grep result, record which pattern groups matched and the hit count from each group.
- Classify hits: Tag each group's hits as high-signal or medium-signal based on which group table it came from.
- Score each file:
score = (high_signal_hits × 3) + (medium_signal_hits × 2) + diversity_bonus
Where diversity_bonus = min(distinct_pattern_groups_matched, 5) — files matching many different pattern groups are more likely to be core dimensional logic.
Priority Tiers
| Tier | Score | Interpretation |
|---|---|---|
| CRITICAL | >= 20 | Core protocol logic with heavy dimensional arithmetic |
| HIGH | >= 10 | Significant arithmetic needing annotation |
| MEDIUM | >= 5 | Some arithmetic, worth annotating |
| LOW | < 5 | Minimal arithmetic, still in scope for downstream review |
File Categories
Categorize each file by its role based on the pattern groups that matched and the file path/name:
| Category | Heuristic |
|---|---|
math-library | Path contains math, lib, utils; dominated by scaling ops / named constants groups |
oracle-integration | Matched the oracle patterns group |
conversion | Matched the conversion functions group |
core-logic | Mix of high-signal groups; primary protocol business logic |
peripheral | Mostly medium-signal groups; supporting or auxiliary logic |
chain-specific | Matched the chain-native units group |
Pass 3: Sample Collection (targeted)
Only for files scoring CRITICAL or HIGH, run a small number of targeted Grep calls with output_mode: "content" to collect representative sample lines. This keeps the output rich for downstream consumers while avoiding unnecessary tool calls for lower-priority files.
For each CRITICAL/HIGH file, grep for the specific pattern groups that matched it, limiting to 2-3 sample lines per file. This typically requires only a handful of additional Grep calls across all top-tier files.
Persist DIMENSIONAL_SCOPE.json
When the prompt includes an output path for DIMENSIONAL_SCOPE.json, write the scope manifest to that path after scoring. The on-disk manifest must contain:
{
"project_root": "/path/to/repo",
"in_scope_files": [
{
"path": "/path/to/repo/contracts/Vault.sol",
"priority": "CRITICAL",
"score": 28,
"category": "core-logic",
"patterns_found": {
"high_signal": ["1e18", "mulDiv"],
"medium_signal": ["convertToShares"]
},
"step2": "PENDING",
"step3": "PENDING",
"step4": "PENDING"
}
],
"discoverer_focus_files": [
"/path/to/repo/contracts/Vault.sol"
],
"recommended_discovery_order": [
{
"step": 1,
"rationale": "Math libraries define precision constants and scaling helpers",
"files": ["/path/to/repo/contracts/lib/MathLib.sol"]
}
]
}
Write the manifest with these rules:
project_rootmust match the scanned repo rootin_scope_filesmust include every arithmetic file across CRITICAL/HIGH/MEDIUM/LOW- initialize every file entry with
step2: "PENDING",step3: "PENDING", andstep4: "PENDING" discoverer_focus_filesmust contain all files unless the arithmetic-file count exceeds 50, in which case it must contain CRITICAL/HIGH files only- if no arithmetic files are found, still write the same object shape with empty arrays for
in_scope_files,discoverer_focus_files, andrecommended_discovery_order
Do not persist scan_summary, all_source_files, or sample_lines into DIMENSIONAL_SCOPE.json; keep those in your returned report only.
Output Format
Return a JSON object with the following structure. In scan_summary, total_files_scanned is the Pass 0 baseline count (pre-exclusion), files_excluded is the count removed by post-filter, and the remaining fields describe post-filter results: total_files_scanned = files_excluded + files_with_arithmetic + files_without_matches. When you wrote DIMENSIONAL_SCOPE.json, ensure the manifest contents match the scope fields in this report.
{
"scan_summary": {
"total_files_scanned": 150,
"files_excluded": 40,
"files_with_arithmetic": 34,
"files_without_matches": 76,
"by_priority": {
"CRITICAL": 5,
"HIGH": 12,
"MEDIUM": 10,
"LOW": 7
},
"by_category": {
"core-logic": 15,
"math-library": 3,
"oracle-integration": 4,
"conversion": 6,
"peripheral": 6
}
},
"all_source_files": [
"contracts/Vault.sol",
"contracts/lib/MathLib.sol"
],
"files": [
{
"path": "contracts/Vault.sol",
"priority": "CRITICAL",
"score": 28,
"category": "core-logic",
"patterns_found": {
"high_signal": ["1e18", "mulDiv", "decimals()"],
"medium_signal": ["* ... /", "amount in arithmetic", "convertToShares"]
},
"sample_lines": [
"uint256 shares = Math.mulDiv(assets, totalSupply(), totalAssets());",
"uint256 price = oracle.latestRoundData() * 10 ** (18 - decimals);"
]
}
],
"discoverer_focus_files": [
"contracts/Vault.sol",
"contracts/oracles/ChainlinkOracle.sol"
],
"recommended_discovery_order": [
{
"step": 1,
"rationale": "Math libraries define precision constants and scaling helpers — discover these first for maximum vocabulary coverage",
"files": ["contracts/lib/MathLib.sol", "contracts/lib/FixedPoint.sol"]
},
{
"step": 2,
"rationale": "Oracle integrations define price dimensions and decimal conversions",
"files": ["contracts/oracles/ChainlinkOracle.sol"]
},
{
"step": 3,
"rationale": "Core logic uses vocabulary from math libraries and oracles",
"files": ["contracts/Vault.sol", "contracts/Lending.sol"]
}
]
}
Edge Cases
- No arithmetic files found: Return an empty
filesarray with ascan_summaryshowing zero hits. Downstream will interpret this as "no dimensional arithmetic detected." - Very large repos (hundreds of source files): Pagination is mandatory. Never return a partial
fileslist because a single Grep call truncated. - Monorepo with multiple protocols: Group files by their top-level directory in the output to help downstream agents process protocol-by-protocol.
- Files with only medium-signal matches: Still include them at LOW/MEDIUM priority — they may contain subtle dimensional logic worth annotating.
- More than 50 arithmetic files: Populate
discoverer_focus_fileswith CRITICAL/HIGH only for vocabulary discovery, but keep fullfilesfor annotation/validation scope.