gitpadi 2.0.3 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8,6 +8,7 @@ import chalk from 'chalk';
8
8
  import inquirer from 'inquirer';
9
9
  import gradient from 'gradient-string';
10
10
  import figlet from 'figlet';
11
+ import { execSync } from 'child_process';
11
12
  import boxen from 'boxen';
12
13
  import { createSpinner } from 'nanospinner';
13
14
  import { Octokit } from '@octokit/rest';
@@ -257,16 +258,50 @@ async function contributorMenu() {
257
258
  await contribute.viewLogs();
258
259
  }
259
260
  else if (action === 'submit') {
260
- const { title, message, issue } = await ask([
261
- { type: 'input', name: 'title', message: 'PR Title:' },
262
- { type: 'input', name: 'message', message: 'Commit message (optional):' },
263
- { type: 'input', name: 'issue', message: 'Related Issue # (optional):' }
261
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
262
+ const match = branch.match(/issue-(\d+)/);
263
+ const detectedIssue = match ? parseInt(match[1]) : undefined;
264
+ let detectedTitle = '';
265
+ if (detectedIssue) {
266
+ process.stdout.write(dim(` 🔍 Detected Issue #${detectedIssue} from branch... `));
267
+ try {
268
+ const { data: issueData } = await getOctokit().issues.get({
269
+ owner: getOwner(),
270
+ repo: getRepo(),
271
+ issue_number: detectedIssue,
272
+ });
273
+ detectedTitle = issueData.title;
274
+ console.log(cyan(`${detectedTitle}`));
275
+ }
276
+ catch {
277
+ console.log(dim(' (could not fetch title)'));
278
+ }
279
+ }
280
+ const { confirm } = await ask([
281
+ {
282
+ type: 'list',
283
+ name: 'confirm',
284
+ message: yellow('🚀 Ready to submit?'),
285
+ choices: [
286
+ { name: `✅ Yes, use automated metadata`, value: 'yes' },
287
+ { name: `✍️ Edit title/message manually`, value: 'edit' },
288
+ { name: `⬅ Back`, value: 'back' }
289
+ ]
290
+ }
264
291
  ]);
265
- await contribute.submitPR({
266
- title,
267
- message: message || title,
268
- issue: issue ? parseInt(issue) : undefined
269
- });
292
+ if (confirm === 'back')
293
+ return;
294
+ let submissionOpts = {};
295
+ if (confirm === 'edit') {
296
+ submissionOpts = await ask([
297
+ { type: 'input', name: 'title', message: 'PR Title:', default: detectedTitle ? `fix: ${detectedTitle}` : '' },
298
+ { type: 'input', name: 'message', message: 'Commit Message:', default: detectedTitle ? `fix: ${detectedTitle} (#${detectedIssue})` : '' },
299
+ { type: 'input', name: 'issue', message: 'Issue #:', default: detectedIssue ? detectedIssue.toString() : '' }
300
+ ]);
301
+ if (submissionOpts.issue)
302
+ submissionOpts.issue = parseInt(submissionOpts.issue);
303
+ }
304
+ await contribute.submitPR(submissionOpts);
270
305
  }
271
306
  }
272
307
  }
