@link-assistant/hive-mind 1.53.0 → 1.54.0

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/src/solve.mjs CHANGED
@@ -54,6 +54,10 @@ const { handleAutoPrCreation } = await import('./solve.auto-pr.lib.mjs');
54
54
  const { setupRepositoryAndClone, verifyDefaultBranchAndStatus } = await import('./solve.repo-setup.lib.mjs');
55
55
  const { createOrCheckoutBranch } = await import('./solve.branch.lib.mjs');
56
56
  const { startWorkSession, endWorkSession, SESSION_TYPES } = await import('./solve.session.lib.mjs');
57
+ // Issue #1625: centralized markers + tracked comment posting for solve.mjs's
58
+ // own usage-limit notifications (so they're excluded from the
59
+ // "did the AI post anything?" check in --auto-attach-solution-summary).
60
+ const { postTrackedComment, USAGE_LIMIT_REACHED_MARKER } = await import('./tool-comments.lib.mjs');
57
61
  const { prepareFeedbackAndTimestamps, checkUncommittedChanges, checkForkActions } = await import('./solve.preparation.lib.mjs');
58
62
  const { validateAndExitOnInvalidModel } = await import('./models/index.mjs');
59
63
  const { autoAcceptInviteForRepo } = await import('./solve.accept-invite.lib.mjs');
