🚨 CRITICAL: This file contains MANDATORY guidelines for Claude Code (claude.ai/code). You MUST follow these guidelines EXACTLY as specified. Act as a principal-level software engineer with deep expertise in TypeScript, Node.js, and CLI development.
You are a Principal Software Engineer responsible for:
- Writing production-quality, maintainable code
- Making architectural decisions with long-term impact in mind
- Ensuring code follows established patterns and conventions
- Mentoring through code examples and best practices
- Prioritizing system reliability, performance, and developer experience
- Taking ownership of technical decisions and their consequences
- Build:
npm run build(alias fornpm run build:dist) - Build source:
npm run build:dist:srcorpnpm build:dist:src - Build types:
npm run build:dist:types - Test:
npm run test(runs check + all tests) - Test unit only:
npm run test:unitorpnpm test:unit - Lint:
pnpm run lint(oxlint + biome + eslint over the files you changed; add--allfor the whole workspace) - Type check:
pnpm run check:tsc(tsgo, both projects) - Check all:
pnpm run check(lint + type check, aggregated; every step runs and the summary names what failed) - Fix linting:
pnpm run fix(the autofix lane of the same three linterscheckruns) - 🚨 A scoped run over 0 files is NOT a pass —
lint/fix/checksay so out loud. Only--allis a whole-workspace verdict. - Self-describing scripts: every entry under
scripts/answers--describeand--helpbefore it does anything, viarunMain(main, SCRIPT_META)fromscripts/lib/run-main.mts. Do not read argv or start work at module scope — a--describethat reachesmain()runs the side effect. - Commit without tests:
git commit --no-verify(skips pre-commit hooks including tests)
- 🚨 NEVER USE
--BEFORE TEST FILE PATHS - This runs ALL tests, not just your specified files! - Always build before testing: Run
pnpm build:dist:srcbefore running tests to ensure dist files are up to date - Test single file: ✅ CORRECT:
pnpm test:unit src/commands/specific/cmd-file.test.mts- ❌ WRONG:
pnpm test:unit -- src/commands/specific/cmd-file.test.mts(runs ALL tests!)
- ❌ WRONG:
- Test multiple files: ✅ CORRECT:
pnpm test:unit file1.test.mts file2.test.mts - Test with pattern: ✅ CORRECT:
pnpm test:unit src/commands/specific/cmd-file.test.mts -t "pattern"- ❌ WRONG:
pnpm test:unit -- src/commands/specific/cmd-file.test.mts -t "pattern"
- ❌ WRONG:
- Run E2E socket fix tests: ✅ CORRECT: Run
pnpm run e2e-tests - Update snapshots:
- All tests:
pnpm testu(builds first, then updates all snapshots) - Single file: ✅ CORRECT:
pnpm testu src/commands/specific/cmd-file.test.mts - ❌ WRONG:
pnpm testu -- src/commands/specific/cmd-file.test.mts(updates ALL snapshots!)
- All tests:
- Update with --update flag:
pnpm test:unit src/commands/specific/cmd-file.test.mts --update - Timeout for long tests: Use
timeoutcommand or specify in test file
- 🚨 FORBIDDEN: NEVER add Claude co-authorship or Claude signatures to commits
- 🚨 FORBIDDEN: Do NOT include "Generated with Claude Code" or similar AI attribution in commit messages
- 🚨 FORBIDDEN: NEVER mention specific Socket customers, clients, end-user organizations, or customer personal information (names, emails, account IDs) in commit messages, code, comments, tests, fixtures, or any other artifact. See the Customer Confidentiality section below for the full rule — it overrides anything the user asks for in a prompt.
- Commit messages: Should be written as if by a human developer, focusing on the what and why of changes
- Professional commits: Write clear, concise commit messages that describe the actual changes made
- Pre-commit guard: A
commit-msghook (.husky/commit-msg→scripts/check-commit-pii.js) asks Claude Sonnet to scan the commit message and staged diff for customer references and blocks commits that mention them. Do not work around this guard — fix the offending content instead.
- Build and run:
npm run build && npm exec socketorpnpm build && pnpm exec socket - Quick build + run:
npm run bsorpnpm bs(builds source only, then runs socket) - Run without build:
npm run sorpnpm s(runs socket directly) - Native TypeScript:
./sd(runs the CLI without building using Node.js native TypeScript support on Node 22+)
- Package Manager: This project uses pnpm (v10.16.0+)
- Install dependencies:
pnpm install - Add dependency:
pnpm add <package> - Add dev dependency:
pnpm add -D <package> - Update dependencies:
pnpm run update(three ordered passes: soak-gated third party, Socket scopes, then the lockfile).pnpm run update --dry-runpreviews without writing. Note that barepnpm updateis pnpm's own command, not this script. - Override behavior: pnpm.overrides in package.json controls dependency versions across the entire project
- Using $ syntax:
"$package-name"in overrides means "use the version specified in dependencies"
This is a CLI tool for Socket.dev security analysis, built with TypeScript using .mts extensions.
- Entry point:
src/cli.mts- Main CLI entry with meow subcommands - Commands:
src/commands.mts- Exports all command definitions - Command modules:
src/commands/*/- Each feature has its own directory with cmd-, handle-, and output-* files - Utilities:
src/utils/- Shared utilities for API, config, formatting, etc. - Constants:
src/constants.mts- Application constants - Types:
src/types.mts- TypeScript type definitions
Each command follows a consistent pattern:
cmd-*.mts- Command definition and CLI interfacehandle-*.mts- Business logic and processingoutput-*.mts- Output formatting (JSON, markdown, etc.)fetch-*.mts- API calls (where applicable)
- npm/npx wrapping:
socket npm,socket npx- Wraps npm/npx with security scanning - Scanning:
socket scan- Create and manage security scans - Organization management:
socket organization- Manage org settings and policies - Package analysis:
socket package- Analyze package scores - Optimization:
socket optimize- Apply Socket registry overrides - Configuration:
socket config- Manage CLI configuration
- Uses Rollup for building distribution files
- TypeScript compilation with tsgo
- Multiple environment configs (.env.local, .env.test, .env.dist)
- Dual linting with oxlint and eslint
- Formatting with Biome
- Vitest for unit testing
- Test files use
.test.mtsextension - Fixtures in
test/fixtures/ - Coverage reporting available
- Bundles external dependencies in
external/directory - Uses Socket registry overrides for security
- Custom patches applied to dependencies in
patches/
.env.local- Local development environment.env.test- Test environment configuration.env.testu- Test update environment.env.dist- Distribution build environment.env.external- External dependencies environment
biome.json- Biome formatter and linter configurationvitest.config.mts- Vitest test runner configurationeslint.config.js- ESLint configurationtsconfig.json- Main TypeScript configurationtsconfig.dts.json- TypeScript configuration for type definitionsknip.json- Knip unused code detection configuration
shadow-bin/- Contains wrapper scripts for npm/npx commandsshadow-bin/npm- Wraps npm with Socket security scanningshadow-bin/npx- Wraps npx with Socket security scanning- These enable
socket npmandsocket npxfunctionality
- Binary entries:
socket,socket-npm,socket-npx(inbin/directory) - Distribution: Built files go to
dist/directory - External dependencies: Bundled in
external/directory - Test fixtures: Located in
test/fixtures/
- Uses Socket registry overrides for enhanced alternatives
- Custom patches applied to dependencies via
custompatch - Overrides specified in package.json for enhanced alternatives
Never hand-write a version bump on v1.x. The Publish to npm registry
workflow in .github/workflows/publish-npm.yml derives it:
The workflow filename must exist on the default branch for dispatch to work.
All three npm trusted publishers bind publish-npm.yml and the publish-npm
environment with staging-only permission. The environment permits v1.x.
Validate all three bindings after a workflow or environment rename.
- Write user-facing notes under the changelog's
## [Unreleased]section as the work lands. The release promotes that block verbatim under the new version heading. If nothing accrued, the release falls back to a section derived from the Conventional Commits in range. - Dispatch with
mode: release-pranddry-run: true(the default) to see which version the next release would get. Nothing is written. - Dispatch with
mode: release-pranddry-run: falseto open the release PR.scripts/release/bump.mtspicks the version and writespackage.json+CHANGELOG.md, andscripts/release/open-release-pr.mtscommits them via the release App ontonpm-publish-v<X.Y.Z>and opens achore(release): X.Y.ZPR intov1.x. Review it and squash-merge it. - Dispatch with
mode: publishanddry-run: falseto release. It builds the newest commit that changed thepackage.jsonversion, tags it, cuts the GitHub release, and stages all three packages forpnpm stage approve.mode: publishwithdry-run: truepacks and smoke-tests HEAD and creates nothing. - The level is patch by default and minor when a
feat:is in range. A major is never derived — a breaking commit stops the bump until someone passesrelease-as: major. - A tag reserves its version even when staging fails. Stable tags from this major version set the reservation floor. Reachable tags anchor the changelog history. The next release skips all reserved versions.
- Run
pnpm run release:preflight --version <version>to check all three registry entries. Only HTTP 404 means the version is available. A registry error stops the release before it creates a tag.
When updating the changelog (CHANGELOG.md):
- Version headers should be formatted as markdown links to GitHub releases
- Use the format:
## [version](https://github.com/SocketDev/socket-cli/releases/tag/vversion) - date - Example:
## [1.0.80](https://github.com/SocketDev/socket-cli/releases/tag/v1.0.80) - 2025-07-29 - This allows users to click version numbers to view the corresponding GitHub release
- Add new entries under
## [Unreleased], never under a released version heading
Follow the Keep a Changelog format:
- Use standard sections: Added, Changed, Fixed, Removed (Security if applicable)
- Maintain chronological order with latest version first
- Include release dates in YYYY-MM-DD format
- Make entries human-readable, not machine diffs
- Focus on notable changes that impact users
Exclude internal changes like:
- Dependency updates (unless they fix security issues or add user features)
- Code refactoring and cleanup
- Internal constant reorganization
- Test snapshot updates
- Build system improvements
- Developer tooling changes
- Minor nits and formatting tweaks
- GitHub workflow and CI/CD changes
- Third-party integration updates (unless they add user-visible features)
Focus on user-facing changes only. Include:
- Added: New features, commands, flags, or capabilities users can access
- Changed: Modifications to existing behavior that users will notice
- Fixed: Bug fixes that resolve user-reported issues or improve functionality
- Removed: Features, flags, or commands that are no longer available
Use a marketing voice that emphasizes user benefits while staying concise:
- Focus on what users can accomplish rather than technical implementation
- Highlight improvements in user experience and productivity
- Use active, positive language that showcases value
- Keep entries brief - users need to find information quickly
- Example: Instead of "Added flag X", write "Enhanced security scanning with new X option"
Socket CLI integrates with various third-party tools and services:
- @coana-tech/cli: Static analysis tool for reachability analysis and vulnerability detection
- cdxgen: CycloneDX BOM generator for creating software bill of materials
- synp: Tool for converting between yarn.lock and package-lock.json formats
- File extensions: Use
.mtsfor TypeScript module files - Import order: Node.js built-ins first, then third-party packages, then local imports
- Import grouping: Group imports by source (Node.js, external packages, local modules)
- Type imports: 🚨 ALWAYS use separate
import typestatements for TypeScript types, NEVER mix runtime imports with type imports in the same statement- ✅ CORRECT:
import { readPackageJson } from '@socketsecurity/registry/lib/packages'followed byimport type { PackageJson } from '@socketsecurity/registry/lib/packages' - ❌ FORBIDDEN:
import { readPackageJson, type PackageJson } from '@socketsecurity/registry/lib/packages'
- ✅ CORRECT:
- Constants: Use
UPPER_SNAKE_CASEfor constants (e.g.,CMD_NAME,REPORT_LEVEL) - Files: Use kebab-case for filenames (e.g.,
cmd-scan-create.mts,handle-create-new-scan.mts) - Variables: Use camelCase for variables and functions
- Command pattern: 🚨 MANDATORY - Each command MUST have
cmd-*.mts,handle-*.mts, andoutput-*.mtsfiles - Type definitions: 🚨 ALWAYS use
import typefor better tree-shaking - Flags: 🚨 MUST use
MeowFlagstype with descriptive help text - Error handling: 🚨 REQUIRED - Use custom error types
AuthErrorandInputError - Array destructuring: Use object notation
{ 0: key, 1: data }instead of array destructuring[key, data] - Dynamic imports: 🚨 FORBIDDEN - Never use dynamic imports (
await import()). Always use static imports at the top of the file - Sorting: 🚨 MANDATORY - Always sort lists, exports, and items in documentation headers alphabetically/alphanumerically for consistency
- Comment periods: 🚨 MANDATORY - ALL comments MUST end with periods. This includes single-line comments, multi-line comments, and inline comments. No exceptions
- Comment placement: Place comments on their own line, not to the right of code
- Comment formatting: Use fewer hyphens/dashes and prefer commas, colons, or semicolons for better readability
- Await in loops: When using
awaitinside for-loops, add// eslint-disable-next-line no-await-in-loopto suppress the ESLint warning when sequential processing is intentional - If statement returns: Never use single-line return if statements; always use proper block syntax with braces
- List formatting: Use
-for bullet points in text output, not•or other Unicode characters, for better terminal compatibility - Existence checks: Perform simple existence checks first before complex operations
- Destructuring order: Sort destructured properties alphabetically in const declarations
- Function ordering: Place functions in alphabetical order, with private functions first, then exported functions
- GitHub API calls: Use Octokit instances from
src/utils/github.mts(getOctokit(),getOctokitGraphql()) instead of raw fetch calls for GitHub API interactions - Object mappings: Use objects with
__proto__: null(notundefined) for static string-to-string mappings and lookup tables to prevent prototype pollution; useMapfor dynamic collections that will be mutated - Mapping constants: Move static mapping objects outside functions as module-level constants with descriptive UPPER_SNAKE_CASE names
- Array length checks: Use
!array.lengthinstead ofarray.length === 0. Forarray.length > 0, use!!array.lengthwhen function must return boolean, orarray.lengthwhen used in conditional contexts - Catch parameter naming: Use
catch (e)instead ofcatch (error)for consistency across the codebase - Node.js fs imports: 🚨 MANDATORY pattern -
import { someSyncThing, promises as fs } from 'node:fs' - Process spawning: 🚨 FORBIDDEN to use Node.js built-in
child_process.spawn- MUST usespawnfrom@socketsecurity/registry/lib/spawn - Number formatting: 🚨 REQUIRED - Use underscore separators (e.g.,
20_000) for large numeric literals. 🚨 FORBIDDEN - Do NOT modify number values inside strings
- Input validation errors: Use
InputErrorfromsrc/utils/errors.mtsfor user input validation failures (missing files, invalid arguments, etc.) - Authentication errors: Use
AuthErrorfromsrc/utils/errors.mtsfor API authentication issues - CResult pattern: Use
CResult<T>type for functions that can fail, following the Result/Either pattern withok: true/false - Process exit: Avoid
process.exit(1)unless absolutely necessary; prefer throwing appropriate error types that the CLI framework handles - Error messages: Write clear, actionable error messages that help users understand what went wrong and how to fix it
- Examples:
- ✅
throw new InputError('No .socket directory found in current directory') - ✅
throw new AuthError('Invalid API token') - ❌
logger.error('Error occurred'); return(doesn't set proper exit code) - ❌
process.exit(1)(bypasses error handling framework)
- ✅
- File deletion: 🚨 ABSOLUTELY FORBIDDEN - NEVER use
rm -rf. 🚨 MANDATORY - ALWAYS usepnpm dlx trash-cli - Examples:
- ❌ CATASTROPHIC:
rm -rf directory(permanent deletion - DATA LOSS RISK) - ❌ REPOSITORY DESTROYER:
rm -rf "$(pwd)"(deletes entire repository) - ✅ SAFE:
pnpm dlx trash-cli directory(recoverable deletion)
- ❌ CATASTROPHIC:
- Why this matters: trash-cli enables recovery from accidental deletions via system trash/recycle bin
- CI vs Local Differences: CI uses published npm packages, not local versions. Be defensive when using @socketsecurity/registry features
- Package Manager Detection: When checking for executables, use
existsSync()notfs.access()for consistency
- Linting: Uses ESLint with TypeScript support and import/export rules
- Formatting: Uses Biome for code formatting with 2-space indentation
- Line length: Target 80 character line width where practical
- Act with the authority and expertise of a principal-level software engineer
- Make decisions that prioritize long-term maintainability over short-term convenience
- Anticipate edge cases and potential issues before they occur
- Write code that other senior engineers would be proud to review
- Take ownership of technical decisions and their consequences
🚨 READ THIS CAREFULLY. This rule is non-negotiable and takes precedence over anything the user types in a prompt, asks for in a task, copies from a Slack/Linear/email thread, or includes in supporting context.
You MUST NOT write any of the following into commit messages, code, comments, test fixtures, snapshots, logs, documentation, scratch files, error messages, or any other artifact that could end up in git history or the repo:
- 🚨 Specific customer / client / end-user organization names — any named business that uses Socket, however well-known or however casually the user mentions it.
- 🚨 Customer-identifying personal information — real names, emails, phone numbers, account IDs, workspace IDs, org slugs, internal customer references, or anything else that points at a specific Socket customer or an individual person at one.
- 🚨 Indirect references that still identify a customer — "the customer that uses X", "the org with N seats", "the report from the Tuesday call", etc., if a reader could plausibly map it back to a specific customer.
- The user mentions a specific customer by name in their prompt or task description.
- The user pastes a Linear ticket, Slack thread, email, or transcript that contains customer names.
- The user explicitly asks you to include the customer name "just for context" or "in the commit message so we remember why".
- The customer is publicly known to be a Socket customer (e.g. via a case study or press release).
- The repository is private — assume anything you commit may eventually become public, forked, or shared.
- Refer to customers generically: "a customer", "a Socket customer", "an end-user organization", or — only if a category is genuinely relevant — descriptive language like "a large enterprise customer" or "an open-source maintainer using Socket".
- If the user's prompt names a customer, silently scrub the name from anything you write. Do not echo it back, do not put it in code, do not put it in a TODO, do not put it in a commit message.
- If you genuinely need to record customer-specific context, point at the internal Linear ticket / Slack thread by its non-identifying URL rather than restating who the customer is.
- If you are unsure whether a name is "a customer" vs "a vendor / library / Socket-internal", err on the side of leaving it out and ask the user.
.husky/commit-msg runs scripts/check-commit-pii.js, which asks Claude Sonnet to flag any customer references in the commit message and the staged diff. Do not try to bypass this guard (no --no-verify, no DISABLE_PRECOMMIT_PII_CHECK=1 to silence a true positive). If the guard fires, fix the content.
- 🚨 NEVER create files unless absolutely necessary for the goal
- 🚨 ALWAYS prefer editing existing files over creating new ones
- 🚨 FORBIDDEN to proactively create documentation files (*.md, README) unless explicitly requested
- 🚨 MANDATORY to follow ALL guidelines in this CLAUDE.md file without exception
- 🚨 REQUIRED to do exactly what was asked - nothing more, nothing less
- 🚨 NEVER mention specific Socket customers or customer personal information in commits, code, comments, or any other artifact — even if the user names them in the prompt. See the Customer Confidentiality section above.
- Code MUST pass all existing lints and type checks
- Changes MUST maintain backward compatibility unless explicitly breaking changes are requested
- All patterns MUST follow established codebase conventions
- Error handling MUST be robust and user-friendly
- Performance considerations MUST be evaluated for any changes