mirror of
https://github.com/luongnv89/claude-howto.git
synced 2026-06-05 22:36:34 +02:00
i18n(uk): add missing files, translate P4 root docs
- Copy code/image/config files across all modules - Translate brand-voice and code-review templates - Translate CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, STYLE_GUIDE - Copy CHANGELOG as-is (technical log) Ref: luongnv89/claude-howto#63
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 296 KiB |
@@ -0,0 +1,5 @@
|
||||
# Local skill testing
|
||||
.claude/
|
||||
|
||||
# Blog post outputs
|
||||
blog-posts/
|
||||
@@ -0,0 +1,14 @@
|
||||
Тема: [Чітка, орієнтована на вигоду тема]
|
||||
|
||||
Вітаю, [Ім'я]!
|
||||
|
||||
[Вступ: Яка цінність для них]
|
||||
|
||||
[Основна частина: Як це працює / Що вони отримають]
|
||||
|
||||
[Конкретний приклад або вигода]
|
||||
|
||||
[Заклик до дії: Чіткий наступний крок]
|
||||
|
||||
З повагою,
|
||||
[Ім'я]
|
||||
@@ -0,0 +1,4 @@
|
||||
[Хук: Привернути увагу в першому рядку]
|
||||
[2-3 рядки: Цінність або цікавий факт]
|
||||
[Заклик до дії: Посилання, питання або залучення]
|
||||
[Емодзі: 1-2 максимум для візуального інтересу]
|
||||
@@ -0,0 +1,13 @@
|
||||
# Приклади тону бренду
|
||||
|
||||
## Захоплива анонс
|
||||
"Зекономте 8 годин на тиждень на код-рев'ю. Claude переглядає ваші PR автоматично."
|
||||
|
||||
## Емпатична підтримка
|
||||
"Ми знаємо, що деплої можуть бути стресовими. Claude бере тестування на себе, щоб вам не хвилюватися."
|
||||
|
||||
## Впевнена презентація продукту
|
||||
"Claude не просто пропонує код. Він розуміє вашу архітектуру і підтримує консистентність."
|
||||
|
||||
## Освітній блог-пост
|
||||
"Давайте розглянемо, як агенти покращують процеси код-рев'ю. Ось що ми дізналися..."
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def analyze_code_metrics(code):
|
||||
"""Analyze code for common metrics."""
|
||||
|
||||
# Count functions
|
||||
functions = len(re.findall(r"^def\s+\w+", code, re.MULTILINE))
|
||||
|
||||
# Count classes
|
||||
classes = len(re.findall(r"^class\s+\w+", code, re.MULTILINE))
|
||||
|
||||
# Average line length
|
||||
lines = code.split("\n")
|
||||
avg_length = sum(len(l) for l in lines) / len(lines) if lines else 0
|
||||
|
||||
# Estimate complexity
|
||||
complexity = len(re.findall(r"\b(if|elif|else|for|while|and|or)\b", code))
|
||||
|
||||
return {
|
||||
"functions": functions,
|
||||
"classes": classes,
|
||||
"avg_line_length": avg_length,
|
||||
"complexity_score": complexity,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open(sys.argv[1]) as f:
|
||||
code = f.read()
|
||||
metrics = analyze_code_metrics(code)
|
||||
for key, value in metrics.items():
|
||||
print(f"{key}: {value:.2f}")
|
||||
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Compare cyclomatic complexity of code before and after changes.
|
||||
Helps identify if refactoring actually simplifies code structure.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
class ComplexityAnalyzer:
|
||||
"""Analyze code complexity metrics."""
|
||||
|
||||
def __init__(self, code: str):
|
||||
self.code = code
|
||||
self.lines = code.split("\n")
|
||||
|
||||
def calculate_cyclomatic_complexity(self) -> int:
|
||||
"""
|
||||
Calculate cyclomatic complexity using McCabe's method.
|
||||
Count decision points: if, elif, else, for, while, except, and, or
|
||||
"""
|
||||
complexity = 1 # Base complexity
|
||||
|
||||
# Count decision points
|
||||
decision_patterns = [
|
||||
r"\bif\b",
|
||||
r"\belif\b",
|
||||
r"\bfor\b",
|
||||
r"\bwhile\b",
|
||||
r"\bexcept\b",
|
||||
r"\band\b(?!$)",
|
||||
r"\bor\b(?!$)",
|
||||
]
|
||||
|
||||
for pattern in decision_patterns:
|
||||
matches = re.findall(pattern, self.code)
|
||||
complexity += len(matches)
|
||||
|
||||
return complexity
|
||||
|
||||
def calculate_cognitive_complexity(self) -> int:
|
||||
"""
|
||||
Calculate cognitive complexity - how hard is it to understand?
|
||||
Based on nesting depth and control flow.
|
||||
"""
|
||||
cognitive = 0
|
||||
nesting_depth = 0
|
||||
|
||||
for line in self.lines:
|
||||
# Track nesting depth
|
||||
if re.search(r"^\s*(if|for|while|def|class|try)\b", line):
|
||||
nesting_depth += 1
|
||||
cognitive += nesting_depth
|
||||
elif re.search(r"^\s*(elif|else|except|finally)\b", line):
|
||||
cognitive += nesting_depth
|
||||
|
||||
# Reduce nesting when unindenting
|
||||
if line and not line[0].isspace():
|
||||
nesting_depth = 0
|
||||
|
||||
return cognitive
|
||||
|
||||
def calculate_maintainability_index(self) -> float:
|
||||
"""
|
||||
Maintainability Index ranges from 0-100.
|
||||
> 85: Excellent
|
||||
> 65: Good
|
||||
> 50: Fair
|
||||
< 50: Poor
|
||||
"""
|
||||
lines = len(self.lines)
|
||||
cyclomatic = self.calculate_cyclomatic_complexity()
|
||||
cognitive = self.calculate_cognitive_complexity()
|
||||
|
||||
# Simplified MI calculation
|
||||
mi = (
|
||||
171
|
||||
- 5.2 * (cyclomatic / lines)
|
||||
- 0.23 * (cognitive)
|
||||
- 16.2 * (lines / 1000)
|
||||
)
|
||||
|
||||
return max(0, min(100, mi))
|
||||
|
||||
def get_complexity_report(self) -> dict:
|
||||
"""Generate comprehensive complexity report."""
|
||||
return {
|
||||
"cyclomatic_complexity": self.calculate_cyclomatic_complexity(),
|
||||
"cognitive_complexity": self.calculate_cognitive_complexity(),
|
||||
"maintainability_index": round(self.calculate_maintainability_index(), 2),
|
||||
"lines_of_code": len(self.lines),
|
||||
"avg_line_length": round(
|
||||
sum(len(l) for l in self.lines) / len(self.lines), 2
|
||||
)
|
||||
if self.lines
|
||||
else 0,
|
||||
}
|
||||
|
||||
|
||||
def compare_files(before_file: str, after_file: str) -> None:
|
||||
"""Compare complexity metrics between two code versions."""
|
||||
|
||||
with open(before_file) as f:
|
||||
before_code = f.read()
|
||||
|
||||
with open(after_file) as f:
|
||||
after_code = f.read()
|
||||
|
||||
before_analyzer = ComplexityAnalyzer(before_code)
|
||||
after_analyzer = ComplexityAnalyzer(after_code)
|
||||
|
||||
before_metrics = before_analyzer.get_complexity_report()
|
||||
after_metrics = after_analyzer.get_complexity_report()
|
||||
|
||||
print("=" * 60)
|
||||
print("CODE COMPLEXITY COMPARISON")
|
||||
print("=" * 60)
|
||||
|
||||
print("\nBEFORE:")
|
||||
print(f" Cyclomatic Complexity: {before_metrics['cyclomatic_complexity']}")
|
||||
print(f" Cognitive Complexity: {before_metrics['cognitive_complexity']}")
|
||||
print(f" Maintainability Index: {before_metrics['maintainability_index']}")
|
||||
print(f" Lines of Code: {before_metrics['lines_of_code']}")
|
||||
print(f" Avg Line Length: {before_metrics['avg_line_length']}")
|
||||
|
||||
print("\nAFTER:")
|
||||
print(f" Cyclomatic Complexity: {after_metrics['cyclomatic_complexity']}")
|
||||
print(f" Cognitive Complexity: {after_metrics['cognitive_complexity']}")
|
||||
print(f" Maintainability Index: {after_metrics['maintainability_index']}")
|
||||
print(f" Lines of Code: {after_metrics['lines_of_code']}")
|
||||
print(f" Avg Line Length: {after_metrics['avg_line_length']}")
|
||||
|
||||
print("\nCHANGES:")
|
||||
cyclomatic_change = (
|
||||
after_metrics["cyclomatic_complexity"] - before_metrics["cyclomatic_complexity"]
|
||||
)
|
||||
cognitive_change = (
|
||||
after_metrics["cognitive_complexity"] - before_metrics["cognitive_complexity"]
|
||||
)
|
||||
mi_change = (
|
||||
after_metrics["maintainability_index"] - before_metrics["maintainability_index"]
|
||||
)
|
||||
loc_change = after_metrics["lines_of_code"] - before_metrics["lines_of_code"]
|
||||
|
||||
print(f" Cyclomatic Complexity: {cyclomatic_change:+d}")
|
||||
print(f" Cognitive Complexity: {cognitive_change:+d}")
|
||||
print(f" Maintainability Index: {mi_change:+.2f}")
|
||||
print(f" Lines of Code: {loc_change:+d}")
|
||||
|
||||
print("\nASSESSMENT:")
|
||||
if mi_change > 0:
|
||||
print(" ✅ Code is MORE maintainable")
|
||||
elif mi_change < 0:
|
||||
print(" ⚠️ Code is LESS maintainable")
|
||||
else:
|
||||
print(" ➡️ Maintainability unchanged")
|
||||
|
||||
if cyclomatic_change < 0:
|
||||
print(" ✅ Complexity DECREASED")
|
||||
elif cyclomatic_change > 0:
|
||||
print(" ⚠️ Complexity INCREASED")
|
||||
else:
|
||||
print(" ➡️ Complexity unchanged")
|
||||
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: python compare-complexity.py <before_file> <after_file>")
|
||||
sys.exit(1)
|
||||
|
||||
compare_files(sys.argv[1], sys.argv[2])
|
||||
@@ -0,0 +1,112 @@
|
||||
# Шаблон знахідки код-рев'ю
|
||||
|
||||
Використовуйте цей шаблон для документування кожної знайденої проблеми під час код-рев'ю.
|
||||
|
||||
---
|
||||
|
||||
## Проблема: [НАЗВА]
|
||||
|
||||
### Серйозність
|
||||
- [ ] Критична (блокує деплой)
|
||||
- [ ] Висока (потрібно виправити перед мерджем)
|
||||
- [ ] Середня (потрібно виправити незабаром)
|
||||
- [ ] Низька (бажано виправити)
|
||||
|
||||
### Категорія
|
||||
- [ ] Безпека
|
||||
- [ ] Продуктивність
|
||||
- [ ] Якість коду
|
||||
- [ ] Підтримуваність
|
||||
- [ ] Тестування
|
||||
- [ ] Патерн проєктування
|
||||
- [ ] Документація
|
||||
|
||||
### Розташування
|
||||
**Файл:** `src/components/UserCard.tsx`
|
||||
|
||||
**Рядки:** 45-52
|
||||
|
||||
**Функція/Метод:** `renderUserDetails()`
|
||||
|
||||
### Опис проблеми
|
||||
|
||||
**Що:** Опишіть суть проблеми.
|
||||
|
||||
**Чому це важливо:** Поясніть вплив та чому це потрібно виправити.
|
||||
|
||||
**Поточна поведінка:** Покажіть проблемний код або поведінку.
|
||||
|
||||
**Очікувана поведінка:** Опишіть, що повинно відбуватися замість цього.
|
||||
|
||||
### Приклад коду
|
||||
|
||||
#### Поточний (проблемний)
|
||||
|
||||
```typescript
|
||||
// Shows the N+1 query problem
|
||||
const users = fetchUsers();
|
||||
users.forEach(user => {
|
||||
const posts = fetchUserPosts(user.id); // Query per user!
|
||||
renderUserPosts(posts);
|
||||
});
|
||||
```
|
||||
|
||||
#### Запропоноване виправлення
|
||||
|
||||
```typescript
|
||||
// Optimized with JOIN query
|
||||
const usersWithPosts = fetchUsersWithPosts();
|
||||
usersWithPosts.forEach(({ user, posts }) => {
|
||||
renderUserPosts(posts);
|
||||
});
|
||||
```
|
||||
|
||||
### Аналіз впливу
|
||||
|
||||
| Аспект | Вплив | Серйозність |
|
||||
|--------|-------|-------------|
|
||||
| Продуктивність | 100+ запитів для 20 користувачів | Висока |
|
||||
| Досвід користувача | Повільне завантаження сторінки | Висока |
|
||||
| Масштабованість | Ламається при масштабуванні | Критична |
|
||||
| Підтримуваність | Складно дебажити | Середня |
|
||||
|
||||
### Пов'язані проблеми
|
||||
|
||||
- Аналогічна проблема в `AdminUserList.tsx` рядок 120
|
||||
- Пов'язаний PR: #456
|
||||
- Пов'язана issue: #789
|
||||
|
||||
### Додаткові ресурси
|
||||
|
||||
- [N+1 Query Problem](https://en.wikipedia.org/wiki/N%2B1_problem)
|
||||
- [Database Join Documentation](https://docs.example.com/joins)
|
||||
|
||||
### Нотатки рецензента
|
||||
|
||||
- Це поширений патерн у цій кодовій базі
|
||||
- Варто додати це до гайду зі стилю коду
|
||||
- Можливо, варто створити допоміжну функцію
|
||||
|
||||
### Відповідь автора (для зворотного зв'язку)
|
||||
|
||||
*Заповнюється автором коду:*
|
||||
|
||||
- [ ] Виправлення реалізовано в коміті: `abc123`
|
||||
- [ ] Статус виправлення: Завершено / В процесі / Потребує обговорення
|
||||
- [ ] Питання або зауваження: (опишіть)
|
||||
|
||||
---
|
||||
|
||||
## Статистика знахідок (для рецензента)
|
||||
|
||||
При рев'ю кількох знахідок, відстежуйте:
|
||||
|
||||
- **Всього знайдено проблем:** X
|
||||
- **Критичних:** X
|
||||
- **Високих:** X
|
||||
- **Середніх:** X
|
||||
- **Низьких:** X
|
||||
|
||||
**Рекомендація:** ✅ Затвердити / ⚠️ Запросити зміни / 🔄 Потребує обговорення
|
||||
|
||||
**Загальна якість коду:** 1-5 зірок
|
||||
@@ -0,0 +1,47 @@
|
||||
# Чеклист код-рев'ю
|
||||
|
||||
## Чеклист безпеки
|
||||
- [ ] Немає захардкоджених облікових даних або секретів
|
||||
- [ ] Валідація введення для всіх даних від користувача
|
||||
- [ ] Захист від SQL-ін'єкцій (параметризовані запити)
|
||||
- [ ] CSRF-захист для операцій зі зміною стану
|
||||
- [ ] Захист від XSS з правильним екрануванням
|
||||
- [ ] Перевірка автентифікації на захищених ендпоінтах
|
||||
- [ ] Перевірка авторизації для ресурсів
|
||||
- [ ] Безпечне хешування паролів (bcrypt, argon2)
|
||||
- [ ] Немає чутливих даних у логах
|
||||
- [ ] HTTPS обов'язковий
|
||||
|
||||
## Чеклист продуктивності
|
||||
- [ ] Немає N+1 запитів
|
||||
- [ ] Правильне використання індексів
|
||||
- [ ] Кешування реалізовано де доцільно
|
||||
- [ ] Немає блокуючих операцій в основному потоці
|
||||
- [ ] Async/await використовується коректно
|
||||
- [ ] Великі набори даних пагіновані
|
||||
- [ ] Пул з'єднань до бази даних
|
||||
- [ ] Регулярні вирази оптимізовані
|
||||
- [ ] Немає зайвого створення об'єктів
|
||||
- [ ] Витоки пам'яті запобігаються
|
||||
|
||||
## Чеклист якості
|
||||
- [ ] Функції < 50 рядків
|
||||
- [ ] Зрозумілі назви змінних
|
||||
- [ ] Немає дублювання коду
|
||||
- [ ] Правильна обробка помилок
|
||||
- [ ] Коментарі пояснюють ЧОМУ, а не ЩО
|
||||
- [ ] Немає console.log у продакшені
|
||||
- [ ] Перевірка типів (TypeScript/JSDoc)
|
||||
- [ ] Принципи SOLID дотримані
|
||||
- [ ] Патерни проєктування застосовані коректно
|
||||
- [ ] Самодокументований код
|
||||
|
||||
## Чеклист тестування
|
||||
- [ ] Юніт-тести написані
|
||||
- [ ] Граничні випадки покриті
|
||||
- [ ] Сценарії помилок протестовані
|
||||
- [ ] Інтеграційні тести є
|
||||
- [ ] Покриття > 80%
|
||||
- [ ] Немає нестабільних тестів
|
||||
- [ ] Зовнішні залежності замоковані
|
||||
- [ ] Зрозумілі назви тестів
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
import ast
|
||||
|
||||
|
||||
class APIDocExtractor(ast.NodeVisitor):
|
||||
"""Extract API documentation from Python source code."""
|
||||
|
||||
def __init__(self):
|
||||
self.endpoints = []
|
||||
|
||||
def visit_FunctionDef(self, node):
|
||||
"""Extract function documentation."""
|
||||
if node.name.startswith("get_") or node.name.startswith("post_"):
|
||||
doc = ast.get_docstring(node)
|
||||
endpoint = {
|
||||
"name": node.name,
|
||||
"docstring": doc,
|
||||
"params": [arg.arg for arg in node.args.args],
|
||||
"returns": self._extract_return_type(node),
|
||||
}
|
||||
self.endpoints.append(endpoint)
|
||||
self.generic_visit(node)
|
||||
|
||||
def _extract_return_type(self, node):
|
||||
"""Extract return type from function annotation."""
|
||||
if node.returns:
|
||||
return ast.unparse(node.returns)
|
||||
return "Any"
|
||||
|
||||
|
||||
def generate_markdown_docs(endpoints: list[dict]) -> str:
|
||||
"""Generate markdown documentation from endpoints."""
|
||||
docs = "# API Documentation\n\n"
|
||||
|
||||
for endpoint in endpoints:
|
||||
docs += f"## {endpoint['name']}\n\n"
|
||||
docs += f"{endpoint['docstring']}\n\n"
|
||||
docs += f"**Parameters**: {', '.join(endpoint['params'])}\n\n"
|
||||
docs += f"**Returns**: {endpoint['returns']}\n\n"
|
||||
docs += "---\n\n"
|
||||
|
||||
return docs
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
with open(sys.argv[1]) as f:
|
||||
tree = ast.parse(f.read())
|
||||
|
||||
extractor = APIDocExtractor()
|
||||
extractor.visit(tree)
|
||||
|
||||
markdown = generate_markdown_docs(extractor.endpoints)
|
||||
print(markdown)
|
||||
@@ -0,0 +1,545 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Code Complexity Analyzer
|
||||
|
||||
Analyzes code complexity metrics for Python, JavaScript, and TypeScript files.
|
||||
Helps measure the impact of refactoring by comparing before/after metrics.
|
||||
|
||||
Usage:
|
||||
python analyze-complexity.py <file>
|
||||
python analyze-complexity.py <before_file> <after_file> # Compare mode
|
||||
python analyze-complexity.py --dir <directory> # Analyze directory
|
||||
|
||||
Metrics:
|
||||
- Cyclomatic Complexity: Decision points in code
|
||||
- Cognitive Complexity: How hard is it to understand
|
||||
- Maintainability Index: Overall maintainability score (0-100)
|
||||
- Lines of Code: Total lines
|
||||
- Function Count: Number of functions/methods
|
||||
- Average Function Length: Lines per function
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class FunctionMetrics:
|
||||
"""Metrics for a single function."""
|
||||
name: str
|
||||
start_line: int
|
||||
end_line: int
|
||||
lines: int
|
||||
cyclomatic_complexity: int
|
||||
cognitive_complexity: int
|
||||
parameter_count: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class FileMetrics:
|
||||
"""Metrics for a file."""
|
||||
filename: str
|
||||
lines_of_code: int
|
||||
blank_lines: int
|
||||
comment_lines: int
|
||||
function_count: int
|
||||
class_count: int
|
||||
cyclomatic_complexity: int
|
||||
cognitive_complexity: int
|
||||
maintainability_index: float
|
||||
avg_function_length: float
|
||||
max_function_length: int
|
||||
functions: List[FunctionMetrics]
|
||||
|
||||
|
||||
class ComplexityAnalyzer:
|
||||
"""Analyze code complexity for multiple languages."""
|
||||
|
||||
# Patterns for different languages
|
||||
PATTERNS = {
|
||||
'python': {
|
||||
'function': r'^\s*def\s+(\w+)\s*\(',
|
||||
'class': r'^\s*class\s+(\w+)',
|
||||
'decision': [r'\bif\b', r'\belif\b', r'\bfor\b', r'\bwhile\b',
|
||||
r'\bexcept\b', r'\band\b(?!$)', r'\bor\b(?!$)',
|
||||
r'\bcase\b', r'\btry\b'],
|
||||
'comment': r'^\s*#',
|
||||
'multiline_comment_start': r'^\s*["\'][\"\'][\"\']',
|
||||
'multiline_comment_end': r'["\'][\"\'][\"\']',
|
||||
},
|
||||
'javascript': {
|
||||
'function': r'(?:function\s+(\w+)|(\w+)\s*[=:]\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>))',
|
||||
'class': r'class\s+(\w+)',
|
||||
'decision': [r'\bif\b', r'\belse\s+if\b', r'\bfor\b', r'\bwhile\b',
|
||||
r'\bcatch\b', r'\b\?\b', r'\b&&\b', r'\b\|\|\b',
|
||||
r'\bcase\b', r'\btry\b'],
|
||||
'comment': r'^\s*//',
|
||||
'multiline_comment_start': r'/\*',
|
||||
'multiline_comment_end': r'\*/',
|
||||
},
|
||||
'typescript': {
|
||||
'function': r'(?:function\s+(\w+)|(\w+)\s*[=:]\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>))',
|
||||
'class': r'class\s+(\w+)',
|
||||
'decision': [r'\bif\b', r'\belse\s+if\b', r'\bfor\b', r'\bwhile\b',
|
||||
r'\bcatch\b', r'\b\?\b', r'\b&&\b', r'\b\|\|\b',
|
||||
r'\bcase\b', r'\btry\b'],
|
||||
'comment': r'^\s*//',
|
||||
'multiline_comment_start': r'/\*',
|
||||
'multiline_comment_end': r'\*/',
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
self.filename = os.path.basename(filepath)
|
||||
self.language = self._detect_language()
|
||||
self.patterns = self.PATTERNS.get(self.language, self.PATTERNS['python'])
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
self.code = f.read()
|
||||
self.lines = self.code.split('\n')
|
||||
|
||||
def _detect_language(self) -> str:
|
||||
"""Detect programming language from file extension."""
|
||||
ext = os.path.splitext(self.filepath)[1].lower()
|
||||
ext_map = {
|
||||
'.py': 'python',
|
||||
'.js': 'javascript',
|
||||
'.jsx': 'javascript',
|
||||
'.ts': 'typescript',
|
||||
'.tsx': 'typescript',
|
||||
}
|
||||
return ext_map.get(ext, 'python')
|
||||
|
||||
def calculate_cyclomatic_complexity(self, code: Optional[str] = None) -> int:
|
||||
"""
|
||||
Calculate cyclomatic complexity using McCabe's method.
|
||||
CC = E - N + 2P where E=edges, N=nodes, P=connected components
|
||||
Simplified: Count decision points + 1
|
||||
"""
|
||||
if code is None:
|
||||
code = self.code
|
||||
|
||||
complexity = 1 # Base complexity
|
||||
|
||||
for pattern in self.patterns['decision']:
|
||||
matches = re.findall(pattern, code)
|
||||
complexity += len(matches)
|
||||
|
||||
return complexity
|
||||
|
||||
def calculate_cognitive_complexity(self, code: Optional[str] = None) -> int:
|
||||
"""
|
||||
Calculate cognitive complexity.
|
||||
Measures how hard it is to understand the code.
|
||||
Accounts for nesting depth and control flow breaks.
|
||||
"""
|
||||
if code is None:
|
||||
code = self.code
|
||||
|
||||
lines = code.split('\n')
|
||||
cognitive = 0
|
||||
nesting_depth = 0
|
||||
in_function = False
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
|
||||
# Track function boundaries
|
||||
if re.search(self.patterns['function'], line):
|
||||
in_function = True
|
||||
nesting_depth = 0
|
||||
|
||||
# Increment for control flow structures
|
||||
if re.search(r'\b(if|for|while|switch)\b', stripped):
|
||||
nesting_depth += 1
|
||||
cognitive += nesting_depth # Nested structures cost more
|
||||
|
||||
elif re.search(r'\b(elif|else if|else|catch|finally)\b', stripped):
|
||||
cognitive += nesting_depth # Same level as parent
|
||||
|
||||
# Track nesting through braces/indentation
|
||||
if self.language in ['javascript', 'typescript']:
|
||||
nesting_depth += stripped.count('{') - stripped.count('}')
|
||||
nesting_depth = max(0, nesting_depth)
|
||||
|
||||
# Bonus for breaks in linear flow
|
||||
if re.search(r'\b(break|continue|return|throw)\b', stripped):
|
||||
if nesting_depth > 1:
|
||||
cognitive += 1
|
||||
|
||||
# Bonus for recursion
|
||||
# (simplified: just look for function calling itself)
|
||||
|
||||
return cognitive
|
||||
|
||||
def calculate_maintainability_index(self) -> float:
|
||||
"""
|
||||
Calculate Maintainability Index (0-100).
|
||||
Based on Halstead Volume, Cyclomatic Complexity, and Lines of Code.
|
||||
|
||||
MI = max(0, (171 - 5.2*ln(V) - 0.23*CC - 16.2*ln(LOC)) * 100/171)
|
||||
|
||||
Interpretation:
|
||||
- 85-100: Highly maintainable
|
||||
- 65-84: Moderately maintainable
|
||||
- 50-64: Difficult to maintain
|
||||
- 0-49: Very difficult to maintain
|
||||
"""
|
||||
import math
|
||||
|
||||
loc = len([l for l in self.lines if l.strip()])
|
||||
cc = self.calculate_cyclomatic_complexity()
|
||||
|
||||
# Simplified Halstead Volume approximation
|
||||
# Count unique operators and operands
|
||||
operators = len(re.findall(r'[+\-*/%=<>!&|^~]', self.code))
|
||||
operands = len(re.findall(r'\b\w+\b', self.code))
|
||||
volume = (operators + operands) * math.log2(max(1, operators + operands))
|
||||
|
||||
# Calculate MI
|
||||
mi = 171 - 5.2 * math.log(max(1, volume)) - 0.23 * cc - 16.2 * math.log(max(1, loc))
|
||||
mi = max(0, min(100, mi * 100 / 171))
|
||||
|
||||
return round(mi, 2)
|
||||
|
||||
def count_lines(self) -> Dict[str, int]:
|
||||
"""Count different types of lines."""
|
||||
total = len(self.lines)
|
||||
blank = 0
|
||||
comment = 0
|
||||
in_multiline_comment = False
|
||||
|
||||
for line in self.lines:
|
||||
stripped = line.strip()
|
||||
|
||||
# Check for multiline comments
|
||||
if re.search(self.patterns['multiline_comment_start'], stripped):
|
||||
in_multiline_comment = True
|
||||
if re.search(self.patterns['multiline_comment_end'], stripped):
|
||||
in_multiline_comment = False
|
||||
comment += 1
|
||||
continue
|
||||
|
||||
if in_multiline_comment:
|
||||
comment += 1
|
||||
elif not stripped:
|
||||
blank += 1
|
||||
elif re.match(self.patterns['comment'], stripped):
|
||||
comment += 1
|
||||
|
||||
return {
|
||||
'total': total,
|
||||
'blank': blank,
|
||||
'comment': comment,
|
||||
'code': total - blank - comment
|
||||
}
|
||||
|
||||
def find_functions(self) -> List[FunctionMetrics]:
|
||||
"""Find all functions and calculate their individual metrics."""
|
||||
functions = []
|
||||
current_function = None
|
||||
function_start = 0
|
||||
brace_depth = 0
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
# Check for function definition
|
||||
match = re.search(self.patterns['function'], line)
|
||||
if match:
|
||||
# Save previous function if exists
|
||||
if current_function:
|
||||
func_code = '\n'.join(self.lines[function_start:i])
|
||||
functions.append(self._create_function_metrics(
|
||||
current_function, function_start, i - 1, func_code
|
||||
))
|
||||
|
||||
current_function = match.group(1) or match.group(2) if match.lastindex and match.lastindex > 1 else match.group(1)
|
||||
function_start = i
|
||||
brace_depth = 0
|
||||
|
||||
# Track braces for JS/TS
|
||||
if self.language in ['javascript', 'typescript']:
|
||||
brace_depth += line.count('{') - line.count('}')
|
||||
|
||||
# Don't forget the last function
|
||||
if current_function:
|
||||
func_code = '\n'.join(self.lines[function_start:])
|
||||
functions.append(self._create_function_metrics(
|
||||
current_function, function_start, len(self.lines) - 1, func_code
|
||||
))
|
||||
|
||||
return functions
|
||||
|
||||
def _create_function_metrics(self, name: str, start: int, end: int, code: str) -> FunctionMetrics:
|
||||
"""Create metrics for a single function."""
|
||||
lines = end - start + 1
|
||||
|
||||
# Count parameters (simplified)
|
||||
param_match = re.search(r'\(([^)]*)\)', code.split('\n')[0])
|
||||
param_count = 0
|
||||
if param_match and param_match.group(1).strip():
|
||||
param_count = len([p for p in param_match.group(1).split(',') if p.strip()])
|
||||
|
||||
return FunctionMetrics(
|
||||
name=name,
|
||||
start_line=start + 1,
|
||||
end_line=end + 1,
|
||||
lines=lines,
|
||||
cyclomatic_complexity=self.calculate_cyclomatic_complexity(code),
|
||||
cognitive_complexity=self.calculate_cognitive_complexity(code),
|
||||
parameter_count=param_count
|
||||
)
|
||||
|
||||
def analyze(self) -> FileMetrics:
|
||||
"""Perform complete analysis of the file."""
|
||||
line_counts = self.count_lines()
|
||||
functions = self.find_functions()
|
||||
|
||||
# Count classes
|
||||
class_count = len(re.findall(self.patterns['class'], self.code))
|
||||
|
||||
# Calculate averages
|
||||
func_lengths = [f.lines for f in functions] if functions else [0]
|
||||
avg_func_length = sum(func_lengths) / len(func_lengths)
|
||||
max_func_length = max(func_lengths)
|
||||
|
||||
return FileMetrics(
|
||||
filename=self.filename,
|
||||
lines_of_code=line_counts['code'],
|
||||
blank_lines=line_counts['blank'],
|
||||
comment_lines=line_counts['comment'],
|
||||
function_count=len(functions),
|
||||
class_count=class_count,
|
||||
cyclomatic_complexity=self.calculate_cyclomatic_complexity(),
|
||||
cognitive_complexity=self.calculate_cognitive_complexity(),
|
||||
maintainability_index=self.calculate_maintainability_index(),
|
||||
avg_function_length=round(avg_func_length, 1),
|
||||
max_function_length=max_func_length,
|
||||
functions=functions
|
||||
)
|
||||
|
||||
|
||||
def print_metrics(metrics: FileMetrics, verbose: bool = False) -> None:
|
||||
"""Print metrics in a readable format."""
|
||||
print("=" * 60)
|
||||
print(f"CODE COMPLEXITY ANALYSIS: {metrics.filename}")
|
||||
print("=" * 60)
|
||||
|
||||
print("\n📊 OVERVIEW")
|
||||
print("-" * 40)
|
||||
print(f" Lines of Code: {metrics.lines_of_code}")
|
||||
print(f" Blank Lines: {metrics.blank_lines}")
|
||||
print(f" Comment Lines: {metrics.comment_lines}")
|
||||
print(f" Functions/Methods: {metrics.function_count}")
|
||||
print(f" Classes: {metrics.class_count}")
|
||||
|
||||
print("\n📈 COMPLEXITY METRICS")
|
||||
print("-" * 40)
|
||||
print(f" Cyclomatic Complexity: {metrics.cyclomatic_complexity}")
|
||||
print(f" Cognitive Complexity: {metrics.cognitive_complexity}")
|
||||
print(f" Maintainability Index: {metrics.maintainability_index}")
|
||||
|
||||
# Interpret maintainability
|
||||
mi = metrics.maintainability_index
|
||||
if mi >= 85:
|
||||
mi_label = "Highly maintainable ✅"
|
||||
elif mi >= 65:
|
||||
mi_label = "Moderately maintainable 🔶"
|
||||
elif mi >= 50:
|
||||
mi_label = "Difficult to maintain ⚠️"
|
||||
else:
|
||||
mi_label = "Very difficult to maintain ❌"
|
||||
print(f" → {mi_label}")
|
||||
|
||||
print("\n📐 FUNCTION METRICS")
|
||||
print("-" * 40)
|
||||
print(f" Avg Function Length: {metrics.avg_function_length} lines")
|
||||
print(f" Max Function Length: {metrics.max_function_length} lines")
|
||||
|
||||
if verbose and metrics.functions:
|
||||
print("\n📋 FUNCTION DETAILS")
|
||||
print("-" * 40)
|
||||
for f in sorted(metrics.functions, key=lambda x: x.cyclomatic_complexity, reverse=True):
|
||||
flag = " ⚠️" if f.cyclomatic_complexity > 10 or f.lines > 50 else ""
|
||||
print(f" {f.name}() [lines {f.start_line}-{f.end_line}]{flag}")
|
||||
print(f" - Lines: {f.lines}, CC: {f.cyclomatic_complexity}, "
|
||||
f"Cognitive: {f.cognitive_complexity}, Params: {f.parameter_count}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
|
||||
|
||||
def print_comparison(before: FileMetrics, after: FileMetrics) -> None:
|
||||
"""Print comparison between two analyses."""
|
||||
print("=" * 70)
|
||||
print("CODE COMPLEXITY COMPARISON")
|
||||
print("=" * 70)
|
||||
|
||||
print(f"\n{'Metric':<30} {'Before':<15} {'After':<15} {'Change':<10}")
|
||||
print("-" * 70)
|
||||
|
||||
def fmt_change(before_val, after_val, lower_is_better=True):
|
||||
diff = after_val - before_val
|
||||
if lower_is_better:
|
||||
symbol = "✅" if diff < 0 else ("⚠️" if diff > 0 else "➖")
|
||||
else:
|
||||
symbol = "✅" if diff > 0 else ("⚠️" if diff < 0 else "➖")
|
||||
return f"{diff:+.1f} {symbol}" if isinstance(diff, float) else f"{diff:+d} {symbol}"
|
||||
|
||||
metrics = [
|
||||
("Lines of Code", before.lines_of_code, after.lines_of_code, True),
|
||||
("Function Count", before.function_count, after.function_count, False),
|
||||
("Class Count", before.class_count, after.class_count, False),
|
||||
("Cyclomatic Complexity", before.cyclomatic_complexity, after.cyclomatic_complexity, True),
|
||||
("Cognitive Complexity", before.cognitive_complexity, after.cognitive_complexity, True),
|
||||
("Maintainability Index", before.maintainability_index, after.maintainability_index, False),
|
||||
("Avg Function Length", before.avg_function_length, after.avg_function_length, True),
|
||||
("Max Function Length", before.max_function_length, after.max_function_length, True),
|
||||
]
|
||||
|
||||
for name, b_val, a_val, lower_better in metrics:
|
||||
change = fmt_change(b_val, a_val, lower_better)
|
||||
print(f"{name:<30} {b_val:<15} {a_val:<15} {change:<10}")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
|
||||
# Overall assessment
|
||||
print("\n🎯 ASSESSMENT")
|
||||
print("-" * 40)
|
||||
|
||||
improvements = 0
|
||||
regressions = 0
|
||||
|
||||
if after.maintainability_index > before.maintainability_index:
|
||||
print(" ✅ Maintainability improved")
|
||||
improvements += 1
|
||||
elif after.maintainability_index < before.maintainability_index:
|
||||
print(" ⚠️ Maintainability decreased")
|
||||
regressions += 1
|
||||
|
||||
if after.cyclomatic_complexity < before.cyclomatic_complexity:
|
||||
print(" ✅ Complexity reduced")
|
||||
improvements += 1
|
||||
elif after.cyclomatic_complexity > before.cyclomatic_complexity:
|
||||
print(" ⚠️ Complexity increased")
|
||||
regressions += 1
|
||||
|
||||
if after.avg_function_length < before.avg_function_length:
|
||||
print(" ✅ Functions are smaller on average")
|
||||
improvements += 1
|
||||
elif after.avg_function_length > before.avg_function_length:
|
||||
print(" ⚠️ Functions grew larger on average")
|
||||
regressions += 1
|
||||
|
||||
print(f"\n Summary: {improvements} improvements, {regressions} regressions")
|
||||
print("=" * 70)
|
||||
|
||||
|
||||
def analyze_directory(directory: str, verbose: bool = False) -> None:
|
||||
"""Analyze all supported files in a directory."""
|
||||
supported_extensions = ['.py', '.js', '.jsx', '.ts', '.tsx']
|
||||
files = []
|
||||
|
||||
for root, _, filenames in os.walk(directory):
|
||||
for filename in filenames:
|
||||
if any(filename.endswith(ext) for ext in supported_extensions):
|
||||
files.append(os.path.join(root, filename))
|
||||
|
||||
if not files:
|
||||
print(f"No supported files found in {directory}")
|
||||
return
|
||||
|
||||
print(f"Analyzing {len(files)} files in {directory}...\n")
|
||||
|
||||
total_loc = 0
|
||||
total_cc = 0
|
||||
total_functions = 0
|
||||
all_metrics = []
|
||||
|
||||
for filepath in sorted(files):
|
||||
try:
|
||||
analyzer = ComplexityAnalyzer(filepath)
|
||||
metrics = analyzer.analyze()
|
||||
all_metrics.append(metrics)
|
||||
|
||||
total_loc += metrics.lines_of_code
|
||||
total_cc += metrics.cyclomatic_complexity
|
||||
total_functions += metrics.function_count
|
||||
|
||||
if verbose:
|
||||
print_metrics(metrics, verbose=True)
|
||||
else:
|
||||
flag = " ⚠️" if metrics.maintainability_index < 65 else ""
|
||||
print(f" {metrics.filename}: LOC={metrics.lines_of_code}, "
|
||||
f"CC={metrics.cyclomatic_complexity}, MI={metrics.maintainability_index}{flag}")
|
||||
except Exception as e:
|
||||
print(f" Error analyzing {filepath}: {e}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("SUMMARY")
|
||||
print("=" * 60)
|
||||
print(f" Files analyzed: {len(all_metrics)}")
|
||||
print(f" Total lines of code: {total_loc}")
|
||||
print(f" Total complexity: {total_cc}")
|
||||
print(f" Total functions: {total_functions}")
|
||||
|
||||
if all_metrics:
|
||||
avg_mi = sum(m.maintainability_index for m in all_metrics) / len(all_metrics)
|
||||
print(f" Avg maintainability: {avg_mi:.1f}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Analyze code complexity metrics',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
%(prog)s myfile.py Analyze single file
|
||||
%(prog)s before.py after.py Compare two versions
|
||||
%(prog)s --dir src/ Analyze directory
|
||||
%(prog)s -v myfile.py Verbose output with function details
|
||||
"""
|
||||
)
|
||||
parser.add_argument('files', nargs='*', help='File(s) to analyze')
|
||||
parser.add_argument('--dir', '-d', help='Directory to analyze')
|
||||
parser.add_argument('--verbose', '-v', action='store_true', help='Show detailed function metrics')
|
||||
parser.add_argument('--json', '-j', action='store_true', help='Output as JSON')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.dir:
|
||||
analyze_directory(args.dir, args.verbose)
|
||||
elif len(args.files) == 1:
|
||||
analyzer = ComplexityAnalyzer(args.files[0])
|
||||
metrics = analyzer.analyze()
|
||||
|
||||
if args.json:
|
||||
import json
|
||||
print(json.dumps({
|
||||
'filename': metrics.filename,
|
||||
'lines_of_code': metrics.lines_of_code,
|
||||
'cyclomatic_complexity': metrics.cyclomatic_complexity,
|
||||
'cognitive_complexity': metrics.cognitive_complexity,
|
||||
'maintainability_index': metrics.maintainability_index,
|
||||
'function_count': metrics.function_count,
|
||||
'avg_function_length': metrics.avg_function_length,
|
||||
}, indent=2))
|
||||
else:
|
||||
print_metrics(metrics, args.verbose)
|
||||
elif len(args.files) == 2:
|
||||
before_analyzer = ComplexityAnalyzer(args.files[0])
|
||||
after_analyzer = ComplexityAnalyzer(args.files[1])
|
||||
before_metrics = before_analyzer.analyze()
|
||||
after_metrics = after_analyzer.analyze()
|
||||
print_comparison(before_metrics, after_metrics)
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,711 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Code Smell Detector
|
||||
|
||||
Detects common code smells in Python, JavaScript, and TypeScript files.
|
||||
Based on Martin Fowler's catalog of code smells.
|
||||
|
||||
Usage:
|
||||
python detect-smells.py <file>
|
||||
python detect-smells.py --dir <directory>
|
||||
python detect-smells.py -v <file> # Verbose with code snippets
|
||||
|
||||
Detects:
|
||||
- Long Method (>30 lines)
|
||||
- Long Parameter List (>4 params)
|
||||
- Duplicate Code (similar code blocks)
|
||||
- Large Class (>300 lines, >10 methods)
|
||||
- Dead Code (unused variables/functions)
|
||||
- Complex Conditionals (deep nesting, long chains)
|
||||
- Magic Numbers/Strings
|
||||
- Feature Envy (methods using other class data heavily)
|
||||
- Comments explaining what (not why)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Optional, Set, Tuple
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class SmellSeverity(Enum):
|
||||
"""Severity levels for code smells."""
|
||||
LOW = "Low"
|
||||
MEDIUM = "Medium"
|
||||
HIGH = "High"
|
||||
CRITICAL = "Critical"
|
||||
|
||||
|
||||
class SmellType(Enum):
|
||||
"""Types of code smells."""
|
||||
LONG_METHOD = "Long Method"
|
||||
LONG_PARAMETER_LIST = "Long Parameter List"
|
||||
DUPLICATE_CODE = "Duplicate Code"
|
||||
LARGE_CLASS = "Large Class"
|
||||
DEAD_CODE = "Dead Code"
|
||||
COMPLEX_CONDITIONAL = "Complex Conditional"
|
||||
MAGIC_NUMBER = "Magic Number/String"
|
||||
FEATURE_ENVY = "Feature Envy"
|
||||
EXCESSIVE_COMMENTS = "Excessive Comments"
|
||||
DEEPLY_NESTED = "Deeply Nested Code"
|
||||
PRIMITIVE_OBSESSION = "Primitive Obsession"
|
||||
DATA_CLUMPS = "Data Clumps"
|
||||
SWITCH_STATEMENT = "Switch Statement"
|
||||
MESSAGE_CHAIN = "Message Chain"
|
||||
|
||||
|
||||
@dataclass
|
||||
class CodeSmell:
|
||||
"""Represents a detected code smell."""
|
||||
smell_type: SmellType
|
||||
severity: SmellSeverity
|
||||
location: str
|
||||
line_start: int
|
||||
line_end: int
|
||||
description: str
|
||||
suggestion: str
|
||||
code_snippet: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class SmellReport:
|
||||
"""Report of all smells found in a file."""
|
||||
filename: str
|
||||
smells: List[CodeSmell] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def critical_count(self) -> int:
|
||||
return sum(1 for s in self.smells if s.severity == SmellSeverity.CRITICAL)
|
||||
|
||||
@property
|
||||
def high_count(self) -> int:
|
||||
return sum(1 for s in self.smells if s.severity == SmellSeverity.HIGH)
|
||||
|
||||
@property
|
||||
def medium_count(self) -> int:
|
||||
return sum(1 for s in self.smells if s.severity == SmellSeverity.MEDIUM)
|
||||
|
||||
@property
|
||||
def low_count(self) -> int:
|
||||
return sum(1 for s in self.smells if s.severity == SmellSeverity.LOW)
|
||||
|
||||
|
||||
class SmellDetector:
|
||||
"""Detect code smells in source files."""
|
||||
|
||||
# Thresholds (configurable)
|
||||
THRESHOLDS = {
|
||||
'long_method_lines': 30,
|
||||
'very_long_method_lines': 50,
|
||||
'max_parameters': 4,
|
||||
'large_class_lines': 300,
|
||||
'large_class_methods': 10,
|
||||
'max_nesting_depth': 4,
|
||||
'long_chain_length': 3,
|
||||
'duplicate_min_lines': 5,
|
||||
}
|
||||
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
self.filename = os.path.basename(filepath)
|
||||
self.language = self._detect_language()
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
self.code = f.read()
|
||||
self.lines = self.code.split('\n')
|
||||
self.smells: List[CodeSmell] = []
|
||||
|
||||
def _detect_language(self) -> str:
|
||||
"""Detect programming language from file extension."""
|
||||
ext = os.path.splitext(self.filepath)[1].lower()
|
||||
ext_map = {
|
||||
'.py': 'python',
|
||||
'.js': 'javascript',
|
||||
'.jsx': 'javascript',
|
||||
'.ts': 'typescript',
|
||||
'.tsx': 'typescript',
|
||||
}
|
||||
return ext_map.get(ext, 'python')
|
||||
|
||||
def detect_all(self) -> SmellReport:
|
||||
"""Run all smell detectors."""
|
||||
self._detect_long_methods()
|
||||
self._detect_long_parameter_lists()
|
||||
self._detect_large_class()
|
||||
self._detect_complex_conditionals()
|
||||
self._detect_magic_numbers()
|
||||
self._detect_excessive_comments()
|
||||
self._detect_deeply_nested()
|
||||
self._detect_switch_statements()
|
||||
self._detect_message_chains()
|
||||
self._detect_duplicate_code()
|
||||
self._detect_dead_code()
|
||||
|
||||
return SmellReport(filename=self.filename, smells=self.smells)
|
||||
|
||||
def _get_snippet(self, start: int, end: int, context: int = 2) -> str:
|
||||
"""Get code snippet with context."""
|
||||
actual_start = max(0, start - context)
|
||||
actual_end = min(len(self.lines), end + context)
|
||||
snippet_lines = []
|
||||
for i in range(actual_start, actual_end):
|
||||
prefix = "→ " if start <= i < end else " "
|
||||
snippet_lines.append(f"{i+1:4d} {prefix}{self.lines[i]}")
|
||||
return '\n'.join(snippet_lines)
|
||||
|
||||
def _detect_long_methods(self) -> None:
|
||||
"""Detect methods that are too long."""
|
||||
if self.language == 'python':
|
||||
pattern = r'^\s*def\s+(\w+)\s*\([^)]*\):'
|
||||
else:
|
||||
pattern = r'(?:function\s+(\w+)|(\w+)\s*[=:]\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>))'
|
||||
|
||||
current_method = None
|
||||
method_start = 0
|
||||
brace_depth = 0
|
||||
indent_level = 0
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
match = re.search(pattern, line)
|
||||
if match:
|
||||
# Check previous method if exists
|
||||
if current_method:
|
||||
method_lines = i - method_start
|
||||
self._check_method_length(current_method, method_start, i - 1, method_lines)
|
||||
|
||||
current_method = match.group(1) or (match.group(2) if match.lastindex and match.lastindex > 1 else None)
|
||||
method_start = i
|
||||
indent_level = len(line) - len(line.lstrip())
|
||||
|
||||
# Track end of Python functions by indentation
|
||||
if self.language == 'python' and current_method:
|
||||
if line.strip() and not line.strip().startswith('#'):
|
||||
current_indent = len(line) - len(line.lstrip())
|
||||
if current_indent <= indent_level and i > method_start:
|
||||
method_lines = i - method_start
|
||||
self._check_method_length(current_method, method_start, i - 1, method_lines)
|
||||
current_method = None
|
||||
|
||||
# Check last method
|
||||
if current_method:
|
||||
method_lines = len(self.lines) - method_start
|
||||
self._check_method_length(current_method, method_start, len(self.lines) - 1, method_lines)
|
||||
|
||||
def _check_method_length(self, name: str, start: int, end: int, lines: int) -> None:
|
||||
"""Check if method is too long and add smell if so."""
|
||||
if lines > self.THRESHOLDS['very_long_method_lines']:
|
||||
severity = SmellSeverity.HIGH
|
||||
desc = f"Method '{name}' is {lines} lines (threshold: {self.THRESHOLDS['long_method_lines']})"
|
||||
elif lines > self.THRESHOLDS['long_method_lines']:
|
||||
severity = SmellSeverity.MEDIUM
|
||||
desc = f"Method '{name}' is {lines} lines (threshold: {self.THRESHOLDS['long_method_lines']})"
|
||||
else:
|
||||
return
|
||||
|
||||
self.smells.append(CodeSmell(
|
||||
smell_type=SmellType.LONG_METHOD,
|
||||
severity=severity,
|
||||
location=f"{self.filename}:{start+1}-{end+1}",
|
||||
line_start=start + 1,
|
||||
line_end=end + 1,
|
||||
description=desc,
|
||||
suggestion="Apply Extract Method to break down into smaller functions",
|
||||
code_snippet=self._get_snippet(start, min(start + 5, end), 0)
|
||||
))
|
||||
|
||||
def _detect_long_parameter_lists(self) -> None:
|
||||
"""Detect functions with too many parameters."""
|
||||
if self.language == 'python':
|
||||
pattern = r'def\s+(\w+)\s*\(([^)]*)\)'
|
||||
else:
|
||||
pattern = r'(?:function\s+(\w+)\s*\(([^)]*)\)|(\w+)\s*[=:]\s*(?:async\s+)?(?:function\s*)?\(([^)]*)\))'
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
match = re.search(pattern, line)
|
||||
if match:
|
||||
# Safely extract groups
|
||||
groups = match.groups()
|
||||
func_name = groups[0] or (groups[2] if len(groups) > 2 else None)
|
||||
params_str = groups[1] if len(groups) > 1 else ""
|
||||
if not params_str and len(groups) > 3:
|
||||
params_str = groups[3] or ""
|
||||
|
||||
# Count parameters
|
||||
if params_str.strip():
|
||||
params = [p.strip() for p in params_str.split(',') if p.strip()]
|
||||
# Filter out 'self', 'cls' for Python
|
||||
if self.language == 'python':
|
||||
params = [p for p in params if p not in ('self', 'cls')]
|
||||
param_count = len(params)
|
||||
|
||||
if param_count > self.THRESHOLDS['max_parameters']:
|
||||
severity = SmellSeverity.HIGH if param_count > 6 else SmellSeverity.MEDIUM
|
||||
self.smells.append(CodeSmell(
|
||||
smell_type=SmellType.LONG_PARAMETER_LIST,
|
||||
severity=severity,
|
||||
location=f"{self.filename}:{i+1}",
|
||||
line_start=i + 1,
|
||||
line_end=i + 1,
|
||||
description=f"Function '{func_name}' has {param_count} parameters (max: {self.THRESHOLDS['max_parameters']})",
|
||||
suggestion="Consider Introduce Parameter Object or Preserve Whole Object",
|
||||
code_snippet=self._get_snippet(i, i + 1, 1)
|
||||
))
|
||||
|
||||
def _detect_large_class(self) -> None:
|
||||
"""Detect classes that are too large."""
|
||||
if self.language == 'python':
|
||||
class_pattern = r'^\s*class\s+(\w+)'
|
||||
method_pattern = r'^\s+def\s+\w+'
|
||||
else:
|
||||
class_pattern = r'class\s+(\w+)'
|
||||
method_pattern = r'(?:^\s+\w+\s*\([^)]*\)\s*\{|^\s+(?:async\s+)?\w+\s*=)'
|
||||
|
||||
current_class = None
|
||||
class_start = 0
|
||||
method_count = 0
|
||||
class_indent = 0
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
class_match = re.search(class_pattern, line)
|
||||
if class_match:
|
||||
# Check previous class
|
||||
if current_class:
|
||||
self._check_class_size(current_class, class_start, i - 1, method_count)
|
||||
|
||||
current_class = class_match.group(1)
|
||||
class_start = i
|
||||
method_count = 0
|
||||
class_indent = len(line) - len(line.lstrip())
|
||||
|
||||
# Count methods in current class
|
||||
if current_class and re.search(method_pattern, line):
|
||||
method_count += 1
|
||||
|
||||
# Check last class
|
||||
if current_class:
|
||||
self._check_class_size(current_class, class_start, len(self.lines) - 1, method_count)
|
||||
|
||||
def _check_class_size(self, name: str, start: int, end: int, methods: int) -> None:
|
||||
"""Check if class is too large."""
|
||||
lines = end - start + 1
|
||||
|
||||
issues = []
|
||||
severity = SmellSeverity.MEDIUM
|
||||
|
||||
if lines > self.THRESHOLDS['large_class_lines']:
|
||||
issues.append(f"{lines} lines (max: {self.THRESHOLDS['large_class_lines']})")
|
||||
severity = SmellSeverity.HIGH
|
||||
|
||||
if methods > self.THRESHOLDS['large_class_methods']:
|
||||
issues.append(f"{methods} methods (max: {self.THRESHOLDS['large_class_methods']})")
|
||||
if severity != SmellSeverity.HIGH:
|
||||
severity = SmellSeverity.MEDIUM
|
||||
|
||||
if issues:
|
||||
self.smells.append(CodeSmell(
|
||||
smell_type=SmellType.LARGE_CLASS,
|
||||
severity=severity,
|
||||
location=f"{self.filename}:{start+1}-{end+1}",
|
||||
line_start=start + 1,
|
||||
line_end=end + 1,
|
||||
description=f"Class '{name}' is too large: {', '.join(issues)}",
|
||||
suggestion="Apply Extract Class to split responsibilities",
|
||||
code_snippet=self._get_snippet(start, start + 3, 0)
|
||||
))
|
||||
|
||||
def _detect_complex_conditionals(self) -> None:
|
||||
"""Detect complex conditional expressions."""
|
||||
for i, line in enumerate(self.lines):
|
||||
# Count logical operators in line
|
||||
and_or_count = len(re.findall(r'\b(and|or|&&|\|\|)\b', line))
|
||||
|
||||
if and_or_count >= 3:
|
||||
self.smells.append(CodeSmell(
|
||||
smell_type=SmellType.COMPLEX_CONDITIONAL,
|
||||
severity=SmellSeverity.MEDIUM,
|
||||
location=f"{self.filename}:{i+1}",
|
||||
line_start=i + 1,
|
||||
line_end=i + 1,
|
||||
description=f"Complex conditional with {and_or_count} logical operators",
|
||||
suggestion="Apply Decompose Conditional or Consolidate Conditional Expression",
|
||||
code_snippet=self._get_snippet(i, i + 1, 1)
|
||||
))
|
||||
|
||||
def _detect_magic_numbers(self) -> None:
|
||||
"""Detect magic numbers and strings."""
|
||||
# Skip common acceptable values
|
||||
acceptable = {'0', '1', '-1', '2', '100', 'true', 'false', 'null', 'None', '""', "''"}
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
# Skip comments and imports
|
||||
stripped = line.strip()
|
||||
if stripped.startswith('#') or stripped.startswith('//') or \
|
||||
stripped.startswith('import') or stripped.startswith('from'):
|
||||
continue
|
||||
|
||||
# Find numeric literals (excluding in variable names)
|
||||
numbers = re.findall(r'(?<![a-zA-Z_])\b(\d+\.?\d*)\b(?![a-zA-Z_])', line)
|
||||
|
||||
for num in numbers:
|
||||
if num not in acceptable and float(num) > 2:
|
||||
# Check if it's likely a magic number (in calculation or comparison)
|
||||
if re.search(rf'[<>=+\-*/]\s*{re.escape(num)}|{re.escape(num)}\s*[<>=+\-*/]', line):
|
||||
self.smells.append(CodeSmell(
|
||||
smell_type=SmellType.MAGIC_NUMBER,
|
||||
severity=SmellSeverity.LOW,
|
||||
location=f"{self.filename}:{i+1}",
|
||||
line_start=i + 1,
|
||||
line_end=i + 1,
|
||||
description=f"Magic number '{num}' - consider using a named constant",
|
||||
suggestion="Replace magic number with named constant",
|
||||
code_snippet=self._get_snippet(i, i + 1, 0)
|
||||
))
|
||||
break # One magic number per line is enough
|
||||
|
||||
def _detect_excessive_comments(self) -> None:
|
||||
"""Detect comments that explain 'what' instead of 'why'."""
|
||||
what_patterns = [
|
||||
r'#\s*(set|get|return|loop|iterate|check|if|increment|decrement)',
|
||||
r'//\s*(set|get|return|loop|iterate|check|if|increment|decrement)',
|
||||
]
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
for pattern in what_patterns:
|
||||
if re.search(pattern, line, re.IGNORECASE):
|
||||
self.smells.append(CodeSmell(
|
||||
smell_type=SmellType.EXCESSIVE_COMMENTS,
|
||||
severity=SmellSeverity.LOW,
|
||||
location=f"{self.filename}:{i+1}",
|
||||
line_start=i + 1,
|
||||
line_end=i + 1,
|
||||
description="Comment explains 'what' not 'why' - consider renaming or removing",
|
||||
suggestion="Use Extract Method with descriptive name instead of comment",
|
||||
code_snippet=self._get_snippet(i, i + 1, 0)
|
||||
))
|
||||
break
|
||||
|
||||
def _detect_deeply_nested(self) -> None:
|
||||
"""Detect deeply nested code blocks."""
|
||||
max_depth = 0
|
||||
current_depth = 0
|
||||
depth_start = 0
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
if self.language == 'python':
|
||||
# Count by indentation
|
||||
if line.strip():
|
||||
indent = len(line) - len(line.lstrip())
|
||||
depth = indent // 4 # Assume 4-space indent
|
||||
if depth > current_depth:
|
||||
if depth > max_depth:
|
||||
max_depth = depth
|
||||
if depth >= self.THRESHOLDS['max_nesting_depth']:
|
||||
depth_start = i
|
||||
current_depth = depth
|
||||
else:
|
||||
# Count braces
|
||||
current_depth += line.count('{') - line.count('}')
|
||||
if current_depth > max_depth:
|
||||
max_depth = current_depth
|
||||
if current_depth >= self.THRESHOLDS['max_nesting_depth']:
|
||||
depth_start = i
|
||||
|
||||
if max_depth >= self.THRESHOLDS['max_nesting_depth']:
|
||||
self.smells.append(CodeSmell(
|
||||
smell_type=SmellType.DEEPLY_NESTED,
|
||||
severity=SmellSeverity.HIGH if max_depth > 5 else SmellSeverity.MEDIUM,
|
||||
location=f"{self.filename}:{depth_start+1}",
|
||||
line_start=depth_start + 1,
|
||||
line_end=depth_start + 1,
|
||||
description=f"Code nested {max_depth} levels deep (max: {self.THRESHOLDS['max_nesting_depth']})",
|
||||
suggestion="Apply Replace Nested Conditional with Guard Clauses or Extract Method",
|
||||
code_snippet=self._get_snippet(depth_start, depth_start + 5, 0)
|
||||
))
|
||||
|
||||
def _detect_switch_statements(self) -> None:
|
||||
"""Detect switch statements that might need polymorphism."""
|
||||
if self.language == 'python':
|
||||
# Python 3.10+ match statements or if/elif chains
|
||||
pattern = r'^\s*(if|elif).*==.*:'
|
||||
consecutive_conditions = 0
|
||||
chain_start = 0
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
if re.search(pattern, line):
|
||||
if consecutive_conditions == 0:
|
||||
chain_start = i
|
||||
consecutive_conditions += 1
|
||||
else:
|
||||
if consecutive_conditions >= 4:
|
||||
self._add_switch_smell(chain_start, i - 1, consecutive_conditions)
|
||||
consecutive_conditions = 0
|
||||
else:
|
||||
# JavaScript/TypeScript switch
|
||||
pattern = r'\bswitch\s*\('
|
||||
for i, line in enumerate(self.lines):
|
||||
if re.search(pattern, line):
|
||||
# Count cases
|
||||
case_count = 0
|
||||
for j in range(i, min(i + 50, len(self.lines))):
|
||||
case_count += len(re.findall(r'\bcase\b', self.lines[j]))
|
||||
if case_count >= 4:
|
||||
self._add_switch_smell(i, i + 1, case_count)
|
||||
|
||||
def _add_switch_smell(self, start: int, end: int, cases: int) -> None:
|
||||
"""Add a switch statement smell."""
|
||||
self.smells.append(CodeSmell(
|
||||
smell_type=SmellType.SWITCH_STATEMENT,
|
||||
severity=SmellSeverity.MEDIUM,
|
||||
location=f"{self.filename}:{start+1}",
|
||||
line_start=start + 1,
|
||||
line_end=end + 1,
|
||||
description=f"Switch/case statement with {cases} cases - consider polymorphism",
|
||||
suggestion="Apply Replace Conditional with Polymorphism",
|
||||
code_snippet=self._get_snippet(start, start + 5, 0)
|
||||
))
|
||||
|
||||
def _detect_message_chains(self) -> None:
|
||||
"""Detect long method chains (train wrecks)."""
|
||||
chain_pattern = r'(\w+(?:\.\w+\([^)]*\)){3,})'
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
matches = re.findall(chain_pattern, line)
|
||||
for match in matches:
|
||||
chain_length = match.count('.')
|
||||
if chain_length >= self.THRESHOLDS['long_chain_length']:
|
||||
self.smells.append(CodeSmell(
|
||||
smell_type=SmellType.MESSAGE_CHAIN,
|
||||
severity=SmellSeverity.MEDIUM,
|
||||
location=f"{self.filename}:{i+1}",
|
||||
line_start=i + 1,
|
||||
line_end=i + 1,
|
||||
description=f"Message chain with {chain_length} calls - violates Law of Demeter",
|
||||
suggestion="Apply Hide Delegate to reduce coupling",
|
||||
code_snippet=self._get_snippet(i, i + 1, 0)
|
||||
))
|
||||
|
||||
def _detect_duplicate_code(self) -> None:
|
||||
"""Detect potential duplicate code blocks (simplified)."""
|
||||
# Create line hashes for comparison
|
||||
line_hashes: Dict[str, List[int]] = defaultdict(list)
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
normalized = re.sub(r'\s+', ' ', line.strip())
|
||||
if len(normalized) > 20: # Only significant lines
|
||||
line_hashes[normalized].append(i)
|
||||
|
||||
# Find duplicates
|
||||
for normalized, positions in line_hashes.items():
|
||||
if len(positions) >= 3: # At least 3 occurrences
|
||||
self.smells.append(CodeSmell(
|
||||
smell_type=SmellType.DUPLICATE_CODE,
|
||||
severity=SmellSeverity.MEDIUM,
|
||||
location=f"{self.filename}:{positions[0]+1}",
|
||||
line_start=positions[0] + 1,
|
||||
line_end=positions[0] + 1,
|
||||
description=f"Potential duplicate code found {len(positions)} times",
|
||||
suggestion="Apply Extract Method to eliminate duplication",
|
||||
code_snippet=self._get_snippet(positions[0], positions[0] + 1, 0)
|
||||
))
|
||||
|
||||
def _detect_dead_code(self) -> None:
|
||||
"""Detect potentially dead code (simplified)."""
|
||||
# Look for common dead code patterns
|
||||
patterns = [
|
||||
(r'^\s*#.*TODO.*delete', "TODO to delete"),
|
||||
(r'^\s*#.*FIXME.*remove', "FIXME to remove"),
|
||||
(r'^\s*//.*TODO.*delete', "TODO to delete"),
|
||||
(r'^\s*//.*FIXME.*remove', "FIXME to remove"),
|
||||
(r'^\s*if\s+False:', "Code behind 'if False'"),
|
||||
(r'^\s*if\s*\(\s*false\s*\)', "Code behind 'if (false)'"),
|
||||
]
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
for pattern, desc in patterns:
|
||||
if re.search(pattern, line, re.IGNORECASE):
|
||||
self.smells.append(CodeSmell(
|
||||
smell_type=SmellType.DEAD_CODE,
|
||||
severity=SmellSeverity.LOW,
|
||||
location=f"{self.filename}:{i+1}",
|
||||
line_start=i + 1,
|
||||
line_end=i + 1,
|
||||
description=f"Potential dead code: {desc}",
|
||||
suggestion="Remove dead code",
|
||||
code_snippet=self._get_snippet(i, i + 1, 0)
|
||||
))
|
||||
|
||||
|
||||
def print_report(report: SmellReport, verbose: bool = False) -> None:
|
||||
"""Print smell report in readable format."""
|
||||
print("=" * 70)
|
||||
print(f"CODE SMELL DETECTION REPORT: {report.filename}")
|
||||
print("=" * 70)
|
||||
|
||||
print(f"\n📊 SUMMARY")
|
||||
print("-" * 40)
|
||||
print(f" Total smells found: {len(report.smells)}")
|
||||
print(f" Critical: {report.critical_count}")
|
||||
print(f" High: {report.high_count}")
|
||||
print(f" Medium: {report.medium_count}")
|
||||
print(f" Low: {report.low_count}")
|
||||
|
||||
if not report.smells:
|
||||
print("\n✅ No code smells detected!")
|
||||
print("=" * 70)
|
||||
return
|
||||
|
||||
# Group by type
|
||||
by_type: Dict[SmellType, List[CodeSmell]] = defaultdict(list)
|
||||
for smell in report.smells:
|
||||
by_type[smell.smell_type].append(smell)
|
||||
|
||||
print(f"\n📋 FINDINGS BY TYPE")
|
||||
print("-" * 40)
|
||||
|
||||
for smell_type, smells in sorted(by_type.items(), key=lambda x: -len(x[1])):
|
||||
print(f"\n### {smell_type.value} ({len(smells)} found)")
|
||||
|
||||
for smell in sorted(smells, key=lambda x: x.severity.value):
|
||||
severity_icon = {
|
||||
SmellSeverity.CRITICAL: "🔴",
|
||||
SmellSeverity.HIGH: "🟠",
|
||||
SmellSeverity.MEDIUM: "🟡",
|
||||
SmellSeverity.LOW: "🟢",
|
||||
}[smell.severity]
|
||||
|
||||
print(f"\n {severity_icon} [{smell.severity.value}] {smell.location}")
|
||||
print(f" {smell.description}")
|
||||
print(f" 💡 {smell.suggestion}")
|
||||
|
||||
if verbose and smell.code_snippet:
|
||||
print(f"\n Code:")
|
||||
for snippet_line in smell.code_snippet.split('\n'):
|
||||
print(f" {snippet_line}")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("💡 RECOMMENDED ACTIONS")
|
||||
print("-" * 40)
|
||||
|
||||
if report.critical_count > 0:
|
||||
print(" 1. Address CRITICAL issues immediately")
|
||||
if report.high_count > 0:
|
||||
print(" 2. Plan to fix HIGH severity issues this sprint")
|
||||
if report.medium_count > 0:
|
||||
print(" 3. Schedule MEDIUM issues for upcoming work")
|
||||
if report.low_count > 0:
|
||||
print(" 4. Fix LOW issues opportunistically")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
|
||||
|
||||
def analyze_directory(directory: str, verbose: bool = False) -> None:
|
||||
"""Analyze all supported files in a directory."""
|
||||
supported_extensions = ['.py', '.js', '.jsx', '.ts', '.tsx']
|
||||
files = []
|
||||
|
||||
for root, _, filenames in os.walk(directory):
|
||||
for filename in filenames:
|
||||
if any(filename.endswith(ext) for ext in supported_extensions):
|
||||
files.append(os.path.join(root, filename))
|
||||
|
||||
if not files:
|
||||
print(f"No supported files found in {directory}")
|
||||
return
|
||||
|
||||
print(f"Scanning {len(files)} files in {directory}...\n")
|
||||
|
||||
total_smells = 0
|
||||
total_critical = 0
|
||||
total_high = 0
|
||||
files_with_smells = 0
|
||||
|
||||
for filepath in sorted(files):
|
||||
try:
|
||||
detector = SmellDetector(filepath)
|
||||
report = detector.detect_all()
|
||||
|
||||
if report.smells:
|
||||
files_with_smells += 1
|
||||
total_smells += len(report.smells)
|
||||
total_critical += report.critical_count
|
||||
total_high += report.high_count
|
||||
|
||||
flag = " 🔴" if report.critical_count else (" 🟠" if report.high_count else " 🟡")
|
||||
print(f" {report.filename}: {len(report.smells)} smells{flag}")
|
||||
|
||||
if verbose:
|
||||
for smell in report.smells:
|
||||
print(f" - [{smell.severity.value}] {smell.smell_type.value}: line {smell.line_start}")
|
||||
else:
|
||||
print(f" {report.filename}: ✅ Clean")
|
||||
|
||||
except Exception as e:
|
||||
print(f" Error analyzing {filepath}: {e}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("SUMMARY")
|
||||
print("=" * 60)
|
||||
print(f" Files analyzed: {len(files)}")
|
||||
print(f" Files with smells: {files_with_smells}")
|
||||
print(f" Total smells found: {total_smells}")
|
||||
print(f" Critical issues: {total_critical}")
|
||||
print(f" High severity issues: {total_high}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Detect code smells in source files',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
%(prog)s myfile.py Analyze single file
|
||||
%(prog)s --dir src/ Analyze directory
|
||||
%(prog)s -v myfile.py Verbose with code snippets
|
||||
"""
|
||||
)
|
||||
parser.add_argument('file', nargs='?', help='File to analyze')
|
||||
parser.add_argument('--dir', '-d', help='Directory to analyze')
|
||||
parser.add_argument('--verbose', '-v', action='store_true', help='Show code snippets')
|
||||
parser.add_argument('--json', '-j', action='store_true', help='Output as JSON')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.dir:
|
||||
analyze_directory(args.dir, args.verbose)
|
||||
elif args.file:
|
||||
detector = SmellDetector(args.file)
|
||||
report = detector.detect_all()
|
||||
|
||||
if args.json:
|
||||
import json
|
||||
smells_data = [{
|
||||
'type': s.smell_type.value,
|
||||
'severity': s.severity.value,
|
||||
'location': s.location,
|
||||
'line_start': s.line_start,
|
||||
'line_end': s.line_end,
|
||||
'description': s.description,
|
||||
'suggestion': s.suggestion,
|
||||
} for s in report.smells]
|
||||
print(json.dumps({
|
||||
'filename': report.filename,
|
||||
'total_smells': len(report.smells),
|
||||
'critical': report.critical_count,
|
||||
'high': report.high_count,
|
||||
'medium': report.medium_count,
|
||||
'low': report.low_count,
|
||||
'smells': smells_data
|
||||
}, indent=2))
|
||||
else:
|
||||
print_report(report, args.verbose)
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
+1086
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"database": {
|
||||
"command": "npx",
|
||||
"args": ["@modelcontextprotocol/server-database"],
|
||||
"env": {
|
||||
"DATABASE_URL": "postgresql://user:pass@localhost/mydb"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"filesystem": {
|
||||
"command": "npx",
|
||||
"args": ["@modelcontextprotocol/server-filesystem", "/home/user/projects"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"command": "npx",
|
||||
"args": ["@modelcontextprotocol/server-github"],
|
||||
"env": {
|
||||
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"command": "npx",
|
||||
"args": ["@modelcontextprotocol/server-github"],
|
||||
"env": {
|
||||
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
|
||||
}
|
||||
},
|
||||
"database": {
|
||||
"command": "npx",
|
||||
"args": ["@modelcontextprotocol/server-database"],
|
||||
"env": {
|
||||
"DATABASE_URL": "${DATABASE_URL}"
|
||||
}
|
||||
},
|
||||
"slack": {
|
||||
"command": "npx",
|
||||
"args": ["@modelcontextprotocol/server-slack"],
|
||||
"env": {
|
||||
"SLACK_TOKEN": "${SLACK_TOKEN}"
|
||||
}
|
||||
},
|
||||
"filesystem": {
|
||||
"command": "npx",
|
||||
"args": ["@modelcontextprotocol/server-filesystem", "/home/user/projects"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Context Usage Tracker (tiktoken version) - Tracks token consumption per request.
|
||||
|
||||
Uses UserPromptSubmit as "pre-message" hook and Stop as "post-response" hook
|
||||
to calculate the delta in token usage for each request.
|
||||
|
||||
This version uses tiktoken with p50k_base encoding for ~90-95% accuracy.
|
||||
Requires: pip install tiktoken
|
||||
|
||||
For a zero-dependency version, see context-tracker.py.
|
||||
|
||||
Usage:
|
||||
Configure both hooks to use the same script:
|
||||
- UserPromptSubmit: saves current token count
|
||||
- Stop: calculates delta and reports usage
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
import tiktoken
|
||||
|
||||
TIKTOKEN_AVAILABLE = True
|
||||
except ImportError:
|
||||
TIKTOKEN_AVAILABLE = False
|
||||
print(
|
||||
"Warning: tiktoken not installed. Install with: pip install tiktoken",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
# Configuration
|
||||
CONTEXT_LIMIT = 128000 # Claude's context window (adjust for your model)
|
||||
|
||||
|
||||
def get_state_file(session_id: str) -> str:
|
||||
"""Get temp file path for storing pre-message token count, isolated by session."""
|
||||
return os.path.join(tempfile.gettempdir(), f"claude-context-{session_id}.json")
|
||||
|
||||
|
||||
def count_tokens(text: str) -> int:
|
||||
"""
|
||||
Count tokens using tiktoken with p50k_base encoding.
|
||||
|
||||
This provides ~90-95% accuracy compared to Claude's actual tokenizer.
|
||||
Falls back to character estimation if tiktoken is not available.
|
||||
|
||||
Note: Anthropic hasn't released an official offline tokenizer.
|
||||
tiktoken with p50k_base is a reasonable approximation since both
|
||||
Claude and GPT models use BPE (byte-pair encoding).
|
||||
"""
|
||||
if TIKTOKEN_AVAILABLE:
|
||||
enc = tiktoken.get_encoding("p50k_base")
|
||||
return len(enc.encode(text))
|
||||
else:
|
||||
# Fallback to character estimation (~4 chars per token)
|
||||
return len(text) // 4
|
||||
|
||||
|
||||
def read_transcript(transcript_path: str) -> str:
|
||||
"""Read and concatenate all content from transcript file."""
|
||||
if not transcript_path or not os.path.exists(transcript_path):
|
||||
return ""
|
||||
|
||||
content = []
|
||||
with open(transcript_path, "r") as f:
|
||||
for line in f:
|
||||
try:
|
||||
entry = json.loads(line.strip())
|
||||
# Extract text content from various message formats
|
||||
if "message" in entry:
|
||||
msg = entry["message"]
|
||||
if isinstance(msg.get("content"), str):
|
||||
content.append(msg["content"])
|
||||
elif isinstance(msg.get("content"), list):
|
||||
for block in msg["content"]:
|
||||
if isinstance(block, dict) and block.get("type") == "text":
|
||||
content.append(block.get("text", ""))
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
return "\n".join(content)
|
||||
|
||||
|
||||
def handle_user_prompt_submit(data: dict) -> None:
|
||||
"""Pre-message hook: Save current token count before request."""
|
||||
session_id = data.get("session_id", "unknown")
|
||||
transcript_path = data.get("transcript_path", "")
|
||||
|
||||
transcript_content = read_transcript(transcript_path)
|
||||
current_tokens = count_tokens(transcript_content)
|
||||
|
||||
# Save to temp file for later comparison
|
||||
state_file = get_state_file(session_id)
|
||||
with open(state_file, "w") as f:
|
||||
json.dump({"pre_tokens": current_tokens}, f)
|
||||
|
||||
|
||||
def handle_stop(data: dict) -> None:
|
||||
"""Post-response hook: Calculate and report token delta."""
|
||||
session_id = data.get("session_id", "unknown")
|
||||
transcript_path = data.get("transcript_path", "")
|
||||
|
||||
transcript_content = read_transcript(transcript_path)
|
||||
current_tokens = count_tokens(transcript_content)
|
||||
|
||||
# Load pre-message count
|
||||
state_file = get_state_file(session_id)
|
||||
pre_tokens = 0
|
||||
if os.path.exists(state_file):
|
||||
try:
|
||||
with open(state_file, "r") as f:
|
||||
state = json.load(f)
|
||||
pre_tokens = state.get("pre_tokens", 0)
|
||||
except (json.JSONDecodeError, IOError):
|
||||
pass
|
||||
|
||||
# Calculate delta
|
||||
delta_tokens = current_tokens - pre_tokens
|
||||
remaining = CONTEXT_LIMIT - current_tokens
|
||||
percentage = (current_tokens / CONTEXT_LIMIT) * 100
|
||||
|
||||
# Report usage (stderr so it doesn't interfere with hook output)
|
||||
method = "tiktoken" if TIKTOKEN_AVAILABLE else "estimated"
|
||||
print(
|
||||
f"Context ({method}): ~{current_tokens:,} tokens "
|
||||
f"({percentage:.1f}% used, ~{remaining:,} remaining)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
if delta_tokens > 0:
|
||||
print(f"This request: ~{delta_tokens:,} tokens", file=sys.stderr)
|
||||
|
||||
|
||||
def main():
|
||||
data = json.load(sys.stdin)
|
||||
event = data.get("hook_event_name", "")
|
||||
|
||||
if event == "UserPromptSubmit":
|
||||
handle_user_prompt_submit(data)
|
||||
elif event == "Stop":
|
||||
handle_stop(data)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Context Usage Tracker - Tracks token consumption per request.
|
||||
|
||||
Uses UserPromptSubmit as "pre-message" hook and Stop as "post-response" hook
|
||||
to calculate the delta in token usage for each request.
|
||||
|
||||
This version uses character-based estimation (no dependencies).
|
||||
For better accuracy, see context-tracker-tiktoken.py.
|
||||
|
||||
Usage:
|
||||
Configure both hooks to use the same script:
|
||||
- UserPromptSubmit: saves current token count
|
||||
- Stop: calculates delta and reports usage
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
# Configuration
|
||||
CONTEXT_LIMIT = 128000 # Claude's context window (adjust for your model)
|
||||
|
||||
|
||||
def get_state_file(session_id: str) -> str:
|
||||
"""Get temp file path for storing pre-message token count, isolated by session."""
|
||||
return os.path.join(tempfile.gettempdir(), f"claude-context-{session_id}.json")
|
||||
|
||||
|
||||
def count_tokens_estimate(text: str) -> int:
|
||||
"""
|
||||
Estimate token count using character-based approximation.
|
||||
|
||||
Uses ~4 characters per token ratio, which provides ~80-90% accuracy
|
||||
for English text. Less accurate for code and non-English text.
|
||||
"""
|
||||
return len(text) // 4
|
||||
|
||||
|
||||
def read_transcript(transcript_path: str) -> str:
|
||||
"""Read and concatenate all content from transcript file."""
|
||||
if not transcript_path or not os.path.exists(transcript_path):
|
||||
return ""
|
||||
|
||||
content = []
|
||||
with open(transcript_path, "r") as f:
|
||||
for line in f:
|
||||
try:
|
||||
entry = json.loads(line.strip())
|
||||
# Extract text content from various message formats
|
||||
if "message" in entry:
|
||||
msg = entry["message"]
|
||||
if isinstance(msg.get("content"), str):
|
||||
content.append(msg["content"])
|
||||
elif isinstance(msg.get("content"), list):
|
||||
for block in msg["content"]:
|
||||
if isinstance(block, dict) and block.get("type") == "text":
|
||||
content.append(block.get("text", ""))
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
return "\n".join(content)
|
||||
|
||||
|
||||
def handle_user_prompt_submit(data: dict) -> None:
|
||||
"""Pre-message hook: Save current token count before request."""
|
||||
session_id = data.get("session_id", "unknown")
|
||||
transcript_path = data.get("transcript_path", "")
|
||||
|
||||
transcript_content = read_transcript(transcript_path)
|
||||
current_tokens = count_tokens_estimate(transcript_content)
|
||||
|
||||
# Save to temp file for later comparison
|
||||
state_file = get_state_file(session_id)
|
||||
with open(state_file, "w") as f:
|
||||
json.dump({"pre_tokens": current_tokens}, f)
|
||||
|
||||
|
||||
def handle_stop(data: dict) -> None:
|
||||
"""Post-response hook: Calculate and report token delta."""
|
||||
session_id = data.get("session_id", "unknown")
|
||||
transcript_path = data.get("transcript_path", "")
|
||||
|
||||
transcript_content = read_transcript(transcript_path)
|
||||
current_tokens = count_tokens_estimate(transcript_content)
|
||||
|
||||
# Load pre-message count
|
||||
state_file = get_state_file(session_id)
|
||||
pre_tokens = 0
|
||||
if os.path.exists(state_file):
|
||||
try:
|
||||
with open(state_file, "r") as f:
|
||||
state = json.load(f)
|
||||
pre_tokens = state.get("pre_tokens", 0)
|
||||
except (json.JSONDecodeError, IOError):
|
||||
pass
|
||||
|
||||
# Calculate delta
|
||||
delta_tokens = current_tokens - pre_tokens
|
||||
remaining = CONTEXT_LIMIT - current_tokens
|
||||
percentage = (current_tokens / CONTEXT_LIMIT) * 100
|
||||
|
||||
# Report usage (stderr so it doesn't interfere with hook output)
|
||||
print(
|
||||
f"Context (estimated): ~{current_tokens:,} tokens "
|
||||
f"({percentage:.1f}% used, ~{remaining:,} remaining)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
if delta_tokens > 0:
|
||||
print(f"This request: ~{delta_tokens:,} tokens", file=sys.stderr)
|
||||
|
||||
|
||||
def main():
|
||||
data = json.load(sys.stdin)
|
||||
event = data.get("hook_event_name", "")
|
||||
|
||||
if event == "UserPromptSubmit":
|
||||
handle_user_prompt_submit(data)
|
||||
elif event == "Stop":
|
||||
handle_stop(data)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,156 @@
|
||||
#!/bin/bash
|
||||
# Check for known vulnerabilities in dependencies after manifest files are modified.
|
||||
# Hook: PostToolUse:Write
|
||||
|
||||
FILE=$1
|
||||
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "Usage: $0 <file_path>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Use basename for matching — $1 may be an absolute path
|
||||
BASENAME=$(basename "$FILE")
|
||||
|
||||
# Only run when a dependency manifest is written
|
||||
case "$BASENAME" in
|
||||
package.json|package-lock.json|yarn.lock|pnpm-lock.yaml| \
|
||||
requirements.txt|Pipfile|Pipfile.lock|pyproject.toml| \
|
||||
go.mod|go.sum| \
|
||||
Cargo.toml|Cargo.lock| \
|
||||
Gemfile|Gemfile.lock| \
|
||||
composer.json|composer.lock| \
|
||||
pom.xml|build.gradle|build.gradle.kts)
|
||||
echo "📦 Dependency manifest updated: $FILE — scanning for vulnerabilities..."
|
||||
;;
|
||||
*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
ISSUES_FOUND=0
|
||||
|
||||
# ── npm / yarn / pnpm ────────────────────────────────────────────────────────
|
||||
if [[ "$BASENAME" == package*.json || "$BASENAME" == yarn.lock || "$BASENAME" == pnpm-lock.yaml ]]; then
|
||||
if command -v npm &>/dev/null; then
|
||||
echo "🔍 Running npm audit..."
|
||||
if ! npm audit --audit-level=high --json 2>/dev/null | \
|
||||
python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
vulns = data.get('metadata', {}).get('vulnerabilities', {})
|
||||
high = vulns.get('high', 0) + vulns.get('critical', 0)
|
||||
if high:
|
||||
print(f' ⚠️ {high} high/critical npm vulnerabilities found. Run: npm audit fix')
|
||||
sys.exit(1)
|
||||
" 2>/dev/null; then
|
||||
ISSUES_FOUND=1
|
||||
else
|
||||
echo " ✅ No high/critical npm vulnerabilities"
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v yarn &>/dev/null && [[ "$BASENAME" == yarn.lock ]]; then
|
||||
echo "🔍 Running yarn audit..."
|
||||
if ! yarn audit --level high --json 2>/dev/null | \
|
||||
grep -q '"type":"auditAdvisory"' 2>/dev/null; then
|
||||
echo " ✅ No high yarn vulnerabilities"
|
||||
else
|
||||
echo " ⚠️ yarn audit found vulnerabilities. Run: yarn audit --level high"
|
||||
ISSUES_FOUND=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Python ───────────────────────────────────────────────────────────────────
|
||||
if [[ "$BASENAME" == requirements.txt || "$BASENAME" == Pipfile* || "$BASENAME" == pyproject.toml ]]; then
|
||||
if command -v pip-audit &>/dev/null; then
|
||||
echo "🔍 Running pip-audit..."
|
||||
if pip-audit --format=json 2>/dev/null | \
|
||||
python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
vulns = [d for d in data.get('dependencies', []) if d.get('vulns')]
|
||||
if vulns:
|
||||
for dep in vulns:
|
||||
for v in dep['vulns']:
|
||||
print(f' ⚠️ {dep[\"name\"]} {dep[\"version\"]}: {v[\"id\"]} — {v[\"fix_versions\"]}')
|
||||
sys.exit(1)
|
||||
" 2>/dev/null; then
|
||||
echo " ✅ No Python vulnerabilities found"
|
||||
else
|
||||
ISSUES_FOUND=1
|
||||
echo " Run: pip-audit for details"
|
||||
fi
|
||||
elif command -v safety &>/dev/null; then
|
||||
echo "🔍 Running safety check..."
|
||||
OUTPUT=$(safety check --short-report 2>&1)
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo " ✅ No Python vulnerabilities found"
|
||||
elif echo "$OUTPUT" | grep -qiE "vulnerability|CVE|insecure"; then
|
||||
echo "$OUTPUT"
|
||||
ISSUES_FOUND=1
|
||||
else
|
||||
echo " ⚠️ safety check could not complete (network or config error)" >&2
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Go ───────────────────────────────────────────────────────────────────────
|
||||
if [[ "$BASENAME" == go.mod || "$BASENAME" == go.sum ]]; then
|
||||
if command -v govulncheck &>/dev/null; then
|
||||
echo "🔍 Running govulncheck..."
|
||||
OUTPUT=$(govulncheck ./... 2>&1)
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo " ✅ No Go vulnerabilities found"
|
||||
elif echo "$OUTPUT" | grep -q "Vulnerability #"; then
|
||||
echo "$OUTPUT"
|
||||
ISSUES_FOUND=1
|
||||
else
|
||||
echo " ⚠️ govulncheck could not complete: $OUTPUT" >&2
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Rust ─────────────────────────────────────────────────────────────────────
|
||||
if [[ "$BASENAME" == Cargo.toml || "$BASENAME" == Cargo.lock ]]; then
|
||||
if command -v cargo-audit &>/dev/null; then
|
||||
echo "🔍 Running cargo audit..."
|
||||
if ! cargo audit 2>/dev/null; then
|
||||
ISSUES_FOUND=1
|
||||
else
|
||||
echo " ✅ No Rust vulnerabilities found"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Ruby ─────────────────────────────────────────────────────────────────────
|
||||
if [[ "$BASENAME" == Gemfile || "$BASENAME" == Gemfile.lock ]]; then
|
||||
if command -v bundler-audit &>/dev/null; then
|
||||
echo "🔍 Running bundler-audit..."
|
||||
bundler-audit check --update 2>/dev/null || ISSUES_FOUND=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Generic fallback: trivy ──────────────────────────────────────────────────
|
||||
if command -v trivy &>/dev/null; then
|
||||
echo "🔍 Running trivy fs scan..."
|
||||
if ! trivy fs --exit-code 1 --severity HIGH,CRITICAL --quiet . 2>/dev/null; then
|
||||
ISSUES_FOUND=1
|
||||
else
|
||||
echo " ✅ trivy found no HIGH/CRITICAL issues"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$ISSUES_FOUND" -eq 0 ]; then
|
||||
echo "✅ Dependency check passed — no vulnerabilities detected"
|
||||
else
|
||||
echo ""
|
||||
echo "⚠️ Vulnerabilities detected. Review and update dependencies before committing."
|
||||
echo " This hook is advisory only and will not block your workflow."
|
||||
fi
|
||||
|
||||
# Always exit 0 — this hook warns but does not block
|
||||
exit 0
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
# Auto-format code after writing
|
||||
# Hook: PostToolUse:Write
|
||||
#
|
||||
# Reads the target file path from stdin JSON and runs the appropriate formatter
|
||||
# in-place on the file after Claude writes it.
|
||||
#
|
||||
# Compatible with: macOS, Linux, Windows (Git Bash)
|
||||
|
||||
# Read JSON input from stdin (Claude Code hook protocol)
|
||||
INPUT=$(cat)
|
||||
|
||||
# Extract file_path using sed (compatible with all platforms)
|
||||
FILE_PATH=$(echo "$INPUT" | sed -n 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
|
||||
|
||||
if [ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Detect file type and format accordingly
|
||||
case "$FILE_PATH" in
|
||||
*.js|*.jsx|*.ts|*.tsx)
|
||||
if command -v prettier &> /dev/null; then
|
||||
prettier --write "$FILE_PATH" 2>/dev/null
|
||||
fi
|
||||
;;
|
||||
*.py)
|
||||
if command -v black &> /dev/null; then
|
||||
black "$FILE_PATH" 2>/dev/null
|
||||
fi
|
||||
;;
|
||||
*.go)
|
||||
if command -v gofmt &> /dev/null; then
|
||||
gofmt -w "$FILE_PATH" 2>/dev/null
|
||||
fi
|
||||
;;
|
||||
*.rs)
|
||||
if command -v rustfmt &> /dev/null; then
|
||||
rustfmt "$FILE_PATH" 2>/dev/null
|
||||
fi
|
||||
;;
|
||||
*.java)
|
||||
if command -v google-java-format &> /dev/null; then
|
||||
google-java-format -i "$FILE_PATH" 2>/dev/null
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Log all bash commands
|
||||
# Hook: PostToolUse:Bash
|
||||
#
|
||||
# Reads the executed command from stdin JSON and logs it to a file.
|
||||
#
|
||||
# Compatible with: macOS, Linux, Windows (Git Bash)
|
||||
|
||||
# Read JSON input from stdin (Claude Code hook protocol)
|
||||
INPUT=$(cat)
|
||||
|
||||
# Extract the bash command from tool_input
|
||||
# Note: sed [^"]* stops at escaped quotes in JSON; for commands with double-quoted
|
||||
# strings, only the portion up to the first \" will be captured — this is a known
|
||||
# limitation of sed-based JSON parsing and is acceptable for logging purposes.
|
||||
COMMAND=$(echo "$INPUT" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
|
||||
|
||||
if [ -z "$COMMAND" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
|
||||
LOGFILE="$HOME/.claude/bash-commands.log"
|
||||
|
||||
# Create log directory if it doesn't exist
|
||||
mkdir -p "$(dirname "$LOGFILE")"
|
||||
|
||||
# Log the command
|
||||
echo "[$TIMESTAMP] $COMMAND" >> "$LOGFILE"
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
# Send notifications on events
|
||||
# Hook: PostToolUse (matcher: Bash) — run after bash commands; filter for git push in script logic
|
||||
# Note: Claude Code has no native PostPush event. To trigger on git push, check the bash command
|
||||
# string for "git push" using a matcher or conditional logic within this script.
|
||||
|
||||
REPO_NAME=$(basename $(git rev-parse --show-toplevel 2>/dev/null) 2>/dev/null)
|
||||
COMMIT_MSG=$(git log -1 --pretty=%B 2>/dev/null)
|
||||
AUTHOR=$(git log -1 --pretty=%an 2>/dev/null)
|
||||
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||
|
||||
echo "📢 Sending notification to team..."
|
||||
|
||||
# Slack webhook example (replace with your webhook URL)
|
||||
SLACK_WEBHOOK="${SLACK_WEBHOOK_URL:-}"
|
||||
|
||||
if [ -n "$SLACK_WEBHOOK" ]; then
|
||||
curl -X POST "$SLACK_WEBHOOK" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{
|
||||
\"text\": \"New push to *$REPO_NAME*\",
|
||||
\"attachments\": [{
|
||||
\"color\": \"good\",
|
||||
\"fields\": [
|
||||
{\"title\": \"Branch\", \"value\": \"$BRANCH\", \"short\": true},
|
||||
{\"title\": \"Author\", \"value\": \"$AUTHOR\", \"short\": true},
|
||||
{\"title\": \"Commit\", \"value\": \"$COMMIT_MSG\"}
|
||||
]
|
||||
}]
|
||||
}" \
|
||||
--silent --output /dev/null
|
||||
|
||||
echo "✅ Slack notification sent"
|
||||
fi
|
||||
|
||||
# Discord webhook example (replace with your webhook URL)
|
||||
DISCORD_WEBHOOK="${DISCORD_WEBHOOK_URL:-}"
|
||||
|
||||
if [ -n "$DISCORD_WEBHOOK" ]; then
|
||||
curl -X POST "$DISCORD_WEBHOOK" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{
|
||||
\"content\": \"**New push to $REPO_NAME**\",
|
||||
\"embeds\": [{
|
||||
\"title\": \"$COMMIT_MSG\",
|
||||
\"color\": 3066993,
|
||||
\"fields\": [
|
||||
{\"name\": \"Branch\", \"value\": \"$BRANCH\", \"inline\": true},
|
||||
{\"name\": \"Author\", \"value\": \"$AUTHOR\", \"inline\": true}
|
||||
]
|
||||
}]
|
||||
}" \
|
||||
--silent --output /dev/null
|
||||
|
||||
echo "✅ Discord notification sent"
|
||||
fi
|
||||
|
||||
# Email notification example
|
||||
EMAIL_TO="${TEAM_EMAIL:-}"
|
||||
|
||||
if [ -n "$EMAIL_TO" ]; then
|
||||
echo "New push to $REPO_NAME by $AUTHOR" | \
|
||||
mail -s "Git Push: $BRANCH" "$EMAIL_TO"
|
||||
|
||||
echo "✅ Email notification sent"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
# Run tests before commit
|
||||
# Hook: PreToolUse (matcher: Bash) - checks if the command is a git commit
|
||||
# Note: There is no "PreCommit" hook event. Use PreToolUse with a Bash matcher
|
||||
# and inspect the command to detect git commit operations.
|
||||
|
||||
echo "🧪 Running tests before commit..."
|
||||
|
||||
# Check if package.json exists (Node.js project)
|
||||
if [ -f "package.json" ]; then
|
||||
if grep -q "\"test\":" package.json; then
|
||||
npm test
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Tests failed! Commit blocked."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if pytest is available (Python project)
|
||||
if [ -f "pytest.ini" ] || [ -f "setup.py" ]; then
|
||||
if command -v pytest &> /dev/null; then
|
||||
pytest
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Tests failed! Commit blocked."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if go.mod exists (Go project)
|
||||
if [ -f "go.mod" ]; then
|
||||
go test ./...
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Tests failed! Commit blocked."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if Cargo.toml exists (Rust project)
|
||||
if [ -f "Cargo.toml" ]; then
|
||||
cargo test
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Tests failed! Commit blocked."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "✅ All tests passed! Proceeding with commit."
|
||||
exit 0
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/bin/bash
|
||||
# Pre-tool safety check for Bash commands
|
||||
# Hook: PreToolUse (matcher: Bash)
|
||||
#
|
||||
# This hook runs before every Bash tool execution and blocks or warns on
|
||||
# potentially destructive or high-risk shell commands.
|
||||
#
|
||||
# Setup:
|
||||
# cp 06-hooks/pre-tool-check.sh ~/.claude/hooks/
|
||||
# chmod +x ~/.claude/hooks/pre-tool-check.sh
|
||||
#
|
||||
# Configure in ~/.claude/settings.json:
|
||||
# {
|
||||
# "hooks": {
|
||||
# "PreToolUse": [
|
||||
# {
|
||||
# "matcher": "Bash",
|
||||
# "hooks": [
|
||||
# {
|
||||
# "type": "command",
|
||||
# "command": "~/.claude/hooks/pre-tool-check.sh"
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# Input: JSON via stdin with the shape:
|
||||
# { "tool_name": "Bash", "tool_input": { "command": "..." } }
|
||||
#
|
||||
# Output: Exit 0 to allow, exit 2 to block, or print JSON to modify behavior.
|
||||
|
||||
# Read the full JSON input from stdin
|
||||
INPUT=$(cat)
|
||||
|
||||
# Extract the command using portable sed (compatible with macOS and Linux)
|
||||
COMMAND=$(echo "$INPUT" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
|
||||
|
||||
# Fall back to the raw input if extraction fails
|
||||
if [ -z "$COMMAND" ]; then
|
||||
COMMAND="$INPUT"
|
||||
fi
|
||||
|
||||
# ── Blocked patterns ──────────────────────────────────────────────────────────
|
||||
# These commands are blocked unconditionally because they are almost always
|
||||
# destructive and rarely intentional in an automated context.
|
||||
|
||||
BLOCKED_PATTERNS=(
|
||||
"rm -rf /"
|
||||
"rm -rf \*"
|
||||
"dd if=/dev/zero"
|
||||
"dd if=/dev/random"
|
||||
":(){:|:&};:" # Fork bomb
|
||||
"mkfs\." # Filesystem format
|
||||
"format c:" # Windows disk format
|
||||
)
|
||||
|
||||
for pattern in "${BLOCKED_PATTERNS[@]}"; do
|
||||
if echo "$COMMAND" | grep -qE "$pattern"; then
|
||||
echo "❌ Blocked: Potentially destructive command detected: $pattern"
|
||||
echo " Command: $COMMAND"
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
# ── Warning patterns ──────────────────────────────────────────────────────────
|
||||
# These patterns are risky but may be intentional. Log a warning and allow.
|
||||
|
||||
WARNING_PATTERNS=(
|
||||
"rm -rf"
|
||||
"git push --force"
|
||||
"git reset --hard"
|
||||
"git clean -f"
|
||||
"chmod -R 777"
|
||||
"sudo rm"
|
||||
"DROP TABLE"
|
||||
"DROP DATABASE"
|
||||
"truncate"
|
||||
)
|
||||
|
||||
WARNINGS=0
|
||||
for pattern in "${WARNING_PATTERNS[@]}"; do
|
||||
if echo "$COMMAND" | grep -qi "$pattern"; then
|
||||
echo "⚠️ Warning: High-risk operation detected: $pattern"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$WARNINGS" -gt 0 ]; then
|
||||
echo " Command: $COMMAND"
|
||||
echo " Proceeding — review the above warnings before continuing."
|
||||
fi
|
||||
|
||||
# ── Allow ─────────────────────────────────────────────────────────────────────
|
||||
exit 0
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
# Security scan on file write
|
||||
# Hook: PostToolUse:Write
|
||||
#
|
||||
# Scans files for hardcoded secrets, API keys, and credentials.
|
||||
# Outputs a non-blocking warning via additionalContext when issues are found.
|
||||
#
|
||||
# Compatible with: macOS, Linux, Windows (Git Bash)
|
||||
|
||||
# Read JSON input from stdin (Claude Code hook protocol)
|
||||
INPUT=$(cat)
|
||||
|
||||
# Extract file_path using sed (compatible with all platforms including Windows Git Bash)
|
||||
# Avoids grep -P (not available on Windows Git Bash) and python3 dependency
|
||||
FILE_PATH=$(echo "$INPUT" | sed -n 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
|
||||
|
||||
if [ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Skip binary files, vendor dirs, and build artifacts
|
||||
case "$FILE_PATH" in
|
||||
*.png|*.jpg|*.jpeg|*.gif|*.svg|*.ico|*.woff|*.woff2|*.ttf|*.eot) exit 0 ;;
|
||||
*/node_modules/*|*/.git/*|*/dist/*|*/build/*) exit 0 ;;
|
||||
esac
|
||||
|
||||
ISSUES=""
|
||||
|
||||
# Check for hardcoded passwords
|
||||
# Handles both JSON format ("password": "value") and code format (password = 'value')
|
||||
# Use \\n as separator — it is a valid JSON newline escape and passes through printf safely
|
||||
if grep -qiE '"password"[[:space:]]*:[[:space:]]*"[^"]+"' "$FILE_PATH" 2>/dev/null; then
|
||||
ISSUES="${ISSUES}- WARNING: Potential hardcoded password detected\\n"
|
||||
elif grep -qiE '(password|passwd|pwd)[[:space:]]*=[[:space:]]*'"'"'[^'"'"']+'"'"'' "$FILE_PATH" 2>/dev/null; then
|
||||
ISSUES="${ISSUES}- WARNING: Potential hardcoded password detected\\n"
|
||||
fi
|
||||
|
||||
# Check for hardcoded API keys
|
||||
if grep -qiE '"(api[_-]?key|apikey|access[_-]?token)"[[:space:]]*:[[:space:]]*"[^"]+"' "$FILE_PATH" 2>/dev/null; then
|
||||
ISSUES="${ISSUES}- WARNING: Potential hardcoded API key detected\\n"
|
||||
fi
|
||||
|
||||
# Check for hardcoded secrets and tokens
|
||||
if grep -qiE '(secret|token)[[:space:]]*=[[:space:]]*['"'"'"][^'"'"'"]+['"'"'"]' "$FILE_PATH" 2>/dev/null; then
|
||||
ISSUES="${ISSUES}- WARNING: Potential hardcoded secret or token detected\\n"
|
||||
fi
|
||||
|
||||
# Check for private keys
|
||||
if grep -q "BEGIN.*PRIVATE KEY" "$FILE_PATH" 2>/dev/null; then
|
||||
ISSUES="${ISSUES}- WARNING: Private key detected\\n"
|
||||
fi
|
||||
|
||||
# Check for AWS keys
|
||||
if grep -qE "AKIA[0-9A-Z]{16}" "$FILE_PATH" 2>/dev/null; then
|
||||
ISSUES="${ISSUES}- WARNING: AWS access key detected\\n"
|
||||
fi
|
||||
|
||||
# Scan with semgrep if available (stdout suppressed to avoid mixing with JSON output)
|
||||
if command -v semgrep &> /dev/null; then
|
||||
semgrep --config=auto "$FILE_PATH" --quiet >/dev/null 2>/dev/null
|
||||
fi
|
||||
|
||||
# Scan with trufflehog if available (stdout suppressed to avoid mixing with JSON output)
|
||||
if command -v trufflehog &> /dev/null; then
|
||||
trufflehog filesystem "$FILE_PATH" --only-verified --quiet >/dev/null 2>/dev/null
|
||||
fi
|
||||
|
||||
# If issues found, output as additionalContext (non-blocking warning)
|
||||
# Use hookSpecificOutput format required by Claude Code PostToolUse protocol
|
||||
if [ -n "$ISSUES" ]; then
|
||||
# Escape file path for JSON (backslash and double-quote)
|
||||
# ISSUES already uses \\n as separator (valid JSON escape) — only escape double-quotes
|
||||
SAFE_PATH=$(printf '%s' "$FILE_PATH" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||
SAFE_ISSUES=$(printf '%s' "$ISSUES" | sed 's/"/\\"/g')
|
||||
printf '{"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "Security scan found issues in %s:\\n%sPlease review and use environment variables instead."}}' "$SAFE_PATH" "$SAFE_ISSUES"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
# Validate user prompts
|
||||
# Hook: UserPromptSubmit
|
||||
#
|
||||
# Reads the user prompt from stdin JSON and blocks dangerous operations.
|
||||
#
|
||||
# Compatible with: macOS, Linux, Windows (Git Bash)
|
||||
|
||||
# Read JSON input from stdin (Claude Code hook protocol)
|
||||
INPUT=$(cat)
|
||||
|
||||
# Extract the prompt text from JSON input
|
||||
# Claude Code sends UserPromptSubmit with field "user_prompt" (falls back to "prompt")
|
||||
PROMPT=$(echo "$INPUT" | sed -n 's/.*"user_prompt"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
|
||||
if [ -z "$PROMPT" ]; then
|
||||
PROMPT=$(echo "$INPUT" | sed -n 's/.*"prompt"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
|
||||
fi
|
||||
|
||||
if [ -z "$PROMPT" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for dangerous operations
|
||||
DANGEROUS_PATTERNS=(
|
||||
"rm -rf /"
|
||||
"delete database"
|
||||
"drop database"
|
||||
"format disk"
|
||||
"dd if="
|
||||
)
|
||||
|
||||
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
|
||||
if echo "$PROMPT" | grep -qi "$pattern"; then
|
||||
printf '{"decision": "block", "reason": "Dangerous operation detected: %s"}' "$pattern"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for production deployments
|
||||
if echo "$PROMPT" | grep -qiE "(deploy|push).*production"; then
|
||||
if [ ! -f ".deployment-approved" ]; then
|
||||
echo '{"decision": "block", "reason": "Production deployment requires approval. Create .deployment-approved file to proceed."}'
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for required context in certain operations
|
||||
if echo "$PROMPT" | grep -qi "refactor"; then
|
||||
if [ ! -d "tests" ] && [ ! -d "test" ]; then
|
||||
printf '{"additionalContext": "Warning: Refactoring without tests may be risky. Consider writing tests first."}'
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "devops-automation",
|
||||
"version": "1.0.0",
|
||||
"description": "Complete DevOps automation for deployment, monitoring, and incident response",
|
||||
"author": {
|
||||
"name": "Community"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="../../resources/logos/claude-howto-logo-dark.svg">
|
||||
<img alt="Claude How To" src="../../resources/logos/claude-howto-logo.svg">
|
||||
</picture>
|
||||
|
||||
# DevOps Automation Plugin
|
||||
|
||||
Complete DevOps automation for deployment, monitoring, and incident response.
|
||||
|
||||
## Features
|
||||
|
||||
✅ Automated deployments
|
||||
✅ Rollback procedures
|
||||
✅ System health monitoring
|
||||
✅ Incident response workflows
|
||||
✅ Kubernetes integration
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
/plugin install devops-automation
|
||||
```
|
||||
|
||||
## What's Included
|
||||
|
||||
### Slash Commands
|
||||
- `/deploy` - Deploy to production or staging
|
||||
- `/rollback` - Rollback to previous version
|
||||
- `/status` - Check system health
|
||||
- `/incident` - Handle production incidents
|
||||
|
||||
### Subagents
|
||||
- `deployment-specialist` - Deployment operations
|
||||
- `incident-commander` - Incident coordination
|
||||
- `alert-analyzer` - System health analysis
|
||||
|
||||
### MCP Servers
|
||||
- Kubernetes integration
|
||||
|
||||
### Scripts
|
||||
- `deploy.sh` - Deployment automation
|
||||
- `rollback.sh` - Rollback automation
|
||||
- `health-check.sh` - Health check utilities
|
||||
|
||||
### Hooks
|
||||
- `pre-deploy.js` - Pre-deployment validation
|
||||
- `post-deploy.js` - Post-deployment tasks
|
||||
|
||||
## Usage
|
||||
|
||||
### Deploy to Staging
|
||||
```
|
||||
/deploy staging
|
||||
```
|
||||
|
||||
### Deploy to Production
|
||||
```
|
||||
/deploy production
|
||||
```
|
||||
|
||||
### Rollback
|
||||
```
|
||||
/rollback production
|
||||
```
|
||||
|
||||
### Check Status
|
||||
```
|
||||
/status
|
||||
```
|
||||
|
||||
### Handle Incident
|
||||
```
|
||||
/incident
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Claude Code 1.0+
|
||||
- Kubernetes CLI (kubectl)
|
||||
- Cluster access configured
|
||||
|
||||
## Configuration
|
||||
|
||||
Set up your Kubernetes config:
|
||||
```bash
|
||||
export KUBECONFIG=~/.kube/config
|
||||
```
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```
|
||||
User: /deploy production
|
||||
|
||||
Claude:
|
||||
1. Runs pre-deploy hook (validates kubectl, cluster connection)
|
||||
2. Delegates to deployment-specialist subagent
|
||||
3. Runs deploy.sh script
|
||||
4. Monitors deployment progress via Kubernetes MCP
|
||||
5. Runs post-deploy hook (waits for pods, smoke tests)
|
||||
6. Provides deployment summary
|
||||
|
||||
Result:
|
||||
✅ Deployment complete
|
||||
📦 Version: v2.1.0
|
||||
🚀 Pods: 3/3 ready
|
||||
⏱️ Time: 2m 34s
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: alert-analyzer
|
||||
description: Analyzes monitoring alerts and system metrics
|
||||
tools: read, grep, bash
|
||||
---
|
||||
|
||||
# Alert Analyzer
|
||||
|
||||
Analyzes system health and alerts:
|
||||
- Alert correlation
|
||||
- Trend analysis
|
||||
- Root cause identification
|
||||
- Metric visualization
|
||||
- Proactive issue detection
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: deployment-specialist
|
||||
description: Handles all deployment operations
|
||||
tools: read, write, bash, grep
|
||||
---
|
||||
|
||||
# Deployment Specialist
|
||||
|
||||
Expert in deployment operations:
|
||||
- Blue-green deployments
|
||||
- Canary releases
|
||||
- Rollback procedures
|
||||
- Health checks
|
||||
- Database migrations
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: incident-commander
|
||||
description: Coordinates incident response
|
||||
tools: read, write, bash, grep
|
||||
---
|
||||
|
||||
# Incident Commander
|
||||
|
||||
Manages incident response:
|
||||
- Severity assessment
|
||||
- Team coordination
|
||||
- Status updates
|
||||
- Resolution tracking
|
||||
- Post-mortem facilitation
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: Deploy
|
||||
description: Deploy application to production or staging
|
||||
---
|
||||
|
||||
# Deploy Application
|
||||
|
||||
Execute deployment workflow:
|
||||
|
||||
1. Run pre-deployment checks
|
||||
2. Build application
|
||||
3. Run tests
|
||||
4. Deploy to target environment
|
||||
5. Run health checks
|
||||
6. Notify team on Slack
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Incident Response
|
||||
description: Handle production incidents with structured response
|
||||
---
|
||||
|
||||
# Incident Response
|
||||
|
||||
Structured incident response workflow:
|
||||
|
||||
1. Create incident record
|
||||
2. Assess severity and impact
|
||||
3. Notify on-call team
|
||||
4. Gather diagnostic information
|
||||
5. Coordinate response efforts
|
||||
6. Document resolution
|
||||
7. Schedule post-mortem
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Rollback
|
||||
description: Rollback to previous deployment
|
||||
---
|
||||
|
||||
# Rollback Deployment
|
||||
|
||||
Rollback to previous stable version:
|
||||
|
||||
1. Identify previous deployment
|
||||
2. Verify rollback target is healthy
|
||||
3. Execute rollback procedure
|
||||
4. Run health checks
|
||||
5. Notify team
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: System Status
|
||||
description: Check overall system health and status
|
||||
---
|
||||
|
||||
# System Status Check
|
||||
|
||||
Check system health across all services:
|
||||
|
||||
1. Query Kubernetes pod status
|
||||
2. Check database connections
|
||||
3. Monitor API response times
|
||||
4. Review error rates
|
||||
5. Check resource utilization
|
||||
6. Report overall health
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Post-deployment hook
|
||||
* Runs after deployment completes
|
||||
*/
|
||||
|
||||
async function postDeploy() {
|
||||
console.log('Running post-deployment tasks...');
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// Wait for pods to be ready
|
||||
console.log('Waiting for pods to be ready...');
|
||||
try {
|
||||
execSync('kubectl wait --for=condition=ready pod -l app=myapp --timeout=300s', {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Pods failed to become ready');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Run smoke tests
|
||||
console.log('Running smoke tests...');
|
||||
// Add your smoke test commands here
|
||||
|
||||
console.log('✅ Post-deployment tasks complete');
|
||||
}
|
||||
|
||||
postDeploy().catch(error => {
|
||||
console.error('Post-deploy hook failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Pre-deployment hook
|
||||
* Validates environment and prerequisites before deployment
|
||||
*/
|
||||
|
||||
async function preDeploy() {
|
||||
console.log('Running pre-deployment checks...');
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// Check if kubectl is installed
|
||||
try {
|
||||
execSync('which kubectl', { stdio: 'pipe' });
|
||||
} catch (error) {
|
||||
console.error('❌ kubectl not found. Please install Kubernetes CLI.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if connected to cluster
|
||||
try {
|
||||
execSync('kubectl cluster-info', { stdio: 'pipe' });
|
||||
} catch (error) {
|
||||
console.error('❌ Not connected to Kubernetes cluster');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('✅ Pre-deployment checks passed');
|
||||
}
|
||||
|
||||
preDeploy().catch(error => {
|
||||
console.error('Pre-deploy hook failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"kubernetes": {
|
||||
"command": "npx",
|
||||
"args": ["@modelcontextprotocol/server-kubernetes"],
|
||||
"env": {
|
||||
"KUBECONFIG": "${KUBECONFIG}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "🚀 Starting deployment..."
|
||||
|
||||
# Load environment
|
||||
ENV=${1:-staging}
|
||||
echo "📦 Target environment: $ENV"
|
||||
|
||||
# Pre-deployment checks
|
||||
echo "✓ Running pre-deployment checks..."
|
||||
npm run lint
|
||||
npm test
|
||||
|
||||
# Build
|
||||
echo "🔨 Building application..."
|
||||
npm run build
|
||||
|
||||
# Deploy
|
||||
echo "🚢 Deploying to $ENV..."
|
||||
kubectl apply -f k8s/$ENV/
|
||||
|
||||
# Health check
|
||||
echo "🏥 Running health checks..."
|
||||
sleep 10
|
||||
curl -f http://api.$ENV.example.com/health
|
||||
|
||||
echo "✅ Deployment complete!"
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "🏥 System Health Check"
|
||||
echo "===================="
|
||||
|
||||
ENV=${1:-production}
|
||||
|
||||
# Check API
|
||||
echo -n "API: "
|
||||
if curl -sf http://api.$ENV.example.com/health > /dev/null; then
|
||||
echo "✅ Healthy"
|
||||
else
|
||||
echo "❌ Unhealthy"
|
||||
fi
|
||||
|
||||
# Check Database
|
||||
echo -n "Database: "
|
||||
if pg_isready -h db.$ENV.example.com > /dev/null 2>&1; then
|
||||
echo "✅ Healthy"
|
||||
else
|
||||
echo "❌ Unhealthy"
|
||||
fi
|
||||
|
||||
# Check Pods
|
||||
echo -n "Kubernetes Pods: "
|
||||
PODS_READY=$(kubectl get pods -n $ENV --no-headers | grep "Running" | wc -l)
|
||||
PODS_TOTAL=$(kubectl get pods -n $ENV --no-headers | wc -l)
|
||||
echo "$PODS_READY/$PODS_TOTAL ready"
|
||||
|
||||
echo "===================="
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "⏪ Starting rollback..."
|
||||
|
||||
ENV=${1:-staging}
|
||||
echo "📦 Target environment: $ENV"
|
||||
|
||||
# Get previous deployment
|
||||
PREVIOUS=$(kubectl rollout history deployment/app -n $ENV | tail -2 | head -1 | awk '{print $1}')
|
||||
echo "🔄 Rolling back to revision: $PREVIOUS"
|
||||
|
||||
# Execute rollback
|
||||
kubectl rollout undo deployment/app -n $ENV
|
||||
|
||||
# Wait for rollback
|
||||
echo "⏳ Waiting for rollback to complete..."
|
||||
kubectl rollout status deployment/app -n $ENV
|
||||
|
||||
# Health check
|
||||
echo "🏥 Running health checks..."
|
||||
sleep 5
|
||||
curl -f http://api.$ENV.example.com/health
|
||||
|
||||
echo "✅ Rollback complete!"
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "documentation",
|
||||
"version": "1.0.0",
|
||||
"description": "Comprehensive documentation generation and maintenance",
|
||||
"author": {
|
||||
"name": "Community"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="../../resources/logos/claude-howto-logo-dark.svg">
|
||||
<img alt="Claude How To" src="../../resources/logos/claude-howto-logo.svg">
|
||||
</picture>
|
||||
|
||||
# Documentation Plugin
|
||||
|
||||
Comprehensive documentation generation and maintenance for your project.
|
||||
|
||||
## Features
|
||||
|
||||
✅ API documentation generation
|
||||
✅ README creation and updates
|
||||
✅ Documentation synchronization
|
||||
✅ Code comment improvements
|
||||
✅ Example generation
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
/plugin install documentation
|
||||
```
|
||||
|
||||
## What's Included
|
||||
|
||||
### Slash Commands
|
||||
- `/generate-api-docs` - Generate API documentation
|
||||
- `/generate-readme` - Create or update README
|
||||
- `/sync-docs` - Sync docs with code changes
|
||||
- `/validate-docs` - Validate documentation
|
||||
|
||||
### Subagents
|
||||
- `api-documenter` - API documentation specialist
|
||||
- `code-commentator` - Code comment improvements
|
||||
- `example-generator` - Code example creation
|
||||
|
||||
### Templates
|
||||
- `api-endpoint.md` - API endpoint documentation template
|
||||
- `function-docs.md` - Function documentation template
|
||||
- `adr-template.md` - Architecture Decision Record template
|
||||
|
||||
### MCP Servers
|
||||
- GitHub integration for documentation syncing
|
||||
|
||||
## Usage
|
||||
|
||||
### Generate API Documentation
|
||||
```
|
||||
/generate-api-docs
|
||||
```
|
||||
|
||||
### Create README
|
||||
```
|
||||
/generate-readme
|
||||
```
|
||||
|
||||
### Sync Documentation
|
||||
```
|
||||
/sync-docs
|
||||
```
|
||||
|
||||
### Validate Documentation
|
||||
```
|
||||
/validate-docs
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Claude Code 1.0+
|
||||
- GitHub access (optional)
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```
|
||||
User: /generate-api-docs
|
||||
|
||||
Claude:
|
||||
1. Scans all API endpoints in /src/api/
|
||||
2. Delegates to api-documenter subagent
|
||||
3. Extracts function signatures and JSDoc
|
||||
4. Organizes by module/endpoint
|
||||
5. Uses api-endpoint.md template
|
||||
6. Generates comprehensive markdown docs
|
||||
7. Includes curl, JavaScript, and Python examples
|
||||
|
||||
Result:
|
||||
✅ API documentation generated
|
||||
📄 Files created:
|
||||
- docs/api/users.md
|
||||
- docs/api/auth.md
|
||||
- docs/api/products.md
|
||||
📊 Coverage: 23/23 endpoints documented
|
||||
```
|
||||
|
||||
## Templates Usage
|
||||
|
||||
### API Endpoint Template
|
||||
Use for documenting REST API endpoints with full examples.
|
||||
|
||||
### Function Documentation Template
|
||||
Use for documenting individual functions/methods.
|
||||
|
||||
### ADR Template
|
||||
Use for documenting architectural decisions.
|
||||
|
||||
## Configuration
|
||||
|
||||
Set up GitHub token for documentation syncing:
|
||||
```bash
|
||||
export GITHUB_TOKEN="your_github_token"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Keep documentation close to code
|
||||
- Update docs with code changes
|
||||
- Include practical examples
|
||||
- Validate regularly
|
||||
- Use templates for consistency
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: api-documenter
|
||||
description: API documentation specialist
|
||||
tools: read, write, grep
|
||||
---
|
||||
|
||||
# API Documenter
|
||||
|
||||
Creates comprehensive API documentation:
|
||||
- Endpoint documentation
|
||||
- Parameter descriptions
|
||||
- Response schemas
|
||||
- Code examples (curl, JS, Python)
|
||||
- Error codes
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: code-commentator
|
||||
description: Code comment and inline documentation specialist
|
||||
tools: read, write, edit
|
||||
---
|
||||
|
||||
# Code Commentator
|
||||
|
||||
Improves code documentation:
|
||||
- JSDoc/docstring comments
|
||||
- Inline explanations
|
||||
- Parameter descriptions
|
||||
- Return type documentation
|
||||
- Usage examples
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: example-generator
|
||||
description: Code example and tutorial specialist
|
||||
tools: read, write
|
||||
---
|
||||
|
||||
# Example Generator
|
||||
|
||||
Creates practical code examples:
|
||||
- Getting started guides
|
||||
- Common use cases
|
||||
- Integration examples
|
||||
- Best practices
|
||||
- Troubleshooting scenarios
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: Generate API Documentation
|
||||
description: Generate comprehensive API documentation from source code
|
||||
---
|
||||
|
||||
# API Documentation Generator
|
||||
|
||||
Generate complete API documentation:
|
||||
|
||||
1. Scan API endpoints
|
||||
2. Extract function signatures and JSDoc
|
||||
3. Organize by module/endpoint
|
||||
4. Create markdown with examples
|
||||
5. Include request/response schemas
|
||||
6. Add error documentation
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: Generate README
|
||||
description: Create or update project README
|
||||
---
|
||||
|
||||
# README Generator
|
||||
|
||||
Generate comprehensive README:
|
||||
|
||||
1. Project overview and description
|
||||
2. Installation instructions
|
||||
3. Usage examples
|
||||
4. API documentation links
|
||||
5. Contributing guidelines
|
||||
6. License information
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Sync Documentation
|
||||
description: Sync documentation with code changes
|
||||
---
|
||||
|
||||
# Documentation Sync
|
||||
|
||||
Synchronize documentation with codebase:
|
||||
|
||||
1. Detect code changes
|
||||
2. Identify outdated documentation
|
||||
3. Update affected docs
|
||||
4. Verify examples still work
|
||||
5. Update version numbers
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Validate Documentation
|
||||
description: Validate documentation for completeness and accuracy
|
||||
---
|
||||
|
||||
# Documentation Validation
|
||||
|
||||
Validate documentation quality:
|
||||
|
||||
1. Check for broken links
|
||||
2. Verify code examples
|
||||
3. Ensure completeness
|
||||
4. Check formatting
|
||||
5. Validate against actual code
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"command": "npx",
|
||||
"args": ["@modelcontextprotocol/server-github"],
|
||||
"env": {
|
||||
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# ADR [Number]: [Title]
|
||||
|
||||
## Status
|
||||
[Proposed | Accepted | Deprecated | Superseded]
|
||||
|
||||
## Context
|
||||
What is the issue that we're seeing that is motivating this decision or change?
|
||||
|
||||
## Decision
|
||||
What is the change that we're proposing and/or doing?
|
||||
|
||||
## Consequences
|
||||
What becomes easier or more difficult to do because of this change?
|
||||
|
||||
### Positive
|
||||
- Benefit 1
|
||||
- Benefit 2
|
||||
|
||||
### Negative
|
||||
- Drawback 1
|
||||
- Drawback 2
|
||||
|
||||
### Neutral
|
||||
- Consideration 1
|
||||
- Consideration 2
|
||||
|
||||
## Alternatives Considered
|
||||
What other options were considered and why were they not chosen?
|
||||
|
||||
### Alternative 1
|
||||
Description and reason for not choosing.
|
||||
|
||||
### Alternative 2
|
||||
Description and reason for not choosing.
|
||||
|
||||
## References
|
||||
- Related ADRs
|
||||
- External documentation
|
||||
- Discussion links
|
||||
@@ -0,0 +1,101 @@
|
||||
# [METHOD] /api/v1/[endpoint]
|
||||
|
||||
## Description
|
||||
Brief explanation of what this endpoint does.
|
||||
|
||||
## Authentication
|
||||
Required authentication method (e.g., Bearer token).
|
||||
|
||||
## Parameters
|
||||
|
||||
### Path Parameters
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| id | string | Yes | Resource ID |
|
||||
|
||||
### Query Parameters
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| page | integer | No | Page number (default: 1) |
|
||||
| limit | integer | No | Items per page (default: 20) |
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"field": "value"
|
||||
}
|
||||
```
|
||||
|
||||
## Responses
|
||||
|
||||
### 200 OK
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "123",
|
||||
"name": "Example"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 400 Bad Request
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Invalid input"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 404 Not Found
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "NOT_FOUND",
|
||||
"message": "Resource not found"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### cURL
|
||||
```bash
|
||||
curl -X GET "https://api.example.com/api/v1/endpoint" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
```javascript
|
||||
const response = await fetch('/api/v1/endpoint', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer token',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
```
|
||||
|
||||
### Python
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.get(
|
||||
'https://api.example.com/api/v1/endpoint',
|
||||
headers={'Authorization': 'Bearer token'}
|
||||
)
|
||||
data = response.json()
|
||||
```
|
||||
|
||||
## Rate Limits
|
||||
- 1000 requests per hour for authenticated users
|
||||
- 100 requests per hour for public endpoints
|
||||
|
||||
## Related Endpoints
|
||||
- [GET /api/v1/related](#)
|
||||
- [POST /api/v1/related](#)
|
||||
@@ -0,0 +1,50 @@
|
||||
# Function: `functionName`
|
||||
|
||||
## Description
|
||||
Brief description of what the function does.
|
||||
|
||||
## Signature
|
||||
```typescript
|
||||
function functionName(param1: Type1, param2: Type2): ReturnType
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| param1 | Type1 | Yes | Description of param1 |
|
||||
| param2 | Type2 | No | Description of param2 |
|
||||
|
||||
## Returns
|
||||
**Type**: `ReturnType`
|
||||
|
||||
Description of what is returned.
|
||||
|
||||
## Throws
|
||||
- `Error`: When invalid input is provided
|
||||
- `TypeError`: When wrong type is passed
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
```typescript
|
||||
const result = functionName('value1', 'value2');
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
```typescript
|
||||
const result = functionName(
|
||||
complexParam1,
|
||||
{ option: true }
|
||||
);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Additional notes or warnings
|
||||
- Performance considerations
|
||||
- Best practices
|
||||
|
||||
## See Also
|
||||
- [Related Function](#)
|
||||
- [API Documentation](#)
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "pr-review",
|
||||
"version": "1.0.0",
|
||||
"description": "Complete PR review workflow with security, testing, and docs",
|
||||
"author": {
|
||||
"name": "Anthropic"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="../../resources/logos/claude-howto-logo-dark.svg">
|
||||
<img alt="Claude How To" src="../../resources/logos/claude-howto-logo.svg">
|
||||
</picture>
|
||||
|
||||
# PR Review Plugin
|
||||
|
||||
Complete PR review workflow with security, testing, and documentation checks.
|
||||
|
||||
## Features
|
||||
|
||||
✅ Security analysis
|
||||
✅ Test coverage checking
|
||||
✅ Documentation verification
|
||||
✅ Code quality assessment
|
||||
✅ Performance impact analysis
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
/plugin install pr-review
|
||||
```
|
||||
|
||||
## What's Included
|
||||
|
||||
### Slash Commands
|
||||
- `/review-pr` - Comprehensive PR review
|
||||
- `/check-security` - Security-focused review
|
||||
- `/check-tests` - Test coverage analysis
|
||||
|
||||
### Subagents
|
||||
- `security-reviewer` - Security vulnerability detection
|
||||
- `test-checker` - Test coverage analysis
|
||||
- `performance-analyzer` - Performance impact evaluation
|
||||
|
||||
### MCP Servers
|
||||
- GitHub integration for PR data
|
||||
|
||||
### Hooks
|
||||
- `pre-review.js` - Pre-review validation
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic PR Review
|
||||
```
|
||||
/review-pr
|
||||
```
|
||||
|
||||
### Security Check Only
|
||||
```
|
||||
/check-security
|
||||
```
|
||||
|
||||
### Test Coverage Check
|
||||
```
|
||||
/check-tests
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Claude Code 1.0+
|
||||
- GitHub access
|
||||
- Git repository
|
||||
|
||||
## Configuration
|
||||
|
||||
Set up your GitHub token:
|
||||
```bash
|
||||
export GITHUB_TOKEN="your_github_token"
|
||||
```
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```
|
||||
User: /review-pr
|
||||
|
||||
Claude:
|
||||
1. Runs pre-review hook (validates git repo)
|
||||
2. Fetches PR data via GitHub MCP
|
||||
3. Delegates security review to security-reviewer subagent
|
||||
4. Delegates testing to test-checker subagent
|
||||
5. Delegates performance to performance-analyzer subagent
|
||||
6. Synthesizes all findings
|
||||
7. Provides comprehensive review report
|
||||
|
||||
Result:
|
||||
✅ Security: No critical issues found
|
||||
⚠️ Testing: Coverage is 65%, recommend 80%+
|
||||
✅ Performance: No significant impact
|
||||
📝 Recommendations: Add tests for edge cases
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: performance-analyzer
|
||||
description: Performance impact analysis
|
||||
tools: read, grep, bash
|
||||
---
|
||||
|
||||
# Performance Analyzer
|
||||
|
||||
Evaluates performance impact of changes:
|
||||
- Algorithm complexity
|
||||
- Database query efficiency
|
||||
- Memory usage
|
||||
- Caching opportunities
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: security-reviewer
|
||||
description: Security-focused code review
|
||||
tools: read, grep, diff
|
||||
---
|
||||
|
||||
# Security Reviewer
|
||||
|
||||
Specializes in finding security vulnerabilities:
|
||||
- Authentication/authorization issues
|
||||
- Data exposure
|
||||
- Injection attacks
|
||||
- Secure configuration
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: test-checker
|
||||
description: Test coverage and quality analysis
|
||||
tools: read, bash, grep
|
||||
---
|
||||
|
||||
# Test Checker
|
||||
|
||||
Analyzes test coverage and quality:
|
||||
- Coverage percentage
|
||||
- Missing test cases
|
||||
- Test quality assessment
|
||||
- Edge case identification
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Security Check
|
||||
description: Run security-focused code review
|
||||
---
|
||||
|
||||
# Security Check
|
||||
|
||||
Perform focused security analysis on code changes:
|
||||
|
||||
1. Authentication/authorization checks
|
||||
2. Data exposure risks
|
||||
3. Injection vulnerabilities
|
||||
4. Cryptographic weaknesses
|
||||
5. Sensitive data in logs
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Test Coverage Check
|
||||
description: Verify test coverage and quality
|
||||
---
|
||||
|
||||
# Test Coverage Check
|
||||
|
||||
Analyze test coverage and quality:
|
||||
|
||||
1. Check test coverage percentage
|
||||
2. Identify untested code paths
|
||||
3. Review test quality
|
||||
4. Suggest missing test cases
|
||||
5. Verify edge cases are covered
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Review PR
|
||||
description: Start comprehensive PR review with security and testing checks
|
||||
---
|
||||
|
||||
# PR Review
|
||||
|
||||
This command initiates a complete pull request review including:
|
||||
|
||||
1. Security analysis
|
||||
2. Test coverage verification
|
||||
3. Documentation updates
|
||||
4. Code quality checks
|
||||
5. Performance impact assessment
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Pre-review hook
|
||||
* Runs before starting PR review to ensure prerequisites are met
|
||||
*/
|
||||
|
||||
async function preReview() {
|
||||
console.log('Running pre-review checks...');
|
||||
|
||||
// Check if git repository
|
||||
const { execSync } = require('child_process');
|
||||
try {
|
||||
execSync('git rev-parse --git-dir', { stdio: 'pipe' });
|
||||
} catch (error) {
|
||||
console.error('❌ Not a git repository');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check for uncommitted changes
|
||||
try {
|
||||
const status = execSync('git status --porcelain', { encoding: 'utf-8' });
|
||||
if (status.trim()) {
|
||||
console.warn('⚠️ Warning: Uncommitted changes detected');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to check git status');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('✅ Pre-review checks passed');
|
||||
}
|
||||
|
||||
preReview().catch(error => {
|
||||
console.error('Pre-review hook failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"command": "npx",
|
||||
"args": ["@modelcontextprotocol/server-github"],
|
||||
"env": {
|
||||
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
{
|
||||
"description": "Example Claude Code configurations for different use cases",
|
||||
|
||||
"examples": {
|
||||
"development": {
|
||||
"name": "Development Environment",
|
||||
"description": "Configuration for active development work",
|
||||
"config": {
|
||||
"general": {
|
||||
"model": "claude-sonnet-4-6",
|
||||
"temperature": 0.7
|
||||
},
|
||||
"planning": {
|
||||
"autoEnter": true,
|
||||
"complexityThreshold": 3,
|
||||
"requireApproval": true
|
||||
},
|
||||
"permissions": {
|
||||
"mode": "unrestricted"
|
||||
},
|
||||
"backgroundTasks": {
|
||||
"enabled": true,
|
||||
"maxConcurrentTasks": 3
|
||||
},
|
||||
"hooks": {
|
||||
"PreToolUse:Write": "prettier --write ${file_path}",
|
||||
"PostToolUse:Write": "eslint ${file_path}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"code_review": {
|
||||
"name": "Code Review Mode",
|
||||
"description": "Configuration for reviewing code without modifications",
|
||||
"config": {
|
||||
"general": {
|
||||
"model": "claude-sonnet-4-6",
|
||||
"temperature": 0.3
|
||||
},
|
||||
"permissions": {
|
||||
"mode": "plan"
|
||||
},
|
||||
"extendedThinking": {
|
||||
"enabled": true,
|
||||
"showThinkingProcess": true
|
||||
},
|
||||
"planning": {
|
||||
"autoEnter": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"learning": {
|
||||
"name": "Learning Mode",
|
||||
"description": "Configuration for learning and experimentation",
|
||||
"config": {
|
||||
"general": {
|
||||
"model": "claude-sonnet-4-6",
|
||||
"temperature": 0.5
|
||||
},
|
||||
"permissions": {
|
||||
"mode": "confirm"
|
||||
},
|
||||
"extendedThinking": {
|
||||
"enabled": true,
|
||||
"showThinkingProcess": true
|
||||
},
|
||||
"planning": {
|
||||
"autoEnter": true,
|
||||
"requireApproval": true,
|
||||
"showTimeEstimates": true
|
||||
},
|
||||
"checkpoints": {
|
||||
"autoCheckpoint": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"production": {
|
||||
"name": "Production Deployment",
|
||||
"description": "Configuration for production operations with safety checks",
|
||||
"config": {
|
||||
"general": {
|
||||
"model": "claude-opus-4",
|
||||
"temperature": 0.1
|
||||
},
|
||||
"permissions": {
|
||||
"mode": "confirm",
|
||||
"requireConfirmationFor": ["Bash", "Git", "Write", "Edit"]
|
||||
},
|
||||
"hooks": {
|
||||
"PreToolUse": "~/.claude/hooks/pre-commit.sh"
|
||||
},
|
||||
"checkpoints": {
|
||||
"autoCheckpoint": true
|
||||
},
|
||||
"planning": {
|
||||
"autoEnter": true,
|
||||
"requireApproval": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"ci_cd": {
|
||||
"name": "CI/CD Pipeline",
|
||||
"description": "Configuration for automated CI/CD operations",
|
||||
"config": {
|
||||
"general": {
|
||||
"model": "claude-sonnet-4-6",
|
||||
"temperature": 0
|
||||
},
|
||||
"permissions": {
|
||||
"mode": "unrestricted"
|
||||
},
|
||||
"headless": {
|
||||
"exitOnError": true,
|
||||
"verbose": true,
|
||||
"timeout": 3600
|
||||
},
|
||||
"logging": {
|
||||
"level": "debug",
|
||||
"file": "./ci-claude.log"
|
||||
},
|
||||
"planning": {
|
||||
"autoEnter": false,
|
||||
"requireApproval": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"security_audit": {
|
||||
"name": "Security Audit",
|
||||
"description": "Configuration for security-focused code analysis",
|
||||
"config": {
|
||||
"general": {
|
||||
"model": "claude-opus-4",
|
||||
"temperature": 0.2
|
||||
},
|
||||
"permissions": {
|
||||
"mode": "plan"
|
||||
},
|
||||
"extendedThinking": {
|
||||
"enabled": true,
|
||||
"showThinkingProcess": true,
|
||||
"minThinkingTime": 10
|
||||
},
|
||||
"hooks": {
|
||||
"PostToolUse:Read": "~/.claude/hooks/security-scan.sh ${file_path}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"performance_optimization": {
|
||||
"name": "Performance Optimization",
|
||||
"description": "Configuration for performance analysis and optimization",
|
||||
"config": {
|
||||
"general": {
|
||||
"model": "claude-sonnet-4-6",
|
||||
"temperature": 0.4
|
||||
},
|
||||
"planning": {
|
||||
"autoEnter": true,
|
||||
"requireApproval": true
|
||||
},
|
||||
"backgroundTasks": {
|
||||
"enabled": true,
|
||||
"maxConcurrentTasks": 5
|
||||
},
|
||||
"checkpoints": {
|
||||
"autoCheckpoint": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"pair_programming": {
|
||||
"name": "Pair Programming",
|
||||
"description": "Configuration for collaborative development",
|
||||
"config": {
|
||||
"general": {
|
||||
"model": "claude-sonnet-4-6",
|
||||
"temperature": 0.6
|
||||
},
|
||||
"permissions": {
|
||||
"mode": "confirm"
|
||||
},
|
||||
"planning": {
|
||||
"autoEnter": true,
|
||||
"requireApproval": true,
|
||||
"showTimeEstimates": true
|
||||
},
|
||||
"extendedThinking": {
|
||||
"enabled": true,
|
||||
"showThinkingProcess": true
|
||||
},
|
||||
"ui": {
|
||||
"compactMode": false,
|
||||
"showProgress": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"refactoring": {
|
||||
"name": "Large Refactoring",
|
||||
"description": "Configuration for major refactoring work",
|
||||
"config": {
|
||||
"general": {
|
||||
"model": "claude-opus-4",
|
||||
"temperature": 0.3
|
||||
},
|
||||
"planning": {
|
||||
"autoEnter": true,
|
||||
"requireApproval": true,
|
||||
"showTimeEstimates": true
|
||||
},
|
||||
"checkpoints": {
|
||||
"autoCheckpoint": true
|
||||
},
|
||||
"hooks": {
|
||||
"PreToolUse:Edit": "~/.claude/hooks/backup-file.sh ${file_path}",
|
||||
"PostToolUse:Edit": "npm test -- --findRelatedTests ${file_path}"
|
||||
},
|
||||
"permissions": {
|
||||
"mode": "confirm"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"autonomous": {
|
||||
"name": "Autonomous Development",
|
||||
"description": "Configuration for autonomous work with auto mode safety checks",
|
||||
"config": {
|
||||
"general": {
|
||||
"model": "claude-sonnet-4-6",
|
||||
"temperature": 0.5
|
||||
},
|
||||
"permissions": {
|
||||
"mode": "auto"
|
||||
},
|
||||
"sandbox": {
|
||||
"enabled": true,
|
||||
"failIfUnavailable": false
|
||||
},
|
||||
"backgroundTasks": {
|
||||
"enabled": true,
|
||||
"maxConcurrentTasks": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"documentation": {
|
||||
"name": "Documentation Writing",
|
||||
"description": "Configuration for writing documentation",
|
||||
"config": {
|
||||
"general": {
|
||||
"model": "claude-sonnet-4-6",
|
||||
"temperature": 0.7
|
||||
},
|
||||
"permissions": {
|
||||
"mode": "unrestricted"
|
||||
},
|
||||
"hooks": {
|
||||
"PostToolUse:Write": "markdownlint ${file_path}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
setup-auto-mode-permissions.py
|
||||
|
||||
Seed ~/.claude/settings.json with a conservative baseline of safe permissions
|
||||
for Claude Code. The default set is read-only and local-inspection oriented;
|
||||
optional flags let you widen the allowlist for editing, test execution, git
|
||||
write operations, package installs, and GitHub CLI writes.
|
||||
|
||||
Usage:
|
||||
python3 setup-auto-mode-permissions.py
|
||||
python3 setup-auto-mode-permissions.py --dry-run
|
||||
python3 setup-auto-mode-permissions.py --include-edits --include-tests
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
SETTINGS_PATH = Path.home() / ".claude" / "settings.json"
|
||||
|
||||
# Core baseline: read-only inspection and low-risk local shell commands.
|
||||
CORE_PERMISSIONS = [
|
||||
"Read(*)",
|
||||
"Glob(*)",
|
||||
"Grep(*)",
|
||||
"Agent(*)",
|
||||
"Skill(*)",
|
||||
"WebSearch(*)",
|
||||
"WebFetch(*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(pwd:*)",
|
||||
"Bash(which:*)",
|
||||
"Bash(echo:*)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(head:*)",
|
||||
"Bash(tail:*)",
|
||||
"Bash(wc:*)",
|
||||
"Bash(sort:*)",
|
||||
"Bash(uniq:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(dirname:*)",
|
||||
"Bash(basename:*)",
|
||||
"Bash(realpath:*)",
|
||||
"Bash(file:*)",
|
||||
"Bash(stat:*)",
|
||||
"Bash(diff:*)",
|
||||
"Bash(md5sum:*)",
|
||||
"Bash(sha256sum:*)",
|
||||
"Bash(date:*)",
|
||||
"Bash(env:*)",
|
||||
"Bash(printenv:*)",
|
||||
"Bash(git status:*)",
|
||||
"Bash(git log:*)",
|
||||
"Bash(git diff:*)",
|
||||
"Bash(git branch:*)",
|
||||
"Bash(git show:*)",
|
||||
"Bash(git rev-parse:*)",
|
||||
"Bash(git remote -v:*)",
|
||||
"Bash(git remote get-url:*)",
|
||||
"Bash(git stash list:*)",
|
||||
]
|
||||
|
||||
# Optional but still local: file edits and task bookkeeping.
|
||||
EDITING_PERMISSIONS = [
|
||||
"Edit(*)",
|
||||
"Write(*)",
|
||||
"NotebookEdit(*)",
|
||||
"TaskCreate(*)",
|
||||
"TaskUpdate(*)",
|
||||
]
|
||||
|
||||
# Optional dev/test commands. These can still execute arbitrary project scripts,
|
||||
# so keep them opt-in rather than part of the default baseline.
|
||||
TEST_AND_BUILD_PERMISSIONS = [
|
||||
"Bash(npm test:*)",
|
||||
"Bash(cargo test:*)",
|
||||
"Bash(go test:*)",
|
||||
"Bash(pytest:*)",
|
||||
"Bash(python3 -m pytest:*)",
|
||||
"Bash(make:*)",
|
||||
"Bash(cmake:*)",
|
||||
]
|
||||
|
||||
# Optional local git write operations. History-rewriting commands stay out of
|
||||
# the default baseline because they are easy to misuse.
|
||||
GIT_WRITE_PERMISSIONS = [
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(git switch:*)",
|
||||
"Bash(git stash:*)",
|
||||
"Bash(git tag:*)",
|
||||
]
|
||||
|
||||
# Optional dependency/package commands. These are intentionally excluded from
|
||||
# the default baseline because they can execute project hooks or fetch code.
|
||||
PACKAGE_MANAGER_PERMISSIONS = [
|
||||
"Bash(npm ci:*)",
|
||||
"Bash(npm install:*)",
|
||||
"Bash(pip install:*)",
|
||||
"Bash(pip3 install:*)",
|
||||
]
|
||||
|
||||
# Optional GitHub CLI write access.
|
||||
GITHUB_WRITE_PERMISSIONS = [
|
||||
"Bash(gh pr create:*)",
|
||||
]
|
||||
|
||||
# Optional extra GitHub CLI read access.
|
||||
GITHUB_READ_PERMISSIONS = [
|
||||
"Bash(gh pr view:*)",
|
||||
"Bash(gh pr list:*)",
|
||||
"Bash(gh issue view:*)",
|
||||
"Bash(gh issue list:*)",
|
||||
"Bash(gh repo view:*)",
|
||||
]
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Seed Claude Code settings with a conservative permission baseline."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Preview the rules that would be added without writing settings.json",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-edits",
|
||||
action="store_true",
|
||||
help="Add file-editing permissions (Edit/Write/NotebookEdit/TaskCreate/TaskUpdate)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-tests",
|
||||
action="store_true",
|
||||
help="Add local build/test commands such as pytest, cargo test, and make",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-git-write",
|
||||
action="store_true",
|
||||
help="Add local git mutation commands such as add, commit, checkout, and stash",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-packages",
|
||||
action="store_true",
|
||||
help="Add package install commands such as npm ci, npm install, and pip install",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-gh-write",
|
||||
action="store_true",
|
||||
help="Add GitHub CLI write permissions such as gh pr create",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-gh-read",
|
||||
action="store_true",
|
||||
help="Add GitHub CLI read permissions such as gh pr view and gh repo view",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def load_settings(path: Path) -> dict:
|
||||
if not path.exists():
|
||||
return {}
|
||||
|
||||
try:
|
||||
with path.open() as f:
|
||||
settings = json.load(f)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise SystemExit(f"Invalid JSON in {path}: {exc}") from exc
|
||||
|
||||
if not isinstance(settings, dict):
|
||||
raise SystemExit(f"Expected {path} to contain a JSON object.")
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
def build_permissions(args: argparse.Namespace) -> list[str]:
|
||||
permissions = list(CORE_PERMISSIONS)
|
||||
|
||||
if args.include_edits:
|
||||
permissions.extend(EDITING_PERMISSIONS)
|
||||
|
||||
if args.include_tests:
|
||||
permissions.extend(TEST_AND_BUILD_PERMISSIONS)
|
||||
|
||||
if args.include_git_write:
|
||||
permissions.extend(GIT_WRITE_PERMISSIONS)
|
||||
|
||||
if args.include_packages:
|
||||
permissions.extend(PACKAGE_MANAGER_PERMISSIONS)
|
||||
|
||||
if args.include_gh_write:
|
||||
permissions.extend(GITHUB_WRITE_PERMISSIONS)
|
||||
|
||||
if args.include_gh_read:
|
||||
permissions.extend(GITHUB_READ_PERMISSIONS)
|
||||
|
||||
return permissions
|
||||
|
||||
|
||||
def append_unique(existing: list, new_items: Iterable[str]) -> list[str]:
|
||||
seen = set(existing)
|
||||
added: list[str] = []
|
||||
for item in new_items:
|
||||
if item not in seen:
|
||||
existing.append(item)
|
||||
seen.add(item)
|
||||
added.append(item)
|
||||
return added
|
||||
|
||||
|
||||
def atomic_write_json(path: Path, payload: dict) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.NamedTemporaryFile(
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
dir=str(path.parent),
|
||||
delete=False,
|
||||
) as tmp:
|
||||
json.dump(payload, tmp, indent=2)
|
||||
tmp.write("\n")
|
||||
tmp_path = Path(tmp.name)
|
||||
|
||||
tmp_path.replace(path)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
permissions_to_add = build_permissions(args)
|
||||
|
||||
settings = load_settings(SETTINGS_PATH)
|
||||
permissions = settings.setdefault("permissions", {})
|
||||
|
||||
if not isinstance(permissions, dict):
|
||||
raise SystemExit("Expected permissions to be a JSON object.")
|
||||
|
||||
allow = permissions.setdefault("allow", [])
|
||||
if not isinstance(allow, list):
|
||||
raise SystemExit("Expected permissions.allow to be a JSON array.")
|
||||
|
||||
added = append_unique(allow, permissions_to_add)
|
||||
|
||||
if not added:
|
||||
print("Nothing to add — all selected rules already present.")
|
||||
return
|
||||
|
||||
print(f"{'Would add' if args.dry_run else 'Adding'} {len(added)} rule(s):")
|
||||
for rule in added:
|
||||
print(f" + {rule}")
|
||||
|
||||
if args.dry_run:
|
||||
print("\nDry run — no changes written.")
|
||||
return
|
||||
|
||||
atomic_write_json(SETTINGS_PATH, settings)
|
||||
print(f"\nDone. {len(added)} rule(s) added to {SETTINGS_PATH}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
# Changelog
|
||||
|
||||
## v2.3.0 — 2026-04-07
|
||||
|
||||
### Features
|
||||
|
||||
- build and publish EPUB artifacts per language (90e9c30) @Thiên Toán
|
||||
- add missing pre-tool-check.sh hook to 06-hooks (b511ed1) @JiayuWang
|
||||
- add Chinese translations in zh/ directory (89e89d4) @Luong NGUYEN
|
||||
- Add performance-optimizer subagent and dependency-check hook (f53d080) @qk
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Windows Git Bash compatibility + stdin JSON protocol (2cbb10c) @Luong NGUYEN
|
||||
- correct autoCheckpoint config documentation in 08-checkpoints (749c79f) @JiayuWang
|
||||
- embed SVG images instead of replacing with placeholders (1b16709) @Thiên Toán
|
||||
- nested code fence rendering in memory README (ce24423) @Zhaoshan Duan
|
||||
- apply review fixes dropped by squash merge (34259ca) @Luong NGUYEN
|
||||
- make hook scripts compatible with Windows Git Bash and use stdin JSON protocol (107153d) @binyu li
|
||||
|
||||
### Documentation
|
||||
|
||||
- sync all tutorials with latest Claude Code docs (April 2026) (72d3b01) @Luong NGUYEN
|
||||
- add Chinese language link to language switcher (6cbaa4d) @Luong NGUYEN
|
||||
- add language switcher between English and Vietnamese (100c45e) @Luong NGUYEN
|
||||
- add GitHub #1 Trending badge (0ca8c37) @Luong NGUYEN
|
||||
- Introduce cc-context-stats for context zone monitoring (d41b335) @Luong NGUYEN
|
||||
- Introduce luongnv89/skills collection and luongnv89/asm skill manager (7e3c0b6) @Luong NGUYEN
|
||||
- Update README stats to reflect current GitHub metrics (5,900+ stars, 690+ forks) (5001525) @Luong NGUYEN
|
||||
- Update README stats to reflect current GitHub metrics (3,900+ stars, 460+ forks) (9cb92d6) @Luong NGUYEN
|
||||
|
||||
### Refactoring
|
||||
|
||||
- replace Kroki HTTP dependency with local mmdc rendering (e76bbe4) @Luong NGUYEN
|
||||
- shift quality checks to pre-commit, CI as 2nd pass (6d1e0ae) @Luong NGUYEN
|
||||
- narrow auto-mode permissions baseline (2790fb2) @Luong NGUYEN
|
||||
- Replace auto-adapt hook with one-time permissions setup script (995a5d6) @Luong NGUYEN
|
||||
|
||||
### Other
|
||||
|
||||
- shift-left quality gates — add mypy to pre-commit, fix CI failures (699fb39) @Luong NGUYEN
|
||||
- Add Vietnamese (Tiếng Việt) Localization (a70777e) @Thiên Toán
|
||||
|
||||
**Full Changelog**: https://github.com/luongnv89/claude-howto/compare/v2.2.0...v2.3.0
|
||||
|
||||
---
|
||||
|
||||
## v2.2.0 — 2026-03-26
|
||||
|
||||
### Documentation
|
||||
|
||||
- Sync all tutorials and references with Claude Code v2.1.84 (f78c094) @luongnv89
|
||||
- Update slash commands to 55+ built-in + 5 bundled skills, mark 3 deprecated
|
||||
- Expand hook events from 18 to 25, add `agent` hook type (now 4 types)
|
||||
- Add Auto Mode, Channels, Voice Dictation to advanced features
|
||||
- Add `effort`, `shell` skill frontmatter fields; `initialPrompt`, `disallowedTools` agent fields
|
||||
- Add WebSocket MCP transport, elicitation, 2KB tool cap
|
||||
- Add plugin LSP support, `userConfig`, `${CLAUDE_PLUGIN_DATA}`
|
||||
- Update all reference docs (CATALOG, QUICK_REFERENCE, LEARNING-ROADMAP, INDEX)
|
||||
- Rewrite README as landing-page-structured guide (32a0776) @luongnv89
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add missing cSpell words and README sections for CI compliance (93f9d51) @luongnv89
|
||||
- Add `Sandboxing` to cSpell dictionary (b80ce6f) @luongnv89
|
||||
|
||||
**Full Changelog**: https://github.com/luongnv89/claude-howto/compare/v2.1.1...v2.2.0
|
||||
|
||||
---
|
||||
|
||||
## v2.1.1 — 2026-03-13
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Remove dead marketplace link failing CI link checks (3fdf0d6) @luongnv89
|
||||
- Add `sandboxed` and `pycache` to cSpell dictionary (dc64618) @luongnv89
|
||||
|
||||
**Full Changelog**: https://github.com/luongnv89/claude-howto/compare/v2.1.0...v2.1.1
|
||||
|
||||
---
|
||||
|
||||
## v2.1.0 — 2026-03-13
|
||||
|
||||
### Features
|
||||
|
||||
- Add adaptive learning path with self-assessment and lesson quiz skills (1ef46cd) @luongnv89
|
||||
- `/self-assessment` — interactive proficiency quiz across 10 feature areas with personalized learning path
|
||||
- `/lesson-quiz [lesson]` — per-lesson knowledge check with 8-10 targeted questions
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Update broken URLs, deprecations, and outdated references (8fe4520) @luongnv89
|
||||
- Fix broken links in resources and self-assessment skill (7a05863) @luongnv89
|
||||
- Use tilde fences for nested code blocks in concepts guide (5f82719) @VikalpP
|
||||
- Add missing words to cSpell dictionary (8df7572) @luongnv89
|
||||
|
||||
### Documentation
|
||||
|
||||
- Phase 5 QA — fix consistency, URLs, and terminology across docs (00bbe4c) @luongnv89
|
||||
- Complete Phases 3-4 — new feature coverage and reference doc updates (132de29) @luongnv89
|
||||
- Add MCPorter runtime to MCP context bloat section (ef52705) @luongnv89
|
||||
- Add missing commands, features, and settings across 6 guides (4bc8f15) @luongnv89
|
||||
- Add style guide based on existing repo conventions (84141d0) @luongnv89
|
||||
- Add self-assessment row to guide comparison table (8fe0c96) @luongnv89
|
||||
- Add VikalpP to contributors list for PR #7 (d5b4350) @luongnv89
|
||||
- Add self-assessment and lesson-quiz skill references to README and roadmap (d5a6106) @luongnv89
|
||||
|
||||
### New Contributors
|
||||
|
||||
- @VikalpP made their first contribution in #7
|
||||
|
||||
**Full Changelog**: https://github.com/luongnv89/claude-howto/compare/v2.0.0...v2.1.0
|
||||
|
||||
---
|
||||
|
||||
## v2.0.0 — 2026-02-01
|
||||
|
||||
### Features
|
||||
|
||||
- Sync all documentation with Claude Code February 2026 features (487c96d)
|
||||
- Update 26 files across all 10 tutorial directories and 7 reference documents
|
||||
- Add documentation for **Auto Memory** — persistent learnings per project
|
||||
- Add documentation for **Remote Control**, **Web Sessions**, and **Desktop App**
|
||||
- Add documentation for **Agent Teams** (experimental multi-agent collaboration)
|
||||
- Add documentation for **MCP OAuth 2.0**, **Tool Search**, and **Claude.ai Connectors**
|
||||
- Add documentation for **Persistent Memory** and **Worktree Isolation** for subagents
|
||||
- Add documentation for **Background Subagents**, **Task List**, **Prompt Suggestions**
|
||||
- Add documentation for **Sandboxing** and **Managed Settings** (Enterprise)
|
||||
- Add documentation for **HTTP Hooks** and 7 new hook events
|
||||
- Add documentation for **Plugin Settings**, **LSP Servers**, and Marketplace updates
|
||||
- Add documentation for **Summarize from Checkpoint** rewind option
|
||||
- Document 17 new slash commands (`/fork`, `/desktop`, `/teleport`, `/tasks`, `/fast`, etc.)
|
||||
- Document new CLI flags (`--worktree`, `--from-pr`, `--remote`, `--teleport`, `--teammate-mode`, etc.)
|
||||
- Document new environment variables for auto memory, effort levels, agent teams, and more
|
||||
|
||||
### Design
|
||||
|
||||
- Redesign logo to compass-bracket mark with minimal palette (20779db)
|
||||
|
||||
### Bug Fixes / Corrections
|
||||
|
||||
- Update model names: Sonnet 4.5 → **Sonnet 4.6**, Opus 4.5 → **Opus 4.6**
|
||||
- Fix permission mode names: replace fictional "Unrestricted/Confirm/Read-only" with actual `default`/`acceptEdits`/`plan`/`dontAsk`/`bypassPermissions`
|
||||
- Fix hook events: remove fictional `PreCommit`/`PostCommit`/`PrePush`, add real events (`SubagentStart`, `WorktreeCreate`, `ConfigChange`, etc.)
|
||||
- Fix CLI syntax: replace `claude-code --headless` with `claude -p` (print mode)
|
||||
- Fix checkpoint commands: replace fictional `/checkpoint save/list/rewind/diff` with actual `Esc+Esc` / `/rewind` interface
|
||||
- Fix session management: replace fictional `/session list/new/switch/save` with real `/resume`/`/rename`/`/fork`
|
||||
- Fix plugin manifest format: migrate `plugin.yaml` → `.claude-plugin/plugin.json`
|
||||
- Fix MCP config paths: `~/.claude/mcp.json` → `.mcp.json` (project) / `~/.claude.json` (user)
|
||||
- Fix documentation URLs: `docs.claude.com` → `docs.anthropic.com`; remove fictional `plugins.claude.com`
|
||||
- Remove fictional configuration fields across multiple files
|
||||
- Update all "Last Updated" dates to February 2026
|
||||
|
||||
**Full Changelog**: https://github.com/luongnv89/claude-howto/compare/20779db...v2.0.0
|
||||
@@ -0,0 +1,224 @@
|
||||
<!-- i18n-source: CODE_OF_CONDUCT.md -->
|
||||
<!-- i18n-source-sha: 63a1416 -->
|
||||
<!-- i18n-date: 2026-04-09 -->
|
||||
|
||||
# Кодекс поведінки контриб'юторів
|
||||
|
||||
## Наше зобов'язання
|
||||
|
||||
Ми зобов'язуємося забезпечити привітну та надихаючу спільноту для всіх. Ми запевняємо, що участь у нашій спільноті є вільною від переслідувань для кожного, незалежно від віку, статури, касти, інвалідності, етнічної приналежності, статевих ознак, гендерної ідентичності та вираження, рівня досвіду, освіти, соціально-економічного статусу, національності, зовнішнього вигляду, раси, релігії або сексуальної ідентичності та орієнтації.
|
||||
|
||||
Ми прагнемо забезпечити позитивне, інклюзивне та безпечне середовище, де всі контриб'ютори відчувають повагу, цінність та можливість внести свій найкращий вклад.
|
||||
|
||||
## Наші стандарти
|
||||
|
||||
Приклади поведінки, що сприяє створенню позитивного середовища:
|
||||
|
||||
### Будьте поважними
|
||||
- Використовуйте привітну та інклюзивну мову
|
||||
- Поважайте різні думки, погляди та досвід
|
||||
- Приймайте конструктивну критику з гідністю
|
||||
- Поважайте культурні та мовні відмінності
|
||||
- Визнавайте та цінуйте різноманітне походження
|
||||
|
||||
### Будьте колаборативними
|
||||
- Працюйте разом над вирішенням конфліктів
|
||||
- Відзначайте та визнавайте внески інших
|
||||
- Допомагайте іншим вчитися та зростати
|
||||
- Діліться знаннями та ставте запитання
|
||||
- Підтримуйте колег по спільноті
|
||||
|
||||
### Будьте професійними
|
||||
- Тримайте обговорення зосередженими та продуктивними
|
||||
- Уникайте принизливих або образливих зауважень
|
||||
- Залишайтесь у темі в issues та обговореннях
|
||||
- Використовуйте чітку та поважну комунікацію
|
||||
- Припускайте добрі наміри в повідомленнях інших
|
||||
|
||||
### Будьте інклюзивними
|
||||
- Вітайте нових контриб'юторів
|
||||
- Допомагайте адаптуватися людям, новим у проєкті
|
||||
- Надавайте конструктивний зворотний зв'язок
|
||||
- Залучайте людей з різними перспективами
|
||||
- Створюйте простір для тихих голосів
|
||||
|
||||
## Неприйнятна поведінка
|
||||
|
||||
Наступні дії вважаються переслідуванням та є неприйнятними в нашій спільноті:
|
||||
|
||||
### Переслідування та дискримінація
|
||||
- Образливі коментарі щодо гендеру, гендерної ідентичності та вираження, сексуальної орієнтації, інвалідності, психічного захворювання, нейро(а)типовості, зовнішнього вигляду, статури, віку, раси або релігії
|
||||
- Небажані коментарі щодо життєвого вибору та практик людини
|
||||
- Навмисне неправильне вживання займенників або використання «мертвих» або відкинутих імен
|
||||
- Безпідставні або не по темі сексуальні зображення або поведінка
|
||||
- Фізичний контакт та імітований фізичний контакт без згоди або після прохання припинити
|
||||
|
||||
### Зловживання
|
||||
- Погрози насильством або агресивна мова, спрямована проти іншої людини
|
||||
- Підбурювання до насильства або агресивна мова щодо будь-якої особи
|
||||
- Навмисне залякування
|
||||
- Переслідування або стеження
|
||||
- Переслідування через повторні контакти
|
||||
- Будь-яка інша поведінка, яку можна обґрунтовано вважати неприйнятною
|
||||
|
||||
### Онлайн-переслідування
|
||||
- Систематичне порушення онлайн-обговорень
|
||||
- Публікація приватної інформації інших (доксинг)
|
||||
- Небажана сексуальна увага або домагання
|
||||
- Тролінг або навмисне порушення
|
||||
- Спам або повторна реклама
|
||||
- Поширення дезінформації
|
||||
|
||||
### Помста
|
||||
- Вжиття карних заходів проти когось за повідомлення про порушення
|
||||
- Несправедливе ставлення до когось через повідомлення про неприйнятну поведінку
|
||||
- Погрози повідомити про когось у відповідь
|
||||
|
||||
## Область застосування
|
||||
|
||||
Цей Кодекс поведінки застосовується до:
|
||||
- Усіх просторів спільноти (GitHub issues, pull requests, обговорення тощо)
|
||||
- Репозиторіїв та документації проєкту
|
||||
- Подій та зібрань, організованих проєктом або від його імені
|
||||
- Публічних просторів, де особи представляють проєкт або спільноту
|
||||
- Приватних комунікацій, коли вони стосуються справ проєкту
|
||||
|
||||
Кодекс поведінки застосовується до всіх просторів проєкту, а також коли особа представляє проєкт або спільноту в публічних просторах. Приклади представлення проєкту або спільноти включають використання офіційної електронної адреси проєкту, публікації через офіційний акаунт у соціальних мережах або діяльність як призначений представник на онлайн- або офлайн-заході.
|
||||
|
||||
## Застосування
|
||||
|
||||
Лідери спільноти відповідають за роз'яснення та дотримання наших стандартів прийнятної поведінки та вживатимуть відповідних та справедливих коригувальних дій у відповідь на будь-яку поведінку, яку вони вважають неприйнятною, загрозливою, образливою або шкідливою.
|
||||
|
||||
### Повідомлення про порушення
|
||||
|
||||
Якщо ви стали свідком або жертвою неприйнятної поведінки, або маєте будь-які інші занепокоєння, будь ласка, повідомте:
|
||||
|
||||
1. **Email**: Зв'яжіться з мейнтейнерами проєкту
|
||||
2. **GitHub Issues**: Функція приватного повідомлення (якщо доступна)
|
||||
3. **Пряме повідомлення**: Зв'яжіться з мейнтейнером проєкту напряму
|
||||
|
||||
Вкажіть:
|
||||
- Вашу контактну інформацію
|
||||
- Імена залучених осіб (якщо вам комфортно ділитися)
|
||||
- Опис інциденту
|
||||
- Контекст та відповідні посилання
|
||||
- Як інцидент вплинув на вас
|
||||
|
||||
**Усі повідомлення зберігатимуться конфіденційно.**
|
||||
|
||||
### Розслідування та реагування
|
||||
|
||||
Лідери спільноти:
|
||||
- Переглядатимуть та розслідуватимуть усі скарги оперативно та справедливо
|
||||
- Зберігатимуть конфіденційність щодо репортера
|
||||
- Повідомлятимуть репортера про статус його повідомлення
|
||||
- Вживатимуть відповідних дій на основі результатів
|
||||
|
||||
Можливі дії:
|
||||
- Приватне попередження або консультація
|
||||
- Публічні вибачення (за згодою порушника)
|
||||
- Тимчасове або постійне видалення з просторів спільноти
|
||||
- Втрата привілеїв контриб'ютора
|
||||
- Ескалація на GitHub або інші платформи за потреби
|
||||
|
||||
### Процес апеляції
|
||||
|
||||
Якщо ви вважаєте, що рішення було прийнято помилково, ви можете оскаржити:
|
||||
1. Надавши нову інформацію або контекст
|
||||
2. Запросивши перегляд кількома мейнтейнерами
|
||||
3. Звернувшись протягом 30 днів після початкового рішення
|
||||
|
||||
## Настанови для шанобливої незгоди
|
||||
|
||||
Розбіжності є природними в технічних спільнотах. Ось як не погоджуватись шанобливо:
|
||||
|
||||
### Робіть
|
||||
- ✅ Зосереджуйтесь на ідеї, а не на людині
|
||||
- ✅ Ставте уточнюючі запитання
|
||||
- ✅ Визнавайте слушні моменти в протилежних поглядах
|
||||
- ✅ Використовуйте «Я»-висловлювання («Я не погоджуюсь, тому що...»)
|
||||
- ✅ Посилайтесь на джерела та факти
|
||||
- ✅ Дякуйте іншим за їхню перспективу
|
||||
|
||||
### Не робіть
|
||||
- ❌ Переходьте на особистості
|
||||
- ❌ Використовуйте зневажливу мову («Це дурниця»)
|
||||
- ❌ Припускайте погані наміри
|
||||
- ❌ Піднімайте непов'язані минулі конфлікти
|
||||
- ❌ Підвищуйте тон або ставайте ворожими
|
||||
- ❌ Навмисно спотворюйте погляди інших
|
||||
|
||||
### Приклад шанобливої незгоди
|
||||
```
|
||||
❌ Погано: "That's a dumb idea and you clearly don't understand the project."
|
||||
|
||||
✅ Добре: "I see your point, but I'm concerned this approach might
|
||||
not scale well with larger projects. Have you considered the
|
||||
performance implications shown in this benchmark? I'd love to
|
||||
hear your thoughts on this."
|
||||
```
|
||||
|
||||
## Будування мостів
|
||||
|
||||
Ми віримо в силу діалогу та примирення:
|
||||
|
||||
- Якщо хтось вказує на вашу поведінку, слухайте та рефлексуйте
|
||||
- Будьте готові вибачитися, якщо завдали шкоди
|
||||
- Підтримуйте інших у навчанні та зростанні
|
||||
- Визнавайте, що люди роблять помилки
|
||||
- Зосереджуйтесь на рішеннях, а не на звинуваченнях
|
||||
- Припускайте добрі наміри, зберігаючи відповідальність
|
||||
|
||||
## Переваги дотримання цього кодексу
|
||||
|
||||
Підтримуючи шанобливу та інклюзивну спільноту, ми:
|
||||
- Залучаємо різноманітних контриб'юторів з різними перспективами
|
||||
- Створюємо кращі рішення через співпрацю
|
||||
- Будуємо міцніші стосунки
|
||||
- Робимо проєкт привітнішим для новачків
|
||||
- Виробляємо якіснішу документацію та приклади
|
||||
- Формуємо культуру навчання та зростання
|
||||
|
||||
## Спеціальні міркування
|
||||
|
||||
### Для мейнтейнерів
|
||||
- Подавайте приклад у дотриманні цього Кодексу
|
||||
- Оперативно реагуйте на повідомлення
|
||||
- Будьте прозорими щодо рішень
|
||||
- Допомагайте справедливо вирішувати конфлікти
|
||||
- Створюйте безпечне середовище для всіх
|
||||
|
||||
### Для контриб'юторів
|
||||
- Припускайте добрі наміри мейнтейнерів та інших контриб'юторів
|
||||
- Надавайте конструктивний зворотний зв'язок
|
||||
- Підтримуйте новачків
|
||||
- Допомагайте дотримуватись стандартів спільноти
|
||||
- Беріть відповідальність за культуру спільноти
|
||||
|
||||
### Для всіх
|
||||
- Пам'ятайте, що за кожним повідомленням стоїть людина
|
||||
- Робіть перерви, якщо відчуваєте фрустрацію
|
||||
- Звертайтесь до лідерів спільноти, якщо потрібна підтримка
|
||||
- Допомагайте створювати спільноту, частиною якої хочете бути
|
||||
|
||||
## Атрибуція
|
||||
|
||||
Цей Кодекс поведінки адаптовано з:
|
||||
- [Contributor Covenant](https://www.contributor-covenant.org/)
|
||||
- [Mozilla Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/)
|
||||
- [Python Community Code of Conduct](https://www.python.org/psf/conduct/)
|
||||
|
||||
## Запитання?
|
||||
|
||||
Якщо у вас є запитання щодо цього Кодексу поведінки, зверніться до мейнтейнерів проєкту.
|
||||
|
||||
## Історія версій
|
||||
|
||||
- **Версія 1.0** (Січень 2026) — Початковий Кодекс поведінки
|
||||
|
||||
---
|
||||
|
||||
**Дякуємо за допомогу в створенні привітної, інклюзивної та шанобливої спільноти!** 🌟
|
||||
|
||||
---
|
||||
**Останнє оновлення**: Квітень 2026
|
||||
@@ -0,0 +1,385 @@
|
||||
<!-- i18n-source: CONTRIBUTING.md -->
|
||||
<!-- i18n-source-sha: 63a1416 -->
|
||||
<!-- i18n-date: 2026-04-09 -->
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="resources/logos/claude-howto-logo-dark.svg">
|
||||
<img alt="Claude How To" src="resources/logos/claude-howto-logo.svg">
|
||||
</picture>
|
||||
|
||||
# Внесок у Claude How To
|
||||
|
||||
Дякуємо за ваш інтерес до участі в цьому проєкті! Цей довідник допоможе вам зрозуміти, як ефективно зробити внесок.
|
||||
|
||||
## Про цей проєкт
|
||||
|
||||
Claude How To — це візуальний, заснований на прикладах довідник з Claude Code. Ми надаємо:
|
||||
- **Mermaid-діаграми**, що пояснюють як працюють функції
|
||||
- **Готові до продакшену шаблони**, які можна використовувати одразу
|
||||
- **Реальні приклади** з контекстом та найкращими практиками
|
||||
- **Прогресивні шляхи навчання** від початківця до просунутого
|
||||
|
||||
## Типи внесків
|
||||
|
||||
### 1. Нові приклади або шаблони
|
||||
Додавайте приклади для існуючих функцій (слеш-команди, навички, хуки тощо):
|
||||
- Код, готовий до копіювання та вставки
|
||||
- Чіткі пояснення як це працює
|
||||
- Сценарії використання та переваги
|
||||
- Поради з усунення проблем
|
||||
|
||||
### 2. Покращення документації
|
||||
- Уточнення заплутаних розділів
|
||||
- Виправлення помилок та граматики
|
||||
- Додавання відсутньої інформації
|
||||
- Покращення прикладів коду
|
||||
|
||||
### 3. Гайди з функцій
|
||||
Створюйте гайди для нових функцій Claude Code:
|
||||
- Покрокові туторіали
|
||||
- Архітектурні діаграми
|
||||
- Поширені патерни та анти-патерни
|
||||
- Реальні робочі процеси
|
||||
|
||||
### 4. Звіти про помилки
|
||||
Повідомляйте про проблеми:
|
||||
- Опишіть, що ви очікували
|
||||
- Опишіть, що сталося насправді
|
||||
- Вкажіть кроки для відтворення
|
||||
- Додайте версію Claude Code та ОС
|
||||
|
||||
### 5. Відгуки та пропозиції
|
||||
Допоможіть покращити довідник:
|
||||
- Запропонуйте кращі пояснення
|
||||
- Вкажіть на прогалини в покритті
|
||||
- Порекомендуйте нові розділи або реорганізацію
|
||||
|
||||
## Початок роботи
|
||||
|
||||
### 1. Форк та клонування
|
||||
```bash
|
||||
git clone https://github.com/luongnv89/claude-howto.git
|
||||
cd claude-howto
|
||||
```
|
||||
|
||||
### 2. Створення гілки
|
||||
Використовуйте описову назву гілки:
|
||||
```bash
|
||||
git checkout -b add/feature-name
|
||||
git checkout -b fix/issue-description
|
||||
git checkout -b docs/improvement-area
|
||||
```
|
||||
|
||||
### 3. Налаштування середовища
|
||||
|
||||
Pre-commit хуки запускають ті ж перевірки, що й CI, локально перед кожним комітом. Усі чотири перевірки повинні пройти перед прийняттям PR.
|
||||
|
||||
**Необхідні залежності:**
|
||||
|
||||
```bash
|
||||
# Python tooling (uv is the package manager for this project)
|
||||
pip install uv
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv pip install -r scripts/requirements-dev.txt
|
||||
|
||||
# Markdown linter (Node.js)
|
||||
npm install -g markdownlint-cli
|
||||
|
||||
# Mermaid diagram validator (Node.js)
|
||||
npm install -g @mermaid-js/mermaid-cli
|
||||
|
||||
# Install pre-commit and activate hooks
|
||||
uv pip install pre-commit
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
**Перевірка налаштування:**
|
||||
|
||||
```bash
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
Хуки, що запускаються при кожному коміті:
|
||||
|
||||
| Хук | Що перевіряє |
|
||||
|-----|-------------|
|
||||
| `markdown-lint` | Форматування та структуру Markdown |
|
||||
| `cross-references` | Відносні посилання, якорі, блоки коду |
|
||||
| `mermaid-syntax` | Усі блоки ` ```mermaid ` коректно парсяться |
|
||||
| `link-check` | Зовнішні URL-адреси доступні |
|
||||
| `build-epub` | EPUB генерується без помилок (при змінах `.md`) |
|
||||
|
||||
## Структура каталогів
|
||||
|
||||
```
|
||||
├── 01-slash-commands/ # Ярлики, ініційовані користувачем
|
||||
├── 02-memory/ # Приклади постійного контексту
|
||||
├── 03-skills/ # Повторно використовувані можливості
|
||||
├── 04-subagents/ # Спеціалізовані AI-асистенти
|
||||
├── 05-mcp/ # Приклади Model Context Protocol
|
||||
├── 06-hooks/ # Автоматизація на основі подій
|
||||
├── 07-plugins/ # Пакетні функції
|
||||
├── 08-checkpoints/ # Знімки сесій
|
||||
├── 09-advanced-features/ # Планування, мислення, фони
|
||||
├── 10-cli/ # Довідник CLI
|
||||
├── scripts/ # Скрипти збірки та утиліт
|
||||
└── README.md # Основний довідник
|
||||
```
|
||||
|
||||
## Як зробити внесок прикладів
|
||||
|
||||
### Додавання слеш-команди
|
||||
1. Створіть файл `.md` в `01-slash-commands/`
|
||||
2. Включіть:
|
||||
- Чіткий опис що вона робить
|
||||
- Сценарії використання
|
||||
- Інструкції з встановлення
|
||||
- Приклади використання
|
||||
- Поради з налаштування
|
||||
3. Оновіть `01-slash-commands/README.md`
|
||||
|
||||
### Додавання навички
|
||||
1. Створіть каталог в `03-skills/`
|
||||
2. Включіть:
|
||||
- `SKILL.md` — основна документація
|
||||
- `scripts/` — допоміжні скрипти за потреби
|
||||
- `templates/` — шаблони промптів
|
||||
- Приклади використання в README
|
||||
3. Оновіть `03-skills/README.md`
|
||||
|
||||
### Додавання субагента
|
||||
1. Створіть файл `.md` в `04-subagents/`
|
||||
2. Включіть:
|
||||
- Призначення та можливості агента
|
||||
- Структуру системного промпту
|
||||
- Приклади використання
|
||||
- Приклади інтеграції
|
||||
3. Оновіть `04-subagents/README.md`
|
||||
|
||||
### Додавання конфігурації MCP
|
||||
1. Створіть файл `.json` в `05-mcp/`
|
||||
2. Включіть:
|
||||
- Пояснення конфігурації
|
||||
- Необхідні змінні оточення
|
||||
- Інструкції з налаштування
|
||||
- Приклади використання
|
||||
3. Оновіть `05-mcp/README.md`
|
||||
|
||||
### Додавання хука
|
||||
1. Створіть файл `.sh` в `06-hooks/`
|
||||
2. Включіть:
|
||||
- Shebang та опис
|
||||
- Чіткі коментарі, що пояснюють логіку
|
||||
- Обробку помилок
|
||||
- Міркування безпеки
|
||||
3. Оновіть `06-hooks/README.md`
|
||||
|
||||
## Настанови з написання
|
||||
|
||||
### Стиль Markdown
|
||||
- Використовуйте чіткі заголовки (H2 для секцій, H3 для підсекцій)
|
||||
- Тримайте абзаци короткими та зосередженими
|
||||
- Використовуйте маркеровані списки
|
||||
- Включайте блоки коду з вказівкою мови
|
||||
- Додавайте порожні рядки між секціями
|
||||
|
||||
### Приклади коду
|
||||
- Робіть приклади готовими до копіювання та вставки
|
||||
- Коментуйте неочевидну логіку
|
||||
- Включайте і прості, і просунуті версії
|
||||
- Показуйте реальні сценарії використання
|
||||
- Виділяйте потенційні проблеми
|
||||
|
||||
### Документація
|
||||
- Пояснюйте «чому», а не тільки «що»
|
||||
- Вказуйте передумови
|
||||
- Додавайте розділи з усунення проблем
|
||||
- Посилайтесь на пов'язані теми
|
||||
- Зберігайте дружність до початківців
|
||||
|
||||
### JSON/YAML
|
||||
- Використовуйте правильні відступи (2 або 4 пробіли послідовно)
|
||||
- Додавайте коментарі, що пояснюють конфігурацію
|
||||
- Включайте приклади валідації
|
||||
|
||||
### Діаграми
|
||||
- Використовуйте Mermaid коли можливо
|
||||
- Тримайте діаграми простими та читабельними
|
||||
- Додавайте текстовий опис під діаграмами для доступності
|
||||
- Посилайтесь на відповідні розділи
|
||||
|
||||
## Настанови для комітів
|
||||
|
||||
Дотримуйтесь формату conventional commits:
|
||||
```
|
||||
type(scope): description
|
||||
|
||||
[optional body]
|
||||
```
|
||||
|
||||
Типи:
|
||||
- `feat`: Нова функція або приклад
|
||||
- `fix`: Виправлення помилки
|
||||
- `docs`: Зміни документації
|
||||
- `refactor`: Реструктуризація коду
|
||||
- `style`: Зміни форматування
|
||||
- `test`: Додавання або зміни тестів
|
||||
- `chore`: Збірка, залежності тощо
|
||||
|
||||
Приклади:
|
||||
```
|
||||
feat(slash-commands): Add API documentation generator
|
||||
docs(memory): Improve personal preferences example
|
||||
fix(README): Correct table of contents link
|
||||
docs(skills): Add comprehensive code review skill
|
||||
```
|
||||
|
||||
## Перед відправкою
|
||||
|
||||
### Чеклист
|
||||
- [ ] Код відповідає стилю та конвенціям проєкту
|
||||
- [ ] Нові приклади включають чітку документацію
|
||||
- [ ] README файли оновлені (локальний та кореневий)
|
||||
- [ ] Немає чутливої інформації (API-ключі, облікові дані)
|
||||
- [ ] Приклади протестовані та працюють
|
||||
- [ ] Посилання перевірені та коректні
|
||||
- [ ] Файли мають правильні дозволи (скрипти виконувані)
|
||||
- [ ] Повідомлення коміту чітке та описове
|
||||
|
||||
### Локальне тестування
|
||||
```bash
|
||||
# Run all pre-commit checks (same checks as CI)
|
||||
pre-commit run --all-files
|
||||
|
||||
# Review your changes
|
||||
git diff
|
||||
```
|
||||
|
||||
## Процес Pull Request
|
||||
|
||||
1. **Створіть PR з чітким описом**:
|
||||
- Що додає/виправляє?
|
||||
- Чому це потрібно?
|
||||
- Пов'язані issues (якщо є)
|
||||
|
||||
2. **Включіть відповідні деталі**:
|
||||
- Нова функція? Включіть сценарії використання
|
||||
- Документація? Поясніть покращення
|
||||
- Приклади? Покажіть до/після
|
||||
|
||||
3. **Посилайтесь на issues**:
|
||||
- Використовуйте `Closes #123` для автоматичного закриття пов'язаних issues
|
||||
|
||||
4. **Будьте терплячими з рев'ю**:
|
||||
- Мейнтейнери можуть запропонувати покращення
|
||||
- Ітеруйте на основі зворотного зв'язку
|
||||
- Остаточне рішення за мейнтейнерами
|
||||
|
||||
## Процес код-рев'ю
|
||||
|
||||
Рецензенти перевірять:
|
||||
- **Точність**: Чи працює як описано?
|
||||
- **Якість**: Чи готово до продакшену?
|
||||
- **Консистентність**: Чи відповідає патернам проєкту?
|
||||
- **Документація**: Чи чітко та повно?
|
||||
- **Безпека**: Чи є вразливості?
|
||||
|
||||
## Повідомлення про проблеми
|
||||
|
||||
### Звіти про помилки
|
||||
Включіть:
|
||||
- Версію Claude Code
|
||||
- Операційну систему
|
||||
- Кроки для відтворення
|
||||
- Очікувану поведінку
|
||||
- Фактичну поведінку
|
||||
- Скріншоти (за потреби)
|
||||
|
||||
### Запити на функції
|
||||
Включіть:
|
||||
- Сценарій використання або проблему, що вирішується
|
||||
- Запропоноване рішення
|
||||
- Альтернативи, які ви розглядали
|
||||
- Додатковий контекст
|
||||
|
||||
### Проблеми з документацією
|
||||
Включіть:
|
||||
- Що незрозуміло або відсутнє
|
||||
- Запропоновані покращення
|
||||
- Приклади або посилання
|
||||
|
||||
## Політики проєкту
|
||||
|
||||
### Чутлива інформація
|
||||
- Ніколи не комітьте API-ключі, токени або облікові дані
|
||||
- Використовуйте значення-заповнювачі в прикладах
|
||||
- Включайте `.env.example` для конфігураційних файлів
|
||||
- Документуйте необхідні змінні оточення
|
||||
|
||||
### Якість коду
|
||||
- Тримайте приклади зосередженими та читабельними
|
||||
- Уникайте надмірного ускладнення рішень
|
||||
- Включайте коментарі для неочевидної логіки
|
||||
- Тестуйте ретельно перед відправкою
|
||||
|
||||
### Інтелектуальна власність
|
||||
- Оригінальний контент належить автору
|
||||
- Проєкт використовує освітню ліцензію
|
||||
- Поважайте існуючі авторські права
|
||||
- Вказуйте атрибуцію за потреби
|
||||
|
||||
## Отримання допомоги
|
||||
|
||||
- **Запитання**: Відкрийте обговорення в GitHub Issues
|
||||
- **Загальна допомога**: Перевірте існуючу документацію
|
||||
- **Допомога з розробки**: Перегляньте подібні приклади
|
||||
- **Код-рев'ю**: Позначте мейнтейнерів у PR
|
||||
|
||||
## Визнання
|
||||
|
||||
Контриб'юторів відзначають у:
|
||||
- Секції контриб'юторів README.md
|
||||
- Сторінці контриб'юторів GitHub
|
||||
- Історії комітів
|
||||
|
||||
## Безпека
|
||||
|
||||
При внесенні прикладів та документації, будь ласка, дотримуйтесь безпечних практик кодування:
|
||||
|
||||
- **Ніколи не захардкоджуйте секрети або API-ключі** — використовуйте змінні оточення
|
||||
- **Попереджайте про наслідки безпеки** — виділяйте потенційні ризики
|
||||
- **Використовуйте безпечні значення за замовчуванням** — увімкніть функції безпеки за замовчуванням
|
||||
- **Валідуйте введення** — показуйте правильну валідацію та санітизацію введення
|
||||
- **Включайте нотатки безпеки** — документуйте міркування безпеки
|
||||
|
||||
Щодо проблем безпеки, див. [SECURITY.md](SECURITY.md) для нашого процесу повідомлення про вразливості.
|
||||
|
||||
## Кодекс поведінки
|
||||
|
||||
Ми зобов'язуємося забезпечити привітну та інклюзивну спільноту. Будь ласка, прочитайте [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) для повних стандартів спільноти.
|
||||
|
||||
Коротко:
|
||||
- Будьте поважними та інклюзивними
|
||||
- Приймайте зворотний зв'язок з гідністю
|
||||
- Допомагайте іншим вчитися та зростати
|
||||
- Уникайте переслідувань або дискримінації
|
||||
- Повідомляйте про проблеми мейнтейнерам
|
||||
|
||||
Усі контриб'ютори повинні дотримуватися цього кодексу та ставитися одне до одного з добротою та повагою.
|
||||
|
||||
## Ліцензія
|
||||
|
||||
Роблячи внесок у цей проєкт, ви погоджуєтесь, що ваші внески будуть ліцензовані за ліцензією MIT. Деталі див. у файлі [LICENSE](LICENSE).
|
||||
|
||||
## Запитання?
|
||||
|
||||
- Перевірте [README](README.md)
|
||||
- Перегляньте [LEARNING-ROADMAP.md](LEARNING-ROADMAP.md)
|
||||
- Подивіться існуючі приклади
|
||||
- Відкрийте issue для обговорення
|
||||
|
||||
Дякуємо за ваш внесок! 🙏
|
||||
|
||||
---
|
||||
**Останнє оновлення**: Квітень 2026
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
<!-- i18n-source: SECURITY.md -->
|
||||
<!-- i18n-source-sha: 63a1416 -->
|
||||
<!-- i18n-date: 2026-04-09 -->
|
||||
|
||||
# Політика безпеки
|
||||
|
||||
## Огляд
|
||||
|
||||
Безпека проєкту Claude How To є важливою для нас. Цей документ описує наші практики безпеки та пояснює, як відповідально повідомляти про вразливості.
|
||||
|
||||
## Підтримувані версії
|
||||
|
||||
Ми надаємо оновлення безпеки для наступних версій:
|
||||
|
||||
| Версія | Статус | Підтримка до |
|
||||
|--------|--------|--------------|
|
||||
| Остання (main) | ✅ Активна | Поточна + 6 місяців |
|
||||
| Релізи 1.x | ✅ Активна | До наступної мажорної версії |
|
||||
|
||||
**Примітка**: Як освітній проєкт-довідник, ми зосереджуємося на підтримці актуальних найкращих практик та безпеки документації, а не на традиційній підтримці версій. Оновлення застосовуються безпосередньо до гілки main.
|
||||
|
||||
## Практики безпеки
|
||||
|
||||
### Безпека коду
|
||||
|
||||
1. **Управління залежностями**
|
||||
- Усі Python-залежності зафіксовані в `requirements.txt`
|
||||
- Регулярні оновлення через dependabot та ручний перегляд
|
||||
- Сканування безпеки за допомогою Bandit при кожному коміті
|
||||
- Pre-commit хуки для перевірок безпеки
|
||||
|
||||
2. **Якість коду**
|
||||
- Лінтинг за допомогою Ruff виявляє поширені проблеми
|
||||
- Перевірка типів за допомогою mypy запобігає вразливостям, пов'язаним з типами
|
||||
- Pre-commit хуки забезпечують дотримання стандартів
|
||||
- Усі зміни переглядаються перед злиттям
|
||||
|
||||
3. **Контроль доступу**
|
||||
- Захист гілки `main`
|
||||
- Обов'язковий рев'ю перед мерджем
|
||||
- Статусні перевірки повинні пройти перед мерджем
|
||||
- Обмежений доступ на запис до репозиторію
|
||||
|
||||
### Безпека документації
|
||||
|
||||
1. **Ніяких секретів у прикладах**
|
||||
- Усі API-ключі в прикладах є заповнювачами
|
||||
- Облікові дані ніколи не захардкоджені
|
||||
- Файли `.env.example` показують необхідні змінні
|
||||
- Чіткі попередження щодо управління секретами
|
||||
|
||||
2. **Найкращі практики безпеки**
|
||||
- Приклади демонструють безпечні патерни
|
||||
- Попередження безпеки виділені в документації
|
||||
- Посилання на офіційні гайди безпеки
|
||||
- Обробка облікових даних обговорюється у відповідних розділах
|
||||
|
||||
3. **Перегляд контенту**
|
||||
- Вся документація перевіряється на проблеми безпеки
|
||||
- Міркування безпеки в настановах для контриб'юторів
|
||||
- Валідація зовнішніх посилань та посилань
|
||||
|
||||
### Безпека залежностей
|
||||
|
||||
1. **Сканування**
|
||||
- Bandit сканує весь Python-код на вразливості
|
||||
- Перевірка вразливостей залежностей через GitHub security alerts
|
||||
- Регулярні ручні аудити безпеки
|
||||
|
||||
2. **Оновлення**
|
||||
- Патчі безпеки застосовуються оперативно
|
||||
- Мажорні версії оцінюються ретельно
|
||||
- Changelog включає оновлення, пов'язані з безпекою
|
||||
|
||||
3. **Прозорість**
|
||||
- Оновлення безпеки задокументовані в комітах
|
||||
- Розкриття вразливостей обробляється відповідально
|
||||
- Публічні рекомендації безпеки за потреби
|
||||
|
||||
## Повідомлення про вразливість
|
||||
|
||||
### Проблеми безпеки, які нас цікавлять
|
||||
|
||||
Ми цінуємо повідомлення про:
|
||||
- **Вразливості коду** в скриптах або прикладах
|
||||
- **Вразливості залежностей** у Python-пакетах
|
||||
- **Проблеми криптографії** в будь-яких прикладах коду
|
||||
- **Недоліки автентифікації/авторизації** в документації
|
||||
- **Ризики витоку даних** у прикладах конфігурації
|
||||
- **Вразливості ін'єкцій** (SQL, команди тощо)
|
||||
- **Проблеми SSRF/XXE/обхід шляхів**
|
||||
|
||||
### Проблеми безпеки поза межами
|
||||
|
||||
Це виходить за рамки цього проєкту:
|
||||
- Вразливості в самому Claude Code (повідомляйте Anthropic)
|
||||
- Проблеми із зовнішніми сервісами або бібліотеками (повідомляйте upstream)
|
||||
- Соціальна інженерія або навчання користувачів (не стосується цього довідника)
|
||||
- Теоретичні вразливості без підтвердження концепції
|
||||
- Вразливості в залежностях, повідомлені через офіційні канали
|
||||
|
||||
## Як повідомити
|
||||
|
||||
### Приватне повідомлення (рекомендовано)
|
||||
|
||||
**Для чутливих проблем безпеки використовуйте приватне повідомлення про вразливості GitHub:**
|
||||
|
||||
1. Перейдіть: https://github.com/luongnv89/claude-howto/security/advisories
|
||||
2. Натисніть "Report a vulnerability"
|
||||
3. Заповніть деталі вразливості
|
||||
4. Вкажіть:
|
||||
- Чіткий опис вразливості
|
||||
- Уражений компонент (файл, розділ, приклад)
|
||||
- Потенційний вплив
|
||||
- Кроки для відтворення (якщо можливо)
|
||||
- Запропоноване виправлення (якщо є)
|
||||
|
||||
**Що відбувається далі:**
|
||||
- Ми підтвердимо отримання протягом 48 годин
|
||||
- Ми дослідимо та оцінимо серйозність
|
||||
- Ми працюватимемо з вами над розробкою виправлення
|
||||
- Ми узгодимо графік розкриття
|
||||
- Ми вкажемо вас у рекомендації безпеки (якщо ви не бажаєте анонімності)
|
||||
|
||||
### Публічне повідомлення
|
||||
|
||||
Для нечутливих проблем або тих, що вже є публічними:
|
||||
|
||||
1. **Створіть GitHub Issue** з міткою `security`
|
||||
2. Вкажіть:
|
||||
- Назва: `[SECURITY]` з коротким описом
|
||||
- Детальний опис
|
||||
- Уражений файл або розділ
|
||||
- Потенційний вплив
|
||||
- Запропоноване виправлення
|
||||
|
||||
## Процес реагування на вразливості
|
||||
|
||||
### Оцінка (24 години)
|
||||
|
||||
1. Ми підтверджуємо отримання повідомлення
|
||||
2. Ми оцінюємо серйозність за [CVSS v3.1](https://www.first.org/cvss/v3.1/specification-document)
|
||||
3. Ми визначаємо, чи входить це в область застосування
|
||||
4. Ми зв'язуємося з вами з початковою оцінкою
|
||||
|
||||
### Розробка (1-7 днів)
|
||||
|
||||
1. Ми розробляємо виправлення
|
||||
2. Ми переглядаємо та тестуємо виправлення
|
||||
3. Ми створюємо рекомендацію безпеки
|
||||
4. Ми готуємо нотатки до випуску
|
||||
|
||||
### Розкриття (залежно від серйозності)
|
||||
|
||||
**Критична (CVSS 9.0-10.0)**
|
||||
- Виправлення випускається негайно
|
||||
- Публічна рекомендація видається
|
||||
- 24-годинне попереднє повідомлення репортерам
|
||||
|
||||
**Висока (CVSS 7.0-8.9)**
|
||||
- Виправлення випускається протягом 48-72 годин
|
||||
- 5-денне попереднє повідомлення репортерам
|
||||
- Публічна рекомендація при випуску
|
||||
|
||||
**Середня (CVSS 4.0-6.9)**
|
||||
- Виправлення випускається в наступному регулярному оновленні
|
||||
- Публічна рекомендація при випуску
|
||||
|
||||
**Низька (CVSS 0.1-3.9)**
|
||||
- Виправлення включається в наступне регулярне оновлення
|
||||
- Рекомендація при випуску
|
||||
|
||||
### Публікація
|
||||
|
||||
Ми публікуємо рекомендації безпеки, що включають:
|
||||
- Опис вразливості
|
||||
- Уражені компоненти
|
||||
- Оцінку серйозності (CVSS-бал)
|
||||
- Версію виправлення
|
||||
- Обхідні рішення (якщо є)
|
||||
- Подяку репортеру (з дозволу)
|
||||
|
||||
## Найкращі практики для репортерів
|
||||
|
||||
### Перед повідомленням
|
||||
|
||||
- **Перевірте проблему**: Чи можете ви відтворити її стабільно?
|
||||
- **Шукайте існуючі issues**: Чи вже повідомлено про це?
|
||||
- **Перевірте документацію**: Чи є настанови щодо безпечного використання?
|
||||
- **Тестуйте виправлення**: Чи працює ваше запропоноване виправлення?
|
||||
|
||||
### При повідомленні
|
||||
|
||||
- **Будьте конкретними**: Вказуйте точні шляхи файлів та номери рядків
|
||||
- **Додавайте контекст**: Чому це проблема безпеки?
|
||||
- **Покажіть вплив**: Що міг би зробити зловмисник?
|
||||
- **Надайте кроки**: Як ми можемо відтворити?
|
||||
- **Запропонуйте виправлення**: Як би ви це виправили?
|
||||
|
||||
### Після повідомлення
|
||||
|
||||
- **Будьте терплячими**: Ми маємо обмежені ресурси
|
||||
- **Будьте на зв'язку**: Відповідайте на подальші запитання швидко
|
||||
- **Зберігайте конфіденційність**: Не розголошуйте публічно до виправлення
|
||||
- **Дотримуйтесь координації**: Слідуйте нашому графіку розкриття
|
||||
|
||||
## Заголовки безпеки та конфігурація
|
||||
|
||||
### Безпека репозиторію
|
||||
|
||||
- **Захист гілок**: Основна гілка потребує 2 схвалень для змін
|
||||
- **Статусні перевірки**: Усі CI/CD перевірки повинні пройти
|
||||
- **CODEOWNERS**: Призначені рецензенти для ключових файлів
|
||||
- **Підписані коміти**: Рекомендовано для контриб'юторів
|
||||
|
||||
### Безпека розробки
|
||||
|
||||
```bash
|
||||
# Install pre-commit hooks
|
||||
pre-commit install
|
||||
|
||||
# Run security scans locally
|
||||
bandit -c pyproject.toml -r scripts/
|
||||
mypy scripts/ --ignore-missing-imports
|
||||
ruff check scripts/
|
||||
```
|
||||
|
||||
### Безпека залежностей
|
||||
|
||||
```bash
|
||||
# Check for known vulnerabilities
|
||||
pip install safety
|
||||
safety check
|
||||
|
||||
# Or use pip-audit
|
||||
pip install pip-audit
|
||||
pip-audit
|
||||
```
|
||||
|
||||
## Настанови безпеки для контриб'юторів
|
||||
|
||||
### При написанні прикладів
|
||||
|
||||
1. **Ніколи не захардкоджуйте секрети**
|
||||
```python
|
||||
# ❌ Bad
|
||||
api_key = "sk-1234567890"
|
||||
|
||||
# ✅ Good
|
||||
api_key = os.getenv("API_KEY")
|
||||
```
|
||||
|
||||
2. **Попереджайте про наслідки безпеки**
|
||||
```markdown
|
||||
⚠️ **Security Note**: Never commit `.env` files to git.
|
||||
Add to `.gitignore` immediately.
|
||||
```
|
||||
|
||||
3. **Використовуйте безпечні значення за замовчуванням**
|
||||
- Увімкнення автентифікації за замовчуванням
|
||||
- Використання HTTPS де можливо
|
||||
- Валідація та санітизація введення
|
||||
- Використання параметризованих запитів
|
||||
|
||||
4. **Документуйте міркування безпеки**
|
||||
- Пояснюйте, чому безпека важлива
|
||||
- Показуйте безпечні vs. небезпечні патерни
|
||||
- Посилайтесь на авторитетні джерела
|
||||
- Розміщуйте попередження на видному місці
|
||||
|
||||
### При перегляді внесків
|
||||
|
||||
1. **Перевіряйте на відкриті секрети**
|
||||
- Сканування на поширені патерни (api_key=, password=)
|
||||
- Перегляд конфігураційних файлів
|
||||
- Перевірка змінних оточення
|
||||
|
||||
2. **Перевіряйте безпечні практики кодування**
|
||||
- Відсутність захардкоджених облікових даних
|
||||
- Правильна валідація введення
|
||||
- Безпечна автентифікація/авторизація
|
||||
- Безпечна робота з файлами
|
||||
|
||||
3. **Тестуйте наслідки безпеки**
|
||||
- Чи можна це зловживати?
|
||||
- Який найгірший сценарій?
|
||||
- Чи є граничні випадки?
|
||||
|
||||
## Ресурси безпеки
|
||||
|
||||
### Офіційні стандарти
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [CWE Top 25](https://cwe.mitre.org/top25/)
|
||||
- [CVSS Calculator](https://www.first.org/cvss/calculator/3.1)
|
||||
|
||||
### Безпека Python
|
||||
- [Python Security Advisories](https://www.python.org/dev/security/)
|
||||
- [PyPI Security](https://pypi.org/help/#security)
|
||||
- [Bandit Documentation](https://bandit.readthedocs.io/)
|
||||
|
||||
### Управління залежностями
|
||||
- [OWASP Dependency Check](https://owasp.org/www-project-dependency-check/)
|
||||
- [GitHub Security Alerts](https://docs.github.com/en/code-security/dependabot/dependabot-alerts/about-dependabot-alerts)
|
||||
|
||||
### Загальна безпека
|
||||
- [Anthropic Security](https://www.anthropic.com/)
|
||||
- [GitHub Security Best Practices](https://docs.github.com/en/code-security)
|
||||
|
||||
## Архів рекомендацій безпеки
|
||||
|
||||
Минулі рекомендації безпеки доступні на вкладці [GitHub Security Advisories](https://github.com/luongnv89/claude-howto/security/advisories).
|
||||
|
||||
## Контакти
|
||||
|
||||
Для запитань, пов'язаних з безпекою, або для обговорення практик безпеки:
|
||||
|
||||
1. **Приватне повідомлення про безпеку**: Використовуйте приватне повідомлення про вразливості GitHub
|
||||
2. **Загальні питання безпеки**: Відкрийте обговорення з тегом `[SECURITY]`
|
||||
3. **Відгуки про політику безпеки**: Створіть issue з міткою `security`
|
||||
|
||||
## Подяки
|
||||
|
||||
Ми цінуємо дослідників безпеки та учасників спільноти, які допомагають підтримувати безпеку цього проєкту. Контриб'юторів, які відповідально повідомляють про вразливості, буде відзначено в наших рекомендаціях безпеки (якщо вони не бажають анонімності).
|
||||
|
||||
## Оновлення політики
|
||||
|
||||
Ця політика безпеки переглядається та оновлюється:
|
||||
- При виявленні нових вразливостей
|
||||
- При розвитку найкращих практик безпеки
|
||||
- При зміні обсягу проєкту
|
||||
- Щорічно як мінімум
|
||||
|
||||
**Останнє оновлення**: Квітень 2026
|
||||
**Наступний перегляд**: Квітень 2027
|
||||
|
||||
---
|
||||
|
||||
Дякуємо за допомогу в забезпеченні безпеки Claude How To! 🔒
|
||||
@@ -0,0 +1,637 @@
|
||||
<!-- i18n-source: STYLE_GUIDE.md -->
|
||||
<!-- i18n-source-sha: 63a1416 -->
|
||||
<!-- i18n-date: 2026-04-09 -->
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="resources/logos/claude-howto-logo-dark.svg">
|
||||
<img alt="Claude How To" src="resources/logos/claude-howto-logo.svg">
|
||||
</picture>
|
||||
|
||||
# Гайд зі стилю
|
||||
|
||||
> Конвенції та правила форматування для внеску в Claude How To. Дотримуйтесь цього гайду, щоб тримати контент консистентним, професійним та зручним для підтримки.
|
||||
|
||||
---
|
||||
|
||||
## Зміст
|
||||
|
||||
- [Іменування файлів та каталогів](#іменування-файлів-та-каталогів)
|
||||
- [Структура документа](#структура-документа)
|
||||
- [Заголовки](#заголовки)
|
||||
- [Форматування тексту](#форматування-тексту)
|
||||
- [Списки](#списки)
|
||||
- [Таблиці](#таблиці)
|
||||
- [Блоки коду](#блоки-коду)
|
||||
- [Посилання та перехресні посилання](#посилання-та-перехресні-посилання)
|
||||
- [Діаграми](#діаграми)
|
||||
- [Використання емодзі](#використання-емодзі)
|
||||
- [YAML Frontmatter](#yaml-frontmatter)
|
||||
- [Зображення та медіа](#зображення-та-медіа)
|
||||
- [Тон та голос](#тон-та-голос)
|
||||
- [Повідомлення комітів](#повідомлення-комітів)
|
||||
- [Чеклист для авторів](#чеклист-для-авторів)
|
||||
|
||||
---
|
||||
|
||||
## Іменування файлів та каталогів
|
||||
|
||||
### Каталоги уроків
|
||||
|
||||
Каталоги уроків використовують **двозначний числовий префікс** з **kebab-case** описом:
|
||||
|
||||
```
|
||||
01-slash-commands/
|
||||
02-memory/
|
||||
03-skills/
|
||||
04-subagents/
|
||||
05-mcp/
|
||||
```
|
||||
|
||||
Номер відображає порядок навчального шляху від початківця до просунутого.
|
||||
|
||||
### Імена файлів
|
||||
|
||||
| Тип | Конвенція | Приклади |
|
||||
|-----|-----------|---------|
|
||||
| **README уроку** | `README.md` | `01-slash-commands/README.md` |
|
||||
| **Файл функції** | Kebab-case `.md` | `code-reviewer.md`, `generate-api-docs.md` |
|
||||
| **Shell-скрипт** | Kebab-case `.sh` | `format-code.sh`, `validate-input.sh` |
|
||||
| **Конфіг-файл** | Стандартні назви | `.mcp.json`, `settings.json` |
|
||||
| **Файл пам'яті** | З префіксом области | `project-CLAUDE.md`, `personal-CLAUDE.md` |
|
||||
| **Документи верхнього рівня** | UPPER_CASE `.md` | `CATALOG.md`, `QUICK_REFERENCE.md`, `CONTRIBUTING.md` |
|
||||
| **Зображення** | Kebab-case | `pr-slash-command.png`, `claude-howto-logo.svg` |
|
||||
|
||||
### Правила
|
||||
|
||||
- Використовуйте **нижній регістр** для всіх імен файлів та каталогів (крім документів верхнього рівня як `README.md`, `CATALOG.md`)
|
||||
- Використовуйте **дефіси** (`-`) як розділювачі слів, ніколи підкреслення або пробіли
|
||||
- Тримайте назви описовими, але стислими
|
||||
|
||||
---
|
||||
|
||||
## Структура документа
|
||||
|
||||
### Кореневий README
|
||||
|
||||
Кореневий `README.md` дотримується порядку:
|
||||
|
||||
1. Логотип (елемент `<picture>` з варіантами dark/light)
|
||||
2. Заголовок H1
|
||||
3. Вступна цитата (однорядкова ціннісна пропозиція)
|
||||
4. Секція "Чому цей довідник?" з таблицею порівняння
|
||||
5. Горизонтальна лінія (`---`)
|
||||
6. Зміст
|
||||
7. Каталог функцій
|
||||
8. Швидка навігація
|
||||
9. Навчальний шлях
|
||||
10. Секції функцій
|
||||
11. Початок роботи
|
||||
12. Найкращі практики / Усунення проблем
|
||||
13. Внесок / Ліцензія
|
||||
|
||||
### README уроку
|
||||
|
||||
Кожен `README.md` уроку дотримується порядку:
|
||||
|
||||
1. Заголовок H1 (напр., `# Slash Commands`)
|
||||
2. Короткий вступний абзац
|
||||
3. Таблиця швидкого довідника (опціонально)
|
||||
4. Архітектурна діаграма (Mermaid)
|
||||
5. Детальні секції (H2)
|
||||
6. Практичні приклади (нумеровані, 4-6 прикладів)
|
||||
7. Найкращі практики (таблиці Do's and Don'ts)
|
||||
8. Усунення проблем
|
||||
9. Пов'язані гайди / Офіційна документація
|
||||
10. Метадані документа у футері
|
||||
|
||||
### Файл функції/прикладу
|
||||
|
||||
Окремі файли функцій (напр., `optimize.md`, `pr.md`):
|
||||
|
||||
1. YAML frontmatter (якщо потрібно)
|
||||
2. Заголовок H1
|
||||
3. Призначення / опис
|
||||
4. Інструкції з використання
|
||||
5. Приклади коду
|
||||
6. Поради з налаштування
|
||||
|
||||
### Розділювачі секцій
|
||||
|
||||
Використовуйте горизонтальні лінії (`---`) для відділення основних регіонів документа:
|
||||
|
||||
```markdown
|
||||
---
|
||||
|
||||
## Нова основна секція
|
||||
```
|
||||
|
||||
Розміщуйте їх після вступної цитати та між логічно окремими частинами документа.
|
||||
|
||||
---
|
||||
|
||||
## Заголовки
|
||||
|
||||
### Ієрархія
|
||||
|
||||
| Рівень | Використання | Приклад |
|
||||
|--------|-------------|---------|
|
||||
| `#` H1 | Заголовок сторінки (один на документ) | `# Slash Commands` |
|
||||
| `##` H2 | Основні секції | `## Best Practices` |
|
||||
| `###` H3 | Підсекції | `### Adding a Skill` |
|
||||
| `####` H4 | Під-підсекції (рідко) | `#### Configuration Options` |
|
||||
|
||||
### Правила
|
||||
|
||||
- **Один H1 на документ** — лише заголовок сторінки
|
||||
- **Ніколи не пропускайте рівні** — не стрибайте з H2 на H4
|
||||
- **Тримайте заголовки стислими** — прагніть до 2-5 слів
|
||||
- **Використовуйте sentence case** — великі літери тільки для першого слова та власних назв (виняток: назви функцій залишаються як є)
|
||||
- **Додавайте емодзі-префікси тільки в кореневому README** для заголовків секцій (див. [Використання емодзі](#використання-емодзі))
|
||||
|
||||
---
|
||||
|
||||
## Форматування тексту
|
||||
|
||||
### Виділення
|
||||
|
||||
| Стиль | Коли використовувати | Приклад |
|
||||
|-------|---------------------|---------|
|
||||
| **Жирний** (`**text**`) | Ключові терміни, мітки в таблицях, важливі поняття | `**Installation**:` |
|
||||
| *Курсив* (`*text*`) | Перше згадування технічного терміну, назви книг/документів | `*frontmatter*` |
|
||||
| `Код` (`` `text` ``) | Імена файлів, команди, значення конфігурації, посилання на код | `` `CLAUDE.md` `` |
|
||||
|
||||
### Цитати для виносок
|
||||
|
||||
Використовуйте цитати з жирними префіксами для важливих нотаток:
|
||||
|
||||
```markdown
|
||||
> **Note**: Custom slash commands have been merged into skills since v2.0.
|
||||
|
||||
> **Important**: Never commit API keys or credentials.
|
||||
|
||||
> **Tip**: Combine memory with skills for maximum effectiveness.
|
||||
```
|
||||
|
||||
Підтримувані типи виносок: **Note**, **Important**, **Tip**, **Warning**.
|
||||
|
||||
### Абзаци
|
||||
|
||||
- Тримайте абзаци короткими (2-4 речення)
|
||||
- Додавайте порожній рядок між абзацами
|
||||
- Починайте з ключового моменту, потім надавайте контекст
|
||||
- Пояснюйте «чому», а не тільки «що»
|
||||
|
||||
---
|
||||
|
||||
## Списки
|
||||
|
||||
### Ненумеровані списки
|
||||
|
||||
Використовуйте дефіси (`-`) з 2-пробіловим відступом для вкладеності:
|
||||
|
||||
```markdown
|
||||
- First item
|
||||
- Second item
|
||||
- Nested item
|
||||
- Another nested item
|
||||
- Deep nested (avoid going deeper than 3 levels)
|
||||
- Third item
|
||||
```
|
||||
|
||||
### Нумеровані списки
|
||||
|
||||
Використовуйте нумеровані списки для послідовних кроків, інструкцій та ранжованих елементів:
|
||||
|
||||
```markdown
|
||||
1. First step
|
||||
2. Second step
|
||||
- Sub-point detail
|
||||
- Another sub-point
|
||||
3. Third step
|
||||
```
|
||||
|
||||
### Описові списки
|
||||
|
||||
Використовуйте жирні мітки для списків ключ-значення:
|
||||
|
||||
```markdown
|
||||
- **Performance bottlenecks** - identify O(n^2) operations, inefficient loops
|
||||
- **Memory leaks** - find unreleased resources, circular references
|
||||
- **Algorithm improvements** - suggest better algorithms or data structures
|
||||
```
|
||||
|
||||
### Правила
|
||||
|
||||
- Підтримуйте консистентні відступи (2 пробіли на рівень)
|
||||
- Додавайте порожній рядок перед та після списку
|
||||
- Тримайте елементи списку паралельними за структурою
|
||||
- Уникайте вкладеності глибше 3 рівнів
|
||||
|
||||
---
|
||||
|
||||
## Таблиці
|
||||
|
||||
### Стандартний формат
|
||||
|
||||
```markdown
|
||||
| Column 1 | Column 2 | Column 3 |
|
||||
|----------|----------|----------|
|
||||
| Data | Data | Data |
|
||||
```
|
||||
|
||||
### Поширені патерни таблиць
|
||||
|
||||
**Порівняння функцій (3-4 колонки):**
|
||||
|
||||
```markdown
|
||||
| Feature | Invocation | Persistence | Best For |
|
||||
|---------|-----------|------------|----------|
|
||||
| **Slash Commands** | Manual (`/cmd`) | Session only | Quick shortcuts |
|
||||
| **Memory** | Auto-loaded | Cross-session | Long-term learning |
|
||||
```
|
||||
|
||||
**Do's and Don'ts:**
|
||||
|
||||
```markdown
|
||||
| Do | Don't |
|
||||
|----|-------|
|
||||
| Use descriptive names | Use vague names |
|
||||
| Keep files focused | Overload a single file |
|
||||
```
|
||||
|
||||
**Швидкий довідник:**
|
||||
|
||||
```markdown
|
||||
| Aspect | Details |
|
||||
|--------|---------|
|
||||
| **Purpose** | Generate API documentation |
|
||||
| **Scope** | Project-level |
|
||||
| **Complexity** | Intermediate |
|
||||
```
|
||||
|
||||
### Правила
|
||||
|
||||
- **Жирний для заголовків таблиці**, коли вони є мітками рядків (перша колонка)
|
||||
- Вирівнюйте пайпи для читабельності у вихідному коді (опціонально, але бажано)
|
||||
- Тримайте вміст комірок стислим; використовуйте посилання для деталей
|
||||
- Використовуйте `форматування коду` для команд та шляхів файлів у комірках
|
||||
|
||||
---
|
||||
|
||||
## Блоки коду
|
||||
|
||||
### Теги мов
|
||||
|
||||
Завжди вказуйте тег мови для підсвічування синтаксису:
|
||||
|
||||
| Мова | Тег | Використання |
|
||||
|------|-----|-------------|
|
||||
| Shell | `bash` | Команди CLI, скрипти |
|
||||
| Python | `python` | Код Python |
|
||||
| JavaScript | `javascript` | Код JS |
|
||||
| TypeScript | `typescript` | Код TS |
|
||||
| JSON | `json` | Конфігураційні файли |
|
||||
| YAML | `yaml` | Frontmatter, конфіг |
|
||||
| Markdown | `markdown` | Приклади Markdown |
|
||||
| SQL | `sql` | Запити до бази даних |
|
||||
| Звичайний текст | (без тегу) | Очікуваний вивід, дерева каталогів |
|
||||
|
||||
### Конвенції
|
||||
|
||||
```bash
|
||||
# Comment explaining what the command does
|
||||
claude mcp add notion --transport http https://mcp.notion.com/mcp
|
||||
```
|
||||
|
||||
- Додавайте **рядок коментаря** перед неочевидними командами
|
||||
- Робіть всі приклади **готовими до копіювання та вставки**
|
||||
- Показуйте **і прості, і просунуті** версії де доречно
|
||||
- Включайте **очікуваний вивід** коли це допомагає розумінню (використовуйте блок коду без тегу)
|
||||
|
||||
### Блоки встановлення
|
||||
|
||||
Використовуйте цей патерн для інструкцій з встановлення:
|
||||
|
||||
```bash
|
||||
# Copy files to your project
|
||||
cp 01-slash-commands/*.md .claude/commands/
|
||||
```
|
||||
|
||||
### Багатокрокові робочі процеси
|
||||
|
||||
```bash
|
||||
# Step 1: Create the directory
|
||||
mkdir -p .claude/commands
|
||||
|
||||
# Step 2: Copy the templates
|
||||
cp 01-slash-commands/*.md .claude/commands/
|
||||
|
||||
# Step 3: Verify installation
|
||||
ls .claude/commands/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Посилання та перехресні посилання
|
||||
|
||||
### Внутрішні посилання (відносні)
|
||||
|
||||
Використовуйте відносні шляхи для всіх внутрішніх посилань:
|
||||
|
||||
```markdown
|
||||
[Slash Commands](01-slash-commands/)
|
||||
[Skills Guide](03-skills/)
|
||||
[Memory Architecture](02-memory/#memory-architecture)
|
||||
```
|
||||
|
||||
З каталогу уроку назад до кореня або сусіда:
|
||||
|
||||
```markdown
|
||||
[Back to main guide](../README.md)
|
||||
[Related: Skills](../03-skills/)
|
||||
```
|
||||
|
||||
### Зовнішні посилання (абсолютні)
|
||||
|
||||
Використовуйте повні URL з описовим текстом якоря:
|
||||
|
||||
```markdown
|
||||
[Anthropic's official documentation](https://code.claude.com/docs/en/overview)
|
||||
```
|
||||
|
||||
- Ніколи не використовуйте "click here" або "this link" як текст якоря
|
||||
- Використовуйте описовий текст, що має сенс поза контекстом
|
||||
|
||||
### Якорі секцій
|
||||
|
||||
Посилайтесь на секції в тому ж документі за допомогою GitHub-style якорів:
|
||||
|
||||
```markdown
|
||||
[Feature Catalog](#-feature-catalog)
|
||||
[Best Practices](#best-practices)
|
||||
```
|
||||
|
||||
### Патерн пов'язаних гайдів
|
||||
|
||||
Завершуйте уроки секцією пов'язаних гайдів:
|
||||
|
||||
```markdown
|
||||
## Related Guides
|
||||
|
||||
- [Slash Commands](../01-slash-commands/) - Quick shortcuts
|
||||
- [Memory](../02-memory/) - Persistent context
|
||||
- [Skills](../03-skills/) - Reusable capabilities
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Діаграми
|
||||
|
||||
### Mermaid
|
||||
|
||||
Використовуйте Mermaid для всіх діаграм. Підтримувані типи:
|
||||
|
||||
- `graph TB` / `graph LR` — архітектура, ієрархія, потік
|
||||
- `sequenceDiagram` — потоки взаємодії
|
||||
- `timeline` — хронологічні послідовності
|
||||
|
||||
### Стильові конвенції
|
||||
|
||||
Застосовуйте консистентні кольори за допомогою блоків стилів:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
A["Component A"] --> B["Component B"]
|
||||
B --> C["Component C"]
|
||||
|
||||
style A fill:#e1f5fe,stroke:#333,color:#333
|
||||
style B fill:#fce4ec,stroke:#333,color:#333
|
||||
style C fill:#e8f5e9,stroke:#333,color:#333
|
||||
```
|
||||
|
||||
**Палітра кольорів:**
|
||||
|
||||
| Колір | Hex | Використання |
|
||||
|-------|-----|-------------|
|
||||
| Світло-блакитний | `#e1f5fe` | Основні компоненти, входи |
|
||||
| Світло-рожевий | `#fce4ec` | Обробка, проміжне ПЗ |
|
||||
| Світло-зелений | `#e8f5e9` | Виходи, результати |
|
||||
| Світло-жовтий | `#fff9c4` | Конфігурація, опціональне |
|
||||
| Світло-фіолетовий | `#f3e5f5` | Користувацький інтерфейс |
|
||||
|
||||
### Правила
|
||||
|
||||
- Використовуйте `["Label text"]` для міток вузлів (дозволяє спеціальні символи)
|
||||
- Використовуйте `<br/>` для переносу рядків у мітках
|
||||
- Тримайте діаграми простими (максимум 10-12 вузлів)
|
||||
- Додавайте короткий текстовий опис під діаграмою для доступності
|
||||
- Використовуйте зверху вниз (`TB`) для ієрархій, зліва направо (`LR`) для робочих процесів
|
||||
|
||||
---
|
||||
|
||||
## Використання емодзі
|
||||
|
||||
### Де використовуються емодзі
|
||||
|
||||
Емодзі використовуються **рідко та цілеспрямовано** — тільки в певних контекстах:
|
||||
|
||||
| Контекст | Емодзі | Приклад |
|
||||
|----------|--------|---------|
|
||||
| Заголовки секцій кореневого README | Іконки категорій | `## 📚 Learning Path` |
|
||||
| Індикатори рівня навичок | Кольорові кола | 🟢 Початківець, 🔵 Середній, 🔴 Просунутий |
|
||||
| Do's and Don'ts | Галочки/хрестики | ✅ Робіть це, ❌ Не робіть це |
|
||||
| Рейтинги складності | Зірки | ⭐⭐⭐ |
|
||||
|
||||
### Стандартний набір емодзі
|
||||
|
||||
| Емодзі | Значення |
|
||||
|--------|---------|
|
||||
| 📚 | Навчання, гайди, документація |
|
||||
| ⚡ | Початок роботи, швидкий довідник |
|
||||
| 🎯 | Функції, швидкий довідник |
|
||||
| 🎓 | Навчальні шляхи |
|
||||
| 📊 | Статистика, порівняння |
|
||||
| 🚀 | Встановлення, швидкі команди |
|
||||
| 🟢 | Рівень початківця |
|
||||
| 🔵 | Середній рівень |
|
||||
| 🔴 | Просунутий рівень |
|
||||
| ✅ | Рекомендована практика |
|
||||
| ❌ | Уникати / анти-патерн |
|
||||
| ⭐ | Одиниця рейтингу складності |
|
||||
|
||||
### Правила
|
||||
|
||||
- **Ніколи не використовуйте емодзі в основному тексті** або абзацах
|
||||
- **Використовуйте емодзі в заголовках тільки** в кореневому README (не в README уроків)
|
||||
- **Не додавайте декоративних емодзі** — кожне емодзі повинно нести значення
|
||||
- Тримайте використання емодзі консистентним з таблицею вище
|
||||
|
||||
---
|
||||
|
||||
## YAML Frontmatter
|
||||
|
||||
### Файли функцій (навички, команди, агенти)
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: unique-identifier
|
||||
description: What this feature does and when to use it
|
||||
allowed-tools: Bash, Read, Grep
|
||||
---
|
||||
```
|
||||
|
||||
### Опціональні поля
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: my-feature
|
||||
description: Brief description
|
||||
argument-hint: "[file-path] [options]"
|
||||
allowed-tools: Bash, Read, Grep, Write, Edit
|
||||
model: opus # opus, sonnet, or haiku
|
||||
disable-model-invocation: true # User-only invocation
|
||||
user-invocable: false # Hidden from user menu
|
||||
context: fork # Run in isolated subagent
|
||||
agent: Explore # Agent type for context: fork
|
||||
---
|
||||
```
|
||||
|
||||
### Правила
|
||||
|
||||
- Розміщуйте frontmatter на самому початку файлу
|
||||
- Використовуйте **kebab-case** для поля `name`
|
||||
- Тримайте `description` в одне речення
|
||||
- Включайте тільки необхідні поля
|
||||
|
||||
---
|
||||
|
||||
## Зображення та медіа
|
||||
|
||||
### Патерн логотипу
|
||||
|
||||
Усі документи, що починаються з логотипу, використовують елемент `<picture>` для підтримки dark/light режимів:
|
||||
|
||||
```html
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="resources/logos/claude-howto-logo-dark.svg">
|
||||
<img alt="Claude How To" src="resources/logos/claude-howto-logo.svg">
|
||||
</picture>
|
||||
```
|
||||
|
||||
### Скріншоти
|
||||
|
||||
- Зберігайте у відповідному каталозі уроку (напр., `01-slash-commands/pr-slash-command.png`)
|
||||
- Використовуйте kebab-case імена файлів
|
||||
- Включайте описовий alt-текст
|
||||
- Віддавайте перевагу SVG для діаграм, PNG для скріншотів
|
||||
|
||||
### Правила
|
||||
|
||||
- Завжди вказуйте alt-текст для зображень
|
||||
- Тримайте розмір файлів зображень розумним (< 500KB для PNG)
|
||||
- Використовуйте відносні шляхи для посилань на зображення
|
||||
- Зберігайте зображення в тому ж каталозі, що й документ, який на них посилається, або в `assets/` для спільних зображень
|
||||
|
||||
---
|
||||
|
||||
## Тон та голос
|
||||
|
||||
### Стиль написання
|
||||
|
||||
- **Професійний, але доступний** — технічна точність без перевантаження жаргоном
|
||||
- **Активний стан** — "Create a file", а не "A file should be created"
|
||||
- **Прямі інструкції** — "Run this command", а не "You might want to run this command"
|
||||
- **Дружній до початківців** — припускаємо, що читач новий у Claude Code, але не новий у програмуванні
|
||||
|
||||
### Принципи контенту
|
||||
|
||||
| Принцип | Приклад |
|
||||
|---------|---------|
|
||||
| **Показуйте, а не розповідайте** | Надавайте працюючі приклади, а не абстрактні описи |
|
||||
| **Прогресивна складність** | Починайте просто, додавайте глибину в наступних секціях |
|
||||
| **Пояснюйте «чому»** | "Use memory for... because...", а не просто "Use memory for..." |
|
||||
| **Готове до копіювання** | Кожен блок коду повинен працювати при безпосередній вставці |
|
||||
| **Реальний контекст** | Використовуйте практичні сценарії, а не штучні приклади |
|
||||
|
||||
### Словник
|
||||
|
||||
- Використовуйте "Claude Code" (не "Claude CLI" або "the tool")
|
||||
- Використовуйте "skill" (не "custom command" — застарілий термін)
|
||||
- Використовуйте "lesson" або "guide" для нумерованих секцій
|
||||
- Використовуйте "example" для окремих файлів функцій
|
||||
|
||||
---
|
||||
|
||||
## Повідомлення комітів
|
||||
|
||||
Дотримуйтесь [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
type(scope): description
|
||||
```
|
||||
|
||||
### Типи
|
||||
|
||||
| Тип | Використання |
|
||||
|-----|-------------|
|
||||
| `feat` | Нова функція, приклад або гайд |
|
||||
| `fix` | Виправлення помилки, корекція, зламане посилання |
|
||||
| `docs` | Покращення документації |
|
||||
| `refactor` | Реструктуризація без зміни поведінки |
|
||||
| `style` | Тільки зміни форматування |
|
||||
| `test` | Додавання або зміни тестів |
|
||||
| `chore` | Збірка, залежності, CI |
|
||||
|
||||
### Скоупи
|
||||
|
||||
Використовуйте назву уроку або області файлу як скоуп:
|
||||
|
||||
```
|
||||
feat(slash-commands): Add API documentation generator
|
||||
docs(memory): Improve personal preferences example
|
||||
fix(README): Correct table of contents link
|
||||
docs(skills): Add comprehensive code review skill
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Метадані документа у футері
|
||||
|
||||
README уроків завершуються блоком метаданих:
|
||||
|
||||
```markdown
|
||||
---
|
||||
**Last Updated**: March 2026
|
||||
**Claude Code Version**: 2.1.97
|
||||
**Compatible Models**: Claude Sonnet 4.6, Claude Opus 4.6, Claude Haiku 4.5
|
||||
```
|
||||
|
||||
- Використовуйте формат місяць + рік (напр., "March 2026")
|
||||
- Оновлюйте версію при зміні функцій
|
||||
- Перелічуйте всі сумісні моделі
|
||||
|
||||
---
|
||||
|
||||
## Чеклист для авторів
|
||||
|
||||
Перед відправкою контенту перевірте:
|
||||
|
||||
- [ ] Імена файлів/каталогів використовують kebab-case
|
||||
- [ ] Документ починається з заголовку H1 (один на файл)
|
||||
- [ ] Ієрархія заголовків коректна (без пропущених рівнів)
|
||||
- [ ] Усі блоки коду мають теги мов
|
||||
- [ ] Приклади коду готові до копіювання та вставки
|
||||
- [ ] Внутрішні посилання використовують відносні шляхи
|
||||
- [ ] Зовнішні посилання мають описовий текст якоря
|
||||
- [ ] Таблиці правильно відформатовані
|
||||
- [ ] Емодзі відповідають стандартному набору (якщо використовуються)
|
||||
- [ ] Mermaid-діаграми використовують стандартну палітру кольорів
|
||||
- [ ] Немає чутливої інформації (API-ключі, облікові дані)
|
||||
- [ ] YAML frontmatter валідний (якщо використовується)
|
||||
- [ ] Зображення мають alt-текст
|
||||
- [ ] Абзаци короткі та зосереджені
|
||||
- [ ] Секція пов'язаних гайдів посилається на відповідні уроки
|
||||
- [ ] Повідомлення коміту відповідає формату conventional commits
|
||||
|
||||
---
|
||||
**Останнє оновлення**: Квітень 2026
|
||||
+13
-13
@@ -2,9 +2,9 @@
|
||||
|
||||
# Прогрес перекладу українською
|
||||
|
||||
**Загальний прогрес:** 5/67 файлів (7%)
|
||||
**Загальний прогрес:** 15/67 файлів (22%)
|
||||
|
||||
**Статус:** 🚧 В ПРОЦЕСІ — P1 ядро завершено ✅
|
||||
**Статус:** 🚧 В ПРОЦЕСІ — P1 ядро ✅ P2 модулі ✅
|
||||
|
||||
## Пріоритет 1 — Ядро (5 файлів) ✅
|
||||
|
||||
@@ -22,18 +22,18 @@
|
||||
|
||||
| Модуль | Рядків | Статус | Дата |
|
||||
|--------|--------|--------|------|
|
||||
| 01 slash-commands | 565 | ⏳ | |
|
||||
| 02 memory | 1156 | ⏳ | |
|
||||
| 03 skills | 811 | ⏳ | |
|
||||
| 04 subagents | 1142 | ⏳ | |
|
||||
| 05 mcp | 1113 | ⏳ | |
|
||||
| 06 hooks | 1170 | ⏳ | |
|
||||
| 07 plugins | 949 | ⏳ | |
|
||||
| 08 checkpoints | 320 | ⏳ | |
|
||||
| 09 advanced | 1945 | ⏳ | |
|
||||
| 10 cli | 837 | ⏳ | |
|
||||
| 01 slash-commands | 565 | ✅ | 2026-04-09 |
|
||||
| 02 memory | 1156 | ✅ | 2026-04-09 |
|
||||
| 03 skills | 811 | ✅ | 2026-04-09 |
|
||||
| 04 subagents | 1142 | ✅ | 2026-04-09 |
|
||||
| 05 mcp | 1113 | ✅ | 2026-04-09 |
|
||||
| 06 hooks | 1170 | ✅ | 2026-04-09 |
|
||||
| 07 plugins | 949 | ✅ | 2026-04-09 |
|
||||
| 08 checkpoints | 320 | ✅ | 2026-04-09 |
|
||||
| 09 advanced | 1945 | ✅ | 2026-04-09 |
|
||||
| 10 cli | 837 | ✅ | 2026-04-09 |
|
||||
|
||||
**Прогрес P2:** 0/10 (0%)
|
||||
**Прогрес P2:** 10/10 (100%) ✅
|
||||
|
||||
## Пріоритет 3 — Приклади (47 файлів) · P4 — Допоміжні (5 файлів)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user