stacks-lint

$npx mdskill add stacksjs/stacks/stacks-lint

Lint and format code in Stacks projects using pickier.

  • Ensures code quality and consistency without manual eslint usage.
  • Depends on pickier, bun, and @stacksjs/lint package.
  • Always runs pickier via bunx, never eslint directly.
  • Provides lint results and auto-fix via pickier commands.

SKILL.md

.github/skills/stacks-lintView on GitHub ↗
---
name: stacks-lint
description: Use when linting or formatting code in a Stacks project. CRITICAL - always use pickier, NEVER eslint directly. Run 'bunx --bun pickier .' to lint, 'bunx --bun pickier . --fix' to auto-fix. For unused variables, prefer eslint-disable-next-line comments over underscore prefix. Covers @stacksjs/lint and config/code-style.ts.
license: MIT
compatibility: Bun >= 1.3.0, TypeScript
allowed-tools: Read Edit Write Bash Grep Glob
---

# Stacks Linting

## CRITICAL RULES
1. **ALWAYS** use `bunx --bun pickier .` to lint -- **NEVER** use `eslint` directly
2. **Auto-fix**: `bunx --bun pickier . --fix`
3. For unused variable warnings: prefer `// eslint-disable-next-line` comments over prefixing with `_`

## Key Paths
- Lint package: `storage/framework/core/lint/` (package: `@stacksjs/lint`)
- Configuration: `config/code-style.ts` (auto-loaded via pickier's bunfig alias `code-style`; pickier handles both linting and formatting)
- Lint command source: `storage/framework/core/buddy/src/commands/lint.ts`

## Package Details
- Package name: `@stacksjs/lint`
- Version: tracks framework version (currently 0.70.23)
- The lint package is a thin wrapper -- the actual linting is done by pickier
- `storage/framework/core/lint/build.ts` is a no-op (nothing to build)
- Dev dependencies: `better-dx`, `@stacksjs/gitlint`, `bun-git-hooks`

## Commands

### Via buddy CLI
The buddy `lint` command internally runs pickier with the project's pickier config:

```bash
# Lint (runs: bunx --bun pickier run --mode lint --config ./pickier.config.ts)
buddy lint

# Auto-fix (runs: bunx --bun pickier run --mode lint --config ./pickier.config.ts --fix)
buddy lint --fix
buddy lint:fix

# Format (runs: bunx --bun pickier run --mode format --config ./pickier.config.ts --write)
buddy format
buddy format --write        # same as above (write is default)

# Check formatting without changes
buddy format --check        # runs: pickier run --mode format --config ./pickier.config.ts --check
buddy format:check
```

### Direct pickier usage
```bash
# Lint entire project
bunx --bun pickier .

# Auto-fix
bunx --bun pickier . --fix

# Via npm scripts
bun run lint
bun run lint:fix
bun run format
bun run format:check
```

## config/code-style.ts (PickierOptions)

The actual configuration used by the project. This is the source of truth for all lint settings:

```typescript
import type { PickierOptions } from 'pickier'

const config: PickierOptions = {
  format: {
    extensions: ['ts', 'js', 'stx', 'json', 'yaml', 'md'],
    indent: 2,
    quotes: 'single',
    semi: false,
  },

  rules: {
    // TypeScript rules
    noDebugger: 'off',
    noConsole: 'off',
  },

  pluginRules: {
    // Regexp rules disabled (false positives on route definitions)
    'regexp/no-unused-capturing-group': 'off',
    'regexp/no-super-linear-backtracking': 'off',
    'regexp/optimal-quantifier-concatenation': 'off',
    // Style rules disabled (conflict with common patterns or generated code)
    'style/brace-style': 'off',
    'style/max-statements-per-line': 'off',
    'style/quotes': 'off',
    'indent': 'off',
    'quotes': 'off',
    // TypeScript rules
    'ts/no-top-level-await': 'off',
    // Console is intentional
    'no-console': 'off',
    // Markdown rules
    'markdown/heading-increment': 'off',
    'markdown/no-empty-links': 'off',
  },

  ignores: [
    '**/fixtures/**',
    '**/coverage/**',
    '**/temp/**',
  ],
}

export default config
```

## Format Settings (enforced)
- **Indent**: 2 spaces
- **Quotes**: single quotes
- **Semicolons**: none (no semicolons)
- **Extensions**: ts, js, stx, json, yaml, md

## Handling Unused Variables

When pickier/eslint reports unused variables, use eslint-disable comments -- do NOT prefix with underscore:

```typescript
// CORRECT: Use eslint-disable comment
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const unusedVar = someFunction()

// ALSO CORRECT: Use eslint-disable for next line only
// eslint-disable-next-line no-unused-vars
function handler(req, res, next) {
  res.send('ok')
}

// INCORRECT: Don't prefix with underscore
const _unusedVar = someFunction()  // DON'T DO THIS
```

## Internal Implementation

The buddy `lint` command (in `storage/framework/core/buddy/src/commands/lint.ts`) delegates entirely to pickier:

```typescript
// lint.ts internals
buddy
  .command('lint', 'Automagically lints your project codebase')
  .option('-f, --fix', 'Automagically fixes all lint errors', { default: false })
  .action(async (options: LintOptions) => {
    if (options.fix)
      await runCommand('bunx --bun pickier run --mode lint --config ./pickier.config.ts --fix', { cwd: path.projectPath() })
    else
      await runCommand('bunx --bun pickier run --mode lint --config ./pickier.config.ts', { cwd: path.projectPath() })
  })
```

The `format` command uses pickier in format mode:

```typescript
buddy
  .command('format', 'Format your project codebase')
  .option('-w, --write', 'Write changes to files', { default: false })
  .option('-c, --check', 'Check formatting without making changes', { default: false })
  .action(async (options) => {
    if (options.check)
      await runCommand('bunx --bun pickier run --mode format --config ./pickier.config.ts --check', { cwd: path.projectPath() })
    else
      await runCommand('bunx --bun pickier run --mode format --config ./pickier.config.ts --write', { cwd: path.projectPath() })
  })
```

## Gotchas
- Pickier is the Stacks linting tool -- it wraps eslint with Stacks-specific config
- **NEVER** run `eslint` directly -- always go through `bunx --bun pickier` or `buddy lint`
- The `config/code-style.ts` file exports a `PickierOptions` object -- not an eslint config (auto-discovered by pickier via bunfig alias `code-style`)
- The project intentionally allows `console.*` calls (`noConsole: 'off'`, `'no-console': 'off'`)
- The project intentionally allows `debugger` statements (`noDebugger: 'off'`)
- Top-level await is allowed (`'ts/no-top-level-await': 'off'`)
- Brace style and max-statements-per-line rules are disabled to avoid conflicts with generated code
- Run `bunx --bun pickier . --fix` after code generation to clean up
- `buddy lint` exits through the `lint:*` wildcard handler if an invalid sub-command is used
- `buddy format` defaults to `--write` mode (writes changes) unless `--check` is specified
- The lint package (`@stacksjs/lint`) itself has no source files to build -- it is a configuration-only package

More from stacksjs/stacks

SkillDescription
stacks-actionsUse when working with Stacks server actions — creating actions in app/Actions/, auto-generated API actions from the useApi model trait, the 80+ default framework actions (auth, dashboard, commerce, content, deployment, jobs), action request/response handling, or action registration. Covers @stacksjs/actions and storage/framework/defaults/actions/.
stacks-aiUse when integrating AI capabilities into a Stacks application — using Anthropic/OpenAI/Ollama/AWS Bedrock drivers, image generation (DALL-E), vision analysis, RAG/vector search, embeddings, MCP (Model Context Protocol) clients, text summarization, sentiment analysis, content classification, personalization, or the buddy AI assistant. Covers @stacksjs/ai and config/ai.ts.
stacks-aliasUse when working with path aliases in a Stacks project — import resolution, module aliasing, or debugging import paths. Covers @stacksjs/alias which defines 260+ path mappings for the entire framework.
stacks-analyticsUse when adding analytics to a Stacks application — configuring Fathom or self-hosted analytics, generating tracking scripts, privacy-friendly analytics setup, or the analytics configuration. Covers @stacksjs/analytics and config/analytics.ts.
stacks-apiUse when building, modifying, or debugging API endpoints in a Stacks application — defining routes, handling requests, API middleware, working with the API server, HTTP client (fetcher), API resources, or OpenAPI generation. Covers both @stacksjs/api utilities and the stacks-api server implementation.
stacks-arraysUse when working with array utilities in Stacks — statistical operations (average, median, mode, standard deviation, z-score, percentile, covariance), array manipulation (unique, flatten, partition, shuffle, sample, move), containment checks, or the Arr facade. Covers @stacksjs/arrays.
stacks-authUse when implementing authentication, authorization, passkeys, TOTP/2FA, RBAC, gates, policies, session auth, token management, email verification, password resets, or rate limiting in a Stacks application. Covers the @stacksjs/auth package, config/auth.ts, app/Gates.ts, and app/Middleware/.
stacks-auto-importsUse when working with the Stacks auto-import system — understanding how browser and server auto-imports work, configuring auto-imported functions/models/composables, the auto-import manifests, type generation, or how globals are injected. Covers the auto-import pipeline at storage/framework/auto-imports/.
stacks-brandkitUse when defining or exploring a brand identity for a Stacks project and you want premium brand-kit REFERENCE IMAGES first - logo directions, palettes, typography specimens, identity boards, and application/mockup decks. Generates IMAGES ONLY, never code. Trained for minimalist, cinematic, editorial, dark-tech, luxury, cultural, security, gaming, developer-tool, and consumer-app brand systems with intentional logo concepting, sparse type, strong symbolic meaning, and disciplined palettes a coding step can build against.
stacks-browseUse for headless browser QA on Stacks applications — navigation, screenshots, responsive testing, console/network monitoring, and accessibility snapshots. Dependency-free: drives a system browser over the Chrome DevTools Protocol using only Bun (no Playwright/Puppeteer). Invoke with /stacks-browse.