@link-assistant/hive-mind 1.59.1 → 1.59.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/CHANGELOG.md +55 -0
- package/package.json +1 -1
- package/src/solve.fork-detection.lib.mjs +126 -0
- package/src/solve.mjs +48 -118
- package/src/telegram-bot.mjs +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.59.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b0bffdc: Fix `solve` to skip fork mode when the upstream repository is private and the
|
|
8
|
+
user has direct write access — even when the existing PR was created from a
|
|
9
|
+
fork (issue #1716).
|
|
10
|
+
|
|
11
|
+
Previously, when a PR was originally created from a fork (e.g. the upstream
|
|
12
|
+
repo was public and the user without write access used `--auto-fork`), but
|
|
13
|
+
the upstream is now private and the user has direct write access, `solve`
|
|
14
|
+
still tried to clone the fork. If the fork had been renamed, deleted, or was
|
|
15
|
+
otherwise inaccessible (which is common after a public→private flip), repo
|
|
16
|
+
setup failed with `Fork not accessible`.
|
|
17
|
+
|
|
18
|
+
The auto-fork path already handled this correctly (logging
|
|
19
|
+
_"Auto-fork: Write access detected to private repository, working directly on
|
|
20
|
+
repository"_ and leaving `forkOwner = null`). The bug was that **continue
|
|
21
|
+
mode** — both the auto-continue path and the direct PR-URL path — re-set
|
|
22
|
+
`forkOwner` from the existing PR's head repository unconditionally,
|
|
23
|
+
overriding the auto-fork bypass.
|
|
24
|
+
|
|
25
|
+
Fix: in [`src/solve.mjs`](./src/solve.mjs):
|
|
26
|
+
- Hoist `detectRepositoryVisibility(owner, repo)` out of the
|
|
27
|
+
`if (argv.autoCleanup === undefined)` block so `isRepoPublic` is
|
|
28
|
+
unconditionally available.
|
|
29
|
+
- Compute one bypass flag,
|
|
30
|
+
`skipForkForPrivateUpstream = !isRepoPublic && !argv.fork && hasWriteAccess`.
|
|
31
|
+
- Gate both fork-from-PR-data branches behind it. When set, log
|
|
32
|
+
_"Issue #1716: Working directly on the private upstream repository"_ and
|
|
33
|
+
leave `forkOwner = null` so the regular non-fork code path runs.
|
|
34
|
+
- Gate the maintainer-modify auto-toggle on `forkOwner` being non-null so it
|
|
35
|
+
doesn't fire when the bypass triggered.
|
|
36
|
+
|
|
37
|
+
Explicit `--fork` still wins (the bypass requires `!argv.fork`), and users
|
|
38
|
+
with no write access on a private repo still hit the existing auto-fork
|
|
39
|
+
private-repo guard (the bypass requires `hasWriteAccess`).
|
|
40
|
+
|
|
41
|
+
Tests: [`tests/test-issue-1716-private-repo-skip-fork.mjs`](./tests/test-issue-1716-private-repo-skip-fork.mjs)
|
|
42
|
+
locks the flag declaration, the exact condition formula, both
|
|
43
|
+
fork-detection paths, and four scenario simulations
|
|
44
|
+
(private+writeAccess → bypass; public → no bypass; explicit `--fork` → no
|
|
45
|
+
bypass; no writeAccess → no bypass).
|
|
46
|
+
|
|
47
|
+
Documentation: [`docs/case-studies/issue-1716/`](./docs/case-studies/issue-1716/README.md)
|
|
48
|
+
contains the timeline reconstructed from the user's failure log, the
|
|
49
|
+
distilled facts, the per-symptom root-cause analysis, and the implementation
|
|
50
|
+
plan.
|
|
51
|
+
|
|
52
|
+
## 1.59.2
|
|
53
|
+
|
|
54
|
+
### Patch Changes
|
|
55
|
+
|
|
56
|
+
- 9e96635: Fix Telegram `/solve` repo-not-accessible message still suggesting `--auto-accept-invite` even when that flag is already active (issue #1714). After issue #1694 flipped `--auto-accept-invite` to default-on, `src/telegram-bot.mjs` was passing `autoAcceptInvite: args.some(a => a === '--auto-accept-invite')` to `validateGitHubEntityExistence()` — but the literal flag is no longer present in the typical default-on invocation, so the suppression added by issue #1692 silently regressed. The call now reads `parsedSolveArgs?.autoAcceptInvite` (matching the auto-accept pre-check two lines above), so the hint is suppressed when the flag is active and only shown when the user explicitly opts out with `--no-auto-accept-invite`. Adds `tests/test-issue-1714-auto-accept-invite-hint.mjs` covering the parsed-argv contract and a source-level guard against the `args.some(...)` form returning, plus a case study under `docs/case-studies/issue-1714/`.
|
|
57
|
+
|
|
3
58
|
## 1.59.1
|
|
4
59
|
|
|
5
60
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fork-detection helpers for solve.mjs
|
|
5
|
+
*
|
|
6
|
+
* Extracted from solve.mjs to keep the file under the 1500-line CI limit.
|
|
7
|
+
* - handleAutoForkOption: implements the --auto-fork detection branch.
|
|
8
|
+
* - handleMaintainerForkAccess: handles the
|
|
9
|
+
* --allow-to-push-to-contributors-pull-requests-as-maintainer follow-up
|
|
10
|
+
* that runs after a fork PR has been detected.
|
|
11
|
+
*
|
|
12
|
+
* Tests for Issue #1716 grep solve.mjs textually, so the *call sites* there
|
|
13
|
+
* still hold the canonical condition checks; only the bodies live here.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
if (typeof globalThis.use === 'undefined') {
|
|
17
|
+
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
18
|
+
}
|
|
19
|
+
const use = globalThis.use;
|
|
20
|
+
const { $ } = await use('command-stream');
|
|
21
|
+
|
|
22
|
+
const lib = await import('./lib.mjs');
|
|
23
|
+
const { log, ghCmdRetry } = lib;
|
|
24
|
+
const githubLib = await import('./github.lib.mjs');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Handle the --auto-fork option: when the user lacks write access to a public
|
|
28
|
+
* repository, automatically enable fork mode; when the repository is private,
|
|
29
|
+
* fail with an actionable error.
|
|
30
|
+
*
|
|
31
|
+
* Mutates argv.fork in place when fork mode is enabled.
|
|
32
|
+
*
|
|
33
|
+
* @param {object} params
|
|
34
|
+
* @param {string} params.owner
|
|
35
|
+
* @param {string} params.repo
|
|
36
|
+
* @param {object} params.argv - CLI arguments (mutated: argv.fork may be set)
|
|
37
|
+
* @param {(code: number, reason?: string) => Promise<void>} params.safeExit
|
|
38
|
+
*/
|
|
39
|
+
export async function handleAutoForkOption({ owner, repo, argv, safeExit }) {
|
|
40
|
+
if (!argv.autoFork || argv.fork) return;
|
|
41
|
+
|
|
42
|
+
const { detectRepositoryVisibility } = githubLib;
|
|
43
|
+
await log('🔍 Checking repository access for auto-fork...');
|
|
44
|
+
const permResult = await ghCmdRetry(() => $`gh api repos/${owner}/${repo} --jq .permissions`, { label: 'auto-fork perms' });
|
|
45
|
+
|
|
46
|
+
if (permResult.code === 0) {
|
|
47
|
+
const permissions = JSON.parse(permResult.stdout.toString().trim());
|
|
48
|
+
const hasWriteAccess = permissions.push === true || permissions.admin === true || permissions.maintain === true;
|
|
49
|
+
|
|
50
|
+
if (!hasWriteAccess) {
|
|
51
|
+
const { isPublic } = await detectRepositoryVisibility(owner, repo);
|
|
52
|
+
|
|
53
|
+
if (!isPublic) {
|
|
54
|
+
await log('');
|
|
55
|
+
await log("❌ --auto-fork failed: Repository is private and you don't have write access", { level: 'error' });
|
|
56
|
+
await log('');
|
|
57
|
+
await log(' 🔍 What happened:', { level: 'error' });
|
|
58
|
+
await log(` Repository ${owner}/${repo} is private`, { level: 'error' });
|
|
59
|
+
await log(" You don't have write access to this repository", { level: 'error' });
|
|
60
|
+
await log(' --auto-fork cannot create a fork of a private repository you cannot access', { level: 'error' });
|
|
61
|
+
await log('');
|
|
62
|
+
await log(' 💡 Solution:', { level: 'error' });
|
|
63
|
+
await log(' • Request collaborator access from the repository owner', { level: 'error' });
|
|
64
|
+
await log(` https://github.com/${owner}/${repo}/settings/access`, { level: 'error' });
|
|
65
|
+
await log('');
|
|
66
|
+
await safeExit(1, 'Auto-fork failed - private repository without access');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await log('✅ Auto-fork: No write access detected, enabling fork mode');
|
|
71
|
+
argv.fork = true;
|
|
72
|
+
} else {
|
|
73
|
+
const { isPublic } = await detectRepositoryVisibility(owner, repo);
|
|
74
|
+
await log(`✅ Auto-fork: Write access detected to ${isPublic ? 'public' : 'private'} repository, working directly on repository`);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
const { isPublic } = await detectRepositoryVisibility(owner, repo);
|
|
78
|
+
|
|
79
|
+
if (!isPublic) {
|
|
80
|
+
await log('');
|
|
81
|
+
await log('❌ --auto-fork failed: Could not verify permissions for private repository', { level: 'error' });
|
|
82
|
+
await log('');
|
|
83
|
+
await log(' 🔍 What happened:', { level: 'error' });
|
|
84
|
+
await log(` Repository ${owner}/${repo} is private`, { level: 'error' });
|
|
85
|
+
await log(' Could not check your permissions to this repository', { level: 'error' });
|
|
86
|
+
await log('');
|
|
87
|
+
await log(' 💡 Solutions:', { level: 'error' });
|
|
88
|
+
await log(' • Check your GitHub CLI authentication: gh auth status', { level: 'error' });
|
|
89
|
+
await log(" • Request collaborator access if you don't have it yet", { level: 'error' });
|
|
90
|
+
await log(` https://github.com/${owner}/${repo}/settings/access`, { level: 'error' });
|
|
91
|
+
await log('');
|
|
92
|
+
await safeExit(1, 'Auto-fork failed - cannot verify private repository permissions');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await log('⚠️ Auto-fork: Could not check permissions, enabling fork mode for public repository');
|
|
97
|
+
argv.fork = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* After a fork PR is detected, optionally check whether the maintainer can
|
|
103
|
+
* push directly to the contributor's fork. If not, request access.
|
|
104
|
+
*
|
|
105
|
+
* @param {object} params
|
|
106
|
+
* @param {string} params.owner
|
|
107
|
+
* @param {string} params.repo
|
|
108
|
+
* @param {string|number} params.prNumber
|
|
109
|
+
*/
|
|
110
|
+
export async function handleMaintainerForkAccess({ owner, repo, prNumber }) {
|
|
111
|
+
const { checkMaintainerCanModifyPR, requestMaintainerAccess } = githubLib;
|
|
112
|
+
const { canModify } = await checkMaintainerCanModifyPR(owner, repo, prNumber);
|
|
113
|
+
|
|
114
|
+
if (canModify) {
|
|
115
|
+
await log('✅ Maintainer can push to fork: Enabled by contributor');
|
|
116
|
+
await log(" Will push changes directly to contributor's fork instead of creating own fork");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await log('⚠️ Maintainer cannot push to fork: "Allow edits by maintainers" is not enabled', { level: 'warning' });
|
|
121
|
+
await log(' Posting comment to request access...', { level: 'warning' });
|
|
122
|
+
await requestMaintainerAccess(owner, repo, prNumber);
|
|
123
|
+
await log(' Comment posted. Proceeding with own fork instead.', { level: 'warning' });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default { handleAutoForkOption, handleMaintainerForkAccess };
|
package/src/solve.mjs
CHANGED
|
@@ -58,6 +58,7 @@ const { postTrackedComment, USAGE_LIMIT_REACHED_MARKER } = await import('./tool-
|
|
|
58
58
|
const { prepareFeedbackAndTimestamps, checkUncommittedChanges, checkForkActions } = await import('./solve.preparation.lib.mjs');
|
|
59
59
|
const { validateAndExitOnInvalidModel } = await import('./models/index.mjs');
|
|
60
60
|
const { autoAcceptInviteForRepo } = await import('./solve.accept-invite.lib.mjs');
|
|
61
|
+
const { handleAutoForkOption, handleMaintainerForkAccess } = await import('./solve.fork-detection.lib.mjs');
|
|
61
62
|
// Initialize log file early (before argument parsing) to capture all output
|
|
62
63
|
const logFile = await initializeLogFile(null);
|
|
63
64
|
// Log version and raw command IMMEDIATELY after log file initialization
|
|
@@ -209,73 +210,7 @@ if (argv.autoAcceptInvite) {
|
|
|
209
210
|
await autoAcceptInviteForRepo(owner, repo, log, argv.verbose);
|
|
210
211
|
}
|
|
211
212
|
// Handle --auto-fork option: automatically fork public repositories without write access
|
|
212
|
-
|
|
213
|
-
const { detectRepositoryVisibility } = githubLib;
|
|
214
|
-
// Check if we have write access first (issue #1536: retry on transient network errors)
|
|
215
|
-
await log('🔍 Checking repository access for auto-fork...');
|
|
216
|
-
const permResult = await lib.ghCmdRetry(() => $`gh api repos/${owner}/${repo} --jq .permissions`, { label: 'auto-fork perms' });
|
|
217
|
-
|
|
218
|
-
if (permResult.code === 0) {
|
|
219
|
-
const permissions = JSON.parse(permResult.stdout.toString().trim());
|
|
220
|
-
const hasWriteAccess = permissions.push === true || permissions.admin === true || permissions.maintain === true;
|
|
221
|
-
|
|
222
|
-
if (!hasWriteAccess) {
|
|
223
|
-
// No write access - check if repository is public before enabling fork mode
|
|
224
|
-
const { isPublic } = await detectRepositoryVisibility(owner, repo);
|
|
225
|
-
|
|
226
|
-
if (!isPublic) {
|
|
227
|
-
// Private repository without write access - cannot fork
|
|
228
|
-
await log('');
|
|
229
|
-
await log("❌ --auto-fork failed: Repository is private and you don't have write access", { level: 'error' });
|
|
230
|
-
await log('');
|
|
231
|
-
await log(' 🔍 What happened:', { level: 'error' });
|
|
232
|
-
await log(` Repository ${owner}/${repo} is private`, { level: 'error' });
|
|
233
|
-
await log(" You don't have write access to this repository", { level: 'error' });
|
|
234
|
-
await log(' --auto-fork cannot create a fork of a private repository you cannot access', {
|
|
235
|
-
level: 'error',
|
|
236
|
-
});
|
|
237
|
-
await log('');
|
|
238
|
-
await log(' 💡 Solution:', { level: 'error' });
|
|
239
|
-
await log(' • Request collaborator access from the repository owner', { level: 'error' });
|
|
240
|
-
await log(` https://github.com/${owner}/${repo}/settings/access`, { level: 'error' });
|
|
241
|
-
await log('');
|
|
242
|
-
await safeExit(1, 'Auto-fork failed - private repository without access');
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Public repository without write access - automatically enable fork mode
|
|
246
|
-
await log('✅ Auto-fork: No write access detected, enabling fork mode');
|
|
247
|
-
argv.fork = true;
|
|
248
|
-
} else {
|
|
249
|
-
// Has write access - work directly on the repo (works for both public and private repos)
|
|
250
|
-
const { isPublic } = await detectRepositoryVisibility(owner, repo);
|
|
251
|
-
await log(`✅ Auto-fork: Write access detected to ${isPublic ? 'public' : 'private'} repository, working directly on repository`);
|
|
252
|
-
}
|
|
253
|
-
} else {
|
|
254
|
-
// Could not check permissions - assume no access and try to fork if public
|
|
255
|
-
const { isPublic } = await detectRepositoryVisibility(owner, repo);
|
|
256
|
-
|
|
257
|
-
if (!isPublic) {
|
|
258
|
-
// Cannot determine permissions for private repo - fail safely
|
|
259
|
-
await log('');
|
|
260
|
-
await log('❌ --auto-fork failed: Could not verify permissions for private repository', { level: 'error' });
|
|
261
|
-
await log('');
|
|
262
|
-
await log(' 🔍 What happened:', { level: 'error' });
|
|
263
|
-
await log(` Repository ${owner}/${repo} is private`, { level: 'error' });
|
|
264
|
-
await log(' Could not check your permissions to this repository', { level: 'error' });
|
|
265
|
-
await log('');
|
|
266
|
-
await log(' 💡 Solutions:', { level: 'error' });
|
|
267
|
-
await log(' • Check your GitHub CLI authentication: gh auth status', { level: 'error' });
|
|
268
|
-
await log(" • Request collaborator access if you don't have it yet", { level: 'error' });
|
|
269
|
-
await log(` https://github.com/${owner}/${repo}/settings/access`, { level: 'error' });
|
|
270
|
-
await log('');
|
|
271
|
-
await safeExit(1, 'Auto-fork failed - cannot verify private repository permissions');
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Public repository but couldn't check permissions - assume no access and fork
|
|
275
|
-
await log('⚠️ Auto-fork: Could not check permissions, enabling fork mode for public repository');
|
|
276
|
-
argv.fork = true;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
213
|
+
await handleAutoForkOption({ owner, repo, argv, safeExit });
|
|
279
214
|
// Permission check BEFORE entity validation (#1552): avoids false 404 on private repos without access
|
|
280
215
|
const { checkRepositoryWritePermission } = githubLib;
|
|
281
216
|
const hasWriteAccess = await checkRepositoryWritePermission(owner, repo, {
|
|
@@ -296,19 +231,26 @@ if (!entityCheck.valid) {
|
|
|
296
231
|
await safeExit(1, `GitHub entity not found (${entityCheck.level})`);
|
|
297
232
|
}
|
|
298
233
|
|
|
299
|
-
// Detect repository visibility and
|
|
234
|
+
// Detect repository visibility once and reuse for downstream decisions
|
|
235
|
+
// (auto-cleanup default + Issue #1716 private-repo fork bypass)
|
|
236
|
+
const { detectRepositoryVisibility } = githubLib;
|
|
237
|
+
const { isPublic: isRepoPublic } = await detectRepositoryVisibility(owner, repo);
|
|
300
238
|
if (argv.autoCleanup === undefined) {
|
|
301
|
-
const { detectRepositoryVisibility } = githubLib;
|
|
302
|
-
const { isPublic } = await detectRepositoryVisibility(owner, repo);
|
|
303
239
|
// For public repos: keep temp directories (default false)
|
|
304
240
|
// For private repos: clean up temp directories (default true)
|
|
305
|
-
argv.autoCleanup = !
|
|
241
|
+
argv.autoCleanup = !isRepoPublic;
|
|
306
242
|
if (argv.verbose) {
|
|
307
|
-
await log(` Auto-cleanup default: ${argv.autoCleanup} (repository is ${
|
|
243
|
+
await log(` Auto-cleanup default: ${argv.autoCleanup} (repository is ${isRepoPublic ? 'public' : 'private'})`, {
|
|
308
244
|
verbose: true,
|
|
309
245
|
});
|
|
310
246
|
}
|
|
311
247
|
}
|
|
248
|
+
// Issue #1716: When the upstream repository is private and the user has direct
|
|
249
|
+
// write access, fork-based workflows should be skipped — even if the existing
|
|
250
|
+
// PR was originally created from a fork. Forks of private repositories often
|
|
251
|
+
// become inaccessible (renamed, deleted, parent re-private'd) and there's no
|
|
252
|
+
// reason to use them when we can push branches and PRs to the upstream repo.
|
|
253
|
+
const skipForkForPrivateUpstream = !isRepoPublic && !argv.fork && hasWriteAccess;
|
|
312
254
|
// Determine mode and get issue details
|
|
313
255
|
let issueNumber;
|
|
314
256
|
let prNumber;
|
|
@@ -345,32 +287,26 @@ if (autoContinueResult.isContinueMode) {
|
|
|
345
287
|
await log(` Merge status: ${mergeStateStatus || 'UNKNOWN'}`, { verbose: true });
|
|
346
288
|
}
|
|
347
289
|
if (prCheckData.headRepositoryOwner && prCheckData.headRepositoryOwner.login !== owner) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
await log(
|
|
354
|
-
|
|
290
|
+
const detectedForkOwner = prCheckData.headRepositoryOwner.login;
|
|
291
|
+
const detectedForkRepoName = prCheckData.headRepository && prCheckData.headRepository.name ? prCheckData.headRepository.name : null;
|
|
292
|
+
// Issue #1716: Skip fork mode for private upstream repos with write access.
|
|
293
|
+
if (skipForkForPrivateUpstream) {
|
|
294
|
+
await log(`🔒 Detected fork PR from ${detectedForkOwner}/${detectedForkRepoName || repo}, but upstream ${owner}/${repo} is private and you have write access.`);
|
|
295
|
+
await log(' Working directly on the private upstream repository (Issue #1716).');
|
|
296
|
+
} else {
|
|
297
|
+
forkOwner = detectedForkOwner;
|
|
298
|
+
// Get actual fork repository name (may be prefixed) and store for use in setupRepository
|
|
299
|
+
forkRepoName = detectedForkRepoName;
|
|
300
|
+
await log(`🍴 Detected fork PR from ${forkOwner}/${forkRepoName || repo}`);
|
|
301
|
+
if (argv.verbose) {
|
|
302
|
+
await log(` Fork owner: ${forkOwner}`, { verbose: true });
|
|
303
|
+
await log(' Will clone fork repository for continue mode', { verbose: true });
|
|
304
|
+
}
|
|
355
305
|
}
|
|
356
306
|
|
|
357
307
|
// Check if maintainer can push to the fork when --allow-to-push-to-contributors-pull-requests-as-maintainer is enabled
|
|
358
|
-
if (argv.allowToPushToContributorsPullRequestsAsMaintainer && argv.autoFork) {
|
|
359
|
-
|
|
360
|
-
const { canModify } = await checkMaintainerCanModifyPR(owner, repo, prNumber);
|
|
361
|
-
|
|
362
|
-
if (canModify) {
|
|
363
|
-
await log('✅ Maintainer can push to fork: Enabled by contributor');
|
|
364
|
-
await log(" Will push changes directly to contributor's fork instead of creating own fork");
|
|
365
|
-
// Don't disable fork mode, but we'll use the contributor's fork
|
|
366
|
-
} else {
|
|
367
|
-
await log('⚠️ Maintainer cannot push to fork: "Allow edits by maintainers" is not enabled', {
|
|
368
|
-
level: 'warning',
|
|
369
|
-
});
|
|
370
|
-
await log(' Posting comment to request access...', { level: 'warning' });
|
|
371
|
-
await requestMaintainerAccess(owner, repo, prNumber);
|
|
372
|
-
await log(' Comment posted. Proceeding with own fork instead.', { level: 'warning' });
|
|
373
|
-
}
|
|
308
|
+
if (forkOwner && argv.allowToPushToContributorsPullRequestsAsMaintainer && argv.autoFork) {
|
|
309
|
+
await handleMaintainerForkAccess({ owner, repo, prNumber });
|
|
374
310
|
}
|
|
375
311
|
}
|
|
376
312
|
}
|
|
@@ -425,32 +361,26 @@ if (isPrUrl) {
|
|
|
425
361
|
prState = prData.state;
|
|
426
362
|
// Check if this is a fork PR
|
|
427
363
|
if (prData.headRepositoryOwner && prData.headRepositoryOwner.login !== owner) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
await log(
|
|
434
|
-
|
|
364
|
+
const detectedForkOwner = prData.headRepositoryOwner.login;
|
|
365
|
+
const detectedForkRepoName = prData.headRepository && prData.headRepository.name ? prData.headRepository.name : null;
|
|
366
|
+
// Issue #1716: Skip fork mode for private upstream repos with write access.
|
|
367
|
+
if (skipForkForPrivateUpstream) {
|
|
368
|
+
await log(`🔒 Detected fork PR from ${detectedForkOwner}/${detectedForkRepoName || repo}, but upstream ${owner}/${repo} is private and you have write access.`);
|
|
369
|
+
await log(' Working directly on the private upstream repository (Issue #1716).');
|
|
370
|
+
} else {
|
|
371
|
+
forkOwner = detectedForkOwner;
|
|
372
|
+
// Get actual fork repository name and store for use in setupRepository
|
|
373
|
+
forkRepoName = detectedForkRepoName;
|
|
374
|
+
await log(`🍴 Detected fork PR from ${forkOwner}/${forkRepoName || repo}`);
|
|
375
|
+
if (argv.verbose) {
|
|
376
|
+
await log(` Fork owner: ${forkOwner}`, { verbose: true });
|
|
377
|
+
await log(' Will clone fork repository for continue mode', { verbose: true });
|
|
378
|
+
}
|
|
435
379
|
}
|
|
436
380
|
|
|
437
381
|
// Check if maintainer can push to the fork when --allow-to-push-to-contributors-pull-requests-as-maintainer is enabled
|
|
438
|
-
if (argv.allowToPushToContributorsPullRequestsAsMaintainer && argv.autoFork) {
|
|
439
|
-
|
|
440
|
-
const { canModify } = await checkMaintainerCanModifyPR(owner, repo, prNumber);
|
|
441
|
-
|
|
442
|
-
if (canModify) {
|
|
443
|
-
await log('✅ Maintainer can push to fork: Enabled by contributor');
|
|
444
|
-
await log(" Will push changes directly to contributor's fork instead of creating own fork");
|
|
445
|
-
// Don't disable fork mode, but we'll use the contributor's fork
|
|
446
|
-
} else {
|
|
447
|
-
await log('⚠️ Maintainer cannot push to fork: "Allow edits by maintainers" is not enabled', {
|
|
448
|
-
level: 'warning',
|
|
449
|
-
});
|
|
450
|
-
await log(' Posting comment to request access...', { level: 'warning' });
|
|
451
|
-
await requestMaintainerAccess(owner, repo, prNumber);
|
|
452
|
-
await log(' Comment posted. Proceeding with own fork instead.', { level: 'warning' });
|
|
453
|
-
}
|
|
382
|
+
if (forkOwner && argv.allowToPushToContributorsPullRequestsAsMaintainer && argv.autoFork) {
|
|
383
|
+
await handleMaintainerForkAccess({ owner, repo, prNumber });
|
|
454
384
|
}
|
|
455
385
|
}
|
|
456
386
|
await log(`📝 PR branch: ${prBranch}`);
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -967,7 +967,9 @@ async function handleSolveCommand(ctx) {
|
|
|
967
967
|
VERBOSE && console.log(`[VERBOSE] Auto-accept invite pre-check failed: ${e.message}`);
|
|
968
968
|
}
|
|
969
969
|
}
|
|
970
|
-
|
|
970
|
+
// Issue #1714: read the parsed argv (default-on per #1694) instead of the raw args list,
|
|
971
|
+
// so the invite hint is suppressed on the default-on path where the literal flag is absent.
|
|
972
|
+
const entityCheck = await validateGitHubEntityExistence({ owner: validation.parsed.owner, repo: validation.parsed.repo, number: validation.parsed.number, type: validation.parsed.type, verbose: VERBOSE, autoAcceptInvite: !!parsedSolveArgs?.autoAcceptInvite });
|
|
971
973
|
if (!entityCheck.valid) {
|
|
972
974
|
await safeReply(ctx, `❌ ${escapeMarkdown(entityCheck.error)}`, { reply_to_message_id: ctx.message.message_id });
|
|
973
975
|
return;
|