gitpadi 2.1.3 → 2.1.4

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
@@ -26,7 +26,7 @@ import { dripsMenu } from './commands/drips.js';
26
26
  import * as gitlabIssues from './commands/gitlab-issues.js';
27
27
  import * as gitlabMRs from './commands/gitlab-mrs.js';
28
28
  import * as gitlabPipelines from './commands/gitlab-pipelines.js';
29
- const VERSION = '2.1.3';
29
+ const VERSION = '2.1.4';
30
30
  let targetConfirmed = false;
31
31
  let gitlabProjectConfirmed = false;
32
32
  // ── Styling ────────────────────────────────────────────────────────────
@@ -96,6 +96,11 @@ function parseSlug(input) {
96
96
  function issueLabels(issue) {
97
97
  return issue.labels.map(l => l.name.toLowerCase());
98
98
  }
99
+ const ORG_APPLICATION_LIMIT = 4;
100
+ /** Extract the org/owner from a repo fullName like "myorg/myrepo" */
101
+ function extractOrg(issue) {
102
+ return issue.repo?.fullName?.split('/')[0]?.toLowerCase() || '__unknown__';
103
+ }
99
104
  /** Detect which track an issue belongs to based on its labels and title */
100
105
  function detectTrack(issue) {
101
106
  const haystack = [...issueLabels(issue), issue.title.toLowerCase()].join(' ');
@@ -265,13 +270,12 @@ async function setupProfile(existing) {
265
270
  type: 'list',
266
271
  name: 'minPoints',
267
272
  message: bold('Minimum points to apply for:'),
268
- default: existing?.minPoints || 0,
273
+ default: existing?.minPoints ?? 100,
269
274
  choices: [
270
- { name: ` Any points (including unspecified)`, value: 0 },
271
- { name: ` 50+ pts`, value: 50 },
272
- { name: ` 100+ pts`, value: 100 },
273
- { name: ` 200+ pts`, value: 200 },
274
- { name: ` 500+ pts`, value: 500 },
275
+ { name: ` Any (including unspecified)`, value: 0 },
276
+ { name: ` 100 pts`, value: 100 },
277
+ { name: ` 150 pts`, value: 150 },
278
+ { name: ` 200 pts`, value: 200 },
275
279
  ],
276
280
  }]);
277
281
  const techStack = stackInput.split(',').map((s) => s.trim()).filter(Boolean);
@@ -344,15 +348,23 @@ async function autoApply(program, profile) {
344
348
  }
345
349
  console.log();
346
350
  // Checkbox selection
351
+ // Pre-compute org counts to show how many slots remain per org
352
+ const previewOrgCounts = new Map();
347
353
  const checkboxChoices = top.map(({ issue, score, matchReasons }) => {
348
354
  const pts = issue.points ? green(`+${issue.points}pts`) : dim('—pts');
349
355
  const applicants = issue.pendingApplicationsCount > 0 ? yellow(`${issue.pendingApplicationsCount}▲`) : dim('0▲');
350
356
  const match = matchReasons.length > 0 ? cyan(`[${matchReasons.slice(0, 3).join(', ')}]`) : '';
351
- const title = truncate(issue.title, 44);
357
+ const org = extractOrg(issue);
358
+ const orgSlots = ORG_APPLICATION_LIMIT - (previewOrgCounts.get(org) || 0);
359
+ const orgTag = dim(`(${org} ${orgSlots}/${ORG_APPLICATION_LIMIT} slots)`);
360
+ const title = truncate(issue.title, 40);
361
+ const autoCheck = score >= 1 && orgSlots > 0;
362
+ if (autoCheck)
363
+ previewOrgCounts.set(org, (previewOrgCounts.get(org) || 0) + 1);
352
364
  return {
353
- name: ` ${pts} ${bold(title)} ${match} ${applicants}`,
365
+ name: ` ${pts} ${bold(title)} ${match} ${applicants} ${orgTag}`,
354
366
  value: issue,
355
- checked: score >= 1, // pre-select strong matches
367
+ checked: autoCheck,
356
368
  };
357
369
  });
