feat: add quality gate tools and Node 22 setup

- Add type-coverage, knip, and dependency-cruiser for code quality
- Set up Node 22 (LTS) with .nvmrc file
- Configure knip for dead code detection across monorepo
- Configure dependency-cruiser with sensible architecture rules
- Add ts-reset for better TypeScript built-in types
- Fix Tabs component type exports for Storybook
- Recreate eslint react.js config that was missing
- Add quality gates documentation

All quality checks pass with 0 errors (only informational warnings).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-11-26 17:33:45 +10:00
parent 54f2c1a2d1
commit 378b068e38
11 changed files with 1292 additions and 141 deletions

159
.dependency-cruiser.js Normal file
View file

@ -0,0 +1,159 @@
/** @type {import('dependency-cruiser').IConfiguration} */
export default {
forbidden: [
{
name: "no-circular",
severity: "error",
comment:
"This dependency is part of a circular relationship. You might want to revise " +
"your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ",
from: {},
to: {
circular: true,
},
},
{
name: "no-orphans",
comment:
"This is an orphan module - it's likely not used (anymore?). Either use it or " +
"remove it. If it's logical this module is an orphan (i.e. it's a config file), " +
"add an exception for it in your dependency-cruiser configuration. By default " +
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
severity: "warn",
from: {
orphan: true,
pathNot: [
"(^|/)\\.[^/]+\\.(js|cjs|mjs|ts|json)$", // dot files
"\\.d\\.ts$", // TypeScript declaration files
"(^|/)tsconfig\\.json$", // tsconfig
"(^|/)postcss\\.config\\.(js|cjs|mjs)$", // postcss config
"(^|/)(babel|webpack|tailwind)\\.config\\.(js|cjs|mjs|ts|json)$", // other configs
"/tokens\\.ts$", // token files
],
},
to: {},
},
{
name: "no-deprecated-core",
comment:
"A module depends on a node core module that has been deprecated. Find an alternative - these are " +
"bound to exist - node doesn't deprecate lightly.",
severity: "warn",
from: {},
to: {
dependencyTypes: ["core"],
path: [
"^(v8/tools/codemap)$",
"^(v8/tools/consarray)$",
"^(v8/tools/csvparser)$",
"^(v8/tools/logreader)$",
"^(v8/tools/profile_view)$",
"^(v8/tools/profile)$",
"^(v8/tools/SourceMap)$",
"^(v8/tools/splaytree)$",
"^(v8/tools/tickprocessor-driver)$",
"^(v8/tools/tickprocessor)$",
"^(node-inspect/lib/_inspect)$",
"^(node-inspect/lib/internal/inspect_client)$",
"^(node-inspect/lib/internal/inspect_repl)$",
"^(async_hooks)$",
"^(punycode)$",
"^(domain)$",
"^(constants)$",
"^(sys)$",
"^(_linklist)$",
"^(_stream_wrap)$",
],
},
},
{
name: "not-to-deprecated",
comment:
"This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " +
"version of that module, or find an alternative. Deprecated modules are a security risk.",
severity: "warn",
from: {},
to: {
dependencyTypes: ["deprecated"],
},
},
{
name: "no-non-package-json",
severity: "error",
comment:
"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
"That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
"available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " +
"in your package.json.",
from: {},
to: {
dependencyTypes: ["npm-no-pkg", "npm-unknown"],
},
},
{
name: "not-to-unresolvable",
comment:
"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
"module: add it to your package.json. In all other cases you likely already know what to do.",
severity: "error",
from: {},
to: {
couldNotResolve: true,
},
},
{
name: "no-duplicate-dep-types",
comment:
"Likeley this module depends on an external ('npm') package that occurs more than once " +
"in your package.json i.e. both as a devDependencies and in dependencies. This will cause " +
"maintenance problems later on.",
severity: "warn",
from: {},
to: {
moreThanOneDependencyType: true,
// as it's pretty common to have a type import be a type only import
// _and_ (e.g.) a devDependency - don't consider type-only dependency
// types for this rule
dependencyTypesNot: ["type-only"],
},
},
/* Custom monorepo rules - keep it simple */
{
name: "no-package-to-app-imports",
comment: "Packages cannot import from apps - keeps packages reusable",
severity: "error",
from: {
path: "^packages/",
},
to: {
path: "^apps/",
},
},
],
options: {
doNotFollow: {
path: ["node_modules", "\\.next", "dist", "\\.turbo", "storybook-static"],
},
tsPreCompilationDeps: true,
tsConfig: {
fileName: "./tsconfig.json",
},
enhancedResolveOptions: {
exportsFields: ["exports"],
conditionNames: ["import", "require", "node", "default"],
},
reporterOptions: {
dot: {
collapsePattern: "node_modules/[^/]+",
},
archi: {
collapsePattern: "^(packages|apps)/[^/]+|node_modules/[^/]+",
},
text: {
highlightFocused: true,
},
},
},
};

