@tonycasey/lisa 2.12.0 → 2.25.2
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/README.md +2 -2
- package/dist/lib/application/handlers/PromptSubmitHandler.js +2 -2
- package/dist/lib/application/handlers/PromptSubmitHandler.js.map +1 -1
- package/dist/lib/application/handlers/SessionStopHandler.d.ts.map +1 -1
- package/dist/lib/application/handlers/SessionStopHandler.js +12 -2
- package/dist/lib/application/handlers/SessionStopHandler.js.map +1 -1
- package/dist/lib/commands/init.js +3 -3
- package/dist/lib/commands/init.js.map +1 -1
- package/dist/lib/commands/knowledge.d.ts.map +1 -1
- package/dist/lib/commands/knowledge.js +449 -1
- package/dist/lib/commands/knowledge.js.map +1 -1
- package/dist/lib/domain/errors/LlmErrors.d.ts +41 -0
- package/dist/lib/domain/errors/LlmErrors.d.ts.map +1 -0
- package/dist/lib/domain/errors/LlmErrors.js +64 -0
- package/dist/lib/domain/errors/LlmErrors.js.map +1 -0
- package/dist/lib/domain/interfaces/IConsolidationService.d.ts +65 -0
- package/dist/lib/domain/interfaces/IConsolidationService.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/IConsolidationService.js +21 -0
- package/dist/lib/domain/interfaces/IConsolidationService.js.map +1 -0
- package/dist/lib/domain/interfaces/ICurationService.d.ts +81 -0
- package/dist/lib/domain/interfaces/ICurationService.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/ICurationService.js +65 -0
- package/dist/lib/domain/interfaces/ICurationService.js.map +1 -0
- package/dist/lib/domain/interfaces/IDeduplicationService.d.ts +67 -0
- package/dist/lib/domain/interfaces/IDeduplicationService.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/IDeduplicationService.js +9 -0
- package/dist/lib/domain/interfaces/IDeduplicationService.js.map +1 -0
- package/dist/lib/domain/interfaces/ILlmConfigService.d.ts +36 -0
- package/dist/lib/domain/interfaces/ILlmConfigService.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/ILlmConfigService.js +11 -0
- package/dist/lib/domain/interfaces/ILlmConfigService.js.map +1 -0
- package/dist/lib/domain/interfaces/ILlmGuard.d.ts +35 -0
- package/dist/lib/domain/interfaces/ILlmGuard.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/ILlmGuard.js +12 -0
- package/dist/lib/domain/interfaces/ILlmGuard.js.map +1 -0
- package/dist/lib/domain/interfaces/ILlmService.d.ts +87 -0
- package/dist/lib/domain/interfaces/ILlmService.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/ILlmService.js +40 -0
- package/dist/lib/domain/interfaces/ILlmService.js.map +1 -0
- package/dist/lib/domain/interfaces/ILlmUsageTracker.d.ts +60 -0
- package/dist/lib/domain/interfaces/ILlmUsageTracker.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/ILlmUsageTracker.js +29 -0
- package/dist/lib/domain/interfaces/ILlmUsageTracker.js.map +1 -0
- package/dist/lib/domain/interfaces/IMemoryService.d.ts +90 -0
- package/dist/lib/domain/interfaces/IMemoryService.d.ts.map +1 -1
- package/dist/lib/domain/interfaces/INlCurationService.d.ts +75 -0
- package/dist/lib/domain/interfaces/INlCurationService.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/INlCurationService.js +31 -0
- package/dist/lib/domain/interfaces/INlCurationService.js.map +1 -0
- package/dist/lib/domain/interfaces/IPreferenceStore.d.ts +46 -0
- package/dist/lib/domain/interfaces/IPreferenceStore.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/IPreferenceStore.js +11 -0
- package/dist/lib/domain/interfaces/IPreferenceStore.js.map +1 -0
- package/dist/lib/domain/interfaces/IRecursionService.d.ts +10 -6
- package/dist/lib/domain/interfaces/IRecursionService.d.ts.map +1 -1
- package/dist/lib/domain/interfaces/ISummarizationService.d.ts +52 -0
- package/dist/lib/domain/interfaces/ISummarizationService.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/ISummarizationService.js +11 -0
- package/dist/lib/domain/interfaces/ISummarizationService.js.map +1 -0
- package/dist/lib/domain/interfaces/ITaskTypeDetector.d.ts +34 -0
- package/dist/lib/domain/interfaces/ITaskTypeDetector.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/ITaskTypeDetector.js +9 -0
- package/dist/lib/domain/interfaces/ITaskTypeDetector.js.map +1 -0
- package/dist/lib/domain/interfaces/ITranscriptEnricher.d.ts +68 -0
- package/dist/lib/domain/interfaces/ITranscriptEnricher.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/ITranscriptEnricher.js +32 -0
- package/dist/lib/domain/interfaces/ITranscriptEnricher.js.map +1 -0
- package/dist/lib/domain/interfaces/IWorkSummary.d.ts +64 -0
- package/dist/lib/domain/interfaces/IWorkSummary.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/IWorkSummary.js +13 -0
- package/dist/lib/domain/interfaces/IWorkSummary.js.map +1 -0
- package/dist/lib/domain/interfaces/dal/IMemoryRelationshipRepository.d.ts +46 -0
- package/dist/lib/domain/interfaces/dal/IMemoryRelationshipRepository.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/dal/IMemoryRelationshipRepository.js +9 -0
- package/dist/lib/domain/interfaces/dal/IMemoryRelationshipRepository.js.map +1 -0
- package/dist/lib/domain/interfaces/dal/IMemoryRepository.d.ts +62 -1
- package/dist/lib/domain/interfaces/dal/IMemoryRepository.d.ts.map +1 -1
- package/dist/lib/domain/interfaces/dal/index.d.ts +3 -2
- package/dist/lib/domain/interfaces/dal/index.d.ts.map +1 -1
- package/dist/lib/domain/interfaces/dal/index.js.map +1 -1
- package/dist/lib/domain/interfaces/dal/types.d.ts +37 -0
- package/dist/lib/domain/interfaces/dal/types.d.ts.map +1 -1
- package/dist/lib/domain/interfaces/dal/types.js.map +1 -1
- package/dist/lib/domain/interfaces/index.d.ts +15 -1
- package/dist/lib/domain/interfaces/index.d.ts.map +1 -1
- package/dist/lib/domain/interfaces/index.js +21 -1
- package/dist/lib/domain/interfaces/index.js.map +1 -1
- package/dist/lib/domain/interfaces/types/ICapturedWork.d.ts +3 -0
- package/dist/lib/domain/interfaces/types/ICapturedWork.d.ts.map +1 -1
- package/dist/lib/domain/interfaces/types/ICapturedWork.js.map +1 -1
- package/dist/lib/domain/interfaces/types/IMemoryLifecycle.d.ts +56 -0
- package/dist/lib/domain/interfaces/types/IMemoryLifecycle.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/types/IMemoryLifecycle.js +94 -0
- package/dist/lib/domain/interfaces/types/IMemoryLifecycle.js.map +1 -0
- package/dist/lib/domain/interfaces/types/IMemoryQuality.d.ts +113 -0
- package/dist/lib/domain/interfaces/types/IMemoryQuality.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/types/IMemoryQuality.js +195 -0
- package/dist/lib/domain/interfaces/types/IMemoryQuality.js.map +1 -0
- package/dist/lib/domain/interfaces/types/IMemoryRelationship.d.ts +49 -0
- package/dist/lib/domain/interfaces/types/IMemoryRelationship.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/types/IMemoryRelationship.js +62 -0
- package/dist/lib/domain/interfaces/types/IMemoryRelationship.js.map +1 -0
- package/dist/lib/domain/interfaces/types/ITaskType.d.ts +59 -0
- package/dist/lib/domain/interfaces/types/ITaskType.d.ts.map +1 -0
- package/dist/lib/domain/interfaces/types/ITaskType.js +104 -0
- package/dist/lib/domain/interfaces/types/ITaskType.js.map +1 -0
- package/dist/lib/domain/interfaces/types/index.d.ts +4 -0
- package/dist/lib/domain/interfaces/types/index.d.ts.map +1 -1
- package/dist/lib/domain/interfaces/types/index.js +33 -1
- package/dist/lib/domain/interfaces/types/index.js.map +1 -1
- package/dist/lib/domain/utils/deduplication.d.ts +38 -0
- package/dist/lib/domain/utils/deduplication.d.ts.map +1 -0
- package/dist/lib/domain/utils/deduplication.js +225 -0
- package/dist/lib/domain/utils/deduplication.js.map +1 -0
- package/dist/lib/domain/utils/index.d.ts +1 -0
- package/dist/lib/domain/utils/index.d.ts.map +1 -1
- package/dist/lib/domain/utils/index.js +6 -1
- package/dist/lib/domain/utils/index.js.map +1 -1
- package/dist/lib/infrastructure/dal/repositories/mcp/McpMemoryRepository.d.ts +12 -2
- package/dist/lib/infrastructure/dal/repositories/mcp/McpMemoryRepository.d.ts.map +1 -1
- package/dist/lib/infrastructure/dal/repositories/mcp/McpMemoryRepository.js +14 -0
- package/dist/lib/infrastructure/dal/repositories/mcp/McpMemoryRepository.js.map +1 -1
- package/dist/lib/infrastructure/dal/repositories/neo4j/Neo4jMemoryRepository.d.ts +25 -3
- package/dist/lib/infrastructure/dal/repositories/neo4j/Neo4jMemoryRepository.d.ts.map +1 -1
- package/dist/lib/infrastructure/dal/repositories/neo4j/Neo4jMemoryRepository.js +156 -1
- package/dist/lib/infrastructure/dal/repositories/neo4j/Neo4jMemoryRepository.js.map +1 -1
- package/dist/lib/infrastructure/di/bootstrap.d.ts.map +1 -1
- package/dist/lib/infrastructure/di/bootstrap.js +92 -2
- package/dist/lib/infrastructure/di/bootstrap.js.map +1 -1
- package/dist/lib/infrastructure/di/tokens.d.ts +24 -0
- package/dist/lib/infrastructure/di/tokens.d.ts.map +1 -1
- package/dist/lib/infrastructure/di/tokens.js +12 -0
- package/dist/lib/infrastructure/di/tokens.js.map +1 -1
- package/dist/lib/infrastructure/services/ConsolidationService.d.ts +21 -0
- package/dist/lib/infrastructure/services/ConsolidationService.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/ConsolidationService.js +134 -0
- package/dist/lib/infrastructure/services/ConsolidationService.js.map +1 -0
- package/dist/lib/infrastructure/services/CurationService.d.ts +28 -0
- package/dist/lib/infrastructure/services/CurationService.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/CurationService.js +112 -0
- package/dist/lib/infrastructure/services/CurationService.js.map +1 -0
- package/dist/lib/infrastructure/services/DeduplicationService.d.ts +21 -0
- package/dist/lib/infrastructure/services/DeduplicationService.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/DeduplicationService.js +111 -0
- package/dist/lib/infrastructure/services/DeduplicationService.js.map +1 -0
- package/dist/lib/infrastructure/services/LlmConfigService.d.ts +19 -0
- package/dist/lib/infrastructure/services/LlmConfigService.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/LlmConfigService.js +161 -0
- package/dist/lib/infrastructure/services/LlmConfigService.js.map +1 -0
- package/dist/lib/infrastructure/services/LlmDeduplicationEnhancer.d.ts +47 -0
- package/dist/lib/infrastructure/services/LlmDeduplicationEnhancer.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/LlmDeduplicationEnhancer.js +176 -0
- package/dist/lib/infrastructure/services/LlmDeduplicationEnhancer.js.map +1 -0
- package/dist/lib/infrastructure/services/LlmGuard.d.ts +29 -0
- package/dist/lib/infrastructure/services/LlmGuard.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/LlmGuard.js +92 -0
- package/dist/lib/infrastructure/services/LlmGuard.js.map +1 -0
- package/dist/lib/infrastructure/services/LlmService.d.ts +19 -0
- package/dist/lib/infrastructure/services/LlmService.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/LlmService.js +253 -0
- package/dist/lib/infrastructure/services/LlmService.js.map +1 -0
- package/dist/lib/infrastructure/services/LlmUsageTracker.d.ts +25 -0
- package/dist/lib/infrastructure/services/LlmUsageTracker.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/LlmUsageTracker.js +130 -0
- package/dist/lib/infrastructure/services/LlmUsageTracker.js.map +1 -0
- package/dist/lib/infrastructure/services/MemoryService.d.ts +29 -2
- package/dist/lib/infrastructure/services/MemoryService.d.ts.map +1 -1
- package/dist/lib/infrastructure/services/MemoryService.js +93 -0
- package/dist/lib/infrastructure/services/MemoryService.js.map +1 -1
- package/dist/lib/infrastructure/services/NlCurationService.d.ts +29 -0
- package/dist/lib/infrastructure/services/NlCurationService.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/NlCurationService.js +276 -0
- package/dist/lib/infrastructure/services/NlCurationService.js.map +1 -0
- package/dist/lib/infrastructure/services/PreferenceStore.d.ts +19 -0
- package/dist/lib/infrastructure/services/PreferenceStore.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/PreferenceStore.js +109 -0
- package/dist/lib/infrastructure/services/PreferenceStore.js.map +1 -0
- package/dist/lib/infrastructure/services/SessionCaptureService.d.ts +60 -19
- package/dist/lib/infrastructure/services/SessionCaptureService.d.ts.map +1 -1
- package/dist/lib/infrastructure/services/SessionCaptureService.js +474 -51
- package/dist/lib/infrastructure/services/SessionCaptureService.js.map +1 -1
- package/dist/lib/infrastructure/services/SummarizationService.d.ts +21 -0
- package/dist/lib/infrastructure/services/SummarizationService.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/SummarizationService.js +132 -0
- package/dist/lib/infrastructure/services/SummarizationService.js.map +1 -0
- package/dist/lib/infrastructure/services/TranscriptEnricher.d.ts +23 -0
- package/dist/lib/infrastructure/services/TranscriptEnricher.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/TranscriptEnricher.js +170 -0
- package/dist/lib/infrastructure/services/TranscriptEnricher.js.map +1 -0
- package/dist/lib/infrastructure/services/index.d.ts +12 -0
- package/dist/lib/infrastructure/services/index.d.ts.map +1 -1
- package/dist/lib/infrastructure/services/index.js +31 -1
- package/dist/lib/infrastructure/services/index.js.map +1 -1
- package/dist/lib/infrastructure/services/prompts/curation.d.ts +22 -0
- package/dist/lib/infrastructure/services/prompts/curation.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/prompts/curation.js +73 -0
- package/dist/lib/infrastructure/services/prompts/curation.js.map +1 -0
- package/dist/lib/infrastructure/services/prompts/deduplication.d.ts +31 -0
- package/dist/lib/infrastructure/services/prompts/deduplication.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/prompts/deduplication.js +52 -0
- package/dist/lib/infrastructure/services/prompts/deduplication.js.map +1 -0
- package/dist/lib/infrastructure/services/prompts/extraction.d.ts +27 -0
- package/dist/lib/infrastructure/services/prompts/extraction.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/prompts/extraction.js +94 -0
- package/dist/lib/infrastructure/services/prompts/extraction.js.map +1 -0
- package/dist/lib/infrastructure/services/prompts/summarization.d.ts +25 -0
- package/dist/lib/infrastructure/services/prompts/summarization.d.ts.map +1 -0
- package/dist/lib/infrastructure/services/prompts/summarization.js +60 -0
- package/dist/lib/infrastructure/services/prompts/summarization.js.map +1 -0
- package/dist/lib/skills/common/type-mappings.d.ts.map +1 -1
- package/dist/lib/skills/common/type-mappings.js +5 -0
- package/dist/lib/skills/common/type-mappings.js.map +1 -1
- package/dist/lib/skills/memory/memory.d.ts +8 -2
- package/dist/lib/skills/memory/memory.d.ts.map +1 -1
- package/dist/lib/skills/memory/memory.js +27 -3
- package/dist/lib/skills/memory/memory.js.map +1 -1
- package/dist/lib/skills/shared/clients/Neo4jClient.d.ts.map +1 -1
- package/dist/lib/skills/shared/clients/Neo4jClient.js +75 -30
- package/dist/lib/skills/shared/clients/Neo4jClient.js.map +1 -1
- package/dist/lib/skills/shared/clients/interfaces/INeo4jClient.d.ts +16 -0
- package/dist/lib/skills/shared/clients/interfaces/INeo4jClient.d.ts.map +1 -1
- package/dist/lib/skills/shared/services/MemoryCliService.d.ts +28 -2
- package/dist/lib/skills/shared/services/MemoryCliService.d.ts.map +1 -1
- package/dist/lib/skills/shared/services/MemoryCliService.js +112 -5
- package/dist/lib/skills/shared/services/MemoryCliService.js.map +1 -1
- package/dist/lib/skills/shared/services/MemoryService.d.ts.map +1 -1
- package/dist/lib/skills/shared/services/MemoryService.js +410 -1
- package/dist/lib/skills/shared/services/MemoryService.js.map +1 -1
- package/dist/lib/skills/shared/services/index.d.ts +1 -1
- package/dist/lib/skills/shared/services/index.d.ts.map +1 -1
- package/dist/lib/skills/shared/services/index.js +2 -1
- package/dist/lib/skills/shared/services/index.js.map +1 -1
- package/dist/lib/skills/shared/services/interfaces/IMemoryService.d.ts +148 -0
- package/dist/lib/skills/shared/services/interfaces/IMemoryService.d.ts.map +1 -1
- package/dist/lib/skills/shared/services/interfaces/IMemoryService.js +0 -4
- package/dist/lib/skills/shared/services/interfaces/IMemoryService.js.map +1 -1
- package/dist/opencode/lisa.js +3904 -507
- package/dist/package.json +16 -16
- package/dist/project/.lisa/skills/git/SKILL.md +4 -13
- package/dist/project/.lisa/skills/init-review/SKILL.md +1 -1
- package/dist/project/.lisa/skills/jira/SKILL.md +1 -1
- package/dist/project/.lisa/skills/lisa/SKILL.md +1 -1
- package/dist/project/.lisa/skills/memory/SKILL.md +1 -2
- package/dist/project/.lisa/skills/tasks/SKILL.md +3 -4
- package/package.json +1 -1
|
@@ -8,10 +8,30 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const child_process_1 = require("child_process");
|
|
10
10
|
const domain_1 = require("../../domain");
|
|
11
|
+
const ITaskType_1 = require("../../domain/interfaces/types/ITaskType");
|
|
11
12
|
/**
|
|
12
13
|
* Minimum messages required for capture.
|
|
13
14
|
*/
|
|
14
15
|
const MIN_MESSAGES_FOR_CAPTURE = 3;
|
|
16
|
+
/**
|
|
17
|
+
* Maximum heuristic detection caps.
|
|
18
|
+
*/
|
|
19
|
+
const MAX_DECISIONS = 3;
|
|
20
|
+
const MAX_ERRORS = 2;
|
|
21
|
+
const MAX_CORRELATIONS = 3;
|
|
22
|
+
/**
|
|
23
|
+
* User confirmation patterns for decision detection.
|
|
24
|
+
*/
|
|
25
|
+
const CONFIRMATION_PATTERN = /^(yes|ok|sounds good|let'?s go with|do that|go ahead|approved?|agreed?)\b/i;
|
|
26
|
+
/**
|
|
27
|
+
* Assistant option-presenting language for decision context.
|
|
28
|
+
*/
|
|
29
|
+
const OPTION_PRESENTING_PATTERN = /\b(option|approach|alternative|should we|we could|either|or we|recommend|suggest)\b/i;
|
|
30
|
+
/**
|
|
31
|
+
* Error patterns in message content.
|
|
32
|
+
*/
|
|
33
|
+
const STACK_TRACE_PATTERN = /^\s+at\s+/m;
|
|
34
|
+
const ERROR_TYPE_PATTERN = /\b(Error|TypeError|ReferenceError|SyntaxError|RangeError):\s+/;
|
|
15
35
|
/**
|
|
16
36
|
* Service for capturing session work from Claude Code transcripts.
|
|
17
37
|
*
|
|
@@ -19,8 +39,9 @@ const MIN_MESSAGES_FOR_CAPTURE = 3;
|
|
|
19
39
|
* This is the full implementation that replaces the session-stop-worker.
|
|
20
40
|
*/
|
|
21
41
|
class SessionCaptureService {
|
|
22
|
-
constructor(logger) {
|
|
42
|
+
constructor(logger, transcriptEnricher) {
|
|
23
43
|
this.logger = logger;
|
|
44
|
+
this.transcriptEnricher = transcriptEnricher;
|
|
24
45
|
}
|
|
25
46
|
/**
|
|
26
47
|
* Capture work from the current session.
|
|
@@ -51,10 +72,36 @@ class SessionCaptureService {
|
|
|
51
72
|
// 4. Build facts from work summary
|
|
52
73
|
const facts = this.buildFacts(work, sessionId);
|
|
53
74
|
const complexity = this.rateComplexity(work);
|
|
75
|
+
// 5. Optionally enrich with LLM extraction
|
|
76
|
+
let enrichedFacts = [];
|
|
77
|
+
let enrichedSummary;
|
|
78
|
+
if (this.transcriptEnricher) {
|
|
79
|
+
try {
|
|
80
|
+
const snippet = this.getTranscriptSnippet(foundPath);
|
|
81
|
+
const enrichment = await this.transcriptEnricher.enrich(work, snippet);
|
|
82
|
+
if (enrichment.facts.length > 0) {
|
|
83
|
+
enrichedFacts = enrichment.facts.map(f => {
|
|
84
|
+
// Filter out any pre-existing metadata tags before appending canonical ones
|
|
85
|
+
const baseTags = f.tags.filter(t => !t.startsWith('type:') && !t.startsWith('confidence:') && !t.startsWith('source:'));
|
|
86
|
+
const tags = [...baseTags, `type:${f.type}`, `confidence:${f.confidence}`, 'source:llm-extracted'];
|
|
87
|
+
return `${f.text} [${tags.join(', ')}]`;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (enrichment.summary && enrichment.summary.length > 0) {
|
|
91
|
+
enrichedSummary = enrichment.summary;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
this.logger?.warn('LLM enrichment failed, using pattern-based extraction only', {
|
|
96
|
+
error: error instanceof Error ? error.message : String(error),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
54
100
|
return {
|
|
55
|
-
facts,
|
|
101
|
+
facts: [...facts, ...enrichedFacts],
|
|
56
102
|
complexity,
|
|
57
|
-
summary: work.summary
|
|
103
|
+
summary: enrichedSummary ?? work.summary ?? undefined,
|
|
104
|
+
work,
|
|
58
105
|
};
|
|
59
106
|
}
|
|
60
107
|
catch (error) {
|
|
@@ -116,49 +163,58 @@ class SessionCaptureService {
|
|
|
116
163
|
/**
|
|
117
164
|
* Find all transcript candidates from standard Claude Code locations.
|
|
118
165
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
166
|
+
* Claude Code stores session transcripts as UUID-named JSONL files:
|
|
167
|
+
* ~/.claude/projects/<project-folder>/<session-uuid>.jsonl
|
|
168
|
+
*
|
|
169
|
+
* The project folder is derived from the working directory path with
|
|
170
|
+
* path separators replaced by dashes (e.g. C:\dev\lisa → C--dev-lisa).
|
|
122
171
|
*
|
|
123
172
|
* @returns Array of candidates with path and modification time
|
|
124
173
|
*/
|
|
125
174
|
findTranscriptCandidates() {
|
|
126
175
|
const candidates = [];
|
|
127
176
|
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
if (fs_1.default.existsSync(
|
|
177
|
+
const projectsDir = path_1.default.join(homeDir, '.claude', 'projects');
|
|
178
|
+
// Derive project folder name from CWD
|
|
179
|
+
// Claude Code convention: C:\dev\lisa → C--dev-lisa
|
|
180
|
+
const cwd = process.cwd();
|
|
181
|
+
const projectFolderName = cwd.replace(/[:\\/]/g, '-');
|
|
182
|
+
const projectDir = path_1.default.join(projectsDir, projectFolderName);
|
|
183
|
+
// Search UUID-named session transcripts in ~/.claude/projects/
|
|
184
|
+
if (fs_1.default.existsSync(projectsDir)) {
|
|
185
|
+
const dirsToSearch = [];
|
|
186
|
+
if (fs_1.default.existsSync(projectDir)) {
|
|
187
|
+
dirsToSearch.push(projectDir);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// Fallback: scan all project folders if derived name doesn't match
|
|
191
|
+
this.logger?.debug('Project dir not found, scanning all projects', {
|
|
192
|
+
expected: projectFolderName,
|
|
193
|
+
});
|
|
138
194
|
try {
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
195
|
+
const entries = fs_1.default.readdirSync(projectsDir, { withFileTypes: true });
|
|
196
|
+
for (const entry of entries) {
|
|
197
|
+
if (entry.isDirectory()) {
|
|
198
|
+
dirsToSearch.push(path_1.default.join(projectsDir, entry.name));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
144
201
|
}
|
|
145
202
|
catch {
|
|
146
|
-
//
|
|
203
|
+
// Ignore permission errors
|
|
147
204
|
}
|
|
148
205
|
}
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
206
|
+
// UUID pattern: 8-4-4-4-12 hex chars
|
|
207
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/i;
|
|
208
|
+
for (const dir of dirsToSearch) {
|
|
209
|
+
try {
|
|
210
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
211
|
+
for (const entry of entries) {
|
|
212
|
+
// Match UUID-named .jsonl files (session transcripts, not subagent files)
|
|
213
|
+
if (entry.isFile() && uuidPattern.test(entry.name)) {
|
|
214
|
+
const filePath = path_1.default.join(dir, entry.name);
|
|
156
215
|
try {
|
|
157
|
-
const stats = fs_1.default.statSync(
|
|
158
|
-
candidates.push({
|
|
159
|
-
path: subPath,
|
|
160
|
-
mtime: stats.mtimeMs,
|
|
161
|
-
});
|
|
216
|
+
const stats = fs_1.default.statSync(filePath);
|
|
217
|
+
candidates.push({ path: filePath, mtime: stats.mtimeMs });
|
|
162
218
|
}
|
|
163
219
|
catch {
|
|
164
220
|
// Skip if can't stat
|
|
@@ -166,15 +222,33 @@ class SessionCaptureService {
|
|
|
166
222
|
}
|
|
167
223
|
}
|
|
168
224
|
}
|
|
225
|
+
catch {
|
|
226
|
+
// Ignore permission errors on directory listing
|
|
227
|
+
}
|
|
169
228
|
}
|
|
170
|
-
|
|
171
|
-
|
|
229
|
+
}
|
|
230
|
+
// Legacy fallback: check for transcript.jsonl (older Claude Code versions)
|
|
231
|
+
if (candidates.length === 0) {
|
|
232
|
+
const legacyPaths = [
|
|
233
|
+
path_1.default.join(projectsDir, projectFolderName, 'transcript.jsonl'),
|
|
234
|
+
path_1.default.join(homeDir, '.claude', 'transcript.jsonl'),
|
|
235
|
+
];
|
|
236
|
+
for (const legacyPath of legacyPaths) {
|
|
237
|
+
if (fs_1.default.existsSync(legacyPath)) {
|
|
238
|
+
try {
|
|
239
|
+
const stats = fs_1.default.statSync(legacyPath);
|
|
240
|
+
candidates.push({ path: legacyPath, mtime: stats.mtimeMs });
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// Skip if can't stat
|
|
244
|
+
}
|
|
245
|
+
}
|
|
172
246
|
}
|
|
173
247
|
}
|
|
174
248
|
return candidates;
|
|
175
249
|
}
|
|
176
250
|
/**
|
|
177
|
-
* Parse transcript file to extract work summary.
|
|
251
|
+
* Parse transcript file to extract work summary including heuristic detections.
|
|
178
252
|
*/
|
|
179
253
|
parseTranscript(transcriptPath) {
|
|
180
254
|
const content = fs_1.default.readFileSync(transcriptPath, 'utf8');
|
|
@@ -185,29 +259,38 @@ class SessionCaptureService {
|
|
|
185
259
|
const filesCreated = [];
|
|
186
260
|
const filesModified = [];
|
|
187
261
|
let summaryText = '';
|
|
262
|
+
const parsedMessages = [];
|
|
188
263
|
for (const line of lines) {
|
|
189
264
|
try {
|
|
190
265
|
const msg = JSON.parse(line);
|
|
191
266
|
// Track message types
|
|
192
267
|
if (msg.type === 'user' || msg.message?.role === 'user') {
|
|
193
268
|
userPrompts++;
|
|
269
|
+
const text = this.extractMessageText(msg);
|
|
270
|
+
parsedMessages.push({ role: 'user', text });
|
|
194
271
|
}
|
|
195
272
|
else if (msg.type === 'assistant' || msg.message?.role === 'assistant') {
|
|
196
273
|
assistantResponses++;
|
|
274
|
+
const text = this.extractMessageText(msg);
|
|
275
|
+
parsedMessages.push({ role: 'assistant', text });
|
|
197
276
|
// Extract summary from last assistant message
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
summaryText = content.slice(0, 500);
|
|
201
|
-
}
|
|
202
|
-
else if (Array.isArray(content)) {
|
|
203
|
-
const textBlock = content.find(c => c.type === 'text');
|
|
204
|
-
if (textBlock?.text) {
|
|
205
|
-
summaryText = textBlock.text.slice(0, 500);
|
|
206
|
-
}
|
|
277
|
+
if (text) {
|
|
278
|
+
summaryText = text.slice(0, 500);
|
|
207
279
|
}
|
|
208
280
|
}
|
|
209
|
-
else if (msg.type === 'tool_use'
|
|
281
|
+
else if (msg.type === 'tool_use') {
|
|
282
|
+
toolCalls++;
|
|
283
|
+
const toolName = this.extractToolName(msg);
|
|
284
|
+
parsedMessages.push({ role: 'tool_use', text: '', toolName });
|
|
285
|
+
}
|
|
286
|
+
else if (msg.type === 'tool_result') {
|
|
210
287
|
toolCalls++;
|
|
288
|
+
const isError = this.extractToolError(msg);
|
|
289
|
+
const text = this.extractMessageText(msg);
|
|
290
|
+
parsedMessages.push({ role: 'tool_result', text, isError });
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
parsedMessages.push({ role: 'other', text: '' });
|
|
211
294
|
}
|
|
212
295
|
// Track file operations from summary
|
|
213
296
|
if (msg.summary) {
|
|
@@ -225,17 +308,29 @@ class SessionCaptureService {
|
|
|
225
308
|
}
|
|
226
309
|
catch {
|
|
227
310
|
// Skip malformed lines
|
|
311
|
+
parsedMessages.push({ role: 'other', text: '' });
|
|
228
312
|
}
|
|
229
313
|
}
|
|
314
|
+
const uniqueFilesCreated = [...new Set(filesCreated)];
|
|
315
|
+
const uniqueFilesModified = [...new Set(filesModified)];
|
|
316
|
+
// Run heuristic detectors
|
|
317
|
+
const detectedDecisions = this.detectDecisions(parsedMessages);
|
|
318
|
+
const detectedErrors = this.detectErrors(parsedMessages);
|
|
319
|
+
const filePromptCorrelations = this.correlateFilePrompts(parsedMessages, uniqueFilesCreated, uniqueFilesModified);
|
|
320
|
+
const detectedTaskType = this.detectTaskType(parsedMessages);
|
|
230
321
|
return {
|
|
231
322
|
messageCount: lines.length,
|
|
232
323
|
userPrompts,
|
|
233
324
|
assistantResponses,
|
|
234
325
|
toolCalls,
|
|
235
|
-
filesCreated:
|
|
236
|
-
filesModified:
|
|
237
|
-
duration: 0,
|
|
326
|
+
filesCreated: uniqueFilesCreated,
|
|
327
|
+
filesModified: uniqueFilesModified,
|
|
328
|
+
duration: 0,
|
|
238
329
|
summary: summaryText,
|
|
330
|
+
detectedDecisions: detectedDecisions.length > 0 ? detectedDecisions : undefined,
|
|
331
|
+
detectedErrors: detectedErrors.length > 0 ? detectedErrors : undefined,
|
|
332
|
+
filePromptCorrelations: filePromptCorrelations.length > 0 ? filePromptCorrelations : undefined,
|
|
333
|
+
detectedTaskType,
|
|
239
334
|
};
|
|
240
335
|
}
|
|
241
336
|
/**
|
|
@@ -262,7 +357,7 @@ class SessionCaptureService {
|
|
|
262
357
|
return work.messageCount >= 5;
|
|
263
358
|
}
|
|
264
359
|
/**
|
|
265
|
-
* Build facts from work summary.
|
|
360
|
+
* Build facts from work summary including heuristic-detected facts.
|
|
266
361
|
*/
|
|
267
362
|
buildFacts(work, sessionId) {
|
|
268
363
|
const facts = [];
|
|
@@ -292,6 +387,24 @@ class SessionCaptureService {
|
|
|
292
387
|
facts.push(`Session summary${sessionTag}: ${shortSummary}...`);
|
|
293
388
|
}
|
|
294
389
|
}
|
|
390
|
+
// Add heuristic-detected decision facts
|
|
391
|
+
if (work.detectedDecisions) {
|
|
392
|
+
for (const decision of work.detectedDecisions) {
|
|
393
|
+
facts.push(`DECISION: ${decision.text} [source:session-capture, confidence:medium, type:decision]`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Add heuristic-detected error facts
|
|
397
|
+
if (work.detectedErrors) {
|
|
398
|
+
for (const error of work.detectedErrors) {
|
|
399
|
+
facts.push(`ERROR: ${error.text} [source:session-capture, confidence:medium, type:error]`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Add file-prompt correlation facts
|
|
403
|
+
if (work.filePromptCorrelations) {
|
|
404
|
+
for (const corr of work.filePromptCorrelations) {
|
|
405
|
+
facts.push(`FILE-CONTEXT: ${corr.filePath} — triggered by: ${corr.triggerSnippet} [source:session-capture, confidence:medium, type:correlation, file:${corr.filePath}]`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
295
408
|
return facts;
|
|
296
409
|
}
|
|
297
410
|
/**
|
|
@@ -309,6 +422,316 @@ class SessionCaptureService {
|
|
|
309
422
|
}
|
|
310
423
|
return 'low';
|
|
311
424
|
}
|
|
425
|
+
// ============================================================
|
|
426
|
+
// Heuristic Detection Methods
|
|
427
|
+
// ============================================================
|
|
428
|
+
/**
|
|
429
|
+
* Detect user decisions from confirmation patterns.
|
|
430
|
+
*
|
|
431
|
+
* Scans for user messages matching confirmation patterns (yes, ok, sounds good, etc.)
|
|
432
|
+
* preceded by assistant messages that present options or recommendations.
|
|
433
|
+
*/
|
|
434
|
+
detectDecisions(messages) {
|
|
435
|
+
const decisions = [];
|
|
436
|
+
for (let i = 0; i < messages.length; i++) {
|
|
437
|
+
if (decisions.length >= MAX_DECISIONS)
|
|
438
|
+
break;
|
|
439
|
+
const msg = messages[i];
|
|
440
|
+
if (msg.role !== 'user')
|
|
441
|
+
continue;
|
|
442
|
+
if (!CONFIRMATION_PATTERN.test(msg.text.trim()))
|
|
443
|
+
continue;
|
|
444
|
+
// Look backward within 3 messages for an assistant message with options
|
|
445
|
+
for (let j = i - 1; j >= Math.max(0, i - 3); j--) {
|
|
446
|
+
const prev = messages[j];
|
|
447
|
+
if (prev.role !== 'assistant')
|
|
448
|
+
continue;
|
|
449
|
+
if (!OPTION_PRESENTING_PATTERN.test(prev.text))
|
|
450
|
+
continue;
|
|
451
|
+
// Extract decision topic from the assistant's message (first sentence or first 120 chars)
|
|
452
|
+
const topic = this.extractDecisionTopic(prev.text);
|
|
453
|
+
if (topic) {
|
|
454
|
+
decisions.push({
|
|
455
|
+
text: topic,
|
|
456
|
+
userMessage: msg.text.slice(0, 100),
|
|
457
|
+
confidence: 0.6,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return decisions;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Detect errors from stack traces, error types, tool failures, and retry patterns.
|
|
467
|
+
*/
|
|
468
|
+
detectErrors(messages) {
|
|
469
|
+
const errors = [];
|
|
470
|
+
// 1. Detect stack traces and Error: patterns in message content
|
|
471
|
+
for (const msg of messages) {
|
|
472
|
+
if (errors.length >= MAX_ERRORS)
|
|
473
|
+
break;
|
|
474
|
+
if (!msg.text)
|
|
475
|
+
continue;
|
|
476
|
+
if (STACK_TRACE_PATTERN.test(msg.text)) {
|
|
477
|
+
const errorLine = this.extractErrorLine(msg.text);
|
|
478
|
+
if (errorLine) {
|
|
479
|
+
errors.push({
|
|
480
|
+
text: errorLine,
|
|
481
|
+
errorType: 'stack-trace',
|
|
482
|
+
context: msg.text.slice(0, 200),
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
else if (ERROR_TYPE_PATTERN.test(msg.text)) {
|
|
487
|
+
const match = msg.text.match(ERROR_TYPE_PATTERN);
|
|
488
|
+
if (match) {
|
|
489
|
+
const matchIndex = msg.text.indexOf(match[0]);
|
|
490
|
+
const errorText = msg.text.slice(matchIndex, matchIndex + 150).split('\n')[0];
|
|
491
|
+
errors.push({
|
|
492
|
+
text: errorText.trim(),
|
|
493
|
+
errorType: 'stack-trace',
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
// 2. Detect tool failures
|
|
499
|
+
if (errors.length < MAX_ERRORS) {
|
|
500
|
+
for (const msg of messages) {
|
|
501
|
+
if (errors.length >= MAX_ERRORS)
|
|
502
|
+
break;
|
|
503
|
+
if (msg.role === 'tool_result' && msg.isError) {
|
|
504
|
+
errors.push({
|
|
505
|
+
text: `Tool failure: ${msg.text.slice(0, 100)}`,
|
|
506
|
+
errorType: 'tool-failure',
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
// 3. Detect retry patterns (same tool_use name >2 times in sequence)
|
|
512
|
+
if (errors.length < MAX_ERRORS) {
|
|
513
|
+
let consecutiveCount = 0;
|
|
514
|
+
let lastToolName = '';
|
|
515
|
+
for (const msg of messages) {
|
|
516
|
+
if (msg.role === 'tool_use' && msg.toolName) {
|
|
517
|
+
if (msg.toolName === lastToolName) {
|
|
518
|
+
consecutiveCount++;
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
consecutiveCount = 1;
|
|
522
|
+
lastToolName = msg.toolName;
|
|
523
|
+
}
|
|
524
|
+
if (consecutiveCount > 2 && errors.length < MAX_ERRORS) {
|
|
525
|
+
errors.push({
|
|
526
|
+
text: `Retry pattern detected: tool "${lastToolName}" called ${consecutiveCount}+ times consecutively`,
|
|
527
|
+
errorType: 'retry-pattern',
|
|
528
|
+
});
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
else if (msg.role !== 'tool_result') {
|
|
533
|
+
// Reset on non-tool messages (but tool_result follows tool_use so skip that)
|
|
534
|
+
consecutiveCount = 0;
|
|
535
|
+
lastToolName = '';
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return errors;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Correlate file changes with preceding user prompts.
|
|
543
|
+
*
|
|
544
|
+
* For each changed file, find preceding user prompt (within 3-message window)
|
|
545
|
+
* whose text mentions the file name or related keywords.
|
|
546
|
+
*/
|
|
547
|
+
correlateFilePrompts(messages, filesCreated, filesModified) {
|
|
548
|
+
const correlations = [];
|
|
549
|
+
const allFiles = [...filesCreated, ...filesModified];
|
|
550
|
+
const matchedFiles = new Set();
|
|
551
|
+
// Find assistant messages that mention each file (tool responses),
|
|
552
|
+
// then search backward within 3-message window for the triggering user prompt.
|
|
553
|
+
const MESSAGE_WINDOW = 3;
|
|
554
|
+
for (const filePath of allFiles) {
|
|
555
|
+
if (correlations.length >= MAX_CORRELATIONS)
|
|
556
|
+
break;
|
|
557
|
+
if (matchedFiles.has(filePath))
|
|
558
|
+
continue;
|
|
559
|
+
const fileName = path_1.default.basename(filePath);
|
|
560
|
+
const fileNameNoExt = fileName.replace(/\.[^.]+$/, '');
|
|
561
|
+
const fileNameLower = fileName.toLowerCase();
|
|
562
|
+
const fileNameNoExtLower = fileNameNoExt.toLowerCase();
|
|
563
|
+
const hasStem = fileNameNoExtLower.length > 0;
|
|
564
|
+
// Find the last assistant message mentioning this file (tool response)
|
|
565
|
+
let fileMessageIndex = -1;
|
|
566
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
567
|
+
if (messages[i].role === 'assistant' && messages[i].text) {
|
|
568
|
+
const textLower = messages[i].text.toLowerCase();
|
|
569
|
+
if (textLower.includes(fileNameLower) || (hasStem && textLower.includes(fileNameNoExtLower))) {
|
|
570
|
+
fileMessageIndex = i;
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
// Search backward from file message within 3-message window for user prompt
|
|
576
|
+
const searchStart = fileMessageIndex >= 0 ? fileMessageIndex : messages.length - 1;
|
|
577
|
+
let messagesChecked = 0;
|
|
578
|
+
for (let j = searchStart; j >= 0 && messagesChecked < MESSAGE_WINDOW; j--) {
|
|
579
|
+
messagesChecked++;
|
|
580
|
+
if (messages[j].role !== 'user' || !messages[j].text)
|
|
581
|
+
continue;
|
|
582
|
+
const textLower = messages[j].text.toLowerCase();
|
|
583
|
+
if (textLower.includes(fileNameLower) || (hasStem && textLower.includes(fileNameNoExtLower))) {
|
|
584
|
+
correlations.push({
|
|
585
|
+
filePath,
|
|
586
|
+
triggerSnippet: messages[j].text.slice(0, 100),
|
|
587
|
+
});
|
|
588
|
+
matchedFiles.add(filePath);
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return correlations;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Detect the dominant task type from user prompts using DETECTION_SIGNALS.
|
|
597
|
+
*/
|
|
598
|
+
detectTaskType(messages) {
|
|
599
|
+
const scores = Object.keys(ITaskType_1.DETECTION_SIGNALS).reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
|
|
600
|
+
for (const msg of messages) {
|
|
601
|
+
if (msg.role !== 'user' || !msg.text)
|
|
602
|
+
continue;
|
|
603
|
+
const textLower = msg.text.toLowerCase();
|
|
604
|
+
for (const [taskType, signals] of Object.entries(ITaskType_1.DETECTION_SIGNALS)) {
|
|
605
|
+
for (const signal of signals) {
|
|
606
|
+
if (textLower.includes(signal.toLowerCase())) {
|
|
607
|
+
scores[taskType]++;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
// Find dominant type
|
|
613
|
+
let maxScore = 0;
|
|
614
|
+
let dominant;
|
|
615
|
+
for (const [taskType, score] of Object.entries(scores)) {
|
|
616
|
+
if (score > maxScore) {
|
|
617
|
+
maxScore = score;
|
|
618
|
+
dominant = taskType;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// Only return if there's a meaningful signal
|
|
622
|
+
return maxScore > 0 ? dominant : undefined;
|
|
623
|
+
}
|
|
624
|
+
// ============================================================
|
|
625
|
+
// Helper Methods
|
|
626
|
+
// ============================================================
|
|
627
|
+
/**
|
|
628
|
+
* Extract text content from a transcript message.
|
|
629
|
+
*/
|
|
630
|
+
extractMessageText(msg) {
|
|
631
|
+
const content = msg.message?.content;
|
|
632
|
+
if (typeof content === 'string') {
|
|
633
|
+
return content;
|
|
634
|
+
}
|
|
635
|
+
if (Array.isArray(content)) {
|
|
636
|
+
const textBlocks = content
|
|
637
|
+
.map(block => block.text)
|
|
638
|
+
.filter((text) => typeof text === 'string' && text.length > 0);
|
|
639
|
+
if (textBlocks.length > 0) {
|
|
640
|
+
return textBlocks.join('\n');
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return '';
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Extract tool name from a transcript message.
|
|
647
|
+
*/
|
|
648
|
+
extractToolName(msg) {
|
|
649
|
+
const content = msg.message?.content;
|
|
650
|
+
if (Array.isArray(content)) {
|
|
651
|
+
const toolBlock = content.find(c => c.name);
|
|
652
|
+
if (toolBlock?.name)
|
|
653
|
+
return toolBlock.name;
|
|
654
|
+
}
|
|
655
|
+
return undefined;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Check if a tool_result message indicates an error.
|
|
659
|
+
*/
|
|
660
|
+
extractToolError(msg) {
|
|
661
|
+
const content = msg.message?.content;
|
|
662
|
+
if (Array.isArray(content)) {
|
|
663
|
+
return content.some(c => c.is_error === true);
|
|
664
|
+
}
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Extract the first Error: line from text containing a stack trace.
|
|
669
|
+
*/
|
|
670
|
+
extractErrorLine(text) {
|
|
671
|
+
const lines = text.split('\n');
|
|
672
|
+
for (const line of lines) {
|
|
673
|
+
if (ERROR_TYPE_PATTERN.test(line)) {
|
|
674
|
+
return line.trim().slice(0, 150);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
// Fallback: use first line
|
|
678
|
+
return lines[0] ? lines[0].trim().slice(0, 150) : null;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Extract a decision topic from an assistant's message.
|
|
682
|
+
* Takes the first sentence or first 120 characters.
|
|
683
|
+
*/
|
|
684
|
+
extractDecisionTopic(text) {
|
|
685
|
+
if (!text)
|
|
686
|
+
return null;
|
|
687
|
+
// Try to get first sentence
|
|
688
|
+
const sentenceMatch = text.match(/^[^.!?]+[.!?]/);
|
|
689
|
+
if (sentenceMatch && sentenceMatch[0].length <= 120) {
|
|
690
|
+
return sentenceMatch[0].trim();
|
|
691
|
+
}
|
|
692
|
+
// Fallback: first 120 chars
|
|
693
|
+
return text.slice(0, 120).trim();
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Extract a snippet of conversation text from the transcript for LLM analysis.
|
|
697
|
+
* Concatenates user and assistant text content, truncated to a reasonable length.
|
|
698
|
+
*/
|
|
699
|
+
getTranscriptSnippet(transcriptPath, maxLength = 4000) {
|
|
700
|
+
const content = fs_1.default.readFileSync(transcriptPath, 'utf8');
|
|
701
|
+
const lines = content.trim().split('\n').filter(line => line.trim());
|
|
702
|
+
const parts = [];
|
|
703
|
+
let totalLength = 0;
|
|
704
|
+
for (const line of lines) {
|
|
705
|
+
try {
|
|
706
|
+
const msg = JSON.parse(line);
|
|
707
|
+
const role = msg.message?.role ?? msg.type;
|
|
708
|
+
if (role === 'user' || role === 'assistant') {
|
|
709
|
+
const msgContent = msg.message?.content;
|
|
710
|
+
let text = '';
|
|
711
|
+
if (typeof msgContent === 'string') {
|
|
712
|
+
text = msgContent;
|
|
713
|
+
}
|
|
714
|
+
else if (Array.isArray(msgContent)) {
|
|
715
|
+
const textBlock = msgContent.find(c => c.type === 'text');
|
|
716
|
+
if (textBlock?.text) {
|
|
717
|
+
text = textBlock.text;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (text) {
|
|
721
|
+
const part = `[${role}] ${text.slice(0, 500)}`;
|
|
722
|
+
parts.push(part);
|
|
723
|
+
totalLength += part.length;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
catch {
|
|
728
|
+
// Skip malformed lines
|
|
729
|
+
}
|
|
730
|
+
if (totalLength >= maxLength)
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
return parts.join('\n\n').slice(0, maxLength);
|
|
734
|
+
}
|
|
312
735
|
/**
|
|
313
736
|
* Detect repository name from git.
|
|
314
737
|
*/
|