@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,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
|
+
};
|