MANDATORY: Act as principal-level engineer. Follow these guidelines exactly.
- Identify users by git credentials: Extract name from git commit author, GitHub account, or context
- 🚨 When identity is verified: ALWAYS use their actual name - NEVER use "the user" or "user"
- Direct communication: Use "you/your" when speaking directly to the verified user
- Discussing their work: Use their actual name when referencing their commits/contributions
- Example: If git shows "John-David Dalton jdalton@example.com", refer to them as "John-David"
- Other contributors: Use their actual names from commit history/context
MANDATORY: Review CLAUDE.md before any action. No exceptions.
- 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
MANDATORY: 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 any test output shows failures, or characterizing incomplete/broken work as complete
- If type-check or lint is configured, run it and fix ALL errors before reporting done
- 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
- Never fix a display/rendering problem by duplicating state — one source of truth, everything reads from it
- 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?"
- You are a collaborator, not just an executor
- Do not add features, refactor, or make improvements beyond what was asked — band-aids when asked for band-aids
- Try the simplest approach first; if architecture is actually flawed, flag it and wait for approval before restructuring
- When asked to "make a plan," output only the plan — no code until given the go-ahead
- Before calling anything done: present two views — what a perfectionist would reject vs. what a pragmatist would ship — let the user decide
- After fixing a bug: explain why it happened and what category of bug it represents
- 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 "we're going in circles": drop everything, rethink from scratch
- Before risky changes: offer to checkpoint — "want me to commit before this?"
- If a file is getting unwieldy (>400 LOC): flag it — "this is big enough to cause pain — want me to split it?"
- Never create files unless necessary
- Always prefer editing existing files
- Forbidden to create docs unless requested
- Required to do exactly what was asked
- 🚨 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.
Principal Software Engineer: production code, architecture, reliability, ownership.
If user repeats instruction 2+ times, ask: "Should I add this to CLAUDE.md?"
Terminal Symbols (based on @socketsecurity/lib/logger LOG_SYMBOLS):
- ✓ Success/checkmark - MUST be green (NOT ✅)
- ✗ Error/failure - MUST be red (NOT ❌)
- ⚠ Warning/caution - MUST be yellow (NOT
⚠️ ) - ℹ Info - MUST be blue (NOT ℹ️)
- → Step/progress - MUST be cyan (NOT ➜ or ▶)
Color: Apply color to icon ONLY using yoctocolors-cjs (NOT ESM yoctocolors):
import colors from 'yoctocolors-cjs'
;`${colors.green('✓')} ${msg}` // Success
`${colors.red('✗')} ${msg}` // Error
`${colors.yellow('⚠')} ${msg}` // Warning
`${colors.blue('ℹ')} ${msg}` // Info
`${colors.cyan('→')} ${msg}` // Step/ProgressUse emojis sparingly (📦 🚀 🎉 💡). Prefer text-based symbols for terminal compatibility.
- MANDATORY: Work on Windows + POSIX
- Paths: Always
path.join(),path.resolve(),path.sep - Temp:
os.tmpdir()+fs.mkdtemp()for unique dirs - File URLs:
fileURLToPath()fromnode:url - Never hard-code
/or\
- Minimum: Node.js 18.0.0
- FORBIDDEN ES2023+:
toReversed(),toSorted(),toSpliced(),with() - Use instead:
slice().reverse(),slice().sort()
- 🚨 FORBIDDEN to maintain — we're our only consumers
- Actively remove compat code when encountered (it's dead code)
- Make clean API breaks; never add deprecation paths or compat layers
- Just delete unused code completely
- 🚨 FORBIDDEN:
fs.rm(),fs.rmSync(),rm -rf - Use
safeDelete()/safeDeleteSync()from@socketsecurity/lib/fs - package.json scripts: use
del-cli - HTTP: NEVER
fetch()— usehttpJson/httpText/httpRequestfrom@socketsecurity/lib/http-request
- Before bulk changes: commit WIP + create backup branch
- FORBIDDEN: automated fix scripts (sed/awk/regex bulk replacements) without backup
- FORBIDDEN: multi-file modifications without backup branch
-
Pre-commit:
pnpm run fix && pnpm run check -
--no-verify: Safe for scripts/workflows/tests/docs; always run hooks for lib/packages
-
Batch commits: First with hooks, rest with
--no-verify(after fix + check) -
Messages: Conventional Commits style
<type>(<scope>): <description> Types: feat, fix, docs, style, refactor, test, chore, perf Examples: feat(auth): add JWT token validation fix(parser): resolve memory leak in tokenizer chore(release): 1.2.3 -
NO AI attribution in commit messages
- NEVER GUESS SHAs: Use
git rev-parse HEADorgit rev-parse main - Format:
@662bbcab1b7533e24ba8e3446cffd8a7e5f7617e # main(full 40-char SHA) - Why: GitHub Actions require pinned full SHAs
- Update workflow refs: 1)
cd repo && git rev-parse main, 2) Use full SHA, 3) Verify withgit show <sha>
- MANDATORY: Use
SocketDev/socket-registry/.github/workflows/ci.yml@<SHA>with full commit SHA - Reusable workflows: Centralized lint/type-check/test/coverage
- Matrix testing: Node.js 20/22/24, cross-platform
- CI script naming:
lint-ci,test-ci,type-ci(no watch/fix modes)
Full reference: .claude/skills/updating-workflows/reference.md
Command: /update-workflows
Actions and workflows reference each other by full 40-char SHA pinned to main. When any action changes, update consumers in layer order (Layer 1 -> 2a -> 2b -> 3 -> 4) via separate PRs. Each PR must merge before the next.
Key rules:
- Each layer gets its own PR — never combine layers
- NEVER type/guess SHAs — always
git fetch origin main && git rev-parse origin/mainAFTER merge - NEVER use a SHA from a PR branch — only use SHAs from main after the PR merges
- Verify SHA exists:
gh api repos/SocketDev/socket-registry/commits/<sha> --jq '.sha' - The propagation SHA is the Layer 3 merge SHA — Layer 4 and external repos all pin to it
- Don't clobber third-party SHAs when doing blanket replacements
- Directories:
test/npm/- NPM package tests,test/registry/- Registry tests - Fixtures:
test/fixtures/- Test fixtures - Utils:
test/utils/- Shared test utilities (see below) - Naming: Descriptive file/describe/test names for coverage clarity
- Coverage: MANDATORY - never decrease, always maintain/increase
- c8 ignore: Must include reason ending with period
Test helpers available in test/utils/:
setupNpmPackageTest()- NPM package test boilerplate (npm-package-helper.mts)withTempDir/withTempFile/runWithTempDir()- Temp file management (temp-file-helper.mts)itOnWindows/itOnUnix/normalizePath()- Platform-specific tests (platform-test-helpers.mts)expectString/expectFrozen/expectHasProperties()- Assertions (assertion-helpers.mts)
- All tests:
pnpm test - Specific file:
pnpm test path/to/file.test.ts - 🚨 NEVER USE
--before test paths - runs all tests - Coverage:
pnpm run cover - NPM packages:
node scripts/test-npm-packages.mjs(long-running)
NEVER write source-code-scanning tests
Do not read source files and assert on their contents (.toContain('pattern')). These tests are brittle and break on any refactor.
- Write functional tests that verify behavior, not string patterns
- For modules requiring a built binary: use integration tests
- For pure logic: use unit tests with real function calls
- Pool:
pool: 'forks',singleFork: true,maxForks: 1,isolate: true - Timeouts:
testTimeout: 60_000, hookTimeout: 60_000 - Threads:
singleThread: true, maxThreads: 1 - Cleanup:
await safeDelete(paths)from@socketsecurity/lib/fsfor all test cleanup
- Default (
.config/vitest.config.mts): threads, 10s timeout — used bypnpm test - Isolated (
.config/vitest.config.isolated.mts): forks forvi.doMock()tests - Optimized (
.config/vitest.config.optimized.mts): fast validation, pre-commit
- pnpm only (not npm)
- Add deps:
pnpm add <pkg> --save-exact(exact versions, no^/~) - Workspace: Use
-wflag for root - Scripts:
pnpm run <script> - READMEs: Use
pnpm installin examples
- After editing
package.jsondeps: runpnpm installto update lockfile - Commit
pnpm-lock.yamlwith dependency changes - Never manually edit
pnpm-lock.yaml
- Pattern: Wrap complex commands in
scripts/*.mtsfiles, not package.json directly - Benefits: Type safety, reusability, testability, better error handling
- Usage:
"script-name": "node scripts/script-name.mts" - Examples:
scripts/cover.mjs,scripts/test.mjs - Structure: Use
spawnfrom node:child_process with proper signal handling - Exit codes: Set
process.exitCode, never callprocess.exit()(n/no-process-exit rule) - Type definitions: Create
.d.mtsfiles for.mjsutilities used by.mtsscripts
- Location:
docs/folder; monorepos also havepackages/*/docs/ - Filenames:
lowercase-with-hyphens.md(exception: README.md, LICENSE, CHANGELOG.md, etc.) - Style: Pithy, direct, scannable. Use ASCII diagrams for complex concepts.
- Prefer single scripts with flags (
pnpm run build --watch) over multiple variants (build:watch,build:prod) - Exception: composite scripts that orchestrate multiple steps
- Extensions:
.js(JSDoc),.mjs(ES modules),.mts(TypeScript modules) - Naming: kebab-case filenames
- Module headers: MANDATORY
@fileoverviewas first content - Node.js imports: MANDATORY
node:prefix (import path from 'node:path') - Import sorting: 1) Node built-ins, 2) External, 3)
@socketsecurity/*, 4) Local, 5) Types. Blank lines between. Alphabetical within. - fs imports:
import { syncMethod, promises as fs } from 'node:fs'
- Constants:
UPPER_SNAKE_CASE - Avoid
null: Useundefinedinstead ofnullthroughout codebase- Default parameters:
function foo(bar = undefined)or justfunction foo(bar) - Variable initialization:
let xorlet x = undefined, notlet x = null - Return values: Return
undefinednotnull - Optional properties: Use
?:syntax, not| null - Exception:
__proto__: nullfor prototype-less objects (required pattern) - Exception: External APIs that explicitly require
null
- Default parameters:
- **proto**: MANDATORY - Always first in literals:
{ __proto__: null, ...opts } - Null-prototype objects: Use
Object.create(null)for empty objects only;{ __proto__: null, key: val }when properties exist - Options pattern: MANDATORY
const opts = { __proto__: null, ...options } as SomeOptions - Array destructuring: Use
{ 0: key, 1: val }forObject.entries()loops (V8 performance) - Array checks:
!array.lengthnotarray.length === 0 - Increments:
var += 1notvar++(standalone statements) - Type safety: FORBIDDEN
any; useunknownor specific types - Loop annotations: FORBIDDEN - Never annotate for...of variables
- String interpolation: MANDATORY - Template literals, not concatenation
- Semicolons: Omit (except SDK which uses them)
-
Order: Alphabetical; private first, then exported
-
Await in loops: Add
// eslint-disable-next-line no-await-in-loopwhen intentional -
Process spawning: Use
@socketsecurity/registry/lib/spawnnotchild_process.spawn -
spawn() with shell: WIN32: 🚨 NEVER change
shell: WIN32toshell: true-
shell: WIN32is the correct cross-platform pattern (enables shell on Windows, disables on Unix) -
If spawn fails with ENOENT, the issue is NOT the shell parameter
-
Fix by properly separating command and arguments instead:
// 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 })
-
This pattern is canonical across all Socket Security codebases
-
-
Working directory: 🚨 NEVER use
process.chdir()- use{ cwd }options and absolute paths instead- Breaks tests, worker threads, and causes race conditions
- Always pass
{ cwd: absolutePath }to spawn/exec/fs operations
- Policy: Default to NO comments. Only add one when the WHY is non-obvious to a senior engineer reading the code cold
- Style: Single-line (
//) over multiline - Periods: MANDATORY - All comments end with periods (except directives/URLs)
- Placement: Own line above code
- JSDoc: Description + optional
@throwsonly - NO@param,@returns,@author,@example - Examples:
// This validates input.(correct) |// this validates(incorrect)
- MANDATORY: Sort lists, exports, object properties, destructuring alphabetically
- Type properties: Required first, then optional; alphabetical within groups
- Class members: 1) Private properties, 2) Private methods, 3) Public methods (all alphabetical)
- Catch:
catch (e)notcatch (error) - Messages: Double quotes, descriptive, actionable, NO periods at end
- Patterns:
"{field}" is requiredor"{field}" is a required {type}"{field}" must be a {type}{context} "{field}" {violation}failed to parse {format}orunable to {action} "{component}"
- Requirements: Throw errors (no silent failures), include
{ cause: e }, noprocess.exit()except script entry - JSDoc:
@throws {ErrorType} When condition.
- Format:
## [version](https://github.com/SocketDev/socket-registry/releases/tag/vversion) - YYYY-MM-DD - Follow: Keep a Changelog format
- Sections: Added, Changed, Fixed, Removed
- Focus: User-facing changes only (no internal refactoring/deps/CI)
- MANDATORY: All actions reference commit SHAs not tags:
uses: owner/repo@sha # vX.Y.Z - Reusable workflows: Create in socket-registry, reference from other projects
- Standard SHAs: actions/checkout@v5, pnpm/action-setup@v4, actions/setup-node@v5, actions/upload-artifact@v4
- NEVER use
sedto edit YAML workflow files — use the Edit tool with exact string matching.sedsilently clobbers YAML content (SHAs, quoting, indentation)
- Core deps: @typescript/native-preview (tsgo), @types/node, typescript-eslint (unified only)
- DevDeps: @dotenvx/dotenvx, @vitest/coverage-v8, del-cli, eslint, eslint-plugin-*, globals, husky, knip, lint-staged, npm-run-all2, oxfmt, taze, type-coverage, vitest, yargs-parser, yoctocolors-cjs
- FORBIDDEN: Separate @typescript-eslint/* packages; use unified
typescript-eslint - TSGO PRESERVATION: Never replace tsgo with tsc
- Update: Use
pnpm run tazeto check/apply updates across all Socket projects
- Location:
.claude/(gitignored) — working notes, never commit
- Registry lib:
/registry/lib/- Core production code - Scripts:
/scripts/- Dev/build scripts - Tests:
/test/- Test files/fixtures - Overrides:
/packages/npm/- NPM package overrides
- Optimize for speed without sacrificing correctness (serves Socket security infrastructure)
- Benchmark performance-sensitive changes
- Avoid unnecessary allocations in hot paths
registry/lib/agent.js: npm/pnpm/yarn (Windows + Unix)- Functions:
execNpm,execPnpm,execYarn
- Dev:
pnpm run build,pnpm run test,pnpm run check,pnpm run fix - Registry:
pnpm run update,pnpm run make-npm-override,pnpm run release-npm - Test npm packages:
node scripts/test-npm-packages.mjs(long-running, tests all overrides)
/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
- esbuild for fast compilation (registry)
- TypeScript → CommonJS (unminified for better Node ESM interop)
- Post-build transform: Converts esbuild wrappers to clear
module.exports = { ... }for Node ESM named imports - Multiple env configs:
.env.local,.env.test,.env.external - Linting: eslint
- Formatting: oxfmt