Claude Skill

dimensional-analysis

Annotates codebases with dimensional analysis comments documenting units, dimensions, and decimal scaling. Use when someone asks to annotate units in a codebase, perform a dimensional analysis, or find vulnerabilities in a DeFi protocol, offchain code, or other blockchain-related

LLM Mart · 0 points · 7 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download trailofbits-skills-plugins_dimensional-analysis_skills_dimensional-analysis-123037e.zip · 19 KB
trailofbits/skills 7234 616 forks CC-BY-SA-4.0 Updated 8h ago
Part of trailofbits/skills — 100 skills

Install

skills CLI npx skills add https://github.com/trailofbits/skills/tree/main/plugins/dimensional-analysis/skills/dimensional-analysis
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install trailofbits-skills@llmmart
Git git clone https://github.com/trailofbits/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole trailofbits/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Dimensional Analysis Skill

This skill orchestrates a dimensional-analysis pipeline for codebases that perform numeric computations with mixed units, precisions, or scaling factors. The main skill context is a workflow controller only: it delegates scanning, vocabulary discovery, annotation, propagation, and validation to specialized subagents, then manages batching, persistence, retries, coverage gates, and final reporting.

When to Use

  • Annotating a codebase with unit/dimension comments (e.g., D18{tok}, D27{UoA/tok})
  • Performing dimensional analysis on DeFi protocols, financial code, or scientific computations
  • Hunting for arithmetic bugs caused by unit mismatches, missing scaling, or precision loss
  • Auditing codebases with mixed decimal precisions or fixed-point arithmetic

When NOT to Use

  • Codebases with no numeric arithmetic or unit conversions — there is nothing to annotate
  • Pure integer counting logic (loop indices, array lengths) with no physical or financial dimensions
  • When you only need a quick spot-check of a single formula — read the code directly instead of running the full pipeline

Execution Mode

This skill runs in one mode only: full-auto. This is a workflow-based skill that delegates step-specific work to specialized agents via the Task tool. You orchestrate the overall process, manage coverage and state persistence, and ensure that every in-scope file is processed through each step of the pipeline.

  • Always run the full pipeline in this order: Step 1 -> Step 2 -> Step 3 -> Step 4.
  • The main skill context must not perform repository-wide dimensional analysis, annotation, propagation, or bug validation itself when a dedicated subagent exists for that step.
  • The main skill context may inspect artifacts, manifests, and subagent outputs only as needed to route work, build prompts, persist state, and determine completion.
  • Any mode argument provided by the caller is ignored.
  • Report all results at the end in a single summary.

When you start a step, report it:

Starting Step: Step {n}

Scope and Coverage Guarantees

This skill must audit all in-scope arithmetic files, including large repositories.

  • In-scope files are defined by Step 1 scanner output (files array), across all priority tiers (CRITICAL, HIGH, MEDIUM, LOW).
  • If Step 1 narrows inputs for vocabulary discovery (for example, CRITICAL/HIGH only), that narrowing applies to discovery only. It never reduces annotation or validation scope.
  • arithmetic-scanner persists the in-scope file manifest to DIMENSIONAL_SCOPE.json in the project root, and that manifest is the source of truth for Steps 2-4.
  • A file is considered fully covered only when all three statuses are present:
    • step2: anchor annotation completed (or explicit no-anchor result)
    • step3: propagation completed (or explicit no-propagation result)
    • step4: validation completed
  • dimension-discoverer persists the discovered dimensional vocabulary to DIMENSIONAL_UNITS.md in the project root for reuse by later steps and future runs.
  • When a file ends in a terminal BLOCKED state, persist the blocking reason and retry count in DIMENSIONAL_SCOPE.json and reflect the same file in coverage.unprocessed_files.
  • Do not finish while any in-scope file remains unprocessed in any step.

Delegation Contract

  • arithmetic-scanner owns repository scanning, arithmetic-file prioritization, and writing DIMENSIONAL_SCOPE.json.
  • dimension-discoverer owns dimensional vocabulary discovery, unit inference, and writing DIMENSIONAL_UNITS.md.
  • dimension-annotator owns annotation format decisions, anchor-point edits, and comment-writing behavior.
  • dimension-propagator owns propagation logic, inferred annotations, and mismatch reporting during tracing.
  • dimension-validator owns bug detection, red-flag evaluation, rationalization rejection, and confirmation or refutation of propagated mismatches.
  • The main skill context must not substitute its own dimensional reasoning for skipped or unlaunched subagents. If a step requires specialized reasoning, launch the corresponding subagent.
  • Use reference files as subagent support material. Pass them to the relevant step in prompts instead of treating them as instructions for the main skill context.

Workflow

Follow these sections in order. Do not advance until the current step satisfies its completion gate.

Shared Orchestration Rules

  • DIMENSIONAL_SCOPE.json and DIMENSIONAL_UNITS.md live in the project root.
  • The main skill context verifies Step 1 artifacts but does not write either Step 1 artifact itself.
  • DIMENSIONAL_SCOPE.json.in_scope_files is the source of truth for Steps 2-4. Never derive later scope from discovery-only inputs.
  • When a later step reaches terminal BLOCKED, persist the matching step*_reason and step*_retry_count fields on the file entry in DIMENSIONAL_SCOPE.json.
  • coverage.unprocessed_files must be derived from terminal BLOCKED entries in DIMENSIONAL_SCOPE.json using { "path": "...", "blocked_step": "step2|step3|step4", "reason": "...", "retry_count": 1 }.
  • A step may retry a BLOCKED file once with a focused prompt. If it is still BLOCKED, keep the documented reason and continue. Do not finalize while any file remains PENDING.

Step 1: Vocabulary and Scope Discovery

If cached artifacts cannot be reused, delegate repository scanning to arithmetic-scanner and vocabulary discovery to dimension-discoverer. Do not do that step-specific analysis directly in the main skill context.

  1. Check whether DIMENSIONAL_UNITS.md and DIMENSIONAL_SCOPE.json already exist in the project root.
  2. If both exist, read them and confirm:
    • DIMENSIONAL_SCOPE.json.project_root matches the current repo root
    • DIMENSIONAL_SCOPE.json contains in_scope_files, discoverer_focus_files, recommended_discovery_order, and per-file step2, step3, step4 fields
    • DIMENSIONAL_UNITS.md is a usable dimensional vocabulary for this repo
  3. If either artifact is stale, malformed, missing required structure, or clearly for another repo, discard reuse and rerun the rest of Step 1.
  4. If both artifacts are valid, reuse them directly. If in_scope_files is empty, skip Steps 2-4 and produce final output with zero findings.
  5. Otherwise use the Task tool to spawn the arithmetic-scanner agent. Its prompt must include:
    • project root path
    • absolute output path for DIMENSIONAL_SCOPE.json
    • instruction to write the Step 1 scope manifest to disk and return the same scope data in its report
  6. The scanner owns Step 1 scope persistence. It must:
    • identify dimensional-arithmetic files and prioritize them as usual
    • write DIMENSIONAL_SCOPE.json with project_root, in_scope_files, discoverer_focus_files, and recommended_discovery_order
    • initialize every in-scope file with step2: "PENDING", step3: "PENDING", and step4: "PENDING"
    • still write an empty manifest when no arithmetic files are found
    • still narrow discoverer_focus_files to CRITICAL/HIGH when more than 50 arithmetic files are found, while keeping all priorities in in_scope_files
  7. After the scanner completes, read DIMENSIONAL_SCOPE.json from disk and confirm it exists and contains the required Step 1 fields before continuing.
  8. Use the Task tool to spawn the dimension-discoverer agent. Its prompt must include:
    • project root path
    • absolute path to DIMENSIONAL_SCOPE.json
    • absolute output path for DIMENSIONAL_UNITS.md
    • prioritized discoverer_focus_files with each file's path, priority, score, and category
    • recommended_discovery_order
  9. The discoverer owns Step 1 vocabulary persistence. It must read DIMENSIONAL_SCOPE.json as the Step 1 source of truth and write DIMENSIONAL_UNITS.md with Base Units, Derived Units, and Precision Prefixes sections. If in_scope_files is empty, it must still write the same headings with empty sections.
  10. Step 1 is complete only when both artifacts exist on disk, pass the reuse checks above, and correctly represent the zero-file case. If in_scope_files is empty after the discoverer writes DIMENSIONAL_UNITS.md, skip Steps 2-4 and produce final output with zero findings.

Step 2: Anchor Annotation

The main skill context must not add annotations itself. Use the Task tool to spawn dimension-annotator agents for all anchor-point annotation work. For full examples and annotation format details, see [{baseDir}/references/annotate.md]({baseDir}/references/annotate.md).

  • Read DIMENSIONAL_SCOPE.json and build batches from in_scope_files. Every in-scope file, including MEDIUM and LOW priority files, must receive a Step 2 outcome.
  • Batch files instead of spawning one agent per file:
    • <= 10 files: one batch
    • 11-30 files: one batch per category
    • > 30 files: one batch per category, splitting categories larger than 10 files into sub-batches of about 8 files
  • Launch categories in Step 1 recommended discovery order: math libraries, then oracles, then core logic, then peripheral. Batches inside the same category may run in parallel.
  • Before launching annotators, set step2 = "PENDING" for every in-scope file and persist the updated DIMENSIONAL_SCOPE.json.
  • Each annotator prompt must include:
    • absolute path to DIMENSIONAL_UNITS.md
    • absolute path to DIMENSIONAL_SCOPE.json
    • assigned file paths in order
    • each file's category and matched patterns from scanner output
    • summary of previously annotated interfaces or types from earlier batches, when applicable
    • required per-file status output: ANNOTATED, REVIEWED_NO_ANCHOR_CHANGES, or BLOCKED plus a one-line justification
  • After each batch, immediately persist each assigned file to exactly one Step 2 status:
    • ANNOTATED
    • REVIEWED_NO_ANCHOR_CHANGES
    • BLOCKED
  • If a file is BLOCKED, also persist step2_reason and step2_retry_count. Retry each BLOCKED file once with a focused prompt.
  • Do not continue to Step 3 while any file remains PENDING in on-disk manifest state.

