@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,278 @@
1
+ #!/usr/bin/env node
2
+ // Model validation library for hive-mind
3
+ // Provides model name validation with exact matching and fuzzy suggestions
4
+
5
+ // Check if use is already defined (when imported from solve.mjs)
6
+ // If not, fetch it (when running standalone)
7
+ if (typeof globalThis.use === 'undefined') {
8
+ globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
9
+ }
10
+
11
+ import { log } from './lib.mjs';
12
+
13
+ // Available models for each tool
14
+ // These are the "known good" model names that we accept
15
+ export const CLAUDE_MODELS = {
16
+ // Short aliases
17
+ 'sonnet': 'claude-sonnet-4-5-20250929',
18
+ 'opus': 'claude-opus-4-5-20251101',
19
+ 'haiku': 'claude-haiku-4-5-20251001',
20
+ 'haiku-3-5': 'claude-3-5-haiku-20241022',
21
+ 'haiku-3': 'claude-3-haiku-20240307',
22
+ // Full model IDs (also valid inputs)
23
+ 'claude-sonnet-4-5-20250929': 'claude-sonnet-4-5-20250929',
24
+ 'claude-opus-4-5-20251101': 'claude-opus-4-5-20251101',
25
+ 'claude-haiku-4-5-20251001': 'claude-haiku-4-5-20251001',
26
+ 'claude-3-5-haiku-20241022': 'claude-3-5-haiku-20241022',
27
+ 'claude-3-haiku-20240307': 'claude-3-haiku-20240307',
28
+ };
29
+
30
+ export const OPENCODE_MODELS = {
31
+ 'gpt4': 'openai/gpt-4',
32
+ 'gpt4o': 'openai/gpt-4o',
33
+ 'claude': 'anthropic/claude-3-5-sonnet',
34
+ 'sonnet': 'anthropic/claude-3-5-sonnet',
35
+ 'opus': 'anthropic/claude-3-opus',
36
+ 'gemini': 'google/gemini-pro',
37
+ 'grok': 'opencode/grok-code',
38
+ 'grok-code': 'opencode/grok-code',
39
+ 'grok-code-fast-1': 'opencode/grok-code',
40
+ // Full model IDs
41
+ 'openai/gpt-4': 'openai/gpt-4',
42
+ 'openai/gpt-4o': 'openai/gpt-4o',
43
+ 'anthropic/claude-3-5-sonnet': 'anthropic/claude-3-5-sonnet',
44
+ 'anthropic/claude-3-opus': 'anthropic/claude-3-opus',
45
+ 'google/gemini-pro': 'google/gemini-pro',
46
+ 'opencode/grok-code': 'opencode/grok-code',
47
+ };
48
+
49
+ export const CODEX_MODELS = {
50
+ 'gpt5': 'gpt-5',
51
+ 'gpt-5': 'gpt-5',
52
+ 'gpt5-codex': 'gpt-5-codex',
53
+ 'gpt-5-codex': 'gpt-5-codex',
54
+ 'o3': 'o3',
55
+ 'o3-mini': 'o3-mini',
56
+ 'gpt4': 'gpt-4',
57
+ 'gpt4o': 'gpt-4o',
58
+ 'claude': 'claude-3-5-sonnet',
59
+ 'sonnet': 'claude-3-5-sonnet',
60
+ 'opus': 'claude-3-opus',
61
+ // Full model IDs
62
+ 'gpt-4': 'gpt-4',
63
+ 'gpt-4o': 'gpt-4o',
64
+ 'claude-3-5-sonnet': 'claude-3-5-sonnet',
65
+ 'claude-3-opus': 'claude-3-opus',
66
+ };
67
+
68
+ export const AGENT_MODELS = {
69
+ // Free models (via OpenCode)
70
+ 'grok': 'opencode/grok-code',
71
+ 'grok-code': 'opencode/grok-code',
72
+ 'grok-code-fast-1': 'opencode/grok-code',
73
+ 'big-pickle': 'opencode/big-pickle',
74
+ 'gpt-5-nano': 'openai/gpt-5-nano',
75
+ // Premium models (requires OpenCode Zen subscription)
76
+ 'sonnet': 'anthropic/claude-3-5-sonnet',
77
+ 'haiku': 'anthropic/claude-3-5-haiku',
78
+ 'opus': 'anthropic/claude-3-opus',
79
+ 'gemini-3-pro': 'google/gemini-3-pro',
80
+ // Full model IDs
81
+ 'opencode/grok-code': 'opencode/grok-code',
82
+ 'opencode/big-pickle': 'opencode/big-pickle',
83
+ 'openai/gpt-5-nano': 'openai/gpt-5-nano',
84
+ 'anthropic/claude-3-5-sonnet': 'anthropic/claude-3-5-sonnet',
85
+ 'anthropic/claude-3-5-haiku': 'anthropic/claude-3-5-haiku',
86
+ 'anthropic/claude-3-opus': 'anthropic/claude-3-opus',
87
+ 'google/gemini-3-pro': 'google/gemini-3-pro',
88
+ };
89
+
90
+ /**
91
+ * Get the model map for a given tool
92
+ * @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent')
93
+ * @returns {Object} The model mapping for the tool
94
+ */
95
+ export const getModelMapForTool = (tool) => {
96
+ switch (tool) {
97
+ case 'opencode':
98
+ return OPENCODE_MODELS;
99
+ case 'codex':
100
+ return CODEX_MODELS;
101
+ case 'agent':
102
+ return AGENT_MODELS;
103
+ case 'claude':
104
+ default:
105
+ return CLAUDE_MODELS;
106
+ }
107
+ };
108
+
109
+ /**
110
+ * Get the list of available model names for a tool (for display in help/error messages)
111
+ * @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent')
112
+ * @returns {string[]} Array of available model short names
113
+ */
114
+ export const getAvailableModelNames = (tool) => {
115
+ const modelMap = getModelMapForTool(tool);
116
+ // Get unique short names (aliases) - exclude full model IDs that contain '/' or long claude- prefixed IDs
117
+ const aliases = Object.keys(modelMap).filter(key => {
118
+ // Keep short aliases only - exclude:
119
+ // - Full model IDs with slashes (e.g., 'openai/gpt-4')
120
+ // - Long claude-prefixed model IDs (e.g., 'claude-sonnet-4-5-20250929')
121
+ // - Full gpt- prefixed IDs with version numbers (e.g., 'gpt-4', 'gpt-4o')
122
+ // But keep short names like 'o3', 'o3-mini', 'gpt5', etc.
123
+ if (key.includes('/')) return false;
124
+ if (key.match(/^claude-.*-\d{8}$/)) return false; // Full claude model IDs with date
125
+ if (key.match(/^gpt-\d+/)) return false; // Full gpt-N model IDs
126
+ return true;
127
+ });
128
+ return [...new Set(aliases)];
129
+ };
130
+
131
+ /**
132
+ * Calculate Levenshtein distance between two strings (case-insensitive)
133
+ * @param {string} a - First string
134
+ * @param {string} b - Second string
135
+ * @returns {number} The edit distance between the strings
136
+ */
137
+ export const levenshteinDistance = (a, b) => {
138
+ const aLower = a.toLowerCase();
139
+ const bLower = b.toLowerCase();
140
+
141
+ if (aLower === bLower) return 0;
142
+ if (aLower.length === 0) return bLower.length;
143
+ if (bLower.length === 0) return aLower.length;
144
+
145
+ const matrix = [];
146
+
147
+ // Initialize first column
148
+ for (let i = 0; i <= bLower.length; i++) {
149
+ matrix[i] = [i];
150
+ }
151
+
152
+ // Initialize first row
153
+ for (let j = 0; j <= aLower.length; j++) {
154
+ matrix[0][j] = j;
155
+ }
156
+
157
+ // Fill in the rest of the matrix
158
+ for (let i = 1; i <= bLower.length; i++) {
159
+ for (let j = 1; j <= aLower.length; j++) {
160
+ if (bLower.charAt(i - 1) === aLower.charAt(j - 1)) {
161
+ matrix[i][j] = matrix[i - 1][j - 1];
162
+ } else {
163
+ matrix[i][j] = Math.min(
164
+ matrix[i - 1][j - 1] + 1, // substitution
165
+ matrix[i][j - 1] + 1, // insertion
166
+ matrix[i - 1][j] + 1 // deletion
167
+ );
168
+ }
169
+ }
170
+ }
171
+
172
+ return matrix[bLower.length][aLower.length];
173
+ };
174
+
175
+ /**
176
+ * Find the closest matching model names using fuzzy matching
177
+ * @param {string} input - The user-provided model name
178
+ * @param {string[]} validModels - Array of valid model names
179
+ * @param {number} maxSuggestions - Maximum number of suggestions to return
180
+ * @param {number} maxDistance - Maximum Levenshtein distance to consider
181
+ * @returns {string[]} Array of suggested model names
182
+ */
183
+ export const findSimilarModels = (input, validModels, maxSuggestions = 3, maxDistance = 3) => {
184
+ const suggestions = validModels
185
+ .map(model => ({
186
+ model,
187
+ distance: levenshteinDistance(input, model)
188
+ }))
189
+ .filter(({ distance }) => distance <= maxDistance)
190
+ .sort((a, b) => a.distance - b.distance)
191
+ .slice(0, maxSuggestions)
192
+ .map(({ model }) => model);
193
+
194
+ return suggestions;
195
+ };
196
+
197
+ /**
198
+ * Validate a model name against the available models for a tool
199
+ * @param {string} model - The model name to validate
200
+ * @param {string} tool - The tool name ('claude', 'opencode', 'codex')
201
+ * @returns {{ valid: boolean, message?: string, suggestions?: string[] }}
202
+ */
203
+ export const validateModelName = (model, tool = 'claude') => {
204
+ if (!model || typeof model !== 'string') {
205
+ return {
206
+ valid: false,
207
+ message: 'Model name is required',
208
+ suggestions: []
209
+ };
210
+ }
211
+
212
+ const modelMap = getModelMapForTool(tool);
213
+ const availableNames = Object.keys(modelMap);
214
+
215
+ // Case-insensitive exact match
216
+ const normalizedModel = model.toLowerCase();
217
+ const matchedKey = availableNames.find(key => key.toLowerCase() === normalizedModel);
218
+
219
+ if (matchedKey) {
220
+ return {
221
+ valid: true,
222
+ mappedModel: modelMap[matchedKey]
223
+ };
224
+ }
225
+
226
+ // Model not found - provide helpful error with suggestions
227
+ const shortNames = getAvailableModelNames(tool);
228
+ const suggestions = findSimilarModels(model, shortNames);
229
+
230
+ let message = `Unrecognized model: "${model}"`;
231
+
232
+ if (suggestions.length > 0) {
233
+ message += `\n Did you mean: ${suggestions.map(s => `"${s}"`).join(', ')}?`;
234
+ }
235
+
236
+ message += `\n Available models for ${tool}: ${shortNames.join(', ')}`;
237
+
238
+ return {
239
+ valid: false,
240
+ message,
241
+ suggestions
242
+ };
243
+ };
244
+
245
+ /**
246
+ * Validate model name and exit with error if invalid
247
+ * This is the main entry point for model validation in solve.mjs, hive.mjs, etc.
248
+ * @param {string} model - The model name to validate
249
+ * @param {string} tool - The tool name ('claude', 'opencode', 'codex')
250
+ * @param {Function} exitFn - Function to call for exiting (default: process.exit)
251
+ * @returns {Promise<boolean>} True if valid, exits process if invalid
252
+ */
253
+ export const validateAndExitOnInvalidModel = async (model, tool = 'claude', exitFn = null) => {
254
+ const result = validateModelName(model, tool);
255
+
256
+ if (!result.valid) {
257
+ await log(`❌ ${result.message}`, { level: 'error' });
258
+
259
+ if (exitFn) {
260
+ await exitFn(1, 'Invalid model name');
261
+ } else {
262
+ process.exit(1);
263
+ }
264
+ return false;
265
+ }
266
+
267
+ return true;
268
+ };
269
+
270
+ /**
271
+ * Format the list of available models for help text
272
+ * @param {string} tool - The tool name
273
+ * @returns {string} Formatted list of available models
274
+ */
275
+ export const formatAvailableModelsForHelp = (tool = 'claude') => {
276
+ const names = getAvailableModelNames(tool);
277
+ return names.join(', ');
278
+ };