@link-assistant/hive-mind 1.56.0 → 1.56.2
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 +39 -0
- package/README.md +8 -3
- package/package.json +1 -1
- package/src/github.lib.mjs +14 -2
- package/src/hive-screens.lib.mjs +71 -17
- package/src/solve.pre-pr-failure-notifier.lib.mjs +26 -12
- package/src/solve.repository.lib.mjs +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.56.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d39f08f: fix(hive-screens): make `--list` default to `--all`, print log/issue after `--enter` exits, and actually close sessions on `--close`
|
|
8
|
+
|
|
9
|
+
Addresses issue #1654:
|
|
10
|
+
- `hive-screens --list` now defaults to `--all` so a bare `--list` lists every
|
|
11
|
+
match, matching user expectations. `--enter` and `--close` keep `--oldest` as
|
|
12
|
+
their default because they are destructive.
|
|
13
|
+
- `hive-screens --enter` now prints `Log:` and `Issue:` lines **after** the
|
|
14
|
+
user detaches from the screen session, so the information is not wiped by
|
|
15
|
+
`screen -r` swapping to the alternate buffer.
|
|
16
|
+
- `hive-screens --close` now spawns `screen -X stuff exit\n` directly (with
|
|
17
|
+
the newline as a literal argv element) instead of shelling out with bash
|
|
18
|
+
ANSI-C quoting (`$'exit\n'`). The legacy form relied on `/bin/sh` being
|
|
19
|
+
bash, but on Debian/Ubuntu it is `dash`, which does not understand
|
|
20
|
+
`$'...'` — so the previous command sent the literal string `$exit\n` into
|
|
21
|
+
each session and never actually closed it.
|
|
22
|
+
- Adds a `--verbose` / `-v` flag that prints scanning diagnostics to stderr.
|
|
23
|
+
|
|
24
|
+
## 1.56.1
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- 32035a2: Issue #1651: When fork-parent auto-recovery tries to delete the mismatched
|
|
29
|
+
fork and the GitHub CLI token is missing the `delete_repo` scope, `solve`
|
|
30
|
+
now prints the real remediation (`gh auth refresh -h github.com -s delete_repo`)
|
|
31
|
+
plus a non-destructive alternative (rename/archive + `--prefix-fork-name-with-owner-name`)
|
|
32
|
+
instead of re-recommending the same `gh repo delete` command that just failed.
|
|
33
|
+
In `--verbose` mode the full `gh` output is also printed so future root-cause
|
|
34
|
+
analyses have the diagnostic lines GitHub already provides.
|
|
35
|
+
|
|
36
|
+
Pre-PR failures that are posted back to GitHub issues now use user-facing
|
|
37
|
+
guidance: they ask the issue reporter to fix repository/account state when
|
|
38
|
+
possible or ask a Hive Mind administrator to handle the affected repository,
|
|
39
|
+
while keeping administrator CLI details in the terminal log instead of the
|
|
40
|
+
public issue comment.
|
|
41
|
+
|
|
3
42
|
## 1.56.0
|
|
4
43
|
|
|
5
44
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -831,7 +831,7 @@ you see under `--list` is guaranteed to be the same set `--close` will act on
|
|
|
831
831
|
|
|
832
832
|
```bash
|
|
833
833
|
# Safe preview — show every finished, mergeable solve session.
|
|
834
|
-
hive-screens --list
|
|
834
|
+
hive-screens --list
|
|
835
835
|
|
|
836
836
|
# Close the oldest finished session (same as the legacy script's default).
|
|
837
837
|
hive-screens --close
|
|
@@ -841,10 +841,15 @@ hive-screens --enter --newest
|
|
|
841
841
|
|
|
842
842
|
# Close every finished session.
|
|
843
843
|
hive-screens --close --all
|
|
844
|
+
|
|
845
|
+
# Print diagnostic output while scanning (useful when matching fails).
|
|
846
|
+
hive-screens --list --verbose
|
|
844
847
|
```
|
|
845
848
|
|
|
846
|
-
|
|
847
|
-
|
|
849
|
+
`--list` defaults to `--all` so a bare `hive-screens --list` shows every match.
|
|
850
|
+
`--enter` and `--close` default to `--oldest` because they are destructive.
|
|
851
|
+
Supply `--oldest`, `--newest`, or `--all` to override. Run
|
|
852
|
+
`hive-screens --help` for the full option list.
|
|
848
853
|
|
|
849
854
|
### Reboot server.
|
|
850
855
|
|
package/package.json
CHANGED
package/src/github.lib.mjs
CHANGED
|
@@ -21,6 +21,18 @@ export { buildCostInfoString };
|
|
|
21
21
|
import { SOLUTION_DRAFT_LOG_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, postTrackedComment, postTrackedCommentFromFile } from './tool-comments.lib.mjs';
|
|
22
22
|
export const maskGitHubToken = maskToken; // Alias for backward compatibility
|
|
23
23
|
export const escapeCodeBlocksInLog = logContent => logContent.replace(/```/g, '\\`\\`\\`'); // Escape ``` in logs
|
|
24
|
+
const buildIssueFailureActionSection = targetType => {
|
|
25
|
+
if (targetType !== 'issue') return '';
|
|
26
|
+
|
|
27
|
+
return `
|
|
28
|
+
|
|
29
|
+
### What you can do
|
|
30
|
+
- Resolve the repository, account, permissions, or environment problem described above, then rerun the solver.
|
|
31
|
+
- If this requires elevated Hive Mind access, ask a Hive Mind administrator to handle the specific failure described above.
|
|
32
|
+
- Repository deletion can require a separate GitHub account or token with repository deletion permission; Hive Mind does not rely on that permission by default.
|
|
33
|
+
|
|
34
|
+
Administrator-only CLI details, if any, are printed in the solver terminal log rather than in this issue comment.`;
|
|
35
|
+
};
|
|
24
36
|
export const checkFileInBranch = async (owner, repo, fileName, branchName) => {
|
|
25
37
|
const { $ } = await use('command-stream');
|
|
26
38
|
|
|
@@ -486,7 +498,7 @@ ${footerNote}`;
|
|
|
486
498
|
The automated solution draft encountered an error:
|
|
487
499
|
\`\`\`
|
|
488
500
|
${errorMessage}
|
|
489
|
-
\`\`\`${modelInfoString}
|
|
501
|
+
\`\`\`${buildIssueFailureActionSection(targetType)}${modelInfoString}
|
|
490
502
|
|
|
491
503
|
<details>
|
|
492
504
|
<summary>Click to expand failure log (${Math.round(logStats.size / 1024)}KB)</summary>
|
|
@@ -675,7 +687,7 @@ ${uploadFooterNote}`;
|
|
|
675
687
|
The automated solution draft encountered an error:
|
|
676
688
|
\`\`\`
|
|
677
689
|
${errorMessage}
|
|
678
|
-
\`\`\`${modelInfoString}
|
|
690
|
+
\`\`\`${buildIssueFailureActionSection(targetType)}${modelInfoString}
|
|
679
691
|
|
|
680
692
|
### 📎 **Failure log uploaded as ${uploadTypeLabel}${chunkInfo}** (${Math.round(logStats.size / 1024)}KB)
|
|
681
693
|
- [View complete failure log](${logUrl})
|
package/src/hive-screens.lib.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { promisify } from 'node:util';
|
|
|
18
18
|
|
|
19
19
|
const execAsync = promisify(execCallback);
|
|
20
20
|
|
|
21
|
-
export const HIVE_SCREENS_HELP = `Usage: hive-screens (--list | --enter | --close) [--oldest|--newest|--all]
|
|
21
|
+
export const HIVE_SCREENS_HELP = `Usage: hive-screens (--list | --enter | --close) [--oldest|--newest|--all] [--verbose]
|
|
22
22
|
|
|
23
23
|
Scan detached GNU screen sessions for completed solve runs and either list,
|
|
24
24
|
enter, or close them. A session matches when its scrollback contains both
|
|
@@ -30,21 +30,24 @@ Actions (one required):
|
|
|
30
30
|
--enter Attach to the selected match (blocking)
|
|
31
31
|
--close Send \`exit\\n\` to the selected match so it terminates
|
|
32
32
|
|
|
33
|
-
Selection (optional
|
|
34
|
-
--oldest Act on the oldest match (default)
|
|
33
|
+
Selection (optional):
|
|
34
|
+
--oldest Act on the oldest match (default for --enter/--close)
|
|
35
35
|
--newest Act on the newest match
|
|
36
|
-
--all Act on every match
|
|
36
|
+
--all Act on every match (default for --list)
|
|
37
37
|
|
|
38
38
|
Options:
|
|
39
|
+
-v, --verbose Print diagnostic output to stderr while scanning
|
|
39
40
|
-h, --help Show this help and exit
|
|
40
41
|
|
|
41
42
|
Examples:
|
|
42
|
-
hive-screens --list #
|
|
43
|
-
hive-screens --list --
|
|
44
|
-
hive-screens --close
|
|
43
|
+
hive-screens --list # list every match (default is --all)
|
|
44
|
+
hive-screens --list --oldest # preview only the oldest match
|
|
45
|
+
hive-screens --close # close the oldest finished run
|
|
45
46
|
hive-screens --enter --newest # attach to the newest finished run
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
References:
|
|
49
|
+
https://github.com/link-assistant/hive-mind/issues/1649
|
|
50
|
+
https://github.com/link-assistant/hive-mind/issues/1654
|
|
48
51
|
`;
|
|
49
52
|
|
|
50
53
|
const ACTION_FLAGS = new Set(['--enter', '--close', '--list']);
|
|
@@ -61,6 +64,7 @@ export const parseHiveScreensArgs = argv => {
|
|
|
61
64
|
close: false,
|
|
62
65
|
list: false,
|
|
63
66
|
selection: null,
|
|
67
|
+
verbose: false,
|
|
64
68
|
help: false,
|
|
65
69
|
error: null,
|
|
66
70
|
};
|
|
@@ -70,6 +74,10 @@ export const parseHiveScreensArgs = argv => {
|
|
|
70
74
|
result.help = true;
|
|
71
75
|
continue;
|
|
72
76
|
}
|
|
77
|
+
if (arg === '--verbose' || arg === '-v') {
|
|
78
|
+
result.verbose = true;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
73
81
|
if (ACTION_FLAGS.has(arg)) {
|
|
74
82
|
if (arg === '--enter') result.enter = true;
|
|
75
83
|
else if (arg === '--close') result.close = true;
|
|
@@ -100,7 +108,10 @@ export const parseHiveScreensArgs = argv => {
|
|
|
100
108
|
return result;
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
|
|
111
|
+
// --list is a safe preview so default to showing every match. --enter and
|
|
112
|
+
// --close are destructive, so their default stays --oldest (mirrors the
|
|
113
|
+
// legacy hive-screens.sh behaviour).
|
|
114
|
+
if (!result.selection) result.selection = result.list ? 'all' : 'oldest';
|
|
104
115
|
return result;
|
|
105
116
|
};
|
|
106
117
|
|
|
@@ -212,7 +223,12 @@ export const selectMatches = (matches, selection) => {
|
|
|
212
223
|
return [matches[0]];
|
|
213
224
|
};
|
|
214
225
|
|
|
215
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Print the Session / Log / Issue triple for one match. Shared by --list,
|
|
228
|
+
* --enter (after leaving), and --close so every action surfaces the same
|
|
229
|
+
* human-readable context the legacy hive-screens.sh script showed.
|
|
230
|
+
*/
|
|
231
|
+
const printSessionInfo = ({ session, logPath, issueUrl }, { log }) => {
|
|
216
232
|
log(`Session: ${session}`);
|
|
217
233
|
log(logPath ? `Log: ${logPath}` : 'Log: (not found)');
|
|
218
234
|
log(issueUrl ? `Issue: ${issueUrl}` : 'Issue: (not found)');
|
|
@@ -220,13 +236,32 @@ const printSession = ({ session, logPath, issueUrl }, { log }) => {
|
|
|
220
236
|
|
|
221
237
|
const SEPARATOR = '-----------------------------------';
|
|
222
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Send `exit\n` to a detached screen session so its login shell terminates
|
|
241
|
+
* and the session is destroyed. Uses `spawn` with an argv array instead of
|
|
242
|
+
* `exec` with a shell string so we do not depend on the invoking shell
|
|
243
|
+
* understanding bash ANSI-C quoting (`$'exit\n'`), which `/bin/sh` on
|
|
244
|
+
* Debian/Ubuntu is dash and does not support. See issue #1654.
|
|
245
|
+
*/
|
|
246
|
+
export const closeScreenSession = async (session, { spawn } = {}) => {
|
|
247
|
+
const spawnFn = spawn || (await import('node:child_process')).spawn;
|
|
248
|
+
return new Promise((resolve, reject) => {
|
|
249
|
+
const child = spawnFn('screen', ['-S', session, '-X', 'stuff', 'exit\n'], { stdio: 'ignore' });
|
|
250
|
+
child.on('error', reject);
|
|
251
|
+
child.on('exit', code => {
|
|
252
|
+
if (code === 0) resolve();
|
|
253
|
+
else reject(new Error(`screen -X stuff exited with code ${code}`));
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
|
|
223
258
|
/**
|
|
224
259
|
* Top-level orchestrator used by the bin. `deps` is injected so tests can
|
|
225
260
|
* stub `exec`, `fs`, stdio, and process spawning without touching real
|
|
226
261
|
* screen sessions.
|
|
227
262
|
*/
|
|
228
263
|
export const runHiveScreens = async (argv, deps = {}) => {
|
|
229
|
-
const { exec = execAsync, fsModule = fs, tmpDir = os.tmpdir(), log = (...args) => console.log(...args), error = (...args) => console.error(...args), spawnScreen, captureOptions } = deps;
|
|
264
|
+
const { exec = execAsync, fsModule = fs, tmpDir = os.tmpdir(), log = (...args) => console.log(...args), error = (...args) => console.error(...args), spawnScreen, closeScreen, captureOptions } = deps;
|
|
230
265
|
|
|
231
266
|
const args = parseHiveScreensArgs(argv);
|
|
232
267
|
if (args.help) {
|
|
@@ -238,8 +273,12 @@ export const runHiveScreens = async (argv, deps = {}) => {
|
|
|
238
273
|
return 1;
|
|
239
274
|
}
|
|
240
275
|
|
|
276
|
+
const debug = args.verbose ? (...parts) => error('[hive-screens]', ...parts) : () => {};
|
|
277
|
+
|
|
241
278
|
const order = args.selection === 'newest' ? 'newest' : 'oldest';
|
|
279
|
+
debug(`scanning detached sessions in ${order}-first order`);
|
|
242
280
|
const matches = await findMatchingSessions({ exec, fsModule, tmpDir, order, captureOptions });
|
|
281
|
+
debug(`found ${matches.length} matching session(s)`);
|
|
243
282
|
|
|
244
283
|
if (!matches.length) {
|
|
245
284
|
log('No matching sessions');
|
|
@@ -247,10 +286,15 @@ export const runHiveScreens = async (argv, deps = {}) => {
|
|
|
247
286
|
}
|
|
248
287
|
|
|
249
288
|
const selected = selectMatches(matches, args.selection);
|
|
289
|
+
debug(`selection=${args.selection} -> acting on ${selected.length} session(s)`);
|
|
250
290
|
|
|
251
291
|
for (const match of selected) {
|
|
252
|
-
printSession(match, { log });
|
|
253
292
|
if (args.enter) {
|
|
293
|
+
// Print the session name up-front so the user knows which one they
|
|
294
|
+
// are about to attach to, then print the Log/Issue context AFTER
|
|
295
|
+
// control returns — otherwise `screen -r` swaps to the alternate
|
|
296
|
+
// buffer and wipes any context we printed beforehand.
|
|
297
|
+
log(`Session: ${match.session}`);
|
|
254
298
|
log(`Entering ${match.session}`);
|
|
255
299
|
if (spawnScreen) {
|
|
256
300
|
await spawnScreen(match.session);
|
|
@@ -258,13 +302,23 @@ export const runHiveScreens = async (argv, deps = {}) => {
|
|
|
258
302
|
await attachScreen(match.session);
|
|
259
303
|
}
|
|
260
304
|
log(`Left ${match.session}`);
|
|
261
|
-
|
|
262
|
-
|
|
305
|
+
log(match.logPath ? `Log: ${match.logPath}` : 'Log: (not found)');
|
|
306
|
+
log(match.issueUrl ? `Issue: ${match.issueUrl}` : 'Issue: (not found)');
|
|
307
|
+
} else if (args.close) {
|
|
308
|
+
printSessionInfo(match, { log });
|
|
263
309
|
log(`Closing ${match.session}`);
|
|
264
|
-
|
|
265
|
-
|
|
310
|
+
debug(`sending 'exit\\n' to ${match.session} via screen -X stuff`);
|
|
311
|
+
try {
|
|
312
|
+
if (closeScreen) {
|
|
313
|
+
await closeScreen(match.session);
|
|
314
|
+
} else {
|
|
315
|
+
await closeScreenSession(match.session);
|
|
316
|
+
}
|
|
317
|
+
} catch (err) {
|
|
266
318
|
error(`Failed to send exit to ${match.session}: ${err.message}`);
|
|
267
|
-
}
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
printSessionInfo(match, { log });
|
|
268
322
|
}
|
|
269
323
|
log(SEPARATOR);
|
|
270
324
|
}
|
|
@@ -8,6 +8,26 @@ const truncate = (value, maxLength = 2000) => {
|
|
|
8
8
|
|
|
9
9
|
const fence = value => truncate(value || 'Unknown error').replaceAll('```', '` ` `');
|
|
10
10
|
|
|
11
|
+
export function buildPrePullRequestFailureActionSection(reason = '') {
|
|
12
|
+
const normalizedReason = String(reason || '').toLowerCase();
|
|
13
|
+
const isForkOrRecoveryFailure = normalizedReason.includes('fork') || normalizedReason.includes('auto-recovery') || normalizedReason.includes('repository setup');
|
|
14
|
+
|
|
15
|
+
if (isForkOrRecoveryFailure) {
|
|
16
|
+
return `### What you can do
|
|
17
|
+
- If the affected fork or repository belongs to you, remove, rename, archive, initialize, or otherwise repair it in GitHub, then rerun the solver.
|
|
18
|
+
- If the action requires elevated Hive Mind access, ask a Hive Mind administrator to handle the affected fork or repository and rerun the solver.
|
|
19
|
+
- Repository deletion can require a separate GitHub account or token with repository deletion permission; Hive Mind does not rely on that permission by default.
|
|
20
|
+
|
|
21
|
+
Administrator-only CLI details, if any, are printed in the solver terminal log rather than in this issue comment.`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return `### What you can do
|
|
25
|
+
- Resolve the repository, account, permissions, or environment problem described above, then rerun the solver.
|
|
26
|
+
- If this requires elevated Hive Mind access, ask a Hive Mind administrator to handle the specific failure described above.
|
|
27
|
+
|
|
28
|
+
Administrator-only CLI details, if any, are printed in the solver terminal log rather than in this issue comment.`;
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
export function shouldNotifyIssueAboutPrePullRequestFailure({ code, globalState }) {
|
|
12
32
|
if (code === 0) return false;
|
|
13
33
|
if (!globalState?.issueNumber || !globalState?.owner || !globalState?.repo) return false;
|
|
@@ -16,18 +36,11 @@ export function shouldNotifyIssueAboutPrePullRequestFailure({ code, globalState
|
|
|
16
36
|
return getTrackedToolCommentIds().size === 0;
|
|
17
37
|
}
|
|
18
38
|
|
|
19
|
-
export function buildPrePullRequestFailureComment({ reason, owner, repo, issueNumber, argv = {},
|
|
39
|
+
export function buildPrePullRequestFailureComment({ reason, owner, repo, issueNumber, argv = {}, logAttachmentAttempted = false }) {
|
|
20
40
|
const tool = argv.tool || 'claude';
|
|
21
41
|
const modelLine = argv.model ? `\n- **Requested model**: \`${argv.model}\`` : '';
|
|
22
|
-
const commandBlock = rawCommand
|
|
23
|
-
? `
|
|
24
|
-
|
|
25
|
-
### Command
|
|
26
|
-
\`\`\`bash
|
|
27
|
-
${fence(rawCommand)}
|
|
28
|
-
\`\`\``
|
|
29
|
-
: '';
|
|
30
42
|
const logLine = logAttachmentAttempted ? 'Log attachment was attempted but failed. Check the solver terminal log for the complete failure output.' : 'Logs were not attached because `--attach-logs` was not enabled.';
|
|
43
|
+
const actionSection = buildPrePullRequestFailureActionSection(reason);
|
|
31
44
|
|
|
32
45
|
return `## 🚨 ${SOLUTION_DRAFT_FAILED_MARKER}
|
|
33
46
|
|
|
@@ -41,11 +54,12 @@ The automated solver stopped before creating a pull request, so no PR was opened
|
|
|
41
54
|
**Reason**
|
|
42
55
|
\`\`\`text
|
|
43
56
|
${fence(reason)}
|
|
44
|
-
|
|
57
|
+
\`\`\`
|
|
45
58
|
|
|
46
|
-
${
|
|
59
|
+
${actionSection}
|
|
47
60
|
|
|
48
|
-
|
|
61
|
+
${logLine}
|
|
62
|
+
`;
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
export async function notifyIssueAboutPrePullRequestFailure(options) {
|
|
@@ -553,8 +553,9 @@ export const setupRepository = async (argv, owner, repo, forkOwner = null, issue
|
|
|
553
553
|
if (deleteResult.code !== 0) {
|
|
554
554
|
const delOut = (deleteResult.stderr?.toString() || '') + (deleteResult.stdout?.toString() || '');
|
|
555
555
|
await log(`${formatAligned('❌', 'Delete failed:', delOut.split('\n')[0])}`, { level: 'error' });
|
|
556
|
-
await log(` 💡 Manual fix: gh repo delete ${existingForkName} --yes, then re-run`);
|
|
557
|
-
await
|
|
556
|
+
await log(/delete_repo/i.test(delOut) || (/HTTP 403/.test(delOut) && /admin rights/i.test(delOut)) ? ` 💡 Token missing "delete_repo" scope. Hive Mind does not request it by default. Admin fix: gh auth refresh -h github.com -s delete_repo\n 🔧 Or no-scope alternative: gh repo rename ${existingForkName} ${existingForkName.split('/')[1]}-old (or gh repo archive ${existingForkName} --yes), then re-run with --prefix-fork-name-with-owner-name` : ` 💡 Manual fix: gh repo delete ${existingForkName} --yes, then re-run`); // Issue #1651
|
|
557
|
+
if (argv.verbose) await log(`${formatAligned('🔧', 'Full delete output:', delOut.trim())}`);
|
|
558
|
+
await safeExit(1, `Auto-recovery failed - ask the fork owner or Hive Mind administrator to delete, rename, or archive ${existingForkName}`);
|
|
558
559
|
}
|
|
559
560
|
await log(`${formatAligned('✅', 'Deleted:', existingForkName)}`);
|
|
560
561
|
existingForkName = null; // Fall through to fork creation below
|