From fc91a9ab09d6a3067ebfeff407c986e40b70af26 Mon Sep 17 00:00:00 2001 From: cryptanu Date: Thu, 22 Jan 2026 13:50:08 +0100 Subject: [PATCH 1/3] Add sloc-initial-sweep plugin for SLOC audits --- README.md | 1 + plugins/sloc-initial-sweep/README.md | 38 ++ .../skills/sloc-initial-sweep/SKILL.md | 375 ++++++++++++++++++ .../examples/sample_output.md | 336 ++++++++++++++++ .../references/SLOC_Standards.md | 180 +++++++++ .../scripts/sloc_counter.py | 313 +++++++++++++++ 6 files changed, 1243 insertions(+) create mode 100644 plugins/sloc-initial-sweep/README.md create mode 100644 plugins/sloc-initial-sweep/skills/sloc-initial-sweep/SKILL.md create mode 100644 plugins/sloc-initial-sweep/skills/sloc-initial-sweep/examples/sample_output.md create mode 100644 plugins/sloc-initial-sweep/skills/sloc-initial-sweep/references/SLOC_Standards.md create mode 100644 plugins/sloc-initial-sweep/skills/sloc-initial-sweep/scripts/sloc_counter.py diff --git a/README.md b/README.md index cd14cec..d4631ce 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ cd /path/to/parent # e.g., if repo is at ~/projects/skills, be in ~/projects | [differential-review](plugins/differential-review/) | Security-focused differential review of code changes with git history analysis | | [semgrep-rule-creator](plugins/semgrep-rule-creator/) | Create and refine Semgrep rules for custom vulnerability detection | | [semgrep-rule-variant-creator](plugins/semgrep-rule-variant-creator/) | Port existing Semgrep rules to new target languages with test-driven validation | +| [sloc-initial-sweep](plugins/sloc-initial-sweep/) | Logical/physical SLOC breakdowns for audit scoping and prioritization | | [sharp-edges](plugins/sharp-edges/) | Identify error-prone APIs, dangerous configurations, and footgun designs | | [static-analysis](plugins/static-analysis/) | Static analysis toolkit with CodeQL, Semgrep, and SARIF parsing | | [testing-handbook-skills](plugins/testing-handbook-skills/) | Skills from the [Testing Handbook](https://appsec.guide): fuzzers, static analysis, sanitizers, coverage | diff --git a/plugins/sloc-initial-sweep/README.md b/plugins/sloc-initial-sweep/README.md new file mode 100644 index 0000000..a2a3e97 --- /dev/null +++ b/plugins/sloc-initial-sweep/README.md @@ -0,0 +1,38 @@ +# sloc-initial-sweep + +Logical/physical SLOC counter for audit scoping, with per-folder and per-language breakdowns. + +## What it does +- Computes logical (NCSS) and physical SLOC. +- Supports common audit stacks: Solidity, TypeScript/JavaScript, Python, Rust, Go, Vyper. +- Highlights largest modules/files to prioritize review order. + +## Layout +``` +plugins/sloc-initial-sweep/ +├── README.md +└── skills/sloc-initial-sweep/ + ├── SKILL.md + ├── scripts/sloc_counter.py + ├── references/SLOC_Standards.md + └── examples/sample_output.md +``` + +## Usage +In Claude Code (plugin baseDir provided automatically): +``` +@skills/sloc-initial-sweep/SKILL.md +Calculate logical SLOC for contracts/, exclude contracts/mocks/ +``` + +Manual script run: +```bash +python3 {baseDir}/skills/sloc-initial-sweep/scripts/sloc_counter.py \ + --dirs contracts \ + --extensions .sol \ + --method logical +``` + +## Notes +- Default path assumes `baseDir` is the plugin root. +- Avoid writing artifacts inside target repos; keep results in scratch space. diff --git a/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/SKILL.md b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/SKILL.md new file mode 100644 index 0000000..816b9c2 --- /dev/null +++ b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/SKILL.md @@ -0,0 +1,375 @@ +--- +name: sloc-initial-sweep +description: Calculate logical or physical SLOC for codebase audits with detailed per-folder and per-language breakdowns +version: 1.0.0 +category: code-analysis +tags: [audit, metrics, sloc, smart-contracts, complexity] +author: Cryptanu +requires: [python3] +--- + +# SLOC/NSLOC Initial Sweep + +## Purpose + +Perform an initial Source Lines of Code (SLOC) analysis for codebase audit preparation. This skill helps quickly understand codebase size, complexity, and distribution across modules. + +Use this as the **first step** in any audit workflow to: +- Estimate audit scope and effort +- Identify largest modules for prioritized review +- Track codebase evolution over time +- Generate baseline metrics for reports + +--- + +## When to Use +- You need audit scoping or effort estimates before deep review. +- You want to rank modules/files by size to set reading order. +- You need before/after deltas for upgrades or refactors. +- You’re comparing logical vs physical SLOC for reporting. + +## When NOT to Use +- The repository is huge (>500 files) and scope is unconfirmed—confirm scope first. +- You need semantic complexity metrics (use static-analysis/linters instead). +- You want test coverage (run coverage tools, not SLOC). + +## Rationalizations to Reject +- “Physical SLOC is enough for Solidity” → use logical SLOC for complexity. +- “Scanning everything is fine” → confirm scope and exclusions before large scans. +- “Mocks/tests always excluded” → decide explicitly; report if included/excluded. + +--- + +## Prerequisites + +- [ ] Python 3.8+ available in environment. +- [ ] Target repository accessible and readable. +- [ ] Confirmed scope with user (directories, languages, test inclusion). +- [ ] `baseDir` set to this plugin root (so scripts live at `{baseDir}/skills/sloc-initial-sweep/scripts/`). + +--- + +## Instructions for Agent + +### Step 1: Scope Selection + +**Ask the user to clarify:** + +1. **Which directories to scan?** + - Common: `contracts/`, `src/`, `lib/`, `scripts/` + - Exclude: `node_modules/`, `vendor/`, `build/`, `artifacts/`, `cache/` + +2. **Should tests/mocks be included?** + - Option A: **Exclude** (production code only) + - Option B: **Include separately** (for transparency) + - Option C: **Include all** (full codebase) + +3. **Which file extensions?** + - Solidity: `.sol` + - TypeScript: `.ts` + - JavaScript: `.js` + - Python: `.py` + - Rust: `.rs` + - Go: `.go` + - Vyper: `.vy` + +4. **Any custom exclusions?** + - Deployment files, migration scripts, generated code, etc. + +**Default recommendation for smart contract audits:** +- Include: `contracts/` (excluding `contracts/mocks/` and `contracts/test/`) +- Extensions: `.sol` only +- Method: Logical SLOC + +--- + +### Step 2: Choose Counting Method + +**Present options:** + +#### Option A: Logical SLOC (NCSS - Recommended) +- Counts executable/declarative **statements**, not physical lines +- Multi-line expressions count as **one** statement +- Better reflects code complexity +- **Use for:** Audit effort estimation, complexity assessment + +**Counting rules:** +- Each `;`-terminated statement (excluding `for(;;)` header semicolons) +- Control structures: `if`, `else`, `for`, `while`, `do`, `switch`, `case`, `default`, `try`, `catch`, `assembly`, `unchecked` +- Declarations: `contract`, `interface`, `library`, `struct`, `enum`, `function`, `constructor`, `modifier`, `fallback`, `receive` + +#### Option B: Physical SLOC (SLOCCount) +- Counts non-empty, non-comment **lines** +- Multi-line expressions count as N lines +- Simpler but less accurate for complexity +- **Use for:** Quick size estimates, line-based billing + +**Ask the user:** "Logical or Physical SLOC? (Default: Logical)" + +--- + +### Step 3: Execute Count + +**Use the provided Python script:** + +```bash +python3 {baseDir}/skills/sloc-initial-sweep/scripts/sloc_counter.py \ + --dirs contracts src scripts \ + --extensions .sol .ts .js \ + --method logical +``` + +**Example for contracts-only:** + +```bash +python3 {baseDir}/skills/sloc-initial-sweep/scripts/sloc_counter.py /path/to/repo \ + --dirs contracts \ + --extensions .sol \ + --method logical +``` + +**Inline alternative (if script not available):** + +```python +import os +from collections import defaultdict + +ROOT = "" +INCLUDE_DIRS = ["contracts"] +EXTENSIONS = {".sol"} +METHOD = "logical" # or "physical" + +# [Insert strip_comments() and logical_sloc() functions from script] + +# Scan and count +files = [] +for rel in INCLUDE_DIRS: + base = os.path.join(ROOT, rel) + for dirpath, _, filenames in os.walk(base): + for name in filenames: + if os.path.splitext(name)[1] in EXTENSIONS: + files.append(os.path.join(dirpath, name)) + +by_folder = defaultdict(int) +by_folder_files = defaultdict(int) + +for path in files: + with open(path, "r", encoding="utf-8", errors="ignore") as f: + text = f.read() + sloc = logical_sloc(text) # or physical_sloc(text) + rel = os.path.relpath(path, ROOT) + folder = rel.split(os.sep)[0] + by_folder[folder] += sloc + by_folder_files[folder] += 1 + +# Print results +print(f"Total: {sum(by_folder.values())} SLOC across {len(files)} files\n") +for folder in sorted(by_folder, key=by_folder.get, reverse=True): + print(f"{folder}: {by_folder[folder]:,} SLOC ({by_folder_files[folder]} files)") +``` + +--- + +### Step 4: Present Results + +**Format the output clearly:** + +``` +============================================================ +SLOC Analysis - LOGICAL Method +============================================================ + +Total SLOC: 6,061 +Files Scanned: 82 + +By Language: +---------------------------------------- + solidity 6,061 SLOC (82 files) + +By Directory: +---------------------------------------- + marketplace 1,163 SLOC (4 files) + core 842 SLOC (4 files) + mocks 821 SLOC (19 files) + polylend 555 SLOC (8 files) + earn 531 SLOC (2 files) + interfaces 473 SLOC (20 files) + oracles 380 SLOC (2 files) + vault 293 SLOC (2 files) + errors 285 SLOC (11 files) + bundler 219 SLOC (1 file) + [... remaining folders ...] +``` + +**Include context:** +- Note any exclusions ("tests excluded", "mocks excluded") +- Mention counting method used +- Highlight any anomalies (e.g., single file with 50% of total SLOC) + +--- + +### Step 5: Document Findings + +**Automatically note:** + +1. **Size classification:** + - Small: < 2,000 SLOC + - Medium: 2,000 - 10,000 SLOC + - Large: 10,000 - 50,000 SLOC + - Very Large: > 50,000 SLOC + +2. **Risk indicators:** + - Single file > 1,000 SLOC (refactoring candidate) + - Mocks/tests > 50% of total (may indicate over-testing or bloat) + - High concentration in one module (single point of failure risk) + +3. **Audit effort estimate:** + - Rule of thumb: ~100-200 SLOC per hour for detailed security audit + - Adjust for complexity, external dependencies, novel patterns + +**Example summary:** + +> **SLOC Summary:** 6,061 logical SLOC across 82 Solidity files in `contracts/`. +> +> **Size:** Medium codebase +> **Largest modules:** Marketplace (1,163), Core (842), Mocks (821) +> **Estimated effort:** 30-60 hours for comprehensive audit (excluding mocks) +> **Notes:** Mocks excluded from audit scope, reducing focus area to ~5,240 SLOC + +--- + +## Safety & Best Practices + +- [ ] **Read-only operation:** Do not modify any files +- [ ] **No artifacts in repo:** Do not create SLOC reports inside the target repository +- [ ] **Handle encoding errors:** Use `encoding="utf-8", errors="ignore"` for file reads +- [ ] **Validate paths:** Check that directories exist before scanning +- [ ] **Confirm large scans:** If > 500 files, confirm with user before running + +--- + +## Output Artifacts + +**Optional:** If user requests persistent reports, create: + +- `sloc_report.json` - Machine-readable metrics (outside repo) +- `sloc_summary.md` - Human-readable report for audit documentation + +**JSON format:** +```json +{ + "timestamp": "2026-01-22T...", + "method": "logical", + "total_sloc": 6061, + "total_files": 82, + "by_language": { + "solidity": 6061 + }, + "by_folder": { + "marketplace": 1163, + "core": 842, + ... + } +} +``` + +--- + +## Integration with Other Skills + +**After SLOC analysis, consider:** + +- `audit-context` - Build full codebase context for review +- `variant-analysis` - Find similar code patterns across modules +- `static-analysis` - Run Slither, Mythril, or other static analyzers +- `differential-review` - Compare against previous version (for upgrades) + +--- + +## Examples + +### Example 1: Smart Contract Audit (Solidity only, exclude tests) + +**User request:** +> "Calculate NSLOC for this smart contract repo, exclude tests" + +**Agent response:** +1. Confirm scope: `contracts/` directory, `.sol` files only, exclude `test/` and `mocks/` +2. Method: Logical SLOC (default for contracts) +3. Run script with filters +4. Present results with folder breakdown +5. Note: "Tests and mocks excluded. Production contracts: 5,240 SLOC" + +### Example 2: Full Stack Audit (Solidity + TypeScript) + +**User request:** +> "Get SLOC for contracts and frontend" + +**Agent response:** +1. Confirm scope: `contracts/` and `frontend/src/`, extensions `.sol` + `.ts` + `.tsx` +2. Method: Logical SLOC +3. Run script with both directories +4. Present results split by language and folder +5. Note: "Backend: 6,061 SLOC (Solidity), Frontend: 15,000 SLOC (TypeScript)" + +### Example 3: Differential SLOC (Before/After Upgrade) + +**User request:** +> "Compare SLOC before and after the upgrade" + +**Agent response:** +1. Run SLOC on `main` branch +2. Run SLOC on `upgrade` branch +3. Present delta: "Added 1,200 SLOC (+19.8%), Removed 300 SLOC" +4. Identify changed modules: "Marketplace +500, Vault +400, New: LendingVault 300" + +--- + +## Notes + +- **Multi-line expressions:** In logical SLOC, a function definition spanning 10 lines = **1** statement +- **Comments:** Always excluded (both `//` line and `/* */` block comments) +- **Blank lines:** Always excluded +- **String literals:** Preserved during comment stripping to avoid false positives +- **Language-specific:** Keywords tuned for Solidity/TypeScript; adjust for other languages + +--- + +## Troubleshooting + +**Issue:** Script fails with encoding errors +**Solution:** Use `errors="ignore"` in file reads, or manually specify encoding + +**Issue:** Unexpected counts (too high/low) +**Solution:** Verify comment stripping works correctly for target language, check for minified code + +**Issue:** Directory not found +**Solution:** Confirm relative paths are correct, use absolute paths if needed + +--- + +## Quick Reference + +| Method | Counts | Multi-line | Best For | +|--------|--------|-----------|----------| +| **Logical SLOC** | Statements | 1 statement | Complexity, audit effort | +| **Physical SLOC** | Lines | N lines | Quick sizing, LOC metrics | + +**Default:** Logical SLOC for smart contract audits + +--- + +## Checklist + +**Before running:** +- [ ] Confirmed scope (directories, extensions) +- [ ] Confirmed method (logical vs physical) +- [ ] Confirmed test/mock inclusion policy +- [ ] Validated Python 3 available + +**After running:** +- [ ] Reported total SLOC and file count +- [ ] Provided per-folder breakdown +- [ ] Provided per-language breakdown (if multi-language) +- [ ] Noted exclusions and caveats +- [ ] Documented size classification and effort estimate diff --git a/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/examples/sample_output.md b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/examples/sample_output.md new file mode 100644 index 0000000..1e041e5 --- /dev/null +++ b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/examples/sample_output.md @@ -0,0 +1,336 @@ +# Sample SLOC Analysis Outputs + +This document shows example outputs for various audit scenarios. + +--- + +## Example 1: Solidity Smart Contract Audit (Contracts Only) + +**Command:** +```bash +python3 scripts/sloc_counter.py /path/to/NettyWorthV2 \ + --dirs contracts \ + --extensions .sol \ + --method logical +``` + +**Output:** + +``` +============================================================ +SLOC Analysis - LOGICAL Method +============================================================ + +Total SLOC: 6,061 +Files Scanned: 82 + +By Language: +---------------------------------------- + solidity 6,061 SLOC (82 files) + +By Directory: +---------------------------------------- + marketplace 1,163 SLOC (4 files) + core 842 SLOC (4 files) + mocks 821 SLOC (19 files) + polylend 555 SLOC (8 files) + earn 531 SLOC (2 files) + interfaces 473 SLOC (20 files) + oracles 380 SLOC (2 files) + vault 293 SLOC (2 files) + errors 285 SLOC (11 files) + bundler 219 SLOC (1 file) + supporting 191 SLOC (2 files) + libraries 158 SLOC (2 files) + helpers 93 SLOC (1 file) + events 28 SLOC (2 files) + validators 16 SLOC (1 file) + tokens 13 SLOC (1 file) +``` + +**Analysis Notes:** +- **Size:** Medium codebase +- **Largest module:** Marketplace (1,163 SLOC = 19% of total) +- **Mocks:** 821 SLOC (14% of total) - can exclude from audit scope +- **Production SLOC:** ~5,240 (excluding mocks) +- **Estimated audit effort:** 26-52 hours (at 100-200 SLOC/hour) + +--- + +## Example 2: Full-Stack Audit (Contracts + TypeScript Scripts) + +**Command:** +```bash +python3 scripts/sloc_counter.py /path/to/NettyWorthV2 \ + --dirs contracts scripts test \ + --extensions .sol .ts \ + --method logical +``` + +**Output:** + +``` +============================================================ +SLOC Analysis - LOGICAL Method +============================================================ + +Total SLOC: 34,570 +Files Scanned: 134 + +By Language: +---------------------------------------- + typescript 21,362 SLOC (52 files) + solidity 13,208 SLOC (82 files) + +By Directory: +---------------------------------------- + test 15,247 SLOC (28 files) + contracts 13,208 SLOC (82 files) + scripts 6,014 SLOC (23 files) + root 101 SLOC (1 file) +``` + +**Analysis Notes:** +- **Size:** Large codebase +- **Language split:** 62% TypeScript, 38% Solidity +- **Test coverage:** Tests are 44% of total SLOC (good coverage) +- **Scripts:** Deployment/utility scripts are 17% of total +- **Audit focus:** Contracts (13,208) + critical scripts (e.g., deployment) + +--- + +## Example 3: Physical SLOC Comparison + +**Command (Physical):** +```bash +python3 scripts/sloc_counter.py /path/to/NettyWorthV2 \ + --dirs contracts \ + --extensions .sol \ + --method physical +``` + +**Output:** + +``` +============================================================ +SLOC Analysis - PHYSICAL Method +============================================================ + +Total SLOC: 13,208 +Files Scanned: 82 + +By Language: +---------------------------------------- + solidity 13,208 SLOC (82 files) + +By Directory: +---------------------------------------- + marketplace 2,456 SLOC (4 files) + core 1,789 SLOC (4 files) + mocks 1,523 SLOC (19 files) + ... +``` + +**Comparison with Logical SLOC:** +- **Logical SLOC:** 6,061 statements +- **Physical SLOC:** 13,208 lines +- **Ratio:** ~2.18 physical lines per logical statement +- **Interpretation:** Code has moderate multi-line formatting (functions, structs, etc.) + +--- + +## Example 4: Rust/Go Multi-Language Project + +**Command:** +```bash +python3 scripts/sloc_counter.py /path/to/omni \ + --dirs src cmd pkg \ + --extensions .go .rs \ + --method logical +``` + +**Output:** + +``` +============================================================ +SLOC Analysis - LOGICAL Method +============================================================ + +Total SLOC: 45,230 +Files Scanned: 156 + +By Language: +---------------------------------------- + go 38,450 SLOC (128 files) + rust 6,780 SLOC (28 files) + +By Directory: +---------------------------------------- + src 20,340 SLOC (85 files) + pkg 18,110 SLOC (43 files) + cmd 6,780 SLOC (28 files) +``` + +**Analysis Notes:** +- **Size:** Very Large codebase +- **Language split:** 85% Go, 15% Rust +- **Architecture:** `src/` (core), `pkg/` (packages), `cmd/` (entry points) + +--- + +## Example 5: Differential SLOC (Before/After Upgrade) + +**Scenario:** Comparing `v1.0` vs `v2.0` branches + +**Commands:** +```bash +# Checkout v1.0 +git checkout v1.0 +python3 scripts/sloc_counter.py . --dirs contracts --extensions .sol --method logical > v1_sloc.txt + +# Checkout v2.0 +git checkout v2.0 +python3 scripts/sloc_counter.py . --dirs contracts --extensions .sol --method logical > v2_sloc.txt + +# Manual comparison +diff v1_sloc.txt v2_sloc.txt +``` + +**Comparison:** + +| Metric | v1.0 | v2.0 | Delta | +|--------|------|------|-------| +| Total SLOC | 5,061 | 6,061 | +1,000 (+19.8%) | +| Files | 75 | 82 | +7 | +| Largest module | Marketplace (980) | Marketplace (1,163) | +183 | + +**New modules in v2.0:** +- `earn/CollateralVault.sol` - 320 SLOC +- `earn/LendingVault.sol` - 211 SLOC +- `polylend/` (entire directory) - 555 SLOC + +**Analysis Notes:** +- **Growth:** 20% increase in codebase size +- **New features:** Earn module (531 SLOC), Polylend integration (555 SLOC) +- **Audit delta:** Focus on new modules + modified contracts +- **Estimated delta audit:** 10-20 hours for new code + +--- + +## Example 6: Python Smart Contract Project (Vyper) + +**Command:** +```bash +python3 scripts/sloc_counter.py /path/to/yield-basis \ + --dirs contracts \ + --extensions .vy .vyi \ + --method logical +``` + +**Output:** + +``` +============================================================ +SLOC Analysis - LOGICAL Method +============================================================ + +Total SLOC: 3,450 +Files Scanned: 22 + +By Language: +---------------------------------------- + vy 3,380 SLOC (21 files) + vyi 70 SLOC (1 file) + +By Directory: +---------------------------------------- + amm 1,850 SLOC (8 files) + dao 1,200 SLOC (7 files) + lt 330 SLOC (6 files) + interfaces 70 SLOC (1 file) +``` + +**Analysis Notes:** +- **Size:** Small-Medium codebase +- **Language:** Vyper (Python-based smart contract language) +- **Largest module:** AMM (54% of total SLOC) + +--- + +## Example 7: JSON Export for Reporting + +**Command:** +```bash +python3 scripts/sloc_counter.py /path/to/repo \ + --dirs contracts \ + --extensions .sol \ + --method logical \ + --output json > sloc_report.json +``` + +**Output (sloc_report.json):** + +```json +{ + "timestamp": "2026-01-22T14:30:00Z", + "repository": "/path/to/NettyWorthV2", + "method": "logical", + "total_sloc": 6061, + "total_files": 82, + "by_language": { + "solidity": { + "sloc": 6061, + "files": 82, + "percentage": 100.0 + } + }, + "by_folder": { + "marketplace": {"sloc": 1163, "files": 4}, + "core": {"sloc": 842, "files": 4}, + "mocks": {"sloc": 821, "files": 19}, + "polylend": {"sloc": 555, "files": 8}, + "earn": {"sloc": 531, "files": 2}, + "interfaces": {"sloc": 473, "files": 20}, + "oracles": {"sloc": 380, "files": 2}, + "vault": {"sloc": 293, "files": 2}, + "errors": {"sloc": 285, "files": 11}, + "bundler": {"sloc": 219, "files": 1}, + "supporting": {"sloc": 191, "files": 2}, + "libraries": {"sloc": 158, "files": 2}, + "helpers": {"sloc": 93, "files": 1}, + "events": {"sloc": 28, "files": 2}, + "validators": {"sloc": 16, "files": 1}, + "tokens": {"sloc": 13, "files": 1} + }, + "size_classification": "medium", + "estimated_audit_hours": { + "min": 30, + "max": 60, + "note": "Based on 100-200 SLOC/hour for security audit" + } +} +``` + +--- + +## Notes on Interpretation + +### Size Classifications +- **Tiny:** < 500 SLOC (1-3 contracts) +- **Small:** 500 - 2,000 SLOC (simple protocol) +- **Medium:** 2,000 - 10,000 SLOC (moderate complexity) +- **Large:** 10,000 - 50,000 SLOC (complex system) +- **Very Large:** > 50,000 SLOC (ecosystem/platform) + +### Audit Effort Estimates +- **Basic review:** 200-300 SLOC/hour +- **Standard audit:** 100-200 SLOC/hour +- **Deep audit:** 50-100 SLOC/hour +- **Critical systems:** 20-50 SLOC/hour + +Adjust based on: +- Code complexity (DeFi, governance, novel crypto) +- Test coverage (better tests = faster audit) +- Documentation quality +- Team experience with codebase +- External dependencies (oracles, cross-chain, etc.) diff --git a/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/references/SLOC_Standards.md b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/references/SLOC_Standards.md new file mode 100644 index 0000000..070978d --- /dev/null +++ b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/references/SLOC_Standards.md @@ -0,0 +1,180 @@ +# SLOC Counting Standards Reference + +## Overview + +This document defines the Source Lines of Code (SLOC) counting standards used in this skill. + +--- + +## Physical SLOC (SLOCCount) + +**Definition:** Count of all non-empty, non-comment lines in source files. + +**Rules:** +1. Blank lines are **excluded** +2. Comment-only lines are **excluded** (both `//` and `/* */`) +3. Each physical line with code counts as **1** +4. Multi-line statements count as **N lines** (one per physical line) + +**Example:** + +```solidity +function transfer( // Line 1 + address to, // Line 2 + uint256 amount // Line 3 +) external { // Line 4 + // Validate recipient + require(to != address(0)); // Line 5 + + balances[msg.sender] -= amount; // Line 6 + balances[to] += amount; // Line 7 +} +``` + +**Physical SLOC:** 7 lines (comments and blank line excluded) + +--- + +## Logical SLOC (NCSS - Non-Commenting Source Statements) + +**Definition:** Count of executable or declarative statements, regardless of physical line count. + +**Rules:** +1. Multi-line expressions count as **one** statement +2. Statements are delimited by: + - Semicolons (`;`) - excluding `for(;;)` header semicolons + - Block openings (`{`) for declarations + - Control keywords: `if`, `else`, `for`, `while`, `do`, `switch`, `case`, `default`, `try`, `catch` +3. Declaration keywords: `contract`, `interface`, `library`, `struct`, `enum`, `function`, `constructor`, `modifier`, `fallback`, `receive` + +**Example (same code as above):** + +```solidity +function transfer( + address to, + uint256 amount +) external { // Statement 1: function declaration + // Validate recipient + require(to != address(0)); // Statement 2: require call + + balances[msg.sender] -= amount; // Statement 3: assignment + balances[to] += amount; // Statement 4: assignment +} +``` + +**Logical SLOC:** 4 statements + +--- + +## When to Use Each Method + +| Metric | Use Case | Advantages | Disadvantages | +|--------|----------|------------|---------------| +| **Physical SLOC** | Quick sizing, LOC-based billing | Simple, objective | Skewed by formatting | +| **Logical SLOC** | Complexity assessment, audit effort | Reflects actual logic | Requires parsing | + +**Recommendation for Smart Contract Audits:** Use **Logical SLOC** as it better reflects code complexity and audit effort. + +--- + +## Language-Specific Notes + +### Solidity +- Assembly blocks: Each instruction in `assembly {}` counts as 1 statement +- Modifiers: Count as 1 statement at definition, not at application +- Events: Event declarations count as 1, `emit` statements count as 1 + +### TypeScript/JavaScript +- Arrow functions: Count as 1 statement +- Ternary operators: Count as 1 statement (inline `if`) +- Template literals: Multi-line templates count as 1 statement + +### Python +- Decorators: Each `@decorator` counts as 1 statement +- List comprehensions: Count as 1 statement +- Multi-line strings: Docstrings excluded (treated as comments) + +--- + +## Edge Cases + +### Case 1: Inline Conditionals +```solidity +uint256 result = x > y ? x : y; +``` +- **Physical SLOC:** 1 line +- **Logical SLOC:** 1 statement (ternary = inline if) + +### Case 2: Chained Calls +```solidity +token.approve(spender, amount) + .transfer(recipient, value) + .burn(burnAmount); +``` +- **Physical SLOC:** 3 lines +- **Logical SLOC:** 1 statement (chained method calls) + +### Case 3: For Loop Headers +```solidity +for (uint i = 0; i < 10; i++) { ... } +``` +- **Physical SLOC:** 1 line +- **Logical SLOC:** 1 statement (the `for` keyword, not the semicolons inside) + +### Case 4: Multi-Line Struct Definition +```solidity +struct User { + address addr; + uint256 balance; + bool active; +} +``` +- **Physical SLOC:** 5 lines (excluding opening brace if alone) +- **Logical SLOC:** 1 statement (struct definition) + 3 statements (field declarations) = 4 + +--- + +## Industry Standards + +### SLOCCount (Wheeler, 2001) +- Physical SLOC measure +- Language-specific comment syntax +- Widely used for OSS projects + +### COCOMO (Constructive Cost Model) +- Uses SLOC for effort estimation +- Typically refers to Logical SLOC +- Formula: Effort = A × (KLOC)^B + +### IEEE 1045 Standard +- Defines "Source Statement" ≈ Logical SLOC +- Excludes comments, blank lines, non-executable declarations + +--- + +## References + +1. Wheeler, D. (2001). "SLOCCount" - https://dwheeler.com/sloccount/ +2. Boehm, B. (1981). "Software Engineering Economics" (COCOMO) +3. IEEE 1045-1992 - "Standard for Software Productivity Metrics" +4. Park, R. (1992). "Software Size Measurement: A Framework for Counting Source Statements" + +--- + +## Implementation Notes + +This skill uses a **custom Logical SLOC counter** optimized for: +- Solidity smart contracts (primary) +- TypeScript/JavaScript (secondary) +- Other C-like languages (best-effort) + +**Comment Stripping Algorithm:** +- State machine tracks context (normal / line-comment / block-comment / string) +- Preserves string literals to avoid false positives +- Handles escaped quotes and multi-line strings + +**Statement Counting Algorithm:** +- Tokenizes code into keywords and symbols +- Tracks declaration context (pending function/contract/etc.) +- Tracks parenthesis depth for `for` loop headers +- Increments count on statement terminators (`;`, `{`, control keywords) diff --git a/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/scripts/sloc_counter.py b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/scripts/sloc_counter.py new file mode 100644 index 0000000..62ba6a9 --- /dev/null +++ b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/scripts/sloc_counter.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 +""" +SLOC Counter - Logical and Physical Source Lines of Code Counter +Supports Solidity, TypeScript, JavaScript, Python, Rust, and Go +""" + +import os +import sys +from collections import defaultdict + + +def strip_comments(code: str) -> str: + """Remove line and block comments while preserving string literals.""" + out = [] + i = 0 + n = len(code) + state = "normal" + quote = "" + + while i < n: + ch = code[i] + nxt = code[i + 1] if i + 1 < n else "" + + if state == "normal": + if ch == "/" and nxt == "/": + state = "line" + i += 2 + continue + if ch == "/" and nxt == "*": + state = "block" + i += 2 + continue + if ch in ('"', "'", "`"): + state = "string" + quote = ch + out.append(ch) + i += 1 + continue + out.append(ch) + i += 1 + continue + + if state == "line": + if ch == "\n": + out.append("\n") + state = "normal" + i += 1 + continue + + if state == "block": + if ch == "\n": + out.append("\n") + i += 1 + continue + if ch == "*" and nxt == "/": + state = "normal" + i += 2 + continue + i += 1 + continue + + if state == "string": + out.append(ch) + if ch == "\\": + if i + 1 < n: + out.append(code[i + 1]) + i += 2 + else: + i += 1 + continue + if ch == quote: + state = "normal" + quote = "" + i += 1 + continue + + return "".join(out) + + +def logical_sloc(code: str) -> int: + """Count logical statements for Solidity/TypeScript-like languages.""" + code = strip_comments(code) + count = 0 + i = 0 + n = len(code) + token = [] + pending_decl = None + pending_for = False + paren_depth = 0 + for_paren_depth = None + in_string = False + quote = "" + + DECL_KEYWORDS = { + "contract", "interface", "library", "struct", "enum", + "function", "constructor", "modifier", "fallback", "receive" + } + CONTROL_KEYWORDS = { + "if", "else", "for", "while", "do", "switch", "case", + "default", "try", "catch", "assembly", "unchecked" + } + + def flush_token(): + nonlocal pending_decl, pending_for, count, token + if not token: + return + word = "".join(token) + if word in DECL_KEYWORDS: + pending_decl = word + if word in CONTROL_KEYWORDS: + count += 1 + if word == "for": + pending_for = True + token = [] + + while i < n: + ch = code[i] + + if in_string: + if ch == "\\": + i += 2 + continue + if ch == quote: + in_string = False + quote = "" + i += 1 + continue + + if ch in ('"', "'", "`"): + flush_token() + in_string = True + quote = ch + i += 1 + continue + + if ch.isalnum() or ch == "_": + token.append(ch) + i += 1 + continue + + flush_token() + + if ch == "(": + paren_depth += 1 + if pending_for: + for_paren_depth = paren_depth + pending_for = False + i += 1 + continue + + if ch == ")": + if for_paren_depth is not None and paren_depth == for_paren_depth: + for_paren_depth = None + paren_depth = max(paren_depth - 1, 0) + i += 1 + continue + + if ch == "{": + if pending_decl: + count += 1 + pending_decl = None + i += 1 + continue + + if ch == ";": + if for_paren_depth is None or paren_depth < for_paren_depth: + count += 1 + pending_decl = None + i += 1 + continue + + i += 1 + + flush_token() + return count + + +def physical_sloc(code: str) -> int: + """Count physical non-empty, non-comment lines.""" + stripped = strip_comments(code) + return sum(1 for line in stripped.splitlines() if line.strip()) + + +def count_sloc(root_dir, include_dirs, extensions, method="logical"): + """ + Count SLOC for specified directories and file extensions. + + Args: + root_dir: Root directory to scan + include_dirs: List of subdirectories to include + extensions: Set of file extensions to count (e.g., {".sol", ".ts"}) + method: "logical" or "physical" + + Returns: + dict: Results with totals and per-folder breakdown + """ + counter_func = logical_sloc if method == "logical" else physical_sloc + + files = [] + for rel in include_dirs: + base = os.path.join(root_dir, rel) + if not os.path.exists(base): + print(f"Warning: Directory not found: {base}", file=sys.stderr) + continue + + for dirpath, _, filenames in os.walk(base): + for name in filenames: + ext = os.path.splitext(name)[1] + if ext in extensions: + files.append(os.path.join(dirpath, name)) + + files = sorted(set(files)) + + by_folder = defaultdict(int) + by_folder_files = defaultdict(int) + by_language = defaultdict(int) + by_language_files = defaultdict(int) + + for path in files: + try: + with open(path, "r", encoding="utf-8", errors="ignore") as f: + text = f.read() + except Exception as e: + print(f"Error reading {path}: {e}", file=sys.stderr) + continue + + sloc = counter_func(text) + + # Determine language + ext = os.path.splitext(path)[1] + lang_map = { + ".sol": "solidity", + ".ts": "typescript", + ".js": "javascript", + ".py": "python", + ".rs": "rust", + ".go": "go" + } + lang = lang_map.get(ext, ext[1:] if ext else "unknown") + + # Determine folder (use immediate parent if within included dir) + rel = os.path.relpath(path, root_dir) + parts = rel.split(os.sep) + # If file is directly in an include_dir, use that dir + # Otherwise, use the subdirectory within the include_dir + if len(parts) > 1 and parts[0] in include_dirs: + folder = os.path.join(parts[0], parts[1]) if len(parts) > 2 else parts[0] + else: + folder = parts[0] + + by_folder[folder] += sloc + by_folder_files[folder] += 1 + by_language[lang] += sloc + by_language_files[lang] += 1 + + total = sum(by_folder.values()) + + return { + "total": total, + "file_count": len(files), + "method": method, + "by_folder": dict(by_folder), + "by_folder_files": dict(by_folder_files), + "by_language": dict(by_language), + "by_language_files": dict(by_language_files) + } + + +def print_results(results): + """Pretty-print SLOC results.""" + print(f"\n{'='*60}") + print(f"SLOC Analysis - {results['method'].upper()} Method") + print(f"{'='*60}\n") + + print(f"Total SLOC: {results['total']:,}") + print(f"Files Scanned: {results['file_count']}\n") + + if results['by_language']: + print("By Language:") + print("-" * 40) + for lang in sorted(results['by_language'], key=results['by_language'].get, reverse=True): + sloc = results['by_language'][lang] + files = results['by_language_files'][lang] + print(f" {lang:15} {sloc:>8,} SLOC ({files} files)") + print() + + if results['by_folder']: + print("By Directory:") + print("-" * 40) + for folder in sorted(results['by_folder'], key=results['by_folder'].get, reverse=True): + sloc = results['by_folder'][folder] + files = results['by_folder_files'][folder] + print(f" {folder:25} {sloc:>8,} SLOC ({files} files)") + print() + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Count SLOC for codebases") + parser.add_argument("root", help="Root directory to scan") + parser.add_argument("-d", "--dirs", nargs="+", default=["contracts", "src", "scripts"], + help="Directories to include (default: contracts src scripts)") + parser.add_argument("-e", "--extensions", nargs="+", default=[".sol", ".ts", ".js"], + help="File extensions to count (default: .sol .ts .js)") + parser.add_argument("-m", "--method", choices=["logical", "physical"], default="logical", + help="Counting method (default: logical)") + + args = parser.parse_args() + + extensions = set(args.extensions) + results = count_sloc(args.root, args.dirs, extensions, args.method) + print_results(results) From 0249450d8f61a057f476519948694096e412d119 Mon Sep 17 00:00:00 2001 From: cryptanu Date: Thu, 22 Jan 2026 13:58:20 +0100 Subject: [PATCH 2/3] Remove real project naming from SLOC examples --- .../skills/sloc-initial-sweep/SKILL.md | 28 ++++---- .../examples/sample_output.md | 72 +++++++++---------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/SKILL.md b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/SKILL.md index 816b9c2..acee0b6 100644 --- a/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/SKILL.md +++ b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/SKILL.md @@ -188,16 +188,16 @@ By Language: By Directory: ---------------------------------------- - marketplace 1,163 SLOC (4 files) - core 842 SLOC (4 files) - mocks 821 SLOC (19 files) - polylend 555 SLOC (8 files) - earn 531 SLOC (2 files) - interfaces 473 SLOC (20 files) - oracles 380 SLOC (2 files) - vault 293 SLOC (2 files) - errors 285 SLOC (11 files) - bundler 219 SLOC (1 file) + module-a 1,163 SLOC (4 files) + module-b 842 SLOC (4 files) + mocks 821 SLOC (19 files) + module-c 555 SLOC (8 files) + module-d 531 SLOC (2 files) + interfaces 473 SLOC (20 files) + adapters 380 SLOC (2 files) + vaults 293 SLOC (2 files) + errors 285 SLOC (11 files) + bundler 219 SLOC (1 file) [... remaining folders ...] ``` @@ -232,7 +232,7 @@ By Directory: > **SLOC Summary:** 6,061 logical SLOC across 82 Solidity files in `contracts/`. > > **Size:** Medium codebase -> **Largest modules:** Marketplace (1,163), Core (842), Mocks (821) +> **Largest modules:** module-a (1,163), module-b (842), mocks (821) > **Estimated effort:** 30-60 hours for comprehensive audit (excluding mocks) > **Notes:** Mocks excluded from audit scope, reducing focus area to ~5,240 SLOC @@ -266,8 +266,8 @@ By Directory: "solidity": 6061 }, "by_folder": { - "marketplace": 1163, - "core": 842, + "module-a": 1163, + "module-b": 842, ... } } @@ -321,7 +321,7 @@ By Directory: 1. Run SLOC on `main` branch 2. Run SLOC on `upgrade` branch 3. Present delta: "Added 1,200 SLOC (+19.8%), Removed 300 SLOC" -4. Identify changed modules: "Marketplace +500, Vault +400, New: LendingVault 300" +4. Identify changed modules: "module-a +500, vaults +400, New: module-d 300" --- diff --git a/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/examples/sample_output.md b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/examples/sample_output.md index 1e041e5..87a477c 100644 --- a/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/examples/sample_output.md +++ b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/examples/sample_output.md @@ -8,7 +8,7 @@ This document shows example outputs for various audit scenarios. **Command:** ```bash -python3 scripts/sloc_counter.py /path/to/NettyWorthV2 \ +python3 scripts/sloc_counter.py /path/to/project \ --dirs contracts \ --extensions .sol \ --method logical @@ -30,27 +30,27 @@ By Language: By Directory: ---------------------------------------- - marketplace 1,163 SLOC (4 files) - core 842 SLOC (4 files) - mocks 821 SLOC (19 files) - polylend 555 SLOC (8 files) - earn 531 SLOC (2 files) - interfaces 473 SLOC (20 files) - oracles 380 SLOC (2 files) - vault 293 SLOC (2 files) - errors 285 SLOC (11 files) - bundler 219 SLOC (1 file) - supporting 191 SLOC (2 files) - libraries 158 SLOC (2 files) - helpers 93 SLOC (1 file) - events 28 SLOC (2 files) - validators 16 SLOC (1 file) - tokens 13 SLOC (1 file) + module-a 1,163 SLOC (4 files) + module-b 842 SLOC (4 files) + mocks 821 SLOC (19 files) + module-c 555 SLOC (8 files) + module-d 531 SLOC (2 files) + interfaces 473 SLOC (20 files) + adapters 380 SLOC (2 files) + vaults 293 SLOC (2 files) + errors 285 SLOC (11 files) + bundler 219 SLOC (1 file) + supporting 191 SLOC (2 files) + libraries 158 SLOC (2 files) + helpers 93 SLOC (1 file) + events 28 SLOC (2 files) + validators 16 SLOC (1 file) + tokens 13 SLOC (1 file) ``` **Analysis Notes:** - **Size:** Medium codebase -- **Largest module:** Marketplace (1,163 SLOC = 19% of total) +- **Largest module:** module-a (1,163 SLOC = 19% of total) - **Mocks:** 821 SLOC (14% of total) - can exclude from audit scope - **Production SLOC:** ~5,240 (excluding mocks) - **Estimated audit effort:** 26-52 hours (at 100-200 SLOC/hour) @@ -61,7 +61,7 @@ By Directory: **Command:** ```bash -python3 scripts/sloc_counter.py /path/to/NettyWorthV2 \ +python3 scripts/sloc_counter.py /path/to/project \ --dirs contracts scripts test \ --extensions .sol .ts \ --method logical @@ -103,7 +103,7 @@ By Directory: **Command (Physical):** ```bash -python3 scripts/sloc_counter.py /path/to/NettyWorthV2 \ +python3 scripts/sloc_counter.py /path/to/project \ --dirs contracts \ --extensions .sol \ --method physical @@ -125,9 +125,9 @@ By Language: By Directory: ---------------------------------------- - marketplace 2,456 SLOC (4 files) - core 1,789 SLOC (4 files) - mocks 1,523 SLOC (19 files) + module-a 2,456 SLOC (4 files) + module-b 1,789 SLOC (4 files) + mocks 1,523 SLOC (19 files) ... ``` @@ -202,16 +202,16 @@ diff v1_sloc.txt v2_sloc.txt |--------|------|------|-------| | Total SLOC | 5,061 | 6,061 | +1,000 (+19.8%) | | Files | 75 | 82 | +7 | -| Largest module | Marketplace (980) | Marketplace (1,163) | +183 | +| Largest module | module-a (980) | module-a (1,163) | +183 | **New modules in v2.0:** -- `earn/CollateralVault.sol` - 320 SLOC -- `earn/LendingVault.sol` - 211 SLOC -- `polylend/` (entire directory) - 555 SLOC +- `module-d/VaultCore.sol` - 320 SLOC +- `module-d/VaultConfig.sol` - 211 SLOC +- `module-c/` (entire directory) - 555 SLOC **Analysis Notes:** - **Growth:** 20% increase in codebase size -- **New features:** Earn module (531 SLOC), Polylend integration (555 SLOC) +- **New features:** New earnings module (531 SLOC), external integration module (555 SLOC) - **Audit delta:** Focus on new modules + modified contracts - **Estimated delta audit:** 10-20 hours for new code @@ -221,7 +221,7 @@ diff v1_sloc.txt v2_sloc.txt **Command:** ```bash -python3 scripts/sloc_counter.py /path/to/yield-basis \ +python3 scripts/sloc_counter.py /path/to/project-vyper \ --dirs contracts \ --extensions .vy .vyi \ --method logical @@ -273,7 +273,7 @@ python3 scripts/sloc_counter.py /path/to/repo \ ```json { "timestamp": "2026-01-22T14:30:00Z", - "repository": "/path/to/NettyWorthV2", + "repository": "/path/to/project", "method": "logical", "total_sloc": 6061, "total_files": 82, @@ -285,14 +285,14 @@ python3 scripts/sloc_counter.py /path/to/repo \ } }, "by_folder": { - "marketplace": {"sloc": 1163, "files": 4}, - "core": {"sloc": 842, "files": 4}, + "module-a": {"sloc": 1163, "files": 4}, + "module-b": {"sloc": 842, "files": 4}, "mocks": {"sloc": 821, "files": 19}, - "polylend": {"sloc": 555, "files": 8}, - "earn": {"sloc": 531, "files": 2}, + "module-c": {"sloc": 555, "files": 8}, + "module-d": {"sloc": 531, "files": 2}, "interfaces": {"sloc": 473, "files": 20}, - "oracles": {"sloc": 380, "files": 2}, - "vault": {"sloc": 293, "files": 2}, + "adapters": {"sloc": 380, "files": 2}, + "vaults": {"sloc": 293, "files": 2}, "errors": {"sloc": 285, "files": 11}, "bundler": {"sloc": 219, "files": 1}, "supporting": {"sloc": 191, "files": 2}, From 8ea0bfe835af3840f9b214c484e13eecfdab0722 Mon Sep 17 00:00:00 2001 From: Adeleke Anuoluwapo <102337992+cryptanu@users.noreply.github.com> Date: Mon, 26 Jan 2026 10:46:31 +0100 Subject: [PATCH 3/3] Update sloc_counter.py Fix ruff lint issues --- .../skills/sloc-initial-sweep/scripts/sloc_counter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/scripts/sloc_counter.py b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/scripts/sloc_counter.py index 62ba6a9..4c86569 100644 --- a/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/scripts/sloc_counter.py +++ b/plugins/sloc-initial-sweep/skills/sloc-initial-sweep/scripts/sloc_counter.py @@ -218,7 +218,7 @@ def count_sloc(root_dir, include_dirs, extensions, method="logical"): for path in files: try: - with open(path, "r", encoding="utf-8", errors="ignore") as f: + with open(path, encoding="utf-8", errors="ignore") as f: text = f.read() except Exception as e: print(f"Error reading {path}: {e}", file=sys.stderr)