@link-assistant/hive-mind 1.71.1 → 1.72.0

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,11 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.72.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fffdfbf: Add experimental `--resume-on-auto-restart` support for resuming Claude auto-restart sessions with a minimal uncommitted-change prompt.
8
+
3
9
  ## 1.71.1
4
10
 
5
11
  ### Patch Changes
package/CLAUDE.md CHANGED
@@ -1,5 +1,5 @@
1
- Issue to solve: https://github.com/link-assistant/hive-mind/issues/962
2
- Your prepared branch: issue-962-7502f60e53a0
3
- Your prepared working directory: /tmp/gh-issue-solver-1766423610256
1
+ Issue to solve: undefined
2
+ Your prepared branch: issue-644-cf3b8243
3
+ Your prepared working directory: /tmp/gh-issue-solver-1762010397765
4
4
 
5
5
  Proceed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.71.1",
3
+ "version": "1.72.0",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -17,6 +17,11 @@ import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
17
17
  export const buildUserPrompt = params => {
18
18
  const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, owner, repo, argv, contributingGuidelines, claudeVersion } = params;
19
19
 
20
+ if (argv?.minimalRestartContext && argv.resume) {
21
+ const lines = feedbackLines && feedbackLines.length > 0 ? feedbackLines : ['Continue the auto-restart from the previous resumed session.'];
22
+ return `${lines.join('\n')}\n`;
23
+ }
24
+
20
25
  const promptLines = [];
21
26
 
22
27
  // Issue or PR reference
