Skip to content

graffhyrum/stepdown-rule

Repository files navigation

Stepdown Rule Analyzer

A TypeScript AST analyzer that enforces the stepdown rule for function organization in codebases. The stepdown rule organizes code from high-level concepts at the top to low-level implementation details at the bottom.

Installation

This is a local package. Clone and install it:

# Clone the repository
git clone https://github.com/graffhyrum/stepdown-rule.git
cd stepdown-rule

# Install dependencies
bun install

# Build the package (produces JS bundles + self-contained native CLI binary)
bun run build

Making stepdown-rule available to all projects on your system (recommended)

The build produces a standalone native executable at dist/stepdown-rule (100MB-ish, embeds Bun runtime + TypeScript + deps). This binary has no external runtime dependency on a bun command in PATH (unlike the old shebang approach).

# Recommended: symlink the binary into a directory on your PATH.
# Create a personal bin dir if you don't have one:
mkdir -p ~/bin
export PATH="$HOME/bin:$PATH"   # add this to ~/.bashrc, ~/.zshrc, etc.

# Durable link (survives source moves, bun updates, etc.)
ln -sf "$(pwd)/dist/stepdown-rule" ~/bin/stepdown-rule

# Now `stepdown-rule` works from ANY directory / project on the machine.
stepdown-rule --version

Alternative (bun-centric, still supported):

# bun link registers the native binary in ~/.bun/bin (requires that dir on PATH)
bun link

bun link (or bun link @stepdown/analyzer in a consumer) will continue to work and will expose the compiled native binary.

Local Usage in Another Project (programmatic API)

For importing the analyzer in code (not the CLI), link the package:

cd /path/to/your-project
bun link @stepdown/analyzer

Then:

import { analyzeFiles, fixFiles } from "@stepdown/analyzer";
import { FileService } from "@stepdown/analyzer/services/FileService";

const fileService = new FileService();
const config = { ignore: [], fix: false, json: false };
const results = await analyzeFiles(["src/**/*.ts"], config, fileService);

console.log(results);

Or simply use the globally available CLI (no link needed once the binary is in your PATH):

stepdown-rule "src/**/*.ts"

Usage

CLI

# Analyze default (src/**/*.ts)
stepdown-rule

# Analyze specific files/globs
stepdown-rule analyze "src/**/*.ts" "lib/**/*.ts"

# Analyze a directory (auto-expands to **/*.ts)
stepdown-rule analyze src/

# Auto-fix violations
stepdown-rule fix

# Fix specific files
stepdown-rule fix "src/**/*.ts"

# Show circular dependencies (verbose mode)
stepdown-rule analyze --verbose

# JSON output for CI
stepdown-rule analyze --json

# Only run specific rules
stepdown-rule analyze --rules stepdown,nested

# Custom ignore patterns
stepdown-rule analyze --ignore "test/**/*" "generated/**/*"

Agents and automation

For coding agents and CI parsers, use the agents subcommand (stable JSON envelope on stdout). See SKILL.md for workflows, exit codes, and decision trees.

stepdown-rule agents analyze 'src/**/*.ts'
stepdown-rule agents fix 'src/**/*.ts' --dry-run
stepdown-rule agents schema rules

Programmatic

import { analyzeFiles, fixFiles } from "@stepdown/analyzer";
import { FileService } from "@stepdown/analyzer/services/FileService";

const fileService = new FileService();
const config = { ignore: [], fix: false, json: false };
const results = await analyzeFiles(["src/**/*.ts"], config, fileService);

console.log(results);

What is the Stepdown Rule?

The stepdown rule organizes functions in a file from high-level to low-level:

// ✅ Good: Stepdown rule followed
function main() {
  const user = createUser("John", "john@example.com", "password123");
  console.log("User created:", user);
}

function createUser(name: string, email: string, password: string): User {
  if (!validateEmail(email)) throw new Error("Invalid email");
  const hashedPassword = hashPassword(password);
  return { id: Math.random().toString(36), name, email, password: hashedPassword };
}

function validateEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function hashPassword(password: string): string {
  return crypto.createHash("sha256").update(password).digest("hex");
}

The rule: within a scope, scope logic comes before subfunction declarations, and if function A calls function B, then A should appear before B in the file.

How It Works

The analyzer reports only actionable violations - violations that can be fixed by reordering code. Violations involving functions in circular dependency cycles are excluded from reporting because reordering cannot fix them; they require refactoring.

What Gets Reported

Reported: Functions that call other functions appearing below them

  • These can be fixed by moving the caller after the callee

Not Reported: Functions involved in circular dependencies

  • Example: funcA → funcB → funcA (mutual recursion)
  • These require architectural changes, not reordering
  • Often appear in tree traversal algorithms, mutual recursion patterns, or interconnected systems

Circular Dependencies

Circular dependencies are always detected and reported separately. To understand what's creating cycles in your code:

stepdown-rule src/analyzer.ts
# Output shows both violations (fixable) and circular dependencies (architectural)

Circular dependencies do NOT prevent the fixer from running, but files with circular dependencies cannot be auto-fixed since reordering won't resolve them.

Configuration

Create a .stepdownrc.json file (optional):

{
  "$schema": "./stepdown-schema.json",
  "ignore": ["node_modules/**", "dist/**", "*.test.ts", "*.spec.ts"]
}

Configuration Options

Option Type Default Description
ignore string[] [] Additional glob patterns to ignore when analyzing files

CLI Options

  • patterns - File patterns or directories to analyze (default: src/**/*.ts)
  • --verbose - Show circular dependencies in output
  • --json - Output results in JSON format
  • --rules <ids> - Comma-separated rule IDs to run (available: stepdown, nested; default: all)
  • --ignore <patterns...> - Additional ignore patterns
  • --config <file> - Configuration file path (default: .stepdownrc.json)

Development

bun install
bun run dev      # Run CLI from source
bun run build    # Build
bun test         # Test
bun run check    # Lint + format check (oxlint + oxfmt)
bun run vet      # Full pipeline: build + typecheck + lint + test

License

MIT

About

A TS CLI to apply the Stepdown rule to TS code

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors