Skip to content

Commit acf5d0f

Browse files
Merge pull request #1 from DiogoRibeiro7/feat/initial_commit
feat: add TODO parser and initial GitHub Action integration
2 parents e913a2d + 0b74b48 commit acf5d0f

File tree

10 files changed

+1196
-30
lines changed

10 files changed

+1196
-30
lines changed

.github/workflows/todo.yml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
1-
name: Run TODO Watcher
1+
name: Smart TODO Check
22

33
on:
44
push:
5-
branches: [ main ]
5+
branches: [main]
66
pull_request:
7-
branches: [ main ]
7+
branches: [main]
88

99
jobs:
10-
todo-watcher:
10+
smart-todo:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- uses: actions/checkout@v3
14+
1415
- name: Setup Node.js
1516
uses: actions/setup-node@v3
1617
with:
17-
node-version: '20'
18-
- run: npm install
19-
- run: npm run build
20-
- uses: ./ # Usa esta Action localmente
18+
node-version: 20
19+
20+
- run: yarn install
21+
- run: yarn prepare
22+
23+
- name: Run Smart TODO Action
24+
uses: ./
25+
with:
26+
repo-token: ${{ secrets.GITHUB_TOKEN }}

action.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: 'Smart TODO Action'
2+
description: 'Transforma comentários TODO/FIXME/BUG do código em GitHub Issues inteligentes.'
3+
author: 'Diogo Ribeiro'
4+
5+
inputs:
6+
repo-token:
7+
description: 'GitHub token com permissão para criar issues'
8+
required: true
9+
10+
runs:
11+
using: 'node20'
12+
main: 'dist/index.js'
13+
14+
branding:
15+
icon: 'check-circle'
16+
color: 'blue'

package.json

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
{
2-
"name": "smart-todo-action",
3-
"version": "0.1.0",
4-
"description": "GitHub Action inteligente para transformar TODOs em issues e tarefas rastreáveis.",
5-
"main": "dist/index.js",
6-
"scripts": {
7-
"build": "tsc",
8-
"test": "vitest run",
9-
"prepare": "ncc build src/main.ts -o dist"
10-
},
11-
"keywords": ["github-action", "todo", "issues", "task-tracking", "llm", "automation"],
12-
"author": "",
13-
"license": "MIT",
14-
"dependencies": {
15-
"@actions/core": "^1.10.0",
16-
"@actions/github": "^5.1.1"
17-
},
18-
"devDependencies": {
19-
"typescript": "^5.3.3",
20-
"vitest": "^1.3.0",
21-
"@types/node": "^20.11.17",
22-
"@vercel/ncc": "^0.38.0"
23-
}
2+
"name": "smart-todo-action",
3+
"version": "0.1.0",
4+
"description": "GitHub Action inteligente para transformar TODOs em issues e tarefas rastreáveis.",
5+
"main": "dist/index.js",
6+
"scripts": {
7+
"build": "tsc",
8+
"test": "vitest run",
9+
"prepare": "yarn ncc build src/ActionMain.ts -o dist"
10+
},
11+
"keywords": [
12+
"github-action",
13+
"todo",
14+
"issues",
15+
"task-tracking",
16+
"llm",
17+
"automation"
18+
],
19+
"author": "",
20+
"license": "MIT",
21+
"dependencies": {
22+
"@actions/core": "^1.10.0",
23+
"@actions/github": "^5.1.1"
24+
},
25+
"devDependencies": {
26+
"@types/node": "^20.11.17",
27+
"@vercel/ncc": "^0.38.3",
28+
"typescript": "^5.3.3",
29+
"vitest": "^1.3.0"
30+
}
2431
}

src/ActionMain.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as core from '@actions/core';
2+
import * as github from '@actions/github';
3+
import path from 'path';
4+
import { extractTodosFromDir } from './parser/extractTodosFromDir';
5+
import { TodoItem } from './parser/types';
6+
7+
async function run(): Promise<void> {
8+
try {
9+
const token = core.getInput('repo-token', { required: true });
10+
const workspace = process.env.GITHUB_WORKSPACE || '.';
11+
12+
const todos: TodoItem[] = extractTodosFromDir(workspace);
13+
14+
core.info(`🔍 Encontrados ${todos.length} TODOs no repositório`);
15+
16+
for (const todo of todos) {
17+
core.info(`📌 [${todo.tag}] ${todo.text} (${todo.file}:${todo.line})`);
18+
}
19+
20+
const octokit = github.getOctokit(token);
21+
const { owner, repo } = github.context.repo;
22+
23+
// No futuro: criar/atualizar issues aqui
24+
25+
core.info(`Contexto: ${owner}/${repo}`);
26+
} catch (error: any) {
27+
core.setFailed(`Erro na execução: ${error.message}`);
28+
}
29+
}
30+
31+
run();

