gitpadi 2.0.2 → 2.0.3

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/README.md CHANGED
@@ -1,270 +1,102 @@
1
- <p align="center">
2
- <h1 align="center">šŸ¤– GitPadi</h1>
3
- <p align="center"><strong>Your AI-powered GitHub management agent.</strong></p>
4
- <p align="center">
5
- Auto-score contributors, review PRs, manage issues & repos — from your terminal or as a GitHub Action.
6
- </p>
7
- <p align="center">
8
- <a href="#-use-the-cli">CLI</a> •
9
- <a href="#-use-as-github-action">GitHub Action</a> •
10
- <a href="#-scoring-algorithm">Scoring Algorithm</a>
11
- </p>
12
- </p>
1
+ # šŸ¤– GitPadi
13
2
 
14
- ---
15
-
16
- ## ⚔ Two Ways to Use GitPadi
3
+ GitPadi is a GitHub management CLI and Action built to automate the repetitive parts of maintaining open-source projects.
17
4
 
18
- | Method | Best For | Setup |
19
- |--------|----------|-------|
20
- | **CLI** (`npx gitpadi`) | Hands-on management from your terminal | One command |
21
- | **GitHub Action** | Automated scoring & reviews on every event | Copy one workflow file |
5
+ Whether you're a maintainer managing hundreds of issues or a contributor making your first PR, GitPadi turns multi-step GitHub workflows into single commands.
22
6
 
23
7
  ---
24
8
 
25
- ## šŸ–„ļø Use the CLI
9
+ ## šŸš€ For Contributors
26
10
 
27
- **No installation needed.** Run directly with `npx`:
11
+ Stop worrying about forking, upstream remotes, and branch naming. GitPadi handles the plumbing so you can focus on the code.
28
12
 
13
+ ### Start a new contribution
29
14
  ```bash
30
- export GITHUB_TOKEN=ghp_your_token
31
- export GITHUB_OWNER=your-org
32
- export GITHUB_REPO=your-repo
33
-
34
- npx gitpadi
35
- ```
36
-
37
- That launches the **interactive terminal**:
38
-
39
- ```
40
- ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
41
- │ šŸ¤– GitPadi v2.0 │
42
- │ Your GitHub Management Companion │
43
- ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
44
-
45
- ? What would you like to do?
46
- šŸ“‹ Manage Issues
47
- šŸ”€ Manage Pull Requests
48
- šŸ“¦ Manage Repositories
49
- šŸ† Score Contributors
50
- šŸš€ Create Release
51
- āš™ļø Switch Repo
52
- šŸ‘‹ Exit
15
+ npx gitpadi start <issue-url>
53
16
  ```
17
+ This single command:
18
+ 1. **Forks** the repository to your account.
19
+ 2. **Clones** it to your machine.
20
+ 3. Sets up the **upstream remote**.
21
+ 4. Creates a **fresh feature branch** linked to the issue.
54
22
 
55
- Or use **direct commands**:
56
-
23
+ ### Submit your work
57
24
  ```bash
58
- npx gitpadi issues list
59
- npx gitpadi prs merge 5 --method squash
60
- npx gitpadi contributors score octocat
61
- npx gitpadi repo create my-app --org MyOrg
25
+ npx gitpadi submit
62
26
  ```
27
+ When you're done, this command:
28
+ 1. **Stages** all your changes.
29
+ 2. Creates a **commit** with a clean message.
30
+ 3. **Pushes** to your fork.
31
+ 4. Opens a **Pull Request** on the original repo, auto-linked to the issue.
32
+
33
+ ---
63
34
 
64
- ### All CLI Commands
35
+ ## šŸ› ļø For Maintainers
65
36
 
66
- <details>
67
- <summary><b>šŸ“‹ Issues</b></summary>
37
+ Manage your community and project health directly from your terminal.
68
38
 
39
+ ### šŸ“‹ Bulk Issue Creation
40
+ Create dozens of issues in seconds from a simple Markdown or JSON file.
69
41
  ```bash
70
- npx gitpadi issues list # List open issues
71
- npx gitpadi issues create -t "Bug fix" -l bug # Create issue
72
- npx gitpadi issues bulk -f issues.json # Bulk create from JSON
73
- npx gitpadi issues close 42 # Close issue
74
- npx gitpadi issues reopen 42 # Reopen issue
75
- npx gitpadi issues delete 42 # Close & lock
76
- npx gitpadi issues assign 42 alice bob # Assign users
77
- npx gitpadi issues assign-best 42 # šŸ† Auto-assign top scorer
78
- npx gitpadi issues search "login bug" # Search issues
79
- npx gitpadi issues label 42 bug priority-high # Add labels
42
+ npx gitpadi issues bulk -f roadmap.md
80
43
  ```
