@link-assistant/hive-mind 1.2.4 → 1.2.6
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 +70 -0
- package/analyze-issue.md +72 -0
- package/package.json +1 -1
- package/src/github.batch.lib.mjs +69 -6
- package/src/log-upload.lib.mjs +18 -11
- package/src/option-suggestions.lib.mjs +120 -0
- package/src/solve.config.lib.mjs +14 -1
- package/src/telegram-bot.mjs +25 -31
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.2.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 94dfb13: Fix gh-upload-log argument parsing bug causing "File does not exist" error
|
|
8
|
+
- Fixed bug where `gh-upload-log` received all arguments as a single concatenated string
|
|
9
|
+
- The issue was caused by using `${commandArgs.join(' ')}` in command-stream template literal, which treats the entire joined string as one argument
|
|
10
|
+
- Now using separate `${}` interpolations for each argument to ensure proper argument parsing
|
|
11
|
+
- Also fixed: description flag is now properly passed to gh-upload-log (was only displayed, never sent)
|
|
12
|
+
- Added comprehensive regression tests and case study documentation
|
|
13
|
+
|
|
14
|
+
## 1.2.5
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 65ee214: fix: Detect malformed flag patterns like "-- model" (Issue #1092)
|
|
19
|
+
|
|
20
|
+
Added `detectMalformedFlags()` function that catches malformed command-line options and provides helpful error messages:
|
|
21
|
+
- Detects "-- option" (space after --) and suggests "--option"
|
|
22
|
+
- Detects "-option" (single dash for long option) and suggests "--option"
|
|
23
|
+
- Detects "---option" (triple dash) and suggests "--option"
|
|
24
|
+
- Integrated into both Telegram bot and CLI argument parsing
|
|
25
|
+
- Added 23 comprehensive unit tests
|
|
26
|
+
|
|
27
|
+
- af950c8: fix(hive): require closing keywords for PR detection
|
|
28
|
+
|
|
29
|
+
The `/hive` command was incorrectly skipping issues by reporting they had
|
|
30
|
+
PRs when those PRs only mentioned the issues without actually solving them.
|
|
31
|
+
|
|
32
|
+
**Root cause**: The `batchCheckPullRequestsForIssues` function used GitHub's
|
|
33
|
+
`CROSS_REFERENCED_EVENT` timeline items, which are created whenever a PR
|
|
34
|
+
body/title/commit mentions an issue number - regardless of whether the PR
|
|
35
|
+
actually solves the issue.
|
|
36
|
+
|
|
37
|
+
**Example**: PR #369 in VisageDvachevsky/StoryGraph is an audit PR that
|
|
38
|
+
created 28 new issues (#370-#397) and listed them in a table. This caused
|
|
39
|
+
GitHub to create cross-reference events linking that PR to all 28 issues,
|
|
40
|
+
but PR #369 only actually fixes #368.
|
|
41
|
+
|
|
42
|
+
**Solution**:
|
|
43
|
+
- Add `prClosesIssue()` function to detect GitHub closing keywords
|
|
44
|
+
(fixes, closes, resolves - case-insensitive)
|
|
45
|
+
- Update GraphQL query to include PR body text
|
|
46
|
+
- Only count PRs that contain "fixes #N", "closes #N", or "resolves #N"
|
|
47
|
+
for the specific issue number
|
|
48
|
+
- Add verbose logging when PRs are skipped for only mentioning issues
|
|
49
|
+
|
|
50
|
+
This aligns with GitHub's own auto-close behavior where only specific
|
|
51
|
+
keywords trigger issue closure when a PR is merged.
|
|
52
|
+
|
|
53
|
+
Fixes #1094
|
|
54
|
+
|
|
55
|
+
- 0d997ac: fix(telegram-bot): stop solve queue on SIGINT/SIGTERM for clean exit
|
|
56
|
+
|
|
57
|
+
The telegram bot was hanging after pressing Ctrl+C because the SolveQueue
|
|
58
|
+
consumer loop kept running with active timers that prevented the Node.js
|
|
59
|
+
event loop from emptying.
|
|
60
|
+
- **Root cause identified**: The SIGINT/SIGTERM handlers only called
|
|
61
|
+
`bot.stop()` (Telegraf) but did not stop the SolveQueue, whose `sleep()`
|
|
62
|
+
timers kept the event loop alive.
|
|
63
|
+
- **Solution**: Added `solveQueue.stop()` call in both SIGINT and SIGTERM
|
|
64
|
+
handlers to stop the consumer loop before calling `bot.stop()`.
|
|
65
|
+
- **Added verbose logging**: When running with `--verbose`, the bot now
|
|
66
|
+
logs "Solve queue stopped" during shutdown.
|
|
67
|
+
- **Case study documentation**: Added detailed analysis in
|
|
68
|
+
`docs/case-studies/issue-1083/` with timeline, root cause investigation,
|
|
69
|
+
and evidence collection.
|
|
70
|
+
|
|
71
|
+
Fixes #1083
|
|
72
|
+
|
|
3
73
|
## 1.2.4
|
|
4
74
|
|
|
5
75
|
### Patch Changes
|
package/analyze-issue.md
ADDED
|
@@ -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
package/src/github.batch.lib.mjs
CHANGED
|
@@ -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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
};
|
package/src/log-upload.lib.mjs
CHANGED
|
@@ -33,23 +33,30 @@ export const uploadLogWithGhUploadLog = async ({ logFile, isPublic, description,
|
|
|
33
33
|
const result = { success: false, url: null, rawUrl: null, type: null, chunks: 1 };
|
|
34
34
|
|
|
35
35
|
try {
|
|
36
|
-
// Build command
|
|
36
|
+
// Build command flags
|
|
37
|
+
// IMPORTANT: When using command-stream's $ template tag, each ${} interpolation is treated
|
|
38
|
+
// as a single argument. DO NOT use commandArgs.join(' ') as it will make all flags part
|
|
39
|
+
// of the first positional argument, causing "File does not exist" errors.
|
|
40
|
+
// See case study: docs/case-studies/issue-1096/README.md
|
|
37
41
|
const publicFlag = isPublic ? '--public' : '--private';
|
|
38
|
-
const descFlag = description ? `--description "${description}"` : '';
|
|
39
|
-
const verboseFlag = verbose ? '--verbose' : '';
|
|
40
|
-
|
|
41
|
-
const command = `gh-upload-log "${logFile}" ${publicFlag} ${descFlag} ${verboseFlag}`.trim().replace(/\s+/g, ' ');
|
|
42
42
|
|
|
43
43
|
if (verbose) {
|
|
44
|
-
|
|
44
|
+
const descDisplay = description ? ` --description "${description}"` : '';
|
|
45
|
+
await log(` 📤 Running: gh-upload-log "${logFile}" ${publicFlag}${descDisplay} --verbose`, { verbose: true });
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
// Execute command with separate interpolations for each argument
|
|
49
|
+
// Each ${} is properly passed as a separate argument to the shell
|
|
50
|
+
let uploadResult;
|
|
51
|
+
if (description && verbose) {
|
|
52
|
+
uploadResult = await $`gh-upload-log ${logFile} ${publicFlag} --description ${description} --verbose`;
|
|
53
|
+
} else if (description) {
|
|
54
|
+
uploadResult = await $`gh-upload-log ${logFile} ${publicFlag} --description ${description}`;
|
|
55
|
+
} else if (verbose) {
|
|
56
|
+
uploadResult = await $`gh-upload-log ${logFile} ${publicFlag} --verbose`;
|
|
57
|
+
} else {
|
|
58
|
+
uploadResult = await $`gh-upload-log ${logFile} ${publicFlag}`;
|
|
51
59
|
}
|
|
52
|
-
const uploadResult = await $`gh-upload-log ${commandArgs.join(' ')}`;
|
|
53
60
|
const output = (uploadResult.stdout?.toString() || '') + (uploadResult.stderr?.toString() || '');
|
|
54
61
|
|
|
55
62
|
if (uploadResult.code !== 0) {
|
|
@@ -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
|
*
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -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()
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -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
|
|
36
|
-
const { createYargsConfig:
|
|
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
|
-
|
|
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
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
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
|
});
|