Skip to content

Commit b4f7898

Browse files
committed
chore: more development
1 parent a1692b3 commit b4f7898

21 files changed

+656
-182
lines changed

.github/workflows/todo.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Smart TODO Check
1+
name: Smart TODO Tracker
22

33
on:
44
push:
@@ -7,6 +7,10 @@ on:
77
jobs:
88
smart-todo:
99
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
issues: write
13+
1014
steps:
1115
- uses: actions/checkout@v3
1216

@@ -22,3 +26,18 @@ jobs:
2226
uses: ./
2327
with:
2428
repo-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
29+
report: true
30+
31+
- name: Upload TODO report
32+
uses: actions/upload-artifact@v3
33+
with:
34+
name: todo-report
35+
path: TODO_REPORT.md
36+
37+
- name: Commit TODO report
38+
run: |
39+
git config --global user.name "github-actions[bot]"
40+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
41+
git add TODO_REPORT.md
42+
git commit -m "chore(report): update TODO report" || echo "No changes"
43+
git push

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
node_modules/
22
dist/
3-
.env
3+
.env
4+
5+
6+
TODO_REPORT.md

ROADMAP.md

Lines changed: 3 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ A smart GitHub Action that detects, classifies, and transforms inline TODOs in y
4444

4545
> Provide visibility into the evolution and structure of tracked TODOs.
4646
47-
- [ ] Markdown/HTML dashboard with summary statistics
48-
_Total TODOs, grouped by folder, priority, author (`git blame`)_
47+
- [X] Markdown/HTML dashboard with summary statistics
48+
_Total TODOs, grouped by folder, priority, author (`git blame`) — **now sorted by `priority` and `due` date**_
49+
4950
- [ ] TODO history tracking (added/removed/modified)
5051
- [ ] Due date notifications or PR comments
5152

@@ -68,101 +69,3 @@ A smart GitHub Action that detects, classifies, and transforms inline TODOs in y
6869
- Clean architecture and modularity are core principles from day one.
6970
- LLM functionality will be optional and clearly separated from core logic.
7071
- Built with automation, extensibility, and developer workflows in mind.
71-
72-
---
73-
74-
# 🚀 Roadmap for TODO Issue Tracker 2.0
75-
76-
This project aims to build an intelligent GitHub Action that scans your codebase for TODOs, classifies them, and transforms them into contextualized GitHub Issues — with semantic analysis and multi-platform integration.
77-
78-
---
79-
80-
## 🧱 Phase 1: Foundations and Parity with the Original Project
81-
82-
🎯 Goal: Recreate the original functionality with clean, modular, and testable code.
83-
84-
- [ ] Create the base project structure
85-
- `src/` folder for source code
86-
- Subfolders: `core/`, `parser/`, `tasks/`, `templates/`, etc.
87-
88-
- [ ] Implement TODO parser
89-
- Detect `TODO`, `FIXME`, `BUG`, etc. comments
90-
- Support for multiple languages (`.js`, `.ts`, `.py`, `.go`, etc.)
91-
92-
- [ ] Initial task system: GitHub Issues
93-
- Create, update, and remove issues based on detected TODOs
94-
95-
- [ ] Templating system for issue creation
96-
- Customizable titles and descriptions via templates
97-
98-
- [ ] Functional GitHub Action workflow
99-
- `action.yml` definition file
100-
- Example usage in `.github/workflows/todo.yml`
101-
102-
- [ ] Unit testing with Jest or Vitest
103-
104-
---
105-
106-
## 🧠 Phase 2: Intelligence and Semantics
107-
108-
🎯 Goal: Make the system smarter by leveraging LLMs and contextual awareness.
109-
110-
- [ ] Automatic TODO classification
111-
- Use LLMs or heuristics to classify as `bug`, `enhancement`, `refactor`, etc.
112-
113-
- [ ] Auto-generate issue titles and descriptions using LLMs
114-
_Example: `Review sorting algorithm``Optimize Sorting Algorithm for Edge Cases`_
115-
116-
- [ ] Extract `due date` and `priority` via inline metadata parsing
117-
_Example: `TODO(priority=high, due=2025-06-01): improve this logic`_
118-
119-
---
120-
121-
## 🌍 Phase 3: Extended Support
122-
123-
🎯 Goal: Make the project adaptable to diverse environments and workflows.
124-
125-
- [ ] Support for multiple task management platforms
126-
_GitHub, Jira, Notion, Trello, Linear (via APIs)_
127-
128-
- [ ] Internationalization (i18n)
129-
_Detect TODOs written in different languages_
130-
131-
- [ ] Support for additional file types
132-
_`.ipynb`, `.yaml`, `.md`, `.json`, `.xml`, and more_
133-
134-
---
135-
136-
## 📊 Phase 4: Analysis and Reporting
137-
138-
🎯 Goal: Provide visibility into the state and evolution of TODOs.
139-
140-
- [ ] Markdown/HTML dashboard with metrics
141-
_Total TODOs, grouped by folder, priority, author (`git blame`)_
142-
143-
- [ ] TODO history tracking
144-
_Track when TODOs are added, removed, or changed over time_
145-
146-
- [ ] Notifications and reminders
147-
_Comment on PRs or issues when due dates are approaching_
148-
149-
---
150-
151-
## 🔁 Phase 5: Optimizations and Contributions
152-
153-
🎯 Goal: Ensure quality, performance, and ease of collaboration.
154-
155-
- [ ] Plugin-based modular architecture
156-
- [ ] CLI support (standalone usage outside GitHub Actions)
157-
- [ ] Test coverage >90%
158-
- [ ] Full documentation with usage examples
159-
- [ ] Publish to GitHub Marketplace as an official Action
160-
161-
---
162-
163-
## 📌 Notes
164-
165-
- Modularity, testability, and code clarity are priorities from day one.
166-
- LLM integration will be optional and cleanly decoupled from core logic.
167-
- Designed with automation, extensibility, and developer experience in mind.
168-

