| name | code-audit |
| description | Comprehensive static code analysis to enforce architectural patterns, conventions, and code quality standards. |
Purpose
Enforce code quality and consistency standards across the entire codebase through automated checks.
What it checks (19 checks, each with its own script):
- Path alias usage (no relative imports to aliased dirs)
- Export patterns (no default exports, no index files)
- Redux abstraction (components use hooks, not direct Redux)
- Service isolation (dependency injection pattern)
- i18n coverage (all UI text wrapped in t())
- Type safety (no "any" type)
- No linter/TypeScript suppressions
- No god files (1 entity per file)
- No TODO/FIXME/HACK comments
- No console usage (use loglevel instead)
- Redux saga patterns (efficient parallelism)
- No type assertions (no "as const", no "satisfies")
- No re-exports (import directly from source)
- No "type" keyword in imports (plain imports only)
- No dangerouslySetInnerHTML (XSS vulnerability)
- React key patterns (no array index as key, no missing keys)
- No magic numbers (use named constants)
- TypeScript strict mode enabled (tsconfig.json)
- Dependency array patterns (useEffect, useMemo, useCallback)
What it doesn't check:
- Feature dependency rules (core → domain) - see
arch-auditskill
Architecture Context
This template uses a core/domain separation:
- core/features/* - Infrastructure features (app, i18n, router, slice-manager, ui, auth, components, layout)
- domain/features/* - Business features (wallet, oauth, blog-demo, ai-assistant, site)
Both follow the same patterns and rules. New features you create will be domain features.
Running Checks
All checks:
node ./.claude/skills/code-audit/scripts/run_all_checks.mjs
Generate report:
node ./.claude/skills/code-audit/scripts/generate_report.mjs
Individual checks:
node ./.claude/skills/code-audit/scripts/check_imports.mjs
node ./.claude/skills/code-audit/scripts/check_exports.mjs
node ./.claude/skills/code-audit/scripts/check_redux_abstraction.mjs
node ./.claude/skills/code-audit/scripts/check_service_imports.mjs
node ./.claude/skills/code-audit/scripts/check_i18n_coverage.mjs
node ./.claude/skills/code-audit/scripts/check_any_usage.mjs
node ./.claude/skills/code-audit/scripts/check_suppressions.mjs
node ./.claude/skills/code-audit/scripts/check_god_files.mjs
node ./.claude/skills/code-audit/scripts/check_todos.mjs
node ./.claude/skills/code-audit/scripts/check_logs.mjs
node ./.claude/skills/code-audit/scripts/check_saga_patterns.mjs
node ./.claude/skills/code-audit/scripts/check_type_assertions.mjs
node ./.claude/skills/code-audit/scripts/check_reexports.mjs
node ./.claude/skills/code-audit/scripts/check_type_imports.mjs
node ./.claude/skills/code-audit/scripts/check_dangerous_html.mjs
node ./.claude/skills/code-audit/scripts/check_react_keys.mjs
node ./.claude/skills/code-audit/scripts/check_magic_numbers.mjs
node ./.claude/skills/code-audit/scripts/check_strict_mode.mjs
node ./.claude/skills/code-audit/scripts/check_dep_arrays.mjs
Quality Rules
1. Path Alias Imports
RULE: Use absolute path aliases (@/features/*, @/services/*, etc.) instead of relative imports when crossing directory boundaries.
Why: Makes imports clear, prevents broken paths when moving files, enables IDE navigation.
Allowed:
- ✅ Internal imports within same feature:
./slice.ts,../models/session/actions.ts - ✅ Imports within same service/page/hook directory
Violations:
- ❌
import { useAuth } from '../../features/oauth/hooks/useAuth' - ❌
import { api } from '../services/api'
Fix:
- ✅
import { useAuth } from '@/core/features/oauth/hooks/useAuth' - ✅
import { api } from '@/services/api'
2. Export Patterns
RULE: Use named exports only. No default exports, no index.ts barrel files.
Why: Makes refactoring safer, imports explicit, no ambiguity.
Violations:
- ❌
export default function MyComponent() { ... } - ❌
index.tsfiles that re-export from other files
Fix:
- ✅
export const MyComponent: React.FC = () => { ... } - ✅ Import directly from source file
Exceptions:
- Storybook files (
*.stories.tsx) - require default exports - Type definition files (
*.d.ts) - may use default
3. Redux Abstraction
RULE: Components NEVER import useDispatch, useSelector, or RootState directly. They use feature hooks.
Why: Abstracts Redux implementation, components don't know about state management.
Pattern:
Components → Feature Hooks → Redux
(NEVER: Components → Redux directly)
Violations:
- ❌ Component imports
useDispatchfromreact-redux - ❌ Component imports
RootState - ❌ Component uses
useSelector
Fix:
- ✅ Use feature action hooks:
useWalletActions(),useBlogActions() - ✅ Use feature state hooks:
useWallet(),useAuth() - ✅ Use
useTypedSelectorfrom@/hooks/useTypedSelectorfor cross-feature state
Allowed files (these ARE the abstraction layer):
(core|domain)/features/*/hooks/*.ts- can use useDispatch, useSelector, RootStatesrc/hooks/*.ts- can use useSelector, RootState(core|domain)/features/*/models/*/actionEffects/*.ts- can use RootState
4. Service Import Boundaries
RULE: Services (@/services/*) are ONLY imported in composition root (src/config/(core|domain)/*/services.ts).
Why: Dependency injection pattern - features receive services through interfaces, easy to swap implementations.
Violations:
- ❌ Feature imports
@/services/ethersV6/wallet/WalletAPI - ❌ Page imports
@/services/oauth/OAuthService
Fix:
- ✅ Feature defines
IFeatureApiinterface - ✅ Service instantiated in
src/config/(core|domain)/{feature}/services.ts - ✅ Feature receives service through interface
Allowed files:
src/config/services.ts(root composition, if exists)src/config/(core|domain)/*/services.ts(feature-specific composition)
5. i18n Coverage
RULE: All user-facing text must be wrapped in t() function for translation.
Why: Enables multi-language support, i18next tooling extracts text.
Violations:
- ❌
<Button>Click me</Button> - ❌
const message = "Error occurred"
Fix:
- ✅
<Button>{t('Click me')}</Button> - ✅
const message = t('Error occurred')
Excluded (not user-facing):
- Log statements:
log.debug('...'),console.log('...') - HTML attributes:
className,id,href,src - CSS values, variable names, paths
- Infrastructure files (main.tsx, error boundaries, debug panels)
Exception paths (developer tools, not user UI):
core/features/slice-manager/components/SliceDebugPanelcore/features/i18n/components/LangMenu/LangModaldomain/layout/ErrorFallback- OAuth callback handlers
6. TypeScript "any" Type
RULE: Never use any type. Use proper types, generics, or unknown.
Why: Defeats TypeScript's type safety, allows runtime errors.
Violations:
- ❌
function process(data: any) { ... } - ❌
const items: any[] = [...]
Fix:
- ✅ Define proper interfaces/types
- ✅ Use generics:
<T>for reusable code - ✅ Use
unknownfor truly dynamic types (forces type guards)
Exceptions:
- Type definition files (
*.d.ts) for external libraries - Test files (
*.test.ts) for mocking (prefer typed mocks)
7. Linter/TypeScript Suppressions
RULE: Never suppress errors with comments. Fix the underlying issue.
Why: Suppressions hide real bugs, accumulate technical debt.
Violations:
- ❌
// @ts-ignore - ❌
// @ts-nocheck - ❌
// eslint-disable - ❌
// prettier-ignore
Fix: Address the root cause, don't hide it.
Exceptions:
- Test files may have legitimate suppressions
- If absolutely necessary, use
@ts-expect-error(fails if error is fixed) with detailed comment
8. God Files (1 Entity Per File)
RULE: Each file exports exactly ONE entity (interface, type, class, enum). File name matches entity name.
Why: Easy to find, clear purpose, follows Single Responsibility Principle.
Violations:
- ❌ File with multiple
export interfacedeclarations - ❌ File with multiple
export typedeclarations
Fix: Split into separate files.
Examples:
UserService.ts→export class UserServiceFeatureConfig.ts→export interface FeatureConfigConnectionState.ts→export type ConnectionState
Exceptions:
- Test files (
*.test.ts,*.spec.ts) - Type definitions (
*.d.ts) for external libraries - Storybook files (
*.stories.tsx) - React component files with props interfaces (e.g.,
Breadcrumb.tsxcan haveBreadcrumbProps) - Specific exception paths (see script for list)
9. TODO/FIXME/HACK Comments
RULE: No technical debt markers in code. Track work in issue tracker instead.
Why: Markers indicate incomplete work, forgotten tasks, or known bugs.
Detected:
TODO,FIXME,HACK,XXX,BUG
Fix: Create GitHub issues, complete work, remove comments.
10. Console Usage
RULE: No console.* statements in production code. Use log.* from loglevel.
Why: Console statements can't be controlled in production, expose debug info.
Violations:
- ❌
console.log(),console.error(),console.warn()
Fix:
- ✅
log.debug()- auto-disabled in production - ✅
log.info(),log.warn(),log.error()- controlled log levels
11. Redux Saga Patterns
RULE: Use single yield all([...]) for parallel operations. Multiple yield all in same function is inefficient.
Why: True parallelism requires combining effects into one yield all.
Violation:
yield all([effect1, effect2]);
yield all([effect3, effect4]); // Sequential, not parallel!
Fix:
yield all([effect1, effect2, effect3, effect4]); // Truly parallel
12. No Type Assertions
RULE: Never use as const or satisfies. Use proper types, interfaces, or enums instead.
Why: Type assertions are shortcuts that reduce code clarity, reusability, and maintainability. Proper type definitions are self-documenting and enforce better architecture.
Violations:
- ❌
const colors = ["red", "blue"] as const - ❌
const config = { ... } satisfies Config - ❌
const options = { mode: "light" } as const
Fix:
- ✅ Define proper types:
type Color = "red" | "blue"; const colors: Color[] = ["red", "blue"]; - ✅ Use explicit type annotations:
const config: Config = { ... }; - ✅ Use enums for constant sets:
enum Mode { Light = "light", Dark = "dark" } const options = { mode: Mode.Light };
Why This Matters:
as constandsatisfiesare lazy shortcuts- They bypass proper type definition and reusability
- Makes code harder to understand and maintain
- Prevents type reuse across the codebase
- Reduces IDE autocomplete effectiveness
Better alternatives:
interfacefor object shapestypefor unions, intersections, and aliasesenumfor constant sets of valuesconstwith explicit type annotations- Proper TypeScript types that are reusable and self-documenting
13. No Re-exports
RULE: Never use re-export statements. Import directly from source files instead of re-exporting from intermediate files.
Why: Re-exports create indirection, make code harder to navigate, and obscure actual dependencies. Direct imports make the codebase more transparent and easier to refactor.
Violations:
- ❌
export { Something } from './somewhere' - ❌
export * from './somewhere' - ❌
export * as namespace from './somewhere' - ❌
export type { TypeName } from './somewhere' - ❌ Index files that re-export:
index.tswith re-exports
Fix:
- ✅ Import directly from source files:
// Instead of re-exporting in index.ts // ❌ export { UserService } from './UserService'; // Import directly from source // ✅ import { UserService } from './path/to/UserService';
Why This Matters:
- Re-exports create unnecessary layers of indirection
- Makes it harder to find where code is actually defined
- IDE "Go to Definition" jumps to re-export, not actual source
- Refactoring becomes harder (must update re-export files)
- Violates "import from source" principle
The Rule:
- Import directly from the file where entity is defined
- No barrel files (index.ts with re-exports)
- No re-export statements anywhere in codebase
14. No "type" Keyword in Imports
RULE: Never use the type keyword in import statements. TypeScript automatically removes type-only imports during compilation.
Why: The type keyword is redundant visual noise. TypeScript's compiler can automatically detect and remove type-only imports without the keyword, making code cleaner and simpler.
Violations:
- ❌
import type { User } from './types' - ❌
import { type User } from './types' - ❌
import { Data, type User } from './types'(mixed)
Fix:
- ✅ Plain imports for everything:
import { User, Data } from './types';
Why This Matters:
typekeyword adds visual clutter without benefit- TypeScript compiler handles type erasure automatically
- Simpler, cleaner import statements
- Consistent import style across entire codebase
- One less thing to think about when writing imports
The Rule:
- Always use plain import syntax
- Let TypeScript handle type-only import optimization
- No
import type { X } - No
import { type X } - Just use
import { X }
15. No dangerouslySetInnerHTML
RULE: Never use dangerouslySetInnerHTML - it bypasses React's XSS protection.
Why: Opens XSS vulnerabilities, allows arbitrary HTML injection, user-controlled content can execute malicious scripts.
Violations:
- ❌
<div dangerouslySetInnerHTML={{ __html: userContent }} /> - ❌ Any use of dangerouslySetInnerHTML prop
Fix:
- ✅ Use React's default rendering (auto-escapes):
<div>{content}</div> - ✅ If HTML rendering is absolutely required, sanitize first:
import DOMPurify from 'dompurify'; <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }} />
Why This Matters:
- React automatically escapes all content by default (XSS protection)
- dangerouslySetInnerHTML bypasses this protection
- Critical security vulnerability if user input is rendered
- Name literally says "dangerous" for a reason
The Rule:
- Avoid dangerouslySetInnerHTML entirely if possible
- If absolutely necessary, sanitize with DOMPurify
- Never use with user-controlled content without sanitization
16. React Key Patterns
RULE: Always use stable, unique identifiers as keys in lists. Never use array index or omit keys.
Why: Using array index as key causes bugs when list order changes. Missing keys cause React warnings and unpredictable re-renders.
Violations:
- ❌ Array index as key:
items.map((item, index) => <Item key={index} />) - ❌ Missing key entirely:
items.map(item => <Item {...item} />)
Fix:
- ✅ Use stable unique identifier from data:
items.map(item => <Item key={item.id} {...item} />)
Why This Matters:
- Index as key: When list order changes (sort, filter, reorder), React cannot track which element is which
- Leads to wrong elements being re-rendered or updated
- Can cause state to be attached to wrong elements
- Performance issues from unnecessary re-renders
- Missing key: React shows warnings, unpredictable behavior, poor reconciliation
The Rule:
- Always provide a
keyprop when rendering lists with.map() - Use a stable, unique identifier (usually
item.id) - Never use array index as key
- Key must be unique among siblings
17. No Magic Numbers
RULE: Never use magic numbers - use named constants instead.
Why: Magic numbers make code harder to understand, difficult to maintain and update, no semantic meaning without context.
Focus: Time-related values (setTimeout, setInterval, delays)
Violations:
- ❌ Magic number in setTimeout:
setTimeout(callback, 3600000); // What is 3600000? - ❌ Magic number in delay/retry logic:
await delay(5000); // 5000 what?
Fix:
- ✅ Named constant:
const ONE_HOUR_MS = 3600000; setTimeout(callback, ONE_HOUR_MS); const FIVE_SECONDS_MS = 5000; await delay(FIVE_SECONDS_MS);
Why This Matters:
- Self-documenting code
- Easy to find and update all usages
- Clear intent and meaning
- Prevents errors from typos
- Easier maintenance
Detection Focus:
- setTimeout/setInterval with values >= 1000ms (1 second)
- Delay/wait/retry functions with large values
- Config files are exempted (often contain configuration numbers)
The Rule:
- Use named constants for time values
- Format:
{VALUE}_{UNIT}_MS(e.g.,ONE_HOUR_MS,30_SECONDS_MS) - Exception: Very small, obvious values (e.g.,
setTimeout(fn, 0))
18. TypeScript Strict Mode
RULE: TypeScript's strict mode must be enabled in tsconfig.json.
Why: Enables 8+ critical type safety checks, catches errors at compile time, industry best practice.
Violation:
- ❌
tsconfig.jsonmissing"strict": true - ❌
"strict": falsein compilerOptions - ❌ No compilerOptions in tsconfig.json
Fix:
- ✅ In tsconfig.json, add or update:
{ "compilerOptions": { "strict": true } }
What Strict Mode Includes:
- noImplicitAny - Prevents implicit "any" types
- noImplicitThis - Requires explicit "this" typing
- alwaysStrict - ECMAScript strict mode in all files
- strictBindCallApply - Validates call/bind/apply arguments
- strictNullChecks - Enforces null/undefined checking
- strictFunctionTypes - Stricter function type checking
- strictPropertyInitialization - Ensures class properties are initialized
- useUnknownInCatchVariables - Catch variables are "unknown" not "any"
Why This Matters:
- Catches type errors at compile time instead of runtime
- Better IDE autocomplete and intellisense
- Self-documenting code with explicit types
- Easier refactoring with type safety
- Industry best practice for professional TypeScript projects
The Rule:
- Always enable
"strict": truein tsconfig.json - Required for production-ready TypeScript code
- Cannot be disabled or set to false
19. React Hook Dependency Arrays
RULE: Dependency arrays must be correct - no missing reactive values, no stable values, no side effects in memoization hooks.
Why: Incorrect dependency arrays cause stale closures, unnecessary re-renders, memory leaks, and bugs that are hard to debug.
5 Sub-Checks:
CHECK 1: Missing Dependencies (HIGH)
Empty [] but reactive values are used inside - will cause stale closures.
Violations:
- ❌ Using
i18n.resolvedLanguagewith empty array:useEffect(() => { actions.fetchPosts({ language: i18n.resolvedLanguage }); }, []); // i18n.resolvedLanguage is used but not in deps!
Fix:
- ✅ Add reactive values to dependency array:
useEffect(() => { actions.fetchPosts({ language: i18n.resolvedLanguage }); }, [i18n.resolvedLanguage]); // Will re-run when language changes
Reactive Patterns Detected:
i18n.resolvedLanguage,i18n.language(language changes)props.*(prop access)
Note: t function is stable and should NOT be in deps. If you need to react to language changes, use i18n.resolvedLanguage.
CHECK 2: Stable Values in Dependencies (HIGH)
These values are guaranteed stable by React/libraries and should NOT be in dependency arrays.
Violations:
- ❌ Stable values in deps:
useEffect(() => { navigate('/home'); }, [isAuthenticated, navigate]); // navigate is stable!
Fix:
- ✅ Remove stable values:
useEffect(() => { navigate('/home'); }, [isAuthenticated]); // Only reactive values
Known Stable Values:
useStatesetters:setX,setState, etc.useReducerdispatchuseNavigate()from react-router:navigateuseTranslation()from i18next:t- Redux dispatch:
dispatch - Custom action hooks:
actions(fromuseActions()) - Route hooks:
pageLink,homeRoute,pageRoutes - Refs: any variable ending with
Ref
CHECK 3: Side Effects in useMemo/useCallback (HIGH)
These hooks must be PURE - no side effects allowed.
Violations:
- ❌ Fetch in useMemo:
const data = useMemo(() => { fetch('/api/data'); // WRONG! Side effect in useMemo return processData(); }, [deps]); - ❌ Console.log in useCallback:
const handler = useCallback(() => { console.log('clicked'); // Side effect doSomething(); }, []);
Fix:
- ✅ Move side effects to useEffect or Redux Saga:
// useMemo should be pure const processed = useMemo(() => processData(rawData), [rawData]); // Side effects go in useEffect useEffect(() => { fetch('/api/data').then(setData); }, []);
Side Effects Detected:
fetch(),axios.*callsconsole.log/warn/error/infolocalStorage.*,sessionStorage.*document.*,window.location
CHECK 4: Over-specified Arrays (WARNING)
4+ dependencies may indicate over-specification or a need to refactor.
Warning:
- ⚠️ 4+ deps:
useEffect(() => { // Complex logic }, [a, b, c, d, e]); // Too many deps - review
Fix:
- Consider extracting logic to a custom hook
- Consider using
useReducerfor complex state - Review if all deps are truly needed
CHECK 5: Direct Fetch in useEffect (INFO)
Direct API calls in useEffect miss caching, deduplication, and proper error handling.
Info:
- ℹ️ Direct fetch detected:
useEffect(() => { fetch('/api/users').then(setUsers); // Direct fetch }, []);
Consider:
- React Query or SWR for data fetching
- Redux Saga for side effects (project pattern)
Note: actions.fetchX() via Redux Saga is OK - it triggers saga, not direct API call.
Why Avoid Direct Fetch:
- No automatic caching or deduplication
- Race conditions on fast navigation
- No automatic retry on failure
- Manual loading/error state management
- No SSR/SSG support
Output Format
Each check reports:
- File path and line number
- Violation description
- Suggested fix
- Count of total violations
Reports are saved to reports/{date}/code-audit-report.md when using generate_report.mjs.
Tools
- Bash: Run Node.js scripts
- Read: Inspect source files
- Write: Generate reports (optional)
Safety
- Read-only operation (unless generating reports)
- No source file modifications
- No external network calls
- Comprehensive scan of entire
src/directory