@@ -964,11 +968,11 @@ try {
964
968
  // Format the reset time with relative time and UTC conversion if available
965
969
  const timezone = global.limitTimezone || null;
966
970
  const formattedResetTime = resetTime ? formatResetTimeWithRelative(resetTime, timezone) : null;
967
- const failureComment = formattedResetTime ? `❌ **Usage Limit Reached**\n\nThe AI tool has reached its usage limit. The limit will reset at: **${formattedResetTime}**\n\n${resumeSection}` : `❌ **Usage Limit Reached**\n\nThe AI tool has reached its usage limit. Please wait for the limit to reset.\n\n${resumeSection}`;
971
+ const failureComment = formattedResetTime ? `❌ **${USAGE_LIMIT_REACHED_MARKER}**\n\nThe AI tool has reached its usage limit. The limit will reset at: **${formattedResetTime}**\n\n${resumeSection}` : `❌ **${USAGE_LIMIT_REACHED_MARKER}**\n\nThe AI tool has reached its usage limit. Please wait for the limit to reset.\n\n${resumeSection}`;
968
972
 
969
- const commentResult = await $`gh pr comment ${prNumber} --repo ${owner}/${repo} --body ${failureComment}`;
970
- if (commentResult.code === 0) {
971
- await log(' Posted failure comment to PR');
973
+ const posted = await postTrackedComment({ $, owner, repo, targetNumber: prNumber, body: failureComment });
974
+ if (posted.ok) {
975
+ await log(` Posted failure comment to PR${posted.commentId ? ` (id=${posted.commentId})` : ''}`);
972
976
  }
973
977
  } catch (error) {
974
978
  await log(` Warning: Could not post failure comment: ${cleanErrorMessage(error)}`, { verbose: true });
@@ -1049,11 +1053,11 @@ try {
1049
1053
  // Format reset time with relative time and UTC for better user understanding
1050
1054
  // See: https://github.com/link-assistant/hive-mind/issues/1236
1051
1055
  const waitingResetTimeFormatted = formatResetTimeWithRelative(global.limitResetTime, global.limitTimezone || null) || global.limitResetTime;
1052
- const waitingComment = `⏳ **Usage Limit Reached - Waiting to ${limitContinueMode === 'restart' ? 'Restart' : 'Continue'}**\n\nThe AI tool has reached its usage limit. ${continueModeName} is enabled.\n\n**Reset time:** ${waitingResetTimeFormatted}\n**Wait time:** ${formatWaitTime(waitMs)} (days:hours:minutes:seconds)\n\n${continueDescription}\n\nSession ID: \`${sessionId}\``;
1056
+ const waitingComment = `⏳ **${USAGE_LIMIT_REACHED_MARKER} - Waiting to ${limitContinueMode === 'restart' ? 'Restart' : 'Continue'}**\n\nThe AI tool has reached its usage limit. ${continueModeName} is enabled.\n\n**Reset time:** ${waitingResetTimeFormatted}\n**Wait time:** ${formatWaitTime(waitMs)} (days:hours:minutes:seconds)\n\n${continueDescription}\n\nSession ID: \`${sessionId}\``;
1053
1057
 
1054
- const commentResult = await $`gh pr comment ${prNumber} --repo ${owner}/${repo} --body ${waitingComment}`;
1055
- if (commentResult.code === 0) {
1056
- await log(' Posted waiting comment to PR');
1058
+ const posted = await postTrackedComment({ $, owner, repo, targetNumber: prNumber, body: waitingComment });
1059
+ if (posted.ok) {
1060
+ await log(` Posted waiting comment to PR${posted.commentId ? ` (id=${posted.commentId})` : ''}`);
1057
1061
  }
1058
1062
  } catch (error) {
1059
1063
  await log(` Warning: Could not post waiting comment: ${cleanErrorMessage(error)}`, { verbose: true });
@@ -24,6 +24,10 @@
24
24
  * @experimental
25
25
  */
26
26
 
27
+ // Issue #1625: centralized markers + tracking helpers so the live-progress
28
+ // comment is excluded from --auto-attach-solution-summary's AI-comment check.
29
+ import { LIVE_PROGRESS_SECTION_START_MARKER, LIVE_PROGRESS_SECTION_END_MARKER, postTrackedCommentFromFile, trackToolCommentId } from './tool-comments.lib.mjs';
30
+
27
31
  /**
28
32
  * Configuration constants for progress monitoring
29
33
  */
@@ -33,9 +37,10 @@ const CONFIG = {
33
37
  // Progress bar characters
34
38
  PROGRESS_CHAR_FILLED: '█',
35
39
  PROGRESS_CHAR_EMPTY: '░',
36
- // Marker comments for identifying the progress section
37
- PROGRESS_SECTION_START: '<!-- LIVE-PROGRESS-START -->',
38
- PROGRESS_SECTION_END: '<!-- LIVE-PROGRESS-END -->',
40
+ // Marker comments for identifying the progress section (imported from
41
+ // tool-comments.lib.mjs — single source of truth).
42
+ PROGRESS_SECTION_START: LIVE_PROGRESS_SECTION_START_MARKER,
43
+ PROGRESS_SECTION_END: LIVE_PROGRESS_SECTION_END_MARKER,
39
44
  // Minimum interval between PR description updates (in ms)
40
45
  MIN_UPDATE_INTERVAL: 10000, // 10 seconds to avoid rate limiting
41
46
  // Valid display modes
@@ -223,25 +228,25 @@ export const createProgressMonitor = ({ owner, repo, prNumber, $, log, verbose =
223
228
  await $`gh api repos/${owner}/${repo}/issues/comments/${state.commentId} --method PATCH --field body=@${tempFile}`;
224
229
  await fs.unlink(tempFile).catch(() => {});
225
230
  } else {
226
- // Create new comment
231
+ // Create new comment. Issue #1625: post via postTrackedCommentFromFile
232
+ // so the comment ID is captured directly from the GitHub API response
233
+ // (and registered in the in-memory tracking set that excludes tool-
234
+ // posted comments from the "did the AI post anything?" check).
227
235
  const fs = (await import('fs')).promises;
228
236
  const tempFile = `/tmp/pr-progress-comment-${prNumber}-${Date.now()}.md`;
229
237
  await fs.writeFile(tempFile, progressSection);
230
- const result = await $`gh pr comment ${prNumber} --repo ${owner}/${repo} --body-file ${tempFile}`;
238
+ const posted = await postTrackedCommentFromFile({ $, owner, repo, targetNumber: prNumber, bodyFile: tempFile });
231
239
  await fs.unlink(tempFile).catch(() => {});
232
240
 
233
- // Extract comment ID from the created comment URL for future edits
234
- // gh pr comment outputs the URL of the created comment
235
- const output = result.stdout?.toString?.() || '';
236
- const urlMatch = output.match(/\/comments\/(\d+)/);
237
- if (urlMatch) {
238
- state.commentId = urlMatch[1];
241
+ if (posted.ok && posted.commentId) {
242
+ state.commentId = posted.commentId;
239
243
  } else {
240
244
  // Fallback: find the comment we just created by looking for our marker
241
245
  const commentsResult = await $`gh api repos/${owner}/${repo}/issues/${prNumber}/comments --jq ${`[.[] | select(.body | contains("${CONFIG.PROGRESS_SECTION_START}")) | .id] | last`}`;
242
246
  const commentId = commentsResult.stdout?.toString?.().trim();
243
247
  if (commentId && commentId !== 'null') {
244
248
  state.commentId = commentId;
249
+ trackToolCommentId(commentId);
245
250
  }
246
251
  }
247
252
  }
@@ -3,6 +3,10 @@
3
3
  * Handles repository cloning, forking, and remote setup
4
4
  */
5
5
 
6
+ // Issue #1625: centralized markers + tracked posting for the "empty repo"
7
+ // issue comment so it's excluded from --auto-attach-solution-summary's check.
8
+ import { REPOSITORY_INITIALIZATION_REQUIRED_MARKER, postTrackedComment } from './tool-comments.lib.mjs';
9
+
6
10
  export async function setupRepositoryAndClone({ argv, owner, repo, forkOwner, forkRepoName, tempDir, isContinueMode, issueUrl, log, $, needsClone = true }) {
7
11
  // Set up repository and handle forking
8
12
  const { repoToClone, forkedRepo, upstreamRemote, prForkOwner } = await setupRepository(argv, owner, repo, forkOwner, issueUrl, forkRepoName);
@@ -223,7 +227,7 @@ async function tryCommentOnIssueAboutEmptyRepo({ issueUrl, owner, repo, log, for
223
227
  const issueNumber = issueMatch[1];
224
228
  await log(`${formatAligned('💬', 'Creating comment:', 'Informing about empty repository...')}`);
225
229
 
226
- const commentBody = `## ⚠️ Repository Initialization Required
230
+ const commentBody = `## ⚠️ ${REPOSITORY_INITIALIZATION_REQUIRED_MARKER}
227
231
 
228
232
  Hello! I attempted to work on this issue, but encountered a problem:
229
233
 
@@ -245,9 +249,9 @@ Once the repository contains at least one commit with any file, I'll be able to
245
249
 
246
250
  Thank you!`;
247
251
 
248
- const commentResult = await $`gh issue comment ${issueNumber} --repo ${owner}/${repo} --body ${commentBody}`;
249
- if (commentResult.code === 0) {
250
- await log(`${formatAligned('✅', 'Comment created:', `Posted to issue #${issueNumber}`)}`);
252
+ const posted = await postTrackedComment({ $, owner, repo, targetNumber: issueNumber, body: commentBody });
253
+ if (posted.ok) {
254
+ await log(`${formatAligned('✅', 'Comment created:', `Posted to issue #${issueNumber}${posted.commentId ? ` (id=${posted.commentId})` : ''}`)}`);
251
255
  } else {
252
256
  await log(`${formatAligned('⚠️', 'Note:', 'Could not post comment to issue (this is not critical)')}`);
253
257
  }
@@ -32,6 +32,8 @@ import { safeExit } from './exit-handler.lib.mjs';
32
32
  // Import GitHub utilities for permission checks
33
33
  const githubLib = await import('./github.lib.mjs');
34
34
  const { checkRepositoryWritePermission } = githubLib;
35
+ // Issue #1625: centralized markers + tracked posting.
36
+ const { REPOSITORY_INITIALIZATION_REQUIRED_MARKER, postTrackedComment } = await import('./tool-comments.lib.mjs');
35
37
 
36
38
  // Get root repository (fork source or self), or null if inaccessible
37
39
  export const getRootRepository = async (owner, repo) => {
@@ -698,7 +700,7 @@ export const setupRepository = async (argv, owner, repo, forkOwner = null, issue
698
700
  const issueNumber = issueMatch[1];
699
701
  await log(`${formatAligned('💬', 'Creating comment:', 'Requesting maintainer to initialize repository...')}`);
700
702
 
701
- const commentBody = `## ⚠️ Repository Initialization Required
703
+ const commentBody = `## ⚠️ ${REPOSITORY_INITIALIZATION_REQUIRED_MARKER}
702
704
 
703
705
  Hello! I attempted to work on this issue, but encountered a problem:
704
706
 
@@ -717,9 +719,9 @@ Once the repository contains at least one commit with any file, I'll be able to
717
719
 
718
720
  Thank you!`;
719
721
 
720
- const commentResult = await $`gh issue comment ${issueNumber} --repo ${owner}/${repo} --body ${commentBody}`;
721
- if (commentResult.code === 0) {
722
- await log(`${formatAligned('✅', 'Comment created:', `Posted to issue #${issueNumber}`)}`);
722
+ const posted = await postTrackedComment({ $, owner, repo, targetNumber: issueNumber, body: commentBody });
723
+ if (posted.ok) {
724
+ await log(`${formatAligned('✅', 'Comment created:', `Posted to issue #${issueNumber}${posted.commentId ? ` (id=${posted.commentId})` : ''}`)}`);
723
725
  } else {
724
726
  await log(`${formatAligned('⚠️', 'Note:', 'Could not post comment to issue (this is not critical)')}`);
725
727
  }
@@ -1041,12 +1041,22 @@ export const handleExecutionError = async (error, shouldAttachLogs, owner, repo,
1041
1041
  await safeExit(1, 'Execution error');
1042
1042
  };
1043
1043
 
1044
+ // Issue #1625: Markers and in-memory comment-ID tracking are centralized in
1045
+ // src/tool-comments.lib.mjs so that every place that *posts* a tool-generated
1046
+ // comment and the filter that *detects* them share the exact same constants.
1047
+ // Re-exported here for backwards compatibility with imports that expect them
1048
+ // from solve.results.lib.mjs.
1049
+ const toolComments = await import('./tool-comments.lib.mjs');
1050
+ export const { TOOL_GENERATED_COMMENT_MARKERS, isToolGeneratedComment, trackToolCommentId, isToolTrackedCommentId, getTrackedToolCommentIds, postTrackedComment } = toolComments;
1051
+
1044
1052
  /**
1045
1053
  * Check if new comments were created by the AI during the session.
1046
1054
  * This is used by --auto-attach-solution-summary to determine if the AI
1047
1055
  * already provided feedback.
1048
1056
  *
1049
1057
  * Issue #1263: Support for --attach-solution-summary and --auto-attach-solution-summary
1058
+ * Issue #1625: Filter out comments produced by solve.mjs itself (session start,
1059
+ * log upload, auto-restart, etc.) so they do not falsely count as AI-authored.
1050
1060
  *
1051
1061
  * @param {Date} referenceTime - The timestamp before tool execution
1052
1062
  * @param {string} owner - Repository owner
@@ -1067,13 +1077,62 @@ export const checkForAiCreatedComments = async (referenceTime, owner, repo, prNu
1067
1077
  return false;
1068
1078
  }
1069
1079
 
1080
+ await log(`🔎 Checking comments by '${currentUser}' after ${referenceTime.toISOString()} (PR #${prNumber ?? 'none'}, issue #${issueNumber ?? 'none'})`, { verbose: true });
1081
+
1082
+ // Issue #1625: A comment counts as an "AI comment" only if it was posted
1083
+ // by the current user AFTER referenceTime AND solve.mjs did NOT post it
1084
+ // itself. We identify tool-posted comments in two ways, in order:
1085
+ // 1. Primary: comment ID is in the in-memory tracked set populated by
1086
+ // every solve.mjs posting site (postTrackedComment / trackToolCommentId).
1087
+ // This is robust to comment-body changes.
1088
+ // 2. Fallback: comment body matches a known TOOL_GENERATED_COMMENT_MARKERS
1089
+ // marker. This catches comments whose IDs weren't captured — for
1090
+ // example, on resumed sessions where the posting happened in an
1091
+ // earlier process, or legacy code paths that predate tracking.
1092
+ // Review-type inline comments cannot be posted by solve.mjs, so they are
1093
+ // treated as AI-authored by default.
1094
+ const filterNewAiComments = (comments, kind) => {
1095
+ const filtered = [];
1096
+ const skippedCounts = {};
1097
+ const skippedByIdCount = { n: 0 };
1098
+ for (const comment of comments) {
1099
+ if (!comment || !comment.user || comment.user.login !== currentUser) continue;
1100
+ if (!(new Date(comment.created_at) > referenceTime)) continue;
1101
+
1102
+ const isReview = kind === 'review';
1103
+ if (!isReview) {
1104
+ if (isToolTrackedCommentId(comment.id)) {
1105
+ skippedByIdCount.n += 1;
1106
+ continue;
1107
+ }
1108
+ if (isToolGeneratedComment(comment.body)) {
1109
+ const markerMatch = TOOL_GENERATED_COMMENT_MARKERS.find(m => (comment.body || '').includes(m)) || 'unknown';
1110
+ skippedCounts[markerMatch] = (skippedCounts[markerMatch] || 0) + 1;
1111
+ continue;
1112
+ }
1113
+ }
1114
+ filtered.push(comment);
1115
+ }
1116
+ if (skippedByIdCount.n > 0) {
1117
+ log(` ⏭️ Skipped ${kind} tool-tracked comment IDs: ${skippedByIdCount.n}`, { verbose: true }).catch(() => {});
1118
+ }
1119
+ if (Object.keys(skippedCounts).length > 0) {
1120
+ const summary = Object.entries(skippedCounts)
1121
+ .map(([m, c]) => `${m}=${c}`)
1122
+ .join(', ');
1123
+ log(` ⏭️ Skipped ${kind} tool-generated comments (marker fallback): ${summary}`, { verbose: true }).catch(() => {});
1124
+ }
1125
+ return filtered;
1126
+ };
1127
+
1070
1128
  // Check comments on the PR first (if we have a PR)
1071
1129
  if (prNumber) {
1072
1130
  // Check PR conversation comments
1073
1131
  const prCommentsResult = await $`gh api repos/${owner}/${repo}/issues/${prNumber}/comments --paginate`;
1074
1132
  if (prCommentsResult.code === 0) {
1075
1133
  const prComments = JSON.parse(prCommentsResult.stdout.toString().trim() || '[]');
1076
- const newPrComments = prComments.filter(comment => comment.user.login === currentUser && new Date(comment.created_at) > referenceTime);
1134
+ const newPrComments = filterNewAiComments(prComments, 'pr');
1135
+ await log(` 📨 PR conversation comments after referenceTime by '${currentUser}' (excluding tool-generated): ${newPrComments.length}`, { verbose: true });
1077
1136
  if (newPrComments.length > 0) {
1078
1137
  return true;
1079
1138
  }
@@ -1083,7 +1142,8 @@ export const checkForAiCreatedComments = async (referenceTime, owner, repo, prNu
1083
1142
  const reviewCommentsResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate`;
1084
1143
  if (reviewCommentsResult.code === 0) {
1085
1144
  const reviewComments = JSON.parse(reviewCommentsResult.stdout.toString().trim() || '[]');
1086
- const newReviewComments = reviewComments.filter(comment => comment.user.login === currentUser && new Date(comment.created_at) > referenceTime);
1145
+ const newReviewComments = filterNewAiComments(reviewComments, 'review');
1146
+ await log(` 📝 PR review (inline) comments after referenceTime by '${currentUser}': ${newReviewComments.length}`, { verbose: true });
1087
1147
  if (newReviewComments.length > 0) {
1088
1148
  return true;
1089
1149
  }
@@ -1095,7 +1155,8 @@ export const checkForAiCreatedComments = async (referenceTime, owner, repo, prNu
1095
1155
  const issueCommentsResult = await $`gh api repos/${owner}/${repo}/issues/${issueNumber}/comments --paginate`;
1096
1156
  if (issueCommentsResult.code === 0) {
1097
1157
  const issueComments = JSON.parse(issueCommentsResult.stdout.toString().trim() || '[]');
1098
- const newIssueComments = issueComments.filter(comment => comment.user.login === currentUser && new Date(comment.created_at) > referenceTime);
1158
+ const newIssueComments = filterNewAiComments(issueComments, 'issue');
1159
+ await log(` 📨 Issue comments after referenceTime by '${currentUser}' (excluding tool-generated): ${newIssueComments.length}`, { verbose: true });
1099
1160
  if (newIssueComments.length > 0) {
1100
1161
  return true;
1101
1162
  }
@@ -3,6 +3,11 @@
3
3
  * Handles starting and ending work sessions, PR status changes, and session comments
4
4
  */
5
5
 
6
+ // Issue #1625: Use the single source of truth for session-comment marker
7
+ // strings. Building comment bodies via these constants guarantees the filter
8
+ // in checkForAiCreatedComments() always matches what we actually posted.
9
+ import { AI_WORK_SESSION_STARTED_MARKER, AI_WORK_SESSION_COMPLETED_MARKER, AI_WORK_SESSION_RESUMED_MARKER, AUTO_RESUME_ON_LIMIT_RESET_MARKER, AUTO_RESTART_ON_LIMIT_RESET_MARKER, postTrackedComment } from './tool-comments.lib.mjs';
10
+
6
11
  /**
7
12
  * Session type definitions for different work session contexts
8
13
  * See: https://github.com/link-assistant/hive-mind/issues/1152
@@ -27,26 +32,26 @@ function getSessionCommentContent(sessionType, timestamp) {
27
32
  case SESSION_TYPES.RESUME:
28
33
  return {
29
34
  emoji: '🔄',
30
- header: 'AI Work Session Resumed',
35
+ header: AI_WORK_SESSION_RESUMED_MARKER,
31
36
  description: `Resuming automated work session at ${isoTime}\n\nThis session continues from a previous session using the \`--resume\` flag.\n\nThe PR has been converted to draft mode while work is in progress.\n\n_This comment marks the resumption of an AI work session. Please wait for the session to finish, and provide your feedback._`,
32
37
  };
33
38
  case SESSION_TYPES.AUTO_RESUME:
34
39
  return {
35
40
  emoji: '⏰',
36
- header: 'Auto Resume (on limit reset)',
41
+ header: AUTO_RESUME_ON_LIMIT_RESET_MARKER,
37
42
  description: `Auto-resuming automated work session at ${isoTime}\n\nThis session automatically resumed after the usage limit reset, continuing with the previous context preserved.\n\nThe PR has been converted to draft mode while work is in progress.\n\n_This is an auto-resumed session. Please wait for the session to finish, and provide your feedback._`,
38
43
  };
39
44
  case SESSION_TYPES.AUTO_RESTART:
40
45
  return {
41
46
  emoji: '🔄',
42
- header: 'Auto Restart (on limit reset)',
47
+ header: AUTO_RESTART_ON_LIMIT_RESET_MARKER,
43
48
  description: `Auto-restarting automated work session at ${isoTime}\n\nThis session automatically restarted after the usage limit reset (fresh start without previous context).\n\nThe PR has been converted to draft mode while work is in progress.\n\n_This is a fresh restart after limit reset. Please wait for the session to finish, and provide your feedback._`,
44
49
  };
45
50
  case SESSION_TYPES.NEW:
46
51
  default:
47
52
  return {
48
53
  emoji: '🤖',
49
- header: 'AI Work Session Started',
54
+ header: AI_WORK_SESSION_STARTED_MARKER,
50
55
  description: `Starting automated work session at ${isoTime}\n\nThe PR has been converted to draft mode while work is in progress.\n\n_This comment marks the beginning of an AI work session. Please wait for the session to finish, and provide your feedback._`,
51
56
  };
52
57
  }
@@ -97,13 +102,17 @@ export async function startWorkSession({ isContinueMode, prNumber, argv, log, fo
97
102
  await log('Warning: Could not check/convert PR draft status', { level: 'warning' });
98
103
  }
99
104
 
100
- // Post a comment marking the start of work session with appropriate header based on session type
105
+ // Post a comment marking the start of work session with appropriate header based on session type.
106
+ // Issue #1625: Use postTrackedComment so the comment ID is registered in-memory and can be
107
+ // excluded from the "did the AI post anything?" check in checkForAiCreatedComments().
101
108
  try {
102
109
  const { emoji, header, description } = getSessionCommentContent(sessionType, workStartTime);
103
110
  const startComment = `${emoji} **${header}**\n\n${description}`;
104
- const commentResult = await $`gh pr comment ${prNumber} --repo ${global.owner}/${global.repo} --body ${startComment}`;
105
- if (commentResult.code === 0) {
106
- await log(formatAligned('💬', 'Posted:', `${header} comment`, 2));
111
+ const { ok, commentId, stderr } = await postTrackedComment({ $, owner: global.owner, repo: global.repo, targetNumber: prNumber, body: startComment });
112
+ if (ok) {
113
+ await log(formatAligned('💬', 'Posted:', `${header} comment${commentId ? ` (id=${commentId})` : ''}`, 2));
114
+ } else {
115
+ await log(`Warning: Could not post work start comment: ${stderr || 'unknown error'}`, { level: 'warning' });
107
116
  }
108
117
  } catch (error) {
109
118
  const sentryLib = await import('./sentry.lib.mjs');
@@ -129,12 +138,15 @@ export async function endWorkSession({ isContinueMode, prNumber, argv, log, form
129
138
  // Only post end comment if logs were NOT already attached
130
139
  // The attachLogToGitHub comment already serves as finishing status with "Now working session is ended" text
131
140
  if (!logsAttached) {
132
- // Post a comment marking the end of work session
141
+ // Post a comment marking the end of work session.
142
+ // Issue #1625: Track the comment ID so it won't be mistaken for AI-authored content.
133
143
  try {
134
- const endComment = `🤖 **AI Work Session Completed**\n\nWork session ended at ${workEndTime.toISOString()}\n\nThe PR will be converted back to ready for review.\n\n_This comment marks the end of an AI work session. New comments after this time will be considered as feedback._`;
135
- const commentResult = await $`gh pr comment ${prNumber} --repo ${global.owner}/${global.repo} --body ${endComment}`;
136
- if (commentResult.code === 0) {
137
- await log(formatAligned('💬', 'Posted:', 'Work session end comment', 2));
144
+ const endComment = `🤖 **${AI_WORK_SESSION_COMPLETED_MARKER}**\n\nWork session ended at ${workEndTime.toISOString()}\n\nThe PR will be converted back to ready for review.\n\n_This comment marks the end of an AI work session. New comments after this time will be considered as feedback._`;
145
+ const { ok, commentId, stderr } = await postTrackedComment({ $, owner: global.owner, repo: global.repo, targetNumber: prNumber, body: endComment });
146
+ if (ok) {
147
+ await log(formatAligned('💬', 'Posted:', `Work session end comment${commentId ? ` (id=${commentId})` : ''}`, 2));
148
+ } else {
149
+ await log(`Warning: Could not post work end comment: ${stderr || 'unknown error'}`, { level: 'warning' });
138
150
  }
139
151
  } catch (error) {
140
152
  const sentryLib = await import('./sentry.lib.mjs');
@@ -40,6 +40,10 @@ const { checkPRMerged, checkForUncommittedChanges, getUncommittedChangesDetails,
40
40
  // Issue #1574: Interruptible sleep so CTRL+C is never blocked by a lingering timer
41
41
  const { interruptibleSleep } = await import('./interruptible-sleep.lib.mjs');
42
42
 
43
+ // Issue #1625: Central marker constants + tracked comment posting
44
+ const toolComments = await import('./tool-comments.lib.mjs');
45
+ const { AUTO_RESTART_MARKER, postTrackedComment } = toolComments;
46
+
43
47
  /**
44
48
  * Monitor for feedback in a loop and trigger restart when detected
45
49
  */
@@ -192,8 +196,9 @@ export const watchForFeedback = async params => {
192
196
  uncommittedFilesList = '\n\n**Uncommitted files:**\n```\n' + changes.join('\n') + '\n```';
193
197
  }
194
198
 
195
- const commentBody = `## 🔄 Auto-restart ${autoRestartCount}/${maxAutoRestartIterations}\n\nDetected uncommitted changes from previous run. Starting new session to review and commit or discard them.${uncommittedFilesList}\n\n---\n*Auto-restart will stop after changes are committed or discarded, or after ${remainingIterations} more iteration${remainingIterations !== 1 ? 's' : ''}. Please wait until working session will end and give your feedback.*`;
196
- await $`gh pr comment ${prNumber} --repo ${owner}/${repo} --body ${commentBody}`;
199
+ const commentBody = `## 🔄 ${AUTO_RESTART_MARKER} ${autoRestartCount}/${maxAutoRestartIterations}\n\nDetected uncommitted changes from previous run. Starting new session to review and commit or discard them.${uncommittedFilesList}\n\n---\n*Auto-restart will stop after changes are committed or discarded, or after ${remainingIterations} more iteration${remainingIterations !== 1 ? 's' : ''}. Please wait until working session will end and give your feedback.*`;
200
+ // Issue #1625: Track so this doesn't falsely count as AI-authored.
201
+ await postTrackedComment({ $, owner, repo, targetNumber: prNumber, body: commentBody });
197
202
  await log(formatAligned('', '💬 Posted auto-restart notification to PR', '', 2));
198
203
  } catch (commentError) {
199
204
  reportError(commentError, {