Skip to content

Commit 1ea06a3

Browse files
committed
chore: develop work
1 parent bab1e1d commit 1ea06a3

File tree

7 files changed

+355
-62
lines changed

7 files changed

+355
-62
lines changed

.github/workflows/todo.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ jobs:
2929
issue-title-template: src/templates/issueTitle.txt
3030
issue-body-template: src/templates/issueBody.md
3131
report: true
32+
llm: true
33+
env:
34+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
3235

3336
- name: Upload TODO report
3437
uses: actions/upload-artifact@v4
@@ -46,5 +49,4 @@ jobs:
4649
git add TODO_REPORT.md CHANGELOG.md
4750
git diff --cached --quiet && echo "No changes to commit." || git commit -m "chore(report): update TODO report and changelog [skip ci]"
4851
git push
49-
5052

action.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,19 @@ inputs:
2020
required: false
2121
description: Optional path to custom issue body template
2222

23+
llm:
24+
required: false
25+
description: Use LLM to generate issue titles and bodies
26+
default: 'false'
27+
28+
openai-api-key:
29+
required: false
30+
description: OpenAI API key used when `llm` is true
31+
2332
runs:
2433
using: 'node20'
2534
main: 'dist/index.js'
2635

2736
branding:
2837
icon: 'check-circle'
2938
color: 'blue'
30-

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@
2323
"dependencies": {
2424
"@actions/core": "^1.10.0",
2525
"@actions/github": "^5.1.1",
26-
"@octokit/rest": "^21.1.1"
26+
"@octokit/rest": "^21.1.1",
27+
"openai": "^4.95.0"
2728
},
2829
"devDependencies": {
29-
"@types/node": "^20.11.17",
30+
"@types/node": "^22.14.1",
3031
"@vercel/ncc": "^0.38.3",
3132
"@vitest/coverage-v8": "^3.1.1",
3233
"ts-node": "^10.9.2",

src/ActionMain.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ async function run(): Promise<void> {
1818
const bodyTemplatePath = core.getInput('issue-body-template');
1919
const workspace = process.env.GITHUB_WORKSPACE || '.';
2020

21+
// LLM support
22+
process.env.OPENAI_API_KEY = core.getInput('openai-api-key') || process.env.OPENAI_API_KEY;
23+
const useLLM = core.getInput('llm') === 'true';
24+
if (useLLM && !process.env.OPENAI_API_KEY) {
25+
core.warning('⚠️ LLM is enabled, but OPENAI_API_KEY is not set.');
26+
}
27+
2128
const todos: TodoItem[] = extractTodosFromDir(workspace);
2229
const octokit = github.getOctokit(token);
2330
const { owner, repo } = github.context.repo;
@@ -63,4 +70,3 @@ async function run(): Promise<void> {
6370
}
6471

6572
run();
66-

src/core/issueManager.ts

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

128
export async function getExistingIssueTitles(
139
octokit: ReturnType<typeof github.getOctokit>,
@@ -45,50 +41,64 @@ export async function getExistingIssueTitles(
4541
}
4642

4743
export async function createIssueIfNeeded(
48-
octokit: ReturnType<typeof github.getOctokit>,
49-
owner: string,
50-
repo: string,
51-
todo: TodoItem,
52-
existingTitles: Set<string>,
53-
titlePath?: string,
54-
bodyPath?: string
55-
): Promise<void> {
56-
const titleTemplate = loadTemplate('issueTitle.txt');
57-
const bodyTemplate = loadTemplate('issueBody.md');
58-
59-
const flattened = {
60-
...todo,
61-
...todo.metadata
62-
} as Record<string, string | number>;
63-
64-
const title = applyTemplate(titleTemplate, flattened);
65-
const body = applyTemplate(bodyTemplate, flattened);
66-
67-
68-
if (existingTitles.has(title)) {
69-
core.info(`🟡 Skipping duplicate issue: ${title}`);
70-
return;
71-
}
72-
73-
const labels = labelsFromTodo(todo);
44+
octokit: ReturnType<typeof github.getOctokit>,
45+
owner: string,
46+
repo: string,
47+
todo: TodoItem,
48+
existingTitles: Set<string>,
49+
titlePath?: string,
50+
bodyPath?: string
51+
): Promise<void> {
52+
const useLLM = core.getInput('llm') === 'true';
7453

75-
76-
for (const label of labels) {
77-
await ensureLabelExists(octokit, owner, repo, label);
78-
}
79-
54+
let title: string;
55+
let body: string;
56+
57+
if (useLLM) {
8058
try {
81-
await octokit.rest.issues.create({
82-
owner,
83-
repo,
84-
title,
85-
body,
86-
labels
87-
});
88-
89-
core.info(`✅ Created issue with labels [${labels.join(', ')}]: ${title}`);
59+
const result = await generateIssueTitleAndBodyLLM(todo);
60+
title = result.title;
61+
body = result.body;
9062
} catch (err: any) {
91-
core.warning(`⚠️ Failed to create issue for: ${title}${err.message}`);
63+
core.warning(`⚠️ LLM fallback triggered for TODO: ${todo.text}`);
64+
const titleTemplate = loadTemplate(titlePath || 'issueTitle.txt');
65+
const bodyTemplate = loadTemplate(bodyPath || 'issueBody.md');
66+
const flattened = { ...todo, ...todo.metadata } as Record<string, string | number>;
67+
title = applyTemplate(titleTemplate, flattened);
68+
body = applyTemplate(bodyTemplate, flattened);
9269
}
70+
} else {
71+
const titleTemplate = loadTemplate(titlePath || 'issueTitle.txt');
72+
const bodyTemplate = loadTemplate(bodyPath || 'issueBody.md');
73+
const flattened = { ...todo, ...todo.metadata } as Record<string, string | number>;
74+
title = applyTemplate(titleTemplate, flattened);
75+
body = applyTemplate(bodyTemplate, flattened);
76+
}
77+
78+
if (existingTitles.has(title)) {
79+
core.info(`🟡 Skipping duplicate issue: ${title}`);
80+
return;
81+
}
82+
83+
const labels = labelsFromTodo(todo);
84+
85+
for (const label of labels) {
86+
await ensureLabelExists(octokit, owner, repo, label);
9387
}
88+
89+
try {
90+
await octokit.rest.issues.create({
91+
owner,
92+
repo,
93+
title,
94+
body,
95+
labels
96+
});
97+
98+
core.info(`✅ Created issue with labels [${labels.join(', ')}]: ${title}`);
99+
} catch (err: any) {
100+
core.warning(`⚠️ Failed to create issue for: ${title}${err.message}`);
101+
}
102+
}
103+
94104

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// src/core/llm/generateIssueContent.ts
2+
import { TodoItem } from '../../parser/types';
3+
import OpenAI from 'openai';
4+
5+
const openai = new OpenAI({
6+
apiKey: process.env.OPENAI_API_KEY || '', // ou core.getInput('openai-api-key')
7+
});
8+
9+
export async function generateIssueTitleAndBodyLLM(todo: TodoItem): Promise<{ title: string; body: string }> {
10+
const prompt = `
11+
You are a helpful assistant converting inline TODO comments from source code into GitHub Issues.
12+
13+
The TODO is:
14+
- Tag: ${todo.tag}
15+
- Text: ${todo.text}
16+
- File: ${todo.file}
17+
- Line: ${todo.line}
18+
- Metadata: ${JSON.stringify(todo.metadata)}
19+
20+
Generate a concise and descriptive GitHub issue title, followed by a detailed body with context.
21+
22+
Format:
23+
TITLE: <title>
24+
BODY:
25+
<detailed body>
26+
`;
27+
28+
const response = await openai.chat.completions.create({
29+
model: 'gpt-4',
30+
messages: [{ role: 'user', content: prompt }],
31+
temperature: 0.4,
32+
});
33+
34+
const result = response.choices[0].message?.content || '';
35+
const match = result.match(/TITLE:\s*(.+?)\s*BODY:\s*([\s\S]*)/i);
36+
37+
if (!match) {
38+
throw new Error('Failed to parse LLM response.');
39+
}
40+
41+
const [, title, body] = match;
42+
return { title: title.trim(), body: body.trim() };
43+
}

0 commit comments

Comments
 (0)