Step 3: Dimension Propagation

The main skill context must not perform propagation reasoning itself. Use the Task tool to spawn dimension-propagator agents to extend annotations through arithmetic, function calls, and assignments. For algebra details, see [{baseDir}/references/dimension-algebra.md]({baseDir}/references/dimension-algebra.md).

  • Read DIMENSIONAL_SCOPE.json and build propagation batches from in_scope_files. Every in-scope file must receive a Step 3 outcome.
  • Use the same batching rules and category ordering as Step 2.
  • Before launching propagators, confirm every file already has a non-pending Step 2 status.
  • Then set step3 = "PENDING" for every in-scope file and persist the updated manifest.
  • Each propagator prompt must include:
    • absolute path to DIMENSIONAL_UNITS.md
    • absolute path to DIMENSIONAL_SCOPE.json
    • assigned file paths in order
    • each file's category and matched patterns
    • summary of Step 2 anchor annotations for the assigned files and any upstream interfaces they depend on
    • required per-file status output: PROPAGATED, REVIEWED_NO_PROPAGATION_CHANGES, or BLOCKED plus a one-line justification
  • After each batch, immediately persist each assigned file to exactly one Step 3 status:
    • PROPAGATED
    • REVIEWED_NO_PROPAGATION_CHANGES
    • BLOCKED
  • If a file is BLOCKED, also persist step3_reason and step3_retry_count. Retry each BLOCKED file once with a focused prompt.
  • After all propagators complete, aggregate:
    • annotations added by confidence level (CERTAIN, INFERRED, UNCERTAIN)
    • mismatches found, with severities for validator deduplication
    • coverage gaps that could not be inferred
  • Do not continue to Step 4 while any file remains PENDING in on-disk manifest state.

Step 4: Bug Detection

The main skill context must not perform bug detection itself. Use the Task tool to spawn dimension-validator agents to detect dimensional bugs in annotated code. For examples, red flags, rationalization checks, and standard vocabulary, see [{baseDir}/references/bug-patterns.md]({baseDir}/references/bug-patterns.md), [{baseDir}/references/common-dimensions.md]({baseDir}/references/common-dimensions.md), and [{baseDir}/references/dimension-algebra.md]({baseDir}/references/dimension-algebra.md). DO NOT DETECT BUGS IN ANY OTHER STEP.

  • Validate every file in DIMENSIONAL_SCOPE.json.in_scope_files.
  • Use this priority order without skipping lower tiers:
    1. files with CRITICAL or HIGH Step 3 mismatches
    2. remaining CRITICAL and HIGH scanner-priority files
    3. remaining MEDIUM and LOW files
  • Before launching validators, confirm every file already has a non-pending Step 3 status.
  • Then set step4 = "PENDING" for every in-scope file and persist the updated manifest.
  • Spawn one dimension-validator agent per file. For large repos, run them in waves of roughly 10-30 files to keep orchestration stable.
  • Each validator prompt must include:
    • absolute path to DIMENSIONAL_UNITS.md
    • absolute path to DIMENSIONAL_SCOPE.json
    • the single file path to validate
    • a summary of anchor and propagated annotations in the file
    • Step 3 mismatch summaries for the file, including mismatch IDs
    • cross-file function signatures or return dimensions needed for call-boundary checks
    • required per-file status output: VALIDATED or BLOCKED
  • After each wave, immediately persist each file to exactly one Step 4 status:
    • VALIDATED
    • BLOCKED
  • If a file is BLOCKED, also persist step4_reason and step4_retry_count. Retry each BLOCKED file once with a focused prompt.
  • Deduplicate findings:
    • confirmed Step 3 mismatches keep their original IDs and severities
    • refuted Step 3 mismatches are noted as false positives and excluded from final counts
    • genuinely new findings receive new DIM-XXX IDs
  • Aggregate confirmed findings, new findings, refuted findings, coverage summary, and final coverage.unprocessed_files.
  • Step 4 is complete only when DIMENSIONAL_SCOPE.json.in_scope_files contains no step4: "PENDING" entries.

Reference Documentation

Pass these references to the relevant subagent when a step needs them:

  • [{baseDir}/references/dimension-algebra.md]({baseDir}/references/dimension-algebra.md) - Propagator and validator algebra rules
  • [{baseDir}/references/common-dimensions.md]({baseDir}/references/common-dimensions.md) - Validator vocabulary reference
  • [{baseDir}/references/bug-patterns.md]({baseDir}/references/bug-patterns.md) - Validator bug-pattern and red-flag reference
  • [{baseDir}/references/annotate.md]({baseDir}/references/annotate.md) - Annotator format and example reference

Final Output

At the end of the analysis, provide a structured summary unless some other output format has been specified:

{
  "mode": "full-auto",
  "project_root": "<path>",
  "vocabulary": {
    "base_units": ["..."],
    "derived_units": ["..."],
    "precision_prefixes": ["..."]
  },
  "annotations": {
    "total_added": 0,
    "by_file": {}
  },
  "findings": {
    "critical": 0,
    "high": 0,
    "medium": 0,
    "details": []
  },
  "uncertainties_resolved": 0,
  "coverage": {
    "in_scope_files": 0,
    "anchor_reviewed_files": "0/0",
    "propagation_reviewed_files": "0/0",
    "validation_reviewed_files": "0/0",
    "annotated_functions": "0/0",
    "annotated_variables": "0/0",
    "unprocessed_files": [
      {
        "path": "/path/to/repo/contracts/LegacyMath.sol",
        "blocked_step": "step3",
        "reason": "Parser could not process generated source",
        "retry_count": 1
      }
    ]
  }
}

Completion Checklist

You are NOT done until all of these are true:

File Coverage Gates

  • DIMENSIONAL_UNITS.md exists in the project root
  • DIMENSIONAL_SCOPE.json exists in the project root and is the source of truth for downstream coverage
  • Every in-scope arithmetic file discovered in Step 1 appears in DIMENSIONAL_SCOPE.json.in_scope_files
  • Every in-scope file has a non-PENDING Step 2 status (ANNOTATED, REVIEWED_NO_ANCHOR_CHANGES, or BLOCKED)
  • Every in-scope file has a non-PENDING Step 3 status (PROPAGATED, REVIEWED_NO_PROPAGATION_CHANGES, or BLOCKED)
  • Every in-scope file has a non-PENDING Step 4 status (VALIDATED or BLOCKED)
  • No in-scope file remains PENDING in any step
  • Any BLOCKED file has a documented reason in the final output
  • coverage.unprocessed_files exactly matches the final set of terminal BLOCKED files after retries, using path, blocked_step, reason, and retry_count

Summary Report

  • Final summary JSON/report provided
  • Final coverage counters match DIMENSIONAL_SCOPE.json
  • List of modified files provided when edits occurred
  • Any dimensional mismatches or bugs found are summarized
  • Any remaining blocked or unprocessed files are called out with reasons

If DIMENSIONAL_SCOPE.json and the final report disagree, reconcile the report or continue processing until they match. Do not claim completion from agent intent alone; completion is determined by manifest coverage and final reported statuses.