@@ -752,8 +787,8 @@ function setupCommander() {
752
787
  .option('-i, --issue <n>', 'Issue number')
753
788
  .action(async (o) => {
754
789
  await contribute.submitPR({
755
- title: o.title || 'Automated PR',
756
- message: o.message || o.title,
790
+ title: o.title,
791
+ message: o.message,
757
792
  issue: o.issue ? parseInt(o.issue) : undefined
758
793
  });
759
794
  });
@@ -235,9 +235,34 @@ export async function submitPR(opts) {
235
235
  const owner = getOwner();
236
236
  const repo = getRepo();
237
237
  const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
238
+ // Auto-infer issue from branch name (e.g. fix/issue-303)
239
+ let linkedIssue = opts.issue;
240
+ if (!linkedIssue) {
241
+ const match = branch.match(/issue-(\d+)/);
242
+ if (match)
243
+ linkedIssue = parseInt(match[1]);
244
+ }
245
+ const octokit = getOctokit();
246
+ // 2. Fetch Issue Details for better PR Metadata
247
+ let issueTitle = '';
248
+ if (linkedIssue) {
249
+ spinner.text = `Fetching details for issue #${linkedIssue}...`;
250
+ try {
251
+ const { data: issueData } = await octokit.issues.get({
252
+ owner,
253
+ repo,
254
+ issue_number: linkedIssue,
255
+ });
256
+ issueTitle = issueData.title;
257
+ }
258
+ catch (e) {
259
+ // Silently fallback if issue doesn't exist or no access
260
+ }
261
+ }
262
+ const prTitle = opts.title || (issueTitle ? `fix: ${issueTitle} (#${linkedIssue})` : 'Automated contribution via GitPadi');
263
+ const commitMsg = opts.message || prTitle;
238
264
  // 1. Stage and Commit
239
265
  spinner.text = 'Staging and committing changes...';
240
- const commitMsg = opts.message || opts.title || 'Automated contribution via GitPadi';
241
266
  try {
242
267
  execSync('git add .', { stdio: 'pipe' });
243
268
  // Check if there are changes to commit
@@ -250,13 +275,6 @@ export async function submitPR(opts) {
250
275
  // If commit fails (e.g. no changes), we might still want to push if there are unpushed commits
251
276
  dim(' (Note: No new changes to commit or commit failed)');
252
277
  }
253
- // Auto-infer issue from branch name (e.g. fix/issue-303)
254
- let linkedIssue = opts.issue;
255
- if (!linkedIssue) {
256
- const match = branch.match(/issue-(\d+)/);
257
- if (match)
258
- linkedIssue = parseInt(match[1]);
259
- }
260
278
  spinner.text = 'Pushing to your fork...';
261
279
  execSync(`git push origin ${branch}`, { stdio: 'pipe' });
262
280
  spinner.text = 'Creating Pull Request...';
@@ -269,10 +287,10 @@ export async function submitPR(opts) {
269
287
  catch {
270
288
  baseBranch = 'master';
271
289
  }
272
- const { data: pr } = await getOctokit().pulls.create({
290
+ const { data: pr } = await octokit.pulls.create({
273
291
  owner,
274
292
  repo,
275
- title: opts.title,
293
+ title: prTitle,
276
294
  body,
277
295
  head: `${await getAuthenticatedUser()}:${branch}`,
278
296
  base: baseBranch,
@@ -94,7 +94,10 @@ function parseMarkdownIssues(content) {
94
94
  for (let i = 1; i < lines.length; i++) {
95
95
  const labelsMatch = lines[i].match(/^\*\*Labels?:\*\*\s*(.+)/i);
96
96
  if (labelsMatch) {
97
- labels = labelsMatch[1].split(',').map(l => l.trim()).filter(Boolean);
97
+ // Strip backticks and trim
98
+ labels = labelsMatch[1].split(',')
99
+ .map(l => l.trim().replace(/^`+/, '').replace(/`+$/, ''))
100
+ .filter(Boolean);
98
101
  }
99
102
  else {
100
103
  bodyLines.push(lines[i]);
@@ -196,6 +199,21 @@ export async function createIssuesFromFile(filePath, opts) {
196
199
  await new Promise((r) => setTimeout(r, 1200));
197
200
  }
198
201
  catch (e) {
202
+ // If it's a 403 (unauthorized labels), try again without labels
203
+ if (e.status === 403 && issue.labels?.length > 0) {
204
+ try {
205
+ const { data } = await octokit.issues.create({
206
+ owner: getOwner(), repo: getRepo(), title: issue.title, body: issue.body,
207
+ });
208
+ created++;
209
+ console.log(` ${chalk.green('✅')} #${String(issue.number).padStart(2, '0')} → GitHub #${data.number} ${chalk.yellow('(labels skipped - permission)')}`);
210
+ await new Promise((r) => setTimeout(r, 1200));
211
+ continue;
212
+ }
213
+ catch (retryErr) {
214
+ e = retryErr;
215
+ }
216
+ }
199
217
  failed++;
200
218
  console.log(` ${chalk.red('❌')} #${String(issue.number).padStart(2, '0')}: ${e.message}`);
201
219
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitpadi",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "GitPadi — Your AI-powered GitHub management CLI. Create repos, manage issues & PRs, score contributors, and automate everything.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -287,16 +287,50 @@ async function contributorMenu() {
287
287
  } else if (action === 'logs') {
288
288
  await contribute.viewLogs();
289
289
  } else if (action === 'submit') {
290
- const { title, message, issue } = await ask([
291
- { type: 'input', name: 'title', message: 'PR Title:' },
292
- { type: 'input', name: 'message', message: 'Commit message (optional):' },
293
- { type: 'input', name: 'issue', message: 'Related Issue # (optional):' }
290
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
291
+ const match = branch.match(/issue-(\d+)/);
292
+ const detectedIssue = match ? parseInt(match[1]) : undefined;
293
+
294
+ let detectedTitle = '';
295
+ if (detectedIssue) {
296
+ process.stdout.write(dim(` 🔍 Detected Issue #${detectedIssue} from branch... `));
297
+ try {
298
+ const { data: issueData } = await getOctokit().issues.get({
299
+ owner: getOwner(),
300
+ repo: getRepo(),
301
+ issue_number: detectedIssue,
302
+ });
303
+ detectedTitle = issueData.title;
304
+ console.log(cyan(`${detectedTitle}`));
305
+ } catch { console.log(dim(' (could not fetch title)')); }
306
+ }
307
+
308
+ const { confirm } = await ask([
309
+ {
310
+ type: 'list',
311
+ name: 'confirm',
312
+ message: yellow('🚀 Ready to submit?'),
313
+ choices: [
314
+ { name: `✅ Yes, use automated metadata`, value: 'yes' },
315
+ { name: `✍️ Edit title/message manually`, value: 'edit' },
316
+ { name: `⬅ Back`, value: 'back' }
317
+ ]
318
+ }
294
319
  ]);
295
- await contribute.submitPR({
296
- title,
297
- message: message || title,
298
- issue: issue ? parseInt(issue) : undefined
299
- });
320
+
321
+ if (confirm === 'back') return;
322
+
323
+ let submissionOpts: any = {};
324
+ if (confirm === 'edit') {
325
+ submissionOpts = await ask([
326
+ { type: 'input', name: 'title', message: 'PR Title:', default: detectedTitle ? `fix: ${detectedTitle}` : '' },
327
+ { type: 'input', name: 'message', message: 'Commit Message:', default: detectedTitle ? `fix: ${detectedTitle} (#${detectedIssue})` : '' },
328
+ { type: 'input', name: 'issue', message: 'Issue #:', default: detectedIssue ? detectedIssue.toString() : '' }
329
+ ]);
330
+ if (submissionOpts.issue) submissionOpts.issue = parseInt(submissionOpts.issue);
331
+ }
332
+
333
+ await contribute.submitPR(submissionOpts);
300
334
  }
301
335
  }
302
336
  }
@@ -784,8 +818,8 @@ function setupCommander(): Command {
784
818
  .option('-i, --issue <n>', 'Issue number')
785
819
  .action(async (o) => {
786
820
  await contribute.submitPR({
787
- title: o.title || 'Automated PR',
788
- message: o.message || o.title,
821
+ title: o.title,
822
+ message: o.message,
789
823
  issue: o.issue ? parseInt(o.issue) : undefined
790
824
  });
791
825
  });
@@ -268,9 +268,36 @@ export async function submitPR(opts: { title: string, body?: string, issue?: num
268
268
  const repo = getRepo();
269
269
  const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
270
270
 
271
+ // Auto-infer issue from branch name (e.g. fix/issue-303)
272
+ let linkedIssue = opts.issue;
273
+ if (!linkedIssue) {
274
+ const match = branch.match(/issue-(\d+)/);
275
+ if (match) linkedIssue = parseInt(match[1]);
276
+ }
277
+
278
+ const octokit = getOctokit();
279
+
280
+ // 2. Fetch Issue Details for better PR Metadata
281
+ let issueTitle = '';
282
+ if (linkedIssue) {
283
+ spinner.text = `Fetching details for issue #${linkedIssue}...`;
284
+ try {
285
+ const { data: issueData } = await octokit.issues.get({
286
+ owner,
287
+ repo,
288
+ issue_number: linkedIssue,
289
+ });
290
+ issueTitle = issueData.title;
291
+ } catch (e) {
292
+ // Silently fallback if issue doesn't exist or no access
293
+ }
294
+ }
295
+
296
+ const prTitle = opts.title || (issueTitle ? `fix: ${issueTitle} (#${linkedIssue})` : 'Automated contribution via GitPadi');
297
+ const commitMsg = opts.message || prTitle;
298
+
271
299
  // 1. Stage and Commit
272
300
  spinner.text = 'Staging and committing changes...';
273
- const commitMsg = opts.message || opts.title || 'Automated contribution via GitPadi';
274
301
 
275
302
  try {
276
303
  execSync('git add .', { stdio: 'pipe' });
@@ -284,13 +311,6 @@ export async function submitPR(opts: { title: string, body?: string, issue?: num
284
311
  dim(' (Note: No new changes to commit or commit failed)');
285
312
  }
286
313
 
287
- // Auto-infer issue from branch name (e.g. fix/issue-303)
288
- let linkedIssue = opts.issue;
289
- if (!linkedIssue) {
290
- const match = branch.match(/issue-(\d+)/);
291
- if (match) linkedIssue = parseInt(match[1]);
292
- }
293
-
294
314
  spinner.text = 'Pushing to your fork...';
295
315
  execSync(`git push origin ${branch}`, { stdio: 'pipe' });
296
316
 
@@ -305,10 +325,10 @@ export async function submitPR(opts: { title: string, body?: string, issue?: num
305
325
  baseBranch = 'master';
306
326
  }
307
327
 
308
- const { data: pr } = await getOctokit().pulls.create({
328
+ const { data: pr } = await octokit.pulls.create({
309
329
  owner,
310
330
  repo,
311
- title: opts.title,
331
+ title: prTitle,
312
332
  body,
313
333
  head: `${await getAuthenticatedUser()}:${branch}`,
314
334
  base: baseBranch,
@@ -109,7 +109,10 @@ function parseMarkdownIssues(content: string): { issues: any[]; labels: Record<s
109
109
  for (let i = 1; i < lines.length; i++) {
110
110
  const labelsMatch = lines[i].match(/^\*\*Labels?:\*\*\s*(.+)/i);
111
111
  if (labelsMatch) {
112
- labels = labelsMatch[1].split(',').map(l => l.trim()).filter(Boolean);
112
+ // Strip backticks and trim
113
+ labels = labelsMatch[1].split(',')
114
+ .map(l => l.trim().replace(/^`+/, '').replace(/`+$/, ''))
115
+ .filter(Boolean);
113
116
  } else {
114
117
  bodyLines.push(lines[i]);
115
118
  }
@@ -214,6 +217,20 @@ export async function createIssuesFromFile(filePath: string, opts: { dryRun?: bo
214
217
  console.log(` ${chalk.green('✅')} #${String(issue.number).padStart(2, '0')} → GitHub #${data.number}`);
215
218
  await new Promise((r) => setTimeout(r, 1200));
216
219
  } catch (e: any) {
220
+ // If it's a 403 (unauthorized labels), try again without labels
221
+ if (e.status === 403 && issue.labels?.length > 0) {
222
+ try {
223
+ const { data } = await octokit.issues.create({
224
+ owner: getOwner(), repo: getRepo(), title: issue.title, body: issue.body,
225
+ });
226
+ created++;
227
+ console.log(` ${chalk.green('✅')} #${String(issue.number).padStart(2, '0')} → GitHub #${data.number} ${chalk.yellow('(labels skipped - permission)')}`);
228
+ await new Promise((r) => setTimeout(r, 1200));
229
+ continue;
230
+ } catch (retryErr: any) {
231
+ e = retryErr;
232
+ }
233
+ }
217
234
  failed++;
218
235
  console.log(` ${chalk.red('❌')} #${String(issue.number).padStart(2, '0')}: ${e.message}`);
219
236
  }