// Comprehensive static code analysis to enforce architectural patterns, conventions, and code quality standards.
| name | code-audit |
| description | Comprehensive static code analysis to enforce architectural patterns, conventions, and code quality standards. |
Enforce code quality and consistency standards across the entire codebase through automated checks.
What it checks (19 checks, each with its own script):
What it doesn't check:
arch-audit skillThis template uses a core/domain separation:
Both follow the same patterns and rules. New features you create will be domain features.
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
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:
./slice.ts, ../models/session/actions.tsViolations:
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'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.ts files that re-export from other filesFix:
export const MyComponent: React.FC = () => { ... }Exceptions:
*.stories.tsx) - require default exports*.d.ts) - may use defaultRULE: 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:
useDispatch from react-reduxRootStateuseSelectorFix:
useWalletActions(), useBlogActions()useWallet(), useAuth()useTypedSelector from @/hooks/useTypedSelector for cross-feature stateAllowed 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 RootStateRULE: 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:
@/services/ethersV6/wallet/WalletAPI@/services/oauth/OAuthServiceFix:
IFeatureApi interfacesrc/config/(core|domain)/{feature}/services.tsAllowed files:
src/config/services.ts (root composition, if exists)src/config/(core|domain)/*/services.ts (feature-specific composition)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.debug('...'), console.log('...')className, id, href, srcException paths (developer tools, not user UI):
core/features/slice-manager/components/SliceDebugPanelcore/features/i18n/components/LangMenu/LangModaldomain/layout/ErrorFallbackRULE: 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:
<T> for reusable codeunknown for truly dynamic types (forces type guards)Exceptions:
*.d.ts) for external libraries*.test.ts) for mocking (prefer typed mocks)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-ignoreFix: Address the root cause, don't hide it.
Exceptions:
@ts-expect-error (fails if error is fixed) with detailed commentRULE: 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:
export interface declarationsexport type declarationsFix: Split into separate files.
Examples:
UserService.ts โ export class UserServiceFeatureConfig.ts โ export interface FeatureConfigConnectionState.ts โ export type ConnectionStateExceptions:
*.test.ts, *.spec.ts)*.d.ts) for external libraries*.stories.tsx)Breadcrumb.tsx can have BreadcrumbProps)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, BUGFix: Create GitHub issues, complete work, remove comments.
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 productionlog.info(), log.warn(), log.error() - controlled log levelsRULE: 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
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 constconst config = { ... } satisfies Configconst options = { mode: "light" } as constFix:
type Color = "red" | "blue";
const colors: Color[] = ["red", "blue"];
const config: Config = { ... };
enum Mode {
Light = "light",
Dark = "dark"
}
const options = { mode: Mode.Light };
Why This Matters:
as const and satisfies are lazy shortcutsBetter alternatives:
interface for object shapestype for unions, intersections, and aliasesenum for constant sets of valuesconst with explicit type annotationsRULE: 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.ts with re-exportsFix:
// Instead of re-exporting in index.ts
// โ export { UserService } from './UserService';
// Import directly from source
// โ
import { UserService } from './path/to/UserService';
Why This Matters:
The Rule:
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:
import { User, Data } from './types';
Why This Matters:
type keyword adds visual clutter without benefitThe Rule:
import type { X }import { type X }import { X }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 }} />Fix:
<div>{content}</div>
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }} />
Why This Matters:
The Rule:
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:
items.map((item, index) => <Item key={index} />)
items.map(item => <Item {...item} />)
Fix:
items.map(item => <Item key={item.id} {...item} />)
Why This Matters:
The Rule:
key prop when rendering lists with .map()item.id)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:
setTimeout(callback, 3600000); // What is 3600000?
await delay(5000); // 5000 what?
Fix:
const ONE_HOUR_MS = 3600000;
setTimeout(callback, ONE_HOUR_MS);
const FIVE_SECONDS_MS = 5000;
await delay(FIVE_SECONDS_MS);
Why This Matters:
Detection Focus:
The Rule:
{VALUE}_{UNIT}_MS (e.g., ONE_HOUR_MS, 30_SECONDS_MS)setTimeout(fn, 0))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.json missing "strict": true"strict": false in compilerOptionsFix:
{
"compilerOptions": {
"strict": true
}
}
What Strict Mode Includes:
Why This Matters:
The Rule:
"strict": true in tsconfig.jsonRULE: 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.
Empty [] but reactive values are used inside - will cause stale closures.
Violations:
i18n.resolvedLanguage with empty array:
useEffect(() => {
actions.fetchPosts({ language: i18n.resolvedLanguage });
}, []); // i18n.resolvedLanguage is used but not in deps!
Fix:
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.
These values are guaranteed stable by React/libraries and should NOT be in dependency arrays.
Violations:
useEffect(() => {
navigate('/home');
}, [isAuthenticated, navigate]); // navigate is stable!
Fix:
useEffect(() => {
navigate('/home');
}, [isAuthenticated]); // Only reactive values
Known Stable Values:
useState setters: setX, setState, etc.useReducer dispatchuseNavigate() from react-router: navigateuseTranslation() from i18next: tdispatchactions (from useActions())pageLink, homeRoute, pageRoutesRefThese hooks must be PURE - no side effects allowed.
Violations:
const data = useMemo(() => {
fetch('/api/data'); // WRONG! Side effect in useMemo
return processData();
}, [deps]);
const handler = useCallback(() => {
console.log('clicked'); // Side effect
doSomething();
}, []);
Fix:
// 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.location4+ dependencies may indicate over-specification or a need to refactor.
Warning:
useEffect(() => {
// Complex logic
}, [a, b, c, d, e]); // Too many deps - review
Fix:
useReducer for complex stateDirect API calls in useEffect miss caching, deduplication, and proper error handling.
Info:
useEffect(() => {
fetch('/api/users').then(setUsers); // Direct fetch
}, []);
Consider:
Note: actions.fetchX() via Redux Saga is OK - it triggers saga, not direct API call.
Why Avoid Direct Fetch:
Each check reports:
Reports are saved to reports/{date}/code-audit-report.md when using generate_report.mjs.
src/ directory