@link-assistant/hive-mind 1.50.11 → 1.50.13
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 +14 -0
- package/README.md +11 -0
- package/package.json +4 -4
- package/pr-1607-body.md +34 -0
- package/src/pr-issue-linking.lib.mjs +91 -0
- package/src/solve.auto-pr.lib.mjs +4 -6
- package/src/solve.mjs +3 -1
- package/src/solve.results.lib.mjs +91 -50
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.50.13
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3eb1428: Preserve GitHub issue-closing links after temporary auto-restart sessions edit pull request descriptions.
|
|
8
|
+
|
|
9
|
+
## 1.50.12
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 065deae: ## Summary
|
|
14
|
+
|
|
15
|
+
Fix Playwright MCP setup guidance and verification for Codex environments.
|
|
16
|
+
|
|
3
17
|
## 1.50.11
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -174,6 +174,10 @@ claude
|
|
|
174
174
|
# Optionally test Claude connection
|
|
175
175
|
claude -p hi --model haiku
|
|
176
176
|
|
|
177
|
+
# Verify Playwright MCP is registered for both CLIs in this container image
|
|
178
|
+
claude mcp list | grep playwright
|
|
179
|
+
codex mcp list | grep playwright
|
|
180
|
+
|
|
177
181
|
# You might need to update hive-mind and agent to latest versions:
|
|
178
182
|
bun install -g @link-assistant/hive-mind
|
|
179
183
|
bun install -g @link-assistant/agent
|
|
@@ -212,6 +216,11 @@ docker run -dit --user sandbox --name hive-mind --restart unless-stopped \
|
|
|
212
216
|
SANDBOX_UID=$(docker exec hive-mind id -u sandbox)
|
|
213
217
|
chown -R $SANDBOX_UID:$SANDBOX_UID /root/.hive-mind/claude /root/.hive-mind/codex /root/.hive-mind/gh
|
|
214
218
|
chown $SANDBOX_UID:$SANDBOX_UID /root/.hive-mind/claude.json
|
|
219
|
+
|
|
220
|
+
# Important: mounted ~/.codex data overrides the image-baked Codex config.
|
|
221
|
+
# If the host directory was created before Playwright MCP was added to the image,
|
|
222
|
+
# re-register it once inside the running container:
|
|
223
|
+
docker exec -it hive-mind bash -lc 'codex mcp list && codex mcp add playwright -- npx -y @playwright/mcp@latest --isolated --headless --no-sandbox --timeout-action=600000 --viewport-size 1920x1080'
|
|
215
224
|
```
|
|
216
225
|
|
|
217
226
|
**Benefits of Docker:**
|
|
@@ -222,6 +231,8 @@ chown $SANDBOX_UID:$SANDBOX_UID /root/.hive-mind/claude.json
|
|
|
222
231
|
- ✅ Easy to run multiple instances with different GitHub accounts
|
|
223
232
|
- ✅ Consistent environment across different machines
|
|
224
233
|
|
|
234
|
+
The Docker image itself now registers Playwright MCP for both Claude and Codex during build, and CI verifies those registrations in the built container. If `codex mcp list` is still empty in a running container, the usual cause is not the published image itself but a mounted `/workspace/.codex` directory from the host that replaces the image's default Codex configuration.
|
|
235
|
+
|
|
225
236
|
See [docs/DOCKER.md](./docs/DOCKER.md) for advanced Docker usage.
|
|
226
237
|
|
|
227
238
|
#### Stoping and removing docker container
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/hive-mind",
|
|
3
|
-
"version": "1.50.
|
|
3
|
+
"version": "1.50.13",
|
|
4
4
|
"description": "AI-powered issue solver and hive mind for collaborative problem solving",
|
|
5
5
|
"main": "src/hive.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
"hive-telegram-bot": "./src/telegram-bot.mjs"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-telegram-bot-launcher.mjs",
|
|
16
|
+
"test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-telegram-bot-launcher.mjs",
|
|
17
17
|
"test:queue": "node tests/solve-queue.test.mjs",
|
|
18
18
|
"test:limits-display": "node tests/limits-display.test.mjs",
|
|
19
19
|
"test:usage-limit": "node tests/test-usage-limit.mjs",
|
|
20
20
|
"lint": "eslint 'src/**/*.{js,mjs,cjs}'",
|
|
21
21
|
"lint:fix": "eslint 'src/**/*.{js,mjs,cjs}' --fix",
|
|
22
22
|
"check:duplication": "jscpd .",
|
|
23
|
-
"format": "prettier --write \"**/*.{js,mjs,json,md}\"",
|
|
24
|
-
"format:check": "prettier --check \"**/*.{js,mjs,json,md}\"",
|
|
23
|
+
"format": "prettier --write \"**/*.{js,mjs,json,md}\" --ignore-path .prettierignore",
|
|
24
|
+
"format:check": "prettier --check \"**/*.{js,mjs,json,md}\" --ignore-path .prettierignore",
|
|
25
25
|
"changeset": "changeset",
|
|
26
26
|
"changeset:version": "changeset version",
|
|
27
27
|
"changeset:publish": "npm run build:pre && changeset publish",
|
package/pr-1607-body.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
Fixes #1606 by documenting and verifying Playwright MCP registration for Codex in addition to Claude Code, including the Docker-specific case where persisted Codex state overrides the image defaults.
|
|
4
|
+
|
|
5
|
+
## Root Cause
|
|
6
|
+
|
|
7
|
+
The reported environment had `@playwright/mcp` installed and registered in Claude, but `codex mcp list` had no configured servers. The immediate cause was missing Codex MCP registration. Local reproduction confirmed that `/workspace/.codex/config.toml` can exist without a Playwright MCP entry and that `codex mcp add playwright ...` fixes the state immediately. In Docker deployments, the most likely explanation is that a host-mounted `/workspace/.codex` directory preserved an older Codex config and replaced the image-baked MCP registration. Existing docs and helper scripts also focused mainly on Claude setup, so the mismatch was easy to miss even though `/version` already reported it correctly.
|
|
8
|
+
|
|
9
|
+
## Changes
|
|
10
|
+
|
|
11
|
+
- added regression coverage for the mixed MCP state where Claude is connected and Codex is not
|
|
12
|
+
- updated Playwright MCP verification and integration scripts to check Codex MCP registration explicitly
|
|
13
|
+
- updated Docker verification to fail if Claude or Codex is missing the Playwright MCP registration
|
|
14
|
+
- updated configuration and Docker docs to include both `claude mcp add ...` and `codex mcp add ...`
|
|
15
|
+
- documented that mounting `/workspace/.codex` can override the image defaults and reintroduce the problem
|
|
16
|
+
- added the investigation record and collected evidence under `docs/case-studies/issue-1606`
|
|
17
|
+
|
|
18
|
+
## Reproduction
|
|
19
|
+
|
|
20
|
+
1. Install `@playwright/mcp` and register it only with Claude.
|
|
21
|
+
2. Run `claude mcp list` and confirm `playwright` is present.
|
|
22
|
+
3. Run `codex mcp list` and observe `No MCP servers configured yet`.
|
|
23
|
+
4. Run `/version` and observe `Playwright MCP: <version> | Claude Code: connected | Codex: not connected`.
|
|
24
|
+
|
|
25
|
+
## Verification
|
|
26
|
+
|
|
27
|
+
- `node tests/test-version-info.mjs`
|
|
28
|
+
- `node tests/test-version-parsing.mjs`
|
|
29
|
+
- `bash scripts/verify-docker-image.sh`
|
|
30
|
+
|
|
31
|
+
## Evidence
|
|
32
|
+
|
|
33
|
+
- case study: `docs/case-studies/issue-1606/README.md`
|
|
34
|
+
- PR: https://github.com/link-assistant/hive-mind/pull/1607
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helpers for preserving GitHub issue-closing links in pull request bodies.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { hasGitHubLinkingKeyword } from './github-linking.lib.mjs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Build the issue reference text to use in a GitHub closing keyword.
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} options
|
|
13
|
+
* @param {string|number} options.issueNumber
|
|
14
|
+
* @param {string|null} [options.owner]
|
|
15
|
+
* @param {string|null} [options.repo]
|
|
16
|
+
* @param {boolean} [options.fork]
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
export function buildIssueReference({ issueNumber, owner = null, repo = null, fork = false } = {}) {
|
|
20
|
+
if (!issueNumber) {
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (fork && owner && repo) {
|
|
25
|
+
return `${owner}/${repo}#${issueNumber}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return `#${issueNumber}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Ensure a pull request body has a GitHub-recognized closing keyword for the issue.
|
|
33
|
+
*
|
|
34
|
+
* @param {string|null|undefined} prBody
|
|
35
|
+
* @param {Object} options
|
|
36
|
+
* @param {string|number} options.issueNumber
|
|
37
|
+
* @param {string|null} [options.owner]
|
|
38
|
+
* @param {string|null} [options.repo]
|
|
39
|
+
* @param {boolean} [options.fork]
|
|
40
|
+
* @returns {{body: string, updated: boolean, issueRef: string}}
|
|
41
|
+
*/
|
|
42
|
+
export function ensureIssueLinkInPullRequestBody(prBody, { issueNumber, owner = null, repo = null, fork = false } = {}) {
|
|
43
|
+
const body = prBody ?? '';
|
|
44
|
+
const issueRef = buildIssueReference({ issueNumber, owner, repo, fork });
|
|
45
|
+
|
|
46
|
+
if (!issueNumber) {
|
|
47
|
+
return { body, updated: false, issueRef };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const hasLinkingKeyword = hasGitHubLinkingKeyword(body, issueNumber, owner, repo);
|
|
51
|
+
if (hasLinkingKeyword) {
|
|
52
|
+
return { body, updated: false, issueRef };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const separator = body.length > 0 ? '\n\n' : '';
|
|
56
|
+
return {
|
|
57
|
+
body: `${body}${separator}Fixes ${issueRef}`,
|
|
58
|
+
updated: true,
|
|
59
|
+
issueRef,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parse GraphQL closingIssuesReferences stdout into issue number strings.
|
|
65
|
+
*
|
|
66
|
+
* @param {string|Buffer|null|undefined} output
|
|
67
|
+
* @returns {string[]}
|
|
68
|
+
*/
|
|
69
|
+
export function parseClosingIssueNumbers(output) {
|
|
70
|
+
return String(output ?? '')
|
|
71
|
+
.trim()
|
|
72
|
+
.split('\n')
|
|
73
|
+
.map(value => value.trim())
|
|
74
|
+
.filter(Boolean);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check whether parsed closing issue numbers contain the requested issue number.
|
|
79
|
+
*
|
|
80
|
+
* @param {Array<string|number>} linkedIssues
|
|
81
|
+
* @param {string|number} issueNumber
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
export function closingIssueNumbersContain(linkedIssues, issueNumber) {
|
|
85
|
+
if (!issueNumber || !Array.isArray(linkedIssues)) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const expectedIssueNumber = String(issueNumber);
|
|
90
|
+
return linkedIssues.map(value => String(value).trim()).includes(expectedIssueNumber);
|
|
91
|
+
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Handles automatic creation of draft pull requests with initial commits
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { closingIssueNumbersContain, parseClosingIssueNumbers } from './pr-issue-linking.lib.mjs';
|
|
7
|
+
|
|
6
8
|
export async function handleAutoPrCreation({ argv, tempDir, branchName, issueNumber, owner, repo, defaultBranch, forkedRepo, isContinueMode, prNumber, log, formatAligned, $, reportError, path, fs }) {
|
|
7
9
|
// Skip auto-PR creation if:
|
|
8
10
|
// 1. Auto-PR creation is disabled AND we're not in continue mode with no PR
|
|
@@ -1329,12 +1331,8 @@ ${prBody}`,
|
|
|
1329
1331
|
const linkCheckResult = await $`gh api graphql -f query='query { repository(owner: "${owner}", name: "${repo}") { pullRequest(number: ${localPrNumber}) { closingIssuesReferences(first: 10) { nodes { number } } } } }' --jq '.data.repository.pullRequest.closingIssuesReferences.nodes[].number'`;
|
|
1330
1332
|
|
|
1331
1333
|
if (linkCheckResult.code === 0) {
|
|
1332
|
-
const linkedIssues = linkCheckResult.stdout
|
|
1333
|
-
|
|
1334
|
-
.trim()
|
|
1335
|
-
.split('\n')
|
|
1336
|
-
.filter(n => n);
|
|
1337
|
-
if (linkedIssues.includes(issueNumber)) {
|
|
1334
|
+
const linkedIssues = parseClosingIssueNumbers(linkCheckResult.stdout);
|
|
1335
|
+
if (closingIssueNumbersContain(linkedIssues, issueNumber)) {
|
|
1338
1336
|
await log(formatAligned('✅', 'Link verified:', `Issue #${issueNumber} → PR #${localPrNumber}`));
|
|
1339
1337
|
} else {
|
|
1340
1338
|
// This is a problem - the link wasn't created
|
package/src/solve.mjs
CHANGED
|
@@ -29,7 +29,7 @@ const { processAutoContinueForIssue } = autoContinue;
|
|
|
29
29
|
const repository = await import('./solve.repository.lib.mjs');
|
|
30
30
|
const { setupTempDirectory, cleanupTempDirectory } = repository;
|
|
31
31
|
const results = await import('./solve.results.lib.mjs');
|
|
32
|
-
const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, buildSolveResumeCommand, checkForAiCreatedComments, attachSolutionSummary } = results;
|
|
32
|
+
const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, buildSolveResumeCommand, checkForAiCreatedComments, attachSolutionSummary, verifyPullRequestIssueLinkAfterAutoRestart } = results;
|
|
33
33
|
const claudeLib = await import('./claude.lib.mjs');
|
|
34
34
|
const { executeClaude, checkPlaywrightMcpAvailability } = claudeLib;
|
|
35
35
|
|
|
@@ -1358,6 +1358,8 @@ try {
|
|
|
1358
1358
|
await log(` cd ${tempDir} && git push origin ${branchName}`, { level: 'error' });
|
|
1359
1359
|
}
|
|
1360
1360
|
|
|
1361
|
+
await verifyPullRequestIssueLinkAfterAutoRestart({ prNumber, issueNumber, owner, repo, argv, cleanErrorMessage });
|
|
1362
|
+
|
|
1361
1363
|
// Attach updated logs to PR after auto-restart completes
|
|
1362
1364
|
// Issue #1154: Skip if logs were already uploaded by verifyResults() to prevent duplicates
|
|
1363
1365
|
// Issue #1290: Always upload if auto-restart ran but last iteration's logs weren't uploaded
|
|
@@ -78,9 +78,9 @@ export const buildSolveResumeCommand = ({ issueUrl, sessionId, tool = null, mode
|
|
|
78
78
|
const sentryLib = await import('./sentry.lib.mjs');
|
|
79
79
|
const { reportError } = sentryLib;
|
|
80
80
|
|
|
81
|
-
// Import
|
|
82
|
-
const
|
|
83
|
-
const {
|
|
81
|
+
// Import pull request issue-link preservation helpers
|
|
82
|
+
const prIssueLinking = await import('./pr-issue-linking.lib.mjs');
|
|
83
|
+
const { buildIssueReference, ensureIssueLinkInPullRequestBody } = prIssueLinking;
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* Placeholder patterns used to detect auto-generated PR content that was not updated by the agent.
|
|
@@ -127,6 +127,83 @@ export const buildPRNotUpdatedHint = (titleNotUpdated, descriptionNotUpdated) =>
|
|
|
127
127
|
return lines;
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Ensure an existing pull request body contains a GitHub closing keyword for the issue.
|
|
132
|
+
*
|
|
133
|
+
* @param {Object} options
|
|
134
|
+
* @param {string|number} options.prNumber - Pull request number
|
|
135
|
+
* @param {string|number} options.issueNumber - Issue number to link
|
|
136
|
+
* @param {string} options.owner - Repository owner
|
|
137
|
+
* @param {string} options.repo - Repository name
|
|
138
|
+
* @param {Object} [options.argv] - Parsed CLI arguments
|
|
139
|
+
* @param {Function} [options.command] - command-stream tagged template
|
|
140
|
+
* @param {Function} [options.logger] - Logger function
|
|
141
|
+
* @returns {Promise<{checked: boolean, updated: boolean, body: string, issueRef: string, error?: string}>}
|
|
142
|
+
*/
|
|
143
|
+
export const ensurePullRequestIssueLink = async ({ prNumber, issueNumber, owner, repo, argv = {}, command = $, logger = log }) => {
|
|
144
|
+
if (!prNumber || !issueNumber || !owner || !repo) {
|
|
145
|
+
return { checked: false, updated: false, body: '', issueRef: buildIssueReference({ issueNumber, owner, repo, fork: argv.fork }), error: 'missing required pull request or issue data' };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let prBody = '';
|
|
149
|
+
const prBodyResult = await command`gh pr view ${prNumber} --repo ${owner}/${repo} --json body --jq .body`;
|
|
150
|
+
if (prBodyResult.code !== 0) {
|
|
151
|
+
const error = prBodyResult.stderr ? prBodyResult.stderr.toString().trim() : 'Unknown error';
|
|
152
|
+
await logger(` ⚠️ Could not read PR body for issue link check: ${error}`);
|
|
153
|
+
return { checked: false, updated: false, body: prBody, issueRef: buildIssueReference({ issueNumber, owner, repo, fork: argv.fork }), error };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
prBody = prBodyResult.stdout.toString();
|
|
157
|
+
const linkResult = ensureIssueLinkInPullRequestBody(prBody, {
|
|
158
|
+
issueNumber,
|
|
159
|
+
owner,
|
|
160
|
+
repo,
|
|
161
|
+
fork: argv.fork,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (!linkResult.updated) {
|
|
165
|
+
await logger(' ✅ PR body already contains issue reference');
|
|
166
|
+
return { checked: true, updated: false, body: linkResult.body, issueRef: linkResult.issueRef };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
await logger(` 📝 Updating PR body to link issue #${issueNumber}...`);
|
|
170
|
+
|
|
171
|
+
const fs = (await use('fs')).promises;
|
|
172
|
+
const tempBodyFile = `/tmp/pr-body-update-${prNumber}-${Date.now()}.md`;
|
|
173
|
+
await fs.writeFile(tempBodyFile, linkResult.body);
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const updateResult = await command`gh pr edit ${prNumber} --repo ${owner}/${repo} --body-file "${tempBodyFile}"`;
|
|
177
|
+
await fs.unlink(tempBodyFile).catch(() => {});
|
|
178
|
+
|
|
179
|
+
if (updateResult.code === 0) {
|
|
180
|
+
await logger(` ✅ Updated PR body to include "Fixes ${linkResult.issueRef}"`);
|
|
181
|
+
return { checked: true, updated: true, body: linkResult.body, issueRef: linkResult.issueRef };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const error = updateResult.stderr ? updateResult.stderr.toString().trim() : 'Unknown error';
|
|
185
|
+
await logger(` ⚠️ Could not update PR body: ${error}`);
|
|
186
|
+
return { checked: true, updated: false, body: prBody, issueRef: linkResult.issueRef, error };
|
|
187
|
+
} catch (updateError) {
|
|
188
|
+
await fs.unlink(tempBodyFile).catch(() => {});
|
|
189
|
+
throw updateError;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export const verifyPullRequestIssueLinkAfterAutoRestart = async ({ prNumber, issueNumber, owner, repo, argv = {}, cleanErrorMessage = error => error.message }) => {
|
|
194
|
+
if (!prNumber) {
|
|
195
|
+
return { checked: false, updated: false, body: '', issueRef: buildIssueReference({ issueNumber, owner, repo, fork: argv.fork }) };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await log('🔗 Verifying PR issue link after auto-restart...');
|
|
199
|
+
try {
|
|
200
|
+
return await ensurePullRequestIssueLink({ prNumber, issueNumber, owner, repo, argv });
|
|
201
|
+
} catch (issueLinkError) {
|
|
202
|
+
await log(`⚠️ Could not verify PR issue link after auto-restart: ${cleanErrorMessage(issueLinkError)}`, { level: 'warning' });
|
|
203
|
+
return { checked: false, updated: false, body: '', issueRef: buildIssueReference({ issueNumber, owner, repo, fork: argv.fork }), error: issueLinkError.message };
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
130
207
|
/**
|
|
131
208
|
* Detect the CLAUDE.md or .gitkeep commit hash from branch structure when not available in session
|
|
132
209
|
* This handles continue mode where the commit hash was lost between sessions
|
|
@@ -622,52 +699,16 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
622
699
|
|
|
623
700
|
// Skip PR body update and ready conversion for merged PRs (they can't be edited)
|
|
624
701
|
if (!isPrMerged) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
// See: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue
|
|
636
|
-
const hasLinkingKeyword = hasGitHubLinkingKeyword(prBody, issueNumber, argv.fork ? owner : null, argv.fork ? repo : null);
|
|
637
|
-
|
|
638
|
-
if (!hasLinkingKeyword) {
|
|
639
|
-
await log(` 📝 Updating PR body to link issue #${issueNumber}...`);
|
|
640
|
-
|
|
641
|
-
// Add proper issue reference to the PR body
|
|
642
|
-
const linkingText = `\n\nFixes ${issueRef}`;
|
|
643
|
-
const updatedBody = prBody + linkingText;
|
|
644
|
-
|
|
645
|
-
// Use --body-file instead of --body to avoid command-line length limits
|
|
646
|
-
// and special character escaping issues that can cause hangs/timeouts
|
|
647
|
-
const fs = (await use('fs')).promises;
|
|
648
|
-
const tempBodyFile = `/tmp/pr-body-update-${pr.number}-${Date.now()}.md`;
|
|
649
|
-
await fs.writeFile(tempBodyFile, updatedBody);
|
|
650
|
-
|
|
651
|
-
try {
|
|
652
|
-
const updateResult = await $`gh pr edit ${pr.number} --repo ${owner}/${repo} --body-file "${tempBodyFile}"`;
|
|
653
|
-
|
|
654
|
-
// Clean up temp file
|
|
655
|
-
await fs.unlink(tempBodyFile).catch(() => {});
|
|
656
|
-
|
|
657
|
-
if (updateResult.code === 0) {
|
|
658
|
-
await log(` ✅ Updated PR body to include "Fixes ${issueRef}"`);
|
|
659
|
-
} else {
|
|
660
|
-
await log(` ⚠️ Could not update PR body: ${updateResult.stderr ? updateResult.stderr.toString().trim() : 'Unknown error'}`);
|
|
661
|
-
}
|
|
662
|
-
} catch (updateError) {
|
|
663
|
-
// Clean up temp file on error
|
|
664
|
-
await fs.unlink(tempBodyFile).catch(() => {});
|
|
665
|
-
throw updateError;
|
|
666
|
-
}
|
|
667
|
-
} else {
|
|
668
|
-
await log(' ✅ PR body already contains issue reference');
|
|
669
|
-
}
|
|
670
|
-
}
|
|
702
|
+
const issueLinkResult = await ensurePullRequestIssueLink({
|
|
703
|
+
prNumber: pr.number,
|
|
704
|
+
issueNumber,
|
|
705
|
+
owner,
|
|
706
|
+
repo,
|
|
707
|
+
argv,
|
|
708
|
+
command: $,
|
|
709
|
+
logger: log,
|
|
710
|
+
});
|
|
711
|
+
const prBody = issueLinkResult.body || '';
|
|
671
712
|
|
|
672
713
|
// Issue #1162: Detect if PR title/description still have auto-generated placeholder content
|
|
673
714
|
// Track this before cleanup for --auto-restart-on-non-updated-pull-request-description
|
|
@@ -709,7 +750,7 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
709
750
|
|
|
710
751
|
// Build new description
|
|
711
752
|
const fs = (await use('fs')).promises;
|
|
712
|
-
const issueRef =
|
|
753
|
+
const issueRef = buildIssueReference({ issueNumber, owner, repo, fork: argv.fork });
|
|
713
754
|
const newDescription = `## Summary
|
|
714
755
|
|
|
715
756
|
This pull request implements a solution for ${issueRef}: ${issueTitle}
|