action.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ author: 'Diogo Ribeiro'
44

55
inputs:
66
repo-token:
7-
description: 'GitHub token com permissão para criar issues'
87
required: true
8+
description: GitHub token to create issues
9+
report:
10+
required: false
11+
description: Whether to generate a TODO markdown report
12+
default: 'false'
913

1014
runs:
1115
using: 'node20'

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"license": "MIT",
2121
"dependencies": {
2222
"@actions/core": "^1.10.0",
23-
"@actions/github": "^5.1.1"
23+
"@actions/github": "^5.1.1",
24+
"@octokit/rest": "^21.1.1"
2425
},
2526
"devDependencies": {
2627
"@types/node": "^20.11.17",

src/ActionMain.ts

Lines changed: 19 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,14 @@ import * as github from '@actions/github';
33
import path from 'path';
44
import { extractTodosFromDir } from './parser/extractTodosFromDir';
55
import { TodoItem } from './parser/types';
6-
7-
const LABELS_BY_TAG: Record<string, string[]> = {
8-
TODO: ['enhancement'],
9-
FIXME: ['bug'],
10-
BUG: ['bug'],
11-
HACK: ['technical-debt']
12-
};
13-
14-
const DEFAULT_LABEL_COLOR = 'cccccc';
15-
const LABEL_COLORS: Record<string, string> = {
16-
bug: 'd73a4a',
17-
enhancement: 'a2eeef',
18-
todo: 'ededed',
19-
'technical-debt': 'f9d0c4'
20-
};
21-
22-
function labelsFromMetadata(metadata?: Record<string, string>): string[] {
23-
if (!metadata) return [];
24-
return Object.entries(metadata).map(([key, value]) => `${key}:${value}`);
25-
}
26-
27-
async function ensureLabelExists(
28-
octokit: ReturnType<typeof github.getOctokit>,
29-
owner: string,
30-
repo: string,
31-
label: string
32-
) {
33-
try {
34-
await octokit.rest.issues.getLabel({ owner, repo, name: label });
35-
} catch (err: any) {
36-
if (err.status === 404) {
37-
const base = label.toLowerCase().split(':')[0];
38-
const color = LABEL_COLORS[base] || DEFAULT_LABEL_COLOR;
39-
await octokit.rest.issues.createLabel({
40-
owner,
41-
repo,
42-
name: label,
43-
color,
44-
description: 'Auto-created by smart-todo-action'
45-
});
46-
core.info(`🏷️ Created label "${label}"`);
47-
} else {
48-
core.warning(`⚠️ Failed to check/create label "${label}": ${err.message}`);
49-
}
50-
}
51-
}
6+
import { getExistingIssueTitles, createIssueIfNeeded } from './core/issueManager';
7+
import { generateMarkdownReport } from './core/report';
8+
import { limitTodos, todoKey } from './core/todoUtils';
529

5310
async function run(): Promise<void> {
5411
try {
5512
const token = core.getInput('repo-token', { required: true });
13+
const generateReport = core.getInput('report') === 'true';
5614
const workspace = process.env.GITHUB_WORKSPACE || '.';
5715

5816
const todos: TodoItem[] = extractTodosFromDir(workspace);
@@ -61,36 +19,25 @@ async function run(): Promise<void> {
6119

6220
core.info(`🔍 Found ${todos.length} TODOs`);
6321

64-
const MAX_ISSUES = 5;
65-
const todosToCreate = todos
66-
.filter(todo => todo.text && todo.text.length > 5)
67-
.slice(0, MAX_ISSUES);
22+
const existingTitles = await getExistingIssueTitles(octokit, owner, repo);
6823

69-
for (const todo of todosToCreate) {
70-
const title = `[${todo.tag}] ${todo.text}`;
71-
const body = `Found in \`${todo.file}:${todo.line}\`\n\n\`\`\`\n${todo.text}\n\`\`\``;
72-
const tag = todo.tag.toUpperCase();
73-
const baseLabels = LABELS_BY_TAG[tag] || ['todo'];
74-
const metaLabels = labelsFromMetadata(todo.metadata);
75-
const labels = [...baseLabels, ...metaLabels];
24+
const seenKeys = new Set<string>();
25+
const uniqueTodos = todos.filter(todo => {
26+
const key = todoKey(todo);
27+
if (seenKeys.has(key)) return false;
28+
seenKeys.add(key);
29+
return true;
30+
});
7631

77-
for (const label of labels) {
78-
await ensureLabelExists(octokit, owner, repo, label);
79-
}
32+
const todosToCreate = limitTodos(uniqueTodos, 5);
8033

81-
try {
82-
await octokit.rest.issues.create({
83-
owner,
84-
repo,
85-
title,
86-
body,
87-
labels
88-
});
34+
for (const todo of todosToCreate) {
35+
await createIssueIfNeeded(octokit, owner, repo, todo, existingTitles);
36+
}
8937

90-
core.info(`✅ Created issue with labels [${labels.join(', ')}]: ${title}`);
91-
} catch (err: any) {
92-
core.warning(`⚠️ Failed to create issue for: ${title}${err.message}`);
93-
}
38+
if (generateReport) {
39+
generateMarkdownReport(todos);
40+
core.info('📝 Generated TODO_REPORT.md');
9441
}
9542

9643
} catch (error: any) {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { extractTodosFromFile } from '../../parser/extractTodos'; // Ensure this file exists at the specified path
3+
import { TodoItem } from '../../parser/types';
4+
5+
6+
7+
describe('extractTodos', () => {
8+
it('extracts simple TODOs with //', () => {
9+
const content = `// TODO: clean this up\nconst a = 1;`;
10+
const todos = extractTodosFromFile(content);
11+
expect(todos.length).toBe(1);
12+
expect(todos[0].text).toBe('clean this up');
13+
expect(todos[0].tag).toBe('TODO');
14+
expect(todos[0].line).toBe(1);
15+
});
16+
17+
it('extracts multiple tags', () => {
18+
const content = `// BUG: crashes\n# FIXME: something wrong`;
19+
const todos = extractTodosFromFile(content);
20+
expect(todos.length).toBe(2);
21+
expect(todos.map(t => t.tag)).toEqual(['BUG', 'FIXME']);
22+
});
23+
24+
it('extracts metadata key=value pairs', () => {
25+
const content = `// TODO(priority=high, due=2025-06-01): fix it`;
26+
const todos = extractTodosFromFile(content);
27+
expect(todos.length).toBe(1);
28+
expect(todos[0].metadata).toEqual({
29+
priority: 'high',
30+
due: '2025-06-01'
31+
});
32+
});
33+
34+
it('supports HTML comments', () => {
35+
const content = `<!-- TODO: fix layout -->`;
36+
const todos = extractTodosFromFile(content);
37+
expect(todos.length).toBe(1);
38+
expect(todos[0].tag).toBe('TODO');
39+
});
40+
41+
it('returns empty list if no TODOs are found', () => {
42+
const content = `const x = 5; // just a comment`;
43+
const todos = extractTodosFromFile(content);
44+
expect(todos.length).toBe(0);
45+
});
46+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { describe, it, expect } from 'vitest';
2+
import path from 'path';
3+
import { extractTodosFromDir } from '../../parser/extractTodosFromDir';
4+
5+
describe('extractTodosFromDir', () => {
6+
const base = path.join(__dirname, 'fixtures');
7+
8+
it('should extract TODOs from supported files recursively', () => {
9+
const todos = extractTodosFromDir(base);
10+
expect(todos.length).toBe(2);
11+
12+
const texts = todos.map(t => t.text);
13+
expect(texts).toContain('Refactor this module');
14+
expect(texts).toContain('Handle edge case');
15+
16+
const tags = todos.map(t => t.tag);
17+
expect(tags).toContain('TODO');
18+
expect(tags).toContain('FIXME');
19+
});
20+
21+
it('should include correct file and line information', () => {
22+
const todos = extractTodosFromDir(base);
23+
const one = todos.find(t => t.text.includes('Refactor'));
24+
expect(one?.file.endsWith('one-file.ts')).toBe(true);
25+
expect(typeof one?.line).toBe('number');
26+
expect(one?.line).toBeGreaterThan(0);
27+
});
28+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# FIXME: Handle edge case
2+
print("Running")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// TODO: Refactor this module
2+
export const a = 42;

0 commit comments

Comments
 (0)