81
- </details>
82
-
83
- <details>
84
- <summary><b>šŸ”€ Pull Requests</b></summary>
44
+ *(Supports `## Heading` titles and `**Labels:**` tags).*
85
45
 
46
+ ### šŸ† Smart Contributor Scoring
47
+ Auto-score applicants based on their GitHub history, account age, and relevance to the issue.
86
48
  ```bash
87
- npx gitpadi prs list # List open PRs
88
- npx gitpadi prs merge 5 --method squash # Merge PR (squash/merge/rebase)
89
- npx gitpadi prs close 5 # Close PR
90
- npx gitpadi prs review 5 # Auto-review (size, tests, security)
91
- npx gitpadi prs approve 5 # Approve PR
92
- npx gitpadi prs diff 5 # Show changed files
49
+ npx gitpadi contributors score octocat
93
50
  ```
94
- </details>
95
-
96
- <details>
97
- <summary><b>šŸ“¦ Repositories</b></summary>
98
-
51
+ Or let GitPadi find the best applicant for an issue:
99
52
  ```bash
100
- npx gitpadi repo list --org MyOrg # List repos
101
- npx gitpadi repo create my-app --org MyOrg # Create repo
102
- npx gitpadi repo delete my-app --org MyOrg # Delete repo
103
- npx gitpadi repo clone my-app # Clone repo
104
- npx gitpadi repo info my-app # Show repo details
105
- npx gitpadi repo topics my-app rust blockchain # Set topics
53
+ npx gitpadi issues assign-best 42
106
54
  ```
107
- </details>
108
55
 
109
- <details>
110
- <summary><b>šŸ† Contributors</b></summary>
56
+ ### šŸ”€ Advanced PR Management
57
+ - **Review PRs**: Get an automated review (size analysis, security checks, test coverage).
58
+ - **Fast Merge**: Squash and merge PRs directly from the CLI.
59
+ - **CI Status**: View GitHub Action logs for a PR without opening a browser.
111
60
 
112
- ```bash
113
- npx gitpadi contributors score octocat # Score a user (0-100)
114
- npx gitpadi contributors rank 42 # Rank all applicants for issue #42
115
- npx gitpadi contributors list # List repo contributors
116
- ```
117
- </details>
61
+ ---
118
62
 
119
- <details>
120
- <summary><b>šŸš€ Releases</b></summary>
63
+ ## ⚔ Quick Start
64
+
65
+ No installation required. Just set your token and go:
121
66
 
122
67
  ```bash
123
- npx gitpadi release create v1.0.0 # Create release (auto-notes)
124
- npx gitpadi release create v1.0.0 --draft # Create as draft
125
- npx gitpadi release list # List releases
68
+ export GITHUB_TOKEN=ghp_your_token
69
+ npx gitpadi
126
70
  ```
127
- </details>
128
-
129
- ### Or install globally:
130
71
 
72
+ ### Direct Commands
131
73
  ```bash
132
- npm install -g gitpadi
133
- gitpadi issues list
74
+ npx gitpadi issues list --limit 100
75
+ npx gitpadi prs review 5
76
+ npx gitpadi release create v1.0.0
134
77
  ```
135
78
 
136
79
  ---
137
80
 
138
- ## šŸ”§ Use as GitHub Action
139
-
140
- Just add a workflow file — no setup, no tokens to manage.
81
+ ## šŸ”§ Use as a GitHub Action
141
82
 
142
- ### šŸ† Auto-Score Contributors
83
+ GitPadi can run on every event in your repo. Just add a workflow file.
143
84
 
