chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,583 @@
|
||||
# Convert Code Analysis to Linear Tasks
|
||||
|
||||
Convert code analysis to Linear tasks
|
||||
|
||||
## Purpose
|
||||
This command scans your codebase for TODO/FIXME comments, technical debt markers, deprecated code, and other indicators that should be tracked as tasks. It automatically creates organized, prioritized Linear tasks to ensure important code improvements aren't forgotten.
|
||||
|
||||
## Usage
|
||||
```bash
|
||||
# Scan entire codebase for TODOs and create tasks
|
||||
claude "Create tasks from all TODO comments in the codebase"
|
||||
|
||||
# Scan specific directory or module
|
||||
claude "Find TODOs in src/api and create Linear tasks"
|
||||
|
||||
# Create tasks from specific patterns
|
||||
claude "Create tasks for all deprecated functions"
|
||||
|
||||
# Generate technical debt report
|
||||
claude "Analyze technical debt in the project and create improvement tasks"
|
||||
```
|
||||
|
||||
## Instructions
|
||||
|
||||
### 1. Scan for Task Markers
|
||||
Search for common patterns indicating needed work:
|
||||
|
||||
```bash
|
||||
# Find TODO comments
|
||||
rg "TODO|FIXME|HACK|XXX|OPTIMIZE|REFACTOR" --type-add 'code:*.{js,ts,py,java,go,rb,php}' -t code
|
||||
|
||||
# Find deprecated markers
|
||||
rg "@deprecated|DEPRECATED|@obsolete" -t code
|
||||
|
||||
# Find temporary code
|
||||
rg "TEMPORARY|TEMP|REMOVE BEFORE|DELETE ME" -t code -i
|
||||
|
||||
# Find technical debt markers
|
||||
rg "TECHNICAL DEBT|TECH DEBT|REFACTOR|NEEDS REFACTORING" -t code -i
|
||||
|
||||
# Find security concerns
|
||||
rg "SECURITY|INSECURE|VULNERABILITY|CVE-" -t code -i
|
||||
|
||||
# Find performance issues
|
||||
rg "SLOW|PERFORMANCE|OPTIMIZE|BOTTLENECK" -t code -i
|
||||
```
|
||||
|
||||
### 2. Parse Comment Context
|
||||
Extract meaningful information from comments:
|
||||
|
||||
```javascript
|
||||
class CommentParser {
|
||||
parseComment(file, lineNumber, comment) {
|
||||
const parsed = {
|
||||
type: 'todo',
|
||||
priority: 'medium',
|
||||
title: '',
|
||||
description: '',
|
||||
author: null,
|
||||
date: null,
|
||||
tags: [],
|
||||
code_context: '',
|
||||
file_path: file,
|
||||
line_number: lineNumber
|
||||
};
|
||||
|
||||
// Detect comment type
|
||||
if (comment.match(/FIXME/i)) {
|
||||
parsed.type = 'fixme';
|
||||
parsed.priority = 'high';
|
||||
} else if (comment.match(/HACK|XXX/i)) {
|
||||
parsed.type = 'hack';
|
||||
parsed.priority = 'high';
|
||||
} else if (comment.match(/OPTIMIZE|PERFORMANCE/i)) {
|
||||
parsed.type = 'optimization';
|
||||
} else if (comment.match(/DEPRECATED/i)) {
|
||||
parsed.type = 'deprecation';
|
||||
parsed.priority = 'high';
|
||||
} else if (comment.match(/SECURITY/i)) {
|
||||
parsed.type = 'security';
|
||||
parsed.priority = 'urgent';
|
||||
}
|
||||
|
||||
// Extract author and date
|
||||
const authorMatch = comment.match(/@(\w+)|by (\w+)/i);
|
||||
if (authorMatch) {
|
||||
parsed.author = authorMatch[1] || authorMatch[2];
|
||||
}
|
||||
|
||||
const dateMatch = comment.match(/(\d{4}-\d{2}-\d{2})|(\d{1,2}\/\d{1,2}\/\d{2,4})/);
|
||||
if (dateMatch) {
|
||||
parsed.date = dateMatch[0];
|
||||
}
|
||||
|
||||
// Extract title and description
|
||||
const cleanComment = comment
|
||||
.replace(/^\/\/\s*|^\/\*\s*|\*\/\s*$|^#\s*/g, '')
|
||||
.replace(/TODO|FIXME|HACK|XXX/i, '')
|
||||
.trim();
|
||||
|
||||
const parts = cleanComment.split(/[:\-–—]/);
|
||||
if (parts.length > 1) {
|
||||
parsed.title = parts[0].trim();
|
||||
parsed.description = parts.slice(1).join(':').trim();
|
||||
} else {
|
||||
parsed.title = cleanComment;
|
||||
}
|
||||
|
||||
// Extract tags
|
||||
const tagMatch = comment.match(/#(\w+)/g);
|
||||
if (tagMatch) {
|
||||
parsed.tags = tagMatch.map(tag => tag.substring(1));
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
getCodeContext(file, lineNumber, contextLines = 5) {
|
||||
const lines = readFileLines(file);
|
||||
const start = Math.max(0, lineNumber - contextLines);
|
||||
const end = Math.min(lines.length, lineNumber + contextLines);
|
||||
|
||||
return lines.slice(start, end).map((line, i) => ({
|
||||
number: start + i + 1,
|
||||
content: line,
|
||||
isTarget: start + i + 1 === lineNumber
|
||||
}));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Group and Deduplicate
|
||||
Organize found issues intelligently:
|
||||
|
||||
```javascript
|
||||
class TaskGrouper {
|
||||
groupTasks(parsedComments) {
|
||||
const groups = {
|
||||
byFile: new Map(),
|
||||
byType: new Map(),
|
||||
byAuthor: new Map(),
|
||||
byModule: new Map()
|
||||
};
|
||||
|
||||
for (const comment of parsedComments) {
|
||||
// Group by file
|
||||
if (!groups.byFile.has(comment.file_path)) {
|
||||
groups.byFile.set(comment.file_path, []);
|
||||
}
|
||||
groups.byFile.get(comment.file_path).push(comment);
|
||||
|
||||
// Group by type
|
||||
if (!groups.byType.has(comment.type)) {
|
||||
groups.byType.set(comment.type, []);
|
||||
}
|
||||
groups.byType.get(comment.type).push(comment);
|
||||
|
||||
// Group by module
|
||||
const module = this.extractModule(comment.file_path);
|
||||
if (!groups.byModule.has(module)) {
|
||||
groups.byModule.set(module, []);
|
||||
}
|
||||
groups.byModule.get(module).push(comment);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
mergeSimilarTasks(tasks) {
|
||||
const merged = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const task of tasks) {
|
||||
if (seen.has(task)) continue;
|
||||
|
||||
// Find similar tasks
|
||||
const similar = tasks.filter(t =>
|
||||
t !== task &&
|
||||
!seen.has(t) &&
|
||||
this.areSimilar(task, t)
|
||||
);
|
||||
|
||||
if (similar.length > 0) {
|
||||
// Merge into one task
|
||||
const mergedTask = {
|
||||
...task,
|
||||
title: this.generateMergedTitle(task, similar),
|
||||
description: this.generateMergedDescription(task, similar),
|
||||
locations: [task, ...similar].map(t => ({
|
||||
file: t.file_path,
|
||||
line: t.line_number
|
||||
}))
|
||||
};
|
||||
merged.push(mergedTask);
|
||||
seen.add(task);
|
||||
similar.forEach(t => seen.add(t));
|
||||
} else {
|
||||
merged.push(task);
|
||||
seen.add(task);
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Analyze Technical Debt
|
||||
Identify code quality issues:
|
||||
|
||||
```javascript
|
||||
class TechnicalDebtAnalyzer {
|
||||
async analyzeFile(filePath) {
|
||||
const issues = [];
|
||||
const content = await readFile(filePath);
|
||||
const lines = content.split('\n');
|
||||
|
||||
// Check for long functions
|
||||
const functionMatches = content.matchAll(/function\s+(\w+)|(\w+)\s*=\s*\(.*?\)\s*=>/g);
|
||||
for (const match of functionMatches) {
|
||||
const functionName = match[1] || match[2];
|
||||
const startLine = getLineNumber(content, match.index);
|
||||
const functionLength = this.getFunctionLength(lines, startLine);
|
||||
|
||||
if (functionLength > 50) {
|
||||
issues.push({
|
||||
type: 'long_function',
|
||||
severity: functionLength > 100 ? 'high' : 'medium',
|
||||
title: `Refactor long function: ${functionName}`,
|
||||
description: `Function ${functionName} is ${functionLength} lines long. Consider breaking it into smaller functions.`,
|
||||
file_path: filePath,
|
||||
line_number: startLine
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check for duplicate code
|
||||
const duplicates = await this.findDuplicateCode(filePath);
|
||||
for (const dup of duplicates) {
|
||||
issues.push({
|
||||
type: 'duplicate_code',
|
||||
severity: 'medium',
|
||||
title: 'Remove duplicate code',
|
||||
description: `Similar code found in ${dup.otherFile}:${dup.otherLine}`,
|
||||
file_path: filePath,
|
||||
line_number: dup.line
|
||||
});
|
||||
}
|
||||
|
||||
// Check for complex conditionals
|
||||
const complexConditions = content.matchAll(/if\s*\([^)]{50,}\)/g);
|
||||
for (const match of complexConditions) {
|
||||
issues.push({
|
||||
type: 'complex_condition',
|
||||
severity: 'low',
|
||||
title: 'Simplify complex conditional',
|
||||
description: 'Consider extracting conditional logic into named variables or functions',
|
||||
file_path: filePath,
|
||||
line_number: getLineNumber(content, match.index)
|
||||
});
|
||||
}
|
||||
|
||||
// Check for outdated dependencies
|
||||
if (filePath.endsWith('package.json')) {
|
||||
const outdated = await this.checkOutdatedDependencies(filePath);
|
||||
for (const dep of outdated) {
|
||||
issues.push({
|
||||
type: 'outdated_dependency',
|
||||
severity: dep.major ? 'high' : 'low',
|
||||
title: `Update ${dep.name} from ${dep.current} to ${dep.latest}`,
|
||||
description: dep.major ? 'Major version update available' : 'Minor update available',
|
||||
file_path: filePath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Create Linear Tasks
|
||||
Convert findings into actionable tasks:
|
||||
|
||||
```javascript
|
||||
async function createLinearTasks(groupedTasks, options = {}) {
|
||||
const created = [];
|
||||
const skipped = [];
|
||||
|
||||
// Check for existing tasks to avoid duplicates
|
||||
const existingTasks = await linear.searchTasks('TODO OR FIXME');
|
||||
const existingTitles = new Set(existingTasks.map(t => t.title));
|
||||
|
||||
// Create parent task for large groups
|
||||
if (options.createEpic && groupedTasks.length > 10) {
|
||||
const epic = await linear.createTask({
|
||||
title: `Technical Debt: ${options.module || 'Codebase'} Cleanup`,
|
||||
description: `Parent task for ${groupedTasks.length} code improvements`,
|
||||
priority: 2,
|
||||
labels: ['technical-debt', 'code-quality']
|
||||
});
|
||||
options.parentId = epic.id;
|
||||
}
|
||||
|
||||
for (const task of groupedTasks) {
|
||||
// Skip if similar task exists
|
||||
if (existingTitles.has(task.title)) {
|
||||
skipped.push({ task, reason: 'duplicate' });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build task description
|
||||
const description = buildTaskDescription(task);
|
||||
|
||||
// Map priority
|
||||
const priorityMap = {
|
||||
urgent: 1,
|
||||
high: 2,
|
||||
medium: 3,
|
||||
low: 4
|
||||
};
|
||||
|
||||
try {
|
||||
const linearTask = await linear.createTask({
|
||||
title: task.title,
|
||||
description,
|
||||
priority: priorityMap[task.priority] || 3,
|
||||
labels: getLabelsForTask(task),
|
||||
parentId: options.parentId,
|
||||
estimate: estimateTaskSize(task)
|
||||
});
|
||||
|
||||
created.push({
|
||||
linear: linearTask,
|
||||
source: task
|
||||
});
|
||||
|
||||
// Add code link as comment
|
||||
await linear.createComment({
|
||||
issueId: linearTask.id,
|
||||
body: `📍 Code location: \`${task.file_path}:${task.line_number}\``
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
skipped.push({ task, reason: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
return { created, skipped };
|
||||
}
|
||||
|
||||
function buildTaskDescription(task) {
|
||||
let description = task.description || '';
|
||||
|
||||
// Add code context
|
||||
if (task.code_context) {
|
||||
description += '\n\n### Code Context\n```\n';
|
||||
task.code_context.forEach(line => {
|
||||
const prefix = line.isTarget ? '>>> ' : ' ';
|
||||
description += `${prefix}${line.number}: ${line.content}\n`;
|
||||
});
|
||||
description += '```\n';
|
||||
}
|
||||
|
||||
// Add metadata
|
||||
description += '\n\n### Details\n';
|
||||
description += `- **Type**: ${task.type}\n`;
|
||||
description += `- **File**: \`${task.file_path}\`\n`;
|
||||
description += `- **Line**: ${task.line_number}\n`;
|
||||
|
||||
if (task.author) {
|
||||
description += `- **Author**: @${task.author}\n`;
|
||||
}
|
||||
if (task.date) {
|
||||
description += `- **Date**: ${task.date}\n`;
|
||||
}
|
||||
if (task.tags.length > 0) {
|
||||
description += `- **Tags**: ${task.tags.join(', ')}\n`;
|
||||
}
|
||||
|
||||
// Add suggestions
|
||||
if (task.type === 'deprecated') {
|
||||
description += '\n### Suggested Actions\n';
|
||||
description += '1. Identify all usages of this deprecated code\n';
|
||||
description += '2. Update to use the recommended alternative\n';
|
||||
description += '3. Add deprecation warnings if not present\n';
|
||||
description += '4. Schedule for removal in next major version\n';
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Generate Summary Report
|
||||
Create overview of findings:
|
||||
|
||||
```javascript
|
||||
function generateReport(scanResults, createdTasks) {
|
||||
const report = {
|
||||
summary: {
|
||||
totalFound: scanResults.length,
|
||||
tasksCreated: createdTasks.created.length,
|
||||
tasksSkipped: createdTasks.skipped.length,
|
||||
byType: {},
|
||||
byPriority: {},
|
||||
byFile: {}
|
||||
},
|
||||
details: [],
|
||||
recommendations: []
|
||||
};
|
||||
|
||||
// Analyze distribution
|
||||
for (const result of scanResults) {
|
||||
report.summary.byType[result.type] = (report.summary.byType[result.type] || 0) + 1;
|
||||
report.summary.byPriority[result.priority] = (report.summary.byPriority[result.priority] || 0) + 1;
|
||||
}
|
||||
|
||||
// Generate recommendations
|
||||
if (report.summary.byType.security > 0) {
|
||||
report.recommendations.push({
|
||||
priority: 'urgent',
|
||||
action: 'Address security-related TODOs immediately',
|
||||
tasks: scanResults.filter(r => r.type === 'security').length
|
||||
});
|
||||
}
|
||||
|
||||
if (report.summary.byType.deprecated > 5) {
|
||||
report.recommendations.push({
|
||||
priority: 'high',
|
||||
action: 'Create deprecation removal sprint',
|
||||
tasks: report.summary.byType.deprecated
|
||||
});
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Error Handling
|
||||
```javascript
|
||||
// Handle access errors
|
||||
try {
|
||||
await scanDirectory(path);
|
||||
} catch (error) {
|
||||
if (error.code === 'EACCES') {
|
||||
console.warn(`Skipping ${path} - permission denied`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Linear API limits
|
||||
const rateLimiter = {
|
||||
tasksCreated: 0,
|
||||
resetTime: Date.now() + 3600000,
|
||||
|
||||
async createTask(taskData) {
|
||||
if (this.tasksCreated >= 50) {
|
||||
console.log('Rate limit approaching, batching remaining tasks...');
|
||||
// Create single task with list of TODOs
|
||||
return this.createBatchTask(remainingTasks);
|
||||
}
|
||||
this.tasksCreated++;
|
||||
return linear.createTask(taskData);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle malformed comments
|
||||
const safeParser = {
|
||||
parse(comment) {
|
||||
try {
|
||||
return this.parseComment(comment);
|
||||
} catch (error) {
|
||||
return {
|
||||
type: 'todo',
|
||||
title: comment.substring(0, 50) + '...',
|
||||
priority: 'low',
|
||||
parseError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
Scanning codebase for TODOs and technical debt...
|
||||
|
||||
📊 Scan Results:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Found 47 items across 23 files:
|
||||
• 24 TODOs
|
||||
• 8 FIXMEs
|
||||
• 5 Deprecated functions
|
||||
• 3 Security concerns
|
||||
• 7 Performance optimizations
|
||||
|
||||
🔍 Breakdown by Priority:
|
||||
🔴 Urgent: 3 (security related)
|
||||
🟠 High: 13 (FIXMEs + deprecations)
|
||||
🟡 Medium: 24 (standard TODOs)
|
||||
🟢 Low: 7 (optimizations)
|
||||
|
||||
📁 Hotspot Files:
|
||||
1. src/api/auth.js - 8 items
|
||||
2. src/utils/validation.js - 6 items
|
||||
3. src/models/User.js - 5 items
|
||||
|
||||
🚨 Critical Findings:
|
||||
|
||||
1. SECURITY: Hardcoded API key
|
||||
File: src/config/api.js:45
|
||||
TODO: Remove hardcoded key and use env variable
|
||||
→ Creating task with URGENT priority
|
||||
|
||||
2. DEPRECATED: Legacy authentication method
|
||||
File: src/api/auth.js:120
|
||||
Multiple usages found in 4 files
|
||||
→ Creating migration task
|
||||
|
||||
3. FIXME: Race condition in concurrent updates
|
||||
File: src/services/sync.js:78
|
||||
Author: @alice (2024-01-03)
|
||||
→ Creating high-priority bug task
|
||||
|
||||
📝 Task Creation Summary:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
✅ Created 32 Linear tasks:
|
||||
- Epic: "Q1 Technical Debt Cleanup" (LIN-456)
|
||||
- 3 urgent security tasks
|
||||
- 10 high-priority fixes
|
||||
- 19 medium-priority improvements
|
||||
|
||||
⏭️ Skipped 15 items:
|
||||
- 8 duplicates (tasks already exist)
|
||||
- 4 low-value comments (e.g., "TODO: think about this")
|
||||
- 3 external dependencies (waiting on upstream)
|
||||
|
||||
📊 Estimates:
|
||||
- Total story points: 89
|
||||
- Estimated effort: 2-3 sprints
|
||||
- Recommended team size: 2-3 developers
|
||||
|
||||
🎯 Recommended Actions:
|
||||
1. Schedule security sprint immediately (3 urgent items)
|
||||
2. Assign deprecation removal to next sprint (5 items)
|
||||
3. Create coding standards to reduce future TODOs
|
||||
4. Set up pre-commit hook to limit new TODOs
|
||||
|
||||
View all created tasks:
|
||||
https://linear.app/yourteam/project/q1-technical-debt-cleanup
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Custom Patterns
|
||||
Define project-specific patterns:
|
||||
```bash
|
||||
# Add custom markers to scan
|
||||
claude "Scan for REVIEW, QUESTION, and ASSUMPTION comments"
|
||||
```
|
||||
|
||||
### Integration with CI/CD
|
||||
```bash
|
||||
# Fail build if critical TODOs found
|
||||
claude "Check for SECURITY or FIXME comments and exit with error if found"
|
||||
```
|
||||
|
||||
### Scheduled Scans
|
||||
```bash
|
||||
# Weekly technical debt report
|
||||
claude "Generate weekly technical debt report and create tasks for new items"
|
||||
```
|
||||
|
||||
## Tips
|
||||
- Run regularly to prevent TODO accumulation
|
||||
- Use consistent comment formats across the team
|
||||
- Include author and date in TODOs
|
||||
- Link TODOs to existing Linear issues when possible
|
||||
- Set up IDE snippets for properly formatted TODOs
|
||||
- Review and close completed TODO tasks
|
||||
- Use TODO comments as a quality gate in PR reviews
|
||||
Reference in New Issue
Block a user