@link-assistant/hive-mind 1.0.5 → 1.2.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 +78 -0
- package/package.json +1 -1
- package/src/agent.lib.mjs +3 -1
- package/src/agent.prompts.lib.mjs +43 -5
- package/src/claude.lib.mjs +3 -1
- package/src/claude.prompts.lib.mjs +44 -5
- package/src/codex.lib.mjs +3 -1
- package/src/codex.prompts.lib.mjs +43 -5
- package/src/hive.config.lib.mjs +5 -0
- package/src/hive.mjs +13 -0
- package/src/hive.recheck.lib.mjs +86 -0
- package/src/opencode.lib.mjs +3 -1
- package/src/opencode.prompts.lib.mjs +44 -4
- package/src/review.mjs +17 -9
- package/src/solve.config.lib.mjs +10 -0
- package/src/solve.mjs +14 -3
- package/src/solve.repository.lib.mjs +47 -2
- package/src/task.mjs +6 -1
- package/src/usage-limit.lib.mjs +105 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,83 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add experimental --execute-tool-with-bun option to improve speed and memory usage
|
|
8
|
+
|
|
9
|
+
This feature adds the `--execute-tool-with-bun` option that allows users to execute the AI tool using `bunx claude` instead of `claude`, which may provide performance benefits in terms of speed and memory usage.
|
|
10
|
+
|
|
11
|
+
**Supported commands:**
|
|
12
|
+
- `solve` - Uses `bunx claude` when option is enabled
|
|
13
|
+
- `task` - Uses `bunx claude` when option is enabled
|
|
14
|
+
- `review` - Uses `bunx claude` when option is enabled
|
|
15
|
+
- `hive` - Passes the option through to the `solve` subprocess
|
|
16
|
+
|
|
17
|
+
**How It Works:**
|
|
18
|
+
When `--execute-tool-with-bun` is enabled, the `claudePath` variable is set to `'bunx claude'` instead of `'claude'` (or `CLAUDE_PATH` environment variable).
|
|
19
|
+
|
|
20
|
+
**Usage Examples:**
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Use with solve command
|
|
24
|
+
solve https://github.com/owner/repo/issues/123 --execute-tool-with-bun
|
|
25
|
+
|
|
26
|
+
# Use with task command
|
|
27
|
+
task "implement feature X" --execute-tool-with-bun
|
|
28
|
+
|
|
29
|
+
# Use with review command
|
|
30
|
+
review https://github.com/owner/repo/pull/456 --execute-tool-with-bun
|
|
31
|
+
|
|
32
|
+
# Use with hive command (passes through to solve)
|
|
33
|
+
hive https://github.com/owner/repo --execute-tool-with-bun
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The option defaults to `false` to maintain backward compatibility.
|
|
37
|
+
|
|
38
|
+
Fixes #812
|
|
39
|
+
|
|
40
|
+
feat(hive): recheck issue conditions before processing queue items
|
|
41
|
+
|
|
42
|
+
Added `recheckIssueConditions()` function to validate issue state right before processing,
|
|
43
|
+
preventing wasted resources on issues that should be skipped due to changed conditions since queuing.
|
|
44
|
+
|
|
45
|
+
**Checks performed:**
|
|
46
|
+
- **Issue state**: Verifies the issue is still open
|
|
47
|
+
- **Open PRs**: Checks if issue has PRs (when `--skip-issues-with-prs` is enabled)
|
|
48
|
+
- **Repository status**: Confirms repository is not archived
|
|
49
|
+
|
|
50
|
+
**Benefits:**
|
|
51
|
+
- Prevents processing closed issues
|
|
52
|
+
- Avoids duplicate work when PRs already exist
|
|
53
|
+
- Stops work on newly archived repositories
|
|
54
|
+
- Saves AI model tokens and compute resources
|
|
55
|
+
|
|
56
|
+
**Performance impact:**
|
|
57
|
+
Minimal overhead per issue (~300-500ms for API calls), negligible compared to 5-15 minute solve time.
|
|
58
|
+
|
|
59
|
+
Fixes #810
|
|
60
|
+
|
|
61
|
+
## 1.1.0
|
|
62
|
+
|
|
63
|
+
### Minor Changes
|
|
64
|
+
|
|
65
|
+
- 4c46685: Add --enable-workspaces option for separate workspace directories
|
|
66
|
+
|
|
67
|
+
This feature adds support for creating separate workspace directories for all AI tools (claude, opencode, codex, agent). When enabled with `--enable-workspaces`, the tool creates a structured workspace:
|
|
68
|
+
- `/tmp/hive-mind-solve-gh-{owner}/{repo}-issue-{issueNumber}-workspace-{timestamp}/repository` - for the cloned repo
|
|
69
|
+
- `/tmp/hive-mind-solve-gh-{owner}/{repo}-issue-{issueNumber}-workspace-{timestamp}/tmp` - for temp files, logs, downloads
|
|
70
|
+
|
|
71
|
+
The workspace tmp directory is passed to all tool prompts, with explicit examples for saving CI logs, diffs, and command outputs.
|
|
72
|
+
|
|
73
|
+
- Add relative time display for usage limit reset messages in GitHub comments
|
|
74
|
+
|
|
75
|
+
When the AI tool hits its usage limit, GitHub comments now show the reset time in a more user-friendly format:
|
|
76
|
+
- Before: `11:00 PM`
|
|
77
|
+
- After: `in 1h 23m (11:00 PM UTC)`
|
|
78
|
+
|
|
79
|
+
This helps users in different timezones understand when the limit will reset more quickly.
|
|
80
|
+
|
|
3
81
|
## 1.0.5
|
|
4
82
|
|
|
5
83
|
### Patch Changes
|
package/package.json
CHANGED
package/src/agent.lib.mjs
CHANGED
|
@@ -245,7 +245,7 @@ export const handleAgentRuntimeSwitch = async () => {
|
|
|
245
245
|
|
|
246
246
|
// Main function to execute Agent with prompts and settings
|
|
247
247
|
export const executeAgent = async params => {
|
|
248
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, agentPath = 'agent', $ } = params;
|
|
248
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, agentPath = 'agent', $ } = params;
|
|
249
249
|
|
|
250
250
|
// Import prompt building functions from agent.prompts.lib.mjs
|
|
251
251
|
const { buildUserPrompt, buildSystemPrompt } = await import('./agent.prompts.lib.mjs');
|
|
@@ -258,6 +258,7 @@ export const executeAgent = async params => {
|
|
|
258
258
|
prUrl,
|
|
259
259
|
branchName,
|
|
260
260
|
tempDir,
|
|
261
|
+
workspaceTmpDir,
|
|
261
262
|
isContinueMode,
|
|
262
263
|
mergeStateStatus,
|
|
263
264
|
forkedRepo,
|
|
@@ -276,6 +277,7 @@ export const executeAgent = async params => {
|
|
|
276
277
|
prNumber,
|
|
277
278
|
branchName,
|
|
278
279
|
tempDir,
|
|
280
|
+
workspaceTmpDir,
|
|
279
281
|
isContinueMode,
|
|
280
282
|
forkedRepo,
|
|
281
283
|
argv,
|
|
@@ -11,7 +11,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mj
|
|
|
11
11
|
* @returns {string} The formatted user prompt
|
|
12
12
|
*/
|
|
13
13
|
export const buildUserPrompt = params => {
|
|
14
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
14
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
15
15
|
|
|
16
16
|
const promptLines = [];
|
|
17
17
|
|
|
@@ -26,6 +26,11 @@ export const buildUserPrompt = params => {
|
|
|
26
26
|
promptLines.push(`Your prepared branch: ${branchName}`);
|
|
27
27
|
promptLines.push(`Your prepared working directory: ${tempDir}`);
|
|
28
28
|
|
|
29
|
+
// Workspace tmp directory for logs and temp files (when --enable-workspaces is used)
|
|
30
|
+
if (workspaceTmpDir) {
|
|
31
|
+
promptLines.push(`Your prepared tmp directory for logs and downloads: ${workspaceTmpDir}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
// PR info if available
|
|
30
35
|
if (prUrl) {
|
|
31
36
|
promptLines.push(`Your prepared Pull Request: ${prUrl}`);
|
|
@@ -76,7 +81,7 @@ export const buildUserPrompt = params => {
|
|
|
76
81
|
* @returns {string} The formatted system prompt
|
|
77
82
|
*/
|
|
78
83
|
export const buildSystemPrompt = params => {
|
|
79
|
-
const { owner, repo, issueNumber, prNumber, branchName, argv } = params;
|
|
84
|
+
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv } = params;
|
|
80
85
|
|
|
81
86
|
// Build thinking instruction based on --think level
|
|
82
87
|
let thinkLine = '';
|
|
@@ -90,9 +95,42 @@ export const buildSystemPrompt = params => {
|
|
|
90
95
|
thinkLine = `\n${thinkMessages[argv.think]}\n`;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
|
|
98
|
+
// Build workspace-specific instructions and examples
|
|
99
|
+
let workspaceInstructions = '';
|
|
100
|
+
if (workspaceTmpDir) {
|
|
101
|
+
workspaceInstructions = `
|
|
102
|
+
Workspace tmp directory.
|
|
103
|
+
- Use ${workspaceTmpDir} for all temporary files, logs, and downloads.
|
|
104
|
+
- When saving command output to files, save to ${workspaceTmpDir}/command-output.log.
|
|
105
|
+
- When downloading CI logs, save to ${workspaceTmpDir}/ci-logs/.
|
|
106
|
+
- When saving diffs for review, save to ${workspaceTmpDir}/diffs/.
|
|
107
|
+
- When creating debug files, save to ${workspaceTmpDir}/debug/.
|
|
108
|
+
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
94
111
|
|
|
95
|
-
|
|
112
|
+
// Build CI command examples with workspace tmp paths
|
|
113
|
+
let ciExamples = '';
|
|
114
|
+
if (workspaceTmpDir) {
|
|
115
|
+
ciExamples = `
|
|
116
|
+
CI investigation with workspace tmp directory.
|
|
117
|
+
- When downloading CI run logs:
|
|
118
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log > ${workspaceTmpDir}/ci-logs/run-RUN_ID.log
|
|
119
|
+
- When downloading failed job logs:
|
|
120
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log-failed > ${workspaceTmpDir}/ci-logs/run-RUN_ID-failed.log
|
|
121
|
+
- When listing CI runs with details:
|
|
122
|
+
gh run list --repo ${owner}/${repo} --branch ${branchName} --limit 5 --json databaseId,conclusion,createdAt,headSha > ${workspaceTmpDir}/ci-logs/recent-runs.json
|
|
123
|
+
- When saving PR diff for review:
|
|
124
|
+
gh pr diff ${prNumber} --repo ${owner}/${repo} > ${workspaceTmpDir}/diffs/pr-${prNumber}.diff
|
|
125
|
+
- When saving command output with stderr:
|
|
126
|
+
npm test 2>&1 | tee ${workspaceTmpDir}/test-output.log
|
|
127
|
+
- When investigating issue details:
|
|
128
|
+
gh issue view ${issueNumber} --repo ${owner}/${repo} --json body,comments > ${workspaceTmpDir}/issue-${issueNumber}.json
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return `You are AI issue solver using @link-assistant/agent.${thinkLine}
|
|
133
|
+
${workspaceInstructions}General guidelines.
|
|
96
134
|
- When you execute commands, always save their logs to files for easier reading if the output becomes large.
|
|
97
135
|
- When running commands, do not set a timeout yourself — let them run as long as needed.
|
|
98
136
|
- When running sudo commands (especially package installations), always run them in the background to avoid timeout issues.
|
|
@@ -185,7 +223,7 @@ GitHub CLI command patterns.
|
|
|
185
223
|
- When adding PR comment, use gh pr comment NUMBER --body "text" --repo OWNER/REPO.
|
|
186
224
|
- When adding issue comment, use gh issue comment NUMBER --body "text" --repo OWNER/REPO.
|
|
187
225
|
- When viewing PR details, use gh pr view NUMBER --repo OWNER/REPO.
|
|
188
|
-
- When filtering with jq, use gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${getArchitectureCareSubPrompt(argv)}`;
|
|
226
|
+
- When filtering with jq, use gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${ciExamples}${getArchitectureCareSubPrompt(argv)}`;
|
|
189
227
|
};
|
|
190
228
|
|
|
191
229
|
// Export all functions as default object too
|
package/src/claude.lib.mjs
CHANGED
|
@@ -413,7 +413,7 @@ export const checkPlaywrightMcpAvailability = async () => {
|
|
|
413
413
|
* @returns {Object} Result of the execution including success status and session info
|
|
414
414
|
*/
|
|
415
415
|
export const executeClaude = async params => {
|
|
416
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, setLogFile, getLogFile, formatAligned, getResourceSnapshot, claudePath, $ } = params;
|
|
416
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, setLogFile, getLogFile, formatAligned, getResourceSnapshot, claudePath, $ } = params;
|
|
417
417
|
// Import prompt building functions from claude.prompts.lib.mjs
|
|
418
418
|
const { buildUserPrompt, buildSystemPrompt } = await import('./claude.prompts.lib.mjs');
|
|
419
419
|
// Build the user prompt
|
|
@@ -424,6 +424,7 @@ export const executeClaude = async params => {
|
|
|
424
424
|
prUrl,
|
|
425
425
|
branchName,
|
|
426
426
|
tempDir,
|
|
427
|
+
workspaceTmpDir,
|
|
427
428
|
isContinueMode,
|
|
428
429
|
mergeStateStatus,
|
|
429
430
|
forkedRepo,
|
|
@@ -443,6 +444,7 @@ export const executeClaude = async params => {
|
|
|
443
444
|
prUrl,
|
|
444
445
|
branchName,
|
|
445
446
|
tempDir,
|
|
447
|
+
workspaceTmpDir,
|
|
446
448
|
isContinueMode,
|
|
447
449
|
forkedRepo,
|
|
448
450
|
argv,
|
|
@@ -11,7 +11,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mj
|
|
|
11
11
|
* @returns {string} The formatted user prompt
|
|
12
12
|
*/
|
|
13
13
|
export const buildUserPrompt = params => {
|
|
14
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, forkedRepo, feedbackLines, owner, repo, argv, contributingGuidelines } = params;
|
|
14
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, owner, repo, argv, contributingGuidelines } = params;
|
|
15
15
|
|
|
16
16
|
const promptLines = [];
|
|
17
17
|
|
|
@@ -26,6 +26,11 @@ export const buildUserPrompt = params => {
|
|
|
26
26
|
promptLines.push(`Your prepared branch: ${branchName}`);
|
|
27
27
|
promptLines.push(`Your prepared working directory: ${tempDir}`);
|
|
28
28
|
|
|
29
|
+
// Workspace tmp directory for logs and temp files (when --enable-workspaces is used)
|
|
30
|
+
if (workspaceTmpDir) {
|
|
31
|
+
promptLines.push(`Your prepared tmp directory for logs and downloads: ${workspaceTmpDir}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
// PR info if available
|
|
30
35
|
if (prUrl) {
|
|
31
36
|
promptLines.push(`Your prepared Pull Request: ${prUrl}`);
|
|
@@ -82,7 +87,7 @@ export const buildUserPrompt = params => {
|
|
|
82
87
|
* @returns {string} The formatted system prompt
|
|
83
88
|
*/
|
|
84
89
|
export const buildSystemPrompt = params => {
|
|
85
|
-
const { owner, repo, issueNumber, prNumber, branchName, argv } = params;
|
|
90
|
+
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv } = params;
|
|
86
91
|
|
|
87
92
|
// Build thinking instruction based on --think level
|
|
88
93
|
let thinkLine = '';
|
|
@@ -96,10 +101,44 @@ export const buildSystemPrompt = params => {
|
|
|
96
101
|
thinkLine = `\n${thinkMessages[argv.think]}\n`;
|
|
97
102
|
}
|
|
98
103
|
|
|
104
|
+
// Build workspace-specific instructions and examples
|
|
105
|
+
let workspaceInstructions = '';
|
|
106
|
+
if (workspaceTmpDir) {
|
|
107
|
+
workspaceInstructions = `
|
|
108
|
+
Workspace tmp directory.
|
|
109
|
+
- Use ${workspaceTmpDir} for all temporary files, logs, and downloads.
|
|
110
|
+
- When saving command output to files, save to ${workspaceTmpDir}/command-output.log.
|
|
111
|
+
- When downloading CI logs, save to ${workspaceTmpDir}/ci-logs/.
|
|
112
|
+
- When saving diffs for review, save to ${workspaceTmpDir}/diffs/.
|
|
113
|
+
- When creating debug files, save to ${workspaceTmpDir}/debug/.
|
|
114
|
+
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Build CI command examples with workspace tmp paths
|
|
119
|
+
let ciExamples = '';
|
|
120
|
+
if (workspaceTmpDir) {
|
|
121
|
+
ciExamples = `
|
|
122
|
+
CI investigation with workspace tmp directory.
|
|
123
|
+
- When downloading CI run logs:
|
|
124
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log > ${workspaceTmpDir}/ci-logs/run-RUN_ID.log
|
|
125
|
+
- When downloading failed job logs:
|
|
126
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log-failed > ${workspaceTmpDir}/ci-logs/run-RUN_ID-failed.log
|
|
127
|
+
- When listing CI runs with details:
|
|
128
|
+
gh run list --repo ${owner}/${repo} --branch ${branchName} --limit 5 --json databaseId,conclusion,createdAt,headSha > ${workspaceTmpDir}/ci-logs/recent-runs.json
|
|
129
|
+
- When saving PR diff for review:
|
|
130
|
+
gh pr diff ${prNumber} --repo ${owner}/${repo} > ${workspaceTmpDir}/diffs/pr-${prNumber}.diff
|
|
131
|
+
- When saving command output with stderr:
|
|
132
|
+
npm test 2>&1 | tee ${workspaceTmpDir}/test-output.log
|
|
133
|
+
- When investigating issue details:
|
|
134
|
+
gh issue view ${issueNumber} --repo ${owner}/${repo} --json body,comments > ${workspaceTmpDir}/issue-${issueNumber}.json
|
|
135
|
+
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
|
|
99
139
|
// Use backticks for jq commands to avoid quote escaping issues
|
|
100
140
|
return `You are an AI issue solver. You prefer to find the root cause of each and every issue. When you talk, you prefer to speak with facts which you have double-checked yourself or cite sources that provide evidence, like quote actual code or give references to documents or pages found on the internet. You are polite and patient, and prefer to assume good intent, trying your best to be helpful. If you are unsure or have assumptions, you prefer to test them yourself or ask questions to clarify requirements.${thinkLine}
|
|
101
|
-
|
|
102
|
-
General guidelines.
|
|
141
|
+
${workspaceInstructions}General guidelines.
|
|
103
142
|
- When you execute commands, always save their logs to files for easier reading if the output becomes large.
|
|
104
143
|
- When running commands, do not set a timeout yourself — let them run as long as needed (default timeout - 2 minutes is more than enough), and once they finish, review the logs in the file.
|
|
105
144
|
- When running sudo commands (especially package installations like apt-get, yum, npm install, etc.), always run them in the background to avoid timeout issues and permission errors when the process needs to be killed. Use the run_in_background parameter or append & to the command.${
|
|
@@ -243,7 +282,7 @@ Plan sub-agent usage.
|
|
|
243
282
|
- When using the Plan sub-agent, you can add it as the first item in your todo list.
|
|
244
283
|
- When you delegate planning, use the Task tool with subagent_type="Plan" before starting implementation work.`
|
|
245
284
|
: ''
|
|
246
|
-
}${getArchitectureCareSubPrompt(argv)}`;
|
|
285
|
+
}${ciExamples}${getArchitectureCareSubPrompt(argv)}`;
|
|
247
286
|
};
|
|
248
287
|
|
|
249
288
|
// Export all functions as default object too
|
package/src/codex.lib.mjs
CHANGED
|
@@ -115,7 +115,7 @@ export const handleCodexRuntimeSwitch = async () => {
|
|
|
115
115
|
|
|
116
116
|
// Main function to execute Codex with prompts and settings
|
|
117
117
|
export const executeCodex = async params => {
|
|
118
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, codexPath = 'codex', $ } = params;
|
|
118
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, codexPath = 'codex', $ } = params;
|
|
119
119
|
|
|
120
120
|
// Import prompt building functions from codex.prompts.lib.mjs
|
|
121
121
|
const { buildUserPrompt, buildSystemPrompt } = await import('./codex.prompts.lib.mjs');
|
|
@@ -128,6 +128,7 @@ export const executeCodex = async params => {
|
|
|
128
128
|
prUrl,
|
|
129
129
|
branchName,
|
|
130
130
|
tempDir,
|
|
131
|
+
workspaceTmpDir,
|
|
131
132
|
isContinueMode,
|
|
132
133
|
mergeStateStatus,
|
|
133
134
|
forkedRepo,
|
|
@@ -146,6 +147,7 @@ export const executeCodex = async params => {
|
|
|
146
147
|
prNumber,
|
|
147
148
|
branchName,
|
|
148
149
|
tempDir,
|
|
150
|
+
workspaceTmpDir,
|
|
149
151
|
isContinueMode,
|
|
150
152
|
forkedRepo,
|
|
151
153
|
argv,
|
|
@@ -11,7 +11,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mj
|
|
|
11
11
|
* @returns {string} The formatted user prompt
|
|
12
12
|
*/
|
|
13
13
|
export const buildUserPrompt = params => {
|
|
14
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
14
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
15
15
|
|
|
16
16
|
const promptLines = [];
|
|
17
17
|
|
|
@@ -26,6 +26,11 @@ export const buildUserPrompt = params => {
|
|
|
26
26
|
promptLines.push(`Your prepared branch: ${branchName}`);
|
|
27
27
|
promptLines.push(`Your prepared working directory: ${tempDir}`);
|
|
28
28
|
|
|
29
|
+
// Workspace tmp directory for logs and temp files (when --enable-workspaces is used)
|
|
30
|
+
if (workspaceTmpDir) {
|
|
31
|
+
promptLines.push(`Your prepared tmp directory for logs and downloads: ${workspaceTmpDir}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
// PR info if available
|
|
30
35
|
if (prUrl) {
|
|
31
36
|
promptLines.push(`Your prepared Pull Request: ${prUrl}`);
|
|
@@ -76,7 +81,7 @@ export const buildUserPrompt = params => {
|
|
|
76
81
|
* @returns {string} The formatted system prompt
|
|
77
82
|
*/
|
|
78
83
|
export const buildSystemPrompt = params => {
|
|
79
|
-
const { owner, repo, issueNumber, prNumber, branchName, argv } = params;
|
|
84
|
+
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv } = params;
|
|
80
85
|
|
|
81
86
|
// Build thinking instruction based on --think level
|
|
82
87
|
let thinkLine = '';
|
|
@@ -90,9 +95,42 @@ export const buildSystemPrompt = params => {
|
|
|
90
95
|
thinkLine = `\n${thinkMessages[argv.think]}\n`;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
|
|
98
|
+
// Build workspace-specific instructions and examples
|
|
99
|
+
let workspaceInstructions = '';
|
|
100
|
+
if (workspaceTmpDir) {
|
|
101
|
+
workspaceInstructions = `
|
|
102
|
+
Workspace tmp directory.
|
|
103
|
+
- Use ${workspaceTmpDir} for all temporary files, logs, and downloads.
|
|
104
|
+
- When saving command output to files, save to ${workspaceTmpDir}/command-output.log.
|
|
105
|
+
- When downloading CI logs, save to ${workspaceTmpDir}/ci-logs/.
|
|
106
|
+
- When saving diffs for review, save to ${workspaceTmpDir}/diffs/.
|
|
107
|
+
- When creating debug files, save to ${workspaceTmpDir}/debug/.
|
|
108
|
+
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
94
111
|
|
|
95
|
-
|
|
112
|
+
// Build CI command examples with workspace tmp paths
|
|
113
|
+
let ciExamples = '';
|
|
114
|
+
if (workspaceTmpDir) {
|
|
115
|
+
ciExamples = `
|
|
116
|
+
CI investigation with workspace tmp directory.
|
|
117
|
+
- When downloading CI run logs:
|
|
118
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log > ${workspaceTmpDir}/ci-logs/run-RUN_ID.log
|
|
119
|
+
- When downloading failed job logs:
|
|
120
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log-failed > ${workspaceTmpDir}/ci-logs/run-RUN_ID-failed.log
|
|
121
|
+
- When listing CI runs with details:
|
|
122
|
+
gh run list --repo ${owner}/${repo} --branch ${branchName} --limit 5 --json databaseId,conclusion,createdAt,headSha > ${workspaceTmpDir}/ci-logs/recent-runs.json
|
|
123
|
+
- When saving PR diff for review:
|
|
124
|
+
gh pr diff ${prNumber} --repo ${owner}/${repo} > ${workspaceTmpDir}/diffs/pr-${prNumber}.diff
|
|
125
|
+
- When saving command output with stderr:
|
|
126
|
+
npm test 2>&1 | tee ${workspaceTmpDir}/test-output.log
|
|
127
|
+
- When investigating issue details:
|
|
128
|
+
gh issue view ${issueNumber} --repo ${owner}/${repo} --json body,comments > ${workspaceTmpDir}/issue-${issueNumber}.json
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return `You are AI issue solver using OpenAI Codex.${thinkLine}
|
|
133
|
+
${workspaceInstructions}General guidelines.
|
|
96
134
|
- When you execute commands, always save their logs to files for easier reading if the output becomes large.
|
|
97
135
|
- When running commands, do not set a timeout yourself — let them run as long as needed (default timeout - 2 minutes is more than enough), and once they finish, review the logs in the file.
|
|
98
136
|
- When running sudo commands (especially package installations like apt-get, yum, npm install, etc.), always run them in the background to avoid timeout issues and permission errors when the process needs to be killed. Use the run_in_background parameter or append & to the command.
|
|
@@ -193,7 +231,7 @@ GitHub CLI command patterns.
|
|
|
193
231
|
- When adding PR comment, use gh pr comment NUMBER --body "text" --repo OWNER/REPO.
|
|
194
232
|
- When adding issue comment, use gh issue comment NUMBER --body "text" --repo OWNER/REPO.
|
|
195
233
|
- When viewing PR details, use gh pr view NUMBER --repo OWNER/REPO.
|
|
196
|
-
- When filtering with jq, use gh api repos/\${owner}/\${repo}/pulls/\${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${getArchitectureCareSubPrompt(argv)}`;
|
|
234
|
+
- When filtering with jq, use gh api repos/\${owner}/\${repo}/pulls/\${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${ciExamples}${getArchitectureCareSubPrompt(argv)}`;
|
|
197
235
|
};
|
|
198
236
|
|
|
199
237
|
// Export all functions as default object too
|
package/src/hive.config.lib.mjs
CHANGED
|
@@ -286,6 +286,11 @@ export const createYargsConfig = yargsInstance => {
|
|
|
286
286
|
description: '[EXPERIMENTAL] Include guidance for managing REQUIREMENTS.md and ARCHITECTURE.md files. When enabled, agents will update these documentation files when changes affect requirements or architecture.',
|
|
287
287
|
default: false,
|
|
288
288
|
})
|
|
289
|
+
.option('execute-tool-with-bun', {
|
|
290
|
+
type: 'boolean',
|
|
291
|
+
description: 'Execute the AI tool using bunx (experimental, may improve speed and memory usage) - passed to solve command',
|
|
292
|
+
default: false,
|
|
293
|
+
})
|
|
289
294
|
.parserConfiguration({
|
|
290
295
|
'boolean-negation': true,
|
|
291
296
|
'strip-dashed': false,
|
package/src/hive.mjs
CHANGED
|
@@ -110,6 +110,8 @@ if (isDirectExecution) {
|
|
|
110
110
|
const { tryFetchIssuesWithGraphQL } = graphqlLib;
|
|
111
111
|
const solutionDraftsLib = await import('./list-solution-drafts.lib.mjs');
|
|
112
112
|
const { listSolutionDrafts } = solutionDraftsLib;
|
|
113
|
+
const recheckLib = await import('./hive.recheck.lib.mjs');
|
|
114
|
+
const { recheckIssueConditions } = recheckLib;
|
|
113
115
|
const commandName = process.argv[1] ? process.argv[1].split('/').pop() : '';
|
|
114
116
|
const isLocalScript = commandName.endsWith('.mjs');
|
|
115
117
|
const solveCommand = isLocalScript ? './solve.mjs' : 'solve';
|
|
@@ -713,6 +715,16 @@ if (isDirectExecution) {
|
|
|
713
715
|
|
|
714
716
|
await log(`\n👷 Worker ${workerId} processing: ${issueUrl}`);
|
|
715
717
|
|
|
718
|
+
// Recheck conditions before processing to avoid wasted work
|
|
719
|
+
const recheckResult = await recheckIssueConditions(issueUrl, argv);
|
|
720
|
+
if (!recheckResult.shouldProcess) {
|
|
721
|
+
await log(` ⏭️ Skipping issue: ${recheckResult.reason}`);
|
|
722
|
+
issueQueue.markCompleted(issueUrl);
|
|
723
|
+
const stats = issueQueue.getStats();
|
|
724
|
+
await log(` 📊 Queue: ${stats.queued} waiting, ${stats.processing} processing, ${stats.completed} completed, ${stats.failed} failed`);
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
|
|
716
728
|
// Track if this issue failed
|
|
717
729
|
let issueFailed = false;
|
|
718
730
|
|
|
@@ -756,6 +768,7 @@ if (isDirectExecution) {
|
|
|
756
768
|
if (argv.promptIssueReporting) args.push('--prompt-issue-reporting');
|
|
757
769
|
if (argv.promptCaseStudies) args.push('--prompt-case-studies');
|
|
758
770
|
if (argv.promptPlaywrightMcp !== undefined) args.push(argv.promptPlaywrightMcp ? '--prompt-playwright-mcp' : '--no-prompt-playwright-mcp');
|
|
771
|
+
if (argv.executeToolWithBun) args.push('--execute-tool-with-bun');
|
|
759
772
|
// Log the actual command being executed so users can investigate/reproduce
|
|
760
773
|
await log(` 📋 Command: ${solveCommand} ${args.join(' ')}`);
|
|
761
774
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Library for rechecking issue conditions in hive queue processing
|
|
3
|
+
|
|
4
|
+
import { log, cleanErrorMessage } from './lib.mjs';
|
|
5
|
+
import { batchCheckPullRequestsForIssues, batchCheckArchivedRepositories } from './github.lib.mjs';
|
|
6
|
+
import { reportError } from './sentry.lib.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Recheck conditions for an issue right before processing
|
|
10
|
+
* This ensures the issue should still be processed even if conditions changed since queuing
|
|
11
|
+
* @param {string} issueUrl - The URL of the issue to check
|
|
12
|
+
* @param {Object} argv - Command line arguments with configuration
|
|
13
|
+
* @returns {Promise<{shouldProcess: boolean, reason?: string}>}
|
|
14
|
+
*/
|
|
15
|
+
export async function recheckIssueConditions(issueUrl, argv) {
|
|
16
|
+
try {
|
|
17
|
+
// Extract owner, repo, and issue number from URL
|
|
18
|
+
const urlMatch = issueUrl.match(/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)/);
|
|
19
|
+
if (!urlMatch) {
|
|
20
|
+
await log(` ⚠️ Could not parse issue URL: ${issueUrl}`, { verbose: true });
|
|
21
|
+
return { shouldProcess: true }; // Process anyway if we can't parse
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const [, owner, repo, issueNumber] = urlMatch;
|
|
25
|
+
const issueNum = parseInt(issueNumber);
|
|
26
|
+
|
|
27
|
+
await log(` 🔍 Rechecking conditions for issue #${issueNum}...`, { verbose: true });
|
|
28
|
+
|
|
29
|
+
// Check 1: Verify issue is still open
|
|
30
|
+
try {
|
|
31
|
+
const { execSync } = await import('child_process');
|
|
32
|
+
const issueState = execSync(`gh api repos/${owner}/${repo}/issues/${issueNum} --jq .state`, {
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
}).trim();
|
|
35
|
+
|
|
36
|
+
if (issueState === 'closed') {
|
|
37
|
+
return {
|
|
38
|
+
shouldProcess: false,
|
|
39
|
+
reason: 'Issue is now closed',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
await log(` ✅ Issue is still open`, { verbose: true });
|
|
43
|
+
} catch (error) {
|
|
44
|
+
await log(` ⚠️ Could not check issue state: ${cleanErrorMessage(error)}`, { verbose: true });
|
|
45
|
+
// Continue checking other conditions
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check 2: If skipIssuesWithPrs is enabled, verify issue still has no open PRs
|
|
49
|
+
if (argv.skipIssuesWithPrs) {
|
|
50
|
+
const prResults = await batchCheckPullRequestsForIssues(owner, repo, [issueNum]);
|
|
51
|
+
const prInfo = prResults[issueNum];
|
|
52
|
+
|
|
53
|
+
if (prInfo && prInfo.openPRCount > 0) {
|
|
54
|
+
return {
|
|
55
|
+
shouldProcess: false,
|
|
56
|
+
reason: `Issue now has ${prInfo.openPRCount} open PR${prInfo.openPRCount > 1 ? 's' : ''}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
await log(` ✅ Issue still has no open PRs`, { verbose: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check 3: Verify repository is not archived
|
|
63
|
+
const archivedStatusMap = await batchCheckArchivedRepositories([{ owner, name: repo }]);
|
|
64
|
+
const repoKey = `${owner}/${repo}`;
|
|
65
|
+
|
|
66
|
+
if (archivedStatusMap[repoKey] === true) {
|
|
67
|
+
return {
|
|
68
|
+
shouldProcess: false,
|
|
69
|
+
reason: 'Repository is now archived',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
await log(` ✅ Repository is not archived`, { verbose: true });
|
|
73
|
+
|
|
74
|
+
await log(` ✅ All conditions passed, proceeding with processing`, { verbose: true });
|
|
75
|
+
return { shouldProcess: true };
|
|
76
|
+
} catch (error) {
|
|
77
|
+
reportError(error, {
|
|
78
|
+
context: 'recheck_issue_conditions',
|
|
79
|
+
issueUrl,
|
|
80
|
+
operation: 'recheck_conditions',
|
|
81
|
+
});
|
|
82
|
+
await log(` ⚠️ Error rechecking conditions: ${cleanErrorMessage(error)}`, { level: 'warning' });
|
|
83
|
+
// On error, allow processing to continue (fail open)
|
|
84
|
+
return { shouldProcess: true };
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/opencode.lib.mjs
CHANGED
|
@@ -109,7 +109,7 @@ export const handleOpenCodeRuntimeSwitch = async () => {
|
|
|
109
109
|
|
|
110
110
|
// Main function to execute OpenCode with prompts and settings
|
|
111
111
|
export const executeOpenCode = async params => {
|
|
112
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, opencodePath = 'opencode', $ } = params;
|
|
112
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, opencodePath = 'opencode', $ } = params;
|
|
113
113
|
|
|
114
114
|
// Import prompt building functions from opencode.prompts.lib.mjs
|
|
115
115
|
const { buildUserPrompt, buildSystemPrompt } = await import('./opencode.prompts.lib.mjs');
|
|
@@ -122,6 +122,7 @@ export const executeOpenCode = async params => {
|
|
|
122
122
|
prUrl,
|
|
123
123
|
branchName,
|
|
124
124
|
tempDir,
|
|
125
|
+
workspaceTmpDir,
|
|
125
126
|
isContinueMode,
|
|
126
127
|
mergeStateStatus,
|
|
127
128
|
forkedRepo,
|
|
@@ -140,6 +141,7 @@ export const executeOpenCode = async params => {
|
|
|
140
141
|
prNumber,
|
|
141
142
|
branchName,
|
|
142
143
|
tempDir,
|
|
144
|
+
workspaceTmpDir,
|
|
143
145
|
isContinueMode,
|
|
144
146
|
forkedRepo,
|
|
145
147
|
argv,
|
|
@@ -11,7 +11,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mj
|
|
|
11
11
|
* @returns {string} The formatted user prompt
|
|
12
12
|
*/
|
|
13
13
|
export const buildUserPrompt = params => {
|
|
14
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
14
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
15
15
|
|
|
16
16
|
const promptLines = [];
|
|
17
17
|
|
|
@@ -26,6 +26,11 @@ export const buildUserPrompt = params => {
|
|
|
26
26
|
promptLines.push(`Your prepared branch: ${branchName}`);
|
|
27
27
|
promptLines.push(`Your prepared working directory: ${tempDir}`);
|
|
28
28
|
|
|
29
|
+
// Workspace tmp directory for logs and temp files (when --enable-workspaces is used)
|
|
30
|
+
if (workspaceTmpDir) {
|
|
31
|
+
promptLines.push(`Your prepared tmp directory for logs and downloads: ${workspaceTmpDir}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
// PR info if available
|
|
30
35
|
if (prUrl) {
|
|
31
36
|
promptLines.push(`Your prepared Pull Request: ${prUrl}`);
|
|
@@ -76,7 +81,7 @@ export const buildUserPrompt = params => {
|
|
|
76
81
|
* @returns {string} The formatted system prompt
|
|
77
82
|
*/
|
|
78
83
|
export const buildSystemPrompt = params => {
|
|
79
|
-
const { owner, repo, issueNumber, prNumber, branchName, argv } = params;
|
|
84
|
+
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv } = params;
|
|
80
85
|
|
|
81
86
|
// Build thinking instruction based on --think level
|
|
82
87
|
let thinkLine = '';
|
|
@@ -90,6 +95,41 @@ export const buildSystemPrompt = params => {
|
|
|
90
95
|
thinkLine = `\n${thinkMessages[argv.think]}\n`;
|
|
91
96
|
}
|
|
92
97
|
|
|
98
|
+
// Build workspace-specific instructions and examples
|
|
99
|
+
let workspaceInstructions = '';
|
|
100
|
+
if (workspaceTmpDir) {
|
|
101
|
+
workspaceInstructions = `
|
|
102
|
+
Workspace tmp directory.
|
|
103
|
+
- Use ${workspaceTmpDir} for all temporary files, logs, and downloads.
|
|
104
|
+
- When saving command output to files, save to ${workspaceTmpDir}/command-output.log.
|
|
105
|
+
- When downloading CI logs, save to ${workspaceTmpDir}/ci-logs/.
|
|
106
|
+
- When saving diffs for review, save to ${workspaceTmpDir}/diffs/.
|
|
107
|
+
- When creating debug files, save to ${workspaceTmpDir}/debug/.
|
|
108
|
+
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Build CI command examples with workspace tmp paths
|
|
113
|
+
let ciExamples = '';
|
|
114
|
+
if (workspaceTmpDir) {
|
|
115
|
+
ciExamples = `
|
|
116
|
+
CI investigation with workspace tmp directory.
|
|
117
|
+
- When downloading CI run logs:
|
|
118
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log > ${workspaceTmpDir}/ci-logs/run-RUN_ID.log
|
|
119
|
+
- When downloading failed job logs:
|
|
120
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log-failed > ${workspaceTmpDir}/ci-logs/run-RUN_ID-failed.log
|
|
121
|
+
- When listing CI runs with details:
|
|
122
|
+
gh run list --repo ${owner}/${repo} --branch ${branchName} --limit 5 --json databaseId,conclusion,createdAt,headSha > ${workspaceTmpDir}/ci-logs/recent-runs.json
|
|
123
|
+
- When saving PR diff for review:
|
|
124
|
+
gh pr diff ${prNumber} --repo ${owner}/${repo} > ${workspaceTmpDir}/diffs/pr-${prNumber}.diff
|
|
125
|
+
- When saving command output with stderr:
|
|
126
|
+
npm test 2>&1 | tee ${workspaceTmpDir}/test-output.log
|
|
127
|
+
- When investigating issue details:
|
|
128
|
+
gh issue view ${issueNumber} --repo ${owner}/${repo} --json body,comments > ${workspaceTmpDir}/issue-${issueNumber}.json
|
|
129
|
+
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
|
|
93
133
|
return `You are AI issue solver using OpenCode.${thinkLine}
|
|
94
134
|
|
|
95
135
|
General guidelines.
|
|
@@ -103,7 +143,7 @@ General guidelines.
|
|
|
103
143
|
- When testing your assumptions, use the experiment scripts, and add it to experiments folder.
|
|
104
144
|
- When your experiments can show real world use case of the software, add it to examples folder.
|
|
105
145
|
- When you face something extremely hard, use divide and conquer — it always helps.
|
|
106
|
-
|
|
146
|
+
${workspaceInstructions}
|
|
107
147
|
Initial research.
|
|
108
148
|
- When you start, make sure you create detailed plan for yourself and follow your todo list step by step, make sure that as many points from these guidelines are added to your todo list to keep track of everything that can help you solve the issue with highest possible quality.
|
|
109
149
|
- When you read issue, read all details and comments thoroughly.
|
|
@@ -184,7 +224,7 @@ GitHub CLI command patterns.
|
|
|
184
224
|
- When adding PR comment, use gh pr comment NUMBER --body "text" --repo OWNER/REPO.
|
|
185
225
|
- When adding issue comment, use gh issue comment NUMBER --body "text" --repo OWNER/REPO.
|
|
186
226
|
- When viewing PR details, use gh pr view NUMBER --repo OWNER/REPO.
|
|
187
|
-
- When filtering with jq, use gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${getArchitectureCareSubPrompt(argv)}`;
|
|
227
|
+
- When filtering with jq, use gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${ciExamples}${getArchitectureCareSubPrompt(argv)}`;
|
|
188
228
|
};
|
|
189
229
|
|
|
190
230
|
// Export all functions as default object too
|
package/src/review.mjs
CHANGED
|
@@ -19,14 +19,15 @@ if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
|
|
|
19
19
|
// Show help and exit
|
|
20
20
|
console.log('Usage: review.mjs <pr-url> [options]');
|
|
21
21
|
console.log('\nOptions:');
|
|
22
|
-
console.log(' --version
|
|
23
|
-
console.log(' --help, -h
|
|
24
|
-
console.log(' --resume, -r
|
|
25
|
-
console.log(' --dry-run, -n
|
|
26
|
-
console.log(' --model, -m
|
|
27
|
-
console.log(' --focus, -f
|
|
28
|
-
console.log(' --approve
|
|
29
|
-
console.log(' --verbose, -v
|
|
22
|
+
console.log(' --version Show version number');
|
|
23
|
+
console.log(' --help, -h Show help');
|
|
24
|
+
console.log(' --resume, -r Resume from a previous session ID');
|
|
25
|
+
console.log(' --dry-run, -n Prepare everything but do not execute Claude');
|
|
26
|
+
console.log(' --model, -m Model to use (opus, sonnet, or full model ID) [default: opus]');
|
|
27
|
+
console.log(' --focus, -f Focus areas for review [default: all]');
|
|
28
|
+
console.log(' --approve If review passes, approve the PR');
|
|
29
|
+
console.log(' --verbose, -v Enable verbose logging');
|
|
30
|
+
console.log(' --execute-tool-with-bun Execute the AI tool using bunx (experimental) [default: false]');
|
|
30
31
|
process.exit(0);
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -91,6 +92,11 @@ const argv = yargs()
|
|
|
91
92
|
alias: 'v',
|
|
92
93
|
default: false,
|
|
93
94
|
})
|
|
95
|
+
.option('execute-tool-with-bun', {
|
|
96
|
+
type: 'boolean',
|
|
97
|
+
description: 'Execute the AI tool using bunx (experimental, may improve speed and memory usage)',
|
|
98
|
+
default: false,
|
|
99
|
+
})
|
|
94
100
|
.demandCommand(1, 'The GitHub pull request URL is required')
|
|
95
101
|
.parserConfiguration({
|
|
96
102
|
'boolean-negation': true,
|
|
@@ -126,7 +132,9 @@ if (!prUrl.match(/^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/)) {
|
|
|
126
132
|
process.exit(1);
|
|
127
133
|
}
|
|
128
134
|
|
|
129
|
-
|
|
135
|
+
// Determine claude command path based on --execute-tool-with-bun option
|
|
136
|
+
// When enabled, uses 'bunx claude' which may improve speed and memory usage
|
|
137
|
+
const claudePath = argv.executeToolWithBun ? 'bunx claude' : process.env.CLAUDE_PATH || 'claude';
|
|
130
138
|
|
|
131
139
|
// Extract repository and PR number from URL
|
|
132
140
|
const urlParts = prUrl.split('/');
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -253,6 +253,16 @@ export const createYargsConfig = yargsInstance => {
|
|
|
253
253
|
choices: ['claude', 'opencode', 'codex', 'agent'],
|
|
254
254
|
default: 'claude',
|
|
255
255
|
})
|
|
256
|
+
.option('execute-tool-with-bun', {
|
|
257
|
+
type: 'boolean',
|
|
258
|
+
description: 'Execute the AI tool using bunx (experimental, may improve speed and memory usage)',
|
|
259
|
+
default: false,
|
|
260
|
+
})
|
|
261
|
+
.option('enable-workspaces', {
|
|
262
|
+
type: 'boolean',
|
|
263
|
+
description: 'Use separate workspace directory structure with repository/ and tmp/ folders. Works with all tools (claude, opencode, codex, agent). Experimental feature.',
|
|
264
|
+
default: false,
|
|
265
|
+
})
|
|
256
266
|
.option('interactive-mode', {
|
|
257
267
|
type: 'boolean',
|
|
258
268
|
description: '[EXPERIMENTAL] Post Claude output as PR comments in real-time. Only supported for --tool claude.',
|
package/src/solve.mjs
CHANGED
|
@@ -65,6 +65,9 @@ const { executeClaude } = claudeLib;
|
|
|
65
65
|
const githubLinking = await import('./github-linking.lib.mjs');
|
|
66
66
|
const { extractLinkedIssueNumber } = githubLinking;
|
|
67
67
|
|
|
68
|
+
const usageLimitLib = await import('./usage-limit.lib.mjs');
|
|
69
|
+
const { formatResetTimeWithRelative } = usageLimitLib;
|
|
70
|
+
|
|
68
71
|
const errorHandlers = await import('./solve.error-handlers.lib.mjs');
|
|
69
72
|
const { createUncaughtExceptionHandler, createUnhandledRejectionHandler, handleMainExecutionError } = errorHandlers;
|
|
70
73
|
|
|
@@ -233,7 +236,7 @@ if (argv.verbose) {
|
|
|
233
236
|
await log(` Is Issue URL: ${!!isIssueUrl}`, { verbose: true });
|
|
234
237
|
await log(` Is PR URL: ${!!isPrUrl}`, { verbose: true });
|
|
235
238
|
}
|
|
236
|
-
const claudePath = process.env.CLAUDE_PATH || 'claude';
|
|
239
|
+
const claudePath = argv.executeToolWithBun ? 'bunx claude' : process.env.CLAUDE_PATH || 'claude';
|
|
237
240
|
// Note: owner, repo, and urlNumber are already extracted from validateGitHubUrl() above
|
|
238
241
|
// The parseUrlComponents() call was removed as it had a bug with hash fragments (#issuecomment-xyz)
|
|
239
242
|
// and the validation result already provides these values correctly parsed
|
|
@@ -508,7 +511,9 @@ if (isPrUrl) {
|
|
|
508
511
|
await log(`📝 Issue mode: Working with issue #${issueNumber}`);
|
|
509
512
|
}
|
|
510
513
|
// Create or find temporary directory for cloning the repository
|
|
511
|
-
|
|
514
|
+
// Pass workspace info for --enable-workspaces mode (works with all tools)
|
|
515
|
+
const workspaceInfo = argv.enableWorkspaces ? { owner, repo, issueNumber } : null;
|
|
516
|
+
const { tempDir, workspaceTmpDir } = await setupTempDirectory(argv, workspaceInfo);
|
|
512
517
|
// Populate cleanup context for signal handlers
|
|
513
518
|
cleanupContext.tempDir = tempDir;
|
|
514
519
|
cleanupContext.argv = argv;
|
|
@@ -761,6 +766,7 @@ try {
|
|
|
761
766
|
prUrl,
|
|
762
767
|
branchName,
|
|
763
768
|
tempDir,
|
|
769
|
+
workspaceTmpDir,
|
|
764
770
|
isContinueMode,
|
|
765
771
|
mergeStateStatus,
|
|
766
772
|
forkedRepo,
|
|
@@ -789,6 +795,7 @@ try {
|
|
|
789
795
|
prUrl,
|
|
790
796
|
branchName,
|
|
791
797
|
tempDir,
|
|
798
|
+
workspaceTmpDir,
|
|
792
799
|
isContinueMode,
|
|
793
800
|
mergeStateStatus,
|
|
794
801
|
forkedRepo,
|
|
@@ -817,6 +824,7 @@ try {
|
|
|
817
824
|
prUrl,
|
|
818
825
|
branchName,
|
|
819
826
|
tempDir,
|
|
827
|
+
workspaceTmpDir,
|
|
820
828
|
isContinueMode,
|
|
821
829
|
mergeStateStatus,
|
|
822
830
|
forkedRepo,
|
|
@@ -858,6 +866,7 @@ try {
|
|
|
858
866
|
prUrl,
|
|
859
867
|
branchName,
|
|
860
868
|
tempDir,
|
|
869
|
+
workspaceTmpDir,
|
|
861
870
|
isContinueMode,
|
|
862
871
|
mergeStateStatus,
|
|
863
872
|
forkedRepo,
|
|
@@ -961,7 +970,9 @@ try {
|
|
|
961
970
|
const tool = argv.tool || 'claude';
|
|
962
971
|
const resumeCmd = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : null;
|
|
963
972
|
const resumeSection = resumeCmd ? `To resume after the limit resets, use:\n\`\`\`bash\n${resumeCmd}\n\`\`\`` : `Session ID: \`${sessionId}\``;
|
|
964
|
-
|
|
973
|
+
// Format the reset time with relative time if available
|
|
974
|
+
const formattedResetTime = resetTime ? formatResetTimeWithRelative(resetTime) : null;
|
|
975
|
+
const failureComment = formattedResetTime ? `❌ **Usage Limit Reached**\n\nThe AI tool has reached its usage limit. The limit will reset at: **${formattedResetTime}**\n\n${resumeSection}` : `❌ **Usage Limit Reached**\n\nThe AI tool has reached its usage limit. Please wait for the limit to reset.\n\n${resumeSection}`;
|
|
965
976
|
|
|
966
977
|
const commentResult = await $`gh pr comment ${prNumber} --repo ${owner}/${repo} --body ${failureComment}`;
|
|
967
978
|
if (commentResult.code === 0) {
|
|
@@ -199,11 +199,35 @@ export const validateForkParent = async (forkRepo, expectedUpstream) => {
|
|
|
199
199
|
}
|
|
200
200
|
};
|
|
201
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Build workspace directory name according to the specification:
|
|
204
|
+
* /tmp/hive-mind-solve-gh-{owner}/{repo}-issue-{issueNumber}-workspace-{timestamp}
|
|
205
|
+
*
|
|
206
|
+
* @param {string} owner - Repository owner
|
|
207
|
+
* @param {string} repo - Repository name
|
|
208
|
+
* @param {number|string} issueNumber - Issue number
|
|
209
|
+
* @param {number} timestamp - Unix timestamp
|
|
210
|
+
* @returns {string} The workspace directory path
|
|
211
|
+
*/
|
|
212
|
+
export const buildWorkspacePath = (owner, repo, issueNumber, timestamp) => {
|
|
213
|
+
// Format: /tmp/hive-mind-solve-gh-{owner}/{repo}-issue-{issueNumber}-workspace-{timestamp}
|
|
214
|
+
const baseDir = path.join(os.tmpdir(), `hive-mind-solve-gh-${owner}`);
|
|
215
|
+
const workspaceDir = path.join(baseDir, `${repo}-issue-${issueNumber}-workspace-${timestamp}`);
|
|
216
|
+
return workspaceDir;
|
|
217
|
+
};
|
|
218
|
+
|
|
202
219
|
// Create or find temporary directory for cloning the repository
|
|
203
|
-
|
|
220
|
+
// When --enable-workspaces is used, creates:
|
|
221
|
+
// {workspace}/repository - for the cloned repo
|
|
222
|
+
// {workspace}/tmp - for temp files, logs, downloads
|
|
223
|
+
export const setupTempDirectory = async (argv, workspaceInfo = null) => {
|
|
204
224
|
let tempDir;
|
|
225
|
+
let workspaceTmpDir = null;
|
|
205
226
|
let isResuming = argv.resume;
|
|
206
227
|
|
|
228
|
+
// Check if workspace mode should be enabled (works with all tools)
|
|
229
|
+
const useWorkspaces = argv.enableWorkspaces;
|
|
230
|
+
|
|
207
231
|
if (isResuming) {
|
|
208
232
|
// When resuming, try to find existing directory or create a new one
|
|
209
233
|
const scriptDir = path.dirname(process.argv[1]);
|
|
@@ -229,13 +253,34 @@ export const setupTempDirectory = async argv => {
|
|
|
229
253
|
await fs.mkdir(tempDir, { recursive: true });
|
|
230
254
|
await log(`Creating temporary directory for resumed session: ${tempDir}`);
|
|
231
255
|
}
|
|
256
|
+
} else if (useWorkspaces && workspaceInfo) {
|
|
257
|
+
// Workspace mode: create structured workspace with repository/ and tmp/ subdirectories
|
|
258
|
+
const { owner, repo, issueNumber } = workspaceInfo;
|
|
259
|
+
const timestamp = Date.now();
|
|
260
|
+
const workspaceDir = buildWorkspacePath(owner, repo, issueNumber, timestamp);
|
|
261
|
+
|
|
262
|
+
// Create the workspace structure:
|
|
263
|
+
// {workspace}/repository - where the repo will be cloned
|
|
264
|
+
// {workspace}/tmp - for temp files, logs, command outputs
|
|
265
|
+
const repoDir = path.join(workspaceDir, 'repository');
|
|
266
|
+
workspaceTmpDir = path.join(workspaceDir, 'tmp');
|
|
267
|
+
|
|
268
|
+
await fs.mkdir(repoDir, { recursive: true });
|
|
269
|
+
await fs.mkdir(workspaceTmpDir, { recursive: true });
|
|
270
|
+
|
|
271
|
+
tempDir = repoDir;
|
|
272
|
+
|
|
273
|
+
await log(`\n${formatAligned('📂', 'Workspace mode:', 'ENABLED')}`);
|
|
274
|
+
await log(formatAligned('', 'Workspace root:', workspaceDir, 2));
|
|
275
|
+
await log(formatAligned('', 'Repository dir:', repoDir, 2));
|
|
276
|
+
await log(formatAligned('', 'Temp dir:', workspaceTmpDir, 2));
|
|
232
277
|
} else {
|
|
233
278
|
tempDir = path.join(os.tmpdir(), `gh-issue-solver-${Date.now()}`);
|
|
234
279
|
await fs.mkdir(tempDir, { recursive: true });
|
|
235
280
|
await log(`\nCreating temporary directory: ${tempDir}`);
|
|
236
281
|
}
|
|
237
282
|
|
|
238
|
-
return { tempDir, isResuming };
|
|
283
|
+
return { tempDir, workspaceTmpDir, isResuming };
|
|
239
284
|
};
|
|
240
285
|
|
|
241
286
|
// Try to initialize an empty repository by creating a simple README.md
|
package/src/task.mjs
CHANGED
|
@@ -124,6 +124,11 @@ const argv = yargs()
|
|
|
124
124
|
default: 'text',
|
|
125
125
|
choices: ['text', 'json'],
|
|
126
126
|
})
|
|
127
|
+
.option('execute-tool-with-bun', {
|
|
128
|
+
type: 'boolean',
|
|
129
|
+
description: 'Execute the AI tool using bunx (experimental, may improve speed and memory usage)',
|
|
130
|
+
default: false,
|
|
131
|
+
})
|
|
127
132
|
.check(argv => {
|
|
128
133
|
if (!argv['task-description'] && !argv._[0]) {
|
|
129
134
|
throw new Error('Please provide a task description');
|
|
@@ -186,7 +191,7 @@ await log(formatAligned('💡', 'Clarify mode:', argv.clarify ? 'enabled' : 'dis
|
|
|
186
191
|
await log(formatAligned('🔍', 'Decompose mode:', argv.decompose ? 'enabled' : 'disabled'));
|
|
187
192
|
await log(formatAligned('📄', 'Output format:', argv.outputFormat));
|
|
188
193
|
|
|
189
|
-
const claudePath = process.env.CLAUDE_PATH || 'claude';
|
|
194
|
+
const claudePath = argv.executeToolWithBun ? 'bunx claude' : process.env.CLAUDE_PATH || 'claude';
|
|
190
195
|
|
|
191
196
|
// Helper function to execute Claude command
|
|
192
197
|
const executeClaude = (prompt, model) => {
|
package/src/usage-limit.lib.mjs
CHANGED
|
@@ -142,6 +142,111 @@ export function detectUsageLimit(message) {
|
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Parse time string (e.g., "11:00 PM") and convert to Date object for today
|
|
147
|
+
*
|
|
148
|
+
* @param {string} timeStr - Time string in format "HH:MM AM/PM"
|
|
149
|
+
* @returns {Date|null} - Date object or null if parsing fails
|
|
150
|
+
*/
|
|
151
|
+
export function parseResetTime(timeStr) {
|
|
152
|
+
if (!timeStr || typeof timeStr !== 'string') {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Match pattern like "11:00 PM" or "11:00PM"
|
|
157
|
+
const match = timeStr.match(/(\d{1,2}):(\d{2})\s*(AM|PM)/i);
|
|
158
|
+
if (!match) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let hour = parseInt(match[1], 10);
|
|
163
|
+
const minute = parseInt(match[2], 10);
|
|
164
|
+
const ampm = match[3].toUpperCase();
|
|
165
|
+
|
|
166
|
+
// Convert to 24-hour format
|
|
167
|
+
if (ampm === 'PM' && hour !== 12) {
|
|
168
|
+
hour += 12;
|
|
169
|
+
} else if (ampm === 'AM' && hour === 12) {
|
|
170
|
+
hour = 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Create date for today with the parsed time
|
|
174
|
+
const now = new Date();
|
|
175
|
+
const resetDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute, 0, 0);
|
|
176
|
+
|
|
177
|
+
// If the time is in the past today, assume it's tomorrow
|
|
178
|
+
if (resetDate <= now) {
|
|
179
|
+
resetDate.setDate(resetDate.getDate() + 1);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return resetDate;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Format relative time (e.g., "in 1h 23m")
|
|
187
|
+
*
|
|
188
|
+
* @param {Date} resetDate - Date object for reset time
|
|
189
|
+
* @returns {string} - Formatted relative time string
|
|
190
|
+
*/
|
|
191
|
+
export function formatRelativeTime(resetDate) {
|
|
192
|
+
if (!resetDate || !(resetDate instanceof Date)) {
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const now = new Date();
|
|
197
|
+
const diffMs = resetDate - now;
|
|
198
|
+
|
|
199
|
+
if (diffMs <= 0) {
|
|
200
|
+
return 'now';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const totalSeconds = Math.floor(diffMs / 1000);
|
|
204
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
205
|
+
const totalHours = Math.floor(totalMinutes / 60);
|
|
206
|
+
const totalDays = Math.floor(totalHours / 24);
|
|
207
|
+
|
|
208
|
+
const days = totalDays;
|
|
209
|
+
const hours = totalHours % 24;
|
|
210
|
+
const minutes = totalMinutes % 60;
|
|
211
|
+
|
|
212
|
+
const parts = [];
|
|
213
|
+
if (days > 0) parts.push(`${days}d`);
|
|
214
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
215
|
+
if (minutes > 0 || parts.length === 0) parts.push(`${minutes}m`);
|
|
216
|
+
|
|
217
|
+
return `in ${parts.join(' ')}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Format reset time with relative time and UTC time
|
|
222
|
+
* Example: "in 1h 23m (11:00 PM UTC)"
|
|
223
|
+
*
|
|
224
|
+
* @param {string} resetTime - Time string in format "HH:MM AM/PM"
|
|
225
|
+
* @returns {string} - Formatted string with relative and absolute time
|
|
226
|
+
*/
|
|
227
|
+
export function formatResetTimeWithRelative(resetTime) {
|
|
228
|
+
if (!resetTime) {
|
|
229
|
+
return resetTime;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const resetDate = parseResetTime(resetTime);
|
|
233
|
+
if (!resetDate) {
|
|
234
|
+
// If we can't parse it, return the original time
|
|
235
|
+
return resetTime;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const relativeTime = formatRelativeTime(resetDate);
|
|
239
|
+
|
|
240
|
+
// Format the UTC time
|
|
241
|
+
const utcHours = resetDate.getUTCHours();
|
|
242
|
+
const utcMinutes = resetDate.getUTCMinutes();
|
|
243
|
+
const utcAmPm = utcHours >= 12 ? 'PM' : 'AM';
|
|
244
|
+
const utcHour12 = utcHours % 12 || 12;
|
|
245
|
+
const utcTimeStr = `${utcHour12}:${String(utcMinutes).padStart(2, '0')} ${utcAmPm} UTC`;
|
|
246
|
+
|
|
247
|
+
return `${relativeTime} (${utcTimeStr})`;
|
|
248
|
+
}
|
|
249
|
+
|
|
145
250
|
/**
|
|
146
251
|
* Format usage limit error message for console output
|
|
147
252
|
*
|