MANDATORY: Act as principal-level engineer. Follow these guidelines exactly.
NEVER use rm -rf with glob patterns matching hidden files
- FORBIDDEN FOREVER:
rm -rf * .*- Deletes .git directory, destroys repository - FORBIDDEN FOREVER: Any variant that expands to hidden files without explicit safeguards
- Safe alternatives:
git clean -fdx(respects .git)rm -rf build/ node_modules/(explicit directories)find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} +
Why this rule exists: On 2026-03-09, rm -rf * .* destroyed the repository during history squashing, deleting .git and all local branches. Recovery required reinitializing from remote.
Recovery if this happens:
git init- Reinitialize repositorygit remote add origin <url>- Re-add remotegit fetch origin- Fetch remote branchesgit checkout -b main origin/main- Restore main branch- All local-only branches are permanently lost
- Before ANY structural refactor on a file >300 LOC: remove dead code, unused exports, unused imports first — commit that cleanup separately before the real work
- Multi-file changes: break into phases (≤5 files each), verify each phase before the next
- When pointed to existing code as a reference: study it before building — working code is a better spec than any description
- Work from raw error data, not theories — if a bug report has no error output, ask for it
- On "yes", "do it", or "go": execute immediately, no plan recap
Before claiming any task is complete:
- Run the actual command — execute the script, run the test, check the output
- State what you verified, not just "looks good"
- FORBIDDEN: Claiming "Done" when output shows failures, or characterizing incomplete/broken work as complete
- Re-read every file modified; confirm nothing references something that no longer exists
- After 10+ messages: re-read any file before editing it — do not trust remembered contents
- Read files >500 LOC in chunks using offset/limit; never assume one read captured the whole file
- Before every edit: re-read the file. After every edit: re-read to confirm the change applied correctly
- When renaming anything, search separately for: direct calls, type references, string literals, dynamic imports, re-exports, test files — one grep is not enough
- Tool results over 50K characters are silently truncated — if search returns suspiciously few results, narrow scope and re-run
- For tasks touching >5 files: use sub-agents with worktree isolation to prevent context decay
- Before calling anything done: present two views — what a perfectionist would reject vs. what a pragmatist would ship
- After fixing a bug: explain why it happened
- If a fix doesn't work after two attempts: stop, re-read the relevant section top-down, state where the mental model was wrong, propose something fundamentally different
- If asked to "step back" or "going in circles": drop everything, rethink from scratch
- If the user's request is based on a misconception, say so before executing
- If you spot a bug adjacent to what was asked, flag it: "I also noticed X — want me to fix it?"
- Do not add features, refactor, or make improvements beyond what was asked
- Try the simplest approach first; flag architectural issues and wait for approval before restructuring
- When asked to "make a plan," output only the plan — no code until given the go-ahead
- If a file is getting unwieldy (>400 LOC): flag it — "this is big enough to cause pain — want me to split it?"
- NEVER claim done with something 80% complete — finish 100% before reporting
- When a multi-step change doesn't immediately show gains, commit and keep iterating — don't revert
- If one approach fails, fix forward: analyze why, adjust, rebuild, re-measure — not
git checkout - After EVERY code change: build, test, verify, commit. This is a single atomic unit
- Reverting is a last resort after exhausting forward fixes — and requires explicit user approval
The file system is working memory. Use it actively:
- Write intermediate results and analysis to files in
.claude/ - For large data operations, save to disk and use bash tools to process
- Use
.claude/for plans, status tracking, and cross-session context - When debugging, save logs and outputs to files for reproducible verification
- Don't hold large analysis in context — write it down, reference it later
- After ANY correction from the user: log the pattern to memory so the same mistake is never repeated
- Convert mistakes into strict rules — don't just note them, enforce them
- After fixing a bug: explain why it happened and whether anything prevents that category of bug in the future
- Fix ALL issues when asked - Never dismiss as "pre-existing" or "not caused by my changes"
- When asked to fix, lint, or check: fix everything found, regardless of who introduced it
NEVER create documentation files unless explicitly requested
- FORBIDDEN: Creating README.md, GUIDE.md, HOWTO.md, ARCHITECTURE.md, or docs/ directories
- Code is documentation - Write self-documenting code with minimal comments
- Only exceptions: Package README.md (1-2 sentences), CLAUDE.md, .claude/ directory
- If user asks for documentation: Ask whether they want it in CLAUDE.md or standalone
- NO BACKWARD COMPATIBILITY - FORBIDDEN to maintain it; we're our only consumers
- Active removal: MUST remove existing backward compatibility code when encountered
- Clean breaks: Make clean API changes without deprecation paths
- Forbidden patterns:
- ❌ Renaming unused
_varsinstead of deleting - ❌ Re-exporting types for "compatibility"
- ❌ Adding
// removedcomments - ❌ Environment variables for legacy behavior
- ✅ Just delete unused code completely
- ❌ Renaming unused
- 🚨 NEVER use
npx,pnpm dlx, oryarn dlx— usepnpm exec <package>for devDep binaries, orpnpm run <script>for package.json scripts. If a tool is needed, add it as a pinned devDependency first.
Default to NO comments. Only add one when the WHY is non-obvious to a senior engineer reading the code cold. The code itself is the documentation.
NEVER change shell: WIN32 to shell: true
shell: WIN32enables shell on Windows (needed) and disables on Unix (not needed)- If spawn fails with ENOENT, fix by separating command and arguments:
// WRONG - passing full command as string
spawn('python3 -m module arg1 arg2', [], { shell: WIN32 })
// CORRECT - separate command and args
spawn('python3', ['-m', 'module', 'arg1', 'arg2'], { shell: WIN32 })ALWAYS use eslint-disable-next-line above the line, NEVER trailing eslint-disable-line
// WRONG - trailing comment
process.exit(1) // eslint-disable-line n/no-process-exit
// CORRECT - line above
// eslint-disable-next-line n/no-process-exit
process.exit(1)Cherry-pick fs, default import path/os/url/crypto, prefer @socketsecurity/lib over child_process
- HTTP Requests: NEVER use
fetch()— usehttpJson/httpText/httpRequestfrom@socketsecurity/lib/http-request
// CORRECT
import { existsSync, readFileSync, promises as fs } from 'node:fs'
import { spawn } from '@socketsecurity/lib/spawn'
import path from 'node:path'
import os from 'node:os'
import { fileURLToPath } from 'node:url' // Exception: cherry-pick from url
// WRONG - using node:child_process directly (except in additions/)
import { execSync } from 'node:child_process'
// WRONG - default import for fs
import fs from 'node:fs'
// WRONG - cherry-picking from path/os
import { join, resolve } from 'node:path'ALWAYS use fileURLToPath + path.resolve, NEVER use endsWith() or raw URL comparison
// CORRECT - works cross-platform (Windows backslashes vs forward slashes)
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const isMainModule = fileURLToPath(import.meta.url) === path.resolve(process.argv[1])
// WRONG - fails on Windows (backslashes vs forward slashes)
const isMainModule = import.meta.url === `file://${process.argv[1]}`
// WRONG - causes race conditions when build scripts import each other
const isMainModule = process.argv[1]?.endsWith('build.mjs')ALWAYS pass libc parameter for Linux platform operations
- Missing libc causes builds to output to wrong directories
- Prefer
getCurrentPlatformArch()which auto-detects libc
// BEST - auto-detects libc
import { getCurrentPlatformArch } from 'build-infra/lib/platform-mappings'
const platformArch = await getCurrentPlatformArch()
// CORRECT - explicit libc
import { getAssetPlatformArch, isMusl } from 'build-infra/lib/platform-mappings'
const libc = process.platform === 'linux' ? ((await isMusl()) ? 'musl' : undefined) : undefined
const platformArch = getAssetPlatformArch(process.platform, arch, libc)ALWAYS use @socketsecurity/lib/logger instead of console.* methods
- NEVER add emoji/symbols manually - logger provides them automatically
- Exception:
additions/directory
// CORRECT
import { getDefaultLogger } from '@socketsecurity/lib/logger'
const logger = getDefaultLogger()
logger.success('Build completed') // Already adds ✓
// WRONG
console.log('Starting build')
logger.info('✓ Build completed') // Don't add symbols manuallyNEVER use dynamic imports (await import())
// WRONG
const logger = (await import('@socketsecurity/lib/logger')).getDefaultLogger()
// CORRECT
import { getDefaultLogger } from '@socketsecurity/lib/logger'Prefer Promise.allSettled over Promise.all for independent operations
Promise.allSettledcontinues on rejection, providing complete results- Use
Promise.allonly when ALL operations must succeed atomically
// PREFERRED - shows ALL failures
const results = await Promise.allSettled([downloadFile('a'), downloadFile('b')])
const failures = results.filter(r => r.status === 'rejected')
// OK - atomic operations
await Promise.all([fs.copyFile(src1, dst1), fs.copyFile(src2, dst2)])NEVER use null except for __proto__: null or external API requirements
// WRONG
let result = null
const libc = isMusl() ? 'musl' : null
// CORRECT
let result
const libc = isMusl() ? 'musl' : undefinedALWAYS use { __proto__: null, ... } for config objects, return objects, and internal state
// CORRECT - prototype-less objects
const config = {
__proto__: null,
host: options.host ?? 'localhost',
};
// CORRECT - spread options into prototype-less object
function foo(options) {
const opts = { __proto__: null, ...options };
}
// WRONG - inherits from Object.prototype
const config = { host: 'localhost' };Code embedded into Node.js during early bootstrap. Special constraints apply.
- No third-party packages - Only built-in modules
- Use
require('fs')notrequire('node:fs')- node: protocol unavailable at bootstrap - NEVER import from
@socketsecurity/*packages
// WRONG
const { safeDeleteSync } = require('@socketsecurity/lib/fs')
// CORRECT
const { rmSync } = require('fs')ALWAYS start all .js files in additions/ with 'use strict';
// CORRECT - first line of file
'use strict';
/**
* Module description
*/
const { ... } = primordials;
// WRONG - missing strict mode directive
/**
* Module description
*/
const { ... } = primordials;All node:smol-* modules REQUIRE the node: prefix
Modules with hyphens in their names cannot be imported without the node: protocol. This is intentional - it prevents confusion with npm packages and follows Node.js conventions for newer built-in modules (like node:test, node:sea).
Enforcement: The node: prefix requirement is enforced in Node.js internals via the schemelessBlockList in lib/internal/bootstrap/realm.js. Our patch (003-realm-smol-bindings.patch) adds all smol modules to this list, ensuring require('smol-http') fails with MODULE_NOT_FOUND while require('node:smol-http') works.
// CORRECT
import { parse } from 'node:smol-purl';
import { serve } from 'node:smol-http';
const versions = require('node:smol-versions');
// WRONG - will fail with MODULE_NOT_FOUND
import { parse } from 'smol-purl';
const versions = require('smol-versions');Available modules:
node:smol-http- High-performance HTTP servernode:smol-https- HTTPS server (wraps smol-http with TLS)node:smol-purl- Package URL (PURL) parsernode:smol-versions- Semver parsing and comparisonnode:smol-manifest- Lockfile parsing (pnpm, npm, yarn)node:smol-ilp- InfluxDB Line Protocol clientnode:smol-sql- Unified SQL API (PostgreSQL, SQLite)node:smol-vfs- SEA Virtual Filesystem access
ALWAYS use primordials for Map/Set operations in internal modules
const {
BigInt: BigIntCtor, // Use *Ctor suffix for constructors shadowing globals
Error: ErrorCtor,
MapPrototypeDelete,
MapPrototypeGet,
MapPrototypeSet,
SafeMap,
ObjectKeys,
} = primordials;
const cache = new SafeMap();
MapPrototypeSet(cache, key, value);
const entry = MapPrototypeGet(cache, key);
// .size is safe on SafeMap/SafeSet (no primordial needed)
if (cache.size > maxSize) { /* ... */ }Common primordials:
- Constructors:
SafeMap,SafeSet,SafeWeakMap - Map:
MapPrototypeGet,MapPrototypeSet,MapPrototypeDelete,MapPrototypeHas,MapPrototypeClear - Set:
SetPrototypeAdd,SetPrototypeDelete,SetPrototypeHas ArrayFrom- safe Array.from()
ALWAYS use ObjectKeys() + indexed for-loop
// CORRECT - faster
const keys = ObjectKeys(obj);
for (let i = 0, len = keys.length; i < len; i++) {
const key = keys[i];
const value = obj[key];
}
// WRONG - slower due to extra function call per iteration
for (const key in obj) {
if (ObjectPrototypeHasOwnProperty(obj, key)) { /* ... */ }
}NEVER use C++ exceptions - Node.js compiled with -fno-exceptions
// WRONG - throws exception
void EnsureCapacity(size_t needed) {
if (needed > max_size_) throw std::runtime_error("overflow");
}
// CORRECT - use status flag
void EnsureCapacity(size_t needed) {
if (needed > max_size_) { overflow_ = true; return; }
}ALWAYS use full socketsecurity/... paths
// CORRECT
#include "socketsecurity/http/http_fast_response.h"
// WRONG - breaks builds
#include "http_fast_response.h"env.h vs env-inl.h: If .cc file uses Environment* methods, include env-inl.h (provides inline definitions).
Use flat files, NEVER directories with index.js
Follow Node.js upstream conventions - internal modules use flat .js files, not directories.
// CORRECT - flat file like Node.js upstream
additions/source-patched/lib/internal/socketsecurity/purl.js
additions/source-patched/lib/internal/socketsecurity/versions.js
additions/source-patched/lib/internal/socketsecurity/manifest.js
// WRONG - directories with index.js (not how Node.js internals work)
additions/source-patched/lib/internal/socketsecurity/purl/index.js
additions/source-patched/lib/internal/socketsecurity/versions/index.jsOnly use directories when the module has multiple files (like http/ with multiple helpers).
NEVER require internalBinding from 'internal/bootstrap/realm' - already in scope
// WRONG - causes duplicate declaration error
const { internalBinding } = require('internal/bootstrap/realm')
// CORRECT - already in scope
const binding = internalBinding('my_binding')Patches modify upstream source code during builds:
- Node.js:
packages/node-smol-builder/patches/source-patched/*.patch - iocraft:
packages/iocraft-builder/patches/*.patch
ALWAYS use standard unified diff format with --- a/ and +++ b/ prefixes
NEVER use git format-patch output (includes From, Subject, Date headers)
# CORRECT - standard unified diff with a/ and b/ prefixes
# @node-versions: v25.5.0
# @description: Brief description of what the patch does
#
--- a/src/node_http_parser.cc
+++ b/src/node_http_parser.cc
@@ -81,7 +81,7 @@ const uint32_t kOnTimeout = 6;
// Any more fields than this will be flushed into JS
-const size_t kMaxHeaderFieldsCount = 32;
+const size_t kMaxHeaderFieldsCount = 128;# FORBIDDEN - git format-patch output
From 676c29ac0ca562f3f36203c9d67410391cbc6741 Mon Sep 17 00:00:00 2001 ← FORBIDDEN
From: jdalton <john.david.dalton@gmail.com> ← FORBIDDEN
Date: Sun, 22 Mar 2026 15:58:12 -0400 ← FORBIDDEN
Subject: [PATCH] feat: add feature ← FORBIDDEN
diff --git a/file.rs b/file.rs ← FORBIDDEN
index ea08e2e..ba655a3 100644 ← FORBIDDEN
--- a/file.rsRequired header format:
# @node-versions: v25.5.0 (for node-smol patches)
# @iocraft-versions: v0.9.4 (for iocraft patches)
# @description: One-line summary
#
# Optional multi-line explanation
# of what the patch does
#- Minimal touch: Only modify minimum lines needed
- Clean diffs: Avoid shifting line numbers unnecessarily
- No style changes: Don't reformat code outside patch scope
- Surgical edits: Add new code at function ends when possible
Each patch affects exactly ONE file and does NOT depend on other patches.
To regenerate Node.js patches:
# Use the regenerating-node-patches skill
/regenerating-node-patchesTo regenerate iocraft patches:
# Use the regenerating-node-patches skill (supports both now)
/regenerating-node-patchesManual patch generation (if needed):
- Get pristine file from upstream (
upstream/nodeorupstream/iocraft) - Apply changes to that single file
- Generate:
diff -u a/original b/modified > patch_file - Add required headers (
@node-versionsor@iocraft-versions,@description) - Validate:
patch --dry-run < patch_file
Common mistakes:
- ❌ Using
git format-patchoutput (includes From/Date/Subject headers) - ❌ Missing
a/andb/prefixes in---and+++lines - ❌ Missing version header (
@node-versionsor@iocraft-versions) - ✅ Use
diff -u a/file b/fileformat with proper headers
/security-scan— runs AgentShield + zizmor security audit/quality-scan— comprehensive code quality analysis/quality-loop— scan and fix iteratively- Agents:
code-reviewer,security-reviewer,refactor-cleaner(in.claude/agents/) - Shared subskills in
.claude/skills/_shared/ - Pipeline state tracked in
.claude/ops/queue.yaml
ALWAYS use pnpm run build, NEVER invoke Makefiles directly
# CORRECT
pnpm --filter stubs-builder build
# WRONG - bypasses dependency downloads
make -f Makefile.macos allBuild scripts handle downloading prebuilt dependencies (curl, LIEF) from releases.
ALWAYS run clean before rebuilding
# CORRECT
pnpm --filter node-smol-builder clean
pnpm --filter node-smol-builder build
# WRONG - may use stale checkpoint
pnpm run buildNEVER manually delete checkpoint files - the clean script knows all locations.
Source packages are canonical - additions syncs FROM packages
binject,bin-infra,build-infraare the source of truth- ALL work in source packages, then sync to
additions/ - NEVER make changes only in additions - they will be overwritten
# CORRECT - edit source package first, then sync
vim packages/build-infra/src/socketsecurity/build-infra/debug_common.h
cp packages/build-infra/src/... additions/.../
# WRONG - editing additions directly
vim packages/node-smol-builder/additions/.../debug_common.hWhen modifying source, bump .github/cache-versions.json for all dependents
| Changed | Bump |
|---|---|
| build-infra/src/socketsecurity/build-infra/ | stubs, binflate, binject, binpress, node-smol |
| bin-infra/src/socketsecurity/bin-infra/ | stubs, binflate, binject, binpress, node-smol |
| binject/src/socketsecurity/binject/ | binject, node-smol |
| stubs-builder/src/ | stubs, binpress, node-smol |
| binpress/src/ | binpress, node-smol |
| binflate/src/ | binflate |
NEVER write source-code-scanning tests (reading .js files and checking .toContain())
Source-scanning tests are brittle — they break on any refactor, rename, or comment change. Write functional tests that verify behavior, not string patterns in source files.
- For modules requiring the built binary (node:smol-http, node:smol-vfs): use integration tests that run inside the SEA binary
- For pure logic: use unit tests with real function calls
- For build output: verify the binary works, not that source contains specific strings
// WRONG — brittle source scanning
const sourceCode = readFileSync('additions/.../server.js', 'utf8')
expect(sourceCode).toContain('reusePort = false')
// CORRECT — functional test in SEA binary
const server = serve({ port: 0, reusePort: true, fetch() {} })
expect(server.reusePort).toBe(true)
expect(server.port).toBeGreaterThan(0)ALWAYS use Final binary, NEVER intermediate stages
// CORRECT
import { getLatestFinalBinary } from '../paths.mjs'
// WRONG
import { getLatestCompressedBinary } from '../paths.mjs'ALWAYS use npm registry directly, NEVER CDNs
# CORRECT
npm pack package-name --pack-destination /tmp
curl -sL "https://registry.npmjs.org/package-name/-/package-name-1.0.0.tgz"
# WRONG
curl -sL "https://unpkg.com/package-name@1.0.0/..."ALWAYS use Edit tool, NEVER sed/awk for code modifications
- Mach-O: macOS/iOS executable format
- ELF: Linux executable format
- PE: Windows executable format
- Checkpoint: Cached snapshot of build progress (e.g., "source-copied", "compiled")
- Progressive Build: Saves checkpoints after each stage for incremental builds
- Cache Version: Version in
.github/cache-versions.jsonthat invalidates CI caches - Upstream: Original Node.js source from nodejs/node before patches
- SEA (Single Executable Application): Standalone executable with Node.js runtime + app code
- VFS (Virtual File System): Filesystem embedded inside a binary
- Additions Directory: Code embedded into Node.js during build
- Source Patches: Unified diff files modifying Node.js source
- Binary Injection: Inserting data into compiled binary without recompilation
- Section: Named region in executable (
.textfor code,.datafor data) - LIEF: Library for reading/modifying executable formats
- Segment: Container for sections in Mach-O binaries
- LZFSE: Apple's compression (fast decompression, good ratio)
- UPX: Classic executable packer (aggressive compression)
- Self-extracting Binary: Compressed executable that decompresses at runtime
- Stub Binary: Small executable that decompresses and runs main binary
- libc/glibc: Standard C library on most Linux distributions
- musl: Lightweight C library used by Alpine Linux
- Universal Binary: macOS binary with ARM64 + x64 code
- Cross-compilation: Building binaries for different platform
- binject: Injects data into binaries (SEA resources, VFS archives)
- binpress: Compresses binaries using LZFSE/UPX
- binflate: Decompresses binaries compressed by binpress
- stubs-builder: Builds self-extracting stub binaries
- node-smol-builder: Builds custom Node.js binary with Socket patches
- pnpm: Fast package manager for monorepo management
- CMake: Cross-platform build system generator
- Husky: Git hooks manager
- Codex is for advice and critical assessment ONLY — never for making code changes
- Ask Codex to: analyze, diagnose, review, estimate, suggest approaches
- Do NOT ask Codex to: implement, write, modify, add code to files
- Implement changes yourself after understanding Codex's advice
- Proactive consultation: Before diving deep into a complex optimization (>30min estimated), consult Codex for critical analysis first — this catches fundamental design flaws early
- Bounce ideas: Use Codex as a sounding board when stuck — describe the problem, what you've tried, and ask for honest assessment of whether the approach is worth continuing
See ../socket-registry/CLAUDE.md for testing, code style, and CI patterns.