Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: 'Smart TODO Action'
description: 'Transforma comentários TODO/FIXME/BUG do código em GitHub Issues inteligentes.'
author: 'teu-nome-ou-org'

inputs:
repo-token:
description: 'GitHub token com permissão para criar issues'
required: true

runs:
using: 'node20'
main: 'dist/index.js'

branding:
icon: 'check-circle'
color: 'blue'
51 changes: 29 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
{
"name": "smart-todo-action",
"version": "0.1.0",
"description": "GitHub Action inteligente para transformar TODOs em issues e tarefas rastreáveis.",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"test": "vitest run",
"prepare": "ncc build src/main.ts -o dist"
},
"keywords": ["github-action", "todo", "issues", "task-tracking", "llm", "automation"],
"author": "",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1"
},
"devDependencies": {
"typescript": "^5.3.3",
"vitest": "^1.3.0",
"@types/node": "^20.11.17",
"@vercel/ncc": "^0.38.0"
}
"name": "smart-todo-action",
"version": "0.1.0",
"description": "GitHub Action inteligente para transformar TODOs em issues e tarefas rastreáveis.",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"test": "vitest run",
"prepare": "yarn ncc build src/ActionMain.ts -o dist"
},
"keywords": [
"github-action",
"todo",
"issues",
"task-tracking",
"llm",
"automation"
],
"author": "",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1"
},
"devDependencies": {
"@types/node": "^20.11.17",
"@vercel/ncc": "^0.38.3",
"typescript": "^5.3.3",
"vitest": "^1.3.0"
}
}
31 changes: 31 additions & 0 deletions src/ActionMain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as core from '@actions/core';
import * as github from '@actions/github';
import path from 'path';
import { extractTodosFromDir } from './parser/extractTodosFromDir';
import { TodoItem } from './parser/types';

async function run(): Promise<void> {
try {
const token = core.getInput('repo-token', { required: true });
const workspace = process.env.GITHUB_WORKSPACE || '.';

const todos: TodoItem[] = extractTodosFromDir(workspace);

core.info(`🔍 Encontrados ${todos.length} TODOs no repositório`);

for (const todo of todos) {
core.info(`📌 [${todo.tag}] ${todo.text} (${todo.file}:${todo.line})`);
}

const octokit = github.getOctokit(token);
const { owner, repo } = github.context.repo;

// No futuro: criar/atualizar issues aqui

core.info(`Contexto: ${owner}/${repo}`);
} catch (error: any) {
core.setFailed(`Erro na execução: ${error.message}`);
}
}

run();
54 changes: 54 additions & 0 deletions src/parser/extractTodos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import fs from 'fs';
import path from 'path';
import { TodoItem } from './types';

const COMMENT_PATTERNS = [
{ ext: ['.ts', '.js', '.java', '.go'], pattern: /^\s*\/\/\s*(.*)$/ },
{ ext: ['.py', '.sh', '.rb'], pattern: /^\s*#\s*(.*)$/ },
{ ext: ['.html', '.xml'], pattern: /<!--\s*(.*?)\s*-->/ }
];

const TAG_REGEX = /(TODO|FIXME|BUG|HACK)(\([^)]*\))?:?\s*(.*)/i;

function extractMetadata(str: string): Record<string, string> {
const meta: Record<string, string> = {};
const match = str.match(/\((.*?)\)/);
if (match) {
const content = match[1];
content.split(',').forEach(pair => {
const [key, val] = pair.split('=').map(s => s.trim());
if (key && val) meta[key] = val;
});
}
return meta;
}

export function extractTodosFromFile(filePath: string): TodoItem[] {
const ext = path.extname(filePath);
const pattern = COMMENT_PATTERNS.find(p => p.ext.includes(ext));
if (!pattern) return [];

const lines = fs.readFileSync(filePath, 'utf-8').split('\n');
const todos: TodoItem[] = [];

lines.forEach((line, idx) => {
const commentMatch = line.match(pattern.pattern);
if (commentMatch) {
const comment = commentMatch[1];
const tagMatch = comment.match(TAG_REGEX);
if (tagMatch) {
const [_, tag, metaRaw, text] = tagMatch;
const metadata = metaRaw ? extractMetadata(metaRaw) : undefined;
todos.push({
file: filePath,
line: idx + 1,
tag,
text: text.trim(),
metadata
});
}
}
});

return todos;
}
31 changes: 31 additions & 0 deletions src/parser/extractTodosFromDir.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import fs from 'fs';
import path from 'path';
import { extractTodosFromFile } from './extractTodos';
import { TodoItem } from './types';

const SUPPORTED_EXTENSIONS = ['.ts', '.js', '.py', '.go', '.java', '.rb', '.sh', '.html', '.xml'];

export function extractTodosFromDir(dirPath: string): TodoItem[] {
const todos: TodoItem[] = [];

function walk(currentPath: string) {
const entries = fs.readdirSync(currentPath, { withFileTypes: true });

for (const entry of entries) {
const fullPath = path.join(currentPath, entry.name);

if (entry.isDirectory()) {
walk(fullPath);
} else if (entry.isFile()) {
const ext = path.extname(entry.name);
if (SUPPORTED_EXTENSIONS.includes(ext)) {
const fileTodos = extractTodosFromFile(fullPath);
todos.push(...fileTodos);
}
}
}
}

walk(dirPath);
return todos;
}
8 changes: 8 additions & 0 deletions src/parser/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface TodoItem {
file: string;
line: number;
tag: string;
text: string;
metadata?: Record<string, string>;
}

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

29 changes: 29 additions & 0 deletions tests/parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, it, expect } from 'vitest';
import { extractTodosFromFile } from '../src/parser/extractTodos';
import path from 'path';

describe('extractTodosFromFile', () => {
const filePath = path.join(__dirname, 'example.ts');
const todos = extractTodosFromFile(filePath);

it('deve encontrar 3 TODOs', () => {
expect(todos.length).toBe(3);
});

it('deve extrair corretamente metadados', () => {
expect(todos[0].metadata).toEqual({ priority: 'high', due: '2025-06-01' });
expect(todos[2].metadata).toEqual({ due: '2025-01-01' });
});

it('deve identificar corretamente as tags', () => {
expect(todos[0].tag).toBe('TODO');
expect(todos[1].tag).toBe('FIXME');
expect(todos[2].tag).toBe('BUG');
});

it('deve capturar corretamente os textos', () => {
expect(todos[0].text).toBe('Refatorar este método');
expect(todos[1].text).toBe('Corrigir possível vazamento de memória');
expect(todos[2].text).toBe('Corrigir lógica de ordenação');
});
});
Loading
Loading