@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +24 -0
  3. package/README.md +769 -0
  4. package/package.json +58 -0
  5. package/src/agent.lib.mjs +705 -0
  6. package/src/agent.prompts.lib.mjs +196 -0
  7. package/src/buildUserMention.lib.mjs +71 -0
  8. package/src/claude-limits.lib.mjs +389 -0
  9. package/src/claude.lib.mjs +1445 -0
  10. package/src/claude.prompts.lib.mjs +203 -0
  11. package/src/codex.lib.mjs +552 -0
  12. package/src/codex.prompts.lib.mjs +194 -0
  13. package/src/config.lib.mjs +207 -0
  14. package/src/contributing-guidelines.lib.mjs +268 -0
  15. package/src/exit-handler.lib.mjs +205 -0
  16. package/src/git.lib.mjs +145 -0
  17. package/src/github-issue-creator.lib.mjs +246 -0
  18. package/src/github-linking.lib.mjs +152 -0
  19. package/src/github.batch.lib.mjs +272 -0
  20. package/src/github.graphql.lib.mjs +258 -0
  21. package/src/github.lib.mjs +1479 -0
  22. package/src/hive.config.lib.mjs +254 -0
  23. package/src/hive.mjs +1500 -0
  24. package/src/instrument.mjs +191 -0
  25. package/src/interactive-mode.lib.mjs +1000 -0
  26. package/src/lenv-reader.lib.mjs +206 -0
  27. package/src/lib.mjs +490 -0
  28. package/src/lino.lib.mjs +176 -0
  29. package/src/local-ci-checks.lib.mjs +324 -0
  30. package/src/memory-check.mjs +419 -0
  31. package/src/model-mapping.lib.mjs +145 -0
  32. package/src/model-validation.lib.mjs +278 -0
  33. package/src/opencode.lib.mjs +479 -0
  34. package/src/opencode.prompts.lib.mjs +194 -0
  35. package/src/protect-branch.mjs +159 -0
  36. package/src/review.mjs +433 -0
  37. package/src/reviewers-hive.mjs +643 -0
  38. package/src/sentry.lib.mjs +284 -0
  39. package/src/solve.auto-continue.lib.mjs +568 -0
  40. package/src/solve.auto-pr.lib.mjs +1374 -0
  41. package/src/solve.branch-errors.lib.mjs +341 -0
  42. package/src/solve.branch.lib.mjs +230 -0
  43. package/src/solve.config.lib.mjs +342 -0
  44. package/src/solve.error-handlers.lib.mjs +256 -0
  45. package/src/solve.execution.lib.mjs +291 -0
  46. package/src/solve.feedback.lib.mjs +436 -0
  47. package/src/solve.mjs +1128 -0
  48. package/src/solve.preparation.lib.mjs +210 -0
  49. package/src/solve.repo-setup.lib.mjs +114 -0
  50. package/src/solve.repository.lib.mjs +961 -0
  51. package/src/solve.results.lib.mjs +558 -0
  52. package/src/solve.session.lib.mjs +135 -0
  53. package/src/solve.validation.lib.mjs +325 -0
  54. package/src/solve.watch.lib.mjs +572 -0
  55. package/src/start-screen.mjs +324 -0
  56. package/src/task.mjs +308 -0
  57. package/src/telegram-bot.mjs +1481 -0
  58. package/src/telegram-markdown.lib.mjs +64 -0
  59. package/src/usage-limit.lib.mjs +218 -0
  60. package/src/version.lib.mjs +41 -0
  61. package/src/youtrack/solve.youtrack.lib.mjs +116 -0
  62. package/src/youtrack/youtrack-sync.mjs +219 -0
  63. 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
+ }