@@ -87,6 +92,10 @@ export const buildUserPrompt = params => {
87
92
  export const buildSystemPrompt = params => {
88
93
  const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
89
94
 
95
+ if (argv?.minimalRestartContext && argv.resume) {
96
+ return '';
97
+ }
98
+
90
99
  // When in fork mode, screenshots are pushed to the fork, not the original repo
91
100
  const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
92
101
 
@@ -187,6 +187,11 @@ export const SOLVE_OPTION_DEFINITIONS = {
187
187
  description: 'Maximum number of auto-restart iterations before stopping (default: 5, 0 = unlimited)',
188
188
  default: 5,
189
189
  },
190
+ 'resume-on-auto-restart': {
191
+ type: 'boolean',
192
+ description: '[EXPERIMENTAL] Resume the previous Claude session on uncommitted-change auto-restart and send only a minimal restart prompt. Disabled by default.',
193
+ default: false,
194
+ },
190
195
  'auto-resume-max-iterations': {
191
196
  type: 'number',
192
197
  description: 'Maximum number of automatic resume/restart continuations after usage-limit resets (default: 5, 0 = unlimited)',
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Generate minimal prompt for auto-restart with session resume
5
+ * This module provides functions to create lightweight prompts for auto-restart
6
+ * that assume the AI has full context from the previous session
7
+ *
8
+ * Part of the cost optimization feature for issue #661
9
+ * @see case-studies/issue-661-session-resume-cost-optimization/
10
+ */
11
+
12
+ // Note: This module does not import $ directly
13
+ // Functions receive $ as a parameter from the calling module
14
+ // This ensures consistent command executor usage across the codebase
15
+
16
+ /**
17
+ * Generate minimal prompt for auto-restart with session resume
18
+ * This prompt assumes the AI has full context from the previous session
19
+ * Target: ~500 tokens (compared to 50k-200k in full context)
20
+ *
21
+ * @param {string} tempDir - Working directory
22
+ * @param {object} $ - Command executor
23
+ * @returns {Promise<string>} Minimal restart prompt
24
+ */
25
+ export const generateMinimalRestartPrompt = async (tempDir, $) => {
26
+ // Get uncommitted changes
27
+ const gitStatus = await $({ cwd: tempDir })`git status --porcelain`;
28
+ const uncommittedFiles = gitStatus.stdout.toString().trim();
29
+
30
+ // Get brief diff summaries (not full diffs to keep the prompt minimal)
31
+ const gitDiffStat = await $({ cwd: tempDir })`git diff --stat`;
32
+ const unstagedDiffSummary = gitDiffStat.stdout.toString().trim();
33
+ const gitCachedDiffStat = await $({ cwd: tempDir })`git diff --cached --stat`;
34
+ const stagedDiffSummary = gitCachedDiffStat.stdout.toString().trim();
35
+ const summarySections = [];
36
+ if (unstagedDiffSummary) summarySections.push(`Unstaged changes:\n${unstagedDiffSummary}`);
37
+ if (stagedDiffSummary) summarySections.push(`Staged changes:\n${stagedDiffSummary}`);
38
+ const diffSummary = summarySections.join('\n\n') || 'No tracked-file diff summary available.';
39
+
40
+ // Count changes
41
+ const fileCount = uncommittedFiles.split('\n').filter(line => line.trim()).length;
42
+
43
+ return `🔄 Auto-restart: resume the previous session and handle its uncommitted changes.
44
+
45
+ Uncommitted files (${fileCount}):
46
+ ${uncommittedFiles}
47
+
48
+ Changes summary:
49
+ ${diffSummary}
50
+
51
+ Please review these changes and commit them with an appropriate commit message.
52
+ Follow the repository's commit message conventions from previous commits.`;
53
+ };
54
+
55
+ /**
56
+ * Generate full context prompt (fallback when resume fails or not enabled)
57
+ * This is used when session resume is not available or failed
58
+ *
59
+ * @param {string} issueUrl - Issue URL
60
+ * @param {string} issueBody - Issue description
61
+ * @param {number} prNumber - PR number
62
+ * @param {Array<string>} feedbackLines - Feedback from reviewers
63
+ * @param {string} tempDir - Working directory
64
+ * @param {object} $ - Command executor
65
+ * @returns {Promise<string>} Full restart prompt
66
+ */
67
+ export const generateFullRestartPrompt = async (issueUrl, issueBody, prNumber, feedbackLines, tempDir, $) => {
68
+ // Get uncommitted changes with full diff
69
+ const gitStatus = await $({ cwd: tempDir })`git status --porcelain`;
70
+ const uncommittedFiles = gitStatus.stdout.toString().trim();
71
+
72
+ const gitDiff = await $({ cwd: tempDir })`git diff`;
73
+ const fullDiff = gitDiff.stdout.toString();
74
+
75
+ let prompt = `
76
+ Continuing work on issue: ${issueUrl}
77
+
78
+ Previous session completed but left uncommitted changes.
79
+ `.trim();
80
+
81
+ if (feedbackLines && feedbackLines.length > 0) {
82
+ prompt += `\n\nFeedback from reviewers:\n${feedbackLines.join('\n')}`;
83
+ }
84
+
85
+ prompt += `\n\nUncommitted changes:\n${uncommittedFiles}\n\nFull diff:\n${fullDiff}`;
86
+
87
+ prompt += '\n\nPlease review these changes and commit them appropriately.';
88
+
89
+ return prompt;
90
+ };
package/src/solve.mjs CHANGED
@@ -826,6 +826,13 @@ try {
826
826
  limitReached = toolResult.limitReached;
827
827
  cleanupContext.limitReached = limitReached;
828
828
 
829
+ if (sessionId && (argv.resumeOnAutoRestart || argv['resume-on-auto-restart'])) {
830
+ global.previousSessionId = sessionId;
831
+ if (argv.verbose) {
832
+ await log(`Session ID stored for auto-restart resume: ${sessionId}`, { verbose: true });
833
+ }
834
+ }
835
+
829
836
  // Capture limit reset time and timezone globally for downstream handlers (auto-continue, cleanup decisions)
830
837
  if (toolResult && toolResult.limitResetTime) {
831
838
  global.limitResetTime = toolResult.limitResetTime;
@@ -290,6 +290,38 @@ export const watchForFeedback = async params => {
290
290
  // to comments posted during *this* iteration only, not across the whole watch loop.
291
291
  const iterationStartTime = new Date();
292
292
 
293
+ let restartFeedbackLines = feedbackLines;
294
+ let restartArgv = argv;
295
+ const shouldUseSessionResume = Boolean(isTemporaryWatch && (firstIterationInTemporaryMode || hasUncommittedInTempMode) && (argv.resumeOnAutoRestart || argv['resume-on-auto-restart']) && (argv.tool === 'claude' || !argv.tool) && global.previousSessionId);
296
+
297
+ if (shouldUseSessionResume) {
298
+ await log(formatAligned('', 'Experimental session resume: using minimal auto-restart prompt', '', 2));
299
+ await log(formatAligned('', `Resuming session: ${global.previousSessionId}`, '', 2));
300
+
301
+ if (argv.verbose) {
302
+ try {
303
+ const { calculateSessionTokens } = await import('./claude.lib.mjs');
304
+ const tokenUsage = await calculateSessionTokens(global.previousSessionId, tempDir);
305
+ if (tokenUsage?.totalTokens) {
306
+ await log(formatAligned('', `Previous session tokens: ${tokenUsage.totalTokens.toLocaleString()}`, '', 2));
307
+ }
308
+ } catch {
309
+ await log(formatAligned('', 'Could not read previous session token usage', '', 2));
310
+ }
311
+ }
312
+
313
+ const { generateMinimalRestartPrompt } = await import('./solve.minimal-restart-prompt.lib.mjs');
314
+ const minimalPrompt = await generateMinimalRestartPrompt(tempDir, $);
315
+ restartFeedbackLines = [minimalPrompt];
316
+ restartArgv = {
317
+ ...argv,
318
+ resume: global.previousSessionId,
319
+ minimalRestartContext: true,
320
+ };
321
+
322
+ await log(formatAligned('', `Minimal restart prompt size: ${minimalPrompt.length} characters`, '', 2));
323
+ }
324
+
293
325
  // Execute tool using shared utility
294
326
  const toolResult = await executeToolIteration({
295
327
  issueUrl,
@@ -300,10 +332,14 @@ export const watchForFeedback = async params => {
300
332
  branchName: prBranch || branchName,
301
333
  tempDir,
302
334
  mergeStateStatus,
303
- feedbackLines,
304
- argv,
335
+ feedbackLines: restartFeedbackLines,
336
+ argv: restartArgv,
305
337
  });
306
338
 
339
+ if (toolResult.sessionId && (argv.resumeOnAutoRestart || argv['resume-on-auto-restart'])) {
340
+ global.previousSessionId = toolResult.sessionId;
341
+ }
342
+
307
343
  if (!toolResult.success) {
308
344
  // Check if this is an API error using shared utility
309
345
  if (isApiError(toolResult)) {