@link-assistant/hive-mind 1.56.1 → 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 CHANGED
@@ -1,5 +1,26 @@
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
+
3
24
  ## 1.56.1
4
25
 
5
26
  ### Patch 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 --all
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
- Selection defaults to `--oldest`. Supply `--newest` or `--all` to change it.
847
- Run `hive-screens --help` for the full option list.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.56.1",
3
+ "version": "1.56.2",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -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, default: --oldest):
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 in oldest-first order
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 # safe preview of matches
43
- hive-screens --list --all # preview every match
44
- hive-screens --close --oldest # close the oldest finished run
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
- Reference: https://github.com/link-assistant/hive-mind/issues/1649
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
- if (!result.selection) result.selection = 'oldest';
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
- const printSession = ({ session, logPath, issueUrl }, { log }) => {
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
- if (args.close) {
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
- const shellSession = match.session.replace(/'/g, "'\\''");
265
- await exec(`screen -S '${shellSession}' -X stuff $'exit\\n'`).catch(err => {
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
  }