85
+ **Auto-Score Applicants:**
144
86
  ```yaml
145
- # .github/workflows/applicant-scorer.yml
146
- name: Score Applicants
147
- on:
148
- issue_comment:
149
- types: [created]
150
- permissions:
151
- contents: read
152
- issues: write
153
- pull-requests: read
154
- jobs:
155
- score:
156
- if: "!github.event.issue.pull_request"
157
- runs-on: ubuntu-latest
158
- steps:
159
- - uses: actions/checkout@v4
160
- - uses: Netwalls/contributor-agent@v2
161
- with:
162
- action: score-applicant
163
- notify-user: 'your-username' # ← you get @mentioned
87
+ - uses: Netwalls/contributor-agent@v2
88
+ with:
89
+ action: score-applicant
164
90
  ```
165
91
 
166
- ### šŸ” Auto-Review PRs
167
-
92
+ **Auto-Review PRs:**
168
93
  ```yaml
169
- # .github/workflows/pr-review.yml
170
- name: PR Review
171
- on:
172
- pull_request:
173
- types: [opened, synchronize, reopened]
174
- permissions:
175
- contents: read
176
- pull-requests: write
177
- jobs:
178
- review:
179
- runs-on: ubuntu-latest
180
- steps:
181
- - uses: actions/checkout@v4
182
- - uses: Netwalls/contributor-agent@v2
183
- with:
184
- action: review-pr
185
- ```
186
-
187
- ### šŸ“‹ Bulk Create Issues
188
-
189
- ```yaml
190
- # .github/workflows/create-issues.yml
191
- name: Create Issues
192
- on:
193
- workflow_dispatch:
194
- inputs:
195
- issues-file:
196
- description: 'Path to issues JSON'
197
- default: 'issues.json'
198
- dry-run:
199
- description: 'Preview only'
200
- default: 'true'
201
- permissions:
202
- issues: write
203
- jobs:
204
- create:
205
- runs-on: ubuntu-latest
206
- steps:
207
- - uses: actions/checkout@v4
208
- - uses: Netwalls/contributor-agent@v2
209
- with:
210
- action: create-issues
211
- issues-file: ${{ github.event.inputs.issues-file }}
212
- dry-run: ${{ github.event.inputs.dry-run }}
213
- ```
214
-
215
- ---
216
-
217
- ## šŸ† Scoring Algorithm
218
-
219
- GitPadi scores contributors on **6 dimensions** (100 points total):
220
-
221
- | Category | Max | What It Measures |
222
- |----------|-----|-----------------|
223
- | šŸ”§ **Repo Experience** | **30** | Merged PRs, open PRs, issues in YOUR specific repo |
224
- | šŸ›ļø Account Maturity | 15 | Account age |
225
- | 🌐 GitHub Presence | 15 | Public repos, followers, profile README |
226
- | ⚔ Activity Level | 15 | Recent public GitHub activity |
227
- | šŸ“ Application Quality | 15 | Comment depth — mentions approach, experience, timeline |
228
- | šŸ’» Language Relevance | 10 | User's languages match issue label languages |
229
-
230
- ### Tiers
231
-
232
- | Tier | Score | Recommendation |
233
- |------|-------|---------------|
234
- | šŸ† **S** | 75+ | Strong — assign immediately |
235
- | 🟢 **A** | 55-74 | Good — assign with confidence |
236
- | 🟔 **B** | 40-54 | Decent — review profile first |
237
- | 🟠 **C** | 25-39 | Below average — wait for better |
238
- | šŸ”“ **D** | 0-24 | Low — manual review needed |
239
-
240
- ### Detected Application Phrases
241
-
242
- > *"I'd like to work on this"* Ā· *"Can I take this?"* Ā· *"Assign me"* Ā· *"I'm interested"* Ā· *"I want to contribute"* Ā· *"Let me handle this"* Ā· *"Picking this up"* Ā· *"Claiming this"*
243
-
244
- ---
245
-
246
- ## āš™ļø Configuration
247
-
248
- ### Environment Variables
249
-
250
- | Variable | Required | Description |
251
- |----------|----------|-------------|
252
- | `GITHUB_TOKEN` | āœ… | GitHub PAT with `repo` scope |
253
- | `GITHUB_OWNER` | For CLI | Org or username |
254
- | `GITHUB_REPO` | For CLI | Repository name |
255
-
256
- ### CLI Flags
257
-
258
- ```bash
259
- npx gitpadi --owner MyOrg --repo my-project --token ghp_xxx issues list
94
+ - uses: Netwalls/contributor-agent@v2
95
+ with:
96
+ action: review-pr
260
97
  ```
