feat(triage): VGAP-15 enrichment comment duplicate detection

Shows yellow warning banner when existing AI comment detected on issue.
Checks via getIssueComments IPC for ENRICHMENT_COMMENT_FOOTER marker.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Sondre Engebråten
2026-02-13 10:01:34 +01:00
co-authored by Claude Opus 4.6
parent b8b53250f1
commit 856bee4e54
6 changed files with 61 additions and 14 deletions
@@ -40,7 +40,7 @@ import {
import { GitHubSetupModal } from "./GitHubSetupModal";
import { useMutationStore } from "../stores/github/mutation-store";
import { useAITriageStore } from "../stores/github/ai-triage-store";
import { formatEnrichmentComment } from "../../shared/constants/ai-triage";
import { formatEnrichmentComment, ENRICHMENT_COMMENT_FOOTER } from "../../shared/constants/ai-triage";
import type { GitHubIssue } from "../../shared/types";
import type { GitHubIssuesProps } from "./github-issues/types";
import type { WorkflowState, Resolution } from "../../shared/types/enrichment";
@@ -183,6 +183,29 @@ export function GitHubIssues({ onOpenSettings, onNavigateToTask }: GitHubIssuesP
const aiTriage = useAITriage(selectedProject?.id ?? '');
const lastBatchSnapshot = useAITriageStore((s) => s.lastBatchSnapshot);
// Check for existing AI comment when enrichment result is available
const [hasExistingAIComment, setHasExistingAIComment] = useState(false);
useEffect(() => {
if (!aiTriage.enrichmentResult || !selectedIssue || !selectedProject?.id) {
setHasExistingAIComment(false);
return;
}
let cancelled = false;
window.electronAPI.github.getIssueComments(selectedProject.id, selectedIssue.number)
.then((result) => {
if (cancelled) return;
const comments = result?.data ?? [];
const hasAI = comments.some((c: { body?: string }) =>
c.body?.includes(ENRICHMENT_COMMENT_FOOTER),
);
setHasExistingAIComment(hasAI);
})
.catch(() => {
if (!cancelled) setHasExistingAIComment(false);
});
return () => { cancelled = true; };
}, [aiTriage.enrichmentResult, selectedIssue, selectedProject?.id]);
// Triage mode (3-panel layout)
const { isEnabled: triageModeEnabled, isAvailable: triageModeAvailable, toggle: toggleTriageMode } = useTriageMode();
@@ -547,6 +570,7 @@ export function GitHubIssues({ onOpenSettings, onNavigateToTask }: GitHubIssuesP
aiTriage.clearEnrichmentResult();
}}
onCancel={aiTriage.clearEnrichmentResult}
hasExistingAIComment={hasExistingAIComment}
/>
)}
@@ -10,9 +10,10 @@ interface EnrichmentCommentPreviewProps {
content: string;
onPost: (content: string) => void;
onCancel: () => void;
hasExistingAIComment?: boolean;
}
export function EnrichmentCommentPreview({ content: initialContent, onPost, onCancel }: EnrichmentCommentPreviewProps) {
export function EnrichmentCommentPreview({ content: initialContent, onPost, onCancel, hasExistingAIComment }: EnrichmentCommentPreviewProps) {
const { t } = useTranslation(['common']);
const [content, setContent] = useState(initialContent);
@@ -23,6 +24,11 @@ export function EnrichmentCommentPreview({ content: initialContent, onPost, onCa
return (
<section className="space-y-3" aria-label={t('common:enrichmentComment.title')}>
{hasExistingAIComment && (
<div role="alert" className="rounded-md border border-yellow-500/50 bg-yellow-500/10 px-3 py-2 text-xs text-yellow-600 dark:text-yellow-400">
{t('common:enrichmentComment.duplicateWarning')}
</div>
)}
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
@@ -63,4 +63,20 @@ describe('EnrichmentCommentPreview', () => {
const postedContent = defaultProps.onPost.mock.calls[0][0];
expect(postedContent).toContain('Auto-Claude');
});
it('shows duplicate warning when hasExistingAIComment is true', () => {
render(<EnrichmentCommentPreview {...defaultProps} hasExistingAIComment={true} />);
expect(screen.getByRole('alert')).toBeDefined();
expect(screen.getByText('common:enrichmentComment.duplicateWarning')).toBeDefined();
});
it('does not show duplicate warning when hasExistingAIComment is false', () => {
render(<EnrichmentCommentPreview {...defaultProps} hasExistingAIComment={false} />);
expect(screen.queryByRole('alert')).toBeNull();
});
it('does not show duplicate warning when hasExistingAIComment is not provided', () => {
render(<EnrichmentCommentPreview {...defaultProps} />);
expect(screen.queryByRole('alert')).toBeNull();
});
});
@@ -626,7 +626,8 @@
"content": "Comment content",
"characters": "characters",
"post": "Post Comment",
"cancel": "Cancel"
"cancel": "Cancel",
"duplicateWarning": "An AI enrichment comment already exists on this issue. Posting will create a duplicate."
},
"progressiveTrust": {
"title": "Progressive Trust Settings",
@@ -626,7 +626,8 @@
"content": "Contenu du commentaire",
"characters": "caractères",
"post": "Publier le commentaire",
"cancel": "Annuler"
"cancel": "Annuler",
"duplicateWarning": "Un commentaire d'enrichissement IA existe déjà sur cette issue. La publication créera un doublon."
},
"progressiveTrust": {
"title": "Paramètres de confiance progressive",
+9 -10
View File
@@ -3,7 +3,7 @@
**Branch:** `terminal/enhancement-issues-tab`
**Created:** 2026-02-13
**Total Gaps:** 46 confirmed (from 9-agent triple-verified audit)
**Status:** 14 / 17 complete
**Status:** 15 / 17 complete
---
@@ -229,18 +229,17 @@ Each gap has: ID, description, status, files to modify, verification source, tes
- **Commit:** VGAP-14
### VGAP-15: No enrichment comment duplicate detection (Phase 3 GAP-5)
- **Status:** `PENDING`
- **Status:** `DONE`
- **Priority:** NICE-TO-HAVE
- **Scope:** Medium
- **Verified by:** Phase3 agent + Verifier-1 (CONFIRMED)
- **Doc ref:** Phase 3 PRD > US-5 > AC5.11; Phase 3 audit > GAP-5
- **Files to modify:** `renderer/components/github-issues/components/EnrichmentCommentPreview.tsx`, `renderer/components/github-issues/hooks/useAITriage.ts`
- **Problem:** No check for existing AI comments (containing `ENRICHMENT_COMMENT_FOOTER`) before showing preview. Can double-post enrichment comments.
- **Fix:** Before showing preview, check issue comments for `ENRICHMENT_COMMENT_FOOTER` string. Show warning banner if existing comment found.
- **Tests:** Test warning displays when existing AI comment detected
- **Test status:** —
- **Files modified:** `renderer/components/github-issues/components/EnrichmentCommentPreview.tsx`, `renderer/components/GitHubIssues.tsx`, `shared/i18n/locales/en/common.json`, `shared/i18n/locales/fr/common.json`, `__tests__/EnrichmentCommentPreview.test.tsx`
- **Fix:** Added `hasExistingAIComment` prop to EnrichmentCommentPreview that shows a yellow warning banner with `role="alert"`. GitHubIssues.tsx fetches issue comments via `getIssueComments` IPC when enrichment result is available, checks for `ENRICHMENT_COMMENT_FOOTER`, and passes result as prop. Added `duplicateWarning` i18n key to EN + FR.
- **Tests:** 3 new tests: warning shown when true, hidden when false, hidden when undefined. All pass.
- **Test status:** `PASS`
- **Depends on:** None
- **Commit:** —
- **Commit:** VGAP-15
### VGAP-16: No cancel mechanism for batch triage subprocess (Phase 3 GAP-7)
- **Status:** `PENDING`
@@ -280,7 +279,7 @@ Each gap has: ID, description, status, files to modify, verification source, tes
| 2 | i18n Hardcoded Strings | 5 | 5 | 0 |
| 3 | Accessibility Keyboard | 2 | 2 | 0 |
| 4 | IPC Consistency | 3 | 3 | 0 |
| 5 | Phase 3 Audit Gaps | 5 | 2 | 3 |
| **Total** | | **17** | **14** | **3** |
| 5 | Phase 3 Audit Gaps | 5 | 3 | 2 |
| **Total** | | **17** | **15** | **2** |
Note: VGAP-03 through VGAP-07 contain 28+ individual hardcoded strings grouped by component file. The 17 gap count represents work units (one per component/file), not individual string count.