@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,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-execution preparation functionality for solve.mjs
|
|
3
|
+
* Handles timestamp collection, feedback detection, and pre-execution checks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import feedback detection functionality
|
|
7
|
+
const feedback = await import('./solve.feedback.lib.mjs');
|
|
8
|
+
const { detectAndCountFeedback } = feedback;
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export async function prepareFeedbackAndTimestamps({
|
|
13
|
+
prNumber,
|
|
14
|
+
branchName: _branchName,
|
|
15
|
+
owner,
|
|
16
|
+
repo,
|
|
17
|
+
issueNumber,
|
|
18
|
+
isContinueMode: _isContinueMode,
|
|
19
|
+
mergeStateStatus: _mergeStateStatus,
|
|
20
|
+
prState: _prState,
|
|
21
|
+
argv: _argv,
|
|
22
|
+
log,
|
|
23
|
+
formatAligned,
|
|
24
|
+
cleanErrorMessage: _cleanErrorMessage,
|
|
25
|
+
$
|
|
26
|
+
}) {
|
|
27
|
+
// Count new comments and detect feedback
|
|
28
|
+
let { feedbackLines } = await detectAndCountFeedback({
|
|
29
|
+
prNumber,
|
|
30
|
+
branchName: _branchName,
|
|
31
|
+
owner,
|
|
32
|
+
repo,
|
|
33
|
+
issueNumber,
|
|
34
|
+
isContinueMode: _isContinueMode,
|
|
35
|
+
argv: _argv,
|
|
36
|
+
mergeStateStatus: _mergeStateStatus,
|
|
37
|
+
prState: _prState,
|
|
38
|
+
workStartTime: null, // Will be set by session management
|
|
39
|
+
log,
|
|
40
|
+
formatAligned,
|
|
41
|
+
cleanErrorMessage: _cleanErrorMessage,
|
|
42
|
+
$
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Get timestamps from GitHub servers before executing the command
|
|
46
|
+
await log(`${formatAligned('📅', 'Getting timestamps:', 'From GitHub servers...')}`);
|
|
47
|
+
|
|
48
|
+
let referenceTime;
|
|
49
|
+
try {
|
|
50
|
+
// Get the issue's last update time
|
|
51
|
+
const issueResult = await $`gh api repos/${owner}/${repo}/issues/${issueNumber} --jq .updated_at`;
|
|
52
|
+
|
|
53
|
+
if (issueResult.code !== 0) {
|
|
54
|
+
throw new Error(`Failed to get issue details: ${issueResult.stderr ? issueResult.stderr.toString() : 'Unknown error'}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const issueUpdatedAt = new Date(issueResult.stdout.toString().trim());
|
|
58
|
+
await log(formatAligned('📝', 'Issue updated:', issueUpdatedAt.toISOString(), 2));
|
|
59
|
+
|
|
60
|
+
// Get the last comment's timestamp (if any)
|
|
61
|
+
const commentsResult = await $`gh api repos/${owner}/${repo}/issues/${issueNumber}/comments`;
|
|
62
|
+
|
|
63
|
+
if (commentsResult.code !== 0) {
|
|
64
|
+
await log(`Warning: Failed to get comments: ${commentsResult.stderr ? commentsResult.stderr.toString() : 'Unknown error'}`, { level: 'warning' });
|
|
65
|
+
// Continue anyway, comments are optional
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const comments = JSON.parse(commentsResult.stdout.toString().trim() || '[]');
|
|
69
|
+
const lastCommentTime = comments.length > 0 ? new Date(comments[comments.length - 1].created_at) : null;
|
|
70
|
+
if (lastCommentTime) {
|
|
71
|
+
await log(formatAligned('💬', 'Last comment:', lastCommentTime.toISOString(), 2));
|
|
72
|
+
} else {
|
|
73
|
+
await log(formatAligned('💬', 'Comments:', 'None found', 2));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Get the most recent pull request's timestamp
|
|
77
|
+
const prsResult = await $`gh pr list --repo ${owner}/${repo} --limit 1 --json createdAt`;
|
|
78
|
+
|
|
79
|
+
if (prsResult.code !== 0) {
|
|
80
|
+
await log(`Warning: Failed to get PRs: ${prsResult.stderr ? prsResult.stderr.toString() : 'Unknown error'}`, { level: 'warning' });
|
|
81
|
+
// Continue anyway, PRs are optional for timestamp calculation
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const prs = JSON.parse(prsResult.stdout.toString().trim() || '[]');
|
|
85
|
+
const lastPrTime = prs.length > 0 ? new Date(prs[0].createdAt) : null;
|
|
86
|
+
if (lastPrTime) {
|
|
87
|
+
await log(formatAligned('🔀', 'Recent PR:', lastPrTime.toISOString(), 2));
|
|
88
|
+
} else {
|
|
89
|
+
await log(formatAligned('🔀', 'Pull requests:', 'None found', 2));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Use the most recent timestamp as reference
|
|
93
|
+
referenceTime = issueUpdatedAt;
|
|
94
|
+
if (lastCommentTime && lastCommentTime > referenceTime) {
|
|
95
|
+
referenceTime = lastCommentTime;
|
|
96
|
+
}
|
|
97
|
+
if (lastPrTime && lastPrTime > referenceTime) {
|
|
98
|
+
referenceTime = lastPrTime;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await log(`\n${formatAligned('✅', 'Reference time:', referenceTime.toISOString())}`);
|
|
102
|
+
} catch (timestampError) {
|
|
103
|
+
const sentryLib = await import('./sentry.lib.mjs');
|
|
104
|
+
const { reportError } = sentryLib;
|
|
105
|
+
reportError(timestampError, {
|
|
106
|
+
context: 'get_reference_timestamp',
|
|
107
|
+
prNumber,
|
|
108
|
+
issueNumber,
|
|
109
|
+
operation: 'fetch_github_timestamps'
|
|
110
|
+
});
|
|
111
|
+
await log('Warning: Could not get GitHub timestamps, using current time as reference', { level: 'warning' });
|
|
112
|
+
await log(` Error: ${timestampError.message}`);
|
|
113
|
+
referenceTime = new Date();
|
|
114
|
+
await log(` Fallback timestamp: ${referenceTime.toISOString()}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { feedbackLines, referenceTime };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function checkUncommittedChanges({
|
|
121
|
+
tempDir,
|
|
122
|
+
argv,
|
|
123
|
+
log,
|
|
124
|
+
$
|
|
125
|
+
}) {
|
|
126
|
+
// Check for uncommitted changes before running Claude
|
|
127
|
+
// Only add to feedback if auto-commit is disabled
|
|
128
|
+
if (!argv['auto-commit-uncommitted-changes']) {
|
|
129
|
+
await log('\n🔍 Checking for uncommitted changes to include as feedback...');
|
|
130
|
+
try {
|
|
131
|
+
const gitStatusResult = await $({ cwd: tempDir })`git status --porcelain 2>&1`;
|
|
132
|
+
if (gitStatusResult.code === 0) {
|
|
133
|
+
const statusOutput = gitStatusResult.stdout.toString().trim();
|
|
134
|
+
if (statusOutput) {
|
|
135
|
+
await log('📝 Found uncommitted changes - adding to feedback');
|
|
136
|
+
|
|
137
|
+
// Add uncommitted changes info to feedbackLines
|
|
138
|
+
let feedbackLines = [];
|
|
139
|
+
|
|
140
|
+
feedbackLines.push('');
|
|
141
|
+
feedbackLines.push('⚠️ UNCOMMITTED CHANGES DETECTED:');
|
|
142
|
+
feedbackLines.push('The following uncommitted changes were found in the repository:');
|
|
143
|
+
feedbackLines.push('');
|
|
144
|
+
|
|
145
|
+
for (const line of statusOutput.split('\n')) {
|
|
146
|
+
feedbackLines.push(` ${line}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
feedbackLines.push('');
|
|
150
|
+
feedbackLines.push('IMPORTANT: You MUST handle these uncommitted changes by either:');
|
|
151
|
+
feedbackLines.push('1. COMMITTING them if they are part of the solution (git add + git commit + git push)');
|
|
152
|
+
feedbackLines.push('2. REVERTING them if they are not needed (git checkout -- <file> or git clean -fd)');
|
|
153
|
+
feedbackLines.push('');
|
|
154
|
+
feedbackLines.push('DO NOT leave uncommitted changes behind. The session will auto-restart until all changes are resolved.');
|
|
155
|
+
return feedbackLines;
|
|
156
|
+
} else {
|
|
157
|
+
await log('✅ No uncommitted changes found');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch (gitError) {
|
|
161
|
+
const sentryLib = await import('./sentry.lib.mjs');
|
|
162
|
+
const { reportError } = sentryLib;
|
|
163
|
+
reportError(gitError, {
|
|
164
|
+
context: 'check_uncommitted_changes',
|
|
165
|
+
tempDir,
|
|
166
|
+
operation: 'git_status'
|
|
167
|
+
});
|
|
168
|
+
await log(`⚠️ Warning: Could not check git status: ${gitError.message}`, { level: 'warning' });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function checkForkActions({
|
|
175
|
+
argv,
|
|
176
|
+
forkedRepo,
|
|
177
|
+
branchName,
|
|
178
|
+
log,
|
|
179
|
+
formatAligned,
|
|
180
|
+
$
|
|
181
|
+
}) {
|
|
182
|
+
// Check for GitHub Actions on fork repository if applicable
|
|
183
|
+
let forkActionsUrl = null;
|
|
184
|
+
if (argv.fork && forkedRepo) {
|
|
185
|
+
try {
|
|
186
|
+
// Get fork owner from forkedRepo (format: owner/repo)
|
|
187
|
+
const forkOwner = forkedRepo.split('/')[0];
|
|
188
|
+
const forkRepo = forkedRepo.split('/')[1];
|
|
189
|
+
|
|
190
|
+
// Check if workflows directory exists in the fork
|
|
191
|
+
const workflowsResult = await $`gh api repos/${forkOwner}/${forkRepo}/contents/.github/workflows --jq '.[].name' 2>/dev/null`;
|
|
192
|
+
|
|
193
|
+
if (workflowsResult.code === 0) {
|
|
194
|
+
const workflows = workflowsResult.stdout.toString().trim();
|
|
195
|
+
if (workflows) {
|
|
196
|
+
// Workflows exist, construct the actions URL for the branch
|
|
197
|
+
forkActionsUrl = `https://github.com/${forkOwner}/${forkRepo}/actions?query=branch%3A${encodeURIComponent(branchName)}`;
|
|
198
|
+
await log(`${formatAligned('📦', 'Fork workflows detected:', forkActionsUrl)}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
// No workflows or error checking - that's fine, forkActionsUrl stays null
|
|
203
|
+
if (argv.verbose) {
|
|
204
|
+
await log('No GitHub Actions workflows found on fork', { verbose: true });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return forkActionsUrl;
|
|
210
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository setup functionality for solve.mjs
|
|
3
|
+
* Handles repository cloning, forking, and remote setup
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export async function setupRepositoryAndClone({
|
|
7
|
+
argv,
|
|
8
|
+
owner,
|
|
9
|
+
repo,
|
|
10
|
+
forkOwner,
|
|
11
|
+
tempDir,
|
|
12
|
+
isContinueMode,
|
|
13
|
+
issueUrl,
|
|
14
|
+
log,
|
|
15
|
+
$
|
|
16
|
+
}) {
|
|
17
|
+
// Set up repository and handle forking
|
|
18
|
+
const { repoToClone, forkedRepo, upstreamRemote, prForkOwner } = await setupRepository(argv, owner, repo, forkOwner, issueUrl);
|
|
19
|
+
|
|
20
|
+
// Clone repository and set up remotes
|
|
21
|
+
await cloneRepository(repoToClone, tempDir, argv, owner, repo);
|
|
22
|
+
// Set up upstream remote and sync fork if needed
|
|
23
|
+
await setupUpstreamAndSync(tempDir, forkedRepo, upstreamRemote, owner, repo, argv);
|
|
24
|
+
// Set up pr-fork remote if we're continuing someone else's fork PR with --fork flag
|
|
25
|
+
const prForkRemote = await setupPrForkRemote(tempDir, argv, prForkOwner, repo, isContinueMode, owner);
|
|
26
|
+
|
|
27
|
+
// Set up git authentication using gh
|
|
28
|
+
const authSetupResult = await $({ cwd: tempDir })`gh auth setup-git 2>&1`;
|
|
29
|
+
if (authSetupResult.code !== 0) {
|
|
30
|
+
await log('Note: gh auth setup-git had issues, continuing anyway\n');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { repoToClone, forkedRepo, upstreamRemote, prForkRemote, prForkOwner };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function setupRepository(argv, owner, repo, forkOwner, issueUrl) {
|
|
37
|
+
const repository = await import('./solve.repository.lib.mjs');
|
|
38
|
+
const { setupRepository: setupRepoFn } = repository;
|
|
39
|
+
return await setupRepoFn(argv, owner, repo, forkOwner, issueUrl);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function cloneRepository(repoToClone, tempDir, argv, owner, repo) {
|
|
43
|
+
const repository = await import('./solve.repository.lib.mjs');
|
|
44
|
+
const { cloneRepository: cloneRepoFn } = repository;
|
|
45
|
+
return await cloneRepoFn(repoToClone, tempDir, argv, owner, repo);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function setupUpstreamAndSync(tempDir, forkedRepo, upstreamRemote, owner, repo, argv) {
|
|
49
|
+
const repository = await import('./solve.repository.lib.mjs');
|
|
50
|
+
const { setupUpstreamAndSync: setupUpstreamFn } = repository;
|
|
51
|
+
return await setupUpstreamFn(tempDir, forkedRepo, upstreamRemote, owner, repo, argv);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function setupPrForkRemote(tempDir, argv, prForkOwner, repo, isContinueMode, owner) {
|
|
55
|
+
const repository = await import('./solve.repository.lib.mjs');
|
|
56
|
+
const { setupPrForkRemote: setupPrForkFn } = repository;
|
|
57
|
+
return await setupPrForkFn(tempDir, argv, prForkOwner, repo, isContinueMode, owner);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function verifyDefaultBranchAndStatus({
|
|
61
|
+
tempDir,
|
|
62
|
+
log,
|
|
63
|
+
formatAligned,
|
|
64
|
+
$
|
|
65
|
+
}) {
|
|
66
|
+
// Verify we're on the default branch and get its name
|
|
67
|
+
const defaultBranchResult = await $({ cwd: tempDir })`git branch --show-current`;
|
|
68
|
+
|
|
69
|
+
if (defaultBranchResult.code !== 0) {
|
|
70
|
+
await log('Error: Failed to get current branch');
|
|
71
|
+
await log(defaultBranchResult.stderr ? defaultBranchResult.stderr.toString() : 'Unknown error');
|
|
72
|
+
throw new Error('Failed to get current branch');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const defaultBranch = defaultBranchResult.stdout.toString().trim();
|
|
76
|
+
if (!defaultBranch) {
|
|
77
|
+
await log('');
|
|
78
|
+
await log(`${formatAligned('❌', 'DEFAULT BRANCH DETECTION FAILED', '')}`, { level: 'error' });
|
|
79
|
+
await log('');
|
|
80
|
+
await log(' 🔍 What happened:');
|
|
81
|
+
await log(' Unable to determine the repository\'s default branch.');
|
|
82
|
+
await log('');
|
|
83
|
+
await log(' 💡 This might mean:');
|
|
84
|
+
await log(' • Repository is empty (no commits)');
|
|
85
|
+
await log(' • Unusual repository configuration');
|
|
86
|
+
await log(' • Git command issues');
|
|
87
|
+
await log('');
|
|
88
|
+
await log(' 🔧 How to fix:');
|
|
89
|
+
await log(' 1. Check repository status');
|
|
90
|
+
await log(` 2. Verify locally: cd ${tempDir} && git branch`);
|
|
91
|
+
await log(` 3. Check remote: cd ${tempDir} && git branch -r`);
|
|
92
|
+
await log('');
|
|
93
|
+
throw new Error('Default branch detection failed');
|
|
94
|
+
}
|
|
95
|
+
await log(`\n${formatAligned('📌', 'Default branch:', defaultBranch)}`);
|
|
96
|
+
|
|
97
|
+
// Ensure we're on a clean default branch
|
|
98
|
+
const statusResult = await $({ cwd: tempDir })`git status --porcelain`;
|
|
99
|
+
if (statusResult.code !== 0) {
|
|
100
|
+
await log('Error: Failed to check git status');
|
|
101
|
+
await log(statusResult.stderr ? statusResult.stderr.toString() : 'Unknown error');
|
|
102
|
+
throw new Error('Failed to check git status');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Note: Empty output means clean working directory
|
|
106
|
+
const statusOutput = statusResult.stdout.toString().trim();
|
|
107
|
+
if (statusOutput) {
|
|
108
|
+
await log('Error: Repository has uncommitted changes after clone');
|
|
109
|
+
await log(`Status output: ${statusOutput}`);
|
|
110
|
+
throw new Error('Repository has uncommitted changes after clone');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return defaultBranch;
|
|
114
|
+
}
|