Skip to main content

Code Quality Standards - Linting Rules

Version: 1.0 Last Updated: 2025-10-30 Status: MANDATORY for all language implementations


Purpose

Define universal code quality standards that prevent technical debt accumulation across all sovdev-logger language implementations. These standards are specifically designed for systematic LLM-driven development.


Philosophy: Prevent LLMs from Going Off the Rails

The Core Problem: Dead Code Accumulation

LLMs naturally accumulate dead code through common patterns:

  1. "I might need this" - Imports libraries "just in case"
  2. "TODO that never happens" - Declares variables for future features that never materialize
  3. "Copy-paste replication" - Copies patterns from examples, including unused code

Without strict linting, dead code multiplies exponentially across implementations.

Real Example from TypeScript Implementation

When we added linting to TypeScript, we found 8 issues immediately:

Dead Code (4 issues)

// ❌ Before linting:
import { context } from '@opentelemetry/api'; // Imported "just in case"
import { SOVDEV_LOGLEVELS } from './logLevels'; // Never used
let globalSessionId: string | null = null; // Declared for "future feature"

// ✅ After linting:
// Unused imports removed
// TODO added: // globalSessionId - implement session tracking in future

Type Safety (2 issues)

// ❌ Before linting:
log(info: any, callback: Function) { // What does it return? What args?

// ✅ After linting:
log(info: any, callback: () => void): void { // Clear contract

Best Practices (2 issues)

  • Using @ts-ignore instead of @ts-expect-error
  • Inconsistent code formatting

Impact: All 8 issues = Technical debt that would have propagated to Python, Go, C#, PHP!


Universal Linting Principles

These principles apply to all language implementations, adapted to each language's ecosystem:

🔴 Principle 1: STRICT Dead Code Prevention (ERRORS - Block Build)

Philosophy: Dead code = confusion and technical debt. Block it immediately.

Rules:

  • ERROR: Unused imports
  • ERROR: Unused variables
  • ERROR: Unused function parameters (except prefixed with _)
  • ERROR: Unreachable code

Why This is Critical for LLMs:

Without strict rules:
TypeScript: import { UNUSED } from './lib'; // ❌ Ships
↓ LLM copies pattern
Python: from lib import UNUSED # ❌ Dead code replicates
↓ LLM copies pattern
Go: import "lib/UNUSED" // ❌ Dead code multiplies

With strict rules:
TypeScript: import { UNUSED } from './lib'; // ❌ Error: unused
↓ Fix immediately
TypeScript: // removed // ✅ Clean
↓ LLM sees clean pattern
Python: # only needed imports // ✅ Clean from start

Language-Specific Examples:

LanguageToolRule Configuration
TypeScriptESLint@typescript-eslint/no-unused-vars: "error"
PythonFlake8F401 (unused import), F841 (unused variable)
Gogolangci-lintunused, deadcode
C#RoslynCS0219 (unused variable), CS8019 (unnecessary using)
PHPPHPStanlevel: 6 (unused variables)

🔴 Principle 2: STRICT Return Type Enforcement (ERRORS - Block Build)

Philosophy: Function contracts must be explicit. No implicit types.

Rules:

  • ERROR: Missing return types on functions
  • ERROR: Implicit return types
  • ALLOW: Explicit any for flexible APIs (public functions only)

Why This Matters:

// ❌ Before: Unclear contract
function process(data) {
return data.filter(x => x.valid);
}
// What does it return? Array? Undefined?

// ✅ After: Clear contract
function process(data: any[]): any[] {
return data.filter(x => x.valid);
}
// Returns array, TypeScript enforces it

Language-Specific Examples:

LanguageToolRule Configuration
TypeScriptESLint@typescript-eslint/explicit-function-return-type: "error"
Pythonmypy--disallow-untyped-defs
GoBuilt-inReturn types required by language
C#RoslynCS1737 (return type required)
PHPPHPStanlevel: 5 (return type checking)

🔴 Principle 3: STRICT Code Complexity Limits (ERRORS - Block Build)

Philosophy: Keep functions manageable. Complex functions = hard to test, maintain, understand.

Rules:

  • ERROR: Cyclomatic complexity > 20
  • ⚠️ WARNING: Functions > 200 lines

Why This Prevents Issues:

  • LLMs can generate large, complex functions
  • Complexity limits force decomposition
  • Smaller functions = easier to test and understand

Language-Specific Examples:

LanguageToolRule Configuration
TypeScriptESLintcomplexity: ["error", 20]
Pythonflake8mccabe plugin, max-complexity: 20
Gogolangci-lintgocyclo, cyclop
C#RoslynCode metrics analyzer
PHPPHPStanComplexity rules

⚠️ Principle 4: WARN on Type Safety Erosion (WARNINGS - Don't Block)

Philosophy: Track type safety issues without blocking flexible APIs.

Rules:

  • ⚠️ WARNING: Use of any type (TypeScript)
  • ⚠️ WARNING: Untyped function arguments (Python)
  • ⚠️ WARNING: Interface{} usage (Go)

Why Warnings, Not Errors:

  • Logging libraries need flexible APIs (accept any JSON)
  • Warning = visibility without rigidity
  • Track type safety erosion over time

Language-Specific Examples:

LanguageWarning RuleWhy Allow It
TypeScript@typescript-eslint/no-explicit-any: "warn"Public API needs flexibility
Pythonmypy --warn-incomplete-stubTrack, don't block
Gointerface{} usageFlexible JSON handling
C#dynamic usageAPI flexibility
PHPmixed typeFlexible input

✅ Principle 5: ALLOW Pragmatic Exceptions (Context-Specific)

Philosophy: Some rules need exceptions based on project context.

sovdev-logger Specific Exceptions:

  1. Console logging allowed

    • Reason: Logging library intentionally uses console output
    • TypeScript: no-console: "off"
    • Python: Allow print() statements
  2. Public API type flexibility

    • Reason: Users pass various JSON structures
    • TypeScript: explicit-module-boundary-types: "off"
    • Python: Allow Any in public functions
  3. Test files may have looser rules

    • Reason: Tests need flexibility
    • All languages: Separate lint config for /test/ directories

Pattern Replication Prevention

The Multiplication Problem

Without Linting:

TypeScript implementation:
import { UNUSED } from './lib'; // ❌ Dead code

LLM studies TypeScript → copies pattern to Python:
from lib import UNUSED # ❌ Dead code replicates

LLM studies both → copies pattern to Go:
import "lib/UNUSED" // ❌ Dead code multiplies (3x now!)

LLM studies all → copies pattern to C#:
using Lib.UNUSED; // ❌ Dead code multiplies (4x now!)

Result: One mistake → Four mistakes

With Strict Linting:

TypeScript implementation:
import { UNUSED } from './lib'; // ❌ Error caught immediately
Fix: Remove it

TypeScript (clean):
// Only needed imports // ✅ Clean pattern

LLM studies TypeScript → sees clean pattern:
# Only needed imports // ✅ Clean from start

LLM studies both → sees clean pattern:
// Only needed imports // ✅ Clean from start

Result: Zero mistakes propagate

Key Insight: TypeScript is the reference implementation. Linting ensures it's clean, so all other languages copy clean patterns.


Integration with Development Loop

The 6-Step Development Loop

Linting is Step 2 (mandatory, blocking):

1. Edit - Make code changes
2. Lint - ⚠️ MANDATORY BLOCKING STEP ⚠️
3. Build - Compile/build library
4. Run/Test - Execute code (start with simple tests, work up to E2E)
5. Validate Logs - Check file format (FAST - instant feedback)
6. Validate OTLP - Check backends (SLOW - requires infrastructure)

Why Lint is Step 2 (Before Build)

BenefitDescription
Fast feedbackCatches issues in seconds, not minutes
Early preventionBefore they compound in build/test cycles
Clear errorsActionable messages for humans and LLMs
SystematicForces quality at every iteration
Cost savingsCheaper to fix linting errors than test failures

Exit Code Requirements

All linting implementations MUST follow these exit codes:

  • Exit 0 - All checks passed (warnings allowed, don't block)
  • Exit non-zero - Errors found (MUST fix before proceeding)

Example:

$ make lint
Running linting...
1 problem (1 error, 0 warnings)
$ echo $?
1 # ❌ Non-zero = Blocks development loop

$ make lint
23 problems (0 errors, 23 warnings)
$ echo $?
0 # ✅ Zero = Continues (warnings don't block)

Language-Specific Implementation Guide

Overview: Consistent Interface Pattern

Key Decision: Use Makefile for Consistent Interface Across All Languages

Each language has its own native way to run linting:

  • TypeScript: npm run lint
  • Python: flake8 src/ or black --check src/
  • Go: golangci-lint run
  • C#: dotnet format --verify-no-changes

Problem: Different commands per language = harder for LLMs and developers to remember.

Solution: Makefile provides a consistent wrapper interface:

# Same command works for ALL languages:
make lint # Check linting
make lint-fix # Auto-fix issues

Benefits:

  1. LLM-friendly - Learn one pattern: "all languages use make lint"
  2. Human-friendly - Don't remember language-specific commands
  3. Consistent documentation - Development loop shows make lint everywhere
  4. Optional - Can still use native tools directly (npm run lint, etc.)

The Makefile wraps language-native tools:

  • TypeScript Makefile calls npm run lint internally
  • Python Makefile calls flake8, black, mypy internally
  • Go Makefile calls golangci-lint run internally

For Each Language Implementation, Create:

1. Configuration Files

Store linting configuration in the language directory root:

LanguageConfiguration FilesLocation
TypeScript.eslintrc.json, .prettierrctypescript/
Python.flake8, pyproject.toml, mypy.inipython/
Go.golangci.ymlgo/
C#.editorconfig, ruleset.xmlcsharp/
PHPphpstan.neon, phpcs.xmlphp/

2. Makefile Target

Every language MUST have a lint target:

.PHONY: lint
lint:
@echo "Running {language} linting..."
# Run language-specific linters
# Exit 0 on success, non-zero on error

.PHONY: lint-fix
lint-fix:
@echo "Running {language} linting with auto-fix..."
# Run linters with auto-fix enabled

3. Package Dependencies

Document linting tools in standard dependency files:

LanguageDependency FileExample
TypeScriptpackage.json devDependencies"eslint": "^8.57.0"
Pythonrequirements-dev.txtblack>=25.0.0
Gogo.mod (tools)github.com/golangci/golangci-lint
C#.csproj<PackageReference> analyzers
PHPcomposer.json require-dev"phpstan/phpstan"

Rule Configuration Templates

TypeScript (Reference Implementation)

{
"rules": {
// Dead Code Prevention (STRICT)
"@typescript-eslint/no-unused-vars": "error",

// Type Safety (STRICT)
"@typescript-eslint/explicit-function-return-type": "error",

// Type Safety (PRAGMATIC)
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/explicit-module-boundary-types": "off",

// Code Quality (STRICT)
"complexity": ["error", 20],
"max-lines-per-function": ["warn", 200],

// Project-Specific Exceptions
"no-console": "off"
}
}

Python (Equivalent Configuration)

# .flake8
[flake8]
max-complexity = 20
max-line-length = 100
ignore = E203,W503
per-file-ignores = __init__.py:F401

# pyproject.toml
[tool.mypy]
warn_unused_configs = true
disallow_untyped_defs = false # Off for public API flexibility
warn_return_any = true

[tool.black]
line-length = 100

Go (Equivalent Configuration)

# .golangci.yml
linters:
enable:
- unused # Dead code prevention
- deadcode # Unreachable code
- gocyclo # Complexity limit
- ineffassign # Unused assignments

linters-settings:
gocyclo:
min-complexity: 20

C# (Equivalent Configuration)

<!-- .editorconfig -->
[*.cs]
# Dead code
dotnet_diagnostic.CS0219.severity = error # Unused variable
dotnet_diagnostic.CS8019.severity = error # Unused using

# Complexity
dotnet_code_quality.CA1502.severity = warning # Avoid excessive complexity

PHP (Equivalent Configuration)

# phpstan.neon
parameters:
level: 6 # Strict type checking
paths:
- src
ignoreErrors:
- '#Function .* has no return type#' # Allow for public API

Measuring Success

Immediate Success (Implementation Phase)

  • ✅ Linting passes with 0 errors on reference implementation (TypeScript)
  • ✅ Configuration files created and documented
  • ✅ Makefile lint target works
  • ✅ Exit codes follow standard (0 = pass, non-zero = fail)

Long-Term Success (Future Implementations)

When LLM implements new language (Go, C#, PHP):

  • ✅ LLM reads this specification document
  • ✅ LLM studies TypeScript configuration (reference)
  • ✅ LLM creates equivalent configuration for new language
  • ✅ LLM runs make lint before build/test
  • ✅ No dead code propagates from TypeScript to new language

Validation Checklist

For each language implementation, verify:

Configuration

  • Linting tool configured (eslint, flake8, golangci-lint, etc.)
  • Configuration file created in language root directory
  • Rules follow the 5 universal principles above
  • Exit codes work correctly (0 = pass, non-zero = fail)

Integration

  • Makefile has lint and lint-fix targets
  • make lint works from language directory inside DevContainer
  • Commands execute successfully at /workspace/
  • Documented in language's README

Quality

  • Linting passes with 0 errors on clean code
  • Intentional errors are caught and blocked
  • Warnings are visible but don't block
  • Auto-fix works where applicable (lint-fix target)

Benefits Summary

For Human Developers

  1. ✅ Consistent code style across team
  2. ✅ Catches bugs early (unused code, type errors)
  3. ✅ Clear error messages
  4. ✅ Auto-fix available for many issues

For LLM Developers

  1. Prevents "going off the rails" - Dead code caught immediately
  2. Clear boundaries - Rules define what's acceptable
  3. Actionable feedback - Error messages are explicit
  4. Pattern enforcement - Clean code from reference implementation propagates

For Multi-Language Projects

  1. Consistent quality - Same principles across all languages
  2. No pattern replication - Mistakes don't multiply
  3. Self-documenting - Configuration files show standards
  4. Reusable - Copy specification folder to other projects

Key Insight

Strict linting = LLM guardrails

The stricter the dead code prevention, the better LLMs stay on track across all implementations.

Proof: TypeScript linting found 8 issues that would have propagated to 4+ languages = 32+ total issues prevented.


References

  • Reference Implementation: typescript/.eslintrc.json - Study this first
  • TypeScript Documentation: typescript/package.json - See lint scripts
  • Development Loop: 09-development-loop.md - Step 2: Lint
  • Implementation Guide: implementation-guide.md - The end-to-end process

Status: ✅ MANDATORY for all implementations Applies to: TypeScript ✅, Python (pending), Go (pending), C# (pending), PHP (pending) Last Validated: 2025-10-30 (TypeScript)