AgentSkillsCN

tzurot-code-quality

使用此技能来搭建 Go 开发环境、管理依赖项、编译并运行 Go 代码。适用于用户请求搭建 Go 环境、安装模块,或在运行 Go 代码时遇到问题时使用。

SKILL.md
--- frontmatter
name: tzurot-code-quality
description: Contains MANDATORY code quality rules enforced by CI. MUST be consulted before refactoring or when hitting ESLint limits. Covers complexity thresholds, extraction patterns, and rule suppression.
lastUpdated: '2026-01-21'

Code Quality & Linting

Use this skill when: Fixing lint warnings, hitting complexity limits, refactoring large functions, or understanding ESLint rule philosophy.

Quick Reference

bash
# Run linting
pnpm lint           # Check all
pnpm lint:fix       # Auto-fix

# Check specific service
pnpm --filter @tzurot/api-gateway lint

ESLint Limits (eslint.config.js)

RuleLimitLevelFix Strategy
max-lines500ErrorSplit into modules
max-lines-per-function100WarnExtract helpers
complexity15WarnData-driven approach
max-depth4WarnEarly returns, extract
max-params5WarnOptions object pattern
max-statements30WarnExtract helpers
max-nested-callbacks3WarnUse async/await

Refactoring Patterns

Options Object Pattern (max-params fix)

typescript
// ❌ BAD - 6 parameters
function processMatch(
  ctx: MatchContext,
  fullMatch: string,
  persona: ResolvedPersona | null,
  logContext: Record<string, unknown>,
  refType: string,
  fallbackName?: string
): MatchResult { ... }

// ✅ GOOD - Options object
interface ProcessMatchOptions {
  ctx: MatchContext;
  fullMatch: string;
  persona: ResolvedPersona | null;
  logContext: Record<string, unknown>;
  refType: string;
  fallbackName?: string;
}

function processMatch(opts: ProcessMatchOptions): MatchResult {
  const { ctx, fullMatch, persona, logContext, refType, fallbackName } = opts;
  ...
}

Data-Driven Approach (complexity fix)

typescript
// ❌ BAD - High cyclomatic complexity from repeated if/else
function formatField(personality: Personality): string {
  let result = '';
  if (personality.characterInfo) {
    result += `<character_info>${personality.characterInfo}</character_info>`;
  }
  if (personality.personalityTraits) {
    result += `<personality_traits>${personality.personalityTraits}</personality_traits>`;
  }
  // ... 7 more similar blocks = complexity 10+
}

// ✅ GOOD - Data-driven, complexity stays at 2
const PERSONALITY_FIELDS = [
  { key: 'characterInfo', tag: 'character_info' },
  { key: 'personalityTraits', tag: 'personality_traits' },
  // ...
] as const;

function formatField(personality: Personality): string {
  return PERSONALITY_FIELDS.map(({ key, tag }) => {
    const value = personality[key];
    return value ? `<${tag}>${value}</${tag}>` : '';
  })
    .filter(Boolean)
    .join('\n');
}

Helper Extraction (max-statements fix)

typescript
// ❌ BAD - 40+ statements in one function
async function handleRequest(req: Request): Promise<Response> {
  // validation (10 statements)
  // business logic (15 statements)
  // response formatting (10 statements)
  // error handling (5 statements)
}

// ✅ GOOD - Split into focused helpers
async function handleRequest(req: Request): Promise<Response> {
  const validated = validateRequest(req); // 10 statements extracted
  const result = await processRequest(validated); // 15 statements extracted
  return formatResponse(result); // 10 statements extracted
}

Early Return Pattern (max-depth fix)

typescript
// ❌ BAD - Deep nesting
function process(data: Data | null): Result {
  if (data !== null) {
    if (data.isValid) {
      if (data.items.length > 0) {
        // actual logic at depth 4
      }
    }
  }
}

// ✅ GOOD - Early returns, flat structure
function process(data: Data | null): Result {
  if (data === null) return defaultResult;
  if (!data.isValid) return invalidResult;
  if (data.items.length === 0) return emptyResult;

  // actual logic at depth 1
}

TypeScript Strict Rules

RuleLevelAlternative
no-explicit-anyErrorUse unknown + type guards
no-unsafe-assignmentErrorValidate with Zod
no-non-null-assertionWarnUse optional chaining + nullish coalescing
strict-boolean-expressionsErrorBe explicit: !== null, !== undefined
typescript
// ❌ BAD
const data = response.json() as MyType;
if (data) { ... }

// ✅ GOOD
const data: unknown = await response.json();
const validated = MyTypeSchema.parse(data);
if (validated !== null) { ... }

When to Suppress Rules

OK to Suppress

typescript
// Generated code or external types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type PrismaPayload = any;

// Test utilities with intentional complexity
// eslint-disable-next-line max-lines-per-function
function createComplexTestFixture() { ... }

// One-off scripts (not production code)
/* eslint-disable complexity */

Never Suppress

  • Production business logic
  • API route handlers
  • Core services
  • Security-related code

If you need to suppress, ask: "Should I refactor instead?"

Pino Logger Format (ESLint Enforced)

Custom ESLint rule enforces correct pino format:

typescript
// ✅ CORRECT - Error object in first argument
logger.error({ err: error }, 'Failed to process request');
logger.warn({ err: error, userId }, 'User quota exceeded');
logger.info({ requestId, duration }, 'Request completed');

// ❌ WRONG - Will fail lint
logger.error(error, 'Failed to process');
logger.error('Failed:', error);

Error Handling Best Practices

Current Pattern (callGatewayApi)

typescript
const result = await callGatewayApi<PersonaResponse>('/user/persona', { userId });
if (!result.ok) {
  logger.warn({ error: result.error, status: result.status }, 'API call failed');
  await interaction.editReply(`❌ ${result.error}`);
  return;
}
// result.data is typed correctly here

Aspirational: Result Pattern for New Services

For complex domain logic, consider typed error returns:

typescript
type Result<T, E = string> = { ok: true; data: T } | { ok: false; error: E };

// Explicit error types at compile time
type GetUserError = 'NOT_FOUND' | 'FORBIDDEN' | 'INVALID_ID';

async function getUser(id: string): Promise<Result<User, GetUserError>> {
  if (!isValidId(id)) return { ok: false, error: 'INVALID_ID' };

  const user = await prisma.user.findUnique({ where: { id } });
  if (!user) return { ok: false, error: 'NOT_FOUND' };

  return { ok: true, data: user };
}

Common Lint Fixes

Promise Handling

typescript
// ❌ Floating promise
someAsyncFunction();

// ✅ Explicit handling
await someAsyncFunction();
void someAsyncFunction(); // Fire-and-forget (intentional)

Boolean Expressions

typescript
// ❌ Implicit boolean coercion
if (user) { ... }
if (items.length) { ... }

// ✅ Explicit checks
if (user !== null && user !== undefined) { ... }
if (items.length > 0) { ... }

Unused Variables

typescript
// ❌ Unused
const result = await fetch();

// ✅ Prefix with underscore if intentionally unused
const _result = await fetch();

Related Skills

  • tzurot-testing - Coverage requirements, test patterns
  • tzurot-architecture - Service boundaries, SRP
  • tzurot-git-workflow - Pre-push checks run lint
  • tzurot-types - Zod validation, type safety

References

  • ESLint config: eslint.config.js
  • TypeScript config: tsconfig.json
  • Prettier config: .prettierrc