src/parser/extractTodos.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import { TodoItem } from './types';
4+
5+
const COMMENT_PATTERNS = [
6+
{ ext: ['.ts', '.js', '.java', '.go'], pattern: /^\s*\/\/\s*(.*)$/ },
7+
{ ext: ['.py', '.sh', '.rb'], pattern: /^\s*#\s*(.*)$/ },
8+
{ ext: ['.html', '.xml'], pattern: /<!--\s*(.*?)\s*-->/ }
9+
];
10+
11+
const TAG_REGEX = /(TODO|FIXME|BUG|HACK)(\([^)]*\))?:?\s*(.*)/i;
12+
13+
function extractMetadata(str: string): Record<string, string> {
14+
const meta: Record<string, string> = {};
15+
const match = str.match(/\((.*?)\)/);
16+
if (match) {
17+
const content = match[1];
18+
content.split(',').forEach(pair => {
19+
const [key, val] = pair.split('=').map(s => s.trim());
20+
if (key && val) meta[key] = val;
21+
});
22+
}
23+
return meta;
24+
}
25+
26+
export function extractTodosFromFile(filePath: string): TodoItem[] {
27+
const ext = path.extname(filePath);
28+
const pattern = COMMENT_PATTERNS.find(p => p.ext.includes(ext));
29+
if (!pattern) return [];
30+
31+
const lines = fs.readFileSync(filePath, 'utf-8').split('\n');
32+
const todos: TodoItem[] = [];
33+
34+
lines.forEach((line, idx) => {
35+
const commentMatch = line.match(pattern.pattern);
36+
if (commentMatch) {
37+
const comment = commentMatch[1];
38+
const tagMatch = comment.match(TAG_REGEX);
39+
if (tagMatch) {
40+
const [_, tag, metaRaw, text] = tagMatch;
41+
const metadata = metaRaw ? extractMetadata(metaRaw) : undefined;
42+
todos.push({
43+
file: filePath,
44+
line: idx + 1,
45+
tag,
46+
text: text.trim(),
47+
metadata
48+
});
49+
}
50+
}
51+
});
52+
53+
return todos;
54+
}

src/parser/extractTodosFromDir.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import { extractTodosFromFile } from './extractTodos';
4+
import { TodoItem } from './types';
5+
6+
const SUPPORTED_EXTENSIONS = ['.ts', '.js', '.py', '.go', '.java', '.rb', '.sh', '.html', '.xml'];
7+
8+
export function extractTodosFromDir(dirPath: string): TodoItem[] {
9+
const todos: TodoItem[] = [];
10+
11+
function walk(currentPath: string) {
12+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
13+
14+
for (const entry of entries) {
15+
const fullPath = path.join(currentPath, entry.name);
16+
17+
if (entry.isDirectory()) {
18+
walk(fullPath);
19+
} else if (entry.isFile()) {
20+
const ext = path.extname(entry.name);
21+
if (SUPPORTED_EXTENSIONS.includes(ext)) {
22+
const fileTodos = extractTodosFromFile(fullPath);
23+
todos.push(...fileTodos);
24+
}
25+
}
26+
}
27+
}
28+
29+
walk(dirPath);
30+
return todos;
31+
}

src/parser/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export interface TodoItem {
2+
file: string;
3+
line: number;
4+
tag: string;
5+
text: string;
6+
metadata?: Record<string, string>;
7+
}
8+

tests/example.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// TODO(priority=high, due=2025-06-01): Refatorar este método
2+
function processData() {
3+
// FIXME: Corrigir possível vazamento de memória
4+
console.log("Processing...");
5+
// Comentário irrelevante
6+
// BUG(due=2025-01-01): Corrigir lógica de ordenação
7+
}
8+

tests/parser.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { extractTodosFromFile } from '../src/parser/extractTodos';
3+
import path from 'path';
4+
5+
describe('extractTodosFromFile', () => {
6+
const filePath = path.join(__dirname, 'example.ts');
7+
const todos = extractTodosFromFile(filePath);
8+
9+
it('deve encontrar 3 TODOs', () => {
10+
expect(todos.length).toBe(3);
11+
});
12+
13+
it('deve extrair corretamente metadados', () => {
14+
expect(todos[0].metadata).toEqual({ priority: 'high', due: '2025-06-01' });
15+
expect(todos[2].metadata).toEqual({ due: '2025-01-01' });
16+
});
17+
18+
it('deve identificar corretamente as tags', () => {
19+
expect(todos[0].tag).toBe('TODO');
20+
expect(todos[1].tag).toBe('FIXME');
21+
expect(todos[2].tag).toBe('BUG');
22+
});
23+
24+
it('deve capturar corretamente os textos', () => {
25+
expect(todos[0].text).toBe('Refatorar este método');
26+
expect(todos[1].text).toBe('Corrigir possível vazamento de memória');
27+
expect(todos[2].text).toBe('Corrigir lógica de ordenação');
28+
});
29+
});

0 commit comments

Comments
 (0)