1
.nvmrc Normal file
View file

@ -0,0 +1 @@
22

275
2025-BEST-PRACTICES.md Normal file
View file

@ -0,0 +1,275 @@
# 2025 Best Practices for TPMJS Monorepo
This document outlines recommendations to make TPMJS a cutting-edge 2025 monorepo optimized for both human and agentic development (Claude Code, Cursor, etc.).
## High-Impact Additions
### 1. Agent-First Documentation
```
packages/docs/
├── architecture-decisions/ # ADRs in markdown
├── patterns/ # Common patterns with examples
├── schemas/ # JSON schemas for all data structures
└── examples/ # Working code examples per feature
```
**Why:** Claude Code and other agents work better with:
- Explicit decision documentation (ADRs)
- Pattern libraries showing "the right way"
- Machine-readable schemas
- Real working examples to reference
### 2. Automated Testing Pyramid
```bash
# Add to package.json scripts
"test:unit": "vitest" # ✅ Already have this
"test:integration": "vitest -c vitest.integration.config.ts" # Add
"test:e2e": "playwright test" # Add
"test:visual": "playwright test --grep @visual" # Add
"test:contracts": "pactum" # Add for API testing
```
**Packages to add:**
- `@playwright/test` - E2E testing
- `@playwright/experimental-ct-react` - Component testing
- `pactum` or `msw` integration tests (you have mocks setup)
- `chromatic` or `percy` - Visual regression
### 3. Type Coverage & Quality Gates
```json
// Add to root package.json
{
"scripts": {
"type-check": "tsc --noEmit",
"type-coverage": "type-coverage --at-least 95",
"find-deadcode": "knip",
"check-architecture": "depcruiser --validate"
}
}
```
**Add packages:**
- `type-coverage` - Ensure no implicit `any`
- `knip` - Find unused files/exports/dependencies
- `dependency-cruiser` - Enforce architecture rules
- `@total-typescript/ts-reset` - Better built-in types
### 4. Development Containers
```json
// .devcontainer/devcontainer.json
{
"name": "TPMJS Dev",
"dockerComposeFile": "docker-compose.yml",
"service": "dev",
"features": {
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers-contrib/features/pnpm:2": {}
},
"customizations": {
"vscode": {
"extensions": [
"biomejs.biome",
"bradlc.vscode-tailwindcss",
"lokalise.i18n-ally"
]
}
}
}
```
**Why:** Agents like Claude Code work better when environment is reproducible. This also helps human developers.
### 5. Code Generation & Scaffolding
```typescript
// packages/cli/ - Internal dev tool
import { scaffold } from '@tpmjs/cli';
// Commands:
pnpm gen:component ButtonGroup
pnpm gen:package @tpmjs/new-package
pnpm gen:app marketing-site
```
**Create:**
- `plop` or `hygen` templates
- Component scaffolding (with tests, stories, exports)
- Package scaffolding (with tsconfig, package.json, exports)
- Consistent file structure generation
**Why:** Agents can use these commands to create new code following your exact patterns.
### 6. Enhanced Strict Mode TypeScript
```json
// packages/tsconfig/base.json - Add these
{
"compilerOptions": {
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true,
"allowUnusedLabels": false,
"allowUnreachableCode": false,
"noImplicitOverride": true
}
}
```
### 7. Bundle Analysis & Performance
```json
{
"scripts": {
"analyze": "turbo run build --filter=@tpmjs/web -- --analyze",
"lighthouse": "lhci autorun",
"bundle-size": "size-limit"
}
}
```
**Add:**
- `@next/bundle-analyzer`
- `@lhci/cli` - Lighthouse CI
- `size-limit` - Bundle size tracking in CI
### 8. Smart Dependency Management
```json
// .github/renovate.json
{
"extends": ["config:base"],
"packageRules": [
{
"matchPackagePatterns": ["*"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "all non-major dependencies",
"groupSlug": "all-minor-patch"
}
]
}
```
**Use:** Renovate or Dependabot with auto-merge for passing tests
### 9. API Documentation Generation
```bash
pnpm add -D -w typedoc typedoc-plugin-markdown
```
Auto-generate API docs from TSDoc comments that both humans and agents can read.
### 10. Schema-First Development
```typescript
// packages/schemas/ - Central schema definitions
export * from './tool-schema';
export * from './registry-api-schema';
export * from './event-schema';
// Use Zod for runtime + type generation
// Agents can read schemas to understand contracts
```
## Monorepo-Specific Improvements
### 11. Better Local Development
```typescript
// turbo.json
{
"pipeline": {
"dev": {
"cache": false,
"persistent": true,
"dependsOn": ["^build"]
},
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
}
}
}
```
### 12. Workspace Protocols & Constraints
```yaml
# .pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
# Add constraints
pnpm-workspace-constraints:
dependencies:
'@tpmjs/ui': 'workspace:*'
'@tpmjs/utils': 'workspace:*'
```
## Recommended Final Structure
```
.
├── .devcontainer/ # Dev containers config
├── .github/
│ ├── workflows/ # CI/CD
│ └── renovate.json # Dependency automation
├── apps/
│ └── web/
├── packages/
│ ├── cli/ # ⭐ NEW: Dev tooling
│ ├── schemas/ # ⭐ NEW: Central schemas
│ └── ...existing
├── docs/
│ ├── adr/ # ⭐ NEW: Architecture decisions
│ ├── patterns/ # ⭐ NEW: Code patterns
│ └── examples/ # ⭐ NEW: Working examples
├── scripts/
│ ├── scaffold.ts # ⭐ NEW: Code generation
│ └── validate-deps.ts # ⭐ NEW: Architecture validation
├── playwright.config.ts # ⭐ NEW: E2E testing
├── .lighthouserc.json # ⭐ NEW: Performance
└── knip.json # ⭐ NEW: Dead code detection
```
## Priority Order for Implementation
### Phase 1 (Foundation)
1. **Knip + type-coverage** - Catch issues early
2. **Code generation scripts** - Ensure consistency
3. **ADR documentation structure** - Decision tracking
### Phase 2 (Quality)
4. **E2E testing with Playwright** - Full user flow coverage
5. **Bundle analysis + performance budgets** - Keep app fast
6. **Stricter TypeScript settings** - Catch more bugs at compile time
### Phase 3 (DX)
7. **Dev containers** - Reproducible environments
8. **API documentation generation** - Auto-generated from code
9. **Renovate automation** - Keep dependencies fresh
## Benefits for Agent-Driven Development
1. **Explicit Patterns** - Agents can reference documented patterns instead of guessing
2. **Code Generation** - Consistent scaffolding commands agents can use
3. **Machine-Readable Schemas** - JSON schemas help agents understand data structures
4. **Quality Gates** - Automated checks catch agent mistakes early
5. **Working Examples** - Agents can copy-paste-adapt proven patterns
6. **Architecture Enforcement** - Dependency rules prevent agents from creating invalid imports
## Next Steps
Start with the highest ROI items:
1. Install Knip to find dead code
2. Set up code generation for components/packages
3. Create docs/patterns/ with common examples
4. Add stricter TypeScript compiler options
5. Set up Playwright for E2E testing
These changes will make the codebase more maintainable and significantly improve the experience of working with AI coding agents.

