mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 04:15:52 +00:00
docs: add comprehensive documentation suite for modular architecture
Add five documentation files reflecting Phase 5 UI modularization: - codebase-summary.md: Complete directory structure for CLI and UI - code-standards.md: 200-line file limit, barrel exports, naming conventions - system-architecture.md: ASCII architecture diagrams and data flows - project-roadmap.md: Completed phases 1-5, future work - project-overview-pdr.md: Product development requirements
This commit is contained in:
1 parent
c70ba89b43
commit
1ffd169b98
5 files changed
+1998
No files matched your search
@@ -0,0 +1,503 @@
|
||||
# CCS Code Standards
|
||||
|
||||
Last Updated: 2025-12-19
|
||||
|
||||
Code standards, modularization patterns, and conventions for the CCS codebase.
|
||||
|
||||
---
|
||||
|
||||
## Core Principles
|
||||
|
||||
### YAGNI (You Aren't Gonna Need It)
|
||||
- No features "just in case"
|
||||
- Only implement what is currently needed
|
||||
- Delete unused code rather than commenting it out
|
||||
|
||||
### KISS (Keep It Simple, Stupid)
|
||||
- Prefer simple solutions over clever ones
|
||||
- Reduce complexity at every opportunity
|
||||
- Use established patterns over custom implementations
|
||||
|
||||
### DRY (Don't Repeat Yourself)
|
||||
- One source of truth for configuration
|
||||
- Extract common logic into shared utilities
|
||||
- Use barrel exports to centralize imports
|
||||
|
||||
---
|
||||
|
||||
## File Organization
|
||||
|
||||
### Directory Structure Rules
|
||||
|
||||
1. **Domain-based organization**: Group files by business domain, not by file type
|
||||
2. **Barrel exports required**: Every directory must have an `index.ts` aggregating exports
|
||||
3. **Flat within depth**: Keep nesting to 3 levels maximum
|
||||
4. **Co-location**: Keep related files together (component + hooks + utils)
|
||||
|
||||
### File Naming Conventions
|
||||
|
||||
| Convention | Example | When to Use |
|
||||
|------------|---------|-------------|
|
||||
| kebab-case | `cliproxy-executor.ts` | All TypeScript/TSX files |
|
||||
| kebab-case | `profile-detector.ts` | Multi-word file names |
|
||||
| PascalCase | `BinaryManager` | Class exports only |
|
||||
| camelCase | `detectProfile` | Function exports |
|
||||
|
||||
**File names should be descriptive**: LLMs should understand the file's purpose from its name alone without reading content.
|
||||
|
||||
### Correct Examples
|
||||
|
||||
```
|
||||
src/cliproxy/binary-manager.ts # Binary management logic
|
||||
src/commands/doctor-command.ts # Doctor CLI command handler
|
||||
ui/src/components/cliproxy/provider-editor/index.tsx
|
||||
```
|
||||
|
||||
### Incorrect Examples
|
||||
|
||||
```
|
||||
src/utils/helper.ts # Too vague
|
||||
src/cliproxy/manager.ts # Which manager?
|
||||
ui/src/components/Editor.tsx # Not kebab-case
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Size Limit: 200 Lines
|
||||
|
||||
**Target**: All code files should be under 200 lines.
|
||||
|
||||
**Exceptions** (with justification):
|
||||
- Data files (model-pricing.ts, model-catalog.ts)
|
||||
- Entry points with routing logic (ccs.ts)
|
||||
- Complex transformation logic that cannot be meaningfully split
|
||||
|
||||
### Why 200 Lines?
|
||||
|
||||
1. **Context efficiency**: LLMs process smaller files faster
|
||||
2. **Single responsibility**: Forces focused, testable modules
|
||||
3. **Navigation**: Easier to scan and understand
|
||||
4. **Maintainability**: Reduces merge conflicts
|
||||
|
||||
### When Files Exceed 200 Lines
|
||||
|
||||
If a file grows beyond 200 lines:
|
||||
|
||||
1. **Identify extraction candidates**:
|
||||
- Helper functions that could be utilities
|
||||
- Constants and type definitions
|
||||
- Subcomponents within React components
|
||||
- Related logic that forms a cohesive unit
|
||||
|
||||
2. **Create subdirectory structure**:
|
||||
```
|
||||
# Before
|
||||
provider-editor.tsx (921 lines)
|
||||
|
||||
# After
|
||||
provider-editor/
|
||||
├── index.tsx # Main component (200 lines)
|
||||
├── model-mapping-form.tsx
|
||||
├── endpoint-config.tsx
|
||||
├── auth-section.tsx
|
||||
├── hooks.ts
|
||||
├── types.ts
|
||||
└── utils.ts
|
||||
```
|
||||
|
||||
3. **Preserve public API**: Main export remains the same through barrel export
|
||||
|
||||
---
|
||||
|
||||
## Barrel Export Pattern
|
||||
|
||||
### What is a Barrel Export?
|
||||
|
||||
An `index.ts` file that aggregates and re-exports module contents:
|
||||
|
||||
```typescript
|
||||
// src/cliproxy/index.ts
|
||||
|
||||
// Types (with explicit type keyword)
|
||||
export type { PlatformInfo, BinaryInfo } from './types';
|
||||
|
||||
// Functions
|
||||
export { detectPlatform } from './platform-detector';
|
||||
export { BinaryManager } from './binary-manager';
|
||||
|
||||
// From subdirectories
|
||||
export * from './auth';
|
||||
export * from './services';
|
||||
```
|
||||
|
||||
### Rules for Barrel Exports
|
||||
|
||||
1. **Every domain directory must have `index.ts`**
|
||||
2. **Export types with `export type`** for tree-shaking
|
||||
3. **Re-export subdirectories** for deep access
|
||||
4. **Keep barrel exports flat** - no logic, only exports
|
||||
|
||||
### Import Patterns
|
||||
|
||||
```typescript
|
||||
// CORRECT: Import from domain barrel
|
||||
import { execClaudeWithCLIProxy, CLIProxyProvider } from '../cliproxy';
|
||||
import { Config, Settings } from '../types';
|
||||
|
||||
// INCORRECT: Import from specific file (bypasses barrel)
|
||||
import { execClaudeWithCLIProxy } from '../cliproxy/cliproxy-executor';
|
||||
```
|
||||
|
||||
### Exception: Deep Imports
|
||||
|
||||
Allowed when:
|
||||
- Importing private utilities not exposed in barrel
|
||||
- Circular dependency avoidance
|
||||
- Performance-critical tree-shaking
|
||||
|
||||
---
|
||||
|
||||
## Monster File Splitting Methodology
|
||||
|
||||
When splitting large files (500+ lines), follow this process:
|
||||
|
||||
### Step 1: Analyze Structure
|
||||
|
||||
Identify logical boundaries:
|
||||
- Render sections in React components
|
||||
- Handler groups in route files
|
||||
- Related utility functions
|
||||
- Constants and types
|
||||
|
||||
### Step 2: Extract Types First
|
||||
|
||||
```typescript
|
||||
// types.ts
|
||||
export interface ProviderEditorProps {
|
||||
providerId: string;
|
||||
onSave: (config: ProviderConfig) => void;
|
||||
}
|
||||
|
||||
export interface ModelMappingValues {
|
||||
model: string;
|
||||
endpoint: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Extract Utilities
|
||||
|
||||
```typescript
|
||||
// utils.ts
|
||||
export function validateEndpoint(url: string): boolean { ... }
|
||||
export function formatModelName(name: string): string { ... }
|
||||
```
|
||||
|
||||
### Step 4: Extract Hooks
|
||||
|
||||
```typescript
|
||||
// hooks.ts
|
||||
export function useProviderConfig(providerId: string) { ... }
|
||||
export function useModelValidation() { ... }
|
||||
```
|
||||
|
||||
### Step 5: Extract Subcomponents
|
||||
|
||||
```typescript
|
||||
// model-mapping-form.tsx
|
||||
export function ModelMappingForm({ values, onChange }: Props) { ... }
|
||||
```
|
||||
|
||||
### Step 6: Compose in Index
|
||||
|
||||
```typescript
|
||||
// index.tsx
|
||||
import { ModelMappingForm } from './model-mapping-form';
|
||||
import { useProviderConfig } from './hooks';
|
||||
import type { ProviderEditorProps } from './types';
|
||||
|
||||
export function ProviderEditor({ providerId, onSave }: ProviderEditorProps) {
|
||||
const config = useProviderConfig(providerId);
|
||||
return (
|
||||
<div>
|
||||
<ModelMappingForm values={config.mapping} onChange={...} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Re-export types for consumers
|
||||
export type { ProviderEditorProps, ModelMappingValues } from './types';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TypeScript Standards
|
||||
|
||||
### Strict Mode Required
|
||||
|
||||
All projects use TypeScript strict mode:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Type Annotations
|
||||
|
||||
```typescript
|
||||
// CORRECT: Explicit return types for public functions
|
||||
export function detectProfile(args: string[]): DetectedProfile { ... }
|
||||
|
||||
// CORRECT: Inferred types for internal functions
|
||||
const formatName = (name: string) => name.trim().toLowerCase();
|
||||
|
||||
// INCORRECT: any type
|
||||
function processData(data: any) { ... } // Use unknown or proper type
|
||||
```
|
||||
|
||||
### Type Exports
|
||||
|
||||
```typescript
|
||||
// CORRECT: Use type keyword for type-only exports
|
||||
export type { Config, Settings } from './config';
|
||||
|
||||
// CORRECT: Group type exports in barrel
|
||||
export type {
|
||||
PlatformInfo,
|
||||
BinaryInfo,
|
||||
DownloadProgress,
|
||||
} from './types';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ESLint Rules (Enforced)
|
||||
|
||||
| Rule | Level | Notes |
|
||||
|------|-------|-------|
|
||||
| `@typescript-eslint/no-unused-vars` | error | Ignore `_` prefix |
|
||||
| `@typescript-eslint/no-explicit-any` | error | Use proper types |
|
||||
| `@typescript-eslint/no-non-null-assertion` | error | No `!` assertions |
|
||||
| `prefer-const` | error | Immutable by default |
|
||||
| `no-var` | error | Use const/let |
|
||||
| `eqeqeq` | error | Strict equality |
|
||||
| `react-hooks/*` | recommended | (UI only) |
|
||||
|
||||
---
|
||||
|
||||
## Terminal Output Standards
|
||||
|
||||
### ASCII Only
|
||||
|
||||
```typescript
|
||||
// CORRECT
|
||||
console.log('[OK] Operation successful');
|
||||
console.log('[!] Warning message');
|
||||
console.log('[X] Error occurred');
|
||||
console.log('[i] Information');
|
||||
|
||||
// INCORRECT - NO EMOJIS
|
||||
console.log('Operation successful'); // NO
|
||||
console.log('Warning message'); // NO
|
||||
```
|
||||
|
||||
### Color Handling
|
||||
|
||||
```typescript
|
||||
import { colors } from '../utils/ui';
|
||||
|
||||
// Colors are TTY-aware and respect NO_COLOR
|
||||
console.log(colors.green('[OK]') + ' Operation successful');
|
||||
```
|
||||
|
||||
### Box Borders
|
||||
|
||||
Use ASCII box drawing for error displays:
|
||||
|
||||
```
|
||||
+=====================================+
|
||||
| [X] ERROR: Configuration failed |
|
||||
| |
|
||||
| Details: Unable to parse config |
|
||||
+=====================================+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## React Component Standards (UI)
|
||||
|
||||
### Component Structure
|
||||
|
||||
```typescript
|
||||
// component-name.tsx
|
||||
|
||||
// 1. Imports (grouped: react, external, internal, relative)
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useProfiles } from '@/hooks';
|
||||
import { formatName } from './utils';
|
||||
import type { ComponentProps } from './types';
|
||||
|
||||
// 2. Types (if not in separate file)
|
||||
interface Props {
|
||||
id: string;
|
||||
onSave: () => void;
|
||||
}
|
||||
|
||||
// 3. Component
|
||||
export function ComponentName({ id, onSave }: Props) {
|
||||
// Hooks first
|
||||
const profiles = useProfiles();
|
||||
const [state, setState] = useState(null);
|
||||
|
||||
// Handlers
|
||||
const handleClick = () => { ... };
|
||||
|
||||
// Render
|
||||
return ( ... );
|
||||
}
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
| Item | Convention | Example |
|
||||
|------|------------|---------|
|
||||
| Component files | kebab-case.tsx | `provider-editor.tsx` |
|
||||
| Component exports | PascalCase | `ProviderEditor` |
|
||||
| Hook files | use-*.ts | `use-profiles.ts` |
|
||||
| Hook exports | useCamelCase | `useProfiles` |
|
||||
| Utility files | kebab-case.ts | `path-utils.ts` |
|
||||
| Utility exports | camelCase | `formatPath` |
|
||||
|
||||
---
|
||||
|
||||
## Quality Gates
|
||||
|
||||
### Pre-Commit Sequence
|
||||
|
||||
```bash
|
||||
# Main project
|
||||
bun run format
|
||||
bun run lint:fix
|
||||
bun run validate
|
||||
|
||||
# UI project (if changed)
|
||||
cd ui
|
||||
bun run format
|
||||
bun run lint:fix
|
||||
bun run validate
|
||||
```
|
||||
|
||||
### Validate Runs
|
||||
|
||||
| Project | Command | Checks |
|
||||
|---------|---------|--------|
|
||||
| Main | `bun run validate` | typecheck + lint + format:check + test |
|
||||
| UI | `bun run validate` | typecheck + lint + format:check |
|
||||
|
||||
---
|
||||
|
||||
## Conventional Commits
|
||||
|
||||
All commits must follow conventional commit format:
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
| Type | When to Use | Version Bump |
|
||||
|------|-------------|--------------|
|
||||
| `feat` | New feature | MINOR |
|
||||
| `fix` | Bug fix | PATCH |
|
||||
| `perf` | Performance | PATCH |
|
||||
| `docs` | Documentation | None |
|
||||
| `style` | Formatting | None |
|
||||
| `refactor` | Code restructure | None |
|
||||
| `test` | Tests | None |
|
||||
| `chore` | Maintenance | None |
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Correct
|
||||
git commit -m "feat(cliproxy): add OAuth token refresh"
|
||||
git commit -m "fix(doctor): handle missing config gracefully"
|
||||
git commit -m "refactor(ui): split provider-editor into modules"
|
||||
|
||||
# Incorrect - REJECTED
|
||||
git commit -m "added new feature"
|
||||
git commit -m "Fixed bug"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### 1. God Files
|
||||
|
||||
```typescript
|
||||
// BAD: One file doing everything
|
||||
// src/utils.ts (2000 lines with mixed concerns)
|
||||
|
||||
// GOOD: Split by domain
|
||||
// src/utils/ui/colors.ts
|
||||
// src/utils/ui/boxes.ts
|
||||
// src/utils/shell-executor.ts
|
||||
// src/utils/config-manager.ts
|
||||
```
|
||||
|
||||
### 2. Barrel Import Bypass
|
||||
|
||||
```typescript
|
||||
// BAD: Direct import bypassing barrel
|
||||
import { detectPlatform } from '../cliproxy/platform-detector';
|
||||
|
||||
// GOOD: Import from domain barrel
|
||||
import { detectPlatform } from '../cliproxy';
|
||||
```
|
||||
|
||||
### 3. Inline Everything
|
||||
|
||||
```typescript
|
||||
// BAD: Huge inline functions in components
|
||||
function Component() {
|
||||
const handleComplexOperation = () => {
|
||||
// 100 lines of logic...
|
||||
};
|
||||
}
|
||||
|
||||
// GOOD: Extract to hooks or utilities
|
||||
function Component() {
|
||||
const { handleComplexOperation } = useComplexOperation();
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Type Duplication
|
||||
|
||||
```typescript
|
||||
// BAD: Same types defined in multiple files
|
||||
// file1.ts
|
||||
interface Config { ... }
|
||||
// file2.ts
|
||||
interface Config { ... }
|
||||
|
||||
// GOOD: Single source of truth
|
||||
// types/config.ts
|
||||
export interface Config { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Codebase Summary](./codebase-summary.md) - Full directory structure
|
||||
- [System Architecture](./system-architecture.md) - Architecture diagrams
|
||||
- [CLAUDE.md](../CLAUDE.md) - AI-facing development guidance
|
||||
Reference in new issue
Block a user