Files
ccs/docs/code-standards.md
T

497 lines
12 KiB
Markdown

# CCS Code Standards
## Overview
This document defines the coding standards and principles for the CCS (Claude Code Switch) project. Following these standards ensures consistency, maintainability, and quality across the codebase.
## Core Principles
### Design Philosophy
**YAGNI** (You Aren't Gonna Need It)
- Only implement features that are immediately needed
- Avoid code "just in case" scenarios
- Keep the codebase minimal and focused
**KISS** (Keep It Simple, Stupid)
- Prefer simple solutions over complex ones
- Avoid unnecessary abstractions
- Use straightforward, readable code
**DRY** (Don't Repeat Yourself)
- Eliminate duplicate code through consolidation
- Create reusable functions for common operations
- Maintain single sources of truth
### Simplification Standards
The recent codebase simplification (35% reduction from 1,315 to 855 lines) established these standards:
1. **Consolidate duplicate logic**: Unified spawn logic in `execClaude()` function
2. **Remove security theater**: Eliminate unnecessary validation functions
3. **Simplify error handling**: Direct console.error instead of complex formatting
4. **Deduplicate platform checks**: Centralize platform-specific logic
## JavaScript Standards
### Code Style
#### File Structure
```javascript
'use strict';
// Dependencies
const { spawn } = require('child_process');
const path = require('path');
const { error } = require('./helpers');
// Constants
const CCS_VERSION = require('../package.json').version;
// Functions (grouped by responsibility)
function mainFunction() {
// Implementation
}
// Main execution
function main() {
// Implementation
}
// Run main
main();
```
#### Function Declarations
- Use `function` declarations for named functions
- Use arrow functions only for anonymous functions or callbacks
- Group related functions together
- Place main execution logic at the bottom
```javascript
// Good
function handleVersionCommand() {
console.log(`CCS version ${CCS_VERSION}`);
process.exit(0);
}
// Acceptable for callbacks
fs.readFile(file, (err, data) => {
if (err) return error(err.message);
// Process data
});
```
#### Variable Declarations
- Use `const` for variables that won't be reassigned
- Use `let` only when reassignment is necessary
- Declare variables as close to usage as possible
- Avoid `var` entirely
```javascript
// Good
const configPath = getConfigPath();
const claudeCli = detectClaudeCli();
// Avoid
let configPath;
configPath = getConfigPath();
```
### Error Handling Standards
#### Simple Error Reporting
```javascript
// Preferred - Simple and direct
function error(message) {
console.error(`ERROR: ${message}`);
process.exit(1);
}
// Usage
if (!fs.existsSync(configPath)) {
error(`Config file not found: ${configPath}`);
}
```
#### Avoid Complex Error Formatting
```javascript
// Avoid - Complex box-drawing formatting
function showErrorBox(message) {
console.log('╔══════════════════════════════════════╗');
console.log('║ ERROR ║');
console.log(`║ ${message.padEnd(34)} ║`);
console.log('╚══════════════════════════════════════╝');
}
```
#### Early Validation Pattern
```javascript
function getSettingsPath(profile) {
// Validate early and exit fast
const config = readConfig();
const settingsPath = config.profiles[profile];
if (!settingsPath) {
error(`Profile '${profile}' not found. Available: ${Object.keys(config.profiles).join(', ')}`);
}
return settingsPath;
}
```
### Process Management Standards
#### Unified Spawn Logic
```javascript
// Consolidated spawn function - single source of truth
function execClaude(claudeCli, args) {
const child = spawn(claudeCli, args, {
stdio: 'inherit',
windowsHide: true
});
child.on('exit', (code, signal) => {
if (signal) process.kill(process.pid, signal);
else process.exit(code || 0);
});
child.on('error', () => {
showClaudeNotFoundError();
process.exit(1);
});
}
```
#### Security Best Practices
- Always use arrays with `spawn()` to prevent shell injection
- Never construct shell command strings with user input
- Validate inputs before using them in file operations
```javascript
// Good - Safe with array arguments
spawn(claudeCli, ['--settings', settingsPath, ...args]);
// Avoid - Unsafe string concatenation
spawn('sh', ['-c', `claude --settings ${settingsPath} ${args.join(' ')}`]);
```
### Module Organization Standards
#### Module Dependencies
```javascript
// Group imports by type
// Node.js built-ins
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
// Local modules
const { error } = require('./helpers');
const { detectClaudeCli } = require('./claude-detector');
```
#### Exports Pattern
```javascript
// Clear, named exports
module.exports = {
getConfigPath,
readConfig,
getSettingsPath
};
// Avoid exports with mixed responsibilities
module.exports = {
getConfigPath,
someUtilityFunction,
anotherUnrelatedFunction
};
```
## Platform Compatibility Standards
### Cross-Platform Development
#### Path Handling
```javascript
// Use path module for cross-platform compatibility
const configPath = path.join(os.homedir(), '.ccs', 'config.json');
// Avoid hardcoded separators
const configPath = os.homedir() + '/.ccs/config.json'; // Unix only
```
#### Platform Detection
```javascript
// Centralized platform detection
const isWindows = process.platform === 'win32';
if (isWindows) {
// Windows-specific logic
} else {
// Unix/macOS logic
}
```
#### Environment Variables
```javascript
// Support both Windows and Unix environment variable formats
function expandPath(pathStr) {
// Unix style: $HOME or ${HOME}
pathStr = pathStr.replace(/\$\{([^}]+)\}/g, (_, name) => process.env[name] || '');
// Windows style: %USERPROFILE%
if (process.platform === 'win32') {
pathStr = pathStr.replace(/%([^%]+)%/g, (_, name) => process.env[name] || '');
}
return path.normalize(pathStr);
}
```
## Configuration Standards
### JSON Configuration Format
#### Configuration Schema
```json
{
"profiles": {
"default": "~/.claude/settings.json",
"glm": "~/.ccs/glm.settings.json"
}
}
```
#### Settings File Format
```json
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
"ANTHROPIC_AUTH_TOKEN": "your_api_key",
"ANTHROPIC_MODEL": "glm-4.6",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "glm-4.6",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.6",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-4.6"
}
}
```
### Configuration Handling Patterns
#### Safe JSON Parsing
```javascript
function readConfig() {
try {
const configContent = fs.readFileSync(configPath, 'utf8');
return JSON.parse(configContent);
} catch (e) {
error(`Invalid JSON in ${configPath}: ${e.message}`);
}
}
```
#### Validation Patterns
```javascript
function validateConfig(config) {
if (!config.profiles || typeof config.profiles !== 'object') {
error(`Config must have 'profiles' object`);
}
// Essential validation only - avoid excessive checks
return true;
}
```
## Testing Standards
### Test Organization
#### Unit Test Structure
```javascript
const assert = require('assert');
const { getSettingsPath } = require('../../../bin/config-manager');
describe('config-manager', () => {
describe('getSettingsPath', () => {
it('should return settings path for valid profile', () => {
const result = getSettingsPath('glm');
assert(result.includes('glm.settings.json'));
});
it('should throw error for invalid profile', () => {
assert.throws(() => {
getSettingsPath('invalid');
}, /Profile 'invalid' not found/);
});
});
});
```
#### Test Data Management
```javascript
// Use fixtures for test data
const testConfig = {
profiles: {
glm: '~/.ccs/glm.settings.json',
default: '~/.claude/settings.json'
}
};
// Clean up after tests
afterEach(() => {
// Clean up test files
});
```
### Test Coverage Requirements
Before any PR, ensure:
- [ ] All new functions have unit tests
- [ ] Error conditions are tested
- [ ] Cross-platform behavior is verified
- [ ] Edge cases are covered
- [ ] Integration tests validate end-to-end workflows
## Documentation Standards
### Code Documentation
#### Function Documentation
```javascript
/**
* Execute Claude CLI with unified spawn logic
* @param {string} claudeCli - Path to Claude CLI executable
* @param {string[]} args - Arguments to pass to Claude CLI
*/
function execClaude(claudeCli, args) {
// Implementation
}
```
#### Inline Comments
```javascript
// Special case: version command (check BEFORE profile detection)
if (firstArg === '--version') {
handleVersionCommand();
}
// Validate settings file exists before using it
if (!fs.existsSync(expandedPath)) {
error(`Settings file not found: ${expandedPath}`);
}
```
### README Standards
#### Installation Instructions
- Provide multiple installation methods
- Include prerequisites clearly
- Show first usage examples
- Include troubleshooting links
#### Usage Examples
```bash
# Basic usage
ccs # Use default profile
ccs glm # Switch to GLM profile
ccs glm "your prompt" # One-time command with GLM
# Special commands
ccs --version # Show version
ccs --help # Show help
ccs --install # Install Claude Code integration
```
## Version Management Standards
### Version Synchronization
When updating version, maintain consistency across:
1. `package.json` version field
2. `VERSION` file
3. Installer scripts (if applicable)
### Semantic Versioning
- **Major**: Breaking changes
- **Minor**: New features (backward compatible)
- **Patch**: Bug fixes (backward compatible)
## Security Standards
### Input Validation
- Validate configuration file existence and format
- Check executable permissions before use
- Sanitize user inputs appropriately
### Safe Process Execution
```javascript
// Good: Using arrays prevents shell injection
spawn(claudeCli, ['--settings', settingsPath, ...userArgs]);
// Avoid: String concatenation can lead to injection
spawn('sh', ['-c', `claude --settings ${settingsPath} ${command}`]);
```
### File System Access
- Only access known configuration directories
- Use path normalization to prevent traversal
- Validate file permissions before reading
## Performance Standards
### Optimization Principles
- Minimize function call overhead
- Reduce I/O operations through caching
- Use efficient data structures
- Avoid unnecessary computations
### Memory Management
- Use streams for large file operations
- Clean up resources properly
- Avoid memory leaks in long-running processes
## Quality Assurance Standards
### Code Review Checklist
Before submitting code, verify:
- [ ] Follows all coding standards
- [ ] Has appropriate test coverage
- [ ] Documentation is updated
- [ ] No console.log statements left in production code
- [ ] Error handling is comprehensive
- [ ] Cross-platform compatibility is maintained
### Release Checklist
Before releasing new version:
- [ ] All tests pass on all platforms
- [ ] Documentation is updated
- [ ] Version numbers are synchronized
- [ ] Installation is tested from scratch
- [ ] Edge cases are manually verified
## Contributing Standards
### Development Workflow
1. Create feature branch from main
2. Implement changes following these standards
3. Add comprehensive tests
4. Update documentation
5. Submit PR with clear description
### Pull Request Requirements
- Clear description of changes
- Test coverage for new functionality
- Documentation updates
- No breaking changes without version bump
- All CI checks passing
## Summary
These code standards ensure the CCS codebase remains:
- **Maintainable**: Clear structure and consistent patterns
- **Reliable**: Comprehensive error handling and testing
- **Performant**: Optimized for speed and memory usage
- **Secure**: Safe process execution and file handling
- **Compatible**: Works consistently across all supported platforms
Following these standards helps maintain the quality and simplicity achieved through the recent codebase simplification while enabling future development and maintenance.