@link-assistant/hive-mind 1.2.3 → 1.2.5

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 CHANGED
@@ -1,5 +1,81 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.2.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 65ee214: fix: Detect malformed flag patterns like "-- model" (Issue #1092)
8
+
9
+ Added `detectMalformedFlags()` function that catches malformed command-line options and provides helpful error messages:
10
+ - Detects "-- option" (space after --) and suggests "--option"
11
+ - Detects "-option" (single dash for long option) and suggests "--option"
12
+ - Detects "---option" (triple dash) and suggests "--option"
13
+ - Integrated into both Telegram bot and CLI argument parsing
14
+ - Added 23 comprehensive unit tests
15
+
16
+ - af950c8: fix(hive): require closing keywords for PR detection
17
+
18
+ The `/hive` command was incorrectly skipping issues by reporting they had
19
+ PRs when those PRs only mentioned the issues without actually solving them.
20
+
21
+ **Root cause**: The `batchCheckPullRequestsForIssues` function used GitHub's
22
+ `CROSS_REFERENCED_EVENT` timeline items, which are created whenever a PR
23
+ body/title/commit mentions an issue number - regardless of whether the PR
24
+ actually solves the issue.
25
+
26
+ **Example**: PR #369 in VisageDvachevsky/StoryGraph is an audit PR that
27
+ created 28 new issues (#370-#397) and listed them in a table. This caused
28
+ GitHub to create cross-reference events linking that PR to all 28 issues,
29
+ but PR #369 only actually fixes #368.
30
+
31
+ **Solution**:
32
+ - Add `prClosesIssue()` function to detect GitHub closing keywords
33
+ (fixes, closes, resolves - case-insensitive)
34
+ - Update GraphQL query to include PR body text
35
+ - Only count PRs that contain "fixes #N", "closes #N", or "resolves #N"
36
+ for the specific issue number
37
+ - Add verbose logging when PRs are skipped for only mentioning issues
38
+
39
+ This aligns with GitHub's own auto-close behavior where only specific
40
+ keywords trigger issue closure when a PR is merged.
41
+
42
+ Fixes #1094
43
+
44
+ - 0d997ac: fix(telegram-bot): stop solve queue on SIGINT/SIGTERM for clean exit
45
+
46
+ The telegram bot was hanging after pressing Ctrl+C because the SolveQueue
47
+ consumer loop kept running with active timers that prevented the Node.js
48
+ event loop from emptying.
49
+ - **Root cause identified**: The SIGINT/SIGTERM handlers only called
50
+ `bot.stop()` (Telegraf) but did not stop the SolveQueue, whose `sleep()`
51
+ timers kept the event loop alive.
52
+ - **Solution**: Added `solveQueue.stop()` call in both SIGINT and SIGTERM
53
+ handlers to stop the consumer loop before calling `bot.stop()`.
54
+ - **Added verbose logging**: When running with `--verbose`, the bot now
55
+ logs "Solve queue stopped" during shutdown.
56
+ - **Case study documentation**: Added detailed analysis in
57
+ `docs/case-studies/issue-1083/` with timeline, root cause investigation,
58
+ and evidence collection.
59
+
60
+ Fixes #1083
61
+
62
+ ## 1.2.4
63
+
64
+ ### Patch Changes
65
+
66
+ - 14ea4b6: Add validation for LINO configuration to detect invalid input
67
+ - Add validation in `lenv-reader.lib.mjs` to reject multiple values on the same line (e.g., `--option1 --option2`)
68
+ - Add validation to reject unrecognized characters in command-line options (e.g., `?`, `@`, `!`)
69
+ - Errors include clear messages showing the problematic value and instructions for correction
70
+ - Valid option characters: letters, numbers, hyphens, underscores, equals signs
71
+ - Add comprehensive unit tests for LINO parsing logic (`test-lino.mjs`)
72
+ - Add validation tests to lenv-reader test suite (`test-lenv-reader.mjs`)
73
+ - Add lino tests to CI/CD workflow
74
+
75
+ This approach helps users identify and correct configuration errors early, rather than silently dropping invalid options.
76
+
77
+ Fixes #1086
78
+
3
79
  ## 1.2.3
4
80
 
5
81
  ### Patch Changes
@@ -0,0 +1,72 @@
1
+ # Issue #1092 Analysis: Silent Ignoring of "-- model" (with space)
2
+
3
+ ## The Problem
4
+
5
+ When a user types `-- model` (with a space between the dashes) instead of `--model`,
6
+ the system silently ignores it and uses the default model instead of producing an error.
7
+
8
+ ## Why This Happens
9
+
10
+ ### Argument Parsing in Shell
11
+
12
+ When a shell receives the command:
13
+
14
+ ```bash
15
+ solve https://github.com/owner/repo/issues/1 -- model sonnet
16
+ ```
17
+
18
+ It parses this as:
19
+
20
+ - `https://github.com/owner/repo/issues/1` (positional arg)
21
+ - `-- model` (single string, NOT a flag)
22
+ - `sonnet` (positional arg)
23
+
24
+ The `"-- model"` string has a space in it, so it's not recognized as a flag by yargs.
25
+
26
+ ### Yargs Behavior with .strict()
27
+
28
+ Yargs `.strict()` mode only rejects:
29
+
30
+ 1. Unknown OPTIONS (flags that start with `-` or `--`)
31
+ 2. Unknown ARGUMENTS that look like flags
32
+
33
+ It does NOT reject:
34
+
35
+ - Positional arguments that look similar to flag names
36
+ - Strings like `"-- model"` (which are treated as regular positional arguments)
37
+
38
+ So when yargs sees `"-- model"` as a positional argument, it:
39
+
40
+ 1. Doesn't match it to `--model` option
41
+ 2. Doesn't throw an error because it's not in strict "option" format
42
+ 3. Silently ignores it
43
+ 4. Uses the default model value
44
+
45
+ ## The Flow
46
+
47
+ 1. User types: `solve <url> -- model sonnet`
48
+ 2. Shell parses as: `['<url>', '-- model', 'sonnet']`
49
+ 3. Yargs receives: `{ _: ['<url>', '-- model', 'sonnet'] }`
50
+ 4. Yargs strict mode checks: Is this an unknown OPTION? No.
51
+ 5. Yargs silently accepts the positional arguments
52
+ 6. Model defaults to 'sonnet'
53
+ 7. User sees no error, but their `--model` flag was ignored
54
+
55
+ ## Solution Approaches
56
+
57
+ 1. **Detect spaced flag patterns**: Check if any positional arguments match the pattern
58
+ `-- <option-name>` where option-name is a known flag
59
+
60
+ 2. **Validate positional arguments**: After parsing, check if the remaining positional
61
+ arguments look like mistyped flags (start with -- or -)
62
+
63
+ 3. **Improve error message**: When detecting such patterns, suggest the correct format
64
+
65
+ ## Key Code References
66
+
67
+ - `/src/solve.config.lib.mjs`: Contains yargs configuration and argument parsing
68
+ - `/src/option-suggestions.lib.mjs`: Already has logic for suggesting similar options
69
+ - `/src/model-validation.lib.mjs`: Validates model names
70
+
71
+ The current `.strict()` mode in solve.config.lib.mjs (line 316) only catches malformed
72
+ OPTIONS, not these kinds of subtle positional argument errors.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -11,6 +11,53 @@ if (typeof globalThis.use === 'undefined') {
11
11
  import { log, cleanErrorMessage } from './lib.mjs';
12
12
  import { githubLimits, timeouts } from './config.lib.mjs';
13
13
 
14
+ /**
15
+ * Check if a PR body/title indicates it fixes/closes/resolves a specific issue number
16
+ * GitHub auto-closes issues when PR body contains keywords like "fixes #123", "closes #123", "resolves #123"
17
+ * See: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue
18
+ * @param {string} text - PR body or title text
19
+ * @param {number} issueNumber - Issue number to check for
20
+ * @returns {boolean} True if the text contains a closing keyword for this issue
21
+ */
22
+ export function prClosesIssue(text, issueNumber) {
23
+ if (!text || typeof text !== 'string') {
24
+ return false;
25
+ }
26
+
27
+ // GitHub closing keywords (case-insensitive)
28
+ // Supports: fix, fixes, fixed, close, closes, closed, resolve, resolves, resolved
29
+ // Also supports variations with repository prefix like "fixes owner/repo#123"
30
+ const closingKeywords = ['fix', 'fixes', 'fixed', 'close', 'closes', 'closed', 'resolve', 'resolves', 'resolved'];
31
+
32
+ // Build regex pattern that matches any of the keywords followed by #N or repo#N
33
+ // Examples matched:
34
+ // - "fixes #123"
35
+ // - "Closes #123"
36
+ // - "RESOLVED #123"
37
+ // - "fixes owner/repo#123"
38
+ // - "fix: #123" (common commit style)
39
+ const issueNum = issueNumber.toString();
40
+
41
+ for (const keyword of closingKeywords) {
42
+ // Pattern: keyword + optional colon/space + optional repo prefix + # + issue number
43
+ // Must be followed by word boundary (not part of larger number)
44
+ const patterns = [
45
+ // Standard format: "fixes #123"
46
+ new RegExp(`\\b${keyword}\\s*:?\\s*#${issueNum}\\b`, 'i'),
47
+ // With repo prefix: "fixes owner/repo#123"
48
+ new RegExp(`\\b${keyword}\\s*:?\\s*[\\w.-]+/[\\w.-]+#${issueNum}\\b`, 'i'),
49
+ ];
50
+
51
+ for (const pattern of patterns) {
52
+ if (pattern.test(text)) {
53
+ return true;
54
+ }
55
+ }
56
+ }
57
+
58
+ return false;
59
+ }
60
+
14
61
  /**
15
62
  * Batch fetch pull request information for multiple issues using GraphQL
16
63
  * @param {string} owner - Repository owner
@@ -34,6 +81,8 @@ export async function batchCheckPullRequestsForIssues(owner, repo, issueNumbers)
34
81
  const batch = issueNumbers.slice(i, i + BATCH_SIZE);
35
82
 
36
83
  // Build GraphQL query for this batch
84
+ // Issue #1094: Include PR body to check for "fixes/closes/resolves #N" keywords
85
+ // This prevents false positives from PRs that only mention issues without solving them
37
86
  const query = `
38
87
  query GetPullRequestsForIssues {
39
88
  repository(owner: "${owner}", name: "${repo}") {
@@ -51,6 +100,7 @@ export async function batchCheckPullRequestsForIssues(owner, repo, issueNumbers)
51
100
  ... on PullRequest {
52
101
  number
53
102
  title
103
+ body
54
104
  state
55
105
  isDraft
56
106
  url
@@ -92,14 +142,26 @@ export async function batchCheckPullRequestsForIssues(owner, repo, issueNumbers)
92
142
  const linkedPRs = [];
93
143
 
94
144
  // Extract linked PRs from timeline items
145
+ // Issue #1094: Only count PRs that explicitly fix/close/resolve this issue
146
+ // This prevents false positives from PRs that only mention issues without solving them
95
147
  for (const item of issueData.timelineItems?.nodes || []) {
96
148
  if (item?.source && item.source.state === 'OPEN' && !item.source.isDraft) {
97
- linkedPRs.push({
98
- number: item.source.number,
99
- title: item.source.title,
100
- state: item.source.state,
101
- url: item.source.url,
102
- });
149
+ // Check if PR actually closes this issue (has "fixes #N", "closes #N", or "resolves #N")
150
+ const prBody = item.source.body || '';
151
+ const prTitle = item.source.title || '';
152
+ const closesThisIssue = prClosesIssue(prBody, issueNum) || prClosesIssue(prTitle, issueNum);
153
+
154
+ if (closesThisIssue) {
155
+ linkedPRs.push({
156
+ number: item.source.number,
157
+ title: item.source.title,
158
+ state: item.source.state,
159
+ url: item.source.url,
160
+ });
161
+ } else {
162
+ // Log that we're skipping a PR that only mentions the issue
163
+ await log(` ℹ️ PR #${item.source.number} mentions issue #${issueNum} but doesn't close it (no fixes/closes/resolves keyword)`, { verbose: true });
164
+ }
103
165
  }
104
166
  }
105
167
 
@@ -271,6 +333,7 @@ export async function batchCheckArchivedRepositories(repositories) {
271
333
 
272
334
  // Export all functions as default object too
273
335
  export default {
336
+ prClosesIssue,
274
337
  batchCheckPullRequestsForIssues,
275
338
  batchCheckArchivedRepositories,
276
339
  };
@@ -77,6 +77,47 @@ export class LenvReader {
77
77
 
78
78
  // The values are the variable value
79
79
  if (link.values && link.values.length > 0) {
80
+ // Check for nested structures (multiple items on same line) - reject with error
81
+ // A nested tuple with id=null that appears amongst other direct values indicates
82
+ // same-line grouping (e.g., "--option1 --option2" on same line)
83
+ // However, if the entire list is a SINGLE nested tuple (e.g., "VAR: (\n 1\n 2\n)"),
84
+ // that's valid parenthesized syntax
85
+ const hasDirectValues = link.values.some(v => v && typeof v === 'object' && v.id !== null);
86
+ const hasNestedTuples = link.values.some(v => v && typeof v === 'object' && v.id === null && v.values && v.values.length > 0);
87
+
88
+ if (hasDirectValues && hasNestedTuples) {
89
+ // Mixed direct values and nested tuples indicates same-line grouping
90
+ for (const v of link.values) {
91
+ if (v && typeof v === 'object' && v.id === null && v.values && v.values.length > 0) {
92
+ const nestedItems = v.values.map(nested => nested.id || nested).join(' ');
93
+ throw new Error(`Invalid LINO format in "${varName}": Multiple values on the same line are not supported.\n` + `Found: "${nestedItems}"\n` + `Each value must be on its own line with proper indentation.`);
94
+ }
95
+ }
96
+ }
97
+
98
+ // Determine which values to validate for invalid characters
99
+ // If it's a single nested tuple (parenthesized list), unwrap it for validation
100
+ let valuesToValidate = link.values;
101
+ if (link.values.length === 1 && link.values[0] && typeof link.values[0] === 'object' && link.values[0].id === null && link.values[0].values) {
102
+ // Single parenthesized list - use inner values
103
+ valuesToValidate = link.values[0].values;
104
+ }
105
+
106
+ // Check for invalid characters in option-like values
107
+ for (const v of valuesToValidate) {
108
+ // Options should match pattern: --option-name or -o (with optional =value)
109
+ const valueStr = v.id || v;
110
+ if (typeof valueStr === 'string' && valueStr.startsWith('-')) {
111
+ // This looks like a command-line option, validate it
112
+ // Valid option pattern: -x, --option-name, --option-name=value
113
+ // Invalid characters: ?, !, @, #, $, %, ^, &, *, etc.
114
+ const invalidCharMatch = valueStr.match(/[^a-zA-Z0-9=_.-]/);
115
+ if (invalidCharMatch) {
116
+ throw new Error(`Invalid LINO format in "${varName}": Unrecognized character "${invalidCharMatch[0]}" in option.\n` + `Found: "${valueStr}"\n` + `Options should only contain letters, numbers, hyphens, underscores, and equals signs.`);
117
+ }
118
+ }
119
+ }
120
+
80
121
  // If there are multiple values, format them as LINO notation
81
122
  const values = link.values.map(v => v.id || v);
82
123
 
@@ -98,6 +139,11 @@ export class LenvReader {
98
139
 
99
140
  return result;
100
141
  } catch (error) {
142
+ // Re-throw validation errors so users can correct their configuration
143
+ if (error.message.includes('Invalid LINO format')) {
144
+ throw error;
145
+ }
146
+ // For other parsing errors, log and return empty
101
147
  console.error(`Error parsing LINO configuration: ${error.message}`);
102
148
  return {};
103
149
  }
@@ -146,6 +146,126 @@ export function formatSuggestions(suggestions) {
146
146
  return `\n\nDid you mean one of these?\n${formattedOptions.map(opt => ` • ${opt}`).join('\n')}`;
147
147
  }
148
148
 
149
+ /**
150
+ * Known valid option names that we use for detecting "-- optionname" typos.
151
+ * These are common options that users might accidentally type with a space after --.
152
+ */
153
+ const KNOWN_OPTION_NAMES = [
154
+ 'model',
155
+ 'verbose',
156
+ 'help',
157
+ 'version',
158
+ 'resume',
159
+ 'fork',
160
+ 'dry-run',
161
+ 'tool',
162
+ 'think',
163
+ 'watch',
164
+ 'sentry',
165
+ 'attach-logs',
166
+ 'auto-continue',
167
+ 'auto-fork',
168
+ 'auto-cleanup',
169
+ 'base-branch',
170
+ 'log-dir',
171
+ 'skip-tool-check',
172
+ 'skip-tool-connection-check',
173
+ 'auto-resume-on-limit-reset',
174
+ 'auto-resume-on-errors',
175
+ 'auto-close-pull-request-on-fail',
176
+ 'auto-pull-request-creation',
177
+ 'auto-commit-uncommitted-changes',
178
+ 'auto-restart-on-uncommitted-changes',
179
+ 'continue-only-on-feedback',
180
+ 'claude-file',
181
+ 'gitkeep-file',
182
+ 'interactive-mode',
183
+ 'prompt-plan-sub-agent',
184
+ 'prompt-explore-sub-agent',
185
+ 'prompt-general-purpose-sub-agent',
186
+ 'prompt-issue-reporting',
187
+ 'prompt-architecture-care',
188
+ 'prompt-case-studies',
189
+ 'prompt-playwright-mcp',
190
+ 'prompt-check-sibling-pull-requests',
191
+ 'enable-workspaces',
192
+ 'execute-tool-with-bun',
193
+ 'tokens-budget-stats',
194
+ 'min-disk-space',
195
+ 'watch-interval',
196
+ 'only-prepare-command',
197
+ 'auto-merge-default-branch-to-pull-request-branch',
198
+ 'allow-fork-divergence-resolution-using-force-push-with-lease',
199
+ 'allow-to-push-to-contributors-pull-requests-as-maintainer',
200
+ 'prefix-fork-name-with-owner-name',
201
+ 'auto-restart-max-iterations',
202
+ 'auto-continue-only-on-new-comments',
203
+ ];
204
+
205
+ /**
206
+ * Detect malformed flag patterns in command line arguments.
207
+ * These are arguments that look like they were intended to be flags
208
+ * but have incorrect formatting (e.g., "-- model" instead of "--model").
209
+ *
210
+ * Issue #1092: When user types "-- model" (with space), yargs silently ignores it
211
+ * as a positional argument instead of producing an error.
212
+ *
213
+ * @param {string[]} args - Array of command line arguments
214
+ * @returns {{ malformed: string[], errors: string[] }} - Detected malformed arguments and error messages
215
+ */
216
+ export function detectMalformedFlags(args) {
217
+ const malformed = [];
218
+ const errors = [];
219
+
220
+ // Patterns that suggest user intended to type a flag but made a mistake
221
+ const malformedPatterns = [
222
+ // "-- option" - space between dashes and option name (Issue #1092)
223
+ { regex: /^-- +\w/, description: 'Space after "--"', suggestion: arg => `--${arg.replace(/^-- +/, '')}` },
224
+ // "- -option" - space between dashes
225
+ { regex: /^- +-/, description: 'Space between dashes', suggestion: arg => arg.replace(/^- +/, '') },
226
+ // "-option" for what looks like a long option (more than 1 char after single dash)
227
+ // Only flag this for known-looking patterns to avoid false positives
228
+ {
229
+ regex: /^-[a-z][a-z]+-?[a-z]*$/i,
230
+ description: 'Single dash for long option',
231
+ suggestion: arg => `-${arg}`,
232
+ },
233
+ // "---option" - triple dash or more
234
+ { regex: /^---+\w/, description: 'Too many dashes', suggestion: arg => arg.replace(/^-+/, '--') },
235
+ ];
236
+
237
+ for (const arg of args) {
238
+ for (const pattern of malformedPatterns) {
239
+ if (pattern.regex.test(arg)) {
240
+ malformed.push(arg);
241
+ const suggestion = pattern.suggestion(arg);
242
+ errors.push(`Malformed option "${arg}": ${pattern.description}. Did you mean "${suggestion}"?`);
243
+ break; // Don't double-report the same argument
244
+ }
245
+ }
246
+ }
247
+
248
+ // Issue #1092: Detect "-- optionname" pattern where the space caused
249
+ // the argument to be split into ['--', 'optionname'] by the tokenizer.
250
+ // Look for standalone '--' followed by a known option name.
251
+ for (let i = 0; i < args.length - 1; i++) {
252
+ if (args[i] === '--') {
253
+ const nextArg = args[i + 1];
254
+ // Check if next argument looks like an option name (not a URL or path)
255
+ // and is a known option name
256
+ if (nextArg && !nextArg.startsWith('-') && !nextArg.includes('/') && !nextArg.includes(':')) {
257
+ const lowerNextArg = nextArg.toLowerCase();
258
+ if (KNOWN_OPTION_NAMES.includes(lowerNextArg)) {
259
+ malformed.push(`-- ${nextArg}`);
260
+ errors.push(`Malformed option "-- ${nextArg}": Space after "--". Did you mean "--${nextArg}"?`);
261
+ }
262
+ }
263
+ }
264
+ }
265
+
266
+ return { malformed, errors };
267
+ }
268
+
149
269
  /**
150
270
  * Create an enhanced error message with suggestions for unknown arguments
151
271
  *
@@ -7,7 +7,10 @@
7
7
  // Note: Strict options validation is now handled by yargs built-in .strict() mode (see below)
8
8
  // This approach was adopted per issue #482 feedback to minimize custom code maintenance
9
9
 
10
- import { enhanceErrorMessage } from './option-suggestions.lib.mjs';
10
+ import { enhanceErrorMessage, detectMalformedFlags } from './option-suggestions.lib.mjs';
11
+
12
+ // Re-export for use by telegram-bot.mjs (avoids extra import lines there)
13
+ export { detectMalformedFlags };
11
14
 
12
15
  // Export an initialization function that accepts 'use'
13
16
  export const initializeConfig = async use => {
@@ -322,6 +325,16 @@ export const createYargsConfig = yargsInstance => {
322
325
  // Parse command line arguments - now needs yargs and hideBin passed in
323
326
  export const parseArguments = async (yargs, hideBin) => {
324
327
  const rawArgs = hideBin(process.argv);
328
+
329
+ // Issue #1092: Detect malformed flag patterns BEFORE yargs parsing
330
+ // This catches cases like "-- model" which yargs silently treats as positional arguments
331
+ const malformedResult = detectMalformedFlags(rawArgs);
332
+ if (malformedResult.malformed.length > 0) {
333
+ const error = new Error(malformedResult.errors.join('\n'));
334
+ error.name = 'MalformedArgumentError';
335
+ throw error;
336
+ }
337
+
325
338
  // Use .parse() instead of .argv to ensure .strict() mode works correctly
326
339
  // When you call yargs(args) and use .argv, strict mode doesn't trigger
327
340
  // See: https://github.com/yargs/yargs/issues - .strict() only works with .parse()
@@ -32,18 +32,12 @@ const yargs = yargsModule.default || yargsModule;
32
32
  const { hideBin } = await use('yargs@17.7.2/helpers');
33
33
 
34
34
  // Import solve and hive yargs configurations for validation
35
- const solveConfigLib = await import('./solve.config.lib.mjs');
36
- const { createYargsConfig: createSolveYargsConfig } = solveConfigLib;
37
-
38
- const hiveConfigLib = await import('./hive.config.lib.mjs');
39
- const { createYargsConfig: createHiveYargsConfig } = hiveConfigLib;
40
-
35
+ const { createYargsConfig: createSolveYargsConfig, detectMalformedFlags } = await import('./solve.config.lib.mjs');
36
+ const { createYargsConfig: createHiveYargsConfig } = await import('./hive.config.lib.mjs');
41
37
  // Import GitHub URL parser for extracting URLs from messages
42
38
  const { parseGitHubUrl } = await import('./github.lib.mjs');
43
-
44
39
  // Import model validation for early validation with helpful error messages
45
40
  const { validateModelName } = await import('./model-validation.lib.mjs');
46
-
47
41
  // Import libraries for /limits, /version, and markdown escaping
48
42
  const { formatUsageMessage, getAllCachedLimits } = await import('./limits.lib.mjs');
49
43
  const { getVersionInfo, formatVersionMessage } = await import('./version-info.lib.mjs');
@@ -1066,7 +1060,12 @@ bot.command(/^solve$/i, async ctx => {
1066
1060
  await ctx.reply(`❌ ${modelError}`, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
1067
1061
  return;
1068
1062
  }
1069
-
1063
+ // Issue #1092: Detect malformed flag patterns like "-- model" (space after --)
1064
+ const { malformed, errors: malformedErrors } = detectMalformedFlags(args);
1065
+ if (malformed.length > 0) {
1066
+ await ctx.reply(`❌ ${malformedErrors.join('\n')}\n\nPlease check your option syntax.`, { parse_mode: 'Markdown', reply_to_message_id: ctx.message.message_id });
1067
+ return;
1068
+ }
1070
1069
  // Validate merged arguments using solve's yargs config
1071
1070
  try {
1072
1071
  // Use .parse() instead of yargs(args).parseSync() to ensure .strict() mode works
@@ -1465,36 +1464,31 @@ bot.telegram
1465
1464
  process.exit(1);
1466
1465
  });
1467
1466
 
1467
+ // Helper to stop solve queue gracefully on shutdown
1468
+ // See: https://github.com/link-assistant/hive-mind/issues/1083
1469
+ const stopSolveQueue = () => {
1470
+ try {
1471
+ getSolveQueue({ verbose: VERBOSE }).stop();
1472
+ if (VERBOSE) console.log('[VERBOSE] Solve queue stopped');
1473
+ } catch (err) {
1474
+ if (VERBOSE) console.log('[VERBOSE] Could not stop solve queue:', err.message);
1475
+ }
1476
+ };
1477
+
1468
1478
  process.once('SIGINT', () => {
1469
1479
  isShuttingDown = true;
1470
1480
  console.log('\n🛑 Received SIGINT (Ctrl+C), stopping bot...');
1471
- if (VERBOSE) {
1472
- console.log('[VERBOSE] Signal: SIGINT');
1473
- console.log('[VERBOSE] Process ID:', process.pid);
1474
- console.log('[VERBOSE] Parent Process ID:', process.ppid);
1475
- }
1481
+ if (VERBOSE) console.log(`[VERBOSE] Signal: SIGINT, PID: ${process.pid}, PPID: ${process.ppid}`);
1482
+ stopSolveQueue();
1476
1483
  bot.stop('SIGINT');
1477
1484
  });
1478
1485
 
1479
1486
  process.once('SIGTERM', () => {
1480
1487
  isShuttingDown = true;
1481
1488
  console.log('\n🛑 Received SIGTERM, stopping bot...');
1482
- if (VERBOSE) {
1483
- console.log('[VERBOSE] Signal: SIGTERM');
1484
- console.log('[VERBOSE] Process ID:', process.pid);
1485
- console.log('[VERBOSE] Parent Process ID:', process.ppid);
1486
- console.log('[VERBOSE] Possible causes:');
1487
- console.log('[VERBOSE] - System shutdown/restart');
1488
- console.log('[VERBOSE] - Process manager (systemd, pm2, etc.) stopping the service');
1489
- console.log('[VERBOSE] - Manual kill command: kill <pid>');
1490
- console.log('[VERBOSE] - Container orchestration (Docker, Kubernetes) stopping container');
1491
- console.log('[VERBOSE] - Out of memory (OOM) killer');
1492
- }
1493
- console.log('ℹ️ SIGTERM is typically sent by:');
1494
- console.log(' - System shutdown/restart');
1495
- console.log(' - Process manager stopping the service');
1496
- console.log(' - Manual termination (kill command)');
1497
- console.log(' - Container/orchestration platform');
1498
- console.log('💡 Check system logs for more details: journalctl -u <service> or dmesg');
1489
+ if (VERBOSE) console.log(`[VERBOSE] Signal: SIGTERM, PID: ${process.pid}, PPID: ${process.ppid}`);
1490
+ console.log('ℹ️ SIGTERM is typically sent by: system shutdown, process manager, kill command, or container orchestration');
1491
+ console.log('💡 Check system logs for details: journalctl -u <service> or dmesg');
1492
+ stopSolveQueue();
1499
1493
  bot.stop('SIGTERM');
1500
1494
  });