261
98
 
262
99
  ---
263
100
 
264
101
  ## šŸ“„ License
265
-
266
- MIT — Built by [Netwalls](https://github.com/Netwalls)
267
-
268
-
269
-
270
- i noticed that a users have more issues but it is just bring out the top 10 issues that are open how do we bring out more issues
102
+ MIT — Built by [Netwalls](https://github.com/Netwalls)
package/dist/cli.js CHANGED
@@ -363,11 +363,10 @@ async function issueMenu() {
363
363
  else if (action === 'bulk') {
364
364
  const a = await ask([
365
365
  { type: 'input', name: 'file', message: yellow('šŸ“ Path to issues file (JSON or MD)') + dim(' (q=back):') },
366
- { type: 'confirm', name: 'dryRun', message: 'Dry run first?', default: true },
367
366
  { type: 'number', name: 'start', message: dim('Start index:'), default: 1 },
368
367
  { type: 'number', name: 'end', message: dim('End index:'), default: 999 },
369
368
  ]);
370
- await issues.createIssuesFromFile(a.file, { dryRun: a.dryRun, start: a.start, end: a.end });
369
+ await issues.createIssuesFromFile(a.file, { start: a.start, end: a.end });
371
370
  }
372
371
  else if (action === 'assign-best') {
373
372
  const spinner = createSpinner(dim('Finding issues with applicants...')).start();
@@ -81,8 +81,23 @@ export async function forkAndClone(target) {
81
81
  execSync(`git remote add upstream https://github.com/${owner}/${repo}.git`, { stdio: 'pipe' });
82
82
  }
83
83
  catch { /* exists */ }
84
+ // 1. Detect default branch
85
+ let defaultBranch = 'main';
86
+ try {
87
+ execSync('git rev-parse upstream/main', { stdio: 'pipe' });
88
+ }
89
+ catch {
90
+ defaultBranch = 'master';
91
+ }
92
+ // 2. Checkout default branch before syncing
93
+ try {
94
+ execSync(`git checkout ${defaultBranch}`, { stdio: 'pipe' });
95
+ }
96
+ catch {
97
+ // If it fails, maybe it's not even a valid default branch locally, but we'll try to sync anyway
98
+ }
84
99
  await syncBranch();
85
- // Create or switch to issue branch
100
+ // 3. Create or switch to issue branch
86
101
  const branchName = issue ? `fix/issue-${issue}` : null;
87
102
  if (branchName) {
88
103
  try {
@@ -246,13 +261,21 @@ export async function submitPR(opts) {
246
261
  execSync(`git push origin ${branch}`, { stdio: 'pipe' });
247
262
  spinner.text = 'Creating Pull Request...';
248
263
  const body = opts.body || (linkedIssue ? `Fixes #${linkedIssue}` : 'Automated PR via GitPadi');
264
+ // Detect base branch
265
+ let baseBranch = 'main';
266
+ try {
267
+ execSync('git rev-parse origin/main', { stdio: 'pipe' });
268
+ }
269
+ catch {
270
+ baseBranch = 'master';
271
+ }
249
272
  const { data: pr } = await getOctokit().pulls.create({
250
273
  owner,
251
274
  repo,
252
275
  title: opts.title,
253
276
  body,
254
277
  head: `${await getAuthenticatedUser()}:${branch}`,
255
- base: 'main',
278
+ base: baseBranch,
256
279
  });
257
280
  spinner.succeed(`PR Created: ${green(pr.html_url)}`);
258
281
  }
@@ -11,16 +11,29 @@ export async function listIssues(opts) {
11
11
  const octokit = getOctokit();
12
12
  const spinner = ora(`Fetching issues from ${chalk.cyan(getFullRepo())}...`).start();
13
13
  try {
14
- const { data: issues } = await octokit.issues.listForRepo({
15
- owner: getOwner(), repo: getRepo(),
16
- state: opts.state || 'open',
17
- labels: opts.labels || undefined,
18
- per_page: opts.limit || 50,
19
- });
20
- // Filter out PRs (GitHub API returns PRs in issues endpoint)
21
- const realIssues = issues.filter((i) => !i.pull_request);
14
+ const requestedLimit = opts.limit || 50;
15
+ const realIssues = [];
16
+ let page = 1;
17
+ // Fetch until we have enough real issues or run out of pages
18
+ while (realIssues.length < requestedLimit) {
19
+ const { data: issues } = await octokit.issues.listForRepo({
20
+ owner: getOwner(), repo: getRepo(),
21
+ state: opts.state || 'open',
22
+ labels: opts.labels || undefined,
23
+ per_page: 100, // Fetch max per page to be efficient
24
+ page: page++,
25
+ });
26
+ if (issues.length === 0)
27
+ break;
28
+ const batch = issues.filter((i) => !i.pull_request);
29
+ realIssues.push(...batch);
30
+ if (issues.length < 100)
31
+ break; // Last page
32
+ }
33
+ // Clip to requested limit
34
+ const finalIssues = realIssues.slice(0, requestedLimit);
22
35
  spinner.stop();
23
- if (realIssues.length === 0) {
36
+ if (finalIssues.length === 0) {
24
37
  console.log(chalk.yellow('\n No issues found.\n'));
25
38
  return;
26
39
  }
@@ -28,13 +41,13 @@ export async function listIssues(opts) {
28
41
  head: ['#', 'Title', 'Labels', 'Assignee', 'State'].map((h) => chalk.cyan(h)),
29
42
  style: { head: [], border: [] },
30
43
  });
31
- realIssues.forEach((i) => {
44
+ finalIssues.forEach((i) => {
32
45
  const labels = i.labels.map((l) => typeof l === 'string' ? l : l.name || '').join(', ');
33
46
  const assignee = i.assignee?.login || chalk.dim('unassigned');
34
47
  const state = i.state === 'open' ? chalk.green('open') : chalk.red('closed');
35
48
  table.push([`#${i.number}`, i.title.substring(0, 60), labels.substring(0, 30), assignee, state]);
36
49
  });
37
- console.log(`\n${chalk.bold(`šŸ“‹ Issues — ${getFullRepo()}`)} (${realIssues.length})\n`);
50
+ console.log(`\n${chalk.bold(`šŸ“‹ Issues — ${getFullRepo()}`)} (${finalIssues.length})\n`);
38
51
  console.log(table.toString());
39
52
  console.log('');
40
53
  }
@@ -137,16 +150,22 @@ export async function createIssuesFromFile(filePath, opts) {
137
150
  console.log(`\n${chalk.bold('šŸ“‹ GitPadi Issue Creator')}`);
138
151
  console.log(chalk.dim(` Repo: ${getFullRepo()}`));
139
152
  console.log(chalk.dim(` File: ${filePath} (${detectedFormat})`));
140
- console.log(chalk.dim(` Range: #${start}-#${end} (${filtered.length} issues)`));
141
- console.log(chalk.dim(` Mode: ${opts.dryRun ? 'DRY RUN' : 'LIVE'}\n`));
142
- if (opts.dryRun) {
143
- filtered.forEach((i) => {
144
- console.log(` ${chalk.dim(`#${String(i.number).padStart(2, '0')}`)} ${i.title}`);
145
- console.log(chalk.dim(` [${(i.labels || []).join(', ')}]`));
146
- if (i.body)
147
- console.log(chalk.dim(` ${i.body.substring(0, 80)}...`));
148
- });
149
- console.log(chalk.green(`\nāœ… Dry run: ${filtered.length} issues would be created.\n`));
153
+ console.log(chalk.dim(` Found: ${filtered.length} issues\n`));
154
+ // Always preview first
155
+ filtered.forEach((i) => {
156
+ console.log(` ${chalk.dim(`#${String(i.number).padStart(2, '0')}`)} ${i.title}`);
157
+ console.log(chalk.dim(` [${(i.labels || []).join(', ')}]`));
158
+ });
159
+ // Ask for confirmation
160
+ const inquirer = (await import('inquirer')).default;
161
+ const { proceed } = await inquirer.prompt([{
162
+ type: 'confirm',
163
+ name: 'proceed',
164
+ message: chalk.yellow(`Create these ${filtered.length} issues on GitHub?`),
165
+ default: true,
166
+ }]);
167
+ if (!proceed) {
168
+ console.log(chalk.dim('\n Cancelled.\n'));
150
169
  return;
151
170
  }
152
171
  const octokit = getOctokit();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gitpadi",
3
- "version": "2.0.2",
4
- "description": "šŸ¤– GitPadi — Your AI-powered GitHub management CLI. Create repos, manage issues & PRs, score contributors, and automate everything.",
3
+ "version": "2.0.3",
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": {
7
7
  "gitpadi": "./dist/cli.js"
package/src/cli.ts CHANGED
@@ -393,11 +393,10 @@ async function issueMenu() {
393
393
  } else if (action === 'bulk') {
394
394
  const a = await ask([
395
395
  { type: 'input', name: 'file', message: yellow('šŸ“ Path to issues file (JSON or MD)') + dim(' (q=back):') },
396
- { type: 'confirm', name: 'dryRun', message: 'Dry run first?', default: true },
397
396
  { type: 'number', name: 'start', message: dim('Start index:'), default: 1 },
398
397
  { type: 'number', name: 'end', message: dim('End index:'), default: 999 },
399
398
  ]);
400
- await issues.createIssuesFromFile(a.file, { dryRun: a.dryRun, start: a.start, end: a.end });
399
+ await issues.createIssuesFromFile(a.file, { start: a.start, end: a.end });
401
400
  } else if (action === 'assign-best') {
402
401
  const spinner = createSpinner(dim('Finding issues with applicants...')).start();
403
402
  const octokit = getOctokit();
@@ -100,9 +100,24 @@ export async function forkAndClone(target: string) {
100
100
  // Ensure upstream remote exists
101
101
  try { execSync(`git remote add upstream https://github.com/${owner}/${repo}.git`, { stdio: 'pipe' }); } catch { /* exists */ }
102
102
 
103
+ // 1. Detect default branch
104
+ let defaultBranch = 'main';
105
+ try {
106
+ execSync('git rev-parse upstream/main', { stdio: 'pipe' });
107
+ } catch {
108
+ defaultBranch = 'master';
109
+ }
110
+
111
+ // 2. Checkout default branch before syncing
112
+ try {
113
+ execSync(`git checkout ${defaultBranch}`, { stdio: 'pipe' });
114
+ } catch {
115
+ // If it fails, maybe it's not even a valid default branch locally, but we'll try to sync anyway
116
+ }
117
+
103
118
  await syncBranch();
104
119
 
105
- // Create or switch to issue branch
120
+ // 3. Create or switch to issue branch
106
121
  const branchName = issue ? `fix/issue-${issue}` : null;
107
122
  if (branchName) {
108
123
  try {
@@ -282,13 +297,21 @@ export async function submitPR(opts: { title: string, body?: string, issue?: num
282
297
  spinner.text = 'Creating Pull Request...';
283
298
  const body = opts.body || (linkedIssue ? `Fixes #${linkedIssue}` : 'Automated PR via GitPadi');
284
299
 
300
+ // Detect base branch
301
+ let baseBranch = 'main';
302
+ try {
303
+ execSync('git rev-parse origin/main', { stdio: 'pipe' });
304
+ } catch {
305
+ baseBranch = 'master';
306
+ }
307
+
285
308
  const { data: pr } = await getOctokit().pulls.create({
286
309
  owner,
287
310
  repo,
288
311
  title: opts.title,
289
312
  body,
290
313
  head: `${await getAuthenticatedUser()}:${branch}`,
291
- base: 'main',
314
+ base: baseBranch,
292
315
  });
293
316
 
294
317
  spinner.succeed(`PR Created: ${green(pr.html_url)}`);
@@ -14,18 +14,34 @@ export async function listIssues(opts: { state?: string; labels?: string; limit?
14
14
  const spinner = ora(`Fetching issues from ${chalk.cyan(getFullRepo())}...`).start();
15
15
 
16
16
  try {
17
- const { data: issues } = await octokit.issues.listForRepo({
18
- owner: getOwner(), repo: getRepo(),
19
- state: (opts.state as 'open' | 'closed' | 'all') || 'open',
20
- labels: opts.labels || undefined,
21
- per_page: opts.limit || 50,
22
- });
17
+ const requestedLimit = opts.limit || 50;
18
+ const realIssues: any[] = [];
19
+ let page = 1;
20
+
21
+ // Fetch until we have enough real issues or run out of pages
22
+ while (realIssues.length < requestedLimit) {
23
+ const { data: issues } = await octokit.issues.listForRepo({
24
+ owner: getOwner(), repo: getRepo(),
25
+ state: (opts.state as 'open' | 'closed' | 'all') || 'open',
26
+ labels: opts.labels || undefined,
27
+ per_page: 100, // Fetch max per page to be efficient
28
+ page: page++,
29
+ });
30
+
31
+ if (issues.length === 0) break;
32
+
33
+ const batch = issues.filter((i) => !i.pull_request);
34
+ realIssues.push(...batch);
35
+
36
+ if (issues.length < 100) break; // Last page
37
+ }
38
+
39
+ // Clip to requested limit
40
+ const finalIssues = realIssues.slice(0, requestedLimit);
23
41
 
24
- // Filter out PRs (GitHub API returns PRs in issues endpoint)
25
- const realIssues = issues.filter((i) => !i.pull_request);
26
42
  spinner.stop();
27
43
 
28
- if (realIssues.length === 0) {
44
+ if (finalIssues.length === 0) {
29
45
  console.log(chalk.yellow('\n No issues found.\n'));
30
46
  return;
31
47
  }
@@ -35,14 +51,14 @@ export async function listIssues(opts: { state?: string; labels?: string; limit?
35
51
  style: { head: [], border: [] },
36
52
  });
37
53
 
38
- realIssues.forEach((i) => {
39
- const labels = i.labels.map((l) => typeof l === 'string' ? l : l.name || '').join(', ');
54
+ finalIssues.forEach((i) => {
55
+ const labels = i.labels.map((l: any) => typeof l === 'string' ? l : l.name || '').join(', ');
40
56
  const assignee = i.assignee?.login || chalk.dim('unassigned');
41
57
  const state = i.state === 'open' ? chalk.green('open') : chalk.red('closed');
42
58
  table.push([`#${i.number}`, i.title.substring(0, 60), labels.substring(0, 30), assignee, state]);
43
59
  });
44
60
 
45
- console.log(`\n${chalk.bold(`šŸ“‹ Issues — ${getFullRepo()}`)} (${realIssues.length})\n`);
61
+ console.log(`\n${chalk.bold(`šŸ“‹ Issues — ${getFullRepo()}`)} (${finalIssues.length})\n`);
46
62
  console.log(table.toString());
47
63
  console.log('');
48
64
  } catch (e: any) {
@@ -153,16 +169,25 @@ export async function createIssuesFromFile(filePath: string, opts: { dryRun?: bo
153
169
  console.log(`\n${chalk.bold('šŸ“‹ GitPadi Issue Creator')}`);
154
170
  console.log(chalk.dim(` Repo: ${getFullRepo()}`));
155
171
  console.log(chalk.dim(` File: ${filePath} (${detectedFormat})`));
156
- console.log(chalk.dim(` Range: #${start}-#${end} (${filtered.length} issues)`));
157
- console.log(chalk.dim(` Mode: ${opts.dryRun ? 'DRY RUN' : 'LIVE'}\n`));
158
-
159
- if (opts.dryRun) {
160
- filtered.forEach((i: any) => {
161
- console.log(` ${chalk.dim(`#${String(i.number).padStart(2, '0')}`)} ${i.title}`);
162
- console.log(chalk.dim(` [${(i.labels || []).join(', ')}]`));
163
- if (i.body) console.log(chalk.dim(` ${i.body.substring(0, 80)}...`));
164
- });
165
- console.log(chalk.green(`\nāœ… Dry run: ${filtered.length} issues would be created.\n`));
172
+ console.log(chalk.dim(` Found: ${filtered.length} issues\n`));
173
+
174
+ // Always preview first
175
+ filtered.forEach((i: any) => {
176
+ console.log(` ${chalk.dim(`#${String(i.number).padStart(2, '0')}`)} ${i.title}`);
177
+ console.log(chalk.dim(` [${(i.labels || []).join(', ')}]`));
178
+ });
179
+
180
+ // Ask for confirmation
181
+ const inquirer = (await import('inquirer')).default;
182
+ const { proceed } = await inquirer.prompt([{
183
+ type: 'confirm',
184
+ name: 'proceed',
185
+ message: chalk.yellow(`Create these ${filtered.length} issues on GitHub?`),
186
+ default: true,
187
+ }]);
188
+
189
+ if (!proceed) {
190
+ console.log(chalk.dim('\n Cancelled.\n'));
166
191
  return;
167
192
  }
168
193