Skip to content

Commit bb5bfcb

Browse files
Merge pull request #102 from DiogoRibeiro7/chore/restart
Chore/restart
2 parents ad5b1ce + e5cda8d commit bb5bfcb

22 files changed

+385
-30
lines changed

.github/workflows/run_tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424

2525
- name: Run tests with coverage
2626
run: yarn vitest run --coverage
27+
2728

2829
# - name: Upload coverage to Codecov
2930
# uses: codecov/codecov-action@v5

.github/workflows/todo.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ jobs:
1212
issues: write
1313

1414
steps:
15-
- uses: actions/checkout@v3
15+
- uses: actions/checkout@v4
1616

1717
- name: Setup Node.js
18-
uses: actions/setup-node@v3
18+
uses: actions/setup-node@v4
1919
with:
2020
node-version: 20
2121

@@ -36,10 +36,15 @@ jobs:
3636
name: todo-report
3737
path: TODO_REPORT.md
3838

39-
- name: Commit TODO report
39+
- name: Generate Changelog from TODOs
40+
run: yarn changelog
41+
42+
- name: Commit TODO report and CHANGELOG
4043
run: |
4144
git config --global user.name "github-actions[bot]"
4245
git config --global user.email "github-actions[bot]@users.noreply.github.com"
43-
git add TODO_REPORT.md
44-
git commit -m "chore(report): update TODO report" || echo "No changes"
46+
git add TODO_REPORT.md CHANGELOG.md
47+
git diff --cached --quiet && echo "No changes to commit." || git commit -m "chore(report): update TODO report and changelog [skip ci]"
4548
git push
49+
50+

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# 📝 Changelog (from TODOs)
2+
3+
## TODO
4+
- .ts (`src/testTodo.ts:1`)
5+
6+
## TODO · refactor
7+
- Refactor this logic to improve performance (`src/testTodo.ts:2`)
8+
9+
## TODO · performance
10+
- Refactor this logic to improve performance (`src/testTodo.ts:2`)

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
"scripts": {
77
"build": "tsc",
88
"test": "vitest run",
9-
"prepare": "yarn ncc build src/ActionMain.ts -o dist"
9+
"prepare": "yarn ncc build src/ActionMain.ts -o dist",
10+
"changelog": "ts-node scripts/generateChangelog.ts",
11+
"build:dist": "ncc build src/ActionMain.ts -o dist"
1012
},
1113
"keywords": [
1214
"github-action",
@@ -27,6 +29,7 @@
2729
"@types/node": "^20.11.17",
2830
"@vercel/ncc": "^0.38.3",
2931
"@vitest/coverage-v8": "^3.1.1",
32+
"ts-node": "^10.9.2",
3033
"typescript": "^5.3.3",
3134
"vitest": "^3.1.1"
3235
}

scripts/generateChangelog.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import { extractTodosFromDir } from '../src/parser/extractTodosFromDir';
4+
import { classifyTodoText } from '../src/core/classifier';
5+
import { TodoItem } from '../src/parser/types';
6+
7+
function formatGroupHeader(tag: string, semantic: string, metadataKey?: string, metadataValue?: string): string {
8+
const parts = [tag];
9+
if (semantic) parts.push(semantic);
10+
if (metadataKey && metadataValue) parts.push(`${metadataKey}:${metadataValue}`);
11+
return `## ${parts.join(' · ')}`;
12+
}
13+
14+
function generateChangelogContent(todos: TodoItem[]): string {
15+
type GroupKey = string;
16+
const groups: Record<GroupKey, TodoItem[]> = {};
17+
18+
for (const todo of todos) {
19+
const semantics = classifyTodoText(todo.text);
20+
const metadataEntries = Object.entries(todo.metadata || {}) || [['', '']];
21+
const tag = todo.tag.toUpperCase();
22+
23+
for (const semantic of semantics.length ? semantics : ['']) {
24+
for (const [metaKey, metaValue] of metadataEntries.length ? metadataEntries : [['', '']]) {
25+
const key = JSON.stringify({ tag, semantic, metaKey, metaValue });
26+
if (!groups[key]) groups[key] = [];
27+
groups[key].push(todo);
28+
}
29+
}
30+
}
31+
32+
const output: string[] = ['# 📝 Changelog (from TODOs)', ''];
33+
34+
for (const key of Object.keys(groups)) {
35+
const { tag, semantic, metaKey, metaValue } = JSON.parse(key);
36+
output.push(formatGroupHeader(tag, semantic, metaKey, metaValue));
37+
38+
for (const todo of groups[key]) {
39+
output.push(`- ${todo.text} (\`${todo.file}:${todo.line}\`)`);
40+
}
41+
42+
output.push('');
43+
}
44+
45+
return output.join('\n');
46+
}
47+
48+
async function main() {
49+
const todos = await extractTodosFromDir('src');
50+
const changelog = generateChangelogContent(todos);
51+
fs.writeFileSync('CHANGELOG.md', changelog, 'utf8');
52+
console.log('✅ Changelog saved to CHANGELOG.md');
53+
}
54+
55+
main();

