gitpadi 2.1.6 β 2.1.8
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 +4 -0
- package/dist/commands/drips.js +60 -68
- package/dist/commands/issues.js +1 -1
- package/dist/remind-contributors.js +5 -2
- package/package.json +1 -1
- package/src/cli.ts +3 -0
- package/src/commands/drips.ts +82 -72
- package/src/commands/issues.ts +1 -1
- package/src/remind-contributors.ts +5 -2
package/dist/cli.js
CHANGED
|
@@ -23,6 +23,7 @@ import * as contribute from './commands/contribute.js';
|
|
|
23
23
|
import * as applyForIssue from './commands/apply-for-issue.js';
|
|
24
24
|
import { runBountyHunter } from './commands/bounty-hunter.js';
|
|
25
25
|
import { dripsMenu } from './commands/drips.js';
|
|
26
|
+
import { remindContributors } from './remind-contributors.js';
|
|
26
27
|
import * as gitlabIssues from './commands/gitlab-issues.js';
|
|
27
28
|
import * as gitlabMRs from './commands/gitlab-mrs.js';
|
|
28
29
|
import * as gitlabPipelines from './commands/gitlab-pipelines.js';
|
|
@@ -1075,6 +1076,7 @@ async function maintainerMenu() {
|
|
|
1075
1076
|
{ name: `${green('π¦')} ${bold('Repositories')} ${dim('β create, delete, clone, topics, info')}`, value: 'repos' },
|
|
1076
1077
|
{ name: `${red('π')} ${bold('Releases')} ${dim('β create, list, tag releases')}`, value: 'releases' },
|
|
1077
1078
|
{ name: `${green('π―')} ${bold('Bounty Hunter')} ${dim('β auto-apply to Drips Wave & GrantFox')}`, value: 'hunt' },
|
|
1079
|
+
{ name: `${cyan('π')} ${bold('Remind Contributors')} ${dim('β warn assignees with no PR (12h)')}`, value: 'remind' },
|
|
1078
1080
|
new inquirer.Separator(dim(' βββββββββββββββββββββββββββββββββββββββββ')),
|
|
1079
1081
|
{ name: `${dim('βοΈ')} ${dim('Switch Repo')}`, value: 'switch' },
|
|
1080
1082
|
{ name: `${dim('β¬
')} ${dim('Back to Mode Selector')}`, value: 'back' },
|
|
@@ -1100,6 +1102,8 @@ async function maintainerMenu() {
|
|
|
1100
1102
|
await safeMenu(releaseMenu);
|
|
1101
1103
|
else if (category === 'hunt')
|
|
1102
1104
|
await runBountyHunter({ platform: 'all', maxApplications: 3, dryRun: false });
|
|
1105
|
+
else if (category === 'remind')
|
|
1106
|
+
await safeMenu(remindContributors);
|
|
1103
1107
|
else if (category === 'review-merge') {
|
|
1104
1108
|
await ensureTargetRepo();
|
|
1105
1109
|
const { prNum } = await ask([{ type: 'input', name: 'prNum', message: cyan('PR number to review:') }]);
|
package/dist/commands/drips.js
CHANGED
|
@@ -218,6 +218,19 @@ export async function ensureDripsAuth() {
|
|
|
218
218
|
else {
|
|
219
219
|
console.log(dim(`\n Open manually: ${cyan(DRIPS_WEB + '/wave/login')}\n`));
|
|
220
220
|
}
|
|
221
|
+
console.log(dim(' After logging in, get your token:'));
|
|
222
|
+
console.log();
|
|
223
|
+
console.log(dim(' 1. Press ') + bold('F12') + dim(' β click the ') + bold('Console') + dim(' tab'));
|
|
224
|
+
console.log(dim(' 2. Paste this command and press ') + bold('Enter') + dim(':'));
|
|
225
|
+
console.log();
|
|
226
|
+
console.log(' ' + cyan('document.cookie.match(/wave_access_token=([^;]+)/)?.[1]'));
|
|
227
|
+
console.log();
|
|
228
|
+
console.log(dim(' 3. Copy the value shown (starts with ') + yellow('eyJ...') + dim(')'));
|
|
229
|
+
console.log();
|
|
230
|
+
console.log(dim(' ββ If Console shows "undefined", use this instead: ββββββββββ'));
|
|
231
|
+
console.log(dim(' F12 β Application tab β Cookies β www.drips.network'));
|
|
232
|
+
console.log(dim(' Find ') + yellow('wave_access_token') + dim(' β copy the Value column'));
|
|
233
|
+
console.log();
|
|
221
234
|
const { token } = await inquirer.prompt([{
|
|
222
235
|
type: 'password',
|
|
223
236
|
name: 'token',
|
|
@@ -456,36 +469,26 @@ async function autoApply(program, profile) {
|
|
|
456
469
|
console.log(bold(` Done: ${green(applied + ' applied')}${skipped > 0 ? ', ' + yellow(skipped + ' skipped (org limit)') : ''}${failed > 0 ? ', ' + red(failed + ' failed') : ''}`));
|
|
457
470
|
console.log(dim(`\n Track your applications: ${cyan(DRIPS_WEB + '/wave/' + program.slug)}\n`));
|
|
458
471
|
}
|
|
459
|
-
// ββ Repo/org search βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
460
472
|
/**
|
|
461
|
-
*
|
|
462
|
-
* contains the search term (case-insensitive).
|
|
473
|
+
* Uses the Drips repos API to search by org/repo name β fast, covers ALL repos.
|
|
463
474
|
*/
|
|
464
475
|
async function searchRepos(program, term) {
|
|
465
|
-
const
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
else {
|
|
482
|
-
repoMap.set(fullName, { org, repo, fullName, issueCount: 1 });
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
if (!res.pagination.hasNextPage)
|
|
486
|
-
break;
|
|
487
|
-
}
|
|
488
|
-
return Array.from(repoMap.values()).sort((a, b) => b.issueCount - a.issueCount);
|
|
476
|
+
const data = await dripsGet(`/api/wave-programs/${program.id}/repos?search=${encodeURIComponent(term)}&limit=20&status=approved`);
|
|
477
|
+
const items = Array.isArray(data?.data) ? data.data : (data ? [data] : []);
|
|
478
|
+
return items.map((item) => ({
|
|
479
|
+
orgId: item.org?.id || '',
|
|
480
|
+
orgLogin: item.org?.gitHubOrgLogin || '',
|
|
481
|
+
repoFullName: item.repo?.gitHubRepoFullName || '',
|
|
482
|
+
repoName: item.repo?.gitHubRepoName || '',
|
|
483
|
+
issueCount: item.issueCount || 0,
|
|
484
|
+
description: item.description || '',
|
|
485
|
+
})).filter(r => r.orgId && r.repoFullName);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Fetch issues filtered by Drips orgId β returns all issues for that org directly.
|
|
489
|
+
*/
|
|
490
|
+
async function getIssuesByOrg(programId, orgId, page, limit = 50) {
|
|
491
|
+
return dripsGet(`/api/issues?waveProgramId=${programId}&orgId=${orgId}&state=open&page=${page}&limit=${limit}&sortBy=points`);
|
|
489
492
|
}
|
|
490
493
|
// ββ Browse by org + track βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
491
494
|
async function browseByTrack(program, profile) {
|
|
@@ -514,48 +517,48 @@ async function browseByTrack(program, profile) {
|
|
|
514
517
|
}
|
|
515
518
|
searchSpinner.succeed(` Found ${matches.length} repo(s) matching "${term}"`);
|
|
516
519
|
console.log();
|
|
517
|
-
|
|
518
|
-
// Value is the full repo name (org/repo) so we can filter precisely
|
|
519
|
-
let targetFullName; // e.g. "inheritx/smart-contracts" or "stellar" (org-level)
|
|
520
|
+
let targetOrgId;
|
|
520
521
|
let displayLabel;
|
|
521
|
-
const exactOrg = matches.find(m => m.
|
|
522
|
-
const exactRepo = matches.find(m => m.
|
|
522
|
+
const exactOrg = matches.find(m => m.orgLogin.toLowerCase() === term.toLowerCase());
|
|
523
|
+
const exactRepo = matches.find(m => m.repoFullName.toLowerCase() === term.toLowerCase());
|
|
523
524
|
if (matches.length === 1) {
|
|
524
|
-
|
|
525
|
-
displayLabel = matches[0].
|
|
525
|
+
targetOrgId = matches[0].orgId;
|
|
526
|
+
displayLabel = matches[0].repoFullName;
|
|
526
527
|
console.log(dim(` Using repo: ${cyan(displayLabel)}\n`));
|
|
527
528
|
}
|
|
528
529
|
else if (exactRepo) {
|
|
529
|
-
|
|
530
|
-
displayLabel = exactRepo.
|
|
530
|
+
targetOrgId = exactRepo.orgId;
|
|
531
|
+
displayLabel = exactRepo.repoFullName;
|
|
531
532
|
console.log(dim(` Using repo: ${cyan(displayLabel)}\n`));
|
|
532
533
|
}
|
|
533
534
|
else if (exactOrg) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
console.log(dim(` Using org: ${cyan(exactOrg.org)} (all repos)\n`));
|
|
535
|
+
targetOrgId = exactOrg.orgId;
|
|
536
|
+
displayLabel = exactOrg.orgLogin + '/*';
|
|
537
|
+
console.log(dim(` Using org: ${cyan(exactOrg.orgLogin)} (all repos)\n`));
|
|
538
538
|
}
|
|
539
539
|
else {
|
|
540
|
-
// Multiple partial matches β let user pick
|
|
540
|
+
// Multiple partial matches β let user pick
|
|
541
541
|
const repoChoices = [];
|
|
542
|
-
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
542
|
+
const orgGroups = new Map();
|
|
543
|
+
for (const r of matches) {
|
|
544
|
+
const list = orgGroups.get(r.orgLogin) || [];
|
|
545
|
+
list.push(r);
|
|
546
|
+
orgGroups.set(r.orgLogin, list);
|
|
547
|
+
}
|
|
548
|
+
for (const [org, orgRepos] of orgGroups) {
|
|
546
549
|
if (orgRepos.length > 1) {
|
|
547
550
|
const total = orgRepos.reduce((s, r) => s + r.issueCount, 0);
|
|
548
551
|
repoChoices.push({
|
|
549
552
|
name: ` ${cyan(org + '/*')} ${dim(`all ${orgRepos.length} repos Β· ${total} issues`)}`,
|
|
550
|
-
value:
|
|
553
|
+
value: orgRepos[0].orgId,
|
|
551
554
|
short: org + '/*',
|
|
552
555
|
});
|
|
553
556
|
}
|
|
554
557
|
for (const r of orgRepos) {
|
|
555
558
|
repoChoices.push({
|
|
556
|
-
name: ` ${dim('β')} ${cyan(r.
|
|
557
|
-
value: r.
|
|
558
|
-
short: r.
|
|
559
|
+
name: ` ${dim('β')} ${cyan(r.repoFullName)} ${dim(r.issueCount + ' issue(s)')}`,
|
|
560
|
+
value: r.orgId,
|
|
561
|
+
short: r.repoFullName,
|
|
559
562
|
});
|
|
560
563
|
}
|
|
561
564
|
}
|
|
@@ -569,8 +572,9 @@ async function browseByTrack(program, profile) {
|
|
|
569
572
|
}]);
|
|
570
573
|
if (picked === '__search__')
|
|
571
574
|
return browseByTrack(program, profile);
|
|
572
|
-
|
|
573
|
-
|
|
575
|
+
targetOrgId = picked;
|
|
576
|
+
const pickedRepo = matches.find(r => r.orgId === picked);
|
|
577
|
+
displayLabel = pickedRepo ? pickedRepo.repoFullName : picked;
|
|
574
578
|
}
|
|
575
579
|
// Step 2: ask for track
|
|
576
580
|
const { browseTrack } = await inquirer.prompt([{
|
|
@@ -586,26 +590,14 @@ async function browseByTrack(program, profile) {
|
|
|
586
590
|
],
|
|
587
591
|
}]);
|
|
588
592
|
const selectedTrack = browseTrack;
|
|
589
|
-
// Step 3:
|
|
590
|
-
const
|
|
591
|
-
const spinner = ora(dim(` Scanning ${program.name} for unassigned ${selectedTrack} issues in ${bold(displayLabel)}β¦`)).start();
|
|
593
|
+
// Step 3: fetch issues for the selected org via orgId filter
|
|
594
|
+
const spinner = ora(dim(` Fetching unassigned ${selectedTrack} issues for ${bold(displayLabel)}β¦`)).start();
|
|
592
595
|
const collected = [];
|
|
593
596
|
let apiPage = 1;
|
|
594
|
-
const MAX_SCAN_PAGES = 20;
|
|
595
597
|
try {
|
|
596
|
-
while (
|
|
597
|
-
const res = await
|
|
598
|
+
while (true) {
|
|
599
|
+
const res = await getIssuesByOrg(program.id, targetOrgId, apiPage, 50);
|
|
598
600
|
for (const issue of res.data) {
|
|
599
|
-
if (!issue.repo)
|
|
600
|
-
continue;
|
|
601
|
-
const issueFullName = issue.repo.fullName.toLowerCase();
|
|
602
|
-
const issueOrg = issueFullName.split('/')[0];
|
|
603
|
-
// Match: exact repo OR org-level (all repos under that org)
|
|
604
|
-
const matches = isOrgLevel
|
|
605
|
-
? issueOrg === targetFullName
|
|
606
|
-
: issueFullName === targetFullName;
|
|
607
|
-
if (!matches)
|
|
608
|
-
continue;
|
|
609
601
|
if (issue.assignedApplicant !== null)
|
|
610
602
|
continue;
|
|
611
603
|
if (!matchesTrack(issue, selectedTrack))
|
|
@@ -614,7 +606,7 @@ async function browseByTrack(program, profile) {
|
|
|
614
606
|
continue;
|
|
615
607
|
collected.push(issue);
|
|
616
608
|
}
|
|
617
|
-
spinner.text = dim(`
|
|
609
|
+
spinner.text = dim(` Fetched page ${apiPage}/${res.pagination.totalPages} β ${collected.length} matching issue(s) so farβ¦`);
|
|
618
610
|
if (!res.pagination.hasNextPage)
|
|
619
611
|
break;
|
|
620
612
|
apiPage++;
|
package/dist/commands/issues.js
CHANGED
|
@@ -114,7 +114,7 @@ function parseMarkdownIssues(content) {
|
|
|
114
114
|
}
|
|
115
115
|
export async function createIssuesFromFile(filePath, opts) {
|
|
116
116
|
requireRepo();
|
|
117
|
-
const resolved = path.resolve(filePath);
|
|
117
|
+
const resolved = path.resolve(filePath.trim());
|
|
118
118
|
if (!fs.existsSync(resolved)) {
|
|
119
119
|
console.error(chalk.red(`\nβ File not found: ${resolved}\n`));
|
|
120
120
|
return;
|
|
@@ -8,7 +8,7 @@ const TIER_3_HOURS = 72; // Auto-unassign
|
|
|
8
8
|
const SIG_TIER_1 = '<!-- gitpadi-reminder-24h -->';
|
|
9
9
|
const SIG_TIER_2 = '<!-- gitpadi-reminder-48h -->';
|
|
10
10
|
const SIG_TIER_3 = '<!-- gitpadi-unassigned -->';
|
|
11
|
-
async function
|
|
11
|
+
export async function remindContributors() {
|
|
12
12
|
console.log(chalk.bold('\nπ GitPadi Escalating Reminder Engine\n'));
|
|
13
13
|
try {
|
|
14
14
|
initGitHub();
|
|
@@ -124,4 +124,7 @@ ${SIG_TIER_1}`;
|
|
|
124
124
|
process.exit(1);
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
// Standalone entry point (npx tsx src/remind-contributors.ts)
|
|
128
|
+
if (process.argv[1]?.endsWith('remind-contributors.ts') || process.argv[1]?.endsWith('remind-contributors.js')) {
|
|
129
|
+
remindContributors();
|
|
130
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitpadi",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.8",
|
|
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
|
@@ -31,6 +31,7 @@ import * as contribute from './commands/contribute.js';
|
|
|
31
31
|
import * as applyForIssue from './commands/apply-for-issue.js';
|
|
32
32
|
import { runBountyHunter } from './commands/bounty-hunter.js';
|
|
33
33
|
import { dripsMenu } from './commands/drips.js';
|
|
34
|
+
import { remindContributors } from './remind-contributors.js';
|
|
34
35
|
import * as gitlabIssues from './commands/gitlab-issues.js';
|
|
35
36
|
import * as gitlabMRs from './commands/gitlab-mrs.js';
|
|
36
37
|
import * as gitlabPipelines from './commands/gitlab-pipelines.js';
|
|
@@ -1120,6 +1121,7 @@ async function maintainerMenu() {
|
|
|
1120
1121
|
{ name: `${green('π¦')} ${bold('Repositories')} ${dim('β create, delete, clone, topics, info')}`, value: 'repos' },
|
|
1121
1122
|
{ name: `${red('π')} ${bold('Releases')} ${dim('β create, list, tag releases')}`, value: 'releases' },
|
|
1122
1123
|
{ name: `${green('π―')} ${bold('Bounty Hunter')} ${dim('β auto-apply to Drips Wave & GrantFox')}`, value: 'hunt' },
|
|
1124
|
+
{ name: `${cyan('π')} ${bold('Remind Contributors')} ${dim('β warn assignees with no PR (12h)')}`, value: 'remind' },
|
|
1123
1125
|
new inquirer.Separator(dim(' βββββββββββββββββββββββββββββββββββββββββ')),
|
|
1124
1126
|
{ name: `${dim('βοΈ')} ${dim('Switch Repo')}`, value: 'switch' },
|
|
1125
1127
|
{ name: `${dim('β¬
')} ${dim('Back to Mode Selector')}`, value: 'back' },
|
|
@@ -1141,6 +1143,7 @@ async function maintainerMenu() {
|
|
|
1141
1143
|
else if (category === 'contributors') await safeMenu(contributorScoringMenu);
|
|
1142
1144
|
else if (category === 'releases') await safeMenu(releaseMenu);
|
|
1143
1145
|
else if (category === 'hunt') await runBountyHunter({ platform: 'all', maxApplications: 3, dryRun: false });
|
|
1146
|
+
else if (category === 'remind') await safeMenu(remindContributors);
|
|
1144
1147
|
else if (category === 'review-merge') {
|
|
1145
1148
|
await ensureTargetRepo();
|
|
1146
1149
|
const { prNum } = await ask([{ type: 'input', name: 'prNum', message: cyan('PR number to review:') }]);
|
package/src/commands/drips.ts
CHANGED
|
@@ -285,6 +285,20 @@ export async function ensureDripsAuth(): Promise<string> {
|
|
|
285
285
|
console.log(dim(`\n Open manually: ${cyan(DRIPS_WEB + '/wave/login')}\n`));
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
console.log(dim(' After logging in, get your token:'));
|
|
289
|
+
console.log();
|
|
290
|
+
console.log(dim(' 1. Press ') + bold('F12') + dim(' β click the ') + bold('Console') + dim(' tab'));
|
|
291
|
+
console.log(dim(' 2. Paste this command and press ') + bold('Enter') + dim(':'));
|
|
292
|
+
console.log();
|
|
293
|
+
console.log(' ' + cyan('document.cookie.match(/wave_access_token=([^;]+)/)?.[1]'));
|
|
294
|
+
console.log();
|
|
295
|
+
console.log(dim(' 3. Copy the value shown (starts with ') + yellow('eyJ...') + dim(')'));
|
|
296
|
+
console.log();
|
|
297
|
+
console.log(dim(' ββ If Console shows "undefined", use this instead: ββββββββββ'));
|
|
298
|
+
console.log(dim(' F12 β Application tab β Cookies β www.drips.network'));
|
|
299
|
+
console.log(dim(' Find ') + yellow('wave_access_token') + dim(' β copy the Value column'));
|
|
300
|
+
console.log();
|
|
301
|
+
|
|
288
302
|
const { token } = await inquirer.prompt([{
|
|
289
303
|
type: 'password',
|
|
290
304
|
name: 'token',
|
|
@@ -556,39 +570,45 @@ async function autoApply(program: DripsProgram, profile: DripsProfile): Promise<
|
|
|
556
570
|
|
|
557
571
|
// ββ Repo/org search βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
558
572
|
|
|
573
|
+
interface DripsRepo {
|
|
574
|
+
orgId: string;
|
|
575
|
+
orgLogin: string;
|
|
576
|
+
repoFullName: string;
|
|
577
|
+
repoName: string;
|
|
578
|
+
issueCount: number;
|
|
579
|
+
description: string;
|
|
580
|
+
}
|
|
581
|
+
|
|
559
582
|
/**
|
|
560
|
-
*
|
|
561
|
-
* contains the search term (case-insensitive).
|
|
583
|
+
* Uses the Drips repos API to search by org/repo name β fast, covers ALL repos.
|
|
562
584
|
*/
|
|
563
|
-
async function searchRepos(
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
)
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const [org, repo] = fullName.split('/');
|
|
578
|
-
if (!org.includes(termLower) && !repo.includes(termLower)) continue;
|
|
579
|
-
|
|
580
|
-
const existing = repoMap.get(fullName);
|
|
581
|
-
if (existing) {
|
|
582
|
-
existing.issueCount++;
|
|
583
|
-
} else {
|
|
584
|
-
repoMap.set(fullName, { org, repo, fullName, issueCount: 1 });
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
if (!res.pagination.hasNextPage) break;
|
|
589
|
-
}
|
|
585
|
+
async function searchRepos(program: DripsProgram, term: string): Promise<DripsRepo[]> {
|
|
586
|
+
const data = await dripsGet(
|
|
587
|
+
`/api/wave-programs/${program.id}/repos?search=${encodeURIComponent(term)}&limit=20&status=approved`,
|
|
588
|
+
);
|
|
589
|
+
const items: any[] = Array.isArray(data?.data) ? data.data : (data ? [data] : []);
|
|
590
|
+
return items.map((item: any) => ({
|
|
591
|
+
orgId: item.org?.id || '',
|
|
592
|
+
orgLogin: item.org?.gitHubOrgLogin || '',
|
|
593
|
+
repoFullName: item.repo?.gitHubRepoFullName || '',
|
|
594
|
+
repoName: item.repo?.gitHubRepoName || '',
|
|
595
|
+
issueCount: item.issueCount || 0,
|
|
596
|
+
description: item.description || '',
|
|
597
|
+
})).filter(r => r.orgId && r.repoFullName);
|
|
598
|
+
}
|
|
590
599
|
|
|
591
|
-
|
|
600
|
+
/**
|
|
601
|
+
* Fetch issues filtered by Drips orgId β returns all issues for that org directly.
|
|
602
|
+
*/
|
|
603
|
+
async function getIssuesByOrg(
|
|
604
|
+
programId: string,
|
|
605
|
+
orgId: string,
|
|
606
|
+
page: number,
|
|
607
|
+
limit: number = 50,
|
|
608
|
+
): Promise<{ data: DripsIssue[]; pagination: DripsPagination }> {
|
|
609
|
+
return dripsGet(
|
|
610
|
+
`/api/issues?waveProgramId=${programId}&orgId=${orgId}&state=open&page=${page}&limit=${limit}&sortBy=points`,
|
|
611
|
+
);
|
|
592
612
|
}
|
|
593
613
|
|
|
594
614
|
// ββ Browse by org + track βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -606,7 +626,7 @@ async function browseByTrack(program: DripsProgram, profile: DripsProfile): Prom
|
|
|
606
626
|
|
|
607
627
|
// Search for matching repos in the wave program
|
|
608
628
|
const searchSpinner = ora(dim(` Searching for "${term}" in ${program.name} waveβ¦`)).start();
|
|
609
|
-
let matches:
|
|
629
|
+
let matches: DripsRepo[];
|
|
610
630
|
try {
|
|
611
631
|
matches = await searchRepos(program, term);
|
|
612
632
|
} catch (e: any) {
|
|
@@ -623,48 +643,49 @@ async function browseByTrack(program: DripsProgram, profile: DripsProfile): Prom
|
|
|
623
643
|
searchSpinner.succeed(` Found ${matches.length} repo(s) matching "${term}"`);
|
|
624
644
|
console.log();
|
|
625
645
|
|
|
626
|
-
|
|
627
|
-
// Value is the full repo name (org/repo) so we can filter precisely
|
|
628
|
-
let targetFullName: string; // e.g. "inheritx/smart-contracts" or "stellar" (org-level)
|
|
646
|
+
let targetOrgId: string;
|
|
629
647
|
let displayLabel: string;
|
|
630
648
|
|
|
631
|
-
const exactOrg = matches.find(m => m.
|
|
632
|
-
const exactRepo = matches.find(m => m.
|
|
649
|
+
const exactOrg = matches.find(m => m.orgLogin.toLowerCase() === term.toLowerCase());
|
|
650
|
+
const exactRepo = matches.find(m => m.repoFullName.toLowerCase() === term.toLowerCase());
|
|
633
651
|
|
|
634
652
|
if (matches.length === 1) {
|
|
635
|
-
|
|
636
|
-
displayLabel = matches[0].
|
|
653
|
+
targetOrgId = matches[0].orgId;
|
|
654
|
+
displayLabel = matches[0].repoFullName;
|
|
637
655
|
console.log(dim(` Using repo: ${cyan(displayLabel)}\n`));
|
|
638
656
|
} else if (exactRepo) {
|
|
639
|
-
|
|
640
|
-
displayLabel = exactRepo.
|
|
657
|
+
targetOrgId = exactRepo.orgId;
|
|
658
|
+
displayLabel = exactRepo.repoFullName;
|
|
641
659
|
console.log(dim(` Using repo: ${cyan(displayLabel)}\n`));
|
|
642
660
|
} else if (exactOrg) {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
console.log(dim(` Using org: ${cyan(exactOrg.org)} (all repos)\n`));
|
|
661
|
+
targetOrgId = exactOrg.orgId;
|
|
662
|
+
displayLabel = exactOrg.orgLogin + '/*';
|
|
663
|
+
console.log(dim(` Using org: ${cyan(exactOrg.orgLogin)} (all repos)\n`));
|
|
647
664
|
} else {
|
|
648
|
-
// Multiple partial matches β let user pick
|
|
665
|
+
// Multiple partial matches β let user pick
|
|
649
666
|
const repoChoices: any[] = [];
|
|
650
667
|
|
|
651
|
-
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
668
|
+
const orgGroups = new Map<string, DripsRepo[]>();
|
|
669
|
+
for (const r of matches) {
|
|
670
|
+
const list = orgGroups.get(r.orgLogin) || [];
|
|
671
|
+
list.push(r);
|
|
672
|
+
orgGroups.set(r.orgLogin, list);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
for (const [org, orgRepos] of orgGroups) {
|
|
655
676
|
if (orgRepos.length > 1) {
|
|
656
677
|
const total = orgRepos.reduce((s, r) => s + r.issueCount, 0);
|
|
657
678
|
repoChoices.push({
|
|
658
679
|
name: ` ${cyan(org + '/*')} ${dim(`all ${orgRepos.length} repos Β· ${total} issues`)}`,
|
|
659
|
-
value:
|
|
680
|
+
value: orgRepos[0].orgId,
|
|
660
681
|
short: org + '/*',
|
|
661
682
|
});
|
|
662
683
|
}
|
|
663
684
|
for (const r of orgRepos) {
|
|
664
685
|
repoChoices.push({
|
|
665
|
-
name: ` ${dim('β')} ${cyan(r.
|
|
666
|
-
value: r.
|
|
667
|
-
short: r.
|
|
686
|
+
name: ` ${dim('β')} ${cyan(r.repoFullName)} ${dim(r.issueCount + ' issue(s)')}`,
|
|
687
|
+
value: r.orgId,
|
|
688
|
+
short: r.repoFullName,
|
|
668
689
|
});
|
|
669
690
|
}
|
|
670
691
|
}
|
|
@@ -683,8 +704,9 @@ async function browseByTrack(program: DripsProgram, profile: DripsProfile): Prom
|
|
|
683
704
|
}]);
|
|
684
705
|
|
|
685
706
|
if (picked === '__search__') return browseByTrack(program, profile);
|
|
686
|
-
|
|
687
|
-
|
|
707
|
+
targetOrgId = picked;
|
|
708
|
+
const pickedRepo = matches.find(r => r.orgId === picked);
|
|
709
|
+
displayLabel = pickedRepo ? pickedRepo.repoFullName : picked;
|
|
688
710
|
}
|
|
689
711
|
|
|
690
712
|
// Step 2: ask for track
|
|
@@ -703,36 +725,24 @@ async function browseByTrack(program: DripsProgram, profile: DripsProfile): Prom
|
|
|
703
725
|
|
|
704
726
|
const selectedTrack: Track = browseTrack;
|
|
705
727
|
|
|
706
|
-
// Step 3:
|
|
707
|
-
const
|
|
708
|
-
const spinner = ora(dim(` Scanning ${program.name} for unassigned ${selectedTrack} issues in ${bold(displayLabel)}β¦`)).start();
|
|
728
|
+
// Step 3: fetch issues for the selected org via orgId filter
|
|
729
|
+
const spinner = ora(dim(` Fetching unassigned ${selectedTrack} issues for ${bold(displayLabel)}β¦`)).start();
|
|
709
730
|
|
|
710
731
|
const collected: DripsIssue[] = [];
|
|
711
732
|
let apiPage = 1;
|
|
712
|
-
const MAX_SCAN_PAGES = 20;
|
|
713
733
|
|
|
714
734
|
try {
|
|
715
|
-
while (
|
|
716
|
-
const res = await
|
|
735
|
+
while (true) {
|
|
736
|
+
const res = await getIssuesByOrg(program.id, targetOrgId, apiPage, 50);
|
|
717
737
|
|
|
718
738
|
for (const issue of res.data) {
|
|
719
|
-
if (!issue.repo) continue;
|
|
720
|
-
const issueFullName = issue.repo.fullName.toLowerCase();
|
|
721
|
-
const issueOrg = issueFullName.split('/')[0];
|
|
722
|
-
|
|
723
|
-
// Match: exact repo OR org-level (all repos under that org)
|
|
724
|
-
const matches = isOrgLevel
|
|
725
|
-
? issueOrg === targetFullName
|
|
726
|
-
: issueFullName === targetFullName;
|
|
727
|
-
if (!matches) continue;
|
|
728
|
-
|
|
729
739
|
if (issue.assignedApplicant !== null) continue;
|
|
730
740
|
if (!matchesTrack(issue, selectedTrack)) continue;
|
|
731
741
|
if (profile.minPoints > 0 && issue.points !== null && issue.points < profile.minPoints) continue;
|
|
732
742
|
collected.push(issue);
|
|
733
743
|
}
|
|
734
744
|
|
|
735
|
-
spinner.text = dim(`
|
|
745
|
+
spinner.text = dim(` Fetched page ${apiPage}/${res.pagination.totalPages} β ${collected.length} matching issue(s) so farβ¦`);
|
|
736
746
|
|
|
737
747
|
if (!res.pagination.hasNextPage) break;
|
|
738
748
|
apiPage++;
|
package/src/commands/issues.ts
CHANGED
|
@@ -131,7 +131,7 @@ function parseMarkdownIssues(content: string): { issues: any[]; labels: Record<s
|
|
|
131
131
|
|
|
132
132
|
export async function createIssuesFromFile(filePath: string, opts: { dryRun?: boolean; start?: number; end?: number }) {
|
|
133
133
|
requireRepo();
|
|
134
|
-
const resolved = path.resolve(filePath);
|
|
134
|
+
const resolved = path.resolve(filePath.trim());
|
|
135
135
|
if (!fs.existsSync(resolved)) {
|
|
136
136
|
console.error(chalk.red(`\nβ File not found: ${resolved}\n`));
|
|
137
137
|
return;
|
|
@@ -11,7 +11,7 @@ const SIG_TIER_1 = '<!-- gitpadi-reminder-24h -->';
|
|
|
11
11
|
const SIG_TIER_2 = '<!-- gitpadi-reminder-48h -->';
|
|
12
12
|
const SIG_TIER_3 = '<!-- gitpadi-unassigned -->';
|
|
13
13
|
|
|
14
|
-
async function
|
|
14
|
+
export async function remindContributors() {
|
|
15
15
|
console.log(chalk.bold('\nπ GitPadi Escalating Reminder Engine\n'));
|
|
16
16
|
|
|
17
17
|
try {
|
|
@@ -156,4 +156,7 @@ ${SIG_TIER_1}`;
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
// Standalone entry point (npx tsx src/remind-contributors.ts)
|
|
160
|
+
if (process.argv[1]?.endsWith('remind-contributors.ts') || process.argv[1]?.endsWith('remind-contributors.js')) {
|
|
161
|
+
remindContributors();
|
|
162
|
+
}
|