Files (skills)
  • agents
    • openai.yaml 245 B
      interface:
        display_name: "Dimensional Analysis"
        short_description: "Annotate units and scaling to find arithmetic mistakes"
        icon_small: "assets/trail-of-bits-mark.svg"
        icon_large: "assets/trail-of-bits-mark.svg"
        brand_color: "#D83A34"
      
  • assets
    • trail-of-bits-mark.svg 3 KB · in bundle
  • references
    • annotate.md 11.7 KB
      # Step 2: Annotate the Codebase
      
      After defining dimensions (Step 1), add annotations to all numeric values in the code.
      
      > **Note:** Examples below use Solidity syntax. For other languages, adapt the comment syntax (e.g., `//` in Rust, `#` in Python, `//` or `/** */` in TypeScript) while keeping the same dimensional annotation format.
      
      ---
      
      ## Comment Placement Patterns
      
      ### Variable Declarations
      
      ```solidity
      // Inline (preferred for brevity)
      uint256 public totalAssets;  // D18{UNDERLYING}
      
      // Above (for longer descriptions)
      /// D18{UNDERLYING} Total assets under management, excluding pending withdrawals
      uint256 public totalAssets;
      ```
      
      ### Function Parameters (NatSpec)
      
      ```solidity
      // NatSpec (preferred)
      /// @param assets D18{UNDERLYING} Amount to deposit
      /// @param receiver Address receiving shares
      /// @return shares D18{SHARE} Shares minted
      function deposit(uint256 assets, address receiver) external returns (uint256 shares);
      
      // Inline (acceptable for simple cases)
      function deposit(
          uint256 assets,   // D18{UNDERLYING}
          address receiver
      ) external returns (uint256 shares);  // D18{SHARE}
      ```
      
      ### Struct Fields
      
      ```solidity
      struct Position {
          uint256 collateral;    // D18{COLLATERAL} Collateral deposited
          uint256 debt;          // D18{DEBT} Amount borrowed
          uint256 lastUpdate;    // {s} Timestamp of last interest accrual
      }
      ```
      
      ### Formula Verification Comments
      
      ```solidity
      // Show dimensional algebra for non-trivial formulas
      //
      // shares = assets * totalSupply / totalAssets
      // {SHARE} = {UNDERLYING} * {SHARE} / {UNDERLYING}
      //         = {SHARE} ✓
      //
      shares = assets.mulDiv(totalSupply(), totalAssets());
      ```
      
      ---
      
      ## Annotating State Variables
      
      For each state variable holding a numeric value:
      
      1. Determine its dimension from the glossary
      2. Determine its decimal scaling (D6, D8, D18, D27, etc.)
      3. Add inline comment in format: `// D{scale}{dimension} Description`
      
      ### Examples
      
      ```solidity
      // Storage variables
      uint256 public totalDeposits;     // D18{UNDERLYING} Total tokens deposited
      uint256 public totalShares;       // D18{VAULT} Total vault shares outstanding
      uint256 public lastPriceUpdate;   // {s} Timestamp of last oracle update
      uint256 public feeRate;           // D18{1} Fee as 18-decimal fraction (1e18 = 100%)
      uint256 public accumulatedFees;   // D18{UNDERLYING} Protocol fees collected
      
      // Mappings
      mapping(address => uint256) public userShares;      // D18{VAULT} per user
      mapping(address => uint256) public userDebt;        // D18{DEBT} per user
      mapping(address => uint256) public lastActionTime;  // {s} per user
      ```
      
      ---
      
      ## Annotating Function Signatures
      
      For each public/external function:
      
      1. Add NatSpec with dimensions for all numeric parameters
      2. Add dimensions for return values
      3. Format: `@param name D{scale}{dimension} Description`
      
      ### Examples
      
      ```solidity
      /// @notice Deposit assets and receive shares
      /// @param assets D18{UNDERLYING} Amount of tokens to deposit
      /// @param minShares D18{VAULT} Minimum shares to receive (slippage protection)
      /// @return shares D18{VAULT} Actual shares minted
      function deposit(uint256 assets, uint256 minShares) external returns (uint256 shares);
      
      /// @notice Get current price from oracle
      /// @return price D8{USD/UNDERLYING} Current asset price
      function getPrice() external view returns (uint256 price);
      
      /// @notice Calculate health factor for a position
      /// @param collateralValue D18{USD} Total collateral value in USD
      /// @param debtValue D18{USD} Total debt value in USD
      /// @return healthFactor D18{1} Health factor (>1e18 is healthy)
      function calculateHealthFactor(
          uint256 collateralValue,
          uint256 debtValue
      ) external pure returns (uint256 healthFactor);
      ```
      
      ---
      
      ## Full Annotated Examples
      
      ### ERC-4626 Vault
      
      ```solidity
      /**
       * ═══════════════════════════════════════════════════════════
       * DIMENSIONAL GLOSSARY
       * ═══════════════════════════════════════════════════════════
       *
       * Base Dimensions:
       * - {UNDERLYING}  The underlying asset token
       * - {SHARE}       Vault share token
       * - {s}           Time in seconds
       * - {1}           Dimensionless
       *
       * Derived Dimensions:
       * - {SHARE/UNDERLYING}  Exchange rate (shares per asset)
       * - {UNDERLYING/SHARE}  Inverse exchange rate
       */
      
      contract Vault is ERC4626 {
          uint256 public totalAssets;      // D{X}{UNDERLYING} where X = token decimals
          uint256 public totalSupply;      // D18{SHARE}
          uint256 public lastHarvestTime;  // {s} Timestamp of last yield harvest
          uint256 public performanceFee;   // D18{1} Fee rate (1e18 = 100%)
      
          /// @notice Deposit assets and receive shares
          /// @param assets D{X}{UNDERLYING} Amount to deposit
          /// @return shares D18{SHARE} Shares minted
          function deposit(uint256 assets) external returns (uint256 shares) {
              // {SHARE} = {UNDERLYING} * {SHARE} / {UNDERLYING} = {SHARE} ✓
              shares = assets.mulDiv(totalSupply(), totalAssets());
          }
      
          /// @notice Withdraw assets by burning shares
          /// @param shares D18{SHARE} Shares to burn
          /// @return assets D{X}{UNDERLYING} Assets returned
          function withdraw(uint256 shares) external returns (uint256 assets) {
              // {UNDERLYING} = {SHARE} * {UNDERLYING} / {SHARE} = {UNDERLYING} ✓
              assets = shares.mulDiv(totalAssets(), totalSupply());
          }
      
          /// @notice Get current exchange rate
          /// @return rate D18{UNDERLYING/SHARE} Assets per share
          function exchangeRate() external view returns (uint256 rate) {
              // {UNDERLYING/SHARE} = {UNDERLYING} * 1e18 / {SHARE}
              rate = totalAssets().mulDiv(1e18, totalSupply());
          }
      }
      ```
      
      ### AMM / DEX
      
      ```solidity
      /**
       * ═══════════════════════════════════════════════════════════
       * DIMENSIONAL GLOSSARY
       * ═══════════════════════════════════════════════════════════
       *
       * Base Dimensions:
       * - {TOKEN_A}  First token in pair
       * - {TOKEN_B}  Second token in pair
       * - {LP}       Liquidity provider token
       * - {1}        Dimensionless
       *
       * Derived Dimensions:
       * - {TOKEN_A * TOKEN_B}  Constant product invariant
       * - {TOKEN_B / TOKEN_A}  Price of A in terms of B
       */
      
      contract AMM {
          uint256 public reserveA;     // D{X}{TOKEN_A}
          uint256 public reserveB;     // D{Y}{TOKEN_B}
          uint256 public totalSupply;  // D18{LP}
          uint256 public kLast;        // {TOKEN_A * TOKEN_B} Last invariant value
          uint256 public swapFee;      // D18{1} Fee rate (e.g., 3e15 = 0.3%)
      
          /// @notice Swap token A for token B
          /// @param amountAIn D{X}{TOKEN_A} Amount of token A to swap
          /// @return amountBOut D{Y}{TOKEN_B} Amount of token B received
          function swapAForB(uint256 amountAIn) external returns (uint256 amountBOut) {
              // Calculate output using constant product formula
              // (reserveA + amountAIn) * (reserveB - amountBOut) = k
              //
              // amountBOut = reserveB - k / (reserveA + amountAIn)
              // {TOKEN_B} = {TOKEN_B} - {TOKEN_A * TOKEN_B} / {TOKEN_A}
              // {TOKEN_B} = {TOKEN_B} - {TOKEN_B} = {TOKEN_B} ✓
          }
      
          /// @notice Get current price of A in terms of B
          /// @return price D18{TOKEN_B / TOKEN_A} Price
          function getPrice() external view returns (uint256 price) {
              // {TOKEN_B / TOKEN_A} = {TOKEN_B} * 1e18 / {TOKEN_A}
              price = reserveB.mulDiv(1e18, reserveA);
          }
      
          /// @notice Add liquidity
          /// @param amountA D{X}{TOKEN_A} Amount of token A
          /// @param amountB D{Y}{TOKEN_B} Amount of token B
          /// @return lpTokens D18{LP} LP tokens minted
          function addLiquidity(uint256 amountA, uint256 amountB)
              external returns (uint256 lpTokens)
          {
              // LP tokens proportional to liquidity added
              // {LP} = {LP} * {TOKEN_A} / {TOKEN_A} = {LP} ✓
              lpTokens = totalSupply.mulDiv(amountA, reserveA);
          }
      }
      ```
      
      ### Lending Protocol
      
      ```solidity
      /**
       * ═══════════════════════════════════════════════════════════
       * DIMENSIONAL GLOSSARY
       * ═══════════════════════════════════════════════════════════
       *
       * Base Dimensions:
       * - {COLLATERAL}  Collateral token (e.g., WETH)
       * - {DEBT}        Borrowed token (e.g., USDC)
       * - {USD}         Oracle price denomination
       * - {aToken}      Receipt token for deposits
       * - {s}           Time in seconds
       * - {1}           Dimensionless
       *
       * Derived Dimensions:
       * - {USD/COLLATERAL}  Collateral price
       * - {USD/DEBT}        Debt token price
       * - {1/s}             Interest rate per second
       */
      
      contract LendingPool {
          uint256 public totalBorrowed;      // D{X}{DEBT}
          uint256 public totalCollateral;    // D{Y}{COLLATERAL}
          uint256 public liquidationRatio;   // D18{1} (e.g., 1.5e18 = 150%)
          uint256 public borrowRate;         // D27{1/s} Interest rate per second
          uint256 public lastAccrualTime;    // {s} Last interest accrual timestamp
      
          struct Position {
              uint256 collateral;    // D{Y}{COLLATERAL} Collateral deposited
              uint256 debt;          // D{X}{DEBT} Amount borrowed
              uint256 lastUpdate;    // {s} Timestamp of last update
          }
          mapping(address => Position) public positions;
      
          /// @notice Calculate health factor for a position
          /// @param collateral D{Y}{COLLATERAL} Collateral amount
          /// @param collateralPrice D8{USD/COLLATERAL} Oracle price
          /// @param debt D{X}{DEBT} Debt amount
          /// @param debtPrice D8{USD/DEBT} Oracle price
          /// @return healthFactor D18{1} Health factor (>1e18 is healthy)
          function getHealthFactor(
              uint256 collateral,
              uint256 collateralPrice,
              uint256 debt,
              uint256 debtPrice
          ) external pure returns (uint256 healthFactor) {
              // collateralValue = collateral * collateralPrice / 1e8
              // D18{USD} = D{Y}{COLLATERAL} * D8{USD/COLLATERAL} / 1e8
              uint256 collateralValue = collateral.mulDiv(collateralPrice, 1e8);
      
              // debtValue = debt * debtPrice / 1e8
              // D18{USD} = D{X}{DEBT} * D8{USD/DEBT} / 1e8
              uint256 debtValue = debt.mulDiv(debtPrice, 1e8);
      
              // healthFactor = collateralValue * 1e18 / debtValue
              // D18{1} = D18{USD} * 1e18 / D18{USD}
              healthFactor = collateralValue.mulDiv(1e18, debtValue);
          }
      
          /// @notice Borrow tokens against collateral
          /// @param amount D{X}{DEBT} Amount to borrow
          function borrow(uint256 amount) external {
              Position storage pos = positions[msg.sender];
              pos.debt += amount;  // {DEBT} + {DEBT} = {DEBT} ✓
              totalBorrowed += amount;  // {DEBT} + {DEBT} = {DEBT} ✓
          }
      
          /// @notice Deposit collateral
          /// @param amount D{Y}{COLLATERAL} Amount to deposit
          function depositCollateral(uint256 amount) external {
              Position storage pos = positions[msg.sender];
              pos.collateral += amount;  // {COLLATERAL} + {COLLATERAL} = {COLLATERAL} ✓
              totalCollateral += amount;  // {COLLATERAL} + {COLLATERAL} = {COLLATERAL} ✓
          }
      }
      ```
      
      ---
      
      ## Step 2 Checklist
      
      - [ ] Annotate all numeric state variables with `// D{scale}{dimension}`
      - [ ] Annotate all function parameters with NatSpec dimensions
      - [ ] Annotate all return values with dimensions
      - [ ] Annotate all struct fields with dimensions
      - [ ] Add dimensional algebra comments for non-trivial formulas
      - [ ] Ensure price dimensions clearly indicate numerator/denominator
      - [ ] Ensure different token types have distinct dimensions
      
    • bug-patterns.md 11.7 KB
      # Dimensional Bug Patterns
      
      This document catalogs common dimensional bugs with examples and detection strategies. Examples use Solidity syntax, but these bug patterns occur in any language performing arithmetic with mixed units and scaling factors (Rust, TypeScript, Python, etc.).
      
      ## Critical Bugs (P0)
      
      ### Pattern 1: Unit Mismatch in Price Feeds
      
      **Description:** Oracle returns price in different precision than expected.
      
      **Example:**
      ```solidity
      // Contract assumes D27 prices
      uint256 price; // D27{UoA/tok}
      
      // But Chainlink returns D8!
      (, int256 answer,,,) = priceFeed.latestRoundData();
      price = uint256(answer); // BUG: D8 assigned to D27 variable
      
      // Correct:
      price = uint256(answer) * 1e19; // Scale D8 to D27
      ```
      
      **Impact:** Price values off by 10^19, causing catastrophic mispricing.
      
      **Detection:** Check oracle `decimals()` vs expected precision.
      
      ---
      
      ### Pattern 2: Cross-Contract Dimension Assumption Mismatch
      
      **Description:** Caller assumes different dimension than callee returns.
      
      **Example:**
      ```solidity
      // Protocol A's Vault
      function getSharePrice() external returns (uint256) {
          return totalAssets * 1e18 / totalShares; // Returns D18{tok/share}
      }
      
      // Protocol B consuming it
      uint256 sharePrice; // D27{tok/share} - WRONG ASSUMPTION
      sharePrice = vaultA.getSharePrice(); // BUG: D18 value in D27 variable
      
      // Correct:
      sharePrice = vaultA.getSharePrice() * 1e9;
      ```
      
      **Impact:** 9 orders of magnitude error in calculations.
      
      **Detection:** Compare interface documentation and actual return dimensions.
      
      ---
      
      ### Pattern 3: Adding Incompatible Dimensions
      
      **Description:** Adding values with different semantic meanings.
      
      **Example:**
      ```solidity
      // User's position
      uint256 tokenBalance;  // {tok}
      uint256 shareBalance;  // {share}
      
      // BUG: Can't add tokens and shares!
      uint256 totalPosition = tokenBalance + shareBalance;
      
      // Correct: Convert to common dimension
      uint256 totalTokens = tokenBalance + convertToAssets(shareBalance);
      ```
      
      **Impact:** Result is mathematically meaningless.
      
      **Detection:** Verify both operands have identical dimensions.
      
      ---
      
      ### Pattern 4: Wrong Precision Causing Overflow
      
      **Description:** Multiplication without scaling causes overflow or precision explosion.
      
      **Example:**
      ```solidity
      uint256 amount; // D18{tok}
      uint256 price;  // D27{UoA/tok}
      
      // BUG: D18 * D27 = D45, overflows!
      uint256 value = amount * price;
      
      // Correct:
      uint256 value = Math.mulDiv(amount, price, 1e27); // Result: D18{UoA}
      ```
      
      **Impact:** Overflow reverts or silent wraparound.
      
      **Detection:** Track precision through multiplication chains.
      
      ---
      
      ## High Severity Bugs (P1)
      
      ### Pattern 5: Missing Scaling Factor
      
      **Description:** Calculation omits necessary precision adjustment.
      
      **Example:**
      ```solidity
      // Calculate shares from deposit
      uint256 assets;     // D18{tok}
      uint256 supply;     // D18{share}
      uint256 totalAssets; // D18{tok}
      
      // BUG: Missing D18 scaling
      uint256 shares = assets * supply / totalAssets;
      // Actually: D18 * D18 / D18 = D18, but intermediate is D36!
      
      // Correct:
      uint256 shares = Math.mulDiv(assets, supply, totalAssets);
      ```
      
      **Impact:** Precision loss or overflow in intermediate calculation.
      
      **Detection:** Verify all multiplications are properly scaled.
      
      ---
      
      ### Pattern 6: Wrong Scaling Direction
      
      **Description:** Multiply when should divide, or vice versa.
      
      **Example:**
      ```solidity
      uint256 priceD27; // D27{UoA/tok}
      
      // BUG: Multiplied instead of divided
      uint256 priceD18 = priceD27 * 1e9; // Now D36!
      
      // Correct:
      uint256 priceD18 = priceD27 / 1e9;
      ```
      
      **Impact:** Value off by 10^18.
      
      **Detection:** Verify scaling direction matches precision conversion intent.
      
      ---
      
      ### Pattern 7: Inconsistent Return Path Dimensions
      
      **Description:** Different code paths return values with different dimensions.
      
      **Example:**
      ```solidity
      function getValue(bool useOracle) returns (uint256) {
          if (useOracle) {
              return oracle.getPrice(token); // D8{UoA/tok}
          } else {
              return cachedPrice; // D18{UoA/tok} - DIFFERENT DIMENSION!
          }
      }
      ```
      
      **Impact:** Callers receive inconsistent values.
      
      **Detection:** Trace all return paths and verify dimensions match.
      
      ---
      
      ### Pattern 8: Implicit Precision Truncation
      
      **Description:** High precision value assigned to lower precision variable.
      
      **Example:**
      ```solidity
      uint256 preciseValue; // D27{UoA}
      uint256 result;       // D18{UoA}
      
      // BUG: Truncates 9 decimal places
      result = preciseValue / 1e9;
      
      // If preciseValue = 1.5e27 (1.5 in D27)
      // result = 1.5e18 (1.5 in D18) - OK
      // But if preciseValue = 1e18 (tiny in D27)
      // result = 1e9 (still tiny in D18, lost precision)
      ```
      
      **Impact:** Small values may become zero.
      
      **Detection:** Identify precision reductions and check for rounding issues.
      
      ---
      
      ## Medium Severity Bugs (P2)
      
      ### Pattern 9: Redundant Scaling
      
      **Description:** Unnecessary conversion that wastes gas or introduces rounding.
      
      **Example:**
      ```solidity
      uint256 priceD18; // D18{UoA/tok}
      
      // Redundant: scale up then down
      uint256 temp = priceD18 * 1e9;  // D27
      uint256 result = temp / 1e9;    // Back to D18
      
      // Could just use priceD18 directly
      ```
      
      **Impact:** Gas waste, possible rounding errors.
      
      **Detection:** Identify inverse scaling operations.
      
      ---
      
      ### Pattern 10: Fee Applied to Wrong Dimension
      
      **Description:** Fee percentage applied to value instead of amount, or vice versa.
      
      **Example:**
      ```solidity
      uint256 depositAmount; // {tok}
      uint256 feePercent;    // D18{1}
      uint256 pricePerToken; // D27{UoA/tok}
      
      // BUG: Fee on USD value, not token amount
      uint256 fee = depositAmount * pricePerToken * feePercent / 1e45;
      
      // Correct: Fee on token amount
      uint256 fee = depositAmount * feePercent / 1e18; // {tok}
      ```
      
      **Impact:** Fee calculation incorrect, may over/under charge.
      
      **Detection:** Verify fee is applied to intended base.
      
      ---
      
      ### Pattern 11: Time Unit Confusion
      
      **Description:** Mixing seconds with other time units.
      
      **Example:**
      ```solidity
      uint256 ratePerYear;  // D18{1} annual rate
      uint256 elapsed;      // {s} seconds
      
      // BUG: Applying annual rate to seconds
      uint256 accrued = principal * ratePerYear * elapsed / 1e18;
      
      // Correct: Convert to per-second rate
      uint256 SECONDS_PER_YEAR = 365.25 days;
      uint256 ratePerSecond = ratePerYear / SECONDS_PER_YEAR;
      uint256 accrued = principal * ratePerSecond * elapsed / 1e18;
      ```
      
      **Impact:** Interest/fees off by ~31.5 million.
      
      **Detection:** Verify time unit consistency in rate calculations.
      
      ---
      
      ### Pattern 12: Division Before Multiplication
      
      **Description:** Dividing first causes precision loss.
      
      **Example:**
      ```solidity
      uint256 a = 100;
      uint256 b = 3;
      uint256 c = 7;
      
      // BUG: Division truncates
      uint256 result = a / b * c; // = 33 * 7 = 231
      
      // Correct:
      uint256 result = a * c / b; // = 700 / 3 = 233
      ```
      
      **Impact:** Silent precision loss.
      
      **Detection:** Reorder to multiply before divide, or use mulDiv.
      
      ---
      
      ## Common Traps
      
      These traps catch even experienced auditors. Always verify explicitly.
      
      ### Trap 1: Assumed Dimensionless Constant
      
      ```solidity
      // Is this correct?
      uint256 result = amount * MULTIPLIER / DIVISOR;
      
      // Question: Are MULTIPLIER and DIVISOR truly dimensionless?
      // If MULTIPLIER is actually a price {USD/TOKEN}, this formula is WRONG
      // If DIVISOR is actually an amount {TOKEN}, this formula is WRONG
      
      // Always verify what constants represent
      ```
      
      ### Trap 2: Hidden Dimension in Helper Function
      
      ```solidity
      // Is this correct?
      uint256 normalized = normalize(amount);
      
      // Question: What dimension does normalize() return?
      // - Does it change {TOKEN_A} to {TOKEN_B}?
      // - Does it convert to {USD}?
      // - Does it change scale but not dimension?
      
      // MUST trace into the helper function
      ```
      
      ### Trap 3: Chained Operations Hiding Dimension Changes
      
      ```solidity
      // Is this correct?
      uint256 final = step1(step2(step3(input)));
      
      // MUST trace dimensions through each step
      // Don't assume the chain is correct because each function "looks right"
      
      // Trace:
      //   input           : {A}
      //   step3(input)    : {?} - determine from step3's implementation
      //   step2(step3...) : {?} - determine from step2's implementation
      //   step1(step2...) : {?} - determine from step1's implementation
      //   final           : expected {?}
      ```
      
      ### Trap 4: Scale Confused with Dimension
      
      ```solidity
      // Common mistake:
      uint256 price = oracle.getPrice();     // D8{USD/TOKEN}
      uint256 value = amount * price / 1e18; // WRONG! Should be 1e8
      
      // Scale is PART of dimensional correctness
      // D18{TOKEN} * D8{USD/TOKEN} / 1e18 = D8{USD}  ← wrong scale
      // D18{TOKEN} * D8{USD/TOKEN} / 1e8  = D18{USD} ← correct scale
      ```
      
      ### Trap 5: Decimals vs Amounts (Common Real Bug)
      
      ```solidity
      // Extremely common bug pattern:
      uint256 decimals = IERC20(token).decimals();  // Returns 18, dimension is {1}
      uint256 amount = vault.convertToAssets(decimals);  // WRONG! Expects {SHARE}
      
      // decimals is a COUNT, not a token amount
      // It has dimension {1}, not {TOKEN} or {SHARE}
      ```
      
      **Real-world example:**
      ```solidity
      // BUGGY CODE (simplified from real audit)
      function price(address _asset) external view returns (uint256 latestAnswer) {
          address underlying = IERC4626(_asset).asset();
          (latestAnswer, ) = IOracle(msg.sender).getPrice(underlying);
          uint256 tokenDecimals = IERC20Metadata(underlying).decimals();
          uint256 pricePerFullShare = IERC4626(_asset).convertToAssets(tokenDecimals);
          latestAnswer = latestAnswer * pricePerFullShare / tokenDecimals;
      }
      ```
      
      **Dimensional analysis catches this:**
      ```
      Step 1: What does convertToAssets expect?
        According to ERC-4626: input is {SHARE}, output is {ASSET}
      
      Step 2: What is tokenDecimals?
        It's the NUMBER of decimals (e.g., 18), dimension is {1}
        NOT a token amount!
      
      Step 3: Trace the call
        convertToAssets(tokenDecimals)
        convertToAssets({1})  // WRONG! Expects {SHARE}
      
        This passes a dimensionless number where a share amount is expected!
      
      BUG: decimals (a count, {1}) was passed to a function expecting assets ({SHARE})
      ```
      
      ### Trap 6: Same Name, Different Tokens
      
      ```solidity
      // Is this correct?
      uint256 total = totalSupplyA + totalSupplyB;
      
      // If A and B are different tokens:
      //   {TOKEN_A} + {TOKEN_B} = ??? (INVALID)
      
      // Same-named variables for different tokens can't be added
      ```
      
      ---
      
      ## Detection Strategies
      
      ### Static Analysis
      
      1. **Parse annotations** - Extract all dimensional comments
      2. **Build dimension graph** - Map variable → dimension
      3. **Trace arithmetic** - Apply algebra rules
      4. **Check assignments** - LHS must match RHS dimension
      5. **Check function boundaries** - Args match params, returns match declarations
      
      ### Code Patterns to Flag
      
      ```solidity
      // Flag: Multiplication without mulDiv
      a * b                           // Needs dimension check
      
      // Flag: Direct oracle assignment
      price = oracle.latestAnswer()   // Check precision match
      
      // Flag: Addition of different variables
      total = valueA + valueB         // Verify same dimension
      
      // Flag: Return in conditional
      if (x) return a; else return b; // Verify a and b same dimension
      
      // Flag: Scaling literals
      value * 1e9                     // Verify direction correct
      value / 1e18                    // Verify scaling appropriate
      ```
      
      ### Human Review Triggers
      
      - Cross-contract calls (external assumptions)
      - Complex multi-step calculations
      - Non-standard token decimals (not 18)
      - Custom oracle implementations
      - Protocol-specific units
      
      ## False Positive Avoidance
      
      ### Acceptable Patterns
      
      ```solidity
      // Intentional dimensionless arithmetic
      uint256 doubled = amount * 2;       // {tok} * {1} = {tok}
      
      // Loop bounds
      for (uint256 i = 0; i < length; i++)  // {1}
      
      // Explicit documented conversion
      // Intentionally converting D27 to D18 with precision loss
      uint256 approxPrice = precisePrice / 1e9;
      
      // Test contracts
      contract MockOracle { ... }         // Ignore test code
      ```
      
      ### Context Clues
      
      - Check for comments explaining intent
      - Check for test file paths
      - Check for "mock" or "test" in names
      - Check for explicit precision documentation
      
    • common-dimensions.md 7.3 KB
      # Common Dimensions in DeFi
      
      This document catalogs standard dimensional units used across DeFi protocols. While examples use Solidity syntax, the dimensional vocabulary is protocol-agnostic and applies equally to Rust (Anchor, CosmWasm), TypeScript, Python, or any other language implementing DeFi logic.
      
      ## Universal Base Units
      
      ### Token Amounts: `{tok}`
      
      Represents a quantity of tokens.
      
      ```solidity
      uint256 public totalSupply;      // {tok}
      uint256 public balanceOf;        // {tok}
      uint256 amount;                  // {tok}
      ```
      
      **Typical precision:** D6 (USDC, USDT), D8 (WBTC), D18 (most ERC20)
      
      ### Share Amounts: `{share}`
      
      Represents vault/pool shares.
      
      ```solidity
      uint256 public totalShares;      // {share}
      uint256 userShares;              // {share}
      ```
      
      **Typical precision:** D18
      
      ### Time: `{s}`
      
      Timestamps and durations in seconds.
      
      ```solidity
      uint256 public lastUpdate;       // {s}
      uint256 elapsed;                 // {s}
      uint256 duration;                // {s}
      block.timestamp                  // {s}
      ```
      
      **Precision:** Integer (no decimals)
      
      ### Dimensionless: `{1}`
      
      Pure ratios, percentages, multipliers.
      
      ```solidity
      uint256 public feeRate;          // D18{1}
      uint256 percent;                 // D18{1} or D4{1} for basis points
      uint256 multiplier;              // D18{1}
      ```
      
      **Typical precision:** D18 or D4 (basis points)
      
      ### Unit of Account: `{UoA}`
      
      Abstract value unit, typically USD-equivalent.
      
      ```solidity
      uint256 public totalValue;       // {UoA}
      uint256 price;                   // {UoA} (absolute)
      ```
      
      **Note:** Often implicit in price dimensions like `{UoA/tok}`
      
      ## Standard Derived Units
      
      ### Exchange Rate: `{tok/share}` or `{share/tok}`
      
      Conversion ratio between tokens and shares.
      
      ```solidity
      uint256 public exchangeRate;     // D18{tok/share}
      uint256 sharePrice;              // D18{tok/share}
      ```
      
      ### Price: `{UoA/tok}`
      
      Value per token in unit of account.
      
      ```solidity
      uint256 public tokenPrice;       // D8{UoA/tok} (Chainlink)
      uint256 oraclePrice;             // D18{UoA/tok} or D27{UoA/tok}
      ```
      
      ### Cross Price: `{tokA/tokB}`
      
      Exchange rate between two tokens.
      
      ```solidity
      uint256 public swapRate;         // D18{tokA/tokB}
      ```
      
      ### Rate Per Second: `{1/s}`
      
      Time-based rate (fees, interest).
      
      ```solidity
      uint256 public interestRate;     // D18{1/s}
      uint256 feePerSecond;            // D27{1/s}
      ```
      
      ### Value Per Share: `{UoA/share}`
      
      Share value in unit of account.
      
      ```solidity
      uint256 public nav;              // D18{UoA/share}
      uint256 shareValue;              // D27{UoA/share}
      ```
      
      ## Protocol-Specific Units
      
      ### Reserve Protocol
      
      | Unit | Description | Example |
      |------|-------------|---------|
      | `{BU}` | Basket Unit | Target basket composition |
      | `{tok/BU}` | Tokens per basket | Weight in basket |
      | `{UoA/BU}` | Basket value | Basket price |
      | `{BU/share}` | Baskets per share | RToken backing |
      | `{RToken}` | RToken amount | Alias for `{share}` |
      | `{RSR}` | RSR token amount | Staking token |
      
      ```solidity
      uint256 public basketsNeeded;    // {BU}
      uint256 weight;                  // D27{tok/BU}
      uint256 price;                   // D27{UoA/tok}
      ```
      
      ### Lending Protocols (Aave, Compound)
      
      | Unit | Description | Example |
      |------|-------------|---------|
      | `{debt}` | Debt token amount | Borrowed amount |
      | `{collateral}` | Collateral amount | Deposited collateral |
      | `{aToken}` | Aave interest-bearing token | Deposit receipt |
      | `{cToken}` | Compound interest-bearing token | Deposit receipt |
      | `{1}` | Health factor, LTV | Risk metrics |
      
      ```solidity
      uint256 public totalDebt;        // {debt}
      uint256 healthFactor;            // D18{1}
      uint256 ltv;                     // D4{1} (basis points)
      ```
      
      ### AMM Protocols (Uniswap, Curve)
      
      | Unit | Description | Example |
      |------|-------------|---------|
      | `{liq}` | Liquidity units | Pool liquidity |
      | `{LP}` | LP token amount | Liquidity provider shares |
      | `{sqrtP}` | Square root price | Uniswap V3 |
      
      ```solidity
      uint256 public liquidity;        // {liq}
      uint256 lpBalance;               // {LP}
      uint160 sqrtPriceX96;            // Q96{sqrtP}
      ```
      
      ### Staking Protocols
      
      | Unit | Description | Example |
      |------|-------------|---------|
      | `{staked}` | Staked token amount | Deposited stake |
      | `{reward}` | Reward token amount | Earned rewards |
      | `{reward/staked}` | Reward rate | Per-token rewards |
      
      ```solidity
      uint256 public totalStaked;      // {staked}
      uint256 rewardPerToken;          // D18{reward/staked}
      ```
      
      ## Standard Precision Levels
      
      | Prefix | Value | Common Usage |
      |--------|-------|--------------|
      | D4 | 1e4 | Basis points |
      | D6 | 1e6 | USDC, USDT decimals |
      | D8 | 1e8 | WBTC, Chainlink prices |
      | D18 | 1e18 | Standard ERC20, most calculations |
      | D27 | 1e27 | High-precision prices (Reserve) |
      | Q96 | 2^96 | Uniswap V3 fixed-point |
      
      ## Interface Dimensions
      
      ### ERC20
      
      ```solidity
      function totalSupply() external view returns (uint256);        // {tok}
      function balanceOf(address) external view returns (uint256);   // {tok}
      function decimals() external view returns (uint8);             // precision info
      function transfer(address, uint256 amount) external;           // amount: {tok}
      function approve(address, uint256 amount) external;            // amount: {tok}
      function transferFrom(address, address, uint256 amount);       // amount: {tok}
      function allowance(address, address) returns (uint256);        // {tok}
      ```
      
      ### ERC4626
      
      ```solidity
      function asset() external view returns (address);              // underlying token
      function totalAssets() external view returns (uint256);        // {tok}
      function convertToShares(uint256 assets) returns (uint256);    // {tok} → {share}
      function convertToAssets(uint256 shares) returns (uint256);    // {share} → {tok}
      function maxDeposit(address) external view returns (uint256);  // {tok}
      function maxMint(address) external view returns (uint256);     // {share}
      function maxWithdraw(address) external view returns (uint256); // {tok}
      function maxRedeem(address) external view returns (uint256);   // {share}
      function previewDeposit(uint256 assets) returns (uint256);     // {tok} → {share}
      function previewMint(uint256 shares) returns (uint256);        // {share} → {tok}
      function previewWithdraw(uint256 assets) returns (uint256);    // {tok} → {share}
      function previewRedeem(uint256 shares) returns (uint256);      // {share} → {tok}
      function deposit(uint256 assets, address) returns (uint256);   // {tok} → {share}
      function mint(uint256 shares, address) returns (uint256);      // {share} → {tok}
      function withdraw(uint256 assets, ...) returns (uint256);      // {tok} → {share}
      function redeem(uint256 shares, ...) returns (uint256);        // {share} → {tok}
      ```
      
      ### Chainlink
      
      ```solidity
      function decimals() external view returns (uint8);             // usually 8
      function latestRoundData() external view returns (
          uint80 roundId,
          int256 answer,      // D8{UoA/tok} typically
          uint256 startedAt,  // {s}
          uint256 updatedAt,  // {s}
          uint80 answeredInRound
      );
      ```
      
      ## Naming Convention Hints
      
      | Pattern | Likely Dimension |
      |---------|-----------------|
      | `*Balance`, `*Amount` | `{tok}` |
      | `*Shares`, `share*` | `{share}` |
      | `*Price`, `price*` | `{UoA/tok}` |
      | `*Rate`, `rate*` | `{1}` or `{1/s}` |
      | `*Time`, `*Timestamp` | `{s}` |
      | `*Duration`, `*Period` | `{s}` |
      | `*Fee`, `fee*` | `{1}` |
      | `*Ratio`, `ratio*` | `{1}` |
      | `*Value`, `value*` | `{UoA}` |
      | `*Per*` | derived unit |
      | `total*` | aggregate amount |
      | `max*`, `min*` | bounds (same as base) |
      
    • dimension-algebra.md 6.3 KB
      # Dimensional Algebra Rules
      
      This document defines the rules for dimensional arithmetic. While examples use Solidity syntax, these algebraic rules are universal and apply to any language performing fixed-point or scaled arithmetic.
      
      ## Notation
      
      - `{A}` - A semantic unit (e.g., `{tok}`, `{share}`, `{UoA}`)
      - `D18` - A precision prefix indicating 18 decimal places
      - `D18{A}` - A value with unit `{A}` and precision D18
      - `{A/B}` - A derived unit (A per B)
      - `{A*B}` - A compound unit (A times B)
      - `{1}` - Dimensionless (pure ratio)
      
      ### Formal Grammar
      
      ```
      annotation     := scale? "{" dimension "}"
      scale          := "D" number
      dimension      := base_dim | derived_dim | "1"
      derived_dim    := dimension "/" dimension | dimension "*" dimension
      base_dim       := identifier
      ```
      
      **Examples:**
      - `{tok}` - Token amount (no scale specified)
      - `D18{tok}` - Token amount, 18 decimals fixed-point
      - `D27{USD/tok}` - Price, 27 decimals fixed-point
      - `{1}` - Dimensionless (pure number or ratio)
      
      ## Basic Composition Rules
      
      ### Multiplication
      
      Dimensions multiply when values are multiplied:
      
      ```
      {A} * {B} = {A*B}
      
      Examples:
      {tok} * {UoA/tok} = {UoA}           # tokens × price = value
      {share} * {tok/share} = {tok}       # shares × exchange rate = tokens
      {1} * {A} = {A}                     # dimensionless preserves dimension
      ```
      
      ### Division
      
      Dimensions divide when values are divided:
      
      ```
      {A} / {B} = {A/B}
      
      Examples:
      {tok} / {share} = {tok/share}       # exchange rate
      {UoA} / {tok} = {UoA/tok}           # price
      {A} / {A} = {1}                     # same dimensions cancel
      {A} / {1} = {A}                     # dividing by dimensionless preserves
      ```
      
      ### Addition and Subtraction
      
      **CRITICAL: Addition and subtraction require identical dimensions.**
      
      ```
      {A} + {A} = {A}                     # Valid
      {A} - {A} = {A}                     # Valid
      {A} + {B} = ERROR                   # Invalid! Dimension mismatch
      {A} - {B} = ERROR                   # Invalid! Dimension mismatch
      
      Examples:
      {tok} + {tok} = {tok}               # Valid: adding token amounts
      {tok} + {share} = ERROR             # Invalid: can't add tokens and shares
      {UoA/tok} + {UoA/tok} = {UoA/tok}   # Valid: adding prices
      ```
      
      ## Precision Arithmetic
      
      ### Multiplication Precision
      
      Precisions ADD when multiplying:
      
      ```
      D18 * D18 = D36
      D27 * D18 = D45
      D18 * D27 = D45
      
      Example:
      D18{tok} * D18{share/tok} = D36{share}  # Need to scale down by D18
      ```
      
      ### Division Precision
      
      Precisions SUBTRACT when dividing:
      
      ```
      D36 / D18 = D18
      D27 / D18 = D9
      D18 / D18 = D0 (integer)
      
      Example:
      D36{share} / D18 = D18{share}           # Scaling down
      D27{UoA/tok} / D18{1} = D9{UoA/tok}     # Precision reduced
      ```
      
      ### Scaling Operations
      
      Scaling is multiplication/division by a pure precision constant:
      
      ```
      D18{A} * D9 = D27{A}                    # Scale up precision
      D27{A} / D9 = D18{A}                    # Scale down precision
      D36{A} / D18 = D18{A}                   # Common pattern after multiplication
      ```
      
      ## Common Patterns
      
      ### Price Calculation
      
      ```solidity
      // Calculate value from amount and price
      // {UoA} = {tok} * D27{UoA/tok} / D27
      // D27{UoA} = D18{tok} * D27{UoA/tok} / D18
      uint256 value = Math.mulDiv(amount, price, D18);
      ```
      
      ### Share Conversion (ERC4626)
      
      ```solidity
      // Convert assets to shares
      // {share} = {tok} * {share} / {tok}
      // D18{share} = D18{tok} * D18{share} / D18{tok}
      uint256 shares = Math.mulDiv(assets, totalSupply, totalAssets);
      
      // Convert shares to assets
      // {tok} = {share} * {tok} / {share}
      uint256 assets = Math.mulDiv(shares, totalAssets, totalSupply);
      ```
      
      ### Fee Application
      
      ```solidity
      // Apply percentage fee
      // {tok} = {tok} * D18{1} / D18
      uint256 fee = Math.mulDiv(amount, feeRate, D18);
      uint256 netAmount = amount - fee;
      ```
      
      ### Rate Per Second
      
      ```solidity
      // Calculate accrued amount
      // {tok} = {tok} * D18{1/s} * {s} / D18
      uint256 accrued = Math.mulDiv(principal, rate * elapsed, D18);
      ```
      
      ### Cross-Rate Calculation
      
      ```solidity
      // Calculate token A price in terms of token B
      // D27{B/A} = D27{UoA/A} * D27 / D27{UoA/B}
      uint256 crossRate = Math.mulDiv(priceA, D27, priceB);
      ```
      
      ## Dimensional Simplification
      
      ### Cancellation
      
      When the same unit appears in numerator and denominator, it cancels:
      
      ```
      {tok/share} * {share} = {tok}           # share cancels
      {UoA/tok} * {tok/BU} = {UoA/BU}         # tok cancels
      {A/B} * {B/C} = {A/C}                   # B cancels
      ```
      
      ### Identity
      
      ```
      {A} * {1} = {A}
      {A} / {1} = {A}
      {A} * {B/B} = {A}                       # Multiplying by 1
      ```
      
      ## Multi-Step Calculations
      
      For complex expressions, track dimensions step by step:
      
      ```solidity
      // Calculate share value in UoA
      // Step 1: {tok/share} = {tok} / {share}
      // Step 2: {UoA/share} = {tok/share} * {UoA/tok}
      //
      // In code:
      // D18{UoA/share} = D18{tok} * D27{UoA/tok} / D18{share} / D9
      uint256 shareValue = Math.mulDiv(
          Math.mulDiv(totalAssets, price, totalShares),
          1,
          1e9  // Scale D27 to D18
      );
      ```
      
      ## Error Patterns
      
      ### Division Before Multiplication
      
      **Risky for precision loss:**
      ```solidity
      // BAD: May lose precision
      uint256 result = a / b * c;
      
      // BETTER: Use mulDiv
      uint256 result = Math.mulDiv(a, c, b);
      ```
      
      ### Missing Scaling
      
      **Common bug pattern:**
      ```solidity
      // BUG: D36 result stored in D18 variable
      uint256 result = amount * rate;  // D18 * D18 = D36!
      
      // CORRECT:
      uint256 result = Math.mulDiv(amount, rate, D18);
      ```
      
      ### Wrong Scaling Direction
      
      ```solidity
      // BUG: Multiplied when should divide
      uint256 price18 = price27 * 1e9;  // Now D36!
      
      // CORRECT:
      uint256 price18 = price27 / 1e9;
      ```
      
      ## Special Cases
      
      ### Dimensionless Constants
      
      Integer constants like `2`, `100`, `MAX_UINT` are dimensionless `{1}`:
      
      ```solidity
      uint256 doubled = amount * 2;     // {tok} * {1} = {tok}
      uint256 half = amount / 2;        // {tok} / {1} = {tok}
      ```
      
      ### Timestamps
      
      Timestamps and durations have dimension `{s}` (seconds):
      
      ```solidity
      uint256 elapsed = block.timestamp - lastUpdate;  // {s} - {s} = {s}
      uint256 rate = feePerSecond * elapsed;           // {1/s} * {s} = {1}
      ```
      
      ### Basis Points
      
      Basis points are `{1}` with implicit D4 precision:
      
      ```solidity
      uint256 constant BPS = 10000;     // D4
      uint256 fee = amount * feeBps / BPS;  // {tok} * {1} / {1} = {tok}
      ```
      
      ## Validation Checklist
      
      For any arithmetic operation:
      
      1. ✓ Do operand dimensions combine correctly?
      2. ✓ Is the result dimension what's expected?
      3. ✓ Is precision handled correctly (scaling)?
      4. ✓ Is rounding direction appropriate?
      5. ✓ Could intermediate values overflow?
      
  • SKILL.md 17.4 KB
    ---
    name: dimensional-analysis
    description: "Annotates codebases with dimensional analysis comments documenting units, dimensions, and decimal scaling. Use when someone asks to annotate units in a codebase, perform a dimensional analysis, or find vulnerabilities in a DeFi protocol, offchain code, or other blockchain-related codebase with arithmetic. Prevents dimensional mismatches and catches formula bugs early."
    allowed-tools: Read Write Grep List Glob Task TodoRead TodoWrite
    ---
    
    # Dimensional Analysis Skill
    
    This skill orchestrates a dimensional-analysis pipeline for codebases that perform numeric computations with mixed units, precisions, or scaling factors. The main skill context is a workflow controller only: it delegates scanning, vocabulary discovery, annotation, propagation, and validation to specialized subagents, then manages batching, persistence, retries, coverage gates, and final reporting.
    
    ## When to Use
    
    - Annotating a codebase with unit/dimension comments (e.g., `D18{tok}`, `D27{UoA/tok}`)
    - Performing dimensional analysis on DeFi protocols, financial code, or scientific computations
    - Hunting for arithmetic bugs caused by unit mismatches, missing scaling, or precision loss
    - Auditing codebases with mixed decimal precisions or fixed-point arithmetic
    
    ## When NOT to Use
    
    - Codebases with no numeric arithmetic or unit conversions — there is nothing to annotate
    - Pure integer counting logic (loop indices, array lengths) with no physical or financial dimensions
    - When you only need a quick spot-check of a single formula — read the code directly instead of running the full pipeline
    
    ## Execution Mode
    
    This skill runs in one mode only: `full-auto`.
    This is a workflow-based skill that delegates step-specific work to specialized agents via the `Task` tool. You orchestrate the overall process, manage coverage and state persistence, and ensure that every in-scope file is processed through each step of the pipeline.
    
    - Always run the full pipeline in this order: Step 1 -> Step 2 -> Step 3 -> Step 4.
    - The main skill context must not perform repository-wide dimensional analysis, annotation, propagation, or bug validation itself when a dedicated subagent exists for that step.
    - The main skill context may inspect artifacts, manifests, and subagent outputs only as needed to route work, build prompts, persist state, and determine completion.
    - Any mode argument provided by the caller is ignored.
    - Report all results at the end in a single summary.
    
    When you start a step, report it:
    
    ```text
    Starting Step: Step {n}
    ```
    
    ## Scope and Coverage Guarantees
    
    This skill must audit **all in-scope arithmetic files**, including large repositories.
    
    - In-scope files are defined by Step 1 scanner output (`files` array), across **all** priority tiers (CRITICAL, HIGH, MEDIUM, LOW).
    - If Step 1 narrows inputs for vocabulary discovery (for example, CRITICAL/HIGH only), that narrowing applies to discovery only. It **never** reduces annotation or validation scope.
    - `arithmetic-scanner` persists the in-scope file manifest to `DIMENSIONAL_SCOPE.json` in the project root, and that manifest is the source of truth for Steps 2-4.
    - A file is considered fully covered only when all three statuses are present:
      - `step2`: anchor annotation completed (or explicit no-anchor result)
      - `step3`: propagation completed (or explicit no-propagation result)
      - `step4`: validation completed
    - `dimension-discoverer` persists the discovered dimensional vocabulary to `DIMENSIONAL_UNITS.md` in the project root for reuse by later steps and future runs.
    - When a file ends in a terminal `BLOCKED` state, persist the blocking reason and retry count in `DIMENSIONAL_SCOPE.json` and reflect the same file in `coverage.unprocessed_files`.
    - Do not finish while any in-scope file remains unprocessed in any step.
    
    ## Delegation Contract
    
    - `arithmetic-scanner` owns repository scanning, arithmetic-file prioritization, and writing `DIMENSIONAL_SCOPE.json`.
    - `dimension-discoverer` owns dimensional vocabulary discovery, unit inference, and writing `DIMENSIONAL_UNITS.md`.
    - `dimension-annotator` owns annotation format decisions, anchor-point edits, and comment-writing behavior.
    - `dimension-propagator` owns propagation logic, inferred annotations, and mismatch reporting during tracing.
    - `dimension-validator` owns bug detection, red-flag evaluation, rationalization rejection, and confirmation or refutation of propagated mismatches.
    - The main skill context must not substitute its own dimensional reasoning for skipped or unlaunched subagents. If a step requires specialized reasoning, launch the corresponding subagent.
    - Use reference files as subagent support material. Pass them to the relevant step in prompts instead of treating them as instructions for the main skill context.
    
    ## Workflow
    
    Follow these sections in order. Do not advance until the current step satisfies its completion gate.
    
    ### Shared Orchestration Rules
    
    - `DIMENSIONAL_SCOPE.json` and `DIMENSIONAL_UNITS.md` live in the project root.
    - The main skill context verifies Step 1 artifacts but does not write either Step 1 artifact itself.
    - `DIMENSIONAL_SCOPE.json.in_scope_files` is the source of truth for Steps 2-4. Never derive later scope from discovery-only inputs.
    - When a later step reaches terminal `BLOCKED`, persist the matching `step*_reason` and `step*_retry_count` fields on the file entry in `DIMENSIONAL_SCOPE.json`.
    - `coverage.unprocessed_files` must be derived from terminal `BLOCKED` entries in `DIMENSIONAL_SCOPE.json` using `{ "path": "...", "blocked_step": "step2|step3|step4", "reason": "...", "retry_count": 1 }`.
    - A step may retry a `BLOCKED` file once with a focused prompt. If it is still `BLOCKED`, keep the documented reason and continue. Do not finalize while any file remains `PENDING`.
    
    ### Step 1: Vocabulary and Scope Discovery
    
    If cached artifacts cannot be reused, delegate repository scanning to `arithmetic-scanner` and vocabulary discovery to `dimension-discoverer`. Do not do that step-specific analysis directly in the main skill context.
    
    1. Check whether `DIMENSIONAL_UNITS.md` and `DIMENSIONAL_SCOPE.json` already exist in the project root.
    2. If both exist, read them and confirm:
       - `DIMENSIONAL_SCOPE.json.project_root` matches the current repo root
       - `DIMENSIONAL_SCOPE.json` contains `in_scope_files`, `discoverer_focus_files`, `recommended_discovery_order`, and per-file `step2`, `step3`, `step4` fields
       - `DIMENSIONAL_UNITS.md` is a usable dimensional vocabulary for this repo
    3. If either artifact is stale, malformed, missing required structure, or clearly for another repo, discard reuse and rerun the rest of Step 1.
    4. If both artifacts are valid, reuse them directly. If `in_scope_files` is empty, skip Steps 2-4 and produce final output with zero findings.
    5. Otherwise use the `Task` tool to spawn the `arithmetic-scanner` agent. Its prompt must include:
       - project root path
       - absolute output path for `DIMENSIONAL_SCOPE.json`
       - instruction to write the Step 1 scope manifest to disk and return the same scope data in its report
    6. The scanner owns Step 1 scope persistence. It must:
       - identify dimensional-arithmetic files and prioritize them as usual
       - write `DIMENSIONAL_SCOPE.json` with `project_root`, `in_scope_files`, `discoverer_focus_files`, and `recommended_discovery_order`
       - initialize every in-scope file with `step2: "PENDING"`, `step3: "PENDING"`, and `step4: "PENDING"`
       - still write an empty manifest when no arithmetic files are found
       - still narrow `discoverer_focus_files` to CRITICAL/HIGH when more than 50 arithmetic files are found, while keeping all priorities in `in_scope_files`
    7. After the scanner completes, read `DIMENSIONAL_SCOPE.json` from disk and confirm it exists and contains the required Step 1 fields before continuing.
    8. Use the `Task` tool to spawn the `dimension-discoverer` agent. Its prompt must include:
       - project root path
       - absolute path to `DIMENSIONAL_SCOPE.json`
       - absolute output path for `DIMENSIONAL_UNITS.md`
       - prioritized `discoverer_focus_files` with each file's path, priority, score, and category
       - `recommended_discovery_order`
    9. The discoverer owns Step 1 vocabulary persistence. It must read `DIMENSIONAL_SCOPE.json` as the Step 1 source of truth and write `DIMENSIONAL_UNITS.md` with `Base Units`, `Derived Units`, and `Precision Prefixes` sections. If `in_scope_files` is empty, it must still write the same headings with empty sections.
    10. Step 1 is complete only when both artifacts exist on disk, pass the reuse checks above, and correctly represent the zero-file case. If `in_scope_files` is empty after the discoverer writes `DIMENSIONAL_UNITS.md`, skip Steps 2-4 and produce final output with zero findings.
    
    ### Step 2: Anchor Annotation
    
    The main skill context must not add annotations itself. Use the `Task` tool to spawn `dimension-annotator` agents for all anchor-point annotation work. For full examples and annotation format details, see `[{baseDir}/references/annotate.md]({baseDir}/references/annotate.md)`.
    
    - Read `DIMENSIONAL_SCOPE.json` and build batches from `in_scope_files`. Every in-scope file, including MEDIUM and LOW priority files, must receive a Step 2 outcome.
    - Batch files instead of spawning one agent per file:
      - `<= 10` files: one batch
      - `11-30` files: one batch per category
      - `> 30` files: one batch per category, splitting categories larger than 10 files into sub-batches of about 8 files
    - Launch categories in Step 1 recommended discovery order: math libraries, then oracles, then core logic, then peripheral. Batches inside the same category may run in parallel.
    - Before launching annotators, set `step2 = "PENDING"` for every in-scope file and persist the updated `DIMENSIONAL_SCOPE.json`.
    - Each annotator prompt must include:
      - absolute path to `DIMENSIONAL_UNITS.md`
      - absolute path to `DIMENSIONAL_SCOPE.json`
      - assigned file paths in order
      - each file's category and matched patterns from scanner output
      - summary of previously annotated interfaces or types from earlier batches, when applicable
      - required per-file status output: `ANNOTATED`, `REVIEWED_NO_ANCHOR_CHANGES`, or `BLOCKED` plus a one-line justification
    - After each batch, immediately persist each assigned file to exactly one Step 2 status:
      - `ANNOTATED`
      - `REVIEWED_NO_ANCHOR_CHANGES`
      - `BLOCKED`
    - If a file is `BLOCKED`, also persist `step2_reason` and `step2_retry_count`. Retry each `BLOCKED` file once with a focused prompt.
    - Do not continue to Step 3 while any file remains `PENDING` in on-disk manifest state.
    
    ### Step 3: Dimension Propagation
    
    The main skill context must not perform propagation reasoning itself. Use the `Task` tool to spawn `dimension-propagator` agents to extend annotations through arithmetic, function calls, and assignments. For algebra details, see `[{baseDir}/references/dimension-algebra.md]({baseDir}/references/dimension-algebra.md)`.
    
    - Read `DIMENSIONAL_SCOPE.json` and build propagation batches from `in_scope_files`. Every in-scope file must receive a Step 3 outcome.
    - Use the same batching rules and category ordering as Step 2.
    - Before launching propagators, confirm every file already has a non-pending Step 2 status.
    - Then set `step3 = "PENDING"` for every in-scope file and persist the updated manifest.
    - Each propagator prompt must include:
      - absolute path to `DIMENSIONAL_UNITS.md`
      - absolute path to `DIMENSIONAL_SCOPE.json`
      - assigned file paths in order
      - each file's category and matched patterns
      - summary of Step 2 anchor annotations for the assigned files and any upstream interfaces they depend on
      - required per-file status output: `PROPAGATED`, `REVIEWED_NO_PROPAGATION_CHANGES`, or `BLOCKED` plus a one-line justification
    - After each batch, immediately persist each assigned file to exactly one Step 3 status:
      - `PROPAGATED`
      - `REVIEWED_NO_PROPAGATION_CHANGES`
      - `BLOCKED`
    - If a file is `BLOCKED`, also persist `step3_reason` and `step3_retry_count`. Retry each `BLOCKED` file once with a focused prompt.
    - After all propagators complete, aggregate:
      - annotations added by confidence level (`CERTAIN`, `INFERRED`, `UNCERTAIN`)
      - mismatches found, with severities for validator deduplication
      - coverage gaps that could not be inferred
    - Do not continue to Step 4 while any file remains `PENDING` in on-disk manifest state.
    
    ### Step 4: Bug Detection
    
    The main skill context must not perform bug detection itself. Use the `Task` tool to spawn `dimension-validator` agents to detect dimensional bugs in annotated code. For examples, red flags, rationalization checks, and standard vocabulary, see `[{baseDir}/references/bug-patterns.md]({baseDir}/references/bug-patterns.md)`, `[{baseDir}/references/common-dimensions.md]({baseDir}/references/common-dimensions.md)`, and `[{baseDir}/references/dimension-algebra.md]({baseDir}/references/dimension-algebra.md)`. DO NOT DETECT BUGS IN ANY OTHER STEP.
    
    - Validate every file in `DIMENSIONAL_SCOPE.json.in_scope_files`.
    - Use this priority order without skipping lower tiers:
      1. files with CRITICAL or HIGH Step 3 mismatches
      2. remaining CRITICAL and HIGH scanner-priority files
      3. remaining MEDIUM and LOW files
    - Before launching validators, confirm every file already has a non-pending Step 3 status.
    - Then set `step4 = "PENDING"` for every in-scope file and persist the updated manifest.
    - Spawn one `dimension-validator` agent per file. For large repos, run them in waves of roughly 10-30 files to keep orchestration stable.
    - Each validator prompt must include:
      - absolute path to `DIMENSIONAL_UNITS.md`
      - absolute path to `DIMENSIONAL_SCOPE.json`
      - the single file path to validate
      - a summary of anchor and propagated annotations in the file
      - Step 3 mismatch summaries for the file, including mismatch IDs
      - cross-file function signatures or return dimensions needed for call-boundary checks
      - required per-file status output: `VALIDATED` or `BLOCKED`
    - After each wave, immediately persist each file to exactly one Step 4 status:
      - `VALIDATED`
      - `BLOCKED`
    - If a file is `BLOCKED`, also persist `step4_reason` and `step4_retry_count`. Retry each `BLOCKED` file once with a focused prompt.
    - Deduplicate findings:
      - confirmed Step 3 mismatches keep their original IDs and severities
      - refuted Step 3 mismatches are noted as false positives and excluded from final counts
      - genuinely new findings receive new `DIM-XXX` IDs
    - Aggregate confirmed findings, new findings, refuted findings, coverage summary, and final `coverage.unprocessed_files`.
    - Step 4 is complete only when `DIMENSIONAL_SCOPE.json.in_scope_files` contains no `step4: "PENDING"` entries.
    
    ## Reference Documentation
    
    Pass these references to the relevant subagent when a step needs them:
    - `[{baseDir}/references/dimension-algebra.md]({baseDir}/references/dimension-algebra.md)` - Propagator and validator algebra rules
    - `[{baseDir}/references/common-dimensions.md]({baseDir}/references/common-dimensions.md)` - Validator vocabulary reference
    - `[{baseDir}/references/bug-patterns.md]({baseDir}/references/bug-patterns.md)` - Validator bug-pattern and red-flag reference
    - `[{baseDir}/references/annotate.md]({baseDir}/references/annotate.md)` - Annotator format and example reference
    
    ## Final Output
    
    At the end of the analysis, provide a structured summary unless some other output format has been specified:
    
    ```json
    {
      "mode": "full-auto",
      "project_root": "<path>",
      "vocabulary": {
        "base_units": ["..."],
        "derived_units": ["..."],
        "precision_prefixes": ["..."]
      },
      "annotations": {
        "total_added": 0,
        "by_file": {}
      },
      "findings": {
        "critical": 0,
        "high": 0,
        "medium": 0,
        "details": []
      },
      "uncertainties_resolved": 0,
      "coverage": {
        "in_scope_files": 0,
        "anchor_reviewed_files": "0/0",
        "propagation_reviewed_files": "0/0",
        "validation_reviewed_files": "0/0",
        "annotated_functions": "0/0",
        "annotated_variables": "0/0",
        "unprocessed_files": [
          {
            "path": "/path/to/repo/contracts/LegacyMath.sol",
            "blocked_step": "step3",
            "reason": "Parser could not process generated source",
            "retry_count": 1
          }
        ]
      }
    }
    ```
    
    ## Completion Checklist
    
    You are NOT done until all of these are true:
    
    ### File Coverage Gates
    - [ ] `DIMENSIONAL_UNITS.md` exists in the project root
    - [ ] `DIMENSIONAL_SCOPE.json` exists in the project root and is the source of truth for downstream coverage
    - [ ] Every in-scope arithmetic file discovered in Step 1 appears in `DIMENSIONAL_SCOPE.json.in_scope_files`
    - [ ] Every in-scope file has a non-`PENDING` Step 2 status (`ANNOTATED`, `REVIEWED_NO_ANCHOR_CHANGES`, or `BLOCKED`)
    - [ ] Every in-scope file has a non-`PENDING` Step 3 status (`PROPAGATED`, `REVIEWED_NO_PROPAGATION_CHANGES`, or `BLOCKED`)
    - [ ] Every in-scope file has a non-`PENDING` Step 4 status (`VALIDATED` or `BLOCKED`)
    - [ ] No in-scope file remains `PENDING` in any step
    - [ ] Any `BLOCKED` file has a documented reason in the final output
    - [ ] `coverage.unprocessed_files` exactly matches the final set of terminal `BLOCKED` files after retries, using `path`, `blocked_step`, `reason`, and `retry_count`
    
    ### Summary Report
    - [ ] Final summary JSON/report provided
    - [ ] Final coverage counters match `DIMENSIONAL_SCOPE.json`
    - [ ] List of modified files provided when edits occurred
    - [ ] Any dimensional mismatches or bugs found are summarized
    - [ ] Any remaining blocked or unprocessed files are called out with reasons
    
    **If `DIMENSIONAL_SCOPE.json` and the final report disagree, reconcile the report or continue processing until they match.**
    **Do not claim completion from agent intent alone; completion is determined by manifest coverage and final reported statuses.**
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related