MANDATORY: Act as principal-level engineer. Follow these guidelines exactly.
- Identify users by git credentials (commit author, GitHub account); use their actual name, never "the user"
- Use "you/your" when speaking directly; use names when referencing their commits/contributions
- Example: git shows "John-David Dalton jdalton@example.com" β "John-David"
MANDATORY: Review CLAUDE.md before any action. No exceptions.
- Before ANY structural refactor on a file >300 LOC: remove dead code first, commit separately
- Multi-file changes: phases of β€5 files, verify each before the next
- Study existing code before building β working code is a better spec than any description
- Work from raw error data, not theories β if no error output, ask for it
- On "yes", "do it", or "go": execute immediately, no plan recap
- Run the actual command β execute, don't assume
- State what you verified, not just "looks good"
- FORBIDDEN: Claiming "Done" when tests show failures
- Run type-check/lint if configured; fix ALL errors before reporting done
- Re-read every modified file; confirm nothing references removed items
- After 10+ messages: re-read files before editing
- Read files >500 LOC in chunks using offset/limit
- Before every edit: re-read. After every edit: re-read to confirm
- When renaming: search direct calls, type refs, string literals, dynamic imports, re-exports, tests
- Never fix a display bug by duplicating state β one source of truth
- 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
- Fix warnings when you find them (lint, type-check, build, runtime) β don't leave them for later
- Do not add features or improvements beyond what was asked β band-aids when asked for band-aids
- Simplest approach first; flag architectural flaws and wait for approval
- When asked to "make a plan," output only the plan β no code until given the go-ahead
- Before calling done: present two views β perfectionist reject vs. pragmatist ship β let the user decide
- After fixing a bug: explain why it happened and what category of bug it is
- If a fix fails twice: stop, re-read top-down, state where the mental model was wrong, try something fundamentally different
- On "step back" or "we're going in circles": drop everything, rethink from scratch
- Offer to checkpoint before risky changes
- Flag files >400 LOC for potential splitting
- Never create files unless necessary; always prefer editing existing files
- Forbidden to create docs unless requested
- π¨ NEVER use
npx,pnpm dlx, oryarn dlxβ usepnpm exec <pkg>orpnpm run <script>; add tools as pinned devDependencies first # zizmor: documentation-prohibition - minimumReleaseAge: NEVER add packages to
minimumReleaseAgeExcludein CI. Locally, ASK before adding β the age threshold is a security control.
If user repeats an instruction 2+ times, ask: "Should I add this to CLAUDE.md?"
Terminal symbols (from @socketsecurity/lib/logger LOG_SYMBOLS):
- β Success β green (NOT β )
- β Error β red (NOT β)
- β Warning β yellow (NOT
β οΈ ) - βΉ Info β blue (NOT βΉοΈ)
- β Step β cyan (NOT β or βΆ)
Color the icon only, using yoctocolors-cjs (NOT ESM yoctocolors):
import colors from 'yoctocolors-cjs'
const success = `${colors.green('β')} ${msg}`
const error = `${colors.red('β')} ${msg}`
const warning = `${colors.yellow('β ')} ${msg}`
const info = `${colors.blue('βΉ')} ${msg}`
const step = `${colors.cyan('β')} ${msg}`Use emojis sparingly. Prefer text symbols for terminal compatibility.
- Must work on Windows + POSIX
- Paths: always
path.join(),path.resolve(),path.sepβ never hard-code/or\ - Temp:
os.tmpdir()+fs.mkdtemp() - File URLs:
fileURLToPath()fromnode:url
- Minimum: Node.js 18.0.0
- FORBIDDEN ES2023+:
toReversed(),toSorted(),toSpliced(),with() - Use
slice().reverse(),slice().sort()instead
- π¨ FORBIDDEN to maintain β we're our only consumers. Remove compat code on sight. Make clean breaks; no deprecation paths.
- π¨ FORBIDDEN:
fs.rm(),fs.rmSync(),rm -rf. UsesafeDelete()/safeDeleteSync()from@socketsecurity/lib/fs. - package.json scripts: use
del-cli - π¨ HTTP: NEVER
fetch()β usehttpJson/httpText/httpRequestfrom@socketsecurity/lib/http-request - π¨ File existence: ALWAYS
existsSyncfromnode:fs. NEVERfs.access,fs.stat-for-existence, or an asyncfileExistswrapper. Import:import { existsSync, promises as fs } from 'node:fs'
- 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 β
<type>(<scope>): <description> - NO AI attribution in commit messages
- π¨ NEVER GUESS SHAs: use
git rev-parse HEADorgit rev-parse origin/main - Format:
@662bbcab1b7533e24ba8e3446cffd8a7e5f7617e # main(full 40-char SHA) - GitHub Actions require pinned full SHAs
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.
-
Each layer gets its own PR β never combine layers
-
π¨ NEVER type/guess SHAs β
git fetch origin main && git rev-parse origin/mainAFTER merge -
π¨ NEVER use a SHA from a PR branch β only SHAs from main after merge
-
Verify SHA:
gh api repos/SocketDev/socket-registry/commits/<sha> --jq '.sha' -
Propagation SHA = whatever
.github/workflows/_local-not-for-reuse-*.ymlcurrently pin. That's the source of truth for external repos:grep -hE 'SocketDev/socket-registry.*@[0-9a-f]{40}' .github/workflows/_local-not-for-reuse-*.yml \ | grep -oE '@[0-9a-f]{40}' | sort -u
Expect exactly one SHA. More than one = Layer 4 bump incomplete; finish it before propagating.
-
L3/L4-only changes (reusable workflow edits, no L1/L2 action changes): skip to Layer 4 bump
-
Don't clobber third-party SHAs during blanket replacements
-
Consumer repos: direct push β socket-btm, sdxgen, stuie, ultrathink; PR β socket-cli, socket-lib, socket-sdk-js, socket-packageurl-js. Note:
sdxgenlocal checkout issocket-sdxgen/but GitHub repo isSocketDev/sdxgenβ use bare name forghcommands.stuielocal checkout issocket-tui/but GitHub repo isSocketDev/stuie.
- MANDATORY: Use
SocketDev/socket-registry/.github/workflows/ci.yml@<SHA>with full commit SHA - Reusable workflows centralize lint/type-check/test/coverage
- Matrix: Node.js 22/24, cross-platform
- CI script naming:
lint-ci,test-ci,type-ci(no watch/fix modes)
- MANDATORY: actions reference commit SHAs, not tags β
uses: owner/repo@sha # vX.Y.Z - 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.sedsilently clobbers SHAs/quoting/indentation.
- Dirs:
test/npm/(NPM package tests),test/registry.test.mts+test/packages.test.mts(registry-level) - Utils:
test/utils/βsetupNpmPackageTest(),itOnWindows/itOnUnix/normalizePath(),expect*helpers,createTypeCheckerTests() - Coverage: MANDATORY β never decrease;
c8 ignoremust include reason ending with period - Commands:
pnpm test,pnpm test path/to/file.test.ts,pnpm run cover,node scripts/npm/test-npm-packages.mts - π¨ NEVER use
--before test paths β runs ALL tests - NEVER write source-scanning tests (reading source files and asserting on contents). Write functional tests that verify behavior.
- Config:
.config/vitest.config.mts(used bypnpm test). Uses forks for isolation. - Pool:
pool: 'forks',singleFork: true,maxForks: 1,isolate: true - Threads:
singleThread: true, maxThreads: 1 - Timeouts:
testTimeout: 60_000, hookTimeout: 60_000 - Cleanup:
await safeDelete(paths)from@socketsecurity/lib/fs
- pnpm only (not npm)
- Add deps:
pnpm add <pkg> --save-exact(exact versions, no^/~) - Workspace root:
-wflag - After editing
package.jsondeps: runpnpm install, commitpnpm-lock.yaml, never manually edit it - READMEs use
pnpm install
- Wrap complex commands in
scripts/*.mts, not package.json directly:"script-name": "node scripts/script-name.mts" - Use
spawnfrom@socketsecurity/lib/spawnwith signal handling - Set
process.exitCode; never callprocess.exit()except at entry point .mtsscripts declare types inline; only write.d.mtsfor legacy.mjsutilities- Prefer single scripts with flags (
pnpm run build --watch) over variants (build:watch)
- Location:
docs/(monorepos alsopackages/*/docs/) - Filenames:
lowercase-with-hyphens.md(except README.md, LICENSE, CHANGELOG.md) - Style: pithy, direct, scannable; ASCII diagrams for complex concepts
- Extensions:
.js(JSDoc),.mjs(ES modules),.mts(TypeScript modules) - kebab-case filenames
- MANDATORY
@fileoverviewas first content - MANDATORY
node:prefix for Node imports - Import sorting: 1) Node built-ins, 2) External, 3)
@socketsecurity/*, 4) Local, 5) Types. Blank lines between groups, alphabetical within. - fs imports:
import { syncMethod, promises as fs } from 'node:fs'
- Constants:
UPPER_SNAKE_CASE - Avoid
nullβ useundefinedeverywhere- Default params:
function foo(bar = undefined)orfunction foo(bar) - Init:
let xorlet x = undefined, notlet x = null - Return
undefined, notnull - Optional properties:
?:not| null - Exceptions:
__proto__: null(required pattern); external APIs requiringnull
- Default params:
__proto__: MANDATORY first in literals:{ __proto__: null, ...opts }- Null-prototype objects:
Object.create(null)for empty;{ __proto__: null, key: val }with props - Options pattern: MANDATORY
const opts = { __proto__: null, ...options } as SomeOptions - Array destructuring:
{ 0: key, 1: val }forObject.entries()loops (V8 perf) - Array checks:
!array.lengthnotarray.length === 0 - Increments:
var += 1notvar++(standalone) - Type safety: FORBIDDEN
any; useunknownor specific types - Loop annotations: FORBIDDEN on
for...ofvariables - Strings: MANDATORY template literals, not concatenation
- Semicolons: omit (except SDK which uses them)
-
Order: alphabetical; private first, then exported
-
awaitin loops: add// eslint-disable-next-line no-await-in-loopwhen intentional -
Process spawning: use
spawnfrom@socketsecurity/lib/spawn, notchild_process.spawn -
spawn() with
shell: WIN32: π¨ NEVER change toshell: true-
shell: WIN32is the correct cross-platform pattern (shell on Windows, off on Unix) -
ENOENT means args are wrong, not the shell param. Separate command and args:
// WRONG β full command as string spawn('python3 -m module arg1 arg2', [], { shell: WIN32 }) // CORRECT spawn('python3', ['-m', 'module', 'arg1', 'arg2'], { shell: WIN32 })
-
-
Working directory: π¨ NEVER use
process.chdir()β pass{ cwd: absolutePath }to spawn/exec/fs.process.chdirbreaks tests, worker threads, and causes races.
- Default to NO comments. Add one only when the WHY is non-obvious to a senior engineer reading cold.
- Single-line (
//) over multiline - MANDATORY all comments end with periods (except directives/URLs)
- Own line above code
- JSDoc: description + optional
@throws. NO@param/@returns/@authorβ types in signatures say that.@exampleonly when the call site is non-obvious.
- 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 (e)notcatch (error)- Messages: double quotes, descriptive, actionable, NO periods at end
- Patterns:
"{field}" is required/"{field}" is a required {type}"{field}" must be a {type}{context} "{field}" {violation}failed to parse {format}/unable to {action} "{component}"
- Throw errors (no silent failures); include
{ cause: e }when wrapping; 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
- Sections: Added, Changed, Fixed, Removed
- User-facing only (no internal refactoring/deps/CI)
- Core: @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 unifiedtypescript-eslint - TSGO PRESERVATION: never replace tsgo with tsc
- Update:
pnpm run taze
- Location:
.claude/(gitignored) β working notes, never commit
/registry/src/β TypeScript source/registry/dist/β build output (esbuild)/scripts/β dev/build scripts (all.mts)/test/β test files/fixtures/packages/npm/β NPM package overrides- Primary export:
getManifestData(ecosystem, packageName)from@socketsecurity/registry
- Optimize for speed without sacrificing correctness (serves Socket security infrastructure)
- Benchmark performance-sensitive changes
- Avoid unnecessary allocations in hot paths
- Lives in
@socketsecurity/lib/spawn, not this repo.
- Dev:
pnpm run build,pnpm run test,pnpm run check,pnpm run fix - Type check:
pnpm run type(tsgo, no emit) - Registry:
pnpm run update,pnpm run make-npm-override,pnpm run release-npm - Test npm packages:
node scripts/npm/test-npm-packages.mts(long-running)
- esbuild (registry); TypeScript β CommonJS (unminified for Node ESM interop)
- Post-build transform converts esbuild wrappers to clear
module.exports = { ... }for Node ESM named imports - Env configs:
.env.test - Lint: oxlint. Format: oxfmt.
/security-scanβ 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:
.claude/ops/queue.yaml