@link-assistant/hive-mind 2.11.13 ā 2.12.1
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 +18 -0
- package/package.json +4 -1
- package/src/agent-command.lib.mjs +74 -0
- package/src/agent.lib.mjs +59 -34
- package/src/agentic-cli-updater.lib.mjs +241 -0
- package/src/claude.connection.lib.mjs +209 -0
- package/src/claude.lib.mjs +6 -202
- package/src/codex.lib.mjs +0 -128
- package/src/formal-ai-isolation.lib.mjs +62 -0
- package/src/formal-ai-maintenance.lib.mjs +106 -0
- package/src/formal-ai-model.lib.mjs +25 -0
- package/src/formal-ai-runtime.lib.mjs +10 -0
- package/src/formal-ai-sidecar.lib.mjs +565 -0
- package/src/formal-ai-updater.lib.mjs +294 -0
- package/src/formal-ai-version.lib.mjs +100 -0
- package/src/formal-ai.lib.mjs +11 -16
- package/src/github-rate-limit.lib.mjs +3 -0
- package/src/github-url-parser.lib.mjs +255 -0
- package/src/github.lib.mjs +22 -343
- package/src/hive.mjs +0 -152
- package/src/interactive-mode.lib.mjs +0 -43
- package/src/isolation-runner.lib.mjs +44 -173
- package/src/limits.lib.mjs +0 -89
- package/src/model-args.lib.mjs +32 -0
- package/src/models/index.mjs +5 -19
- package/src/session-monitor.lib.mjs +14 -172
- package/src/solve.auto-merge.lib.mjs +70 -164
- package/src/solve.mjs +31 -193
- package/src/solve.repository.lib.mjs +0 -83
- package/src/solve.results.lib.mjs +2 -92
- package/src/solve.session.lib.mjs +52 -19
- package/src/solve.tool-uncommitted.lib.mjs +22 -0
- package/src/state-lock.lib.mjs +82 -0
- package/src/telegram-bot.mjs +17 -65
- package/src/telegram-fix-command.lib.mjs +1 -8
- package/src/telegram-merge-queue.lib.mjs +3 -155
- package/src/telegram-solve-queue.lib.mjs +9 -168
- package/src/telegram-task-command.lib.mjs +1 -8
- package/src/use-m-bootstrap.lib.mjs +6 -5
- package/src/use-with-retry.lib.mjs +128 -2
- package/src/working-session-summary.lib.mjs +47 -1
|
@@ -3,7 +3,6 @@ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
|
3
3
|
|
|
4
4
|
// Results processing module for solve command
|
|
5
5
|
// Extracted from solve.mjs to keep files under 1500 lines
|
|
6
|
-
|
|
7
6
|
// Use use-m to dynamically import modules for cross-runtime compatibility
|
|
8
7
|
// Check if use is already defined globally (when imported from solve.mjs)
|
|
9
8
|
// If not, fetch it (when running standalone)
|
|
@@ -18,18 +17,15 @@ const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
|
|
|
18
17
|
const { QUIET_PROBE } = await import('./quiet-probe.lib.mjs'); // issue #2130: keep read-only probe payloads out of the attached log
|
|
19
18
|
const $ = wrapDollarWithGhRetry(__rawDollar$);
|
|
20
19
|
const path = (await use('path')).default;
|
|
21
|
-
|
|
22
20
|
// Import shared library functions
|
|
23
21
|
const lib = await import('./lib.mjs');
|
|
24
22
|
const { log, getLogFile, formatAligned } = lib;
|
|
25
23
|
|
|
26
24
|
// Import exit handler
|
|
27
25
|
import { safeExit } from './exit-handler.lib.mjs';
|
|
28
|
-
|
|
29
26
|
// Import GitHub-related functions
|
|
30
27
|
const githubLib = await import('./github.lib.mjs');
|
|
31
28
|
const { sanitizeLogContent, attachLogToGitHub } = githubLib;
|
|
32
|
-
|
|
33
29
|
// Issue #1745: process-wide sanitization counters used to print a one-line
|
|
34
30
|
// "we masked N secrets" summary at the end of each run.
|
|
35
31
|
const { formatSanitizationSummary, sanitizeForPublication, writeSanitizedPublicationFile } = await import('./token-sanitization.lib.mjs');
|
|
@@ -41,7 +37,6 @@ const { runPostFinishSweep } = await import('./post-finish-sanitization-sweep.li
|
|
|
41
37
|
// Import continuation functions (session resumption, PR detection)
|
|
42
38
|
const autoContinue = await import('./solve.auto-continue.lib.mjs');
|
|
43
39
|
const { autoContinueWhenLimitResets } = autoContinue;
|
|
44
|
-
|
|
45
40
|
// Import Claude-specific command builders
|
|
46
41
|
// These are used to generate copy-pasteable Claude CLI resume commands for users
|
|
47
42
|
// Pattern: (cd "/tmp/gh-issue-solver-..." && claude --resume <session-id>)
|
|
@@ -55,21 +50,18 @@ export const { buildClaudeResumeCommand, buildClaudeAutonomousResumeCommand, bui
|
|
|
55
50
|
// imported from tool libraries (claude/codex/gemini) without circular imports.
|
|
56
51
|
import { buildSolveResumeCommand } from './solve.resume-command.lib.mjs';
|
|
57
52
|
export { buildSolveResumeCommand };
|
|
58
|
-
|
|
59
53
|
// Import error handling functions
|
|
60
54
|
// const errorHandlers = await import('./solve.error-handlers.lib.mjs'); // Not currently used
|
|
61
55
|
// Import Sentry integration
|
|
62
56
|
const sentryLib = await import('./sentry.lib.mjs');
|
|
63
57
|
const { reportError } = sentryLib;
|
|
64
|
-
|
|
65
58
|
// Import pull request issue-link preservation helpers
|
|
66
59
|
const prIssueLinking = await import('./pr-issue-linking.lib.mjs');
|
|
67
60
|
const { buildIssueReference, ensureIssueLinkInPullRequestBody } = prIssueLinking;
|
|
68
61
|
|
|
69
62
|
// Issue #2119: the one place that decides whether a pull request changed anything.
|
|
70
63
|
const { formatChangeSummary, getPullRequestChangeStats } = await import('./pull-request-changes.lib.mjs');
|
|
71
|
-
const { buildNoChangesNotice, redactWorkspacePaths } = await import('./working-session-summary.lib.mjs');
|
|
72
|
-
|
|
64
|
+
const { buildNoChangesNotice, formatWorkingSessionSummaryMarkdown, redactWorkspacePaths } = await import('./working-session-summary.lib.mjs');
|
|
73
65
|
/**
|
|
74
66
|
* Placeholder patterns used to detect auto-generated PR content that was not updated by the agent.
|
|
75
67
|
* These patterns match the initial WIP PR created by solve.auto-pr.lib.mjs.
|
|
@@ -77,7 +69,6 @@ const { buildNoChangesNotice, redactWorkspacePaths } = await import('./working-s
|
|
|
77
69
|
export const PR_TITLE_PLACEHOLDER_PREFIX = '[WIP]';
|
|
78
70
|
|
|
79
71
|
export const PR_BODY_PLACEHOLDER_PATTERNS = ['_Details will be added as the solution draft is developed..._', '**Work in Progress** - The AI assistant is currently analyzing and implementing the solution draft.', '### š§ Status'];
|
|
80
|
-
|
|
81
72
|
/**
|
|
82
73
|
* Check if PR title still contains auto-generated placeholder content
|
|
83
74
|
* @param {string} title - PR title
|
|
@@ -95,7 +86,6 @@ export const hasPRTitlePlaceholder = title => {
|
|
|
95
86
|
export const hasPRBodyPlaceholder = body => {
|
|
96
87
|
return body && PR_BODY_PLACEHOLDER_PATTERNS.some(pattern => body.includes(pattern));
|
|
97
88
|
};
|
|
98
|
-
|
|
99
89
|
/**
|
|
100
90
|
* Build a short factual hint for auto-restart when PR title/description was not updated.
|
|
101
91
|
* Uses neutral, fact-stating language (no forcing words).
|
|
@@ -114,7 +104,6 @@ export const buildPRNotUpdatedHint = (titleNotUpdated, descriptionNotUpdated) =>
|
|
|
114
104
|
}
|
|
115
105
|
return lines;
|
|
116
106
|
};
|
|
117
|
-
|
|
118
107
|
/**
|
|
119
108
|
* Ensure an existing pull request body contains a GitHub closing keyword for the issue.
|
|
120
109
|
*
|
|
@@ -140,7 +129,6 @@ export const ensurePullRequestIssueLink = async ({ prNumber, issueNumber, owner,
|
|
|
140
129
|
await logger(` ā ļø Could not read PR body for issue link check: ${error}`);
|
|
141
130
|
return { checked: false, updated: false, body: prBody, issueRef: buildIssueReference({ issueNumber, owner, repo, fork: argv.fork }), error };
|
|
142
131
|
}
|
|
143
|
-
|
|
144
132
|
prBody = prBodyResult.stdout.toString();
|
|
145
133
|
const linkResult = ensureIssueLinkInPullRequestBody(prBody, {
|
|
146
134
|
issueNumber,
|
|
@@ -153,9 +141,7 @@ export const ensurePullRequestIssueLink = async ({ prNumber, issueNumber, owner,
|
|
|
153
141
|
await logger(' ā
PR body already contains issue reference');
|
|
154
142
|
return { checked: true, updated: false, body: linkResult.body, issueRef: linkResult.issueRef };
|
|
155
143
|
}
|
|
156
|
-
|
|
157
144
|
await logger(` š Updating PR body to link issue #${issueNumber}...`);
|
|
158
|
-
|
|
159
145
|
const fs = (await use('fs')).promises;
|
|
160
146
|
const tempBodyFile = `/tmp/pr-body-update-${prNumber}-${Date.now()}.md`;
|
|
161
147
|
await writeSanitizedPublicationFile(tempBodyFile, linkResult.body);
|
|
@@ -163,7 +149,6 @@ export const ensurePullRequestIssueLink = async ({ prNumber, issueNumber, owner,
|
|
|
163
149
|
try {
|
|
164
150
|
const updateResult = await command`gh pr edit ${prNumber} --repo ${owner}/${repo} --body-file ${tempBodyFile}`;
|
|
165
151
|
await fs.unlink(tempBodyFile).catch(() => {});
|
|
166
|
-
|
|
167
152
|
if (updateResult.code === 0) {
|
|
168
153
|
await logger(` ā
Updated PR body to include "Fixes ${linkResult.issueRef}"`);
|
|
169
154
|
return { checked: true, updated: true, body: linkResult.body, issueRef: linkResult.issueRef };
|
|
@@ -177,7 +162,6 @@ export const ensurePullRequestIssueLink = async ({ prNumber, issueNumber, owner,
|
|
|
177
162
|
throw updateError;
|
|
178
163
|
}
|
|
179
164
|
};
|
|
180
|
-
|
|
181
165
|
export const verifyPullRequestIssueLinkAfterAutoRestart = async ({ prNumber, issueNumber, owner, repo, argv = {}, cleanErrorMessage = error => error.message }) => {
|
|
182
166
|
if (!prNumber) {
|
|
183
167
|
return { checked: false, updated: false, body: '', issueRef: buildIssueReference({ issueNumber, owner, repo, fork: argv.fork }) };
|
|
@@ -191,7 +175,6 @@ export const verifyPullRequestIssueLinkAfterAutoRestart = async ({ prNumber, iss
|
|
|
191
175
|
return { checked: false, updated: false, body: '', issueRef: buildIssueReference({ issueNumber, owner, repo, fork: argv.fork }), error: issueLinkError.message };
|
|
192
176
|
}
|
|
193
177
|
};
|
|
194
|
-
|
|
195
178
|
/**
|
|
196
179
|
* Detect the CLAUDE.md or .gitkeep commit hash from branch structure when not available in session
|
|
197
180
|
* This handles continue mode where the commit hash was lost between sessions
|
|
@@ -209,7 +192,6 @@ export const verifyPullRequestIssueLinkAfterAutoRestart = async ({ prNumber, iss
|
|
|
209
192
|
const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
210
193
|
try {
|
|
211
194
|
await log(' Attempting to detect CLAUDE.md or .gitkeep commit from branch structure...', { verbose: true });
|
|
212
|
-
|
|
213
195
|
// First check if CLAUDE.md or .gitkeep exists in current branch
|
|
214
196
|
const claudeMdExistsResult = await $({ cwd: tempDir })`git ls-files CLAUDE.md 2>&1`;
|
|
215
197
|
const gitkeepExistsResult = await $({ cwd: tempDir })`git ls-files .gitkeep 2>&1`;
|
|
@@ -220,7 +202,6 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
220
202
|
await log(' Neither CLAUDE.md nor .gitkeep exists in current branch', { verbose: true });
|
|
221
203
|
return null;
|
|
222
204
|
}
|
|
223
|
-
|
|
224
205
|
// Get the default branch to find the fork point
|
|
225
206
|
const defaultBranchResult = await $({ cwd: tempDir })`git symbolic-ref refs/remotes/origin/HEAD 2>&1`;
|
|
226
207
|
let defaultBranch = 'main';
|
|
@@ -240,7 +221,6 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
240
221
|
}
|
|
241
222
|
const mergeBase = mergeBaseResult.stdout.toString().trim();
|
|
242
223
|
await log(` Merge base: ${mergeBase.substring(0, 7)}`, { verbose: true });
|
|
243
|
-
|
|
244
224
|
// Get all commits on the PR branch (commits after the merge base)
|
|
245
225
|
// Format: hash|message|files_changed
|
|
246
226
|
const branchCommitsResult = await $({ cwd: tempDir })`git log ${mergeBase}..HEAD --reverse --format="%H|%s" 2>&1`;
|
|
@@ -248,7 +228,6 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
248
228
|
await log(' No commits found on PR branch', { verbose: true });
|
|
249
229
|
return null;
|
|
250
230
|
}
|
|
251
|
-
|
|
252
231
|
const branchCommits = branchCommitsResult.stdout.toString().trim().split('\n').filter(Boolean);
|
|
253
232
|
if (branchCommits.length === 0) {
|
|
254
233
|
await log(' No commits found on PR branch', { verbose: true });
|
|
@@ -256,7 +235,6 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
256
235
|
}
|
|
257
236
|
|
|
258
237
|
await log(` Found ${branchCommits.length} commit(s) on PR branch`, { verbose: true });
|
|
259
|
-
|
|
260
238
|
// Safety check: Must have at least 2 commits (CLAUDE.md commit + actual work)
|
|
261
239
|
if (branchCommits.length < 2) {
|
|
262
240
|
await log(' Only 1 commit on branch - not enough commits to safely revert CLAUDE.md', { verbose: true });
|
|
@@ -267,14 +245,12 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
267
245
|
// Get the first commit on the PR branch
|
|
268
246
|
const firstCommitLine = branchCommits[0];
|
|
269
247
|
const [firstCommitHash, firstCommitMessage] = firstCommitLine.split('|');
|
|
270
|
-
|
|
271
248
|
await log(` First commit on branch: ${firstCommitHash.substring(0, 7)} - "${firstCommitMessage}"`, {
|
|
272
249
|
verbose: true,
|
|
273
250
|
});
|
|
274
251
|
|
|
275
252
|
// Safety check: Verify commit message matches expected pattern (CLAUDE.md or .gitkeep)
|
|
276
253
|
const expectedMessagePatterns = [/^Initial commit with task details/i, /^Add CLAUDE\.md/i, /^CLAUDE\.md/i, /^Add \.gitkeep/i, /\.gitkeep/i];
|
|
277
|
-
|
|
278
254
|
const messageMatches = expectedMessagePatterns.some(pattern => pattern.test(firstCommitMessage));
|
|
279
255
|
if (!messageMatches) {
|
|
280
256
|
await log(' First commit message does not match expected pattern', { verbose: true });
|
|
@@ -283,7 +259,6 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
283
259
|
});
|
|
284
260
|
return null;
|
|
285
261
|
}
|
|
286
|
-
|
|
287
262
|
// Safety check: Verify the commit ONLY adds CLAUDE.md or .gitkeep file (no other files)
|
|
288
263
|
const filesChangedResult = await $({
|
|
289
264
|
cwd: tempDir,
|
|
@@ -295,7 +270,6 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
295
270
|
|
|
296
271
|
const filesChanged = filesChangedResult.stdout.toString().trim().split('\n').filter(Boolean);
|
|
297
272
|
await log(` Files changed in first commit: ${filesChanged.join(', ')}`, { verbose: true });
|
|
298
|
-
|
|
299
273
|
// Check if CLAUDE.md or .gitkeep is in the files changed
|
|
300
274
|
const hasClaudeMd = filesChanged.includes('CLAUDE.md');
|
|
301
275
|
const hasGitkeep = filesChanged.includes('.gitkeep');
|
|
@@ -305,7 +279,6 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
305
279
|
}
|
|
306
280
|
|
|
307
281
|
const targetFile = hasClaudeMd ? 'CLAUDE.md' : '.gitkeep';
|
|
308
|
-
|
|
309
282
|
// CRITICAL SAFETY CHECK: Only allow revert if the target file is the ONLY file changed
|
|
310
283
|
// This prevents Issue #617 where reverting a commit deleted .gitignore, LICENSE, README.md
|
|
311
284
|
if (filesChanged.length > 1) {
|
|
@@ -316,7 +289,6 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
316
289
|
await log(' Refusing to revert to prevent data loss (Issue #617 safety)', { verbose: true });
|
|
317
290
|
return null;
|
|
318
291
|
}
|
|
319
|
-
|
|
320
292
|
// All safety checks passed!
|
|
321
293
|
await log(` ā
Detected ${targetFile} commit: ${firstCommitHash.substring(0, 7)}`, { verbose: true });
|
|
322
294
|
await log(` ā
Commit only contains ${targetFile} (safe to revert)`, { verbose: true });
|
|
@@ -334,7 +306,6 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
|
334
306
|
return null;
|
|
335
307
|
}
|
|
336
308
|
};
|
|
337
|
-
|
|
338
309
|
const wasFileTouchedAfterCommit = async (tempDir, commitHash, fileName) => {
|
|
339
310
|
const changedCommitsResult = await $({ cwd: tempDir, silent: true })`git log --format=%H ${commitHash}..HEAD -- ${fileName}`;
|
|
340
311
|
if (changedCommitsResult.code === 0) {
|
|
@@ -345,7 +316,6 @@ const wasFileTouchedAfterCommit = async (tempDir, commitHash, fileName) => {
|
|
|
345
316
|
await log(` Could not inspect ${fileName} changes after initial commit`, { verbose: true });
|
|
346
317
|
await log(` git log output: ${changedCommitsResult.stderr || changedCommitsResult.stdout || 'no output'}`, { verbose: true });
|
|
347
318
|
}
|
|
348
|
-
|
|
349
319
|
return true;
|
|
350
320
|
};
|
|
351
321
|
|
|
@@ -357,14 +327,12 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
357
327
|
if (!claudeCommitHash) {
|
|
358
328
|
await log(' No initial commit hash from session, attempting to detect from branch...', { verbose: true });
|
|
359
329
|
claudeCommitHash = await detectClaudeMdCommitFromBranch(tempDir, branchName);
|
|
360
|
-
|
|
361
330
|
if (!claudeCommitHash) {
|
|
362
331
|
await log(' Could not safely detect initial commit to revert', { verbose: true });
|
|
363
332
|
return;
|
|
364
333
|
}
|
|
365
334
|
await log(` Detected initial commit: ${claudeCommitHash.substring(0, 7)}`, { verbose: true });
|
|
366
335
|
}
|
|
367
|
-
|
|
368
336
|
// Determine which file was used based on the commit message or actual files changed
|
|
369
337
|
// Use %B (full message including body) instead of %s (subject only) to catch ".gitkeep" in body
|
|
370
338
|
// Also check the actual files changed as a fallback (Issue #1436)
|
|
@@ -379,7 +347,6 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
379
347
|
isGitkeepFile = files.includes('.gitkeep');
|
|
380
348
|
}
|
|
381
349
|
const fileName = isGitkeepFile ? '.gitkeep' : 'CLAUDE.md';
|
|
382
|
-
|
|
383
350
|
await log(formatAligned('š', 'Cleanup:', `Reverting ${fileName} commit`));
|
|
384
351
|
await log(` Using saved commit hash: ${claudeCommitHash.substring(0, 7)}...`, { verbose: true });
|
|
385
352
|
|
|
@@ -391,9 +358,7 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
391
358
|
} else {
|
|
392
359
|
throw new Error(`git pull failed (code ${pullResult.code}): ${pullResult.stdout || pullResult.stderr || 'no output'}`);
|
|
393
360
|
}
|
|
394
|
-
|
|
395
361
|
const commitToRevert = claudeCommitHash;
|
|
396
|
-
|
|
397
362
|
// Issue #1791: .gitkeep is a normal repository file in some projects, and
|
|
398
363
|
// user work may intentionally edit or delete it. Once later PR commits touch
|
|
399
364
|
// .gitkeep, final cleanup must not restore the pre-session version.
|
|
@@ -410,7 +375,6 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
410
375
|
// Issue #2135: `mirror: false`. Only "is it non-empty" is asked here, and
|
|
411
376
|
// the answer is a file's whole diff.
|
|
412
377
|
const diffResult = await $({ cwd: tempDir, ...QUIET_PROBE })`git diff ${commitToRevert} HEAD -- ${fileName} 2>&1`;
|
|
413
|
-
|
|
414
378
|
if (diffResult.stdout && diffResult.stdout.trim()) {
|
|
415
379
|
// File was modified after initial commit - use manual approach to avoid conflicts
|
|
416
380
|
await log(` ${fileName} was modified after initial commit, using manual cleanup...`, { verbose: true });
|
|
@@ -418,7 +382,6 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
418
382
|
// Get the state of the file from before the initial commit (parent of the commit we're reverting)
|
|
419
383
|
const parentCommit = `${commitToRevert}~1`;
|
|
420
384
|
const parentFileExists = await $({ cwd: tempDir })`git cat-file -e ${parentCommit}:${fileName} 2>&1`;
|
|
421
|
-
|
|
422
385
|
if (parentFileExists.code === 0) {
|
|
423
386
|
// File existed before the initial commit - restore it to that state
|
|
424
387
|
await log(` ${fileName} existed before session, restoring to previous state...`, { verbose: true });
|
|
@@ -431,10 +394,8 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
431
394
|
|
|
432
395
|
// Create a manual revert commit
|
|
433
396
|
const commitResult = await $({ cwd: tempDir })`git commit -m "Revert: Remove ${fileName} changes from initial commit" 2>&1`;
|
|
434
|
-
|
|
435
397
|
if (commitResult.code === 0) {
|
|
436
398
|
await log(formatAligned('š¦', 'Committed:', `${fileName} revert (manual)`));
|
|
437
|
-
|
|
438
399
|
// Push the revert
|
|
439
400
|
const pushRevertResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
440
401
|
if (pushRevertResult.code === 0) {
|
|
@@ -454,7 +415,6 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
454
415
|
const revertResult = await $({ cwd: tempDir })`git revert ${commitToRevert} --no-edit 2>&1`;
|
|
455
416
|
if (revertResult.code === 0) {
|
|
456
417
|
await log(formatAligned('š¦', 'Committed:', `${fileName} revert`));
|
|
457
|
-
|
|
458
418
|
// Push the revert
|
|
459
419
|
const pushRevertResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
460
420
|
if (pushRevertResult.code === 0) {
|
|
@@ -469,11 +429,9 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
469
429
|
|
|
470
430
|
if (hasConflict) {
|
|
471
431
|
await log(' Unexpected conflict detected, attempting automatic resolution...', { verbose: true });
|
|
472
|
-
|
|
473
432
|
// Check git status to see what files are in conflict
|
|
474
433
|
const statusResult = await $({ cwd: tempDir })`git status --short 2>&1`;
|
|
475
434
|
const statusOutput = statusResult.stdout || '';
|
|
476
|
-
|
|
477
435
|
// Check if the file is in the conflict
|
|
478
436
|
if (statusOutput.includes(fileName)) {
|
|
479
437
|
await log(` Resolving ${fileName} conflict by restoring pre-session state...`, { verbose: true });
|
|
@@ -481,7 +439,6 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
481
439
|
// Get the state of the file from before the initial commit (parent of the commit we're reverting)
|
|
482
440
|
const parentCommit = `${commitToRevert}~1`;
|
|
483
441
|
const parentFileExists = await $({ cwd: tempDir })`git cat-file -e ${parentCommit}:${fileName} 2>&1`;
|
|
484
|
-
|
|
485
442
|
if (parentFileExists.code === 0) {
|
|
486
443
|
// File existed before the initial commit - restore it to that state
|
|
487
444
|
await log(` ${fileName} existed before session, restoring to previous state...`, { verbose: true });
|
|
@@ -497,7 +454,6 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
497
454
|
|
|
498
455
|
// Complete the revert with the resolved conflict
|
|
499
456
|
const continueResult = await $({ cwd: tempDir })`git revert --continue --no-edit 2>&1`;
|
|
500
|
-
|
|
501
457
|
if (continueResult.code === 0) {
|
|
502
458
|
await log(formatAligned('š¦', 'Committed:', `${fileName} revert (conflict resolved)`));
|
|
503
459
|
|
|
@@ -559,11 +515,9 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
|
|
|
559
515
|
await log(' Initial commit revert failed or not needed', { verbose: true });
|
|
560
516
|
}
|
|
561
517
|
};
|
|
562
|
-
|
|
563
518
|
// Show session summary and handle limit reached scenarios
|
|
564
519
|
export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl, tempDir, shouldAttachLogs = false) => {
|
|
565
520
|
await log('\n=== Session Summary ===');
|
|
566
|
-
|
|
567
521
|
// Issue #1745: report how many tokens were masked during this run, with the
|
|
568
522
|
// "use --dangerously-skip-output-sanitization to skip" hint when > 0.
|
|
569
523
|
try {
|
|
@@ -580,7 +534,6 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
|
|
|
580
534
|
// Always use absolute path for log file display
|
|
581
535
|
const absoluteLogPath = path.resolve(getLogFile());
|
|
582
536
|
await log(`ā
Complete log file: ${absoluteLogPath}`);
|
|
583
|
-
|
|
584
537
|
// Show three resume options:
|
|
585
538
|
// 1. Interactive claude - opens Claude Code interactively (claude only)
|
|
586
539
|
// 2. Autonomous claude - one-shot claude --resume w/ --dangerously-skip-permissions -p (claude only)
|
|
@@ -601,7 +554,6 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
|
|
|
601
554
|
|
|
602
555
|
if (limitReached) {
|
|
603
556
|
await log('ā° LIMIT REACHED DETECTED!');
|
|
604
|
-
|
|
605
557
|
if ((argv.autoResumeOnLimitReset || argv.autoRestartOnLimitReset) && global.limitResetTime) {
|
|
606
558
|
const isRestart = !!argv.autoRestartOnLimitReset;
|
|
607
559
|
await log(`\nš AUTO-${isRestart ? 'RESTART' : 'RESUME'} ON LIMIT RESET ENABLED - Will ${isRestart ? 'restart' : 'resume'} at ${global.limitResetTime}`);
|
|
@@ -612,7 +564,6 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
|
|
|
612
564
|
if (global.limitResetTime) {
|
|
613
565
|
await log(`\nā° Limit resets at: ${global.limitResetTime}`);
|
|
614
566
|
}
|
|
615
|
-
|
|
616
567
|
await log('\nš” After the limit resets, resume using the command above.');
|
|
617
568
|
|
|
618
569
|
if (argv.autoCleanup !== false) {
|
|
@@ -628,7 +579,6 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
|
|
|
628
579
|
await log(' To keep the directory for debugging or resuming, use --no-auto-cleanup');
|
|
629
580
|
}
|
|
630
581
|
}
|
|
631
|
-
|
|
632
582
|
// Don't show log preview, it's too technical
|
|
633
583
|
} else {
|
|
634
584
|
// For agent tool, session IDs may not be meaningful for resuming, so don't show as error
|
|
@@ -669,7 +619,6 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
|
|
|
669
619
|
await log(`ā ļø Post-finish sanitization sweep failed: ${sweepErr.message || sweepErr}`);
|
|
670
620
|
}
|
|
671
621
|
};
|
|
672
|
-
|
|
673
622
|
// Build token/context data once so every end-of-session publication can use the
|
|
674
623
|
// same observed facts (working-session summary and attached log alike).
|
|
675
624
|
export const buildSessionBudgetStatsData = async ({ argv, sessionId = null, tempDir = null, resultModelUsage = null, streamTokenUsage = null, subAgentCalls = null, pricingInfo = null }) => {
|
|
@@ -714,7 +663,6 @@ export const buildSessionBudgetStatsData = async ({ argv, sessionId = null, temp
|
|
|
714
663
|
// Verify results by searching for new PRs and comments
|
|
715
664
|
export const verifyResults = async (owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, argv, shouldAttachLogs, shouldRestart = false, sessionId = null, tempDir = null, anthropicTotalCostUSD = null, publicPricingEstimate = null, pricingInfo = null, errorDuringExecution = false, sessionType = 'new', resultModelUsage = null, streamTokenUsage = null, subAgentCalls = null, precomputedBudgetStatsData = null) => {
|
|
716
665
|
await log('\nš Searching for created pull requests or comments...');
|
|
717
|
-
|
|
718
666
|
// Issue #1491, #1526, #2115: reuse data already calculated for the working
|
|
719
667
|
// session summary; retain the fallback for callers that do not precompute it.
|
|
720
668
|
const budgetStatsData =
|
|
@@ -728,7 +676,6 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
728
676
|
subAgentCalls,
|
|
729
677
|
pricingInfo,
|
|
730
678
|
}));
|
|
731
|
-
|
|
732
679
|
try {
|
|
733
680
|
// Get the current user's GitHub username
|
|
734
681
|
const userResult = await $(QUIET_PROBE)`gh api user --jq .login`;
|
|
@@ -736,7 +683,6 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
736
683
|
if (userResult.code !== 0) {
|
|
737
684
|
throw new Error(`Failed to get current user: ${userResult.stderr ? userResult.stderr.toString() : 'Unknown error'}`);
|
|
738
685
|
}
|
|
739
|
-
|
|
740
686
|
const currentUser = userResult.stdout.toString().trim();
|
|
741
687
|
if (!currentUser) {
|
|
742
688
|
throw new Error('Unable to determine current GitHub user');
|
|
@@ -744,20 +690,17 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
744
690
|
|
|
745
691
|
// Search for pull requests created from our branch
|
|
746
692
|
await log('\nš Checking for pull requests from branch ' + branchName + '...');
|
|
747
|
-
|
|
748
693
|
// First, get all PRs from our branch
|
|
749
694
|
// IMPORTANT: Use --state all to find PRs that may have been merged during the session (Issue #1008)
|
|
750
695
|
// Without --state all, gh pr list only returns OPEN PRs, missing merged ones
|
|
751
696
|
// Issue #2135: `mirror: false` - the pull requests found are named below.
|
|
752
697
|
const allBranchPrsResult = await $(QUIET_PROBE)`gh pr list --repo ${owner}/${repo} --head ${branchName} --state all --json number,url,createdAt,headRefName,title,state,updatedAt,isDraft`;
|
|
753
|
-
|
|
754
698
|
if (allBranchPrsResult.code !== 0) {
|
|
755
699
|
await log(' ā ļø Failed to check pull requests');
|
|
756
700
|
// Continue with empty list
|
|
757
701
|
}
|
|
758
702
|
|
|
759
703
|
const allBranchPrs = allBranchPrsResult.stdout.toString().trim() ? JSON.parse(allBranchPrsResult.stdout.toString().trim()) : [];
|
|
760
|
-
|
|
761
704
|
// Check if we have any PRs from our branch
|
|
762
705
|
// If auto-PR was created, it should be the one we're working on
|
|
763
706
|
if (allBranchPrs.length > 0) {
|
|
@@ -766,7 +709,6 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
766
709
|
// If we created a PR earlier in this session, it would be prNumber
|
|
767
710
|
// Or if the PR was updated during the session (updatedAt > referenceTime)
|
|
768
711
|
const isPrFromSession = (prNumber && pr.number.toString() === prNumber) || (prUrl && pr.url === prUrl) || new Date(pr.updatedAt) > referenceTime || new Date(pr.createdAt) > referenceTime;
|
|
769
|
-
|
|
770
712
|
if (isPrFromSession) {
|
|
771
713
|
await log(` ā
Found pull request #${pr.number}: "${pr.title}"`);
|
|
772
714
|
|
|
@@ -775,11 +717,9 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
775
717
|
if (isPrMerged) {
|
|
776
718
|
await log(` ā¹ļø PR #${pr.number} was merged during the session`);
|
|
777
719
|
}
|
|
778
|
-
|
|
779
720
|
// Declare placeholder detection variables outside block scopes for use in return value
|
|
780
721
|
let prTitleHasPlaceholder = false;
|
|
781
722
|
let prBodyHasPlaceholder = false;
|
|
782
|
-
|
|
783
723
|
// Skip PR body update and ready conversion for merged PRs (they can't be edited)
|
|
784
724
|
if (!isPrMerged) {
|
|
785
725
|
const issueLinkResult = await ensurePullRequestIssueLink({
|
|
@@ -797,7 +737,6 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
797
737
|
// Track this before cleanup for --auto-restart-on-non-updated-pull-request-description
|
|
798
738
|
prTitleHasPlaceholder = hasPRTitlePlaceholder(pr.title);
|
|
799
739
|
prBodyHasPlaceholder = hasPRBodyPlaceholder(prBody);
|
|
800
|
-
|
|
801
740
|
// Issue #1162: Remove [WIP] prefix from title if still present
|
|
802
741
|
// Skip cleanup if auto-restart-on-non-updated-pull-request-description is enabled
|
|
803
742
|
// (let the agent handle it on restart instead)
|
|
@@ -817,7 +756,6 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
817
756
|
const hasPlaceholder = prBodyHasPlaceholder;
|
|
818
757
|
if (hasPlaceholder && !argv.autoRestartOnNonUpdatedPullRequestDescription) {
|
|
819
758
|
await log(` š Updating PR description to remove placeholder text...`);
|
|
820
|
-
|
|
821
759
|
// Issue #2119: measure the net diff. The reproduction PRs published
|
|
822
760
|
// "1 file(s) modified, 1 line(s) added" for a pull request that
|
|
823
761
|
// changed nothing, because the stats were never checked for being
|
|
@@ -826,7 +764,6 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
826
764
|
if (!changeStats.hasChanges) {
|
|
827
765
|
await log(` ā ļø PR #${pr.number} has an empty diff - the description will say so instead of claiming changes`, { level: 'warning' });
|
|
828
766
|
}
|
|
829
|
-
|
|
830
767
|
// Get the issue title for context
|
|
831
768
|
const issueTitleResult = await $`gh issue view ${issueNumber} --repo ${owner}/${repo} --json title --jq .title 2>&1`;
|
|
832
769
|
const issueTitle = issueTitleResult.code === 0 ? issueTitleResult.stdout.toString().trim() : 'the issue';
|
|
@@ -846,10 +783,8 @@ Fixes ${issueRef}
|
|
|
846
783
|
|
|
847
784
|
---
|
|
848
785
|
*This PR was created automatically by the AI issue solver*`;
|
|
849
|
-
|
|
850
786
|
const tempBodyFile = `/tmp/pr-body-finalize-${pr.number}-${Date.now()}.md`;
|
|
851
787
|
await writeSanitizedPublicationFile(tempBodyFile, newDescription);
|
|
852
|
-
|
|
853
788
|
try {
|
|
854
789
|
const descResult = await $`gh pr edit ${pr.number} --repo ${owner}/${repo} --body-file ${tempBodyFile}`;
|
|
855
790
|
await fs.unlink(tempBodyFile).catch(() => {});
|
|
@@ -864,7 +799,6 @@ Fixes ${issueRef}
|
|
|
864
799
|
await log(` ā ļø Error updating PR description: ${descError.message}`);
|
|
865
800
|
}
|
|
866
801
|
}
|
|
867
|
-
|
|
868
802
|
// Check if PR is ready for review (convert from draft if necessary)
|
|
869
803
|
if (pr.isDraft) {
|
|
870
804
|
await log(' š Converting PR from draft to ready for review...');
|
|
@@ -913,7 +847,6 @@ Fixes ${issueRef}
|
|
|
913
847
|
budgetStatsData,
|
|
914
848
|
});
|
|
915
849
|
}
|
|
916
|
-
|
|
917
850
|
await log('\nš SUCCESS: A solution draft has been prepared as a pull request');
|
|
918
851
|
await log(`š URL: ${pr.url}`);
|
|
919
852
|
if (shouldAttachLogs && logUploadSuccess) {
|
|
@@ -945,7 +878,6 @@ Fixes ${issueRef}
|
|
|
945
878
|
} else {
|
|
946
879
|
await log(` ā¹ļø No pull requests found from branch ${branchName}`);
|
|
947
880
|
}
|
|
948
|
-
|
|
949
881
|
// If no PR found, search for recent comments on the issue
|
|
950
882
|
await log('\nš Checking for new comments on issue #' + issueNumber + '...');
|
|
951
883
|
|
|
@@ -954,21 +886,18 @@ Fixes ${issueRef}
|
|
|
954
886
|
// Issue #2135: `mirror: false` - the counts below are the report; the raw
|
|
955
887
|
// answer is every comment body on the issue.
|
|
956
888
|
const allCommentsResult = await $(QUIET_PROBE)`gh api repos/${owner}/${repo}/issues/${issueNumber}/comments --paginate`;
|
|
957
|
-
|
|
958
889
|
if (allCommentsResult.code !== 0) {
|
|
959
890
|
await log(' ā ļø Failed to check comments');
|
|
960
891
|
// Continue with empty list
|
|
961
892
|
}
|
|
962
893
|
|
|
963
894
|
const allComments = JSON.parse(allCommentsResult.stdout.toString().trim() || '[]');
|
|
964
|
-
|
|
965
895
|
// Filter for new comments by current user
|
|
966
896
|
const newCommentsByUser = allComments.filter(comment => comment.user.login === currentUser && new Date(comment.created_at) > referenceTime);
|
|
967
897
|
|
|
968
898
|
if (newCommentsByUser.length > 0) {
|
|
969
899
|
const lastComment = newCommentsByUser[newCommentsByUser.length - 1];
|
|
970
900
|
await log(` ā
Found new comment by ${currentUser}`);
|
|
971
|
-
|
|
972
901
|
// Upload log file to issue if requested
|
|
973
902
|
if (shouldAttachLogs) {
|
|
974
903
|
await log('\nš Uploading solution draft log to issue...');
|
|
@@ -1002,7 +931,6 @@ Fixes ${issueRef}
|
|
|
1002
931
|
budgetStatsData,
|
|
1003
932
|
});
|
|
1004
933
|
}
|
|
1005
|
-
|
|
1006
934
|
await log('\nš¬ SUCCESS: Comment posted on issue');
|
|
1007
935
|
await log(`š URL: ${lastComment.html_url}`);
|
|
1008
936
|
if (shouldAttachLogs) {
|
|
@@ -1059,7 +987,6 @@ Fixes ${issueRef}
|
|
|
1059
987
|
return { logUploadSuccess: false }; // Return for watch mode
|
|
1060
988
|
}
|
|
1061
989
|
};
|
|
1062
|
-
|
|
1063
990
|
// Handle execution errors with log attachment
|
|
1064
991
|
export const handleExecutionError = async (error, shouldAttachLogs, owner, repo, argv = {}) => {
|
|
1065
992
|
const { cleanErrorMessage } = await import('./lib.mjs');
|
|
@@ -1069,7 +996,6 @@ export const handleExecutionError = async (error, shouldAttachLogs, owner, repo,
|
|
|
1069
996
|
// If --attach-logs is enabled, try to attach failure logs
|
|
1070
997
|
if (shouldAttachLogs && getLogFile()) {
|
|
1071
998
|
await log('\nš Attempting to attach failure logs...');
|
|
1072
|
-
|
|
1073
999
|
// Try to attach to existing PR first
|
|
1074
1000
|
if (global.createdPR && global.createdPR.number) {
|
|
1075
1001
|
try {
|
|
@@ -1089,7 +1015,6 @@ export const handleExecutionError = async (error, shouldAttachLogs, owner, repo,
|
|
|
1089
1015
|
requestedModel: argv.originalModel || argv.model,
|
|
1090
1016
|
tool: argv.tool || 'claude',
|
|
1091
1017
|
});
|
|
1092
|
-
|
|
1093
1018
|
if (logUploadSuccess) {
|
|
1094
1019
|
await log('š Failure log attached to Pull Request');
|
|
1095
1020
|
}
|
|
@@ -1123,7 +1048,6 @@ export const handleExecutionError = async (error, shouldAttachLogs, owner, repo,
|
|
|
1123
1048
|
await log(`ā ļø Could not close pull request: ${closeError.message}`, { level: 'warning' });
|
|
1124
1049
|
}
|
|
1125
1050
|
}
|
|
1126
|
-
|
|
1127
1051
|
await safeExit(1, 'Execution error');
|
|
1128
1052
|
};
|
|
1129
1053
|
|
|
@@ -1134,7 +1058,6 @@ export const handleExecutionError = async (error, shouldAttachLogs, owner, repo,
|
|
|
1134
1058
|
// from solve.results.lib.mjs.
|
|
1135
1059
|
const toolComments = await import('./tool-comments.lib.mjs');
|
|
1136
1060
|
export const { TOOL_GENERATED_COMMENT_MARKERS, isToolGeneratedComment, trackToolCommentId, isToolTrackedCommentId, getTrackedToolCommentIds, postTrackedComment } = toolComments;
|
|
1137
|
-
|
|
1138
1061
|
/**
|
|
1139
1062
|
* Check if new comments were created by the AI during the session.
|
|
1140
1063
|
* This is used by --auto-attach-solution-summary to determine if the AI
|
|
@@ -1164,7 +1087,6 @@ export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, p
|
|
|
1164
1087
|
}
|
|
1165
1088
|
|
|
1166
1089
|
await log(`š Checking comments by '${currentUser}' after session start ${sessionStartTime.toISOString()} (PR #${prNumber ?? 'none'}, issue #${issueNumber ?? 'none'})`, { verbose: true });
|
|
1167
|
-
|
|
1168
1090
|
// Issue #1625: A comment counts as an "AI comment" only if it was posted
|
|
1169
1091
|
// by the current user AFTER sessionStartTime AND solve.mjs did NOT post it
|
|
1170
1092
|
// itself. We identify tool-posted comments in two ways, in order:
|
|
@@ -1184,7 +1106,6 @@ export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, p
|
|
|
1184
1106
|
for (const comment of comments) {
|
|
1185
1107
|
if (!comment || !comment.user || comment.user.login !== currentUser) continue;
|
|
1186
1108
|
if (!(new Date(comment.created_at) > sessionStartTime)) continue;
|
|
1187
|
-
|
|
1188
1109
|
const isReview = kind === 'review';
|
|
1189
1110
|
if (!isReview) {
|
|
1190
1111
|
if (isToolTrackedCommentId(comment.id)) {
|
|
@@ -1223,7 +1144,6 @@ export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, p
|
|
|
1223
1144
|
return true;
|
|
1224
1145
|
}
|
|
1225
1146
|
}
|
|
1226
|
-
|
|
1227
1147
|
// Check PR review comments (inline code comments)
|
|
1228
1148
|
const reviewCommentsResult = await $(QUIET_PROBE)`gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate`;
|
|
1229
1149
|
if (reviewCommentsResult.code === 0) {
|
|
@@ -1248,7 +1168,6 @@ export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, p
|
|
|
1248
1168
|
}
|
|
1249
1169
|
}
|
|
1250
1170
|
}
|
|
1251
|
-
|
|
1252
1171
|
return false;
|
|
1253
1172
|
} catch (error) {
|
|
1254
1173
|
// On error, default to not attaching (safer choice)
|
|
@@ -1256,7 +1175,6 @@ export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, p
|
|
|
1256
1175
|
return false;
|
|
1257
1176
|
}
|
|
1258
1177
|
};
|
|
1259
|
-
|
|
1260
1178
|
/**
|
|
1261
1179
|
* Attach the AI's working session summary as a comment to the PR or issue.
|
|
1262
1180
|
* The summary is extracted from the tool's result field and posted
|
|
@@ -1295,7 +1213,6 @@ export const attachSolutionSummary = async ({ resultSummary, prNumber, issueNumb
|
|
|
1295
1213
|
await log('ā ļø No working session summary available to attach', { verbose: true });
|
|
1296
1214
|
return false;
|
|
1297
1215
|
}
|
|
1298
|
-
|
|
1299
1216
|
const targetNumber = prNumber || issueNumber;
|
|
1300
1217
|
const targetType = prNumber ? 'pr' : 'issue';
|
|
1301
1218
|
|
|
@@ -1303,13 +1220,12 @@ export const attachSolutionSummary = async ({ resultSummary, prNumber, issueNumb
|
|
|
1303
1220
|
await log('ā ļø No PR or issue number to attach working session summary to', { verbose: true });
|
|
1304
1221
|
return false;
|
|
1305
1222
|
}
|
|
1306
|
-
|
|
1307
1223
|
try {
|
|
1308
1224
|
// Issue #2119: publish what the session actually produced. The reported
|
|
1309
1225
|
// summary said "The `pwd` command completed" and printed the solver's own
|
|
1310
1226
|
// /tmp workspace, on a pull request that was still empty.
|
|
1311
1227
|
const noChangesNotice = buildNoChangesNotice(changeStats);
|
|
1312
|
-
const summaryBody = redactWorkspacePaths(resultSummary);
|
|
1228
|
+
const summaryBody = formatWorkingSessionSummaryMarkdown(redactWorkspacePaths(resultSummary));
|
|
1313
1229
|
|
|
1314
1230
|
const comment = `${toolComments.WORKING_SESSION_SUMMARY_AUTOMATION_MARKER}
|
|
1315
1231
|
## ${toolComments.WORKING_SESSION_SUMMARY_MARKER}
|
|
@@ -1320,7 +1236,6 @@ ${summaryBody}${noChangesNotice ? `\n\n${noChangesNotice}` : ''}
|
|
|
1320
1236
|
*${toolComments.WORKING_SESSION_SUMMARY_AUTOMATED_FOOTER}*`;
|
|
1321
1237
|
|
|
1322
1238
|
const { ok, commentId, stderr } = await postTrackedComment({ $, owner, repo, targetNumber, body: comment });
|
|
1323
|
-
|
|
1324
1239
|
if (ok) {
|
|
1325
1240
|
await log(`ā
Working session summary attached to ${targetType} #${targetNumber}${commentId ? ` (id=${commentId})` : ''}`);
|
|
1326
1241
|
return true;
|
|
@@ -1375,10 +1290,8 @@ export const maybeAttachWorkingSessionSummary = async ({ argv, resultSummary, wo
|
|
|
1375
1290
|
if (!success) {
|
|
1376
1291
|
return { attached: false, reason: 'iteration_failed' };
|
|
1377
1292
|
}
|
|
1378
|
-
|
|
1379
1293
|
const attachFlag = argv && (argv.attachSolutionSummary || argv['attach-solution-summary']);
|
|
1380
1294
|
const autoAttachFlag = argv && (argv.autoAttachSolutionSummary || argv['auto-attach-solution-summary']);
|
|
1381
|
-
|
|
1382
1295
|
if (!attachFlag && !autoAttachFlag) {
|
|
1383
1296
|
return { attached: false, reason: 'flag_disabled' };
|
|
1384
1297
|
}
|
|
@@ -1387,7 +1300,6 @@ export const maybeAttachWorkingSessionSummary = async ({ argv, resultSummary, wo
|
|
|
1387
1300
|
await log('ā¹ļø No working session summary available from AI tool output', { verbose: true });
|
|
1388
1301
|
return { attached: false, reason: 'no_result_summary' };
|
|
1389
1302
|
}
|
|
1390
|
-
|
|
1391
1303
|
let shouldAttach = false;
|
|
1392
1304
|
if (attachFlag) {
|
|
1393
1305
|
shouldAttach = true;
|
|
@@ -1406,7 +1318,6 @@ export const maybeAttachWorkingSessionSummary = async ({ argv, resultSummary, wo
|
|
|
1406
1318
|
if (!shouldAttach) {
|
|
1407
1319
|
return { attached: false, reason: 'no_attach_decision' };
|
|
1408
1320
|
}
|
|
1409
|
-
|
|
1410
1321
|
const resolvedBudgetStatsData =
|
|
1411
1322
|
budgetStatsData ??
|
|
1412
1323
|
(sessionUsage
|
|
@@ -1419,7 +1330,6 @@ export const maybeAttachWorkingSessionSummary = async ({ argv, resultSummary, wo
|
|
|
1419
1330
|
// Issue #2119: a summary posted on a pull request that changed nothing must
|
|
1420
1331
|
// say so, instead of reading as a report of completed work.
|
|
1421
1332
|
const changeStats = prNumber ? await getPullRequestChangeStats({ owner, repo, prNumber, $, log }) : null;
|
|
1422
|
-
|
|
1423
1333
|
// Issue #2132: the summary carries no cost/budget block. `resolvedBudgetStatsData`
|
|
1424
1334
|
// is computed only so the caller can reuse it for this session's log comment.
|
|
1425
1335
|
const ok = await attachSolutionSummary({
|