@link-assistant/hive-mind 1.54.6 â 1.54.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/CHANGELOG.md +12 -0
- package/package.json +4 -4
- package/src/github-merge-ci.lib.mjs +4 -2
- package/src/github-merge-repo-actions.lib.mjs +5 -4
- package/src/github-merge.lib.mjs +29 -34
- package/src/log-upload.lib.mjs +1 -1
- package/src/solve.accept-invite.lib.mjs +2 -2
- package/src/solve.auto-merge-helpers.lib.mjs +5 -3
- package/src/solve.auto-pr.lib.mjs +3 -3
- package/src/solve.feedback.lib.mjs +6 -4
- package/src/solve.mjs +2 -2
- package/src/solve.progress-monitoring.lib.mjs +1 -1
- package/src/solve.repository.lib.mjs +1 -1
- package/src/solve.results.lib.mjs +8 -8
- package/src/telegram-accept-invitations.lib.mjs +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.54.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 12f5761: Fix `--auto-restart-until-mergeable` readiness comment deduplication for pull requests with more than one page of comments, and enforce pagination on list-returning `gh api` calls.
|
|
8
|
+
|
|
9
|
+
## 1.54.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 06b1a41: Fix `--auto-attach-solution-summary` so the AI-comment scan starts at the current work session instead of the older feedback reference time.
|
|
14
|
+
|
|
3
15
|
## 1.54.6
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/hive-mind",
|
|
3
|
-
"version": "1.54.
|
|
3
|
+
"version": "1.54.8",
|
|
4
4
|
"description": "AI-powered issue solver and hive mind for collaborative problem solving",
|
|
5
5
|
"main": "src/hive.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
"hive-telegram-bot": "./src/telegram-bot.mjs"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-codex-support.mjs && node tests/test-build-cost-info-string.mjs && node tests/test-claude-code-install-method.mjs && node tests/test-claude-quiet-config.mjs && node tests/test-configure-claude-bin.mjs && node tests/test-docker-release-order.mjs && node tests/test-docker-box-migration.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-pre-pr-failure-notifier-1640.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-telegram-bot-launcher.mjs",
|
|
17
|
+
"test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-codex-support.mjs && node tests/test-build-cost-info-string.mjs && node tests/test-claude-code-install-method.mjs && node tests/test-claude-quiet-config.mjs && node tests/test-configure-claude-bin.mjs && node tests/test-docker-release-order.mjs && node tests/test-docker-box-migration.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-pre-pr-failure-notifier-1640.mjs && node tests/test-ready-to-merge-pagination-1645.mjs && node tests/test-require-gh-paginate-rule.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-telegram-bot-launcher.mjs",
|
|
18
18
|
"test:queue": "node tests/solve-queue.test.mjs",
|
|
19
19
|
"test:limits-display": "node tests/limits-display.test.mjs",
|
|
20
20
|
"test:usage-limit": "node tests/test-usage-limit.mjs",
|
|
21
|
-
"lint": "eslint 'src/**/*.{js,mjs,cjs}'",
|
|
22
|
-
"lint:fix": "eslint 'src/**/*.{js,mjs,cjs}' --fix",
|
|
21
|
+
"lint": "eslint 'src/**/*.{js,mjs,cjs}' 'scripts/**/*.{js,mjs,cjs}' 'eslint-rules/**/*.{js,mjs,cjs}'",
|
|
22
|
+
"lint:fix": "eslint 'src/**/*.{js,mjs,cjs}' 'scripts/**/*.{js,mjs,cjs}' 'eslint-rules/**/*.{js,mjs,cjs}' --fix",
|
|
23
23
|
"check:duplication": "jscpd .",
|
|
24
24
|
"format": "prettier --write \"**/*.{js,mjs,json,md}\" --ignore-path .prettierignore",
|
|
25
25
|
"format:check": "prettier --check \"**/*.{js,mjs,json,md}\" --ignore-path .prettierignore",
|
|
@@ -185,8 +185,10 @@ export async function checkBranchCIHealth(owner, repo, branch = 'main', options,
|
|
|
185
185
|
|
|
186
186
|
// Issue #1425: Query CI runs specifically for the HEAD SHA (no status filter).
|
|
187
187
|
// This ensures we see in-progress runs for the latest commit, not just completed ones.
|
|
188
|
-
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?head_sha=${headSha}&per_page=
|
|
189
|
-
const runs = JSON.parse(stdout.trim() || '[]')
|
|
188
|
+
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?head_sha=${headSha}&per_page=100" --paginate --slurp`);
|
|
189
|
+
const runs = JSON.parse(stdout.trim() || '[]')
|
|
190
|
+
.flatMap(page => page.workflow_runs || [])
|
|
191
|
+
.map(run => ({ id: run.id, name: run.name, status: run.status, conclusion: run.conclusion, html_url: run.html_url, head_sha: run.head_sha, created_at: run.created_at }));
|
|
190
192
|
|
|
191
193
|
if (verbose) {
|
|
192
194
|
console.log(`[VERBOSE] /merge: Found ${runs.length} CI run(s) for HEAD commit ${headSha.substring(0, 7)} on ${owner}/${repo} branch ${branch}`);
|
|
@@ -22,10 +22,11 @@ const exec = promisify(execCallback);
|
|
|
22
22
|
*/
|
|
23
23
|
export async function getAllActiveRepoRuns(owner, repo, verbose = false) {
|
|
24
24
|
try {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?per_page=100" --paginate --slurp`);
|
|
26
|
+
const runs = JSON.parse(stdout.trim() || '[]')
|
|
27
|
+
.flatMap(page => page.workflow_runs || [])
|
|
28
|
+
.filter(run => ['in_progress', 'queued', 'waiting', 'requested', 'pending'].includes(run.status))
|
|
29
|
+
.map(run => ({ id: run.id, name: run.name, status: run.status, head_branch: run.head_branch, head_sha: run.head_sha?.slice(0, 7) }));
|
|
29
30
|
if (verbose && runs.length > 0) {
|
|
30
31
|
console.log(`[VERBOSE] repo-actions: ${runs.length} active run(s) in ${owner}/${repo}`);
|
|
31
32
|
for (const r of runs) console.log(`[VERBOSE] repo-actions: ${r.name} (${r.status}) on ${r.head_branch}`);
|
package/src/github-merge.lib.mjs
CHANGED
|
@@ -313,9 +313,8 @@ export async function checkPRCIStatus(owner, repo, prNumber, verbose = false) {
|
|
|
313
313
|
const prData = JSON.parse(prJson.trim());
|
|
314
314
|
const sha = prData.headRefOid;
|
|
315
315
|
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
const checkRuns = JSON.parse(checksJson.trim() || '[]');
|
|
316
|
+
const { stdout: checksJson } = await exec(`gh api repos/${owner}/${repo}/commits/${sha}/check-runs --paginate --slurp`);
|
|
317
|
+
const checkRuns = JSON.parse(checksJson.trim() || '[]').flatMap(page => page.check_runs || []);
|
|
319
318
|
|
|
320
319
|
// Get commit statuses (some CI systems use status API instead of checks API)
|
|
321
320
|
const { stdout: statusJson } = await exec(`gh api repos/${owner}/${repo}/commits/${sha}/status --jq '.statuses'`);
|
|
@@ -685,9 +684,11 @@ export function parseRepositoryUrl(url) {
|
|
|
685
684
|
export async function getActiveBranchRuns(owner, repo, branch = 'main', verbose = false) {
|
|
686
685
|
try {
|
|
687
686
|
// Query for in_progress and queued runs on the specified branch
|
|
688
|
-
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?branch=${branch}&per_page=
|
|
689
|
-
|
|
690
|
-
|
|
687
|
+
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?branch=${branch}&per_page=100" --paginate --slurp`);
|
|
688
|
+
const runs = JSON.parse(stdout.trim() || '[]')
|
|
689
|
+
.flatMap(page => page.workflow_runs || [])
|
|
690
|
+
.filter(run => run.status === 'in_progress' || run.status === 'queued')
|
|
691
|
+
.map(run => ({ id: run.id, name: run.name, status: run.status, created_at: run.created_at, html_url: run.html_url }));
|
|
691
692
|
|
|
692
693
|
if (verbose) {
|
|
693
694
|
console.log(`[VERBOSE] /merge: Found ${runs.length} active runs on ${owner}/${repo} branch ${branch}`);
|
|
@@ -839,7 +840,7 @@ export async function getDefaultBranch(owner, repo, verbose = false) {
|
|
|
839
840
|
*/
|
|
840
841
|
export async function getCheckRunAnnotations(owner, repo, checkRunId, verbose = false) {
|
|
841
842
|
try {
|
|
842
|
-
const { stdout } = await exec(`gh api repos/${owner}/${repo}/check-runs/${checkRunId}/annotations 2>/dev/null || echo "[]"`);
|
|
843
|
+
const { stdout } = await exec(`gh api repos/${owner}/${repo}/check-runs/${checkRunId}/annotations --paginate 2>/dev/null || echo "[]"`);
|
|
843
844
|
const annotations = JSON.parse(stdout.trim() || '[]');
|
|
844
845
|
|
|
845
846
|
if (verbose) {
|
|
@@ -895,12 +896,6 @@ export const BILLING_LIMIT_ERROR_PATTERN = 'The job was not started because rece
|
|
|
895
896
|
* Check if CI failure is due to billing/spending limits
|
|
896
897
|
* Issue #1314: Detects when GitHub Actions jobs fail due to billing issues rather than code problems
|
|
897
898
|
*
|
|
898
|
-
* Detection criteria:
|
|
899
|
-
* 1. Job has conclusion='failure'
|
|
900
|
-
* 2. Job has empty steps array (no steps were executed)
|
|
901
|
-
* 3. Job has runner_id=0 or null (no runner was assigned)
|
|
902
|
-
* 4. Annotation contains the billing limit error message
|
|
903
|
-
*
|
|
904
899
|
* @param {string} owner - Repository owner
|
|
905
900
|
* @param {string} repo - Repository name
|
|
906
901
|
* @param {number} prNumber - Pull request number
|
|
@@ -909,14 +904,14 @@ export const BILLING_LIMIT_ERROR_PATTERN = 'The job was not started because rece
|
|
|
909
904
|
*/
|
|
910
905
|
export async function checkForBillingLimitError(owner, repo, prNumber, verbose = false) {
|
|
911
906
|
try {
|
|
912
|
-
// Get the PR's head SHA
|
|
913
907
|
const { stdout: prJson } = await exec(`gh pr view ${prNumber} --repo ${owner}/${repo} --json headRefOid`);
|
|
914
908
|
const prData = JSON.parse(prJson.trim());
|
|
915
909
|
const sha = prData.headRefOid;
|
|
916
910
|
|
|
917
|
-
|
|
918
|
-
const
|
|
919
|
-
|
|
911
|
+
const { stdout: runsJson } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?head_sha=${sha}&per_page=100" --paginate --slurp`);
|
|
912
|
+
const runIds = JSON.parse(runsJson.trim() || '[]')
|
|
913
|
+
.flatMap(page => page.workflow_runs || [])
|
|
914
|
+
.map(run => run.id);
|
|
920
915
|
|
|
921
916
|
if (verbose) {
|
|
922
917
|
console.log(`[VERBOSE] /merge: Found ${runIds.length} workflow runs for PR #${prNumber} at SHA ${sha.substring(0, 7)}`);
|
|
@@ -925,11 +920,10 @@ export async function checkForBillingLimitError(owner, repo, prNumber, verbose =
|
|
|
925
920
|
const affectedJobs = [];
|
|
926
921
|
let totalJobs = 0;
|
|
927
922
|
|
|
928
|
-
// Check each workflow run's jobs
|
|
929
923
|
for (const runId of runIds) {
|
|
930
924
|
try {
|
|
931
|
-
const { stdout: jobsJson } = await exec(`gh api repos/${owner}/${repo}/actions/runs/${runId}/jobs --
|
|
932
|
-
const jobs = JSON.parse(jobsJson.trim() || '[]');
|
|
925
|
+
const { stdout: jobsJson } = await exec(`gh api repos/${owner}/${repo}/actions/runs/${runId}/jobs --paginate --slurp`);
|
|
926
|
+
const jobs = JSON.parse(jobsJson.trim() || '[]').flatMap(page => page.jobs || []);
|
|
933
927
|
|
|
934
928
|
for (const job of jobs) {
|
|
935
929
|
totalJobs++;
|
|
@@ -1068,9 +1062,8 @@ export async function getDetailedCIStatus(owner, repo, prNumber, verbose = false
|
|
|
1068
1062
|
const prData = JSON.parse(prJson.trim());
|
|
1069
1063
|
const sha = prData.headRefOid;
|
|
1070
1064
|
|
|
1071
|
-
|
|
1072
|
-
const
|
|
1073
|
-
const checkRuns = JSON.parse(checksJson.trim() || '[]');
|
|
1065
|
+
const { stdout: checksJson } = await exec(`gh api repos/${owner}/${repo}/commits/${sha}/check-runs --paginate --slurp`);
|
|
1066
|
+
const checkRuns = JSON.parse(checksJson.trim() || '[]').flatMap(page => page.check_runs || []);
|
|
1074
1067
|
|
|
1075
1068
|
// Get commit statuses
|
|
1076
1069
|
const { stdout: statusJson } = await exec(`gh api repos/${owner}/${repo}/commits/${sha}/status --jq '.statuses'`);
|
|
@@ -1214,8 +1207,10 @@ export async function getDetailedCIStatus(owner, repo, prNumber, verbose = false
|
|
|
1214
1207
|
*/
|
|
1215
1208
|
export async function getWorkflowRunsForSha(owner, repo, sha, verbose = false) {
|
|
1216
1209
|
try {
|
|
1217
|
-
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?head_sha=${sha}&per_page=
|
|
1218
|
-
const runs = JSON.parse(stdout.trim() || '[]')
|
|
1210
|
+
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?head_sha=${sha}&per_page=100" --paginate --slurp`);
|
|
1211
|
+
const runs = JSON.parse(stdout.trim() || '[]')
|
|
1212
|
+
.flatMap(page => page.workflow_runs || [])
|
|
1213
|
+
.map(run => ({ id: run.id, status: run.status, conclusion: run.conclusion, name: run.name, html_url: run.html_url }));
|
|
1219
1214
|
|
|
1220
1215
|
if (verbose) {
|
|
1221
1216
|
console.log(`[VERBOSE] /merge: Found ${runs.length} workflow runs for SHA ${sha.substring(0, 7)}`);
|
|
@@ -1251,13 +1246,13 @@ export async function getWorkflowRunsForSha(owner, repo, sha, verbose = false) {
|
|
|
1251
1246
|
*/
|
|
1252
1247
|
export async function getActiveRepoWorkflows(owner, repo, verbose = false) {
|
|
1253
1248
|
try {
|
|
1254
|
-
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/workflows" --
|
|
1255
|
-
const allWorkflows = JSON.parse(stdout.trim() || '[]')
|
|
1249
|
+
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/workflows" --paginate --slurp`);
|
|
1250
|
+
const allWorkflows = JSON.parse(stdout.trim() || '[]')
|
|
1251
|
+
.flatMap(page => page.workflows || [])
|
|
1252
|
+
.filter(workflow => workflow.state === 'active')
|
|
1253
|
+
.map(workflow => ({ id: workflow.id, name: workflow.name, state: workflow.state, path: workflow.path }));
|
|
1256
1254
|
|
|
1257
|
-
//
|
|
1258
|
-
// These have path "dynamic/pages/pages-build-deployment" and only run on the
|
|
1259
|
-
// default branch after merge â they never produce check-runs on PR branches.
|
|
1260
|
-
// Including them causes an infinite loop when waiting for PR CI checks.
|
|
1255
|
+
// GitHub Pages workflows only run after merge and never produce PR check-runs.
|
|
1261
1256
|
const workflows = allWorkflows.filter(wf => !wf.path.startsWith('dynamic/pages/'));
|
|
1262
1257
|
|
|
1263
1258
|
if (verbose) {
|
|
@@ -1351,8 +1346,8 @@ export async function checkPreviousPRCommitsHadCI(owner, repo, prNumber, headSha
|
|
|
1351
1346
|
|
|
1352
1347
|
for (const sha of commitsToCheck) {
|
|
1353
1348
|
try {
|
|
1354
|
-
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?head_sha=${sha}&per_page=
|
|
1355
|
-
const count =
|
|
1349
|
+
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?head_sha=${sha}&per_page=100" --paginate --slurp`);
|
|
1350
|
+
const count = JSON.parse(stdout.trim() || '[]').reduce((sum, page) => sum + (page.workflow_runs?.length || 0), 0);
|
|
1356
1351
|
if (count > 0) {
|
|
1357
1352
|
commitsWithCI++;
|
|
1358
1353
|
}
|
|
@@ -1390,7 +1385,7 @@ export async function checkWorkflowsHavePRTriggers(owner, repo, verbose = false,
|
|
|
1390
1385
|
// Issue #1503: Support querying workflow files from a specific branch (ref)
|
|
1391
1386
|
const refParam = ref ? `?ref=${encodeURIComponent(ref)}` : '';
|
|
1392
1387
|
// List workflow files in .github/workflows/ (uses ref if provided, otherwise default branch)
|
|
1393
|
-
const { stdout: listJson } = await exec(`gh api "repos/${owner}/${repo}/contents/.github/workflows${refParam}" --jq '[.[] | select(.name | test("\\\\.(yml|yaml)$")) | {name: .name, download_url: .download_url, path: .path}]' 2>/dev/null`);
|
|
1388
|
+
const { stdout: listJson } = await exec(`gh api "repos/${owner}/${repo}/contents/.github/workflows${refParam}" --paginate --jq '[.[] | select(.name | test("\\\\.(yml|yaml)$")) | {name: .name, download_url: .download_url, path: .path}]' 2>/dev/null`);
|
|
1394
1389
|
const files = JSON.parse(listJson.trim() || '[]');
|
|
1395
1390
|
|
|
1396
1391
|
if (files.length === 0) {
|
package/src/log-upload.lib.mjs
CHANGED
|
@@ -149,7 +149,7 @@ export const uploadLogWithGhUploadLog = async ({ logFile, isPublic, description,
|
|
|
149
149
|
if (verbose) {
|
|
150
150
|
await log(` đ Fetching repository contents for raw URL resolution (repoPath=${repoPath})`, { verbose: true });
|
|
151
151
|
}
|
|
152
|
-
const contentsResult = await $silent`gh api repos/${repoPath}/contents --jq '.[].name'`;
|
|
152
|
+
const contentsResult = await $silent`gh api repos/${repoPath}/contents --paginate --jq '.[].name'`;
|
|
153
153
|
if (verbose) {
|
|
154
154
|
await log(` đĨ Repository contents fetch completed (code=${contentsResult.code ?? 'unknown'})`, { verbose: true });
|
|
155
155
|
}
|
|
@@ -43,7 +43,7 @@ export async function autoAcceptInviteForRepo(owner, repo, log, verbose) {
|
|
|
43
43
|
|
|
44
44
|
// Check for pending repository invitation
|
|
45
45
|
try {
|
|
46
|
-
const { stdout: repoInvJson } = await ghRetry(() => exec('gh api /user/repository_invitations 2>/dev/null || echo "[]"'), { label: 'fetch repo invitations' });
|
|
46
|
+
const { stdout: repoInvJson } = await ghRetry(() => exec('gh api /user/repository_invitations --paginate 2>/dev/null || echo "[]"'), { label: 'fetch repo invitations' });
|
|
47
47
|
const repoInvitations = JSON.parse(repoInvJson.trim() || '[]');
|
|
48
48
|
verbose && (await log(` Found ${repoInvitations.length} total pending repo invitation(s)`, { verbose: true }));
|
|
49
49
|
|
|
@@ -66,7 +66,7 @@ export async function autoAcceptInviteForRepo(owner, repo, log, verbose) {
|
|
|
66
66
|
|
|
67
67
|
// Check for pending organization membership
|
|
68
68
|
try {
|
|
69
|
-
const { stdout: orgMemJson } = await ghRetry(() => exec('gh api /user/memberships/orgs 2>/dev/null || echo "[]"'), { label: 'fetch org memberships' });
|
|
69
|
+
const { stdout: orgMemJson } = await ghRetry(() => exec('gh api /user/memberships/orgs --paginate 2>/dev/null || echo "[]"'), { label: 'fetch org memberships' });
|
|
70
70
|
const orgMemberships = JSON.parse(orgMemJson.trim() || '[]');
|
|
71
71
|
const pendingOrgs = orgMemberships.filter(m => m.state === 'pending');
|
|
72
72
|
verbose && (await log(` Found ${pendingOrgs.length} total pending org invitation(s)`, { verbose: true }));
|
|
@@ -63,12 +63,14 @@ const { SESSION_ENDING_MARKERS } = toolComments;
|
|
|
63
63
|
* @param {number} prNumber - Pull request number
|
|
64
64
|
* @param {string} commentSignature - Unique signature to search for in comment body (e.g., "â
Ready to merge")
|
|
65
65
|
* @param {boolean} verbose - Enable verbose logging
|
|
66
|
+
* @param {Function} commandRunner - Tagged-template command runner, injectable for tests
|
|
66
67
|
* @returns {Promise<boolean>} - True if a matching comment already exists
|
|
67
68
|
*/
|
|
68
|
-
export const checkForExistingComment = async (owner, repo, prNumber, commentSignature, verbose = false) => {
|
|
69
|
+
export const checkForExistingComment = async (owner, repo, prNumber, commentSignature, verbose = false, commandRunner = $) => {
|
|
69
70
|
try {
|
|
70
|
-
// Fetch
|
|
71
|
-
|
|
71
|
+
// Fetch every PR comment page so long threads don't scope deduplication to
|
|
72
|
+
// a stale first-page session-ending marker.
|
|
73
|
+
const result = await commandRunner`gh api repos/${owner}/${repo}/issues/${prNumber}/comments --paginate --jq '[.[].body]' 2>/dev/null`;
|
|
72
74
|
if (result.code === 0 && result.stdout) {
|
|
73
75
|
const rawOutput = result.stdout.toString().trim();
|
|
74
76
|
if (!rawOutput) return false;
|
|
@@ -604,7 +604,7 @@ Proceed.
|
|
|
604
604
|
}
|
|
605
605
|
compareResult = await $({
|
|
606
606
|
silent: true,
|
|
607
|
-
})`gh api repos/${owner}/${repo}/compare/${targetBranchForCompare}...${headRef} --jq '.ahead_by' 2>&1`;
|
|
607
|
+
})`gh api repos/${owner}/${repo}/compare/${targetBranchForCompare}...${headRef} --paginate --jq '.ahead_by' 2>&1`;
|
|
608
608
|
|
|
609
609
|
if (compareResult.code === 0) {
|
|
610
610
|
const aheadBy = parseInt(compareResult.stdout.toString().trim(), 10);
|
|
@@ -798,9 +798,9 @@ Proceed.
|
|
|
798
798
|
// Use the correct head reference for the compare API check
|
|
799
799
|
if (argv.fork && forkedRepo) {
|
|
800
800
|
const forkUser = forkedRepo.split('/')[0];
|
|
801
|
-
await log(` gh api repos/${owner}/${repo}/compare/${targetBranchForCompare}...${forkUser}:${branchName}`);
|
|
801
|
+
await log(` gh api repos/${owner}/${repo}/compare/${targetBranchForCompare}...${forkUser}:${branchName} --paginate`);
|
|
802
802
|
} else {
|
|
803
|
-
await log(` gh api repos/${owner}/${repo}/compare/${targetBranchForCompare}...${branchName}`);
|
|
803
|
+
await log(` gh api repos/${owner}/${repo}/compare/${targetBranchForCompare}...${branchName} --paginate`);
|
|
804
804
|
}
|
|
805
805
|
await log('');
|
|
806
806
|
|
|
@@ -329,10 +329,12 @@ export const detectAndCountFeedback = async params => {
|
|
|
329
329
|
|
|
330
330
|
// 6. Check for failed PR checks
|
|
331
331
|
try {
|
|
332
|
-
const
|
|
333
|
-
if (
|
|
334
|
-
const
|
|
335
|
-
const
|
|
332
|
+
const prHeadResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber} --jq '.head.sha'`;
|
|
333
|
+
if (prHeadResult.code === 0) {
|
|
334
|
+
const prHeadSha = prHeadResult.stdout.toString().trim();
|
|
335
|
+
const checksResult = await $`gh api repos/${owner}/${repo}/commits/${prHeadSha}/check-runs --paginate --slurp`;
|
|
336
|
+
const checkRuns = checksResult.code === 0 ? JSON.parse(checksResult.stdout.toString() || '[]').flatMap(page => page.check_runs || []) : [];
|
|
337
|
+
const failedChecks = checkRuns.filter(check => check.conclusion === 'failure' && new Date(check.completed_at) > lastCommitTime);
|
|
336
338
|
|
|
337
339
|
if (failedChecks.length > 0) {
|
|
338
340
|
feedbackLines.push(`Failed pull request checks: ${failedChecks.length}`);
|
package/src/solve.mjs
CHANGED
|
@@ -642,7 +642,7 @@ try {
|
|
|
642
642
|
// Continue mode is a manual resume via PR URL
|
|
643
643
|
sessionType = SESSION_TYPES.RESUME;
|
|
644
644
|
}
|
|
645
|
-
await startWorkSession({
|
|
645
|
+
const workStartTime = await startWorkSession({
|
|
646
646
|
isContinueMode,
|
|
647
647
|
prNumber,
|
|
648
648
|
argv,
|
|
@@ -1190,7 +1190,7 @@ try {
|
|
|
1190
1190
|
} else if (argv.autoAttachSolutionSummary) {
|
|
1191
1191
|
// Auto mode - only attach if AI didn't create comments
|
|
1192
1192
|
await log('đ Checking if AI created any comments during session (--auto-attach-solution-summary)...');
|
|
1193
|
-
const aiCreatedComments = await checkForAiCreatedComments(
|
|
1193
|
+
const aiCreatedComments = await checkForAiCreatedComments(workStartTime, owner, repo, prNumber, issueNumber);
|
|
1194
1194
|
if (aiCreatedComments) {
|
|
1195
1195
|
await log('âšī¸ AI created comments during session, skipping solution summary attachment');
|
|
1196
1196
|
} else {
|
|
@@ -242,7 +242,7 @@ export const createProgressMonitor = ({ owner, repo, prNumber, $, log, verbose =
|
|
|
242
242
|
state.commentId = posted.commentId;
|
|
243
243
|
} else {
|
|
244
244
|
// Fallback: find the comment we just created by looking for our marker
|
|
245
|
-
const commentsResult = await $`gh api repos/${owner}/${repo}/issues/${prNumber}/comments --jq ${`[.[] | select(.body | contains("${CONFIG.PROGRESS_SECTION_START}")) | .id] | last`}`;
|
|
245
|
+
const commentsResult = await $`gh api repos/${owner}/${repo}/issues/${prNumber}/comments --paginate --jq ${`[.[] | select(.body | contains("${CONFIG.PROGRESS_SECTION_START}")) | .id] | last`}`;
|
|
246
246
|
const commentId = commentsResult.stdout?.toString?.().trim();
|
|
247
247
|
if (commentId && commentId !== 'null') {
|
|
248
248
|
state.commentId = commentId;
|
|
@@ -525,7 +525,7 @@ export const setupRepository = async (argv, owner, repo, forkOwner = null, issue
|
|
|
525
525
|
await log(`${formatAligned('đ', 'Safety check:', 'Comparing commits against upstream...')}`);
|
|
526
526
|
let safeToDelete = false;
|
|
527
527
|
try {
|
|
528
|
-
const cmp = await $`gh api repos/${owner}/${repo}/compare/${owner}:HEAD...${existingForkName.split('/')[0]}:HEAD --jq '.ahead_by' 2>&1`;
|
|
528
|
+
const cmp = await $`gh api repos/${owner}/${repo}/compare/${owner}:HEAD...${existingForkName.split('/')[0]}:HEAD --paginate --jq '.ahead_by' 2>&1`;
|
|
529
529
|
if (cmp.code === 0 && parseInt(cmp.stdout.toString().trim(), 10) === 0) {
|
|
530
530
|
await log(`${formatAligned('â
', 'Safe to delete:', 'No additional commits in non-fork repository')}`);
|
|
531
531
|
safeToDelete = true;
|
|
@@ -1058,14 +1058,14 @@ export const { TOOL_GENERATED_COMMENT_MARKERS, isToolGeneratedComment, trackTool
|
|
|
1058
1058
|
* Issue #1625: Filter out comments produced by solve.mjs itself (session start,
|
|
1059
1059
|
* log upload, auto-restart, etc.) so they do not falsely count as AI-authored.
|
|
1060
1060
|
*
|
|
1061
|
-
* @param {Date}
|
|
1061
|
+
* @param {Date} sessionStartTime - The timestamp when this solve work session started
|
|
1062
1062
|
* @param {string} owner - Repository owner
|
|
1063
1063
|
* @param {string} repo - Repository name
|
|
1064
1064
|
* @param {number} prNumber - Pull request number (null if working on issue only)
|
|
1065
1065
|
* @param {number} issueNumber - Issue number
|
|
1066
1066
|
* @returns {Promise<boolean>} - True if AI created comments during the session
|
|
1067
1067
|
*/
|
|
1068
|
-
export const checkForAiCreatedComments = async (
|
|
1068
|
+
export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, prNumber, issueNumber) => {
|
|
1069
1069
|
try {
|
|
1070
1070
|
// Get the current user's GitHub username
|
|
1071
1071
|
const userResult = await $`gh api user --jq .login`;
|
|
@@ -1077,10 +1077,10 @@ export const checkForAiCreatedComments = async (referenceTime, owner, repo, prNu
|
|
|
1077
1077
|
return false;
|
|
1078
1078
|
}
|
|
1079
1079
|
|
|
1080
|
-
await log(`đ Checking comments by '${currentUser}' after ${
|
|
1080
|
+
await log(`đ Checking comments by '${currentUser}' after session start ${sessionStartTime.toISOString()} (PR #${prNumber ?? 'none'}, issue #${issueNumber ?? 'none'})`, { verbose: true });
|
|
1081
1081
|
|
|
1082
1082
|
// Issue #1625: A comment counts as an "AI comment" only if it was posted
|
|
1083
|
-
// by the current user AFTER
|
|
1083
|
+
// by the current user AFTER sessionStartTime AND solve.mjs did NOT post it
|
|
1084
1084
|
// itself. We identify tool-posted comments in two ways, in order:
|
|
1085
1085
|
// 1. Primary: comment ID is in the in-memory tracked set populated by
|
|
1086
1086
|
// every solve.mjs posting site (postTrackedComment / trackToolCommentId).
|
|
@@ -1097,7 +1097,7 @@ export const checkForAiCreatedComments = async (referenceTime, owner, repo, prNu
|
|
|
1097
1097
|
const skippedByIdCount = { n: 0 };
|
|
1098
1098
|
for (const comment of comments) {
|
|
1099
1099
|
if (!comment || !comment.user || comment.user.login !== currentUser) continue;
|
|
1100
|
-
if (!(new Date(comment.created_at) >
|
|
1100
|
+
if (!(new Date(comment.created_at) > sessionStartTime)) continue;
|
|
1101
1101
|
|
|
1102
1102
|
const isReview = kind === 'review';
|
|
1103
1103
|
if (!isReview) {
|
|
@@ -1132,7 +1132,7 @@ export const checkForAiCreatedComments = async (referenceTime, owner, repo, prNu
|
|
|
1132
1132
|
if (prCommentsResult.code === 0) {
|
|
1133
1133
|
const prComments = JSON.parse(prCommentsResult.stdout.toString().trim() || '[]');
|
|
1134
1134
|
const newPrComments = filterNewAiComments(prComments, 'pr');
|
|
1135
|
-
await log(` đ¨ PR conversation comments after
|
|
1135
|
+
await log(` đ¨ PR conversation comments after session start by '${currentUser}' (excluding tool-generated): ${newPrComments.length}`, { verbose: true });
|
|
1136
1136
|
if (newPrComments.length > 0) {
|
|
1137
1137
|
return true;
|
|
1138
1138
|
}
|
|
@@ -1143,7 +1143,7 @@ export const checkForAiCreatedComments = async (referenceTime, owner, repo, prNu
|
|
|
1143
1143
|
if (reviewCommentsResult.code === 0) {
|
|
1144
1144
|
const reviewComments = JSON.parse(reviewCommentsResult.stdout.toString().trim() || '[]');
|
|
1145
1145
|
const newReviewComments = filterNewAiComments(reviewComments, 'review');
|
|
1146
|
-
await log(` đ PR review (inline) comments after
|
|
1146
|
+
await log(` đ PR review (inline) comments after session start by '${currentUser}': ${newReviewComments.length}`, { verbose: true });
|
|
1147
1147
|
if (newReviewComments.length > 0) {
|
|
1148
1148
|
return true;
|
|
1149
1149
|
}
|
|
@@ -1156,7 +1156,7 @@ export const checkForAiCreatedComments = async (referenceTime, owner, repo, prNu
|
|
|
1156
1156
|
if (issueCommentsResult.code === 0) {
|
|
1157
1157
|
const issueComments = JSON.parse(issueCommentsResult.stdout.toString().trim() || '[]');
|
|
1158
1158
|
const newIssueComments = filterNewAiComments(issueComments, 'issue');
|
|
1159
|
-
await log(` đ¨ Issue comments after
|
|
1159
|
+
await log(` đ¨ Issue comments after session start by '${currentUser}' (excluding tool-generated): ${newIssueComments.length}`, { verbose: true });
|
|
1160
1160
|
if (newIssueComments.length > 0) {
|
|
1161
1161
|
return true;
|
|
1162
1162
|
}
|
|
@@ -174,13 +174,13 @@ export function registerAcceptInvitesCommand(bot, options) {
|
|
|
174
174
|
|
|
175
175
|
try {
|
|
176
176
|
// Fetch repository invitations
|
|
177
|
-
const { stdout: repoInvJson } = await exec('gh api /user/repository_invitations 2>/dev/null || echo "[]"');
|
|
177
|
+
const { stdout: repoInvJson } = await exec('gh api /user/repository_invitations --paginate 2>/dev/null || echo "[]"');
|
|
178
178
|
const repoInvitations = JSON.parse(repoInvJson.trim() || '[]');
|
|
179
179
|
state.totalRepos = repoInvitations.length;
|
|
180
180
|
VERBOSE && console.log(`[VERBOSE] Found ${repoInvitations.length} pending repo invitations`);
|
|
181
181
|
|
|
182
182
|
// Fetch organization invitations
|
|
183
|
-
const { stdout: orgMemJson } = await exec('gh api /user/memberships/orgs 2>/dev/null || echo "[]"');
|
|
183
|
+
const { stdout: orgMemJson } = await exec('gh api /user/memberships/orgs --paginate 2>/dev/null || echo "[]"');
|
|
184
184
|
const orgMemberships = JSON.parse(orgMemJson.trim() || '[]');
|
|
185
185
|
const pendingOrgs = orgMemberships.filter(m => m.state === 'pending');
|
|
186
186
|
state.totalOrgs = pendingOrgs.length;
|