@link-assistant/hive-mind 2.11.8 → 2.11.10
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 +52 -0
- package/package.json +1 -1
- package/src/bidirectional-interactive.lib.mjs +6 -2
- package/src/child-exit.lib.mjs +107 -0
- package/src/codex-health.lib.mjs +21 -1
- package/src/codex.lib.mjs +43 -5
- package/src/codex.run-diagnostics.lib.mjs +11 -0
- package/src/contributing-guidelines.lib.mjs +19 -6
- package/src/development-log.lib.mjs +82 -5
- package/src/fix.ci-cd-issue.lib.mjs +5 -3
- package/src/fix.mjs +5 -2
- package/src/github-entity-validation.lib.mjs +4 -1
- package/src/github.lib.mjs +3 -3
- package/src/hive.mjs +15 -21
- package/src/isolation-runner.lib.mjs +5 -2
- package/src/lib.mjs +21 -0
- package/src/log-growth.lib.mjs +94 -0
- package/src/opencode.lib.mjs +17 -3
- package/src/pull-request-changes.lib.mjs +94 -24
- package/src/qwen.lib.mjs +42 -8
- package/src/review.mjs +12 -3
- package/src/session-kill-recovery.lib.mjs +6 -2
- package/src/solve.auto-continue.lib.mjs +6 -2
- package/src/solve.auto-merge.lib.mjs +1 -1
- package/src/solve.keep-working.lib.mjs +7 -2
- package/src/solve.minimal-restart-prompt.lib.mjs +11 -3
- package/src/solve.preparation.lib.mjs +5 -1
- package/src/solve.progress-monitoring.lib.mjs +5 -1
- package/src/solve.repository.lib.mjs +5 -2
- package/src/solve.results.lib.mjs +13 -8
- package/src/task.mjs +5 -3
- package/src/telegram-command-execution.lib.mjs +5 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { QUIET_PROBE } from './quiet-probe.lib.mjs';
|
|
2
3
|
import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -68,7 +69,8 @@ export const collectDeferredWorkSources = async ({ owner, repo, prNumber, result
|
|
|
68
69
|
|
|
69
70
|
// 1. Pull request description
|
|
70
71
|
try {
|
|
71
|
-
|
|
72
|
+
// Issue #2135: `mirror: false` - the description is scanned here, not shown.
|
|
73
|
+
const prResult = await $(QUIET_PROBE)`gh api repos/${owner}/${repo}/pulls/${prNumber} --jq '.body // ""'`;
|
|
72
74
|
if (prResult.code === 0) {
|
|
73
75
|
const body = prResult.stdout.toString();
|
|
74
76
|
if (body && body.trim()) {
|
|
@@ -86,7 +88,10 @@ export const collectDeferredWorkSources = async ({ owner, repo, prNumber, result
|
|
|
86
88
|
|
|
87
89
|
// 3. Changed markdown documents (scan only added lines from the diff)
|
|
88
90
|
try {
|
|
89
|
-
|
|
91
|
+
// Issue #2135: `mirror: false`. Every entry carries the file's patch, so
|
|
92
|
+
// this answer is as large as the pull request's diff - and it was being
|
|
93
|
+
// echoed into the log that gets attached to that same pull request.
|
|
94
|
+
const filesResult = await $(QUIET_PROBE)`gh api repos/${owner}/${repo}/pulls/${prNumber}/files --paginate`;
|
|
90
95
|
if (filesResult.code === 0) {
|
|
91
96
|
const files = JSON.parse(filesResult.stdout.toString() || '[]');
|
|
92
97
|
for (const file of files) {
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* @see case-studies/issue-661-session-resume-cost-optimization/
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { QUIET_PROBE } from './quiet-probe.lib.mjs';
|
|
13
|
+
|
|
12
14
|
// Note: This module does not import $ directly
|
|
13
15
|
// Functions receive $ as a parameter from the calling module
|
|
14
16
|
// This ensures consistent command executor usage across the codebase
|
|
@@ -28,9 +30,11 @@ export const generateMinimalRestartPrompt = async (tempDir, $) => {
|
|
|
28
30
|
const uncommittedFiles = gitStatus.stdout.toString().trim();
|
|
29
31
|
|
|
30
32
|
// Get brief diff summaries (not full diffs to keep the prompt minimal)
|
|
31
|
-
|
|
33
|
+
// Issue #2135: `mirror: false` - the summaries go into the prompt below, so
|
|
34
|
+
// echoing them into the log only duplicates them into the attached log file.
|
|
35
|
+
const gitDiffStat = await $({ cwd: tempDir, ...QUIET_PROBE })`git diff --stat`;
|
|
32
36
|
const unstagedDiffSummary = gitDiffStat.stdout.toString().trim();
|
|
33
|
-
const gitCachedDiffStat = await $({ cwd: tempDir })`git diff --cached --stat`;
|
|
37
|
+
const gitCachedDiffStat = await $({ cwd: tempDir, ...QUIET_PROBE })`git diff --cached --stat`;
|
|
34
38
|
const stagedDiffSummary = gitCachedDiffStat.stdout.toString().trim();
|
|
35
39
|
const summarySections = [];
|
|
36
40
|
if (unstagedDiffSummary) summarySections.push(`Unstaged changes:\n${unstagedDiffSummary}`);
|
|
@@ -69,7 +73,11 @@ export const generateFullRestartPrompt = async (issueUrl, issueBody, prNumber, f
|
|
|
69
73
|
const gitStatus = await $({ cwd: tempDir })`git status --porcelain`;
|
|
70
74
|
const uncommittedFiles = gitStatus.stdout.toString().trim();
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
// Issue #2135: `mirror: false`. This is the working tree's whole diff and it
|
|
77
|
+
// is embedded in the prompt below; mirroring it wrote a second copy into the
|
|
78
|
+
// session log, which is attached to the pull request and (with
|
|
79
|
+
// --development-log) committed into the branch the diff is taken from.
|
|
80
|
+
const gitDiff = await $({ cwd: tempDir, ...QUIET_PROBE })`git diff`;
|
|
73
81
|
const fullDiff = gitDiff.stdout.toString();
|
|
74
82
|
|
|
75
83
|
let prompt = `
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
|
|
7
|
+
import { quietProbe } from './quiet-probe.lib.mjs';
|
|
7
8
|
// Import feedback detection functionality
|
|
8
9
|
const feedback = await import('./solve.feedback.lib.mjs');
|
|
9
10
|
const { detectAndCountFeedback } = feedback;
|
|
@@ -45,7 +46,10 @@ export async function prepareFeedbackAndTimestamps({ tempDir = null, prNumber, b
|
|
|
45
46
|
|
|
46
47
|
// Get the last comment's timestamp (if any)
|
|
47
48
|
// Use --paginate to get all comments - GitHub API returns max 30 per page by default
|
|
48
|
-
|
|
49
|
+
// Issue #2135: `mirror: false`. Only the last comment's timestamp is read
|
|
50
|
+
// from this answer, but the answer itself is every comment body on the
|
|
51
|
+
// issue - tens of kilobytes echoed into the log on every run.
|
|
52
|
+
const commentsResult = await quietProbe($)`gh api repos/${owner}/${repo}/issues/${issueNumber}/comments --paginate`;
|
|
49
53
|
|
|
50
54
|
if (commentsResult.code !== 0) {
|
|
51
55
|
await log(`Warning: Failed to get comments: ${commentsResult.stderr ? commentsResult.stderr.toString() : 'Unknown error'}`, { level: 'warning' });
|
|
@@ -30,6 +30,7 @@ import { LIVE_PROGRESS_SECTION_START_MARKER, LIVE_PROGRESS_SECTION_END_MARKER, p
|
|
|
30
30
|
import { writeSanitizedPublicationFile } from './token-sanitization.lib.mjs';
|
|
31
31
|
|
|
32
32
|
import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
|
|
33
|
+
import { quietProbe } from './quiet-probe.lib.mjs'; // issue #2135: keep large read-only probe payloads out of the attached log
|
|
33
34
|
/**
|
|
34
35
|
* Configuration constants for progress monitoring
|
|
35
36
|
*/
|
|
@@ -280,7 +281,10 @@ export const createProgressMonitor = ({ owner, repo, prNumber, $, log, verbose =
|
|
|
280
281
|
state.currentTodos = todos;
|
|
281
282
|
|
|
282
283
|
// Fetch current PR description
|
|
283
|
-
|
|
284
|
+
// Issue #2135: `mirror: false`. This runs on every progress update and
|
|
285
|
+
// the answer is the whole pull-request description, which by then holds
|
|
286
|
+
// the progress section itself.
|
|
287
|
+
const prData = await quietProbe($)`gh pr view ${prNumber} --repo ${owner}/${repo} --json body`;
|
|
284
288
|
const prInfo = JSON.parse(prData.stdout);
|
|
285
289
|
let currentBody = prInfo.body || '';
|
|
286
290
|
|
|
@@ -65,7 +65,8 @@ export const checkExistingForkOfRoot = async rootRepo => {
|
|
|
65
65
|
// not to the shell, and command-stream quotes interpolated values itself - so
|
|
66
66
|
// interpolating inside the quotes would leak shell quotes into the comparison.
|
|
67
67
|
const forkFilter = `.[] | select(.owner.login == ${JSON.stringify(currentUser)}) | .full_name`;
|
|
68
|
-
|
|
68
|
+
// Issue #2135: `mirror: false` - see the fork-name lookup below.
|
|
69
|
+
const forksResult = await lib.ghCmdRetry(() => $(QUIET_PROBE)`gh api repos/${rootRepo}/forks --paginate --jq ${forkFilter}`, { label: `check forks of ${rootRepo}` });
|
|
69
70
|
if (forksResult.code !== 0) return null;
|
|
70
71
|
|
|
71
72
|
const forks = forksResult.stdout
|
|
@@ -1225,7 +1226,9 @@ export const setupPrForkRemote = async (tempDir, argv, prForkOwner, repo, isCont
|
|
|
1225
1226
|
// Issue #2119: the double quotes here are jq syntax, so the expression is
|
|
1226
1227
|
// built in JS and interpolated as one already-escaped argument.
|
|
1227
1228
|
const forkNameFilter = `.[] | select(.owner.login == ${JSON.stringify(prForkOwner)}) | .name`;
|
|
1228
|
-
|
|
1229
|
+
// Issue #2135: `mirror: false` - a popular repository has thousands of
|
|
1230
|
+
// forks, and only the matching name is used.
|
|
1231
|
+
const forksResult = await $(QUIET_PROBE)`gh api repos/${owner}/${repo}/forks --paginate --jq ${forkNameFilter}`;
|
|
1229
1232
|
if (forksResult.code === 0 && forksResult.stdout) {
|
|
1230
1233
|
const forkName = forksResult.stdout.toString().trim().split('\n')[0]; // Take first match
|
|
1231
1234
|
if (forkName) {
|
|
@@ -407,7 +407,9 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
407
407
|
// APPROACH 3: Check for modifications before reverting (proactive detection)
|
|
408
408
|
// This is the main strategy - detect if the file was modified after initial commit
|
|
409
409
|
await log(` Checking if ${fileName} was modified since initial commit...`, { verbose: true });
|
|
410
|
-
|
|
410
|
+
// Issue #2135: `mirror: false`. Only "is it non-empty" is asked here, and
|
|
411
|
+
// the answer is a file's whole diff.
|
|
412
|
+
const diffResult = await $({ cwd: tempDir, ...QUIET_PROBE })`git diff ${commitToRevert} HEAD -- ${fileName} 2>&1`;
|
|
411
413
|
|
|
412
414
|
if (diffResult.stdout && diffResult.stdout.trim()) {
|
|
413
415
|
// File was modified after initial commit - use manual approach to avoid conflicts
|
|
@@ -746,7 +748,8 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
746
748
|
// First, get all PRs from our branch
|
|
747
749
|
// IMPORTANT: Use --state all to find PRs that may have been merged during the session (Issue #1008)
|
|
748
750
|
// Without --state all, gh pr list only returns OPEN PRs, missing merged ones
|
|
749
|
-
|
|
751
|
+
// Issue #2135: `mirror: false` - the pull requests found are named below.
|
|
752
|
+
const allBranchPrsResult = await $(QUIET_PROBE)`gh pr list --repo ${owner}/${repo} --head ${branchName} --state all --json number,url,createdAt,headRefName,title,state,updatedAt,isDraft`;
|
|
750
753
|
|
|
751
754
|
if (allBranchPrsResult.code !== 0) {
|
|
752
755
|
await log(' ⚠️ Failed to check pull requests');
|
|
@@ -819,7 +822,7 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
819
822
|
// "1 file(s) modified, 1 line(s) added" for a pull request that
|
|
820
823
|
// changed nothing, because the stats were never checked for being
|
|
821
824
|
// empty.
|
|
822
|
-
const changeStats = await getPullRequestChangeStats({ owner, repo, prNumber: pr.number,
|
|
825
|
+
const changeStats = await getPullRequestChangeStats({ owner, repo, prNumber: pr.number, $, log });
|
|
823
826
|
if (!changeStats.hasChanges) {
|
|
824
827
|
await log(` ⚠️ PR #${pr.number} has an empty diff - the description will say so instead of claiming changes`, { level: 'warning' });
|
|
825
828
|
}
|
|
@@ -948,7 +951,9 @@ Fixes ${issueRef}
|
|
|
948
951
|
|
|
949
952
|
// Get all comments and filter them
|
|
950
953
|
// Use --paginate to get all comments - GitHub API returns max 30 per page by default
|
|
951
|
-
|
|
954
|
+
// Issue #2135: `mirror: false` - the counts below are the report; the raw
|
|
955
|
+
// answer is every comment body on the issue.
|
|
956
|
+
const allCommentsResult = await $(QUIET_PROBE)`gh api repos/${owner}/${repo}/issues/${issueNumber}/comments --paginate`;
|
|
952
957
|
|
|
953
958
|
if (allCommentsResult.code !== 0) {
|
|
954
959
|
await log(' ⚠️ Failed to check comments');
|
|
@@ -1209,7 +1214,7 @@ export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, p
|
|
|
1209
1214
|
// Check comments on the PR first (if we have a PR)
|
|
1210
1215
|
if (prNumber) {
|
|
1211
1216
|
// Check PR conversation comments
|
|
1212
|
-
const prCommentsResult = await
|
|
1217
|
+
const prCommentsResult = await $(QUIET_PROBE)`gh api repos/${owner}/${repo}/issues/${prNumber}/comments --paginate`;
|
|
1213
1218
|
if (prCommentsResult.code === 0) {
|
|
1214
1219
|
const prComments = JSON.parse(prCommentsResult.stdout.toString().trim() || '[]');
|
|
1215
1220
|
const newPrComments = filterNewAiComments(prComments, 'pr');
|
|
@@ -1220,7 +1225,7 @@ export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, p
|
|
|
1220
1225
|
}
|
|
1221
1226
|
|
|
1222
1227
|
// Check PR review comments (inline code comments)
|
|
1223
|
-
const reviewCommentsResult = await
|
|
1228
|
+
const reviewCommentsResult = await $(QUIET_PROBE)`gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate`;
|
|
1224
1229
|
if (reviewCommentsResult.code === 0) {
|
|
1225
1230
|
const reviewComments = JSON.parse(reviewCommentsResult.stdout.toString().trim() || '[]');
|
|
1226
1231
|
const newReviewComments = filterNewAiComments(reviewComments, 'review');
|
|
@@ -1233,7 +1238,7 @@ export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, p
|
|
|
1233
1238
|
|
|
1234
1239
|
// Check issue comments (if different from PR number or no PR)
|
|
1235
1240
|
if (issueNumber && issueNumber !== prNumber) {
|
|
1236
|
-
const issueCommentsResult = await
|
|
1241
|
+
const issueCommentsResult = await $(QUIET_PROBE)`gh api repos/${owner}/${repo}/issues/${issueNumber}/comments --paginate`;
|
|
1237
1242
|
if (issueCommentsResult.code === 0) {
|
|
1238
1243
|
const issueComments = JSON.parse(issueCommentsResult.stdout.toString().trim() || '[]');
|
|
1239
1244
|
const newIssueComments = filterNewAiComments(issueComments, 'issue');
|
|
@@ -1413,7 +1418,7 @@ export const maybeAttachWorkingSessionSummary = async ({ argv, resultSummary, wo
|
|
|
1413
1418
|
: null);
|
|
1414
1419
|
// Issue #2119: a summary posted on a pull request that changed nothing must
|
|
1415
1420
|
// say so, instead of reading as a report of completed work.
|
|
1416
|
-
const changeStats = prNumber ? await getPullRequestChangeStats({ owner, repo, prNumber,
|
|
1421
|
+
const changeStats = prNumber ? await getPullRequestChangeStats({ owner, repo, prNumber, $, log }) : null;
|
|
1417
1422
|
|
|
1418
1423
|
// Issue #2132: the summary carries no cost/budget block. `resolvedBudgetStatsData`
|
|
1419
1424
|
// is computed only so the caller can reuse it for this session's log comment.
|
package/src/task.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import crypto from 'crypto';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
|
+
import { describeChildExit } from './child-exit.lib.mjs';
|
|
6
7
|
import { promises as fs } from 'fs';
|
|
7
8
|
import { buildStartAgentArgs, resolveStartAgentCommand } from './task.agent-command.lib.mjs';
|
|
8
9
|
import { getDefaultTaskModel, parseTaskArguments } from './task.config.lib.mjs';
|
|
@@ -112,8 +113,8 @@ function runCommand(command, args, options = {}) {
|
|
|
112
113
|
child.on('error', error => {
|
|
113
114
|
resolve({ code: 1, stdout, stderr: stderr || error.message });
|
|
114
115
|
});
|
|
115
|
-
child.on('close', code => {
|
|
116
|
-
resolve({ code, stdout, stderr });
|
|
116
|
+
child.on('close', (code, signal) => {
|
|
117
|
+
resolve({ code, stdout, stderr, signal });
|
|
117
118
|
});
|
|
118
119
|
});
|
|
119
120
|
}
|
|
@@ -122,7 +123,8 @@ async function commandOutput(command, args, options = {}) {
|
|
|
122
123
|
const result = await runCommand(command, args, options);
|
|
123
124
|
if (result.code !== 0) {
|
|
124
125
|
const output = `${result.stderr || ''}${result.stdout || ''}`.trim();
|
|
125
|
-
|
|
126
|
+
// Issue #2135: `describeChildExit` names a signal instead of "code null".
|
|
127
|
+
throw new Error(output || describeChildExit({ command, code: result.code, signal: result.signal }));
|
|
126
128
|
}
|
|
127
129
|
return result.stdout.trim();
|
|
128
130
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
|
+
import { describeChildExit } from './child-exit.lib.mjs';
|
|
2
3
|
import { promisify } from 'util';
|
|
3
4
|
import { exec as execCallback } from 'child_process';
|
|
4
5
|
import { t } from './i18n.lib.mjs';
|
|
@@ -57,7 +58,7 @@ function executeWithCommand(startScreenCmd, command, args, verbose = false) {
|
|
|
57
58
|
});
|
|
58
59
|
});
|
|
59
60
|
|
|
60
|
-
child.on('close', code => {
|
|
61
|
+
child.on('close', (code, signal) => {
|
|
61
62
|
if (code === 0) {
|
|
62
63
|
resolve({
|
|
63
64
|
success: true,
|
|
@@ -67,7 +68,9 @@ function executeWithCommand(startScreenCmd, command, args, verbose = false) {
|
|
|
67
68
|
resolve({
|
|
68
69
|
success: false,
|
|
69
70
|
output: stdout,
|
|
70
|
-
|
|
71
|
+
// Issue #2135: name the signal, so an out-of-memory abort is not
|
|
72
|
+
// reported as "code null".
|
|
73
|
+
error: stderr || describeChildExit({ command: 'Command', code, signal }),
|
|
71
74
|
});
|
|
72
75
|
}
|
|
73
76
|
});
|