Contributing to Screeps Bot
June 15, 2026 · View on GitHub
Framework-First Development Policy
IMPORTANT: This repository follows a framework-first development approach. Framework packages (@ralphschuler/*) are the source of truth for all shared functionality.
Framework Packages are Canonical
All behavior implementations, role logic, and reusable components must be developed in framework packages:
- ✅ Develop in:
packages/@ralphschuler/screeps-*packages - ❌ Do NOT develop in:
packages/screeps-bot/src(monolith)
The monolith (packages/screeps-bot) should only contain:
- Integration/wiring code
- Thin adapters that import from framework packages
- Bot-specific configuration
Rules for Code Placement
Behavior & Role Code → @ralphschuler/screeps-roles
// ✅ Correct: Framework package
packages/@ralphschuler/screeps-roles/src/behaviors/executor.ts
// ❌ Wrong: Monolith
packages/screeps-bot/src/roles/behaviors/executor.ts // Should NOT exist
Economy Logic → @ralphschuler/screeps-economy
Defense Logic → @ralphschuler/screeps-defense
Spawn Logic → @ralphschuler/screeps-spawn
Core Utilities → @ralphschuler/screeps-core
Importing from Framework Packages
Always import from framework packages, never from local monolith files:
// ✅ Correct: Import from framework package
import { createContext, executeAction } from "@ralphschuler/screeps-roles";
// ❌ Wrong: Local import (should not exist)
import { createContext } from "./roles/behaviors/context";
CI Enforcement
The repository has automated checks (.github/workflows/framework-sync-check.yml) that will fail your PR if:
- A
behaviors/directory exists in the monolith - Local imports from
behaviors/are detected - Code duplication is found between monolith and framework
Making Changes to Role Behavior
When modifying role behavior, edit the framework package directly:
# ✅ Edit framework package
vim packages/@ralphschuler/screeps-roles/src/behaviors/executor.ts
# Build framework package
cd packages/@ralphschuler/screeps-roles
npm run build
# Build monolith (which imports the updated framework)
cd ../screeps-bot
npm run build
Why Framework-First?
- Single Source of Truth: Eliminates code divergence and duplication
- Community Reusability: Framework packages can be published to npm
- Better Testing: Framework packages are independently testable
- Clear Ownership: Framework packages own features, monolith just integrates
- Easier Maintenance: Changes in one place, benefits everywhere
Migration Status
As of January 2026, the synchronization between monolith and framework is complete:
- ✅ All behavior files migrated to
@ralphschuler/screeps-roles - ✅ Monolith behaviors directory removed
- ✅ All monolith imports updated to use framework packages
- ✅ CI checks prevent future divergence
See FRAMEWORK_MATURITY_ROADMAP.md for more details on framework adoption progress.
Package Structure
This monorepo contains multiple packages with two different build output patterns:
Pattern 1: Simple Packages (No Cross-Package Dependencies)
Used by: screeps-spawn, screeps-chemistry, screeps-utils, screeps-posis
tsconfig.json:
{
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
}
}
package.json:
{
"main": "dist/index.js",
"types": "dist/index.d.ts"
}
Build Output:
dist/
index.js
index.d.ts
(other files from src/)
Pattern 2: Complex Packages (With Cross-Package Dependencies)
Used by: screeps-defense, screeps-economy
tsconfig.json:
{
"compilerOptions": {
"outDir": "dist",
// NO rootDir specified - required for cross-package imports
// TypeScript enforces that all source files must be under rootDir when specified
// Cross-package imports reference files outside src/, so rootDir cannot be used
"paths": {
"@bot/*": ["../screeps-bot/src/*"]
}
}
}
package.json:
{
"main": "dist/screeps-defense/src/index.js",
"types": "dist/screeps-defense/src/index.d.ts"
}
Build Output:
dist/
screeps-defense/
src/
index.js
index.d.ts
(other files)
screeps-bot/
src/
(imported files from @bot/*)
Why Two Patterns?
Pattern 1 is simpler and produces a clean output structure, but requires all code to be within the package's src/ directory.
Pattern 2 is necessary when a package imports from other packages using path aliases (@bot/*). When rootDir is specified, TypeScript enforces that all source files must be located under that directory. Since cross-package imports reference files outside the package's src/ directory, omitting rootDir allows TypeScript to compile these external dependencies into the output directory. This results in the nested directory structure where both the package's code and its cross-package dependencies are included in dist/.
Test Environment
Tests run using Mocha with the bot package's .mocharc.json, which loads test/setup-mocha.mjs for Screeps globals and package stubs. This allows packages using Pattern 2 to be tested without requiring a live Screeps runtime.
Adding New Packages
- If your package doesn't import from other packages: Use Pattern 1
- If your package imports from
@bot/*or other packages: Use Pattern 2
Framework Package Dependencies
All framework packages with names starting with @ralphschuler/screeps-* share the same devDependencies configuration. This is automatically synchronized to ensure consistency across those packages.
Included framework packages:
- All packages under
packages/@ralphschuler/*(e.g.,screeps-core,screeps-cache,screeps-kernel) - Framework packages under
packages/screeps-*/with@ralphschuler/screeps-*names (e.g.,screeps-spawn,screeps-chemistry,screeps-defense,screeps-economy,screeps-utils)
Excluded packages (managed separately):
- Server/tooling packages (
@ralphschuler/screeps-server,@ralphschuler/screeps-roles,@ralphschuler/screeps-posis) - have different dependency requirements
How It Works
- Single source of truth:
scripts/shared-dependencies.jsondefines all shared devDependencies - Automated sync: Run
npm run sync:depsto update all framework packages - CI enforcement: PR checks fail if packages have inconsistent dependencies
- Scope: Applies to framework
@ralphschuler/screeps-*packages except server/tooling packages
Updating Dependencies
To update a dependency version (e.g., TypeScript, @types/node):
-
Edit
scripts/shared-dependencies.json:{ "framework": { "devDependencies": { "typescript": "^5.5.0" // Update version here } } } -
Run the sync script:
npm run sync:deps -
Commit all changes:
git add scripts/shared-dependencies.json 'packages/@ralphschuler/*/package.json' 'packages/screeps-*/package.json' git commit -m "chore: Update TypeScript to 5.5.0"
Removing a Shared Dependency
If you remove a dependency from scripts/shared-dependencies.json, it will persist in individual package.json files. To fully remove it:
- Remove it from
scripts/shared-dependencies.json - Manually delete it from each affected
package.jsonfile - Or modify the sync script to track and remove explicitly removed dependencies
Checking for Drift
To verify all packages are synchronized:
npm run sync:deps:check
This is automatically run in CI and will fail the build if packages have drifted.
Adding New Framework Packages
When creating a new @ralphschuler/screeps-* package:
- Create the package directory and files
- Add minimal
package.json(without devDependencies) - Run
npm run sync:depsto add shared devDependencies - The package will automatically have consistent dependencies
Why Automated Synchronization?
Before (manual):
- 14 files to edit for each dependency update
- High risk of inconsistencies
- Manual verification required
After (automated):
- 1 file edit → 14 packages updated
- Zero drift (enforced by CI)
- Self-healing on
npm install
Module Resolution
Both patterns work correctly with Node.js module resolution:
- Pattern 1:
node_modules/@ralphschuler/package-name/dist/index.js - Pattern 2:
node_modules/@ralphschuler/package-name/dist/package-name/src/index.js
The main and types fields in package.json correctly point to these files.
Build Requirements
System Requirements
- Node.js: 24.x (
>=24 <25) - npm: 10.0.0 or higher
- Python: Not required (native modules have been removed/updated)
Initial Setup
# Clone the repository
git clone https://github.com/ralphschuler/screeps.git
cd screeps
# Install dependencies
npm install
# Build all packages
npm run build:all
Building the Project
Build all packages:
npm run build:all
Build specific packages:
npm run build:kernel # @ralphschuler/screeps-kernel
npm run build:utils # @ralphschuler/screeps-utils
npm run build:stats # @ralphschuler/screeps-stats
npm run build # Main bot (screeps-typescript-starter)
# ... see package.json for all build:* scripts
Build order (for manual builds):
- Core packages first (kernel, utils, stats)
- Feature packages (spawn, economy, defense, etc.)
- Main bot last
Common Build Issues
Issue: Type errors in test files blocking build
Symptom:
error TS2339: Property 'greaterThanOrEqual' does not exist on type 'typeof Assert'
Solution:
Test files should not be included in production builds. The main bot's tsconfig.json excludes test files, and the rollup config has check: false to prevent type-checking errors from blocking builds.
Issue: "Index signature missing" type errors
Symptom:
error TS2345: Type 'X' is not assignable to parameter of type 'Y'
Index signature for type 'string' is missing
Solution: This occurs when interfaces need to be compatible with visualization or external packages. Add an index signature to the interface:
export interface MyInterface {
specificField: number;
// Add index signature for compatibility
[key: string]: unknown;
}
Issue: npm install warnings about engine versions
Symptom:
npm warn EBADENGINE Unsupported engine { required: { node: '>=24 <25' } }
Solution: Use the repository-supported runtime: Node.js 24.x with npm 10+.
# Using nvm
nvm install 24
nvm use 24
npm run check-versions
Issue: Rollup cache causing stale type errors
Solution:
# Clean rollup cache
rm -rf packages/screeps-bot/.rpt2_cache
# Clean all build artifacts
npm run clean # If available, or manually:
find packages -name "dist" -type d -exec rm -rf {} +
find packages -name ".rpt2_cache" -type d -exec rm -rf {} +
# Rebuild
npm run build:all
Troubleshooting Checklist
If you encounter build failures:
-
Check Node/npm versions:
node --version # Should be >=24 <25 npm --version # Should be >=10.0.0 npm run check-versions -
Clean install:
rm -rf node_modules package-lock.json npm install -
Clean build artifacts:
rm -rf packages/*/dist packages/*/.rpt2_cache npm run build:all -
Check for uncommitted changes:
git status # Should show clean working tree -
Verify package dependencies:
npm run build:kernel # Build dependencies first npm run build # Then build main bot
CI/CD Build Process
The GitHub Actions CI pipeline runs:
- Checkout code with full git history
- Setup Node.js (version specified in workflow)
- Install dependencies:
npm ci(clean install) - Build packages:
npm run build:all - Run tests:
npm test - Security scan: CodeQL analysis
See .github/workflows/ for complete CI configuration.
Deployment to Screeps
The repository includes automated deployment workflows that push built code to Screeps servers:
- Deploy Workflow (
.github/workflows/deploy.yml) - Deploys on release or manual trigger - Supports multiple environments - screeps.com, sim, season, ptr, and private servers
For repository maintainers: To enable deployment, you must configure GitHub Environments with Screeps credentials. See .github/ENVIRONMENT_SETUP.md for detailed setup instructions.
For local development: You can deploy manually using:
# Set credentials
export SCREEPS_TOKEN=your-token-here
# OR
export SCREEPS_USER=your-username
export SCREEPS_PASS=your-password
# Deploy to Screeps
npm run push
The build process will show diagnostic output indicating whether credentials are configured correctly. If you see "Credentials not configured" warnings, the code will build but won't upload (dryRun mode).
Testing
Run tests with:
npm test # Run all tests
npm test -w <package> # Run tests for specific package
Tests must pass before merging. The CI/CD pipeline runs:
npm ci- Install dependenciesnpm run build- Build all packagesnpm test- Run test suite
TODO Comment Workflow
This repository uses an automated TODO-to-issue workflow that converts TODO comments into GitHub issues. Use TODO comments liberally - they are a feature, not a code smell.
How It Works
- Write TODO comments in your code following the format below
- Workflow runs automatically:
- On every push to
mainbranch - Weekly on Sunday at midnight UTC (for catching any missed TODOs)
- On every push to
- Issues are created automatically with labels and context
- Issue URLs are inserted back into the code next to the TODO comment
TODO Comment Format
The workflow recognizes various TODO formats. Use descriptive comments that will become clear GitHub issues:
Basic Format:
// TODO: Brief description of what needs to be done
With Priority and Category:
// TODO(P1): BUG - Add error handling for missing source map file
// TODO(P2): PERF - Cache source map parsing to avoid expensive re-parsing
// TODO(P3): FEATURE - Add cluster-wide construction planning
With Estimates:
// TODO: [P1, Est: 4h] Posture switching to defensive may be too aggressive
// TODO: [P2, Est: 2h] Defense pheromone threshold may be too low
Multi-line with Context:
// TODO(P2): ARCH - Implement adaptive CPU budgets based on room count
// This should monitor actual process performance and adjust budgets dynamically
// See ROADMAP.md Section 15 for kernel requirements
Priority Levels
- P1 (Critical): Bugs, critical features, blocking issues - should be addressed ASAP
- P2 (High): Important improvements, architecture changes, performance optimizations
- P3 (Medium): Nice-to-have features, minor optimizations, documentation improvements
Category Tags
- BUG: Bug fixes and error handling
- PERF: Performance optimizations
- ARCH: Architectural improvements
- FEATURE: New features and capabilities
- TEST: Test coverage and testing improvements
- DOCS: Documentation updates
- STYLE: Code style and linting
When to Use TODO Comments
✅ Use TODO comments when:
- Setting up code structure but full implementation is out of scope
- Identifying work that should be done but exceeds the current task's boundaries
- Delivering a minimal working solution with clear next steps
- Documenting future enhancements during implementation
- Breaking down large features into smaller, trackable pieces
- Encountering errors that need separate investigation
❌ Don't use TODO comments for:
- Issues that can be fixed immediately in the current task
- External library issues (file issues with the library instead)
- Intentional design decisions (use regular comments to explain)
- Already fixed issues (remove the TODO)
After Workflow Runs
Once the workflow runs, your TODO will be updated with an issue link:
// TODO(P1): BUG - Add error handling for missing source map file
// Issue URL: https://github.com/ralphschuler/screeps/issues/809
You can then:
- Track progress on the GitHub issue
- Assign the issue to someone
- Add milestones for planning
- Reference the issue in commits:
git commit -m "fix: handle missing source map, fixes #809"
Workflow Configuration
The workflow is defined in .github/workflows/auto-todo-issue.yml and uses alstr/todo-to-issue-action.
Manual Trigger (if needed):
gh workflow run auto-todo-issue.yml
Examples
Example 1: Bug Fix Needed
function processMemory(data: any) {
// TODO(P1): BUG - Add null check for data parameter
// Currently throws if data is undefined
return data.map(item => item.value);
}
Example 2: Performance Optimization
export function calculatePath(from: RoomPosition, to: RoomPosition) {
// TODO(P2): PERF - Implement path caching with 50-tick TTL
// Current implementation recalculates paths every tick
// Expected CPU savings: ~0.5 CPU per creep per tick
return PathFinder.search(from, to);
}
Example 3: Architecture Improvement
class SpawnQueue {
// TODO(P2): ARCH - Implement priority-based queueing system
// Features needed:
// - Priority levels (emergency > defense > economy)
// - Energy availability prediction
// - Body part optimization based on available energy
// - Queue persistence across global resets
// Current implementation uses simple FIFO queue
private queue: SpawnRequest[] = [];
}
Best Practices
- Be specific: Describe what needs to be done and why
- Add context: Reference related files, functions, or documentation
- Estimate effort: Include time estimates when known (
Est: 2h) - Reference roadmap: Link to ROADMAP.md sections when applicable
- Group related TODOs: Use similar wording for related work items
- Keep TODOs updated: Remove when work is complete
Caching Patterns
This codebase uses a unified cache system owned by @ralphschuler/screeps-cache (packages/@ralphschuler/screeps-cache/src). All new caches should use this package instead of creating independent Map<> implementations.
When to Use Unified Cache
✅ Use the unified cache system when:
- Data needs TTL-based expiration
- Data should be tracked in observability metrics
- Cache needs coordinated invalidation
- Cache is performance-critical
❌ Don't use unified cache when:
- Using Map as a data structure (not a cache)
- Need very specific eviction logic
- Data structure requires Map-specific methods
Basic Usage
import { globalCache } from "@ralphschuler/screeps-cache";
const CACHE_NAMESPACE = "myFeature";
const TTL = 100; // ticks
// Get from cache
const value = globalCache.get<MyType>(key, {
namespace: CACHE_NAMESPACE,
ttl: TTL
});
// Set in cache
globalCache.set(key, value, {
namespace: CACHE_NAMESPACE,
ttl: TTL
});
// Invalidate
globalCache.invalidate(key, CACHE_NAMESPACE);
TTL Guidelines
- 1 tick: Per-tick ephemeral data (e.g., target assignments)
- 20-50 ticks: Frequently changing data (e.g., structure counts)
- 100-500 ticks: Stable data (e.g., paths, waypoints)
- -1: Permanent (use sparingly)
Cache Registration
New caches should be registered in packages/@ralphschuler/screeps-cache/src/cacheRegistration.ts:
cacheCoherence.registerCache(
"myFeature",
globalCache,
CacheLayer.L2,
{
priority: 50,
maxMemory: 1 * 1024 * 1024 // 1MB
}
);
Documentation
See packages/@ralphschuler/screeps-cache/src/CACHE_MIGRATION.md for detailed migration patterns and best practices.
Code Quality Standards
Dead Code Prevention
This repository follows a "required code only" philosophy to maintain a clean, maintainable codebase:
What to Remove Immediately
- ❌ Unused imports - Remove imports that are never used
- ❌ Unused variables - Remove variables that are never read
- ❌ Commented-out code - Use git history instead of keeping old code as comments
- ❌ Unreachable code - Remove code after return/throw statements
- ❌ Dead feature flags - If a feature is permanently disabled, remove it entirely
What NOT to Remove
- ✅ Runtime feature flags - Features that can be toggled at runtime (e.g.,
tooangel.enabled) - ✅ Framework exports - Types and functions exported from
@ralphschuler/*packages for reusability - ✅ Test utilities - Helper functions used only in tests
- ✅ Type definitions - TypeScript types used for type checking
ESLint Enforcement
The codebase uses strict ESLint rules to prevent dead code:
{
"@typescript-eslint/no-unused-vars": ["error", {
"argsIgnorePattern": "^_", // Allow _unused for intentionally unused params
"varsIgnorePattern": "^_", // Allow _temp for intentionally unused vars
"caughtErrorsIgnorePattern": "^_" // Allow catch (_error) for ignored errors
}],
"no-unreachable": "error", // Prevent code after return/throw
"no-constant-condition": "error", // Prevent if(false) and similar patterns
"prefer-const": "error", // Require const for non-reassigned variables
"import/no-duplicates": "error" // Prevent duplicate imports
}
Prefixing Intentionally Unused Variables
When you need to accept a parameter but don't use it (e.g., to match an interface), prefix it with underscore:
// ✅ Good: Unused parameter prefixed with _
function processCreep(_creep: Creep, room: Room) {
// Only using room, not creep
return room.energyAvailable;
}
// ❌ Bad: Unused parameter not prefixed (ESLint error)
function processCreep(creep: Creep, room: Room) {
return room.energyAvailable; // Error: 'creep' is defined but never used
}
Pre-Commit Checks
Before committing code:
- Run linter:
npm run lint(must pass with no errors) - Build code:
npm run build(must compile successfully) - Run tests:
npm test(all tests must pass)
Regular Maintenance
- Quarterly audit: Run
npx ts-pruneto find unused exports - Review TODO comments: Evaluate if TODOs should be implemented or removed
- Update dependencies: Remove unused npm packages with
npx depcheck
Tools for Dead Code Detection
-
ts-prune: Finds unused TypeScript exports
npx ts-prune --project tsconfig.json -
ESLint: Detects unused variables and imports
npm run lint -
depcheck: Finds unused npm dependencies
npx depcheck
Why Required Code Only?
- Performance: Unused code increases bundle size and memory usage
- Maintainability: Less code means easier understanding and modification
- Security: Fewer lines of code means smaller attack surface
- Build speed: Faster compilation with less code to process
Additional Resources
- Framework Maturity Roadmap - Framework adoption strategy
- ROADMAP.md - Bot architecture and design principles
- GitHub workflows - CI/CD pipeline definitions
- Quality Developer Guide - Quality gates, checks, and metrics
- Testing Guide - Test infrastructure overview
- Package Publishing - Framework package publishing guide