@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,176 @@
1
+ if (typeof use === 'undefined') {
2
+ globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
3
+ }
4
+
5
+ const linoModule = await use('links-notation');
6
+ const LinoParser = linoModule.Parser || linoModule.default?.Parser;
7
+
8
+ const fs = await import('fs');
9
+ const path = await import('path');
10
+ const os = await import('os');
11
+
12
+ export class LinksNotationManager {
13
+ constructor() {
14
+ this.parser = new LinoParser();
15
+ this.cacheDir = path.join(os.homedir(), '.hive-mind');
16
+ }
17
+
18
+ parse(input) {
19
+ if (!input) return [];
20
+
21
+ const parsed = this.parser.parse(input);
22
+
23
+ if (parsed && parsed.length > 0) {
24
+ const link = parsed[0];
25
+ const values = [];
26
+
27
+ if (link.values && link.values.length > 0) {
28
+ for (const value of link.values) {
29
+ const val = value.id || value;
30
+ values.push(val);
31
+ }
32
+ } else if (link.id) {
33
+ values.push(link.id);
34
+ }
35
+
36
+ return values;
37
+ }
38
+
39
+ return [];
40
+ }
41
+
42
+ parseNumericIds(input) {
43
+ if (!input) return [];
44
+
45
+ const parsed = this.parser.parse(input);
46
+
47
+ if (parsed && parsed.length > 0) {
48
+ const link = parsed[0];
49
+ const ids = [];
50
+
51
+ if (link.values && link.values.length > 0) {
52
+ for (const value of link.values) {
53
+ const num = parseInt(value.id || value);
54
+ if (!isNaN(num)) {
55
+ ids.push(num);
56
+ }
57
+ }
58
+ } else if (link.id) {
59
+ const nums = link.id.match(/\d+/g);
60
+ if (nums) {
61
+ ids.push(...nums.map(n => parseInt(n)).filter(n => !isNaN(n)));
62
+ }
63
+ }
64
+
65
+ return ids;
66
+ }
67
+
68
+ return [];
69
+ }
70
+
71
+ parseStringValues(input) {
72
+ if (!input) return [];
73
+
74
+ const parsed = this.parser.parse(input);
75
+
76
+ if (parsed && parsed.length > 0) {
77
+ const link = parsed[0];
78
+ const links = [];
79
+
80
+ if (link.values && link.values.length > 0) {
81
+ for (const value of link.values) {
82
+ const linkStr = value.id || value;
83
+ if (typeof linkStr === 'string') {
84
+ links.push(linkStr);
85
+ }
86
+ }
87
+ } else if (link.id) {
88
+ if (typeof link.id === 'string') {
89
+ links.push(link.id);
90
+ }
91
+ }
92
+
93
+ return links;
94
+ }
95
+
96
+ return [];
97
+ }
98
+
99
+ format(values) {
100
+ if (!values || values.length === 0) return '()';
101
+
102
+ const formattedValues = values.map(value => ` ${value}`).join('\n');
103
+ return `(\n${formattedValues}\n)`;
104
+ }
105
+
106
+ async ensureCacheDir() {
107
+ try {
108
+ await fs.promises.access(this.cacheDir);
109
+ return false;
110
+ } catch {
111
+ await fs.promises.mkdir(this.cacheDir, { recursive: true });
112
+ return true;
113
+ }
114
+ }
115
+
116
+ async saveToCache(filename, values) {
117
+ await this.ensureCacheDir();
118
+ const cacheFile = path.join(this.cacheDir, filename);
119
+ const linksNotation = this.format(values);
120
+ await fs.promises.writeFile(cacheFile, linksNotation);
121
+ return cacheFile;
122
+ }
123
+
124
+ async loadFromCache(filename) {
125
+ const cacheFile = path.join(this.cacheDir, filename);
126
+
127
+ try {
128
+ await fs.promises.access(cacheFile);
129
+ } catch {
130
+ return null;
131
+ }
132
+
133
+ const content = await fs.promises.readFile(cacheFile, 'utf8');
134
+ return {
135
+ raw: content,
136
+ parsed: this.parse(content),
137
+ numericIds: this.parseNumericIds(content),
138
+ stringValues: this.parseStringValues(content),
139
+ file: cacheFile
140
+ };
141
+ }
142
+
143
+ async cacheExists(filename) {
144
+ const cacheFile = path.join(this.cacheDir, filename);
145
+ try {
146
+ await fs.promises.access(cacheFile);
147
+ return true;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ getCachePath(filename) {
154
+ return path.join(this.cacheDir, filename);
155
+ }
156
+
157
+ async requireCache(filename, errorMessage) {
158
+ const cache = await this.loadFromCache(filename);
159
+
160
+ if (!cache) {
161
+ const cacheFile = this.getCachePath(filename);
162
+ console.error(`❌ ${errorMessage || `Cache file not found: ${cacheFile}`}`);
163
+ console.log('💡 Run the appropriate script first to create the cache file');
164
+ process.exit(1);
165
+ }
166
+
167
+ console.log(`📂 Using cached data from: ${cache.file}\n`);
168
+ return cache;
169
+ }
170
+ }
171
+
172
+ export const CACHE_FILES = {
173
+ TELEGRAM_CHATS: 'telegram-chats.lino'
174
+ };
175
+
176
+ export const lino = new LinksNotationManager();
@@ -0,0 +1,324 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Local CI Checks Module
5
+ * Detects and runs local CI checks before pushing to avoid CI failures
6
+ */
7
+
8
+ if (typeof globalThis.use === 'undefined') {
9
+ globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
10
+ }
11
+
12
+ const { $ } = await use('command-stream');
13
+ const fs = (await use('fs')).promises;
14
+ const path = (await use('path')).default;
15
+
16
+ /**
17
+ * Detect which CI tools are configured in the project
18
+ * @param {string} workDir - Working directory
19
+ * @returns {Promise<Object>} Detected CI tools and their configs
20
+ */
21
+ export async function detectCITools(workDir) {
22
+ const tools = {
23
+ python: {
24
+ ruff: false,
25
+ mypy: false,
26
+ pytest: false,
27
+ nox: false,
28
+ black: false,
29
+ flake8: false
30
+ },
31
+ javascript: {
32
+ eslint: false,
33
+ prettier: false,
34
+ jest: false,
35
+ vitest: false
36
+ },
37
+ rust: {
38
+ rustfmt: false,
39
+ clippy: false,
40
+ cargoTest: false
41
+ },
42
+ general: {
43
+ preCommit: false
44
+ }
45
+ };
46
+
47
+ try {
48
+ // Check for Python tools
49
+ const pyprojectPath = path.join(workDir, 'pyproject.toml');
50
+ const ruffConfigPath = path.join(workDir, 'ruff.toml');
51
+ const mypyConfigPath = path.join(workDir, 'mypy.ini');
52
+ const noxfilePath = path.join(workDir, 'noxfile.py');
53
+ const flake8ConfigPath = path.join(workDir, '.flake8');
54
+
55
+ // Check ruff
56
+ try {
57
+ await fs.access(ruffConfigPath);
58
+ tools.python.ruff = true;
59
+ } catch {
60
+ // File doesn't exist, ruff not configured
61
+ }
62
+
63
+ try {
64
+ const pyproject = await fs.readFile(pyprojectPath, 'utf-8');
65
+ if (pyproject.includes('[tool.ruff]')) tools.python.ruff = true;
66
+ if (pyproject.includes('[tool.mypy]')) tools.python.mypy = true;
67
+ if (pyproject.includes('[tool.black]')) tools.python.black = true;
68
+ if (pyproject.includes('[tool.pytest]')) tools.python.pytest = true;
69
+ } catch {
70
+ // File doesn't exist or can't be read, tools not configured in pyproject.toml
71
+ }
72
+
73
+ // Check mypy
74
+ try {
75
+ await fs.access(mypyConfigPath);
76
+ tools.python.mypy = true;
77
+ } catch {
78
+ // File doesn't exist, mypy not configured
79
+ }
80
+
81
+ // Check nox
82
+ try {
83
+ await fs.access(noxfilePath);
84
+ tools.python.nox = true;
85
+ } catch {
86
+ // File doesn't exist, nox not configured
87
+ }
88
+
89
+ // Check flake8
90
+ try {
91
+ await fs.access(flake8ConfigPath);
92
+ tools.python.flake8 = true;
93
+ } catch {
94
+ // File doesn't exist, flake8 not configured
95
+ }
96
+
97
+ // Check for JavaScript tools
98
+ const packageJsonPath = path.join(workDir, 'package.json');
99
+ try {
100
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
101
+
102
+ if (packageJson.devDependencies?.eslint || packageJson.dependencies?.eslint) {
103
+ tools.javascript.eslint = true;
104
+ }
105
+ if (packageJson.devDependencies?.prettier || packageJson.dependencies?.prettier) {
106
+ tools.javascript.prettier = true;
107
+ }
108
+ if (packageJson.devDependencies?.jest || packageJson.dependencies?.jest) {
109
+ tools.javascript.jest = true;
110
+ }
111
+ if (packageJson.devDependencies?.vitest || packageJson.dependencies?.vitest) {
112
+ tools.javascript.vitest = true;
113
+ }
114
+ } catch {
115
+ // File doesn't exist or can't be parsed, JavaScript tools not configured
116
+ }
117
+
118
+ // Check for Rust tools
119
+ const cargoTomlPath = path.join(workDir, 'Cargo.toml');
120
+ try {
121
+ await fs.access(cargoTomlPath);
122
+ tools.rust.rustfmt = true;
123
+ tools.rust.clippy = true;
124
+ tools.rust.cargoTest = true;
125
+ } catch {
126
+ // File doesn't exist, Rust tools not configured
127
+ }
128
+
129
+ // Check for pre-commit
130
+ const preCommitPath = path.join(workDir, '.pre-commit-config.yaml');
131
+ try {
132
+ await fs.access(preCommitPath);
133
+ tools.general.preCommit = true;
134
+ } catch {
135
+ // File doesn't exist, pre-commit not configured
136
+ }
137
+
138
+ } catch (err) {
139
+ console.error('Error detecting CI tools:', err.message);
140
+ }
141
+
142
+ return tools;
143
+ }
144
+
145
+ /**
146
+ * Run local CI checks
147
+ * @param {string} workDir - Working directory
148
+ * @param {Object} tools - Detected CI tools from detectCITools()
149
+ * @param {Object} options - Options for running checks
150
+ * @returns {Promise<Object>} Results of CI checks
151
+ */
152
+ export async function runLocalCIChecks(workDir, tools, options = {}) {
153
+ const results = {
154
+ success: true,
155
+ checks: [],
156
+ errors: []
157
+ };
158
+
159
+ const verbose = options.verbose || false;
160
+
161
+ // Run Python checks
162
+ if (tools.python.ruff) {
163
+ const result = await runCheck(workDir, 'ruff check .', 'Ruff linting', verbose);
164
+ results.checks.push(result);
165
+ if (!result.success) results.success = false;
166
+ }
167
+
168
+ if (tools.python.mypy) {
169
+ const result = await runCheck(workDir, 'mypy .', 'MyPy type checking', verbose);
170
+ results.checks.push(result);
171
+ if (!result.success) results.success = false;
172
+ }
173
+
174
+ if (tools.python.black) {
175
+ const result = await runCheck(workDir, 'black --check .', 'Black formatting', verbose);
176
+ results.checks.push(result);
177
+ if (!result.success) results.success = false;
178
+ }
179
+
180
+ if (tools.python.flake8) {
181
+ const result = await runCheck(workDir, 'flake8 .', 'Flake8 linting', verbose);
182
+ results.checks.push(result);
183
+ if (!result.success) results.success = false;
184
+ }
185
+
186
+ // Run JavaScript checks
187
+ if (tools.javascript.eslint) {
188
+ const result = await runCheck(workDir, 'npm run lint', 'ESLint', verbose);
189
+ results.checks.push(result);
190
+ if (!result.success) results.success = false;
191
+ }
192
+
193
+ if (tools.javascript.prettier) {
194
+ const result = await runCheck(workDir, 'npm run format:check', 'Prettier', verbose);
195
+ results.checks.push(result);
196
+ if (!result.success) results.success = false;
197
+ }
198
+
199
+ // Run Rust checks
200
+ if (tools.rust.rustfmt) {
201
+ const result = await runCheck(workDir, 'cargo fmt -- --check', 'Rustfmt', verbose);
202
+ results.checks.push(result);
203
+ if (!result.success) results.success = false;
204
+ }
205
+
206
+ if (tools.rust.clippy) {
207
+ const result = await runCheck(workDir, 'cargo clippy -- -D warnings', 'Clippy', verbose);
208
+ results.checks.push(result);
209
+ if (!result.success) results.success = false;
210
+ }
211
+
212
+ // Run tests only if requested
213
+ if (options.runTests) {
214
+ if (tools.python.pytest) {
215
+ const result = await runCheck(workDir, 'pytest', 'Pytest', verbose);
216
+ results.checks.push(result);
217
+ if (!result.success) results.success = false;
218
+ }
219
+
220
+ if (tools.python.nox) {
221
+ const result = await runCheck(workDir, 'nox', 'Nox', verbose);
222
+ results.checks.push(result);
223
+ if (!result.success) results.success = false;
224
+ }
225
+
226
+ if (tools.javascript.jest) {
227
+ const result = await runCheck(workDir, 'npm test', 'Jest', verbose);
228
+ results.checks.push(result);
229
+ if (!result.success) results.success = false;
230
+ }
231
+
232
+ if (tools.rust.cargoTest) {
233
+ const result = await runCheck(workDir, 'cargo test', 'Cargo test', verbose);
234
+ results.checks.push(result);
235
+ if (!result.success) results.success = false;
236
+ }
237
+ }
238
+
239
+ return results;
240
+ }
241
+
242
+ /**
243
+ * Run a single check command
244
+ * @param {string} workDir - Working directory
245
+ * @param {string} command - Command to run
246
+ * @param {string} name - Display name for the check
247
+ * @param {boolean} verbose - Whether to show verbose output
248
+ * @returns {Promise<Object>} Check result
249
+ */
250
+ async function runCheck(workDir, command, name, verbose) {
251
+ const result = {
252
+ name,
253
+ command,
254
+ success: false,
255
+ output: '',
256
+ error: ''
257
+ };
258
+
259
+ try {
260
+ const checkResult = await $`cd ${workDir} && ${command}`.raw().trim();
261
+
262
+ result.success = checkResult.exitCode === 0;
263
+ result.output = checkResult.text;
264
+
265
+ if (verbose || !result.success) {
266
+ console.log(`\n${result.success ? '✅' : '❌'} ${name}:`);
267
+ if (result.output) {
268
+ console.log(result.output);
269
+ }
270
+ }
271
+ } catch (err) {
272
+ result.success = false;
273
+ result.error = err.message;
274
+
275
+ if (verbose) {
276
+ console.log(`\n❌ ${name}:`);
277
+ console.log(result.error);
278
+ }
279
+ }
280
+
281
+ return result;
282
+ }
283
+
284
+ /**
285
+ * Generate a summary report of CI check results
286
+ * @param {Object} results - Results from runLocalCIChecks()
287
+ * @returns {string} Formatted summary report
288
+ */
289
+ export function generateCICheckReport(results) {
290
+ const lines = [];
291
+
292
+ lines.push('\n=== Local CI Checks Summary ===\n');
293
+
294
+ if (results.checks.length === 0) {
295
+ lines.push('No CI checks were run.');
296
+ return lines.join('\n');
297
+ }
298
+
299
+ const passed = results.checks.filter(c => c.success).length;
300
+ const failed = results.checks.filter(c => !c.success).length;
301
+
302
+ lines.push(`Total checks: ${results.checks.length}`);
303
+ lines.push(`Passed: ${passed}`);
304
+ lines.push(`Failed: ${failed}`);
305
+ lines.push('');
306
+
307
+ if (failed > 0) {
308
+ lines.push('Failed checks:');
309
+ results.checks.filter(c => !c.success).forEach(check => {
310
+ lines.push(` ❌ ${check.name}`);
311
+ if (check.output) {
312
+ lines.push(` ${check.output.split('\n')[0]}`);
313
+ }
314
+ });
315
+ }
316
+
317
+ if (results.success) {
318
+ lines.push('\n✅ All CI checks passed! Safe to commit and push.');
319
+ } else {
320
+ lines.push('\n❌ Some CI checks failed. Please fix the issues before committing.');
321
+ }
322
+
323
+ return lines.join('\n');
324
+ }