Claude Development Guidelines
February 9, 2026 ยท View on GitHub
Project Standards
RULE #1: THE BAZEL WAY FIRST
๐ฏ PRIMARY PRINCIPLE: Always prefer Bazel-native solutions over shell scripts, even for proof of concepts
Before implementing any solution:
- Check for existing Bazel rules that solve the problem
- Use ctx.actions.run() instead of shell commands
- Create custom rules instead of complex genrules
- Use Bazel toolchains instead of system tools
- Apply transitions instead of manual platform detection
Examples:
- โ
genrulewith shell script for file processing - โ
Custom
rule()withctx.actions.run() - โ System rust/cargo calls
- โ
@rules_rustwith proper transitions - โ Shell-based tool wrappers
- โ Hermetic toolchain rules
Even for prototypes and proof-of-concepts, follow Bazel principles to avoid technical debt.
Shell Script Elimination Policy
๐ซ Prohibited Patterns
- No Shell Script Files: No
.shfiles in the repository - No Shell Scripts in Disguise: Avoid complex shell commands in
genrulecmd attributes - No Embedded Shell Scripts: Minimize multi-line shell scripts in
.bzlfiles - No Platform-Specific Commands: Avoid Unix-specific commands that won't work on Windows
โ Approved Patterns
- Single Command genrules: Simple tool invocations (
wasm-tools validate) - Bazel Native Rules: Use built-in test rules, toolchain rules
- Tool Wrapper Actions: Direct tool execution via
ctx.actions.run() - Platform-Agnostic Logic: Use Bazel's platform detection and toolchain system
๐ Migration Strategy
Phase 2 COMPLETE โ
- โ All 6 shell script files eliminated
- โ Complex genrules replaced with Bazel-native approaches
- โ Test scripts converted to build_test and test_suite rules
Phase 4 FINAL SUCCESS ๐ Achieved 76% Reduction: 82 โ 31 ctx.execute() calls
โ COMPLETED MODERNIZATIONS:
-
wasm_toolchain.bzl - 40 โ 17 calls (-23)
- โ All mv/cp operations โ repository_ctx.symlink()
- โ Git clone operations โ git_repository rules
- โ Cargo builds โ Hybrid git_repository + genrule approach
-
tool_cache.bzl - 22 โ 6 calls (-16)
- โ Custom cache system โ Simplified to use Bazel repository caching
- โณ Tool validation calls remain
-
tinygo_toolchain.bzl - 8 โ 3 calls (-5)
- โ uname -m โ repository_ctx.os.arch
- โ File operations โ repository_ctx.symlink()
- โณ Tool installation and validation remain
-
Simple files ELIMINATED - 7 โ 0 calls (-7)
- โ wasi_sdk_toolchain.bzl: mkdir operations eliminated
- โ cpp_component_toolchain.bzl: test/mkdir โ Bazel-native path operations
- โ diagnostics.bzl: which/test โ repository_ctx.which() and path.exists
-
Medium files IMPROVED - 5 โ 3 calls (-2)
- โ wkg_toolchain.bzl: cp โ symlink
- โ wizer_toolchain.bzl: REMOVED (wizer now part of wasmtime v39.0.0+)
REMAINING COMPLEX OPERATIONS (29 calls):
- wasm_toolchain.bzl (17): Remaining download and build operations (hybrid approach working)
- tool_cache.bzl (6): Tool validation and file existence checks
- tinygo_toolchain.bzl (3): Tool installation and validation
- Others (3): Package management and validation
Shell Operation Categories MODERNIZED:
- โ File System: All cp, mv, mkdir operations โ Bazel-native
- โ Platform Detection: uname โ repository_ctx.os properties
- โ Git Operations: git clone โ git_repository rules
- โ Source Management: git_repository + hybrid cargo builds
- โ Tool Discovery: which โ repository_ctx.which()
- โ Cache System: Custom shell cache โ Bazel repository caching
- โ Path Operations: test โ repository_ctx.path().exists
- โณ Tool Validation: --version checks (appropriate to keep)
- โณ Complex Builds: Some cargo builds (complex dependency resolution)
Phase 4 PROGRESS โก
- MAJOR SUCCESS: Modernized 4 component rule files with significant improvements
- Identified: 15 remaining
ctx.actions.run_shell()calls across 7 files
โ COMPLETED MODERNIZATIONS:
-
wit_deps_check.bzl - โ COMPLETE MODERNIZATION
- Replaced
ctx.actions.run_shell()withctx.actions.run()for tool execution - Eliminated shell command for simple tool invocation with output redirection
- Now uses
stdoutparameter for clean output capture
- Replaced
-
rust_wasm_component_bindgen.bzl - โ SIMPLIFIED
- Cleaned up complex shell string interpolation
- Replaced multi-command echo/cat sequence with cleaner approach
- Pre-generate content with
ctx.actions.write(), then simplecatcommand
-
wit_markdown.bzl - โ ENHANCED
- Improved file copying operations using
findinstead of shell globs - Pre-generate index.md content using Bazel's template system
- Cleaner separation of content generation vs file operations
- Improved file copying operations using
-
wasm_validate.bzl - โ RESTRUCTURED
- Broke down monolithic 67-line shell script into focused, single-purpose actions
- Separated validation, component inspection, and module info extraction
- Better error handling and progress reporting with distinct mnemonics
๐ REMAINING COMPLEX CASES (11 calls):
- go/defs.bzl (5): Complex TinyGo compilation pipeline with Go module resolution
- wkg/defs.bzl (1): WKG package extraction and component discovery
- wit/wit_bindgen.bzl (2): WIT binding generation with language-specific outputs
- wasm_validate.bzl (4): Multi-step validation process (partially modernized)
๐ฏ STRATEGY FOR REMAINING CASES: These remaining shell scripts are appropriate complexity for their tasks:
- Go module resolution: Requires system Go binary detection and GOPATH management
- WKG package handling: Complex archive extraction and component detection
- WIT code generation: Language-specific file discovery and copying
- WASM validation: Multi-tool validation workflow
Phase 4 Assessment: โ SUCCESSFUL MODERNIZATION
- Focused on quick wins and quality improvements
- Eliminated unnecessary shell complexity where possible
- Left appropriate complexity in place for legitimate use cases
- All builds still working - no regressions introduced
Target State: Bazel-native, cross-platform implementation
- Zero shell script files โ
- Minimal single-command genrules only โ
- Platform-agnostic toolchain setup โ (major progress)
- Direct tool execution without shell wrappers โ (file operations modernized)
WIZER INTEGRATION STATUS
โ MIGRATION COMPLETE: Wasmtime v39.0.0+ Integration
As of November 2025, Wizer has been merged into Wasmtime and is available as the wasmtime wizer subcommand.
This eliminates the need for a standalone wizer toolchain and simplifies dependency management.
Architecture
The wizer pre-initialization workflow now uses wasmtime's built-in wizer subcommand:
-
wasm_component_wizer Rule (
//wasm:wasm_component_wizer.bzl)- Uses
wasmtime_toolchain_typeinstead of standalone wizer - Invokes
wasmtime wizersubcommand - Default init function:
wizer-initialize(breaking change fromwizer.initialize)
- Uses
-
wasm_component_wizer_library Rule (
//wasm:wasm_component_wizer_library.bzl)- Library-based pre-initialization support
- Uses wasmtime toolchain for wizer functionality
-
Working Example (
//examples/wizer_example)- Demonstrates pre-initialization with wasmtime wizer
- Uses
#[export_name = "wizer-initialize"](new naming convention)
Breaking Changes (Issue #246)
- Init function name changed:
wizer.initializeโwizer-initialize - Standalone wizer toolchain removed (use wasmtime toolchain)
//tools/wizer_initializerremoved (no longer needed)checksums/tools/wizer.jsonremoved
Migration Guide
Before (standalone wizer):
#[export_name = "wizer.initialize"]
pub extern "C" fn init() { ... }
After (wasmtime wizer):
#[export_name = "wizer-initialize"]
pub extern "C" fn init() { ... }
๐ Implementation Guidelines
Instead of Shell Scripts:
# โ BAD: Complex shell in genrule
genrule(
cmd = """
if [ -f input.txt ]; then
grep "pattern" input.txt > $@
else
echo "No input" > $@
fi
""",
)
# โ
GOOD: Simple tool invocation
genrule(
cmd = "$(location //tools:processor) $(location input.txt) > $@",
tools = ["//tools:processor"],
)
Instead of ctx.execute():
# โ BAD: Shell command execution
ctx.execute(["bash", "-c", "git clone ... && cd ... && make"])
# โ
GOOD: Use Bazel's http_archive or repository rules
http_archive(
name = "external_tool",
urls = ["https://github.com/tool/releases/download/v1.0.0/tool.tar.gz"],
build_file = "@//tools:BUILD.external_tool",
)
Bazel-First Approach
- Testing: Use
genrule, built-in test rules, validation markers - Validation: Direct toolchain binary invocation
- Cross-platform: Bazel's platform constraint system
- Build reproducibility: No external shell dependencies
Cross-Platform Compatibility Requirements
- Windows Support: All builds must work on Windows without WSL
- Tool Availability: Don't assume Unix tools (
git,make,bash) - Path Handling: Use Bazel's path utilities
- Platform Detection: Use
@platforms//constraints, notuname
Dependency Management Patterns
๐ฏ RULE #2: STRATIFIED HYBRID APPROACH
Use the RIGHT download pattern for each dependency category
This project uses a stratified hybrid approach to dependency management, selecting the most appropriate mechanism based on the characteristics of each dependency type.
Decision Matrix
| Dependency Type | Pattern | Location | Why |
|---|---|---|---|
| Multi-platform GitHub binaries | JSON Registry + secure_download | checksums/tools/*.json | Solves platform ร version matrix, central security auditing |
| Bazel Central Registry deps | bazel_dep | MODULE.bazel | Ecosystem standard, automatic dependency resolution |
| Source builds | git_repository | wasm_tools_repositories.bzl | Bazel standard, maximum flexibility |
| Universal WASM binaries | JSON Registry (preferred) or http_file | checksums/tools/*.json or MODULE.bazel | Platform-independent, security auditable |
| NPM packages | Hermetic npm + package.json | `toolchains/jco_toolchain.bzl$ | \text{Ecosystem} \text{standard}, \text{package} \text{lock} \text{files} |
\text{Pattern} 1: \text{JSON} \text{Registry} (\text{Multi}-\text{Platform} \text{GitHub} \text{Binaries})
\text{Use} \text{for}: \text{Tools} \text{with} \text{different} \text{binaries} \text{per} \text{platform} (\text{wasm}-\text{tools}, \text{wit}-\text{bindgen}, \text{wac}, \text{wkg}, \text{wasmtime}, \text{wizer}, \text{wasi}-\text{sdk}, \text{nodejs}, \text{tinygo})
\text{Why}: \text{Elegantly} \text{handles} \text{the} \text{combinatorial} \text{explosion} \text{of} (\text{platforms} \times \text{versions} \times \text{URL} \text{patterns})
\text{Structure}: $``json { "tool_name": "wasm-tools", "github_repo": "bytecodealliance/wasm-tools", "latest_version": "1.240.0", "supported_platforms": ["darwin_amd64", "darwin_arm64", "linux_amd64", "linux_arm64", "windows_amd64"], "versions": { "1.240.0": { "release_date": "2025-10-08", "platforms": { "darwin_arm64": { "sha256": "8959eb9f494af13868af9e13e74e4fa0fa6c9306b492a9ce80f0e576eb10c0c6", "url_suffix": "aarch64-macos.tar.gz" } // ... other platforms } } } }
**Usage**:
```python
# In toolchain .bzl file
from toolchains.secure_download import secure_download_tool
secure_download_tool(ctx, "wasm-tools", "1.240.0", platform)
Benefits:
- โ Single source of truth for all versions and checksums
- โ
Central security auditing (
checksums/directory) - โ Supports multiple versions side-by-side
- โ Platform detection and URL construction automatic
- โ
Clean API via
registry.bzl
Pattern 2: Bazel Central Registry (bazel_dep)
Use for: Standard Bazel ecosystem dependencies (rules_rust, bazel_skylib, platforms, rules_cc, etc.)
Why: Bazel's standard mechanism with automatic dependency resolution
Structure:
# MODULE.bazel
bazel_dep(name = "rules_rust", version = "0.65.0")
bazel_dep(name = "bazel_skylib", version = "1.8.1")
bazel_dep(name = "platforms", version = "1.0.0")
Benefits:
- โ Ecosystem standard - no learning curve
- โ Automatic transitive dependency resolution
- โ Maintained by Bazel team
- โ Built-in security and version compatibility
Do NOT:
- โ Duplicate BCR deps in JSON registry
- โ Use http_archive for tools available in BCR
Pattern 3: Git Repository (Source Builds)
Use for: Custom forks, bleeding edge versions, or when source builds are required
Why: Bazel-native source repository management
Structure:
# wasm_tools_repositories.bzl
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "wasm_tools_src",
remote = "https://github.com/bytecodealliance/wasm-tools.git",
tag = "v1.235.0",
build_file = "//toolchains:BUILD.wasm_tools",
)
When to use:
- Custom fork with patches
- Need bleeding edge from main branch
- Binary not available for your platform
- Building from source is required for licensing
Prefer download over build: When prebuilt binaries are available and work correctly, use Pattern 1 (JSON Registry) instead for faster, more hermetic builds.
Pattern 4: Universal WASM Binaries
Use for: WebAssembly components (platform-independent .wasm files)
Preferred: JSON Registry for consistency and security auditing
// checksums/tools/file-ops-component.json
{
"tool_name": "file-ops-component",
"github_repo": "pulseengine/bazel-file-ops-component",
"latest_version": "0.1.0-rc.3",
"supported_platforms": ["wasm"], // Universal
"versions": {
"0.1.0-rc.3": {
"release_date": "2025-10-15",
"platforms": {
"wasm": {
"sha256": "8a9b1aa8a2c9d3dc36f1724ccbf24a48c473808d9017b059c84afddc55743f1e",
"url": "https://github.com/.../file_ops_component.wasm"
}
}
}
}
}
Alternative: http_file for very simple cases (legacy)
# MODULE.bazel (only for simple cases)
http_file(
name = "component_external",
url = "https://github.com/.../component.wasm",
sha256 = "abc123...",
downloaded_file_path = "component.wasm",
)
Recommendation: Migrate all WASM components to JSON Registry for:
- Consistent security auditing
- Version management
- Same tooling as other downloads
Pattern 5: NPM Packages
Use for: Node.js ecosystem tools (jco, componentize-js)
Why: npm is the standard package manager with lock file support
Structure:
# Download hermetic Node.js first (Pattern 1)
secure_download_tool(ctx, "nodejs", "20.18.0", platform)
# Use hermetic npm for package installation
ctx.execute([npm_path, "install", "@bytecodealliance/jco@1.4.0"])
Benefits:
- โ Hermetic builds (no system Node.js dependency)
- โ Package lock files for reproducibility
- โ Ecosystem standard
Adding New Dependencies
Decision Tree:
-
Is it in Bazel Central Registry?
- YES โ Use
bazel_dep(Pattern 2) - NO โ Continue to step 2
- YES โ Use
-
Is it a GitHub release with platform-specific binaries?
- YES โ Create JSON in
checksums/tools/(Pattern 1) - NO โ Continue to step 3
- YES โ Create JSON in
-
Is it a universal WASM component?
- YES โ Create JSON in
checksums/tools/with platform "wasm" (Pattern 4) - NO โ Continue to step 4
- YES โ Create JSON in
-
Is it an NPM package?
- YES โ Use hermetic npm installation (Pattern 5)
- NO โ Continue to step 5
-
Must it be built from source?
- YES โ Use
git_repository(Pattern 3) - NO โ Reconsider if this dependency is needed
- YES โ Use
Security Best Practices
- Always verify checksums: All downloads MUST have SHA256 verification
- Central audit trail: Prefer JSON registry for auditability
- Version pinning: Always specify exact versions, never use "latest"
- Minimal versions: Keep only latest stable + previous stable in JSON files
- Review changes: All checksum changes require careful PR review
Maintenance Guidelines
Adding a new version to JSON registry:
# 1. Download binaries for all platforms
# 2. Calculate SHA256 checksums
shasum -a 256 wasm-tools-1.241.0-*.tar.gz
# 3. Add version block to JSON file
# 4. Update "latest_version" if appropriate
# 5. Remove old versions if keeping only latest + previous
Updating a BCR dependency:
# Simply change version in MODULE.bazel
bazel_dep(name = "rules_rust", version = "0.66.0") # Updated
Removing old versions:
- Keep latest stable version
- Keep previous stable version (for rollback capability)
- Remove all older versions
- Update tests if they pin to old versions
Anti-Patterns to Avoid
โ DO NOT create custom download mechanisms โ DO NOT hardcode URLs in .bzl files โ DO NOT duplicate BCR dependencies in JSON registry โ DO NOT use http_archive for multi-platform binaries (use JSON registry) โ DO NOT keep more than 2 versions per tool without strong justification โ DO NOT use "strategy options" - pick ONE best approach per tool
Current State
Toolchains Implemented
- โ TinyGo v0.39.0 with WASI Preview 2 support
- โ Rust WebAssembly components
- โ C++ components with WASI SDK
- โ JavaScript/TypeScript components with ComponentizeJS (jco)
- โ Wizer pre-initialization support
- โ Javy is NOT supported (see decision below)
JavaScript Tooling Decision: jco vs Javy
Decision: Use jco/ComponentizeJS exclusively. Do NOT add Javy support.
Rationale:
- jco provides full WebAssembly Component Model support; Javy does not
- jco uses WASI 0.2 (current standard); Javy uses WASI 0.1 (legacy)
- jco enables interoperability with other components (Rust, Go, C++); Javy cannot
- jco supports TypeScript, NPM ecosystem, modern JS features
- Size trade-off (~8MB vs 1-16KB) is acceptable for Component Model benefits
Javy is unsuitable because rules_wasm_component is fundamentally about WebAssembly Components, and Javy only produces WASI modules without Component Model support.
Performance Optimizations
- โ Wizer pre-initialization (1.35-6x startup improvement)
- โ Platform constraint validation
- โ Cross-platform toolchain resolution
- โ Build caching and parallelization
Documentation Status
- โ All READMEs updated with current implementation
- โ Multi-language support documented
- โ Production-ready examples provided
Bazel Version Compatibility
Supported Versions
| Bazel Version | Status | Notes |
|---|---|---|
| 7.x | โ Supported | Tested in CI |
| 8.x | โ Supported (Primary) | Current development target, tested in CI |
| 9.x | โ Supported | Tested in CI, benefits from performance improvements |
Bazel 9 Specific Features
The following Bazel 9 features are available but not required (graceful fallback for 7/8):
-
Memory Efficiency (automatic)
- 20% retained heap reduction
- 30% faster remote cache builds
- 80% faster tree artifact sandbox extraction
-
module_ctx.facts(future enhancement)- Can be used to cache checksum lookups in extension
- Would reduce network calls after first resolution
-
Native
setData Type (future enhancement)- Could simplify duplicate detection in file processing
- Requires version checking for fallback
Version Detection Pattern
For Bazel 9+ specific features with fallback:
# Check Bazel version for feature availability
def _is_bazel_9_or_later():
"""Check if running on Bazel 9 or later."""
version = native.bazel_version
if not version:
return False
parts = version.split(".")
return int(parts[0]) >= 9
# Usage with fallback
def deduplicate_files(files):
if _is_bazel_9_or_later():
# Use native set() in Bazel 9+
return list(set(files))
else:
# Fallback for Bazel 7/8
seen = {}
result = []
for f in files:
if f not in seen:
seen[f] = True
result.append(f)
return result
CI Matrix
The CI workflow tests across:
- Ubuntu (Linux x86_64) - Bazel 8.x
- macOS (ARM64) - Bazel 8.x
- Windows (x86_64) - Bazel 8.x (experimental)
- BCR Docker environment - Latest Bazel