@link-assistant/hive-mind 0.39.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 +20 -0
- package/LICENSE +24 -0
- package/README.md +769 -0
- package/package.json +58 -0
- package/src/agent.lib.mjs +705 -0
- package/src/agent.prompts.lib.mjs +196 -0
- package/src/buildUserMention.lib.mjs +71 -0
- package/src/claude-limits.lib.mjs +389 -0
- package/src/claude.lib.mjs +1445 -0
- package/src/claude.prompts.lib.mjs +203 -0
- package/src/codex.lib.mjs +552 -0
- package/src/codex.prompts.lib.mjs +194 -0
- package/src/config.lib.mjs +207 -0
- package/src/contributing-guidelines.lib.mjs +268 -0
- package/src/exit-handler.lib.mjs +205 -0
- package/src/git.lib.mjs +145 -0
- package/src/github-issue-creator.lib.mjs +246 -0
- package/src/github-linking.lib.mjs +152 -0
- package/src/github.batch.lib.mjs +272 -0
- package/src/github.graphql.lib.mjs +258 -0
- package/src/github.lib.mjs +1479 -0
- package/src/hive.config.lib.mjs +254 -0
- package/src/hive.mjs +1500 -0
- package/src/instrument.mjs +191 -0
- package/src/interactive-mode.lib.mjs +1000 -0
- package/src/lenv-reader.lib.mjs +206 -0
- package/src/lib.mjs +490 -0
- package/src/lino.lib.mjs +176 -0
- package/src/local-ci-checks.lib.mjs +324 -0
- package/src/memory-check.mjs +419 -0
- package/src/model-mapping.lib.mjs +145 -0
- package/src/model-validation.lib.mjs +278 -0
- package/src/opencode.lib.mjs +479 -0
- package/src/opencode.prompts.lib.mjs +194 -0
- package/src/protect-branch.mjs +159 -0
- package/src/review.mjs +433 -0
- package/src/reviewers-hive.mjs +643 -0
- package/src/sentry.lib.mjs +284 -0
- package/src/solve.auto-continue.lib.mjs +568 -0
- package/src/solve.auto-pr.lib.mjs +1374 -0
- package/src/solve.branch-errors.lib.mjs +341 -0
- package/src/solve.branch.lib.mjs +230 -0
- package/src/solve.config.lib.mjs +342 -0
- package/src/solve.error-handlers.lib.mjs +256 -0
- package/src/solve.execution.lib.mjs +291 -0
- package/src/solve.feedback.lib.mjs +436 -0
- package/src/solve.mjs +1128 -0
- package/src/solve.preparation.lib.mjs +210 -0
- package/src/solve.repo-setup.lib.mjs +114 -0
- package/src/solve.repository.lib.mjs +961 -0
- package/src/solve.results.lib.mjs +558 -0
- package/src/solve.session.lib.mjs +135 -0
- package/src/solve.validation.lib.mjs +325 -0
- package/src/solve.watch.lib.mjs +572 -0
- package/src/start-screen.mjs +324 -0
- package/src/task.mjs +308 -0
- package/src/telegram-bot.mjs +1481 -0
- package/src/telegram-markdown.lib.mjs +64 -0
- package/src/usage-limit.lib.mjs +218 -0
- package/src/version.lib.mjs +41 -0
- package/src/youtrack/solve.youtrack.lib.mjs +116 -0
- package/src/youtrack/youtrack-sync.mjs +219 -0
- package/src/youtrack/youtrack.lib.mjs +425 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// OpenCode-related utility functions
|
|
3
|
+
|
|
4
|
+
// Check if use is already defined (when imported from solve.mjs)
|
|
5
|
+
// If not, fetch it (when running standalone)
|
|
6
|
+
if (typeof globalThis.use === 'undefined') {
|
|
7
|
+
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { $ } = await use('command-stream');
|
|
11
|
+
const fs = (await use('fs')).promises;
|
|
12
|
+
const path = (await use('path')).default;
|
|
13
|
+
const os = (await use('os')).default;
|
|
14
|
+
|
|
15
|
+
// Import log from general lib
|
|
16
|
+
import { log } from './lib.mjs';
|
|
17
|
+
import { reportError } from './sentry.lib.mjs';
|
|
18
|
+
import { timeouts } from './config.lib.mjs';
|
|
19
|
+
import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
|
|
20
|
+
|
|
21
|
+
// Model mapping to translate aliases to full model IDs for OpenCode
|
|
22
|
+
export const mapModelToId = (model) => {
|
|
23
|
+
const modelMap = {
|
|
24
|
+
'gpt4': 'openai/gpt-4',
|
|
25
|
+
'gpt4o': 'openai/gpt-4o',
|
|
26
|
+
'claude': 'anthropic/claude-3-5-sonnet',
|
|
27
|
+
'sonnet': 'anthropic/claude-3-5-sonnet',
|
|
28
|
+
'opus': 'anthropic/claude-3-opus',
|
|
29
|
+
'gemini': 'google/gemini-pro',
|
|
30
|
+
'grok': 'opencode/grok-code',
|
|
31
|
+
'grok-code': 'opencode/grok-code',
|
|
32
|
+
'grok-code-fast-1': 'opencode/grok-code',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Return mapped model ID if it's an alias, otherwise return as-is
|
|
36
|
+
return modelMap[model] || model;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Function to validate OpenCode connection
|
|
40
|
+
export const validateOpenCodeConnection = async (model = 'grok-code-fast-1') => {
|
|
41
|
+
// Map model alias to full ID
|
|
42
|
+
const mappedModel = mapModelToId(model);
|
|
43
|
+
|
|
44
|
+
// Retry configuration
|
|
45
|
+
const maxRetries = 3;
|
|
46
|
+
let retryCount = 0;
|
|
47
|
+
|
|
48
|
+
const attemptValidation = async () => {
|
|
49
|
+
try {
|
|
50
|
+
if (retryCount === 0) {
|
|
51
|
+
await log('🔍 Validating OpenCode connection...');
|
|
52
|
+
} else {
|
|
53
|
+
await log(`🔄 Retry attempt ${retryCount}/${maxRetries} for OpenCode validation...`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check if OpenCode CLI is installed and get version
|
|
57
|
+
try {
|
|
58
|
+
const versionResult = await $`timeout ${Math.floor(timeouts.opencodeCli / 1000)} opencode --version`;
|
|
59
|
+
if (versionResult.code === 0) {
|
|
60
|
+
const version = versionResult.stdout?.toString().trim();
|
|
61
|
+
if (retryCount === 0) {
|
|
62
|
+
await log(`📦 OpenCode CLI version: ${version}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch (versionError) {
|
|
66
|
+
if (retryCount === 0) {
|
|
67
|
+
await log(`⚠️ OpenCode CLI version check failed (${versionError.code}), proceeding with connection test...`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Test basic OpenCode functionality with a simple "hi" message
|
|
72
|
+
// Check for non-error result to validate the connection
|
|
73
|
+
const testResult = await $`printf "hi" | timeout ${Math.floor(timeouts.opencodeCli / 1000)} opencode run --format json --model ${mappedModel}`;
|
|
74
|
+
|
|
75
|
+
if (testResult.code !== 0) {
|
|
76
|
+
const stderr = testResult.stderr?.toString() || '';
|
|
77
|
+
|
|
78
|
+
if (stderr.includes('auth') || stderr.includes('login')) {
|
|
79
|
+
await log('❌ OpenCode authentication failed', { level: 'error' });
|
|
80
|
+
await log(' 💡 Please run: opencode auth', { level: 'error' });
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await log(`❌ OpenCode validation failed with exit code ${testResult.code}`, { level: 'error' });
|
|
85
|
+
if (stderr) await log(` Error: ${stderr.trim()}`, { level: 'error' });
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Success
|
|
90
|
+
await log('✅ OpenCode connection validated successfully');
|
|
91
|
+
return true;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
await log(`❌ Failed to validate OpenCode connection: ${error.message}`, { level: 'error' });
|
|
94
|
+
await log(' 💡 Make sure OpenCode CLI is installed and accessible', { level: 'error' });
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Start the validation
|
|
100
|
+
return await attemptValidation();
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Function to handle OpenCode runtime switching (if applicable)
|
|
104
|
+
export const handleOpenCodeRuntimeSwitch = async () => {
|
|
105
|
+
// OpenCode is typically run as a CLI tool, runtime switching may not be applicable
|
|
106
|
+
// This function can be used for any runtime-specific configurations if needed
|
|
107
|
+
await log('ℹ️ OpenCode runtime handling not required for this operation');
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Main function to execute OpenCode with prompts and settings
|
|
111
|
+
export const executeOpenCode = async (params) => {
|
|
112
|
+
const {
|
|
113
|
+
issueUrl,
|
|
114
|
+
issueNumber,
|
|
115
|
+
prNumber,
|
|
116
|
+
prUrl,
|
|
117
|
+
branchName,
|
|
118
|
+
tempDir,
|
|
119
|
+
isContinueMode,
|
|
120
|
+
mergeStateStatus,
|
|
121
|
+
forkedRepo,
|
|
122
|
+
feedbackLines,
|
|
123
|
+
forkActionsUrl,
|
|
124
|
+
owner,
|
|
125
|
+
repo,
|
|
126
|
+
argv,
|
|
127
|
+
log,
|
|
128
|
+
formatAligned,
|
|
129
|
+
getResourceSnapshot,
|
|
130
|
+
opencodePath = 'opencode',
|
|
131
|
+
$
|
|
132
|
+
} = params;
|
|
133
|
+
|
|
134
|
+
// Import prompt building functions from opencode.prompts.lib.mjs
|
|
135
|
+
const { buildUserPrompt, buildSystemPrompt } = await import('./opencode.prompts.lib.mjs');
|
|
136
|
+
|
|
137
|
+
// Build the user prompt
|
|
138
|
+
const prompt = buildUserPrompt({
|
|
139
|
+
issueUrl,
|
|
140
|
+
issueNumber,
|
|
141
|
+
prNumber,
|
|
142
|
+
prUrl,
|
|
143
|
+
branchName,
|
|
144
|
+
tempDir,
|
|
145
|
+
isContinueMode,
|
|
146
|
+
mergeStateStatus,
|
|
147
|
+
forkedRepo,
|
|
148
|
+
feedbackLines,
|
|
149
|
+
forkActionsUrl,
|
|
150
|
+
owner,
|
|
151
|
+
repo,
|
|
152
|
+
argv
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Build the system prompt
|
|
156
|
+
const systemPrompt = buildSystemPrompt({
|
|
157
|
+
owner,
|
|
158
|
+
repo,
|
|
159
|
+
issueNumber,
|
|
160
|
+
prNumber,
|
|
161
|
+
branchName,
|
|
162
|
+
tempDir,
|
|
163
|
+
isContinueMode,
|
|
164
|
+
forkedRepo,
|
|
165
|
+
argv
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Log prompt details in verbose mode
|
|
169
|
+
if (argv.verbose) {
|
|
170
|
+
await log('\n📝 Final prompt structure:', { verbose: true });
|
|
171
|
+
await log(` Characters: ${prompt.length}`, { verbose: true });
|
|
172
|
+
await log(` System prompt characters: ${systemPrompt.length}`, { verbose: true });
|
|
173
|
+
if (feedbackLines && feedbackLines.length > 0) {
|
|
174
|
+
await log(' Feedback info: Included', { verbose: true });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (argv.dryRun) {
|
|
178
|
+
await log('\n📋 User prompt content:', { verbose: true });
|
|
179
|
+
await log('---BEGIN USER PROMPT---', { verbose: true });
|
|
180
|
+
await log(prompt, { verbose: true });
|
|
181
|
+
await log('---END USER PROMPT---', { verbose: true });
|
|
182
|
+
await log('\n📋 System prompt content:', { verbose: true });
|
|
183
|
+
await log('---BEGIN SYSTEM PROMPT---', { verbose: true });
|
|
184
|
+
await log(systemPrompt, { verbose: true });
|
|
185
|
+
await log('---END SYSTEM PROMPT---', { verbose: true });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Execute the OpenCode command
|
|
190
|
+
return await executeOpenCodeCommand({
|
|
191
|
+
tempDir,
|
|
192
|
+
branchName,
|
|
193
|
+
prompt,
|
|
194
|
+
systemPrompt,
|
|
195
|
+
argv,
|
|
196
|
+
log,
|
|
197
|
+
formatAligned,
|
|
198
|
+
getResourceSnapshot,
|
|
199
|
+
forkedRepo,
|
|
200
|
+
feedbackLines,
|
|
201
|
+
opencodePath,
|
|
202
|
+
$
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export const executeOpenCodeCommand = async (params) => {
|
|
207
|
+
const {
|
|
208
|
+
tempDir,
|
|
209
|
+
branchName,
|
|
210
|
+
prompt,
|
|
211
|
+
systemPrompt,
|
|
212
|
+
argv,
|
|
213
|
+
log,
|
|
214
|
+
formatAligned,
|
|
215
|
+
getResourceSnapshot,
|
|
216
|
+
forkedRepo,
|
|
217
|
+
feedbackLines,
|
|
218
|
+
opencodePath,
|
|
219
|
+
$
|
|
220
|
+
} = params;
|
|
221
|
+
|
|
222
|
+
// Retry configuration
|
|
223
|
+
const maxRetries = 3;
|
|
224
|
+
let retryCount = 0;
|
|
225
|
+
|
|
226
|
+
const executeWithRetry = async () => {
|
|
227
|
+
// Execute opencode command from the cloned repository directory
|
|
228
|
+
if (retryCount === 0) {
|
|
229
|
+
await log(`\n${formatAligned('🤖', 'Executing OpenCode:', argv.model.toUpperCase())}`);
|
|
230
|
+
} else {
|
|
231
|
+
await log(`\n${formatAligned('🔄', 'Retry attempt:', `${retryCount}/${maxRetries}`)}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (argv.verbose) {
|
|
235
|
+
await log(` Model: ${argv.model}`, { verbose: true });
|
|
236
|
+
await log(` Working directory: ${tempDir}`, { verbose: true });
|
|
237
|
+
await log(` Branch: ${branchName}`, { verbose: true });
|
|
238
|
+
await log(` Prompt length: ${prompt.length} chars`, { verbose: true });
|
|
239
|
+
await log(` System prompt length: ${systemPrompt.length} chars`, { verbose: true });
|
|
240
|
+
if (feedbackLines && feedbackLines.length > 0) {
|
|
241
|
+
await log(` Feedback info included: Yes (${feedbackLines.length} lines)`, { verbose: true });
|
|
242
|
+
} else {
|
|
243
|
+
await log(' Feedback info included: No', { verbose: true });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Take resource snapshot before execution
|
|
248
|
+
const resourcesBefore = await getResourceSnapshot();
|
|
249
|
+
await log('📈 System resources before execution:', { verbose: true });
|
|
250
|
+
await log(` Memory: ${resourcesBefore.memory.split('\n')[1]}`, { verbose: true });
|
|
251
|
+
await log(` Load: ${resourcesBefore.load}`, { verbose: true });
|
|
252
|
+
|
|
253
|
+
// Build OpenCode command
|
|
254
|
+
let execCommand;
|
|
255
|
+
|
|
256
|
+
// Map model alias to full ID
|
|
257
|
+
const mappedModel = mapModelToId(argv.model);
|
|
258
|
+
|
|
259
|
+
// Build opencode command arguments
|
|
260
|
+
let opencodeArgs = `run --format json --model ${mappedModel}`;
|
|
261
|
+
|
|
262
|
+
if (argv.resume) {
|
|
263
|
+
await log(`🔄 Resuming from session: ${argv.resume}`);
|
|
264
|
+
opencodeArgs = `run --format json --resume ${argv.resume} --model ${mappedModel}`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// For OpenCode, we pass the prompt via stdin
|
|
268
|
+
// The system prompt is typically not supported separately in opencode
|
|
269
|
+
// We'll combine system and user prompts into a single message
|
|
270
|
+
const combinedPrompt = systemPrompt ? `${systemPrompt}\n\n${prompt}` : prompt;
|
|
271
|
+
|
|
272
|
+
// Write the combined prompt to a file for piping
|
|
273
|
+
// Use OS temporary directory instead of repository workspace to avoid polluting the repo
|
|
274
|
+
const promptFile = path.join(os.tmpdir(), `opencode_prompt_${Date.now()}_${process.pid}.txt`);
|
|
275
|
+
await fs.writeFile(promptFile, combinedPrompt);
|
|
276
|
+
|
|
277
|
+
// Build the full command - pipe the prompt file to opencode
|
|
278
|
+
const fullCommand = `(cd "${tempDir}" && cat "${promptFile}" | ${opencodePath} ${opencodeArgs})`;
|
|
279
|
+
|
|
280
|
+
await log(`\n${formatAligned('📝', 'Raw command:', '')}`);
|
|
281
|
+
await log(`${fullCommand}`);
|
|
282
|
+
await log('');
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
// Pipe the prompt file to opencode via stdin
|
|
286
|
+
if (argv.resume) {
|
|
287
|
+
execCommand = $({
|
|
288
|
+
cwd: tempDir,
|
|
289
|
+
mirror: false
|
|
290
|
+
})`cat ${promptFile} | ${opencodePath} run --format json --resume ${argv.resume} --model ${mappedModel}`;
|
|
291
|
+
} else {
|
|
292
|
+
execCommand = $({
|
|
293
|
+
cwd: tempDir,
|
|
294
|
+
mirror: false
|
|
295
|
+
})`cat ${promptFile} | ${opencodePath} run --format json --model ${mappedModel}`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
await log(`${formatAligned('📋', 'Command details:', '')}`);
|
|
299
|
+
await log(formatAligned('📂', 'Working directory:', tempDir, 2));
|
|
300
|
+
await log(formatAligned('🌿', 'Branch:', branchName, 2));
|
|
301
|
+
await log(formatAligned('🤖', 'Model:', `OpenCode ${argv.model.toUpperCase()}`, 2));
|
|
302
|
+
if (argv.fork && forkedRepo) {
|
|
303
|
+
await log(formatAligned('🍴', 'Fork:', forkedRepo, 2));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
await log(`\n${formatAligned('▶️', 'Streaming output:', '')}\n`);
|
|
307
|
+
|
|
308
|
+
let exitCode = 0;
|
|
309
|
+
let sessionId = null;
|
|
310
|
+
let limitReached = false;
|
|
311
|
+
let limitResetTime = null;
|
|
312
|
+
let lastMessage = '';
|
|
313
|
+
|
|
314
|
+
for await (const chunk of execCommand.stream()) {
|
|
315
|
+
if (chunk.type === 'stdout') {
|
|
316
|
+
const output = chunk.data.toString();
|
|
317
|
+
await log(output);
|
|
318
|
+
lastMessage = output;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (chunk.type === 'stderr') {
|
|
322
|
+
const errorOutput = chunk.data.toString();
|
|
323
|
+
if (errorOutput) {
|
|
324
|
+
await log(errorOutput, { stream: 'stderr' });
|
|
325
|
+
}
|
|
326
|
+
} else if (chunk.type === 'exit') {
|
|
327
|
+
exitCode = chunk.code;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (exitCode !== 0) {
|
|
332
|
+
// Check for usage limit errors first (more specific)
|
|
333
|
+
const limitInfo = detectUsageLimit(lastMessage);
|
|
334
|
+
if (limitInfo.isUsageLimit) {
|
|
335
|
+
limitReached = true;
|
|
336
|
+
limitResetTime = limitInfo.resetTime;
|
|
337
|
+
|
|
338
|
+
// Format and display user-friendly message
|
|
339
|
+
const messageLines = formatUsageLimitMessage({
|
|
340
|
+
tool: 'OpenCode',
|
|
341
|
+
resetTime: limitInfo.resetTime,
|
|
342
|
+
sessionId,
|
|
343
|
+
resumeCommand: sessionId ? `${process.argv[0]} ${process.argv[1]} ${argv.url} --resume ${sessionId}` : null
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
for (const line of messageLines) {
|
|
347
|
+
await log(line, { level: 'warning' });
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
await log(`\n\n❌ OpenCode command failed with exit code ${exitCode}`, { level: 'error' });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const resourcesAfter = await getResourceSnapshot();
|
|
354
|
+
await log('\n📈 System resources after execution:', { verbose: true });
|
|
355
|
+
await log(` Memory: ${resourcesAfter.memory.split('\n')[1]}`, { verbose: true });
|
|
356
|
+
await log(` Load: ${resourcesAfter.load}`, { verbose: true });
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
success: false,
|
|
360
|
+
sessionId,
|
|
361
|
+
limitReached,
|
|
362
|
+
limitResetTime
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
await log('\n\n✅ OpenCode command completed');
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
success: true,
|
|
370
|
+
sessionId,
|
|
371
|
+
limitReached,
|
|
372
|
+
limitResetTime
|
|
373
|
+
};
|
|
374
|
+
} catch (error) {
|
|
375
|
+
reportError(error, {
|
|
376
|
+
context: 'execute_opencode',
|
|
377
|
+
command: params.command,
|
|
378
|
+
opencodePath: params.opencodePath,
|
|
379
|
+
operation: 'run_opencode_command'
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
await log(`\n\n❌ Error executing OpenCode command: ${error.message}`, { level: 'error' });
|
|
383
|
+
return {
|
|
384
|
+
success: false,
|
|
385
|
+
sessionId: null,
|
|
386
|
+
limitReached: false,
|
|
387
|
+
limitResetTime: null
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// Start the execution with retry logic
|
|
393
|
+
return await executeWithRetry();
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
export const checkForUncommittedChanges = async (tempDir, owner, repo, branchName, $, log, autoCommit = false, autoRestartEnabled = true) => {
|
|
397
|
+
// Similar to Claude version, check for uncommitted changes
|
|
398
|
+
await log('\n🔍 Checking for uncommitted changes...');
|
|
399
|
+
try {
|
|
400
|
+
const gitStatusResult = await $({ cwd: tempDir })`git status --porcelain 2>&1`;
|
|
401
|
+
|
|
402
|
+
if (gitStatusResult.code === 0) {
|
|
403
|
+
const statusOutput = gitStatusResult.stdout.toString().trim();
|
|
404
|
+
|
|
405
|
+
if (statusOutput) {
|
|
406
|
+
await log('📝 Found uncommitted changes');
|
|
407
|
+
await log('Changes:');
|
|
408
|
+
for (const line of statusOutput.split('\n')) {
|
|
409
|
+
await log(` ${line}`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (autoCommit) {
|
|
413
|
+
await log('💾 Auto-committing changes (--auto-commit-uncommitted-changes is enabled)...');
|
|
414
|
+
|
|
415
|
+
const addResult = await $({ cwd: tempDir })`git add -A`;
|
|
416
|
+
if (addResult.code === 0) {
|
|
417
|
+
const commitMessage = 'Auto-commit: Changes made by OpenCode during problem-solving session';
|
|
418
|
+
const commitResult = await $({ cwd: tempDir })`git commit -m ${commitMessage}`;
|
|
419
|
+
|
|
420
|
+
if (commitResult.code === 0) {
|
|
421
|
+
await log('✅ Changes committed successfully');
|
|
422
|
+
|
|
423
|
+
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
|
|
424
|
+
|
|
425
|
+
if (pushResult.code === 0) {
|
|
426
|
+
await log('✅ Changes pushed successfully');
|
|
427
|
+
} else {
|
|
428
|
+
await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim()}`, { level: 'warning' });
|
|
429
|
+
}
|
|
430
|
+
} else {
|
|
431
|
+
await log(`⚠️ Warning: Could not commit changes: ${commitResult.stderr?.toString().trim()}`, { level: 'warning' });
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
await log(`⚠️ Warning: Could not stage changes: ${addResult.stderr?.toString().trim()}`, { level: 'warning' });
|
|
435
|
+
}
|
|
436
|
+
return false;
|
|
437
|
+
} else if (autoRestartEnabled) {
|
|
438
|
+
await log('');
|
|
439
|
+
await log('⚠️ IMPORTANT: Uncommitted changes detected!');
|
|
440
|
+
await log(' OpenCode made changes that were not committed.');
|
|
441
|
+
await log('');
|
|
442
|
+
await log('🔄 AUTO-RESTART: Restarting OpenCode to handle uncommitted changes...');
|
|
443
|
+
await log(' OpenCode will review the changes and decide what to commit.');
|
|
444
|
+
await log('');
|
|
445
|
+
return true;
|
|
446
|
+
} else {
|
|
447
|
+
await log('');
|
|
448
|
+
await log('⚠️ Uncommitted changes detected but auto-restart is disabled.');
|
|
449
|
+
await log(' Use --auto-restart-on-uncommitted-changes to enable or commit manually.');
|
|
450
|
+
await log('');
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
await log('✅ No uncommitted changes found');
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
await log(`⚠️ Warning: Could not check git status: ${gitStatusResult.stderr?.toString().trim()}`, { level: 'warning' });
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
} catch (gitError) {
|
|
462
|
+
reportError(gitError, {
|
|
463
|
+
context: 'check_uncommitted_changes_opencode',
|
|
464
|
+
tempDir,
|
|
465
|
+
operation: 'git_status_check'
|
|
466
|
+
});
|
|
467
|
+
await log(`⚠️ Warning: Error checking for uncommitted changes: ${gitError.message}`, { level: 'warning' });
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// Export all functions as default object too
|
|
473
|
+
export default {
|
|
474
|
+
validateOpenCodeConnection,
|
|
475
|
+
handleOpenCodeRuntimeSwitch,
|
|
476
|
+
executeOpenCode,
|
|
477
|
+
executeOpenCodeCommand,
|
|
478
|
+
checkForUncommittedChanges
|
|
479
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode prompts module
|
|
3
|
+
* Handles building prompts for OpenCode commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Build the user prompt for OpenCode
|
|
8
|
+
* @param {Object} params - Parameters for building the user prompt
|
|
9
|
+
* @returns {string} The formatted user prompt
|
|
10
|
+
*/
|
|
11
|
+
export const buildUserPrompt = (params) => {
|
|
12
|
+
const {
|
|
13
|
+
issueUrl,
|
|
14
|
+
issueNumber,
|
|
15
|
+
prNumber,
|
|
16
|
+
prUrl,
|
|
17
|
+
branchName,
|
|
18
|
+
tempDir,
|
|
19
|
+
isContinueMode,
|
|
20
|
+
forkedRepo,
|
|
21
|
+
feedbackLines,
|
|
22
|
+
forkActionsUrl,
|
|
23
|
+
owner,
|
|
24
|
+
repo,
|
|
25
|
+
argv
|
|
26
|
+
} = params;
|
|
27
|
+
|
|
28
|
+
const promptLines = [];
|
|
29
|
+
|
|
30
|
+
// Issue or PR reference
|
|
31
|
+
if (isContinueMode) {
|
|
32
|
+
promptLines.push(`Issue to solve: ${issueNumber ? `https://github.com/${owner}/${repo}/issues/${issueNumber}` : `Issue linked to PR #${prNumber}`}`);
|
|
33
|
+
} else {
|
|
34
|
+
promptLines.push(`Issue to solve: ${issueUrl}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Basic info
|
|
38
|
+
promptLines.push(`Your prepared branch: ${branchName}`);
|
|
39
|
+
promptLines.push(`Your prepared working directory: ${tempDir}`);
|
|
40
|
+
|
|
41
|
+
// PR info if available
|
|
42
|
+
if (prUrl) {
|
|
43
|
+
promptLines.push(`Your prepared Pull Request: ${prUrl}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fork info if applicable
|
|
47
|
+
if (argv && argv.fork && forkedRepo) {
|
|
48
|
+
promptLines.push(`Your forked repository: ${forkedRepo}`);
|
|
49
|
+
promptLines.push(`Original repository (upstream): ${owner}/${repo}`);
|
|
50
|
+
|
|
51
|
+
// Check for GitHub Actions on fork
|
|
52
|
+
if (branchName && forkActionsUrl) {
|
|
53
|
+
promptLines.push(`GitHub Actions on your fork: ${forkActionsUrl}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Add blank line
|
|
58
|
+
promptLines.push('');
|
|
59
|
+
|
|
60
|
+
// Add feedback info if in continue mode and there are feedback items
|
|
61
|
+
if (isContinueMode && feedbackLines && feedbackLines.length > 0) {
|
|
62
|
+
// Add each feedback line directly
|
|
63
|
+
feedbackLines.forEach(line => promptLines.push(line));
|
|
64
|
+
promptLines.push('');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Add thinking instruction based on --think level
|
|
68
|
+
if (argv && argv.think) {
|
|
69
|
+
const thinkMessages = {
|
|
70
|
+
low: 'Think.',
|
|
71
|
+
medium: 'Think hard.',
|
|
72
|
+
high: 'Think harder.',
|
|
73
|
+
max: 'Ultrathink.'
|
|
74
|
+
};
|
|
75
|
+
promptLines.push(thinkMessages[argv.think]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Final instruction
|
|
79
|
+
promptLines.push(isContinueMode ? 'Continue.' : 'Proceed.');
|
|
80
|
+
|
|
81
|
+
// Build the final prompt
|
|
82
|
+
return promptLines.join('\n');
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Build the system prompt for OpenCode - adapted for OpenCode's capabilities
|
|
87
|
+
* @param {Object} params - Parameters for building the prompt
|
|
88
|
+
* @returns {string} The formatted system prompt
|
|
89
|
+
*/
|
|
90
|
+
export const buildSystemPrompt = (params) => {
|
|
91
|
+
const { owner, repo, issueNumber, prNumber, branchName, argv } = params;
|
|
92
|
+
|
|
93
|
+
// Build thinking instruction based on --think level
|
|
94
|
+
let thinkLine = '';
|
|
95
|
+
if (argv && argv.think) {
|
|
96
|
+
const thinkMessages = {
|
|
97
|
+
low: 'You always think on every step.',
|
|
98
|
+
medium: 'You always think hard on every step.',
|
|
99
|
+
high: 'You always think harder on every step.',
|
|
100
|
+
max: 'You always ultrathink on every step.'
|
|
101
|
+
};
|
|
102
|
+
thinkLine = `\n${thinkMessages[argv.think]}\n`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return `You are AI issue solver using OpenCode.${thinkLine}
|
|
106
|
+
|
|
107
|
+
General guidelines.
|
|
108
|
+
- When you execute commands, always save their logs to files for easier reading if the output becomes large.
|
|
109
|
+
- When running commands, do not set a timeout yourself — let them run as long as needed.
|
|
110
|
+
- When running sudo commands (especially package installations), always run them in the background to avoid timeout issues.
|
|
111
|
+
- When CI is failing, make sure you download the logs locally and carefully investigate them.
|
|
112
|
+
- When a code or log file has more than 1500 lines, read it in chunks of 1500 lines.
|
|
113
|
+
- When facing a complex problem, do as much tracing as possible and turn on all verbose modes.
|
|
114
|
+
- When you create debug, test, or example/experiment scripts for fixing, always keep them in an examples and/or experiments folders so you can reuse them later.
|
|
115
|
+
- When testing your assumptions, use the experiment scripts, and add it to experiments folder.
|
|
116
|
+
- When your experiments can show real world use case of the software, add it to examples folder.
|
|
117
|
+
- When you face something extremely hard, use divide and conquer — it always helps.
|
|
118
|
+
|
|
119
|
+
Initial research.
|
|
120
|
+
- 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.
|
|
121
|
+
- When you read issue, read all details and comments thoroughly.
|
|
122
|
+
- When you see screenshots or images in issue descriptions, pull request descriptions, comments, or discussions, use WebFetch tool to download the image first, then use Read tool to view and analyze it.
|
|
123
|
+
- When you need issue details, use gh issue view https://github.com/${owner}/${repo}/issues/${issueNumber}.
|
|
124
|
+
- When you need related code, use gh search code --owner ${owner} [keywords].
|
|
125
|
+
- When you need repo context, read files in your working directory.
|
|
126
|
+
- When you study related work, study the most recent related pull requests.
|
|
127
|
+
- When issue is not defined enough, write a comment to ask clarifying questions.
|
|
128
|
+
- When accessing GitHub Gists, use gh gist view command instead of direct URL fetching.
|
|
129
|
+
- When you are fixing a bug, please make sure you first find the actual root cause, do as many experiments as needed.
|
|
130
|
+
- When you are fixing a bug and code does not have enough tracing/logs, add them and make sure they stay in the code, but are switched off by default.
|
|
131
|
+
- When you need latest comments on pull request, use gh api repos/${owner}/${repo}/pulls/${prNumber}/comments.
|
|
132
|
+
- When you need latest comments on issue, use gh api repos/${owner}/${repo}/issues/${issueNumber}/comments.
|
|
133
|
+
|
|
134
|
+
Solution development and testing.
|
|
135
|
+
- When issue is solvable, implement code with tests.
|
|
136
|
+
- When coding, each atomic step that can be useful by itself should be commited to the pull request's branch, meaning if work will be interrupted by any reason parts of solution will still be kept intact and safe in pull request.
|
|
137
|
+
- When you test:
|
|
138
|
+
start from testing of small functions using separate scripts;
|
|
139
|
+
write unit tests with mocks for easy and quick start.
|
|
140
|
+
- When you test integrations, use existing framework.
|
|
141
|
+
- When you test solution draft, include automated checks in pr.
|
|
142
|
+
- When issue is unclear, write comment on issue asking questions.
|
|
143
|
+
- When you encounter any problems that you unable to solve yourself, write a comment to the pull request asking for help.
|
|
144
|
+
- When you need human help, use gh pr comment ${prNumber} --body "your message" to comment on existing PR.
|
|
145
|
+
|
|
146
|
+
Preparing pull request.
|
|
147
|
+
- When you code, follow contributing guidelines.
|
|
148
|
+
- When you commit, write clear message.
|
|
149
|
+
- When you need examples of style, use gh pr list --repo ${owner}/${repo} --state merged --search [keywords].
|
|
150
|
+
- When you open pr, describe solution draft and include tests.
|
|
151
|
+
- When there is a package with version and GitHub Actions workflows for automatic release, update the version in your pull request to prepare for next release.
|
|
152
|
+
- When you update existing pr ${prNumber}, use gh pr edit to modify title and description.
|
|
153
|
+
- When you finalize the pull request:
|
|
154
|
+
follow style from merged prs for code, title, and description,
|
|
155
|
+
make sure no uncommitted changes corresponding to the original requirements are left behind,
|
|
156
|
+
make sure the default branch is merged to the pull request's branch,
|
|
157
|
+
make sure all CI checks passing if they exist before you finish,
|
|
158
|
+
double-check that all changes in the pull request answer to original requirements of the issue,
|
|
159
|
+
make sure no new bugs are introduced in pull request by carefully reading gh pr diff,
|
|
160
|
+
make sure no previously existing features were removed without an explicit request from users via the issue description, issue comments, and/or pull request comments.
|
|
161
|
+
- When you finish implementation, use gh pr ready ${prNumber}.
|
|
162
|
+
|
|
163
|
+
Workflow and collaboration.
|
|
164
|
+
- When you check branch, verify with git branch --show-current.
|
|
165
|
+
- When you push, push only to branch ${branchName}.
|
|
166
|
+
- When you finish, create a pull request from branch ${branchName}.
|
|
167
|
+
- When pr ${prNumber} already exists for this branch, update it instead of creating new one.
|
|
168
|
+
- When you organize workflow, use pull requests instead of direct merges to default branch (main or master).
|
|
169
|
+
- When you manage commits, preserve commit history for later analysis.
|
|
170
|
+
- When you contribute, keep repository history forward-moving with regular commits, pushes, and reverts if needed.
|
|
171
|
+
- When you face conflict that you cannot resolve yourself, ask for help.
|
|
172
|
+
- When you collaborate, respect branch protections by working only on ${branchName}.
|
|
173
|
+
- When you mention result, include pull request url or comment url.
|
|
174
|
+
- When you need to create pr, remember pr ${prNumber} already exists for this branch.
|
|
175
|
+
|
|
176
|
+
Self review.
|
|
177
|
+
- When you check your solution draft, run all tests locally.
|
|
178
|
+
- When you compare with repo style, use gh pr diff [number].
|
|
179
|
+
- When you finalize, confirm code, tests, and description are consistent.
|
|
180
|
+
|
|
181
|
+
GitHub CLI command patterns.
|
|
182
|
+
- When listing PR comments, use gh api repos/OWNER/REPO/pulls/NUMBER/comments.
|
|
183
|
+
- When listing issue comments, use gh api repos/OWNER/REPO/issues/NUMBER/comments.
|
|
184
|
+
- When adding PR comment, use gh pr comment NUMBER --body "text" --repo OWNER/REPO.
|
|
185
|
+
- When adding issue comment, use gh issue comment NUMBER --body "text" --repo OWNER/REPO.
|
|
186
|
+
- 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 --jq 'reverse | .[0:5]'.`;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Export all functions as default object too
|
|
191
|
+
export default {
|
|
192
|
+
buildUserPrompt,
|
|
193
|
+
buildSystemPrompt
|
|
194
|
+
};
|