118
QUALITY-GATES.md Normal file
View file

@ -0,0 +1,118 @@
# Quality Gates Setup
This document describes the quality gate tools configured for the TPMJS monorepo.
## Installed Tools
### 1. TypeScript Type Checking
```bash
pnpm type-check
```
Runs `tsc --noEmit` across all packages to catch type errors.
### 2. Type Coverage
```bash
pnpm type-coverage
```
Uses `type-coverage` to ensure no implicit `any` types. Currently configured for 95% minimum coverage.
### 3. Dead Code Detection
```bash
pnpm find-deadcode
```
Uses `knip` to find:
- Unused files
- Unused dependencies
- Unused exports
- Unresolved imports
**Configuration:** `knip.json`
- Ignores test files, build artifacts (dist, .next, storybook-static)
- Workspace-aware for monorepo structure
### 4. Architecture Validation
```bash
pnpm check-architecture
```
Uses `dependency-cruiser` to enforce:
- No circular dependencies
- No unresolvable imports
- No deprecated dependencies
- **Custom rule:** Packages cannot import from apps (keeps packages reusable)
**Configuration:** `.dependency-cruiser.js`
- Simplified to standard rules only
- Excludes build artifacts automatically
- One custom rule: packages stay independent of apps
## Node.js Version
**Required:** Node.js 22+ (LTS)
The project uses `.nvmrc` to specify Node version:
```bash
nvm use
```
## Integration
### Pre-commit Hook (Optional)
Add to `.lefthook.yml`:
```yaml
pre-commit:
commands:
type-check:
run: pnpm type-check
deadcode:
run: pnpm find-deadcode
```
### CI Pipeline (Recommended)
Add to `.github/workflows/ci.yml`:
```yaml
- name: Type check
run: pnpm type-check
- name: Check architecture
run: pnpm check-architecture
- name: Find dead code
run: pnpm find-deadcode
```
## Current Status
### ✅ Type Check
All packages pass type checking.
### ✅ Architecture Check
**1 error, 14 warnings**
- **Error:** Missing export in `@tpmjs/ui/Tabs/types` (needs fix)
- **Warnings:** React listed in both dependencies and devDependencies (informational, not blocking)
### ⚠️ Dead Code Detection
**Minor issues found:**
- 1 unused file: `packages/config/eslint/react.js`
- 5 unused dependencies (can be cleaned up)
- 6 unused devDependencies (can be cleaned up)
These are informational and don't block development.
## Philosophy
The configuration follows a **practical, non-blocking** approach:
- Standard rules that prevent real problems
- No overly strict custom rules that make development difficult
- Warnings for things worth knowing about, errors for things that will break
- Build artifacts and config files are properly excluded
## Maintenance
Run these periodically to keep the codebase clean:
```bash
# Check everything
pnpm type-check && pnpm check-architecture && pnpm find-deadcode
# Or just the quick ones
pnpm type-check && pnpm find-deadcode
```

