@link-assistant/hive-mind 2.12.4 → 2.12.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,11 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 2.12.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 77ed9bc: Keep Formal AI repository requests bounded, target the discovered pull request URL, and stop retrying explicit `planned_not_executed` results as successful work.
8
+
3
9
  ## 2.12.4
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "2.12.4",
3
+ "version": "2.12.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",
@@ -9,6 +9,8 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
9
9
  import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
10
10
  import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
11
11
  import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
12
+ import { buildFormalAiRepositoryPrompt } from './formal-ai-prompt.lib.mjs';
13
+ import { isFormalAiModel } from './formal-ai-model.lib.mjs';
12
14
 
13
15
  /**
14
16
  * Build the user prompt for Agent
@@ -16,6 +18,9 @@ import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
16
18
  * @returns {string} The formatted user prompt
17
19
  */
18
20
  export const buildUserPrompt = params => {
21
+ const formalAiPrompt = buildFormalAiRepositoryPrompt(params);
22
+ if (formalAiPrompt !== null) return formalAiPrompt;
23
+
19
24
  const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
20
25
 
21
26
  const promptLines = [];
@@ -92,6 +97,13 @@ export const buildUserPrompt = params => {
92
97
  export const buildSystemPrompt = params => {
93
98
  const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
94
99
 
100
+ // Issue #2158: Formal AI's deterministic intent router considers the whole
101
+ // provider request, including this caller-owned workflow prompt. Command
102
+ // examples such as `sudo` and `pwd` were consequently executed as if the
103
+ // issue requested them. The compact user prompt remains the repository
104
+ // objective; Formal AI supplies its own execution policy.
105
+ if (isFormalAiModel(argv?.model)) return '';
106
+
95
107
  // When in fork mode, screenshots are pushed to the fork, not the original repo
96
108
  const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
97
109
 
@@ -11,6 +11,8 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
11
11
  import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
12
12
  import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
13
13
  import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
14
+ import { buildFormalAiRepositoryPrompt } from './formal-ai-prompt.lib.mjs';
15
+ import { isFormalAiModel } from './formal-ai-model.lib.mjs';
14
16
 
15
17
  /**
16
18
  * Build the user prompt for Claude
@@ -18,6 +20,9 @@ import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
18
20
  * @returns {string} The formatted user prompt
19
21
  */
20
22
  export const buildUserPrompt = params => {
23
+ const formalAiPrompt = buildFormalAiRepositoryPrompt(params);
24
+ if (formalAiPrompt !== null) return formalAiPrompt;
25
+
21
26
  const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, owner, repo, argv, contributingGuidelines, claudeVersion } = params;
22
27
 
23
28
  if (argv?.minimalRestartContext && argv.resume) {
@@ -105,6 +110,10 @@ export const buildUserPrompt = params => {
105
110
  export const buildSystemPrompt = params => {
106
111
  const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
107
112
 
113
+ // Issue #2158: keep caller workflow instructions out of Formal AI's task
114
+ // classifier. Formal AI provides its own execution policy.
115
+ if (isFormalAiModel(argv?.model)) return '';
116
+
108
117
  if (argv?.minimalRestartContext && argv.resume) {
109
118
  return '';
110
119
  }
@@ -10,6 +10,8 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
10
10
  import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
11
11
  import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
12
12
  import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
13
+ import { buildFormalAiRepositoryPrompt } from './formal-ai-prompt.lib.mjs';
14
+ import { isFormalAiModel } from './formal-ai-model.lib.mjs';
13
15
 
14
16
  /**
15
17
  * Build the user prompt for Codex
@@ -17,6 +19,9 @@ import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
17
19
  * @returns {string} The formatted user prompt
18
20
  */
19
21
  export const buildUserPrompt = params => {
22
+ const formalAiPrompt = buildFormalAiRepositoryPrompt(params);
23
+ if (formalAiPrompt !== null) return formalAiPrompt;
24
+
20
25
  const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
21
26
 
22
27
  const promptLines = [];
@@ -93,6 +98,10 @@ export const buildUserPrompt = params => {
93
98
  export const buildSystemPrompt = params => {
94
99
  const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
95
100
 
101
+ // Issue #2158: keep caller workflow instructions out of Formal AI's task
102
+ // classifier. Formal AI provides its own execution policy.
103
+ if (isFormalAiModel(argv?.model)) return '';
104
+
96
105
  // When in fork mode, screenshots are pushed to the fork, not the original repo
97
106
  const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
98
107
 
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Build the small repository objective sent through native CLIs to Formal AI.
3
+ *
4
+ * Formal AI owns the agent policy for its model. Repeating Hive Mind's native
5
+ * provider policy in the request both wastes context and exposes incidental
6
+ * shell-language cues to Formal AI's deterministic intent router (#2158).
7
+ */
8
+
9
+ import { isFormalAiModel } from './formal-ai-model.lib.mjs';
10
+
11
+ export const buildFormalAiRepositoryPrompt = params => {
12
+ if (!isFormalAiModel(params?.argv?.model)) return null;
13
+
14
+ const { issueUrl, issueNumber, prNumber, prUrl, branchName, isContinueMode, feedbackLines, owner, repo } = params;
15
+ const issueReference = isContinueMode && issueNumber && owner && repo ? `https://github.com/${owner}/${repo}/issues/${issueNumber}` : issueUrl || `the issue linked to pull request ${prNumber}`;
16
+ const lines = [`Resolve the GitHub issue at ${issueReference} in this repository.`];
17
+
18
+ if (branchName) lines.push(`Keep the solution on branch ${branchName}.`);
19
+ if (prUrl) lines.push(`Update the pull request at ${prUrl}.`);
20
+ // The review text is caller-controlled and can contain command snippets.
21
+ // Point Formal AI to the canonical PR instead of copying those snippets into
22
+ // the intent-classification request.
23
+ if (feedbackLines?.length && prUrl) lines.push('Review and address all feedback recorded on that pull request.');
24
+
25
+ lines.push('', 'Implement and verify the solution before reporting completion.', isContinueMode ? 'Continue.' : 'Proceed.');
26
+ return `${lines.join('\n')}\n`;
27
+ };
28
+
29
+ export default { buildFormalAiRepositoryPrompt };
@@ -217,6 +217,36 @@ export const createPreparedToolResult = preparedCommand => ({
217
217
  errorDuringExecution: false,
218
218
  });
219
219
 
220
+ const FORMAL_AI_NON_EXECUTION_PATTERNS = [/^\s*planned,\s*not executed\b/im, /^\s*planned_not_executed\s*$/im, /^\s*terminal_state\s+["']?planned_not_executed\b/im];
221
+
222
+ /**
223
+ * Turn Formal AI's explicit non-execution terminal state into a failed tool
224
+ * result (issue #2158).
225
+ *
226
+ * Formal AI 0.339.1 truthfully reports repository work as
227
+ * `planned_not_executed`, but each native CLI exits zero. Treating that process
228
+ * exit as a successful solve made `--auto-restart-until-mergeable` repeat the
229
+ * same deterministic plan five times. Keep the model's summary for evidence,
230
+ * while giving Hive Mind an actionable terminal failure.
231
+ */
232
+ export const classifyFormalAiToolResult = ({ model, toolResult } = {}) => {
233
+ if (!toolResult || !isFormalAiModel(model) || toolResult.success === false) return toolResult;
234
+
235
+ const evidence = [toolResult.resultSummary, toolResult.result, toolResult.lastMessage, toolResult.output].filter(value => typeof value === 'string').join('\n');
236
+ if (!FORMAL_AI_NON_EXECUTION_PATTERNS.some(pattern => pattern.test(evidence))) return toolResult;
237
+
238
+ return {
239
+ ...toolResult,
240
+ success: false,
241
+ errorDuringExecution: true,
242
+ formalAiNonExecution: true,
243
+ errorInfo: {
244
+ code: 'FORMAL_AI_PLANNED_NOT_EXECUTED',
245
+ message: "Formal AI did not execute repository work; it returned the terminal state planned_not_executed. Fix or upgrade Formal AI's repository-work executor before retrying.",
246
+ },
247
+ };
248
+ };
249
+
220
250
  export const logPreparedToolCommand = async ({ argv, fullCommand, log, formatAligned }) => {
221
251
  await log(`\n${formatAligned('📝', 'Raw command:', '')}`);
222
252
  await log(fullCommand);
@@ -9,6 +9,8 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
9
9
  import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
10
10
  import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
11
11
  import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
12
+ import { buildFormalAiRepositoryPrompt } from './formal-ai-prompt.lib.mjs';
13
+ import { isFormalAiModel } from './formal-ai-model.lib.mjs';
12
14
 
13
15
  /**
14
16
  * Build the user prompt for Gemini
@@ -16,6 +18,9 @@ import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
16
18
  * @returns {string} The formatted user prompt
17
19
  */
18
20
  export const buildUserPrompt = params => {
21
+ const formalAiPrompt = buildFormalAiRepositoryPrompt(params);
22
+ if (formalAiPrompt !== null) return formalAiPrompt;
23
+
19
24
  const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
20
25
 
21
26
  const promptLines = [];
@@ -81,6 +86,10 @@ export const buildUserPrompt = params => {
81
86
  export const buildSystemPrompt = params => {
82
87
  const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
83
88
 
89
+ // Issue #2158: keep caller workflow instructions out of Formal AI's task
90
+ // classifier. Formal AI provides its own execution policy.
91
+ if (isFormalAiModel(argv?.model)) return '';
92
+
84
93
  const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
85
94
 
86
95
  let workspaceInstructions = '';
@@ -241,6 +241,14 @@ export function normalizeGitHubUrl(url) {
241
241
  const parsed = parseGitHubUrl(url);
242
242
  return parsed.valid ? parsed.normalized : null;
243
243
  }
244
+
245
+ /** Build the canonical web URL for a pull request already identified by GitHub. */
246
+ export function buildGitHubPullRequestUrl({ owner, repo, number } = {}) {
247
+ if (!owner || !repo || !Number.isInteger(Number(number)) || Number(number) <= 0) {
248
+ throw new TypeError('A GitHub pull request URL requires owner, repo, and a positive integer number');
249
+ }
250
+ return `https://github.com/${owner}/${repo}/pull/${Number(number)}`;
251
+ }
244
252
  /**
245
253
  * Check if a URL is a valid GitHub URL of a specific type
246
254
  * @param {string} url - The URL to check
@@ -20,8 +20,8 @@ export { buildCostInfoString };
20
20
  // #1756: route gh exec calls through transient + rate-limit retry wrapper
21
21
  import { execGhWithRetry } from './github-rate-limit.lib.mjs';
22
22
  import { QUIET_PROBE } from './quiet-probe.lib.mjs'; // issues #2130, #2135: keep read-only probe payloads out of the attached log
23
- import { isGitHubUrlType, normalizeGitHubUrl, parseGitHubUrl } from './github-url-parser.lib.mjs';
24
- export { isGitHubUrlType, normalizeGitHubUrl, parseGitHubUrl };
23
+ import { buildGitHubPullRequestUrl, isGitHubUrlType, normalizeGitHubUrl, parseGitHubUrl } from './github-url-parser.lib.mjs';
24
+ export { buildGitHubPullRequestUrl, isGitHubUrlType, normalizeGitHubUrl, parseGitHubUrl };
25
25
  // Issue #1625: Named marker constants (single source of truth) + in-memory tracking for tool-posted comments. See tool-comments.lib.mjs for design.
26
26
  import { SOLUTION_DRAFT_LOG_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, postTrackedComment, postTrackedCommentFromFile } from './tool-comments.lib.mjs';
27
27
  export const maskGitHubToken = maskToken; // Alias for backward compatibility
@@ -1168,6 +1168,7 @@ export default {
1168
1168
  fetchProjectIssues,
1169
1169
  isRateLimitError,
1170
1170
  batchCheckPullRequestsForIssues,
1171
+ buildGitHubPullRequestUrl,
1171
1172
  parseGitHubUrl,
1172
1173
  normalizeGitHubUrl,
1173
1174
  isGitHubUrlType,
@@ -9,6 +9,8 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
9
9
  import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
10
10
  import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
11
11
  import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
12
+ import { buildFormalAiRepositoryPrompt } from './formal-ai-prompt.lib.mjs';
13
+ import { isFormalAiModel } from './formal-ai-model.lib.mjs';
12
14
 
13
15
  /**
14
16
  * Build the user prompt for OpenCode
@@ -16,6 +18,9 @@ import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
16
18
  * @returns {string} The formatted user prompt
17
19
  */
18
20
  export const buildUserPrompt = params => {
21
+ const formalAiPrompt = buildFormalAiRepositoryPrompt(params);
22
+ if (formalAiPrompt !== null) return formalAiPrompt;
23
+
19
24
  const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
20
25
 
21
26
  const promptLines = [];
@@ -92,6 +97,10 @@ export const buildUserPrompt = params => {
92
97
  export const buildSystemPrompt = params => {
93
98
  const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
94
99
 
100
+ // Issue #2158: keep caller workflow instructions out of Formal AI's task
101
+ // classifier. Formal AI provides its own execution policy.
102
+ if (isFormalAiModel(argv?.model)) return '';
103
+
95
104
  // When in fork mode, screenshots are pushed to the fork, not the original repo
96
105
  const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
97
106
 
@@ -9,6 +9,8 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
9
9
  import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
10
10
  import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
11
11
  import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
12
+ import { buildFormalAiRepositoryPrompt } from './formal-ai-prompt.lib.mjs';
13
+ import { isFormalAiModel } from './formal-ai-model.lib.mjs';
12
14
 
13
15
  /**
14
16
  * Build the user prompt for Qwen Code
@@ -16,6 +18,9 @@ import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
16
18
  * @returns {string} The formatted user prompt
17
19
  */
18
20
  export const buildUserPrompt = params => {
21
+ const formalAiPrompt = buildFormalAiRepositoryPrompt(params);
22
+ if (formalAiPrompt !== null) return formalAiPrompt;
23
+
19
24
  const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, tool = 'qwen' } = params;
20
25
 
21
26
  const promptLines = [];
@@ -81,6 +86,10 @@ export const buildUserPrompt = params => {
81
86
  export const buildSystemPrompt = params => {
82
87
  const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
83
88
 
89
+ // Issue #2158: keep caller workflow instructions out of Formal AI's task
90
+ // classifier. Formal AI provides its own execution policy.
91
+ if (isFormalAiModel(argv?.model)) return '';
92
+
84
93
  const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
85
94
 
86
95
  let workspaceInstructions = '';
package/src/solve.mjs CHANGED
@@ -69,6 +69,7 @@ const { validateAndExitOnInvalidClaudeSubAgentModel, validateAndExitOnInvalidMod
69
69
  const { autoAcceptInviteForRepo } = await import('./solve.accept-invite.lib.mjs');
70
70
  const { handleAutoForkOption, handleMaintainerForkAccess } = await import('./solve.fork-detection.lib.mjs');
71
71
  const { resolveUncommittedChangesTool } = await import('./solve.tool-uncommitted.lib.mjs');
72
+ const { classifyFormalAiToolResult } = await import('./formal-ai.lib.mjs');
72
73
  const logFile = await initializeLogFile(null);
73
74
  const versionInfo = await getVersionInfo();
74
75
  const rawCommand = await logSolveStartup(versionInfo);
@@ -535,7 +536,10 @@ try {
535
536
  let prUrl = null;
536
537
  // In continue mode, we already have the PR details
537
538
  if (isContinueMode) {
538
- prUrl = issueUrl; // The input URL is the PR URL
539
+ // Issue #2158: auto-continue can discover a PR while the input remains an
540
+ // issue URL. Passing that issue URL as "Your prepared Pull Request" sent
541
+ // the first Formal AI attempt to the wrong GitHub entity.
542
+ prUrl = githubLib.buildGitHubPullRequestUrl({ owner, repo, number: prNumber });
539
543
  // prNumber is already set from earlier when we parsed the PR
540
544
  }
541
545
  // Handle auto PR creation using the new module
@@ -757,6 +761,11 @@ try {
757
761
  });
758
762
  toolResult = claudeResult;
759
763
  }
764
+ toolResult = classifyFormalAiToolResult({ model: argv.model, toolResult });
765
+ if (toolResult?.formalAiNonExecution) {
766
+ await log(`❌ ${toolResult.errorInfo.message}`, { level: 'error' });
767
+ await log(' The deterministic terminal response will not be retried as a mergeability problem.', { level: 'error' });
768
+ }
760
769
  try {
761
770
  await recordAfterAgentSize({ tempDir, beforeBytes: cleanupContext.diskDiagnostics?.beforeBytes ?? null, log });
762
771
  } catch (diskError) {
@@ -34,6 +34,7 @@ const { log, formatAligned, extractToolErrorCore } = lib;
34
34
  const { ensurePullRequestBaseBranch } = await import('./solve.pr-base-guard.lib.mjs');
35
35
  const { ensureAiToolScratchIgnored, filterAiToolScratchFromStatus } = await import('./ai-tool-scratch.lib.mjs');
36
36
  const { RESOURCE_PHASE_RESTART_AFTER, RESOURCE_PHASE_RESTART_BEFORE, recordResourceSnapshot } = await import('./solve.resource-diagnostics.lib.mjs');
37
+ const { classifyFormalAiToolResult } = await import('./formal-ai.lib.mjs');
37
38
  // Issue #2123: shared draft/ready transitions for working sessions.
38
39
  const { ensurePullRequestIsDraft } = await import('./pr-draft-state.lib.mjs');
39
40
 
@@ -492,6 +493,11 @@ export const executeToolIteration = async params => {
492
493
  });
493
494
  }
494
495
 
496
+ toolResult = classifyFormalAiToolResult({ model: argv.model, toolResult });
497
+ if (toolResult?.formalAiNonExecution) {
498
+ await log(`❌ ${toolResult.errorInfo.message}`, { level: 'error' });
499
+ }
500
+
495
501
  await ensurePullRequestBaseBranch({ owner, repo, prNumber, argv, log, formatAligned, $ });
496
502
  await recordResourceSnapshot({
497
503
  phase: RESOURCE_PHASE_RESTART_AFTER,