feat(triage): GAP-29 persist enrichment results to enrichment.json
After runEnrichment completes, write enrichment sections and completenessScore to local enrichment.json. After applyTriageResults successfully applies labels, persist triageResult with category, confidence, labels, and triagedAt. Both use readEnrichmentFile + writeEnrichmentFile with createDefaultEnrichment fallback. Errors are caught and logged (non-fatal). 2 new tests (15 total). Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
2120211f00
commit
34f4b41ee6
@@ -229,6 +229,44 @@ describe('runEnrichment handler', () => {
|
||||
|
||||
expect(mockSendError).toHaveBeenCalledWith('GitHub module not installed');
|
||||
});
|
||||
|
||||
it('persists enrichment result to enrichment.json', async () => {
|
||||
const { readEnrichmentFile, writeEnrichmentFile } = await import('../enrichment-persistence');
|
||||
mockRunPythonSubprocess.mockReturnValue({
|
||||
promise: Promise.resolve({
|
||||
success: true,
|
||||
data: {
|
||||
issueNumber: 42,
|
||||
problem: 'Test problem',
|
||||
goal: 'Test goal',
|
||||
scopeIn: ['scope-in'],
|
||||
scopeOut: ['scope-out'],
|
||||
acceptanceCriteria: ['AC-1'],
|
||||
technicalContext: 'Some context',
|
||||
risksEdgeCases: ['Risk 1'],
|
||||
confidence: 0.85,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await trigger('test-project', 42);
|
||||
|
||||
expect(readEnrichmentFile).toHaveBeenCalledWith('/fake/project');
|
||||
expect(writeEnrichmentFile).toHaveBeenCalledWith(
|
||||
'/fake/project',
|
||||
expect.objectContaining({
|
||||
issues: expect.objectContaining({
|
||||
'42': expect.objectContaining({
|
||||
enrichment: expect.objectContaining({
|
||||
problem: 'Test problem',
|
||||
goal: 'Test goal',
|
||||
}),
|
||||
completenessScore: 0.85,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================
|
||||
@@ -390,6 +428,30 @@ describe('applyTriageResults handler', () => {
|
||||
expect(result.succeeded).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('persists triage result to enrichment.json after applying', async () => {
|
||||
const { readEnrichmentFile, writeEnrichmentFile } = await import('../enrichment-persistence');
|
||||
mockExecFileSync.mockReturnValue(Buffer.from(''));
|
||||
|
||||
const singleItem = [reviewItems[0]]; // accepted item with bug + priority:high
|
||||
await trigger('test-project', singleItem);
|
||||
|
||||
expect(readEnrichmentFile).toHaveBeenCalled();
|
||||
expect(writeEnrichmentFile).toHaveBeenCalledWith(
|
||||
'/fake/project',
|
||||
expect.objectContaining({
|
||||
issues: expect.objectContaining({
|
||||
'1': expect.objectContaining({
|
||||
triageResult: expect.objectContaining({
|
||||
category: 'bug',
|
||||
labelsToAdd: ['bug', 'priority:high'],
|
||||
triagedAt: '2026-01-01T00:00:00Z',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('skips rejected items', async () => {
|
||||
const allRejected = reviewItems.map(item => ({ ...item, status: 'rejected' as const }));
|
||||
mockExecFileSync.mockReturnValue(Buffer.from(''));
|
||||
|
||||
@@ -31,6 +31,9 @@ import {
|
||||
} from './utils/subprocess-runner';
|
||||
import { MAX_SPLIT_SUB_ISSUES } from '../../../shared/constants/ai-triage';
|
||||
import { createDefaultProgressiveTrust } from '../../../shared/types/ai-triage';
|
||||
import { readEnrichmentFile, writeEnrichmentFile } from './enrichment-persistence';
|
||||
import { createDefaultEnrichment } from '../../../shared/types/enrichment';
|
||||
import type { TriageCategory } from '../../../shared/types/enrichment';
|
||||
import type {
|
||||
AIEnrichmentResult,
|
||||
SplitSuggestion,
|
||||
@@ -141,7 +144,35 @@ export function registerAITriageHandlers(
|
||||
return;
|
||||
}
|
||||
|
||||
sendComplete(result.data as AIEnrichmentResult);
|
||||
const enrichmentResult = result.data as AIEnrichmentResult;
|
||||
sendComplete(enrichmentResult);
|
||||
|
||||
// Persist enrichment data to local file
|
||||
try {
|
||||
const enrichmentFile = await readEnrichmentFile(project.path);
|
||||
const key = String(issueNumber);
|
||||
const existing = enrichmentFile.issues[key] ?? createDefaultEnrichment(issueNumber);
|
||||
enrichmentFile.issues[key] = {
|
||||
...existing,
|
||||
enrichment: {
|
||||
problem: enrichmentResult.problem,
|
||||
goal: enrichmentResult.goal,
|
||||
scopeIn: enrichmentResult.scopeIn,
|
||||
scopeOut: enrichmentResult.scopeOut,
|
||||
acceptanceCriteria: enrichmentResult.acceptanceCriteria,
|
||||
technicalContext: enrichmentResult.technicalContext,
|
||||
risksEdgeCases: enrichmentResult.risksEdgeCases,
|
||||
},
|
||||
completenessScore: enrichmentResult.confidence,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
await writeEnrichmentFile(project.path, enrichmentFile);
|
||||
} catch (persistErr) {
|
||||
debugLog('Failed to persist enrichment result', {
|
||||
issueNumber,
|
||||
error: persistErr instanceof Error ? persistErr.message : persistErr,
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
sendError(error instanceof Error ? error.message : 'Failed to run enrichment');
|
||||
@@ -309,6 +340,35 @@ export function registerAITriageHandlers(
|
||||
}
|
||||
|
||||
succeeded++;
|
||||
|
||||
// Persist triage result to enrichment file
|
||||
try {
|
||||
const enrichmentFile = await readEnrichmentFile(project.path);
|
||||
const key = String(item.issueNumber);
|
||||
const existing = enrichmentFile.issues[key] ?? createDefaultEnrichment(item.issueNumber);
|
||||
enrichmentFile.issues[key] = {
|
||||
...existing,
|
||||
triageResult: {
|
||||
category: item.result.category as TriageCategory,
|
||||
confidence: item.result.confidence,
|
||||
labelsToAdd: item.result.labelsToAdd,
|
||||
labelsToRemove: item.result.labelsToRemove,
|
||||
isDuplicate: item.result.isDuplicate,
|
||||
duplicateOf: item.result.duplicateOf,
|
||||
isSpam: item.result.isSpam,
|
||||
suggestedBreakdown: item.result.suggestedBreakdown,
|
||||
comment: item.result.comment,
|
||||
triagedAt: item.result.triagedAt,
|
||||
},
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
await writeEnrichmentFile(project.path, enrichmentFile);
|
||||
} catch (persistErr) {
|
||||
debugLog('Failed to persist triage result', {
|
||||
issueNumber: item.issueNumber,
|
||||
error: persistErr instanceof Error ? persistErr.message : persistErr,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
debugLog('Failed to apply results to issue', {
|
||||
issueNumber: item.issueNumber,
|
||||
|
||||
+6
-6
@@ -3,7 +3,7 @@
|
||||
**Branch:** `terminal/enhancement-issues-tab`
|
||||
**Created:** 2026-02-12
|
||||
**Total Gaps:** 41 confirmed (from triple-verified audit)
|
||||
**Status:** 19 / 41 complete
|
||||
**Status:** 20 / 41 complete
|
||||
|
||||
---
|
||||
|
||||
@@ -357,16 +357,16 @@ Each gap has: ID, description, status, files to modify, doc reference, test stat
|
||||
- **Commit:** —
|
||||
|
||||
### GAP-29: No enrichment persistence to local files after AI triage
|
||||
- **Status:** `PENDING`
|
||||
- **Status:** `DONE`
|
||||
- **Priority:** MUST-FIX
|
||||
- **Scope:** Medium
|
||||
- **Doc ref:** Phase 3 PRD > US-1 > AC1.11; Phase 3 PRD > US-5 > AC5.9
|
||||
- **Files to modify:** `main/ipc-handlers/github/ai-triage-handlers.ts`
|
||||
- **Fix:** After sendComplete(), import readEnrichmentFile/writeEnrichmentFile from enrichment-persistence. Persist triageResult and lastTriagedAt to enrichment.json. Same in applyTriageResults.
|
||||
- **Tests:** Run enrichment → enrichment.json contains triageResult + lastTriagedAt
|
||||
- **Test status:** `PENDING`
|
||||
- **Fix:** After sendComplete() in runEnrichment, persist enrichment sections + completenessScore to enrichment.json. After successful label apply in applyTriageResults, persist triageResult. Both use readEnrichmentFile/writeEnrichmentFile with createDefaultEnrichment fallback. Errors caught and logged (non-fatal).
|
||||
- **Tests:** 2 new tests: runEnrichment persists to file, applyTriageResults persists triageResult. 15 total.
|
||||
- **Test status:** `PASS`
|
||||
- **Depends on:** None
|
||||
- **Commit:** —
|
||||
- **Commit:** pending
|
||||
|
||||
### GAP-30: No `actor: 'ai-triage'` audit trail
|
||||
- **Status:** `PENDING`
|
||||
|
||||
Reference in New Issue
Block a user