MANDATORY: Act as principal-level engineer. Follow these guidelines exactly.
This is a reference to shared Socket standards. See ../socket-registry/CLAUDE.md for canonical source.
- 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.
MANDATORY: Before claiming any task is complete:
- Test the solution end-to-end
- Verify all changes work as expected
- Run the actual commands to confirm functionality
- Never claim "Done" without verification
- Never create files unless necessary
- Always prefer editing existing files
- Forbidden to create docs unless requested
- Required to do exactly what was asked
Principal Software Engineer: production code, architecture, reliability, ownership.
If user repeats instruction 2+ times, ask: "Should I add this to CLAUDE.md?"
Canonical reference: ../socket-registry/CLAUDE.md
All shared standards (git, testing, code style, cross-platform, CI) defined in socket-registry/CLAUDE.md.
Quick references:
- Commits: Conventional Commits
<type>(<scope>): <description>- NO AI attribution - Scripts: Prefer
pnpm run foo --flagoverfoo:barscripts - Docs: Use
docs/folder, lowercase-with-hyphens.md filenames, pithy writing with visuals - Dependencies: After
package.jsonedits, runpnpm installto updatepnpm-lock.yaml - Backward Compatibility: 🚨 FORBIDDEN to maintain - actively remove when encountered (see canonical CLAUDE.md)
- Work Safeguards: MANDATORY commit + backup branch before bulk changes
- Safe Deletion: Use
safeDelete()from@socketsecurity/lib/fs(NEVERfs.rm/rmSyncorrm -rf)
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 Requirements (apply color to icon ONLY, not entire message):
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/ProgressColor Package:
- Use
yoctocolors-cjs(NOTyoctocolorsESM package) - Pinned dev dependency in all Socket projects
- CommonJS compatibility for scripts and tooling
Allowed Emojis (use sparingly):
- 📦 Packages
- 💡 Ideas/tips
- 🚀 Launch/deploy/excitement
- 🎉 Major success/celebration
General Philosophy:
- Prefer colored text-based symbols (✓✗⚠ℹ→) for maximum terminal compatibility
- Always color-code symbols: green=success, red=error, yellow=warning, blue=info, cyan=step
- Use emojis sparingly for emphasis and delight
- Avoid emoji overload - less is more
- When in doubt, use plain text
Core infrastructure library for Socket.dev security tools.
Directory structure:
src/
├── index.ts # Main export barrel
├── types.ts # TypeScript type definitions
├── constants/ # Node.js, npm, package manager constants
├── env/ # Typed environment variable access
├── packages/ # Package management utilities
├── external/ # Vendored external dependencies
└── utils/ # Shared utilities
dist/ # Build output (CommonJS)
├── external/ # Bundled external dependencies
└── ... # Compiled source files
scripts/ # Build and development scripts
test/ # Test files
Path aliases (defined in .config/tsconfig.external-aliases.json):
#constants/* → src/constants/*
#env/* → src/env/*
#lib/* → src/*
#packages/* → src/packages/*
#types → src/types
#utils/* → src/utils/*
- Build:
pnpm build(production build) - Watch:
pnpm run dev(development mode) - Test:
pnpm test(run tests) - Type check:
pnpm run check(TypeScript type checking) - Lint:
pnpm run lint(Biome linting) - Fix:
pnpm run fix(auto-fix formatting/lint issues) - Coverage:
pnpm run cover(test coverage) - Clean:
pnpm run clean(remove build artifacts)
- Target: TypeScript → CommonJS (ES2022)
- Builder: esbuild via
scripts/build/js.mjs - Type generation: tsgo (TypeScript Native Preview)
- Output:
dist/directory
All build scripts are Node.js modules (.mjs) in scripts/:
build/js.mjs- Main JavaScript compilationbuild/externals.mjs- External dependency bundlingfix/commonjs-exports.mjs- Post-build CommonJS export fixesfix/external-imports.mjs- Fix external import patternsfix/generate-package-exports.mjs- Auto-generate package.json exports
🚨 FORBIDDEN: Shell scripts (.sh) - Always use Node.js scripts
The main build command (pnpm build) orchestrates via scripts/build/main.mjs:
- Clean previous build
- Build in parallel: source code, types, and externals
- Fix exports via
scripts/fix/main.mjs
Individual commands:
pnpm run clean- Clean build artifacts onlypnpm build- Full build (default)
- Extensions:
.tsfor TypeScript,.d.tsfor type definitions - Naming: kebab-case filenames (e.g.,
cache-with-ttl.ts) - Module headers: 🚨 MANDATORY
@fileoverviewheaders - Node.js imports: 🚨 MANDATORY
node:prefix - Semicolons: ❌ OMIT (consistent with socket-registry)
- Type safety: ❌ FORBIDDEN
any; useunknownor specific types - Type imports: Always separate
import typefrom runtime imports - Null-prototype objects: Use
{ __proto__: null, ...props }pattern - Options pattern:
const opts = { __proto__: null, ...options } as SomeOptions
- Node.js built-ins (with
node:prefix) - External dependencies
@socketsecurity/*packages- Internal path aliases (
#constants/*,#env/*,#lib/*, etc.) - Type imports (separate)
Blank lines between groups, alphabetical within groups.
- Internal imports: Always use path aliases for internal modules
- ✅
import { getCI } from '#env/ci' - ❌
import { getCI } from '../env/ci'
- ✅
- External modules: Regular imports
- ✅
import path from 'node:path'
- ✅
- Named exports ONLY: 🚨 MANDATORY for all library modules
- ✅
export { value }- Direct named export - ✅
export { foo, bar, baz }- Multiple named exports - ❌
export default value- FORBIDDEN (breaks dual CJS/ESM compatibility) - ❌
export default X; export { X as 'module.exports' }- FORBIDDEN (dual export pattern)
- ✅
- Rationale: Dual-format (CJS/ESM) compatibility requires consistent named exports
- Named exports work identically in both module systems
- Default exports require
.defaultaccess, breaking consistency - Build validation enforces this pattern (enabled in CI)
- Enforcement:
- Biome linting rule:
"noDefaultExport": "error" - Build-time validation:
scripts/validate/esm-named-exports.mjs - CI validation:
scripts/validate/dist-exports.mjs
- Biome linting rule:
- Alphabetical ordering: 🚨 MANDATORY for all files with 3+ exported functions
- Private functions first: Non-exported helpers, getters, utilities (alphabetically sorted)
- Exported functions second: All public API functions (alphabetically sorted)
- Constants/types before functions: Interfaces, types, constants at top of file
- Benefits:
- Predictable function location for navigation
- Reduced merge conflicts when adding new functions
- Easier code review (spot missing/duplicate exports)
- Consistent structure across entire codebase
- Example:
// 1. Imports import { foo } from 'bar' // 2. Types/Constants export interface Options { ... } // 3. Private functions (alphabetical) function helperA() { ... } function helperB() { ... } // 4. Exported functions (alphabetical) export function publicA() { ... } export function publicB() { ... } export function publicC() { ... }
All modules are exported via package.json exports field:
- Constants:
./constants/<name>→dist/constants/<name>.js - Environment:
./env/<name>→dist/env/<name>.js - Libraries:
./<name>→dist/<name>.js - Packages:
./packages/<name>→dist/packages/<name>.js - Types:
./types→dist/types.js
When adding new modules, update package.json exports:
"./module-name": {
"types": "./dist/path/to/module.d.ts",
"default": "./dist/path/to/module.js"
}Or use scripts/generate-package-exports.mjs to auto-generate exports.
Vitest Configuration: This repo uses the shared vitest configuration pattern documented in ../socket-registry/CLAUDE.md (see "Vitest Configuration Variants" section). Main config: .config/vitest.config.mts
- Directories:
test/- All test files - Naming: Match source structure (e.g.,
test/spinner.test.tsforsrc/spinner.ts) - Framework: Vitest
- Coverage: c8/v8 coverage via Vitest
- Use descriptive test names
- Test both success and error paths
- Mock external dependencies appropriately
- Use path helpers for cross-platform tests
- All tests:
pnpm test - Specific file:
pnpm test path/to/file.test.ts - Coverage:
pnpm run cover - 🚨 NEVER USE
--before test paths - runs all tests
Some dependencies are vendored in src/external/:
- Type definitions for external packages
- Optimized versions of dependencies
tsconfig.json includes path mappings for vendored deps:
"paths": {
"cacache": ["./src/external/cacache"],
"make-fetch-happen": ["./src/external/make-fetch-happen"],
"fast-sort": ["./src/external/fast-sort"],
"pacote": ["./src/external/pacote"]
}Workflow: .github/workflows/ci.yml - Custom optimized pipeline
Key Optimizations:
- Separate lint job: Runs once (not 6x in matrix) - saves ~10s
- Build caching: Build runs once, artifacts cached for all jobs - eliminates 5 rebuilds (~8s saved)
- Parallel execution: Lint, build, test, type-check run in parallel where possible
- Smart dependencies: Type-check runs after build completes, tests wait for lint + build
- Matrix strategy: Tests run on Node 20/22/24 × Ubuntu/Windows (6 combinations)
Performance:
- Build time: ~1.6s (esbuild, parallelized)
- Test execution: ~5s (4582 tests, multi-threaded)
- Total CI time: ~40-60% faster than previous setup
- Status check job: Single required check for branch protection
Job Structure:
- lint - Runs Biome linting (once, Node 22/Ubuntu)
- build - Compiles source, caches dist + node_modules
- test - Runs test suite on all matrix combinations (uses cached build)
- type-check - TypeScript type checking (uses cached build)
- ci-success - Aggregates all job results for branch protection
Cache Strategy:
key: build-${{ github.sha }}-${{ runner.os }}
path: |
dist
node_modulesPrevious Setup (for reference):
- Used reusable workflow:
SocketDev/socket-registry/.github/workflows/ci.yml@<SHA> - 🚨 MANDATORY: Use full commit SHA, not tags
- Format:
@662bbcab1b7533e24ba8e3446cffd8a7e5f7617e # main
pnpm run fix- Auto-fix formatting/lint issuespnpm run check- Type checkpnpm test- Run tests (orpnpm run coverfor coverage)
Use pnpm run dev for development with automatic rebuilds.
- Create utility in appropriate
src/subdirectory - Use path aliases for internal imports
- Add type definitions
- Update
package.jsonexports if direct export needed - Add tests in
test/matching structure - Update types and build
Access via typed getter functions in src/env/:
import { getCI } from '#env/ci'
import { getNodeEnv } from '#env/node-env'
import { isTest } from '#env/test'Each env module exports a pure getter function that accesses only its own environment variable. For fallback logic, compose multiple getters:
import { getHome } from '#env/home'
import { getUserProfile } from '#env/userprofile'
const homeDir = getHome() || getUserProfile() // Cross-platform fallbackTesting with rewiring:
Environment getters support test rewiring without modifying process.env:
import { setEnv, clearEnv, resetEnv } from '#env/rewire'
import { getCI } from '#env/ci'
// In test
setEnv('CI', '1')
expect(getCI()).toBe(true)
clearEnv('CI') // Clear single override
resetEnv() // Clear all overrides (use in afterEach)This allows isolated tests without polluting the global process.env state.
Use utilities from #lib/fs:
import { readJsonFile, writeJsonFile } from '#lib/fs'Use spawn utility from #lib/spawn:
import { spawn } from '#lib/spawn'Use path utilities from #lib/paths:
import { normalizePath } from '#lib/paths'- 🚨 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
- Path alias resolution: Ensure
tsconfig.jsonpaths match actual file structure - Module resolution: Use
node:prefix for Node.js built-ins - Build errors: Check for missing exports in
package.json - Test failures: Verify path alias resolution in test environment
- Check
dist/output structure - Verify CommonJS exports are correctly transformed
- Ensure type definitions are generated
- This is a core utilities library - maintain high quality and test coverage
- Breaking changes impact all Socket.dev tools - coordinate carefully
- Cross-platform compatibility is critical
- Performance matters - this code runs frequently in security tools