hungry-ghost-hive 0.40.3 → 0.41.1

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.
Files changed (50) hide show
  1. package/dist/cli/commands/init.d.ts.map +1 -1
  2. package/dist/cli/commands/init.js +8 -0
  3. package/dist/cli/commands/init.js.map +1 -1
  4. package/dist/cli/commands/manager/auto-assignment.js +4 -4
  5. package/dist/cli/commands/manager/auto-assignment.js.map +1 -1
  6. package/dist/cli/commands/manager/auto-reject-comment-only-reviews.test.d.ts +2 -0
  7. package/dist/cli/commands/manager/auto-reject-comment-only-reviews.test.d.ts.map +1 -0
  8. package/dist/cli/commands/manager/auto-reject-comment-only-reviews.test.js +462 -0
  9. package/dist/cli/commands/manager/auto-reject-comment-only-reviews.test.js.map +1 -0
  10. package/dist/cli/commands/manager/index.d.ts +12 -0
  11. package/dist/cli/commands/manager/index.d.ts.map +1 -1
  12. package/dist/cli/commands/manager/index.js +171 -3
  13. package/dist/cli/commands/manager/index.js.map +1 -1
  14. package/dist/cli/wizard/init-wizard.d.ts +2 -1
  15. package/dist/cli/wizard/init-wizard.d.ts.map +1 -1
  16. package/dist/cli/wizard/init-wizard.js +56 -3
  17. package/dist/cli/wizard/init-wizard.js.map +1 -1
  18. package/dist/cli/wizard/init-wizard.test.js +56 -9
  19. package/dist/cli/wizard/init-wizard.test.js.map +1 -1
  20. package/dist/config/schema.d.ts +389 -0
  21. package/dist/config/schema.d.ts.map +1 -1
  22. package/dist/config/schema.js +18 -0
  23. package/dist/config/schema.js.map +1 -1
  24. package/dist/context-files/index.test.js +6 -0
  25. package/dist/context-files/index.test.js.map +1 -1
  26. package/dist/git/github.d.ts +9 -0
  27. package/dist/git/github.d.ts.map +1 -1
  28. package/dist/git/github.js +15 -0
  29. package/dist/git/github.js.map +1 -1
  30. package/dist/git/github.test.js +77 -0
  31. package/dist/git/github.test.js.map +1 -1
  32. package/dist/orchestrator/scheduler.d.ts.map +1 -1
  33. package/dist/orchestrator/scheduler.js +1 -0
  34. package/dist/orchestrator/scheduler.js.map +1 -1
  35. package/dist/tmux/manager.d.ts.map +1 -1
  36. package/dist/tmux/manager.js +15 -8
  37. package/dist/tmux/manager.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/cli/commands/init.ts +8 -0
  40. package/src/cli/commands/manager/auto-assignment.ts +4 -4
  41. package/src/cli/commands/manager/auto-reject-comment-only-reviews.test.ts +589 -0
  42. package/src/cli/commands/manager/index.ts +228 -3
  43. package/src/cli/wizard/init-wizard.test.ts +62 -9
  44. package/src/cli/wizard/init-wizard.ts +66 -4
  45. package/src/config/schema.ts +20 -0
  46. package/src/context-files/index.test.ts +6 -0
  47. package/src/git/github.test.ts +93 -0
  48. package/src/git/github.ts +28 -0
  49. package/src/orchestrator/scheduler.ts +1 -0
  50. package/src/tmux/manager.ts +19 -8
@@ -508,4 +508,97 @@ describe('github module', () => {
508
508
  expect(result[0].author).toBe('reviewer1');
509
509
  });
510
510
  });