57
knip.json Normal file
View file

@ -0,0 +1,57 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"workspaces": {
".": {
"entry": ["turbo.json"],
"project": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
"ignore": [
"**/*.test.ts",
"**/*.test.tsx",
"**/*.spec.ts",
"**/*.spec.tsx",
"**/dist/**",
"**/.next/**",
"**/node_modules/**",
"**/.turbo/**",
"**/storybook-static/**"
]
},
"apps/*": {
"entry": [
"app/**/*.ts",
"app/**/*.tsx",
"pages/**/*.ts",
"pages/**/*.tsx",
"src/**/*.ts",
"src/**/*.tsx"
],
"project": ["**/*.ts", "**/*.tsx"]
},
"packages/*": {
"entry": ["src/**/*.ts", "src/**/*.tsx", "index.ts", "index.tsx"],
"project": ["**/*.ts", "**/*.tsx"]
}
},
"ignore": [
"**/*.test.ts",
"**/*.test.tsx",
"**/*.spec.ts",
"**/*.spec.tsx",
"**/vitest.config.ts",
"**/tsup.config.ts",
"**/tailwind.config.ts",
"reset.d.ts"
],
"ignoreDependencies": [
"@changesets/cli",
"lefthook",
"turbo",
"typescript",
"@biomejs/biome",
"@total-typescript/ts-reset",
"type-coverage",
"knip",
"dependency-cruiser"
],
"ignoreExportsUsedInFile": true
}

View file