src/ActionMain.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
// src/ActionMain.ts
12
import * as core from '@actions/core';
23
import * as github from '@actions/github';
34
import path from 'path';
5+
import fs from 'fs';
46
import { extractTodosFromDir } from './parser/extractTodosFromDir';
57
import { TodoItem } from './parser/types';
68
import { getExistingIssueTitles, createIssueIfNeeded } from './core/issueManager';
79
import { generateMarkdownReport } from './core/report';
810
import { limitTodos, todoKey } from './core/todoUtils';
11+
import { generateChangelogFromTodos } from './core/changelog';
912

1013
async function run(): Promise<void> {
1114
try {
@@ -48,6 +51,10 @@ async function run(): Promise<void> {
4851
if (generateReport) {
4952
generateMarkdownReport(todos);
5053
core.info('📝 Generated TODO_REPORT.md');
54+
55+
const changelog = generateChangelogFromTodos(todos);
56+
fs.writeFileSync('CHANGELOG.md', changelog, 'utf8');
57+
core.info('📦 Generated CHANGELOG.md');
5158
}
5259

5360
} catch (error: any) {
@@ -56,3 +63,4 @@ async function run(): Promise<void> {
5663
}
5764

5865
run();
66+

src/core/changelog.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// src/core/changelog.ts
2+
import { TodoItem } from '../parser/types';
3+
import { classifyTodoText } from './classifier';
4+
5+
function formatGroupHeader(tag: string, semantic: string, metadataKey?: string, metadataValue?: string): string {
6+
const parts = [tag];
7+
if (semantic) parts.push(semantic);
8+
if (metadataKey && metadataValue) parts.push(`${metadataKey}:${metadataValue}`);
9+
return `## ${parts.join(' · ')}`;
10+
}
11+
12+
export function generateChangelogFromTodos(todos: TodoItem[]): string {
13+
type GroupKey = string;
14+
const groups: Record<GroupKey, TodoItem[]> = {};
15+
16+
for (const todo of todos) {
17+
const semantics = classifyTodoText(todo.text);
18+
const metadataEntries = Object.entries(todo.metadata || {}) || [['', '']];
19+
const tag = todo.tag.toUpperCase();
20+
21+
for (const semantic of semantics.length ? semantics : ['']) {
22+
for (const [metaKey, metaValue] of metadataEntries.length ? metadataEntries : [['', '']]) {
23+
const key = JSON.stringify({ tag, semantic, metaKey, metaValue });
24+
if (!groups[key]) groups[key] = [];
25+
groups[key].push(todo);
26+
}
27+
}
28+
}
29+
30+
const output: string[] = ['# 📝 Changelog (from TODOs)', ''];
31+
32+
for (const key of Object.keys(groups)) {
33+
const { tag, semantic, metaKey, metaValue } = JSON.parse(key);
34+
output.push(formatGroupHeader(tag, semantic, metaKey, metaValue));
35+
36+
for (const todo of groups[key]) {
37+
output.push(`- ${todo.text} (\`${todo.file}:${todo.line}\`)`);
38+
}
39+
40+
output.push('');
41+
}
42+
43+
return output.join('\n');
44+
}

src/core/classifier.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* Classifies a given TODO text into one or more predefined categories based on its content.
3+
*
4+
* @param text - The TODO text to classify.
5+
* @returns An array of strings representing the labels that match the content of the text.
6+
*
7+
* The function identifies the following categories:
8+
* - `refactor`: Matches keywords related to code refactoring, simplification, or optimization.
9+
* - `test`: Matches keywords related to testing, such as adding tests or verifying functionality.
10+
* - `doc`: Matches keywords related to documentation, comments, or explaining code.
11+
* - `performance`: Matches keywords related to performance improvements or latency issues.
12+
* - `security`: Matches keywords related to security concerns, vulnerabilities, or sanitization.
13+
* - `maintenance`: Matches keywords related to deprecation, migration, upgrades, or legacy code removal.
14+
*/
15+
export function classifyTodoText(text: string): string[] {
16+
const lower = text.toLowerCase();
17+
const labels = new Set<string>();
18+
19+
// Refactor / cleanup
20+
if (
21+
/\b(refactor|simplify|clean[\s\-]?up|restructure|optimi[sz]e|rework|rewrite)\b/.test(lower)
22+
) {
23+
labels.add('refactor');
24+
}
25+
26+
// Testing
27+
if (
28+
/\b(tests?|add(ed)? tests?|unit tests?|test\s+coverage|verify|assert)\b/.test(lower)
29+
) {
30+
labels.add('test');
31+
}
32+
33+
// Documentation
34+
if (
35+
/\b(docs?|documentation|comment[s]?|explain|document(ed|ing)?)\b/.test(lower)
36+
) {
37+
labels.add('doc');
38+
}
39+
40+
// Performance
41+
if (
42+
/\b(performance|perf|slow|latency|optimi[sz]e)\b/.test(lower)
43+
) {
44+
labels.add('performance');
45+
}
46+
47+
// Security
48+
if (
49+
/\b(security|vuln(?:erability)?|injection|auth|encrypt|sanitize)\b/.test(lower)
50+
) {
51+
labels.add('security');
52+
}
53+
54+
// Deprecation / migration / upgrade
55+
if (
56+
/\b(deprecat(e|ed|ing)?|migrat(e|ed|ing)?|upgrade[d]?|legacy|remove[d]?)\b/.test(lower)
57+
) {
58+
labels.add('maintenance');
59+
}
60+
61+
return Array.from(labels);
62+
}
63+
64+

src/core/issueManager.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import * as core from '@actions/core';
22
import * as github from '@actions/github';
33
import { TodoItem } from '../parser/types';
4-
import { LABELS_BY_TAG, labelsFromMetadata, ensureLabelExists } from './labelManager';
4+
import {
5+
LABELS_BY_TAG,
6+
labelsFromMetadata,
7+
ensureLabelExists,
8+
labelsFromTodo // ⬅️ novo
9+
} from './labelManager';
510
import { loadTemplate, applyTemplate } from '../templates/utils';
611

712
export async function getExistingIssueTitles(
@@ -65,10 +70,8 @@ export async function createIssueIfNeeded(
6570
return;
6671
}
6772

68-
const tag = todo.tag.toUpperCase();
69-
const baseLabels = LABELS_BY_TAG[tag] || ['todo'];
70-
const metaLabels = labelsFromMetadata(todo.metadata);
71-
const labels = [...baseLabels, ...metaLabels];
73+
const labels = labelsFromTodo(todo);
74+
7275

7376
for (const label of labels) {
7477
await ensureLabelExists(octokit, owner, repo, label);

src/core/labelManager.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as github from '@actions/github';
22
import * as core from '@actions/core';
3+
import { TodoItem } from '../parser/types';
4+
import { classifyTodoText } from './classifier'; // Novo: classificador heurístico ou LLM
35

46
// Labels atribuídas por tipo de comentário
57
export const LABELS_BY_TAG: Record<string, string[]> = {
@@ -14,7 +16,10 @@ export const LABEL_COLORS: Record<string, string> = {
1416
bug: 'd73a4a',
1517
enhancement: 'a2eeef',
1618
todo: 'cfd3d7',
17-
'technical-debt': 'e99695'
19+
'technical-debt': 'e99695',
20+
refactor: 'f9d0c4',
21+
test: 'fef2c0',
22+
doc: '0075ca'
1823
};
1924

2025
// Fallback para labels metadata:priority, due, etc.
@@ -23,6 +28,16 @@ export function labelsFromMetadata(metadata?: Record<string, string>): string[]
2328
return Object.entries(metadata).map(([key, value]) => `${key}:${value}`);
2429
}
2530

31+
// Novo: combina tag, metadata e classificação semântica
32+
export function labelsFromTodo(todo: TodoItem): string[] {
33+
const tag = todo.tag.toUpperCase();
34+
const tagLabels = LABELS_BY_TAG[tag] || ['todo'];
35+
const metaLabels = labelsFromMetadata(todo.metadata);
36+
const semanticLabels = classifyTodoText(todo.text); // ← vem de `classifier.ts`
37+
38+
return Array.from(new Set([...tagLabels, ...metaLabels, ...semanticLabels]));
39+
}
40+
2641
// Garante que uma label existe no repositório
2742
export async function ensureLabelExists(
2843
octokit: ReturnType<typeof github.getOctokit>,
@@ -49,3 +64,4 @@ export async function ensureLabelExists(
4964
}
5065
}
5166
}
67+

0 commit comments

Comments
 (0)