511
+
512
+ describe('getPullRequestComments', () => {
513
+ const workDir = '/test/repo';
514
+ const prNumber = 123;
515
+
516
+ it('should get PR comments with author, body, and createdAt', async () => {
517
+ mockedExeca.mockResolvedValueOnce({
518
+ stdout: JSON.stringify({
519
+ comments: [
520
+ {
521
+ author: { login: 'user1' },
522
+ body: 'This needs a fix in the error handling path.',
523
+ createdAt: '2026-03-01T10:00:00Z',
524
+ },
525
+ {
526
+ author: { login: 'user2' },
527
+ body: 'Coverage is below threshold.',
528
+ createdAt: '2026-03-01T11:00:00Z',
529
+ },
530
+ ],
531
+ }),
532
+ stderr: '',
533
+ } as any);
534
+
535
+ const result = await github.getPullRequestComments(workDir, prNumber);
536
+
537
+ expect(result).toHaveLength(2);
538
+ expect(result[0]).toEqual({
539
+ author: 'user1',
540
+ body: 'This needs a fix in the error handling path.',
541
+ createdAt: '2026-03-01T10:00:00Z',
542
+ });
543
+ expect(result[1].author).toBe('user2');
544
+ expect(mockedExeca).toHaveBeenCalledWith('gh', ['pr', 'view', '123', '--json', 'comments'], {
545
+ cwd: workDir,
546
+ });
547
+ });
548
+
549
+ it('should return empty array when no comments exist', async () => {
550
+ mockedExeca.mockResolvedValueOnce({
551
+ stdout: JSON.stringify({ comments: [] }),
552
+ stderr: '',
553
+ } as any);
554
+
555
+ const result = await github.getPullRequestComments(workDir, prNumber);
556
+
557
+ expect(result).toHaveLength(0);
558
+ });
559
+
560
+ it('should handle missing comments field gracefully', async () => {
561
+ mockedExeca.mockResolvedValueOnce({
562
+ stdout: JSON.stringify({}),
563
+ stderr: '',
564
+ } as any);
565
+
566
+ const result = await github.getPullRequestComments(workDir, prNumber);
567
+
568
+ expect(result).toHaveLength(0);
569
+ });
570
+
571
+ it('should handle comments with missing author fields', async () => {
572
+ mockedExeca.mockResolvedValueOnce({
573
+ stdout: JSON.stringify({
574
+ comments: [
575
+ { body: 'Anonymous comment', createdAt: '2026-03-01T10:00:00Z' },
576
+ { author: {}, body: 'No login', createdAt: '2026-03-01T11:00:00Z' },
577
+ ],
578
+ }),
579
+ stderr: '',
580
+ } as any);
581
+
582
+ const result = await github.getPullRequestComments(workDir, prNumber);
583
+
584
+ expect(result).toHaveLength(2);
585
+ expect(result[0].author).toBe('unknown');
586
+ expect(result[1].author).toBe('unknown');
587
+ });
588
+
589
+ it('should handle comments with missing body and createdAt', async () => {
590
+ mockedExeca.mockResolvedValueOnce({
591
+ stdout: JSON.stringify({
592
+ comments: [{ author: { login: 'user1' } }],
593
+ }),
594
+ stderr: '',
595
+ } as any);
596
+
597
+ const result = await github.getPullRequestComments(workDir, prNumber);
598
+
599
+ expect(result).toHaveLength(1);
600
+ expect(result[0].body).toBe('');
601
+ expect(result[0].createdAt).toBe('');
602
+ });
603
+ });
511
604
  });
package/src/git/github.ts CHANGED
@@ -301,3 +301,31 @@ export async function getPullRequestReviews(
301
301
  const data = JSON.parse(stdout);
302
302
  return data.reviews || [];
303
303
  }
304
+
305
+ /**
306
+ * Get PR comments (issue comments, not inline review comments).
307
+ * Returns comments with author login, body, and creation date.
308
+ */
309
+ export async function getPullRequestComments(
310
+ workDir: string,
311
+ prNumber: number
312
+ ): Promise<
313
+ Array<{
314
+ author: string;
315
+ body: string;
316
+ createdAt: string;
317
+ }>
318
+ > {
319
+ const { stdout } = await execa('gh', ['pr', 'view', prNumber.toString(), '--json', 'comments'], {
320
+ cwd: workDir,
321
+ });
322
+
323
+ const data = JSON.parse(stdout);
324
+ return (data.comments || []).map(
325
+ (c: { author?: { login?: string }; body?: string; createdAt?: string }) => ({
326
+ author: c.author?.login || 'unknown',
327
+ body: c.body || '',
328
+ createdAt: c.createdAt || '',
329
+ })
330
+ );
331
+ }
@@ -938,6 +938,7 @@ export class Scheduler {
938
938
  temperature: GODMODE_TEMPERATURE,
939
939
  cli_tool: 'claude',
940
940
  safety_mode: modelConfig.safety_mode,
941
+ personas: modelConfig.personas,
941
942
  };
942
943
  }
943
944
 
@@ -266,14 +266,25 @@ export async function sendToTmuxSession(
266
266
  await new Promise(resolve => setTimeout(resolve, CLEAR_INPUT_DELAY_MS));
267
267
  }
268
268
 
269
- // For single-line text, use send-keys with literal flag then Enter separately.
270
- // '--' signals end of options, preventing text starting with '-' from being parsed as flags.
271
- //
272
- // NOTE: Multi-line initial prompts should be passed via spawnTmuxSession's
273
- // initialPrompt option, which writes to a temp file and uses $(cat ...) to
274
- // deliver the prompt as a CLI positional argument. This function is only for
275
- // single-line runtime messages (nudges, commands, etc).
276
- await execa('tmux', ['send-keys', '-t', sessionName, '-l', '--', text]);
269
+ const isMultiLine = text.includes('\n');
270
+
271
+ if (isMultiLine) {
272
+ // Multi-line text: write to temp file and paste via $(cat ...) to avoid
273
+ // the [Pasted text #N] buffering issue in Claude CLI. The shell expands
274
+ // $(cat file) into a single argument, bypassing tmux's paste-buffer handling.
275
+ const tempFile = join(
276
+ tmpdir(),
277
+ `hive-nudge-${Date.now()}-${sessionName.replace(/[^a-zA-Z0-9-]/g, '_')}.txt`
278
+ );
279
+ writeFileSync(tempFile, text, 'utf-8');
280
+ const catCmd = `$(cat ${tempFile})`;
281
+ await execa('tmux', ['send-keys', '-t', sessionName, '-l', '--', catCmd]);
282
+ } else {
283
+ // Single-line: use send-keys with literal flag directly.
284
+ // '--' signals end of options, preventing text starting with '-' from being parsed as flags.
285
+ await execa('tmux', ['send-keys', '-t', sessionName, '-l', '--', text]);
286
+ }
287
+
277
288
  // Send Enter as a key event (C-m = carriage return = Enter) to ensure prompt receives it
278
289
  await execa('tmux', ['send-keys', '-t', sessionName, 'C-m']);
279
290
  }