358
370
  const { selectedIssues } = await inquirer.prompt([{
@@ -413,11 +425,21 @@ async function autoApply(program, profile) {
413
425
  console.log();
414
426
  let applied = 0;
415
427
  let failed = 0;
428
+ let skipped = 0;
429
+ const orgCounts = new Map();
416
430
  for (const issue of selectedIssues) {
431
+ const org = extractOrg(issue);
432
+ const orgCount = orgCounts.get(org) || 0;
433
+ if (orgCount >= ORG_APPLICATION_LIMIT) {
434
+ console.log(yellow(` ⚠ Skipped: ${truncate(issue.title, 50)} — already applied to ${ORG_APPLICATION_LIMIT} issues from ${org}`));
435
+ skipped++;
436
+ continue;
437
+ }
417
438
  const spinner = ora(` Applying to: ${truncate(issue.title, 50)}…`).start();
418
439
  try {
419
440
  await dripsPost(`/api/wave-programs/${program.id}/issues/${issue.id}/applications`, { applicationText: baseMessage }, token);
420
- spinner.succeed(green(` ✅ ${truncate(issue.title, 55)}`) + (issue.points ? dim(` (+${issue.points}pts)`) : ''));
441
+ orgCounts.set(org, orgCount + 1);
442
+ spinner.succeed(green(` ✅ ${truncate(issue.title, 55)}`) + (issue.points ? dim(` (+${issue.points}pts)`) : '') + dim(` [${org}: ${orgCount + 1}/${ORG_APPLICATION_LIMIT}]`));
421
443
  applied++;
422
444
  }
423
445
  catch (e) {
@@ -431,7 +453,7 @@ async function autoApply(program, profile) {
431
453
  }
432
454
  }
433
455
  console.log();
434
- console.log(bold(` Done: ${green(applied + ' applied')}${failed > 0 ? ', ' + red(failed + ' failed') : ''}`));
456
+ console.log(bold(` Done: ${green(applied + ' applied')}${skipped > 0 ? ', ' + yellow(skipped + ' skipped (org limit)') : ''}${failed > 0 ? ', ' + red(failed + ' failed') : ''}`));
435
457
  console.log(dim(`\n Track your applications: ${cyan(DRIPS_WEB + '/wave/' + program.slug)}\n`));
436
458
  }
437
459
  // ── Browse by track ───────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitpadi",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "GitPadi — AI-powered GitHub & GitLab management CLI. Fork repos, manage issues & PRs, score contributors, grade assignments, and automate everything. Powered by Anthropic Claude via GitLab Duo Agent Platform.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -35,7 +35,7 @@ import * as gitlabIssues from './commands/gitlab-issues.js';
35
35
  import * as gitlabMRs from './commands/gitlab-mrs.js';
36
36
  import * as gitlabPipelines from './commands/gitlab-pipelines.js';
37
37
 
38
- const VERSION = '2.1.3';
38
+ const VERSION = '2.1.4';
39
39
  let targetConfirmed = false;
40
40
  let gitlabProjectConfirmed = false;
41
41
 
@@ -154,6 +154,13 @@ function issueLabels(issue: DripsIssue): string[] {
154
154
  return issue.labels.map(l => l.name.toLowerCase());
155
155
  }
156
156
 
157
+ const ORG_APPLICATION_LIMIT = 4;
158
+
159
+ /** Extract the org/owner from a repo fullName like "myorg/myrepo" */
160
+ function extractOrg(issue: DripsIssue): string {
161
+ return issue.repo?.fullName?.split('/')[0]?.toLowerCase() || '__unknown__';
162
+ }
163
+
157
164
  /** Detect which track an issue belongs to based on its labels and title */
158
165
  function detectTrack(issue: DripsIssue): Exclude<Track, 'all'> | null {
159
166
  const haystack = [...issueLabels(issue), issue.title.toLowerCase()].join(' ');
@@ -335,13 +342,12 @@ async function setupProfile(existing?: DripsProfile): Promise<DripsProfile> {
335
342
  type: 'list',
336
343
  name: 'minPoints',
337
344
  message: bold('Minimum points to apply for:'),
338
- default: existing?.minPoints || 0,
345
+ default: existing?.minPoints ?? 100,
339
346
  choices: [
340
- { name: ` Any points (including unspecified)`, value: 0 },
341
- { name: ` 50+ pts`, value: 50 },
342
- { name: ` 100+ pts`, value: 100 },
343
- { name: ` 200+ pts`, value: 200 },
344
- { name: ` 500+ pts`, value: 500 },
347
+ { name: ` Any (including unspecified)`, value: 0 },
348
+ { name: ` 100 pts`, value: 100 },
349
+ { name: ` 150 pts`, value: 150 },
350
+ { name: ` 200 pts`, value: 200 },
345
351
  ],
346
352
  }]);
347
353
 
@@ -427,15 +433,22 @@ async function autoApply(program: DripsProgram, profile: DripsProfile): Promise<
427
433
  console.log();
428
434
 
429
435
  // Checkbox selection
436
+ // Pre-compute org counts to show how many slots remain per org
437
+ const previewOrgCounts = new Map<string, number>();
430
438
  const checkboxChoices = top.map(({ issue, score, matchReasons }) => {
431
439
  const pts = issue.points ? green(`+${issue.points}pts`) : dim('—pts');
432
440
  const applicants = issue.pendingApplicationsCount > 0 ? yellow(`${issue.pendingApplicationsCount}▲`) : dim('0▲');
433
441
  const match = matchReasons.length > 0 ? cyan(`[${matchReasons.slice(0, 3).join(', ')}]`) : '';
434
- const title = truncate(issue.title, 44);
442
+ const org = extractOrg(issue);
443
+ const orgSlots = ORG_APPLICATION_LIMIT - (previewOrgCounts.get(org) || 0);
444
+ const orgTag = dim(`(${org} ${orgSlots}/${ORG_APPLICATION_LIMIT} slots)`);
445
+ const title = truncate(issue.title, 40);
446
+ const autoCheck = score >= 1 && orgSlots > 0;
447
+ if (autoCheck) previewOrgCounts.set(org, (previewOrgCounts.get(org) || 0) + 1);
435
448
  return {
436
- name: ` ${pts} ${bold(title)} ${match} ${applicants}`,
449
+ name: ` ${pts} ${bold(title)} ${match} ${applicants} ${orgTag}`,
437
450
  value: issue,
438
- checked: score >= 1, // pre-select strong matches
451
+ checked: autoCheck,
439
452
  };
440
453
  });
441
454
 
@@ -502,8 +515,19 @@ async function autoApply(program: DripsProgram, profile: DripsProfile): Promise<
502
515
  console.log();
503
516
  let applied = 0;
504
517
  let failed = 0;
518
+ let skipped = 0;
519
+ const orgCounts = new Map<string, number>();
505
520
 
506
521
  for (const issue of selectedIssues as DripsIssue[]) {
522
+ const org = extractOrg(issue);
523
+ const orgCount = orgCounts.get(org) || 0;
524
+
525
+ if (orgCount >= ORG_APPLICATION_LIMIT) {
526
+ console.log(yellow(` ⚠ Skipped: ${truncate(issue.title, 50)} — already applied to ${ORG_APPLICATION_LIMIT} issues from ${org}`));
527
+ skipped++;
528
+ continue;
529
+ }
530
+
507
531
  const spinner = ora(` Applying to: ${truncate(issue.title, 50)}…`).start();
508
532
  try {
509
533
  await dripsPost(
@@ -511,7 +535,8 @@ async function autoApply(program: DripsProgram, profile: DripsProfile): Promise<
511
535
  { applicationText: baseMessage },
512
536
  token,
513
537
  );
514
- spinner.succeed(green(` ✅ ${truncate(issue.title, 55)}`) + (issue.points ? dim(` (+${issue.points}pts)`) : ''));
538
+ orgCounts.set(org, orgCount + 1);
539
+ spinner.succeed(green(` ✅ ${truncate(issue.title, 55)}`) + (issue.points ? dim(` (+${issue.points}pts)`) : '') + dim(` [${org}: ${orgCount + 1}/${ORG_APPLICATION_LIMIT}]`));
515
540
  applied++;
516
541
  } catch (e: any) {
517
542
  spinner.fail(red(` ✗ ${truncate(issue.title, 55)} — ${e.message}`));
@@ -525,7 +550,7 @@ async function autoApply(program: DripsProgram, profile: DripsProfile): Promise<
525
550
  }
526
551
 
527
552
  console.log();
528
- console.log(bold(` Done: ${green(applied + ' applied')}${failed > 0 ? ', ' + red(failed + ' failed') : ''}`));
553
+ console.log(bold(` Done: ${green(applied + ' applied')}${skipped > 0 ? ', ' + yellow(skipped + ' skipped (org limit)') : ''}${failed > 0 ? ', ' + red(failed + ' failed') : ''}`));
529
554
  console.log(dim(`\n Track your applications: ${cyan(DRIPS_WEB + '/wave/' + program.slug)}\n`));
530
555
  }
531
556