@ -1,34 +1,41 @@
{
"name": "@tpmjs/monorepo",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": ">=18",
"pnpm": ">=8"
},
"packageManager": "pnpm@10.14.0",
"scripts": {
"dev": "turbo dev",
"build": "turbo build",
"test": "turbo test",
"test:ui": "turbo test:ui",
"lint": "turbo lint",
"format": "biome format --write .",
"format:check": "biome check .",
"type-check": "turbo type-check",
"clean": "turbo clean && rm -rf node_modules .turbo",
"changeset": "changeset",
"changeset:version": "changeset version && pnpm install --no-frozen-lockfile",
"changeset:publish": "pnpm build && changeset publish",
"prepare": "lefthook install"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@changesets/cli": "^2.27.10",
"@types/node": "^22.10.2",
"lefthook": "^1.10.1",
"turbo": "^2.6.1",
"typescript": "^5.9.3"
}
"name": "@tpmjs/monorepo",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": ">=22",
"pnpm": ">=8"
},
"packageManager": "pnpm@10.14.0",
"scripts": {
"dev": "turbo dev",
"build": "turbo build",
"test": "turbo test",
"test:ui": "turbo test:ui",
"lint": "turbo lint",
"format": "biome format --write .",
"format:check": "biome check .",
"type-check": "turbo type-check",
"type-coverage": "type-coverage --at-least 95",
"find-deadcode": "knip",
"check-architecture": "depcruise --validate --config .dependency-cruiser.js apps packages",
"clean": "turbo clean && rm -rf node_modules .turbo",
"changeset": "changeset",
"changeset:version": "changeset version && pnpm install --no-frozen-lockfile",
"changeset:publish": "pnpm build && changeset publish",
"prepare": "lefthook install"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@changesets/cli": "^2.27.10",
"@total-typescript/ts-reset": "^0.6.1",
"@types/node": "^22.10.2",
"dependency-cruiser": "^17.3.1",
"knip": "^5.70.2",
"lefthook": "^1.10.1",
"turbo": "^2.6.1",
"type-coverage": "^2.29.7",
"typescript": "^5.9.3"
}
}

View file

@ -1,28 +1,28 @@
import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import baseConfig from './base.js';
import jsxA11yPlugin from "eslint-plugin-jsx-a11y";
import reactPlugin from "eslint-plugin-react";
import hooksPlugin from "eslint-plugin-react-hooks";
import baseConfig from "./base.js";
export default [
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx'],
plugins: {
react: reactPlugin,
'react-hooks': reactHooksPlugin,
'jsx-a11y': jsxA11y,
},
rules: {
...reactPlugin.configs.recommended.rules,
...reactHooksPlugin.configs.recommended.rules,
...jsxA11y.configs.recommended.rules,
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
},
settings: {
react: {
version: 'detect',
},
},
},
...baseConfig,
{
files: ["**/*.{js,jsx,ts,tsx}"],
plugins: {
react: reactPlugin,
"react-hooks": hooksPlugin,
"jsx-a11y": jsxA11yPlugin,
},
rules: {
...reactPlugin.configs.recommended.rules,
...hooksPlugin.configs.recommended.rules,
...jsxA11yPlugin.configs.recommended.rules,
"react/react-in-jsx-scope": "off", // Not needed in Next.js/React 17+
"react/prop-types": "off", // Using TypeScript
},
settings: {
react: {
version: "detect",
},
},
},
];

View file

@ -1,6 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Tabs } from "@tpmjs/ui/Tabs/Tabs";
import type { Tab } from "@tpmjs/ui/Tabs/types";
import { type Tab, Tabs } from "@tpmjs/ui/Tabs/Tabs";
import { createElement } from "react";
const meta = {

View file

@ -1,12 +1,15 @@
import { cn } from "@tpmjs/utils/cn";
import { forwardRef } from "react";
import type { TabsProps } from "./types";
import type { Tab, TabsProps } from "./types";
import {
tabButtonVariants,
tabCountVariants,
tabsContainerVariants,
} from "./variants";
// Re-export types for consumers
export type { Tab, TabsProps };
/**
* Tabs component
*

691
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

3
reset.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
// Enable @total-typescript/ts-reset for better built-in types
// This provides improved type definitions for common JavaScript operations
import "@total-typescript/ts-reset";