@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.
Files changed (245) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/application/handlers/PromptSubmitHandler.js +2 -2
  3. package/dist/lib/application/handlers/PromptSubmitHandler.js.map +1 -1
  4. package/dist/lib/application/handlers/SessionStopHandler.d.ts.map +1 -1
  5. package/dist/lib/application/handlers/SessionStopHandler.js +12 -2
  6. package/dist/lib/application/handlers/SessionStopHandler.js.map +1 -1
  7. package/dist/lib/commands/init.js +3 -3
  8. package/dist/lib/commands/init.js.map +1 -1
  9. package/dist/lib/commands/knowledge.d.ts.map +1 -1
  10. package/dist/lib/commands/knowledge.js +449 -1
  11. package/dist/lib/commands/knowledge.js.map +1 -1
  12. package/dist/lib/domain/errors/LlmErrors.d.ts +41 -0
  13. package/dist/lib/domain/errors/LlmErrors.d.ts.map +1 -0
  14. package/dist/lib/domain/errors/LlmErrors.js +64 -0
  15. package/dist/lib/domain/errors/LlmErrors.js.map +1 -0
  16. package/dist/lib/domain/interfaces/IConsolidationService.d.ts +65 -0
  17. package/dist/lib/domain/interfaces/IConsolidationService.d.ts.map +1 -0
  18. package/dist/lib/domain/interfaces/IConsolidationService.js +21 -0
  19. package/dist/lib/domain/interfaces/IConsolidationService.js.map +1 -0
  20. package/dist/lib/domain/interfaces/ICurationService.d.ts +81 -0
  21. package/dist/lib/domain/interfaces/ICurationService.d.ts.map +1 -0
  22. package/dist/lib/domain/interfaces/ICurationService.js +65 -0
  23. package/dist/lib/domain/interfaces/ICurationService.js.map +1 -0
  24. package/dist/lib/domain/interfaces/IDeduplicationService.d.ts +67 -0
  25. package/dist/lib/domain/interfaces/IDeduplicationService.d.ts.map +1 -0
  26. package/dist/lib/domain/interfaces/IDeduplicationService.js +9 -0
  27. package/dist/lib/domain/interfaces/IDeduplicationService.js.map +1 -0
  28. package/dist/lib/domain/interfaces/ILlmConfigService.d.ts +36 -0
  29. package/dist/lib/domain/interfaces/ILlmConfigService.d.ts.map +1 -0
  30. package/dist/lib/domain/interfaces/ILlmConfigService.js +11 -0
  31. package/dist/lib/domain/interfaces/ILlmConfigService.js.map +1 -0
  32. package/dist/lib/domain/interfaces/ILlmGuard.d.ts +35 -0
  33. package/dist/lib/domain/interfaces/ILlmGuard.d.ts.map +1 -0
  34. package/dist/lib/domain/interfaces/ILlmGuard.js +12 -0
  35. package/dist/lib/domain/interfaces/ILlmGuard.js.map +1 -0
  36. package/dist/lib/domain/interfaces/ILlmService.d.ts +87 -0
  37. package/dist/lib/domain/interfaces/ILlmService.d.ts.map +1 -0
  38. package/dist/lib/domain/interfaces/ILlmService.js +40 -0
  39. package/dist/lib/domain/interfaces/ILlmService.js.map +1 -0
  40. package/dist/lib/domain/interfaces/ILlmUsageTracker.d.ts +60 -0
  41. package/dist/lib/domain/interfaces/ILlmUsageTracker.d.ts.map +1 -0
  42. package/dist/lib/domain/interfaces/ILlmUsageTracker.js +29 -0
  43. package/dist/lib/domain/interfaces/ILlmUsageTracker.js.map +1 -0
  44. package/dist/lib/domain/interfaces/IMemoryService.d.ts +90 -0
  45. package/dist/lib/domain/interfaces/IMemoryService.d.ts.map +1 -1
  46. package/dist/lib/domain/interfaces/INlCurationService.d.ts +75 -0
  47. package/dist/lib/domain/interfaces/INlCurationService.d.ts.map +1 -0
  48. package/dist/lib/domain/interfaces/INlCurationService.js +31 -0
  49. package/dist/lib/domain/interfaces/INlCurationService.js.map +1 -0
  50. package/dist/lib/domain/interfaces/IPreferenceStore.d.ts +46 -0
  51. package/dist/lib/domain/interfaces/IPreferenceStore.d.ts.map +1 -0
  52. package/dist/lib/domain/interfaces/IPreferenceStore.js +11 -0
  53. package/dist/lib/domain/interfaces/IPreferenceStore.js.map +1 -0
  54. package/dist/lib/domain/interfaces/IRecursionService.d.ts +10 -6
  55. package/dist/lib/domain/interfaces/IRecursionService.d.ts.map +1 -1
  56. package/dist/lib/domain/interfaces/ISummarizationService.d.ts +52 -0
  57. package/dist/lib/domain/interfaces/ISummarizationService.d.ts.map +1 -0
  58. package/dist/lib/domain/interfaces/ISummarizationService.js +11 -0
  59. package/dist/lib/domain/interfaces/ISummarizationService.js.map +1 -0
  60. package/dist/lib/domain/interfaces/ITaskTypeDetector.d.ts +34 -0
  61. package/dist/lib/domain/interfaces/ITaskTypeDetector.d.ts.map +1 -0
  62. package/dist/lib/domain/interfaces/ITaskTypeDetector.js +9 -0
  63. package/dist/lib/domain/interfaces/ITaskTypeDetector.js.map +1 -0
  64. package/dist/lib/domain/interfaces/ITranscriptEnricher.d.ts +68 -0
  65. package/dist/lib/domain/interfaces/ITranscriptEnricher.d.ts.map +1 -0
  66. package/dist/lib/domain/interfaces/ITranscriptEnricher.js +32 -0
  67. package/dist/lib/domain/interfaces/ITranscriptEnricher.js.map +1 -0
  68. package/dist/lib/domain/interfaces/IWorkSummary.d.ts +64 -0
  69. package/dist/lib/domain/interfaces/IWorkSummary.d.ts.map +1 -0
  70. package/dist/lib/domain/interfaces/IWorkSummary.js +13 -0
  71. package/dist/lib/domain/interfaces/IWorkSummary.js.map +1 -0
  72. package/dist/lib/domain/interfaces/dal/IMemoryRelationshipRepository.d.ts +46 -0
  73. package/dist/lib/domain/interfaces/dal/IMemoryRelationshipRepository.d.ts.map +1 -0
  74. package/dist/lib/domain/interfaces/dal/IMemoryRelationshipRepository.js +9 -0
  75. package/dist/lib/domain/interfaces/dal/IMemoryRelationshipRepository.js.map +1 -0
  76. package/dist/lib/domain/interfaces/dal/IMemoryRepository.d.ts +62 -1
  77. package/dist/lib/domain/interfaces/dal/IMemoryRepository.d.ts.map +1 -1
  78. package/dist/lib/domain/interfaces/dal/index.d.ts +3 -2
  79. package/dist/lib/domain/interfaces/dal/index.d.ts.map +1 -1
  80. package/dist/lib/domain/interfaces/dal/index.js.map +1 -1
  81. package/dist/lib/domain/interfaces/dal/types.d.ts +37 -0
  82. package/dist/lib/domain/interfaces/dal/types.d.ts.map +1 -1
  83. package/dist/lib/domain/interfaces/dal/types.js.map +1 -1
  84. package/dist/lib/domain/interfaces/index.d.ts +15 -1
  85. package/dist/lib/domain/interfaces/index.d.ts.map +1 -1
  86. package/dist/lib/domain/interfaces/index.js +21 -1
  87. package/dist/lib/domain/interfaces/index.js.map +1 -1
  88. package/dist/lib/domain/interfaces/types/ICapturedWork.d.ts +3 -0
  89. package/dist/lib/domain/interfaces/types/ICapturedWork.d.ts.map +1 -1
  90. package/dist/lib/domain/interfaces/types/ICapturedWork.js.map +1 -1
  91. package/dist/lib/domain/interfaces/types/IMemoryLifecycle.d.ts +56 -0
  92. package/dist/lib/domain/interfaces/types/IMemoryLifecycle.d.ts.map +1 -0
  93. package/dist/lib/domain/interfaces/types/IMemoryLifecycle.js +94 -0
  94. package/dist/lib/domain/interfaces/types/IMemoryLifecycle.js.map +1 -0
  95. package/dist/lib/domain/interfaces/types/IMemoryQuality.d.ts +113 -0
  96. package/dist/lib/domain/interfaces/types/IMemoryQuality.d.ts.map +1 -0
  97. package/dist/lib/domain/interfaces/types/IMemoryQuality.js +195 -0
  98. package/dist/lib/domain/interfaces/types/IMemoryQuality.js.map +1 -0
  99. package/dist/lib/domain/interfaces/types/IMemoryRelationship.d.ts +49 -0
  100. package/dist/lib/domain/interfaces/types/IMemoryRelationship.d.ts.map +1 -0
  101. package/dist/lib/domain/interfaces/types/IMemoryRelationship.js +62 -0
  102. package/dist/lib/domain/interfaces/types/IMemoryRelationship.js.map +1 -0
  103. package/dist/lib/domain/interfaces/types/ITaskType.d.ts +59 -0
  104. package/dist/lib/domain/interfaces/types/ITaskType.d.ts.map +1 -0
  105. package/dist/lib/domain/interfaces/types/ITaskType.js +104 -0
  106. package/dist/lib/domain/interfaces/types/ITaskType.js.map +1 -0
  107. package/dist/lib/domain/interfaces/types/index.d.ts +4 -0
  108. package/dist/lib/domain/interfaces/types/index.d.ts.map +1 -1
  109. package/dist/lib/domain/interfaces/types/index.js +33 -1
  110. package/dist/lib/domain/interfaces/types/index.js.map +1 -1
  111. package/dist/lib/domain/utils/deduplication.d.ts +38 -0
  112. package/dist/lib/domain/utils/deduplication.d.ts.map +1 -0
  113. package/dist/lib/domain/utils/deduplication.js +225 -0
  114. package/dist/lib/domain/utils/deduplication.js.map +1 -0
  115. package/dist/lib/domain/utils/index.d.ts +1 -0
  116. package/dist/lib/domain/utils/index.d.ts.map +1 -1
  117. package/dist/lib/domain/utils/index.js +6 -1
  118. package/dist/lib/domain/utils/index.js.map +1 -1
  119. package/dist/lib/infrastructure/dal/repositories/mcp/McpMemoryRepository.d.ts +12 -2
  120. package/dist/lib/infrastructure/dal/repositories/mcp/McpMemoryRepository.d.ts.map +1 -1
  121. package/dist/lib/infrastructure/dal/repositories/mcp/McpMemoryRepository.js +14 -0
  122. package/dist/lib/infrastructure/dal/repositories/mcp/McpMemoryRepository.js.map +1 -1
  123. package/dist/lib/infrastructure/dal/repositories/neo4j/Neo4jMemoryRepository.d.ts +25 -3
  124. package/dist/lib/infrastructure/dal/repositories/neo4j/Neo4jMemoryRepository.d.ts.map +1 -1
  125. package/dist/lib/infrastructure/dal/repositories/neo4j/Neo4jMemoryRepository.js +156 -1
  126. package/dist/lib/infrastructure/dal/repositories/neo4j/Neo4jMemoryRepository.js.map +1 -1
  127. package/dist/lib/infrastructure/di/bootstrap.d.ts.map +1 -1
  128. package/dist/lib/infrastructure/di/bootstrap.js +92 -2
  129. package/dist/lib/infrastructure/di/bootstrap.js.map +1 -1
  130. package/dist/lib/infrastructure/di/tokens.d.ts +24 -0
  131. package/dist/lib/infrastructure/di/tokens.d.ts.map +1 -1
  132. package/dist/lib/infrastructure/di/tokens.js +12 -0
  133. package/dist/lib/infrastructure/di/tokens.js.map +1 -1
  134. package/dist/lib/infrastructure/services/ConsolidationService.d.ts +21 -0
  135. package/dist/lib/infrastructure/services/ConsolidationService.d.ts.map +1 -0
  136. package/dist/lib/infrastructure/services/ConsolidationService.js +134 -0
  137. package/dist/lib/infrastructure/services/ConsolidationService.js.map +1 -0
  138. package/dist/lib/infrastructure/services/CurationService.d.ts +28 -0
  139. package/dist/lib/infrastructure/services/CurationService.d.ts.map +1 -0
  140. package/dist/lib/infrastructure/services/CurationService.js +112 -0
  141. package/dist/lib/infrastructure/services/CurationService.js.map +1 -0
  142. package/dist/lib/infrastructure/services/DeduplicationService.d.ts +21 -0
  143. package/dist/lib/infrastructure/services/DeduplicationService.d.ts.map +1 -0
  144. package/dist/lib/infrastructure/services/DeduplicationService.js +111 -0
  145. package/dist/lib/infrastructure/services/DeduplicationService.js.map +1 -0
  146. package/dist/lib/infrastructure/services/LlmConfigService.d.ts +19 -0
  147. package/dist/lib/infrastructure/services/LlmConfigService.d.ts.map +1 -0
  148. package/dist/lib/infrastructure/services/LlmConfigService.js +161 -0
  149. package/dist/lib/infrastructure/services/LlmConfigService.js.map +1 -0
  150. package/dist/lib/infrastructure/services/LlmDeduplicationEnhancer.d.ts +47 -0
  151. package/dist/lib/infrastructure/services/LlmDeduplicationEnhancer.d.ts.map +1 -0
  152. package/dist/lib/infrastructure/services/LlmDeduplicationEnhancer.js +176 -0
  153. package/dist/lib/infrastructure/services/LlmDeduplicationEnhancer.js.map +1 -0
  154. package/dist/lib/infrastructure/services/LlmGuard.d.ts +29 -0
  155. package/dist/lib/infrastructure/services/LlmGuard.d.ts.map +1 -0
  156. package/dist/lib/infrastructure/services/LlmGuard.js +92 -0
  157. package/dist/lib/infrastructure/services/LlmGuard.js.map +1 -0
  158. package/dist/lib/infrastructure/services/LlmService.d.ts +19 -0
  159. package/dist/lib/infrastructure/services/LlmService.d.ts.map +1 -0
  160. package/dist/lib/infrastructure/services/LlmService.js +253 -0
  161. package/dist/lib/infrastructure/services/LlmService.js.map +1 -0
  162. package/dist/lib/infrastructure/services/LlmUsageTracker.d.ts +25 -0
  163. package/dist/lib/infrastructure/services/LlmUsageTracker.d.ts.map +1 -0
  164. package/dist/lib/infrastructure/services/LlmUsageTracker.js +130 -0
  165. package/dist/lib/infrastructure/services/LlmUsageTracker.js.map +1 -0
  166. package/dist/lib/infrastructure/services/MemoryService.d.ts +29 -2
  167. package/dist/lib/infrastructure/services/MemoryService.d.ts.map +1 -1
  168. package/dist/lib/infrastructure/services/MemoryService.js +93 -0
  169. package/dist/lib/infrastructure/services/MemoryService.js.map +1 -1
  170. package/dist/lib/infrastructure/services/NlCurationService.d.ts +29 -0
  171. package/dist/lib/infrastructure/services/NlCurationService.d.ts.map +1 -0
  172. package/dist/lib/infrastructure/services/NlCurationService.js +276 -0
  173. package/dist/lib/infrastructure/services/NlCurationService.js.map +1 -0
  174. package/dist/lib/infrastructure/services/PreferenceStore.d.ts +19 -0
  175. package/dist/lib/infrastructure/services/PreferenceStore.d.ts.map +1 -0
  176. package/dist/lib/infrastructure/services/PreferenceStore.js +109 -0
  177. package/dist/lib/infrastructure/services/PreferenceStore.js.map +1 -0
  178. package/dist/lib/infrastructure/services/SessionCaptureService.d.ts +60 -19
  179. package/dist/lib/infrastructure/services/SessionCaptureService.d.ts.map +1 -1
  180. package/dist/lib/infrastructure/services/SessionCaptureService.js +474 -51
  181. package/dist/lib/infrastructure/services/SessionCaptureService.js.map +1 -1
  182. package/dist/lib/infrastructure/services/SummarizationService.d.ts +21 -0
  183. package/dist/lib/infrastructure/services/SummarizationService.d.ts.map +1 -0
  184. package/dist/lib/infrastructure/services/SummarizationService.js +132 -0
  185. package/dist/lib/infrastructure/services/SummarizationService.js.map +1 -0
  186. package/dist/lib/infrastructure/services/TranscriptEnricher.d.ts +23 -0
  187. package/dist/lib/infrastructure/services/TranscriptEnricher.d.ts.map +1 -0
  188. package/dist/lib/infrastructure/services/TranscriptEnricher.js +170 -0
  189. package/dist/lib/infrastructure/services/TranscriptEnricher.js.map +1 -0
  190. package/dist/lib/infrastructure/services/index.d.ts +12 -0
  191. package/dist/lib/infrastructure/services/index.d.ts.map +1 -1
  192. package/dist/lib/infrastructure/services/index.js +31 -1
  193. package/dist/lib/infrastructure/services/index.js.map +1 -1
  194. package/dist/lib/infrastructure/services/prompts/curation.d.ts +22 -0
  195. package/dist/lib/infrastructure/services/prompts/curation.d.ts.map +1 -0
  196. package/dist/lib/infrastructure/services/prompts/curation.js +73 -0
  197. package/dist/lib/infrastructure/services/prompts/curation.js.map +1 -0
  198. package/dist/lib/infrastructure/services/prompts/deduplication.d.ts +31 -0
  199. package/dist/lib/infrastructure/services/prompts/deduplication.d.ts.map +1 -0
  200. package/dist/lib/infrastructure/services/prompts/deduplication.js +52 -0
  201. package/dist/lib/infrastructure/services/prompts/deduplication.js.map +1 -0
  202. package/dist/lib/infrastructure/services/prompts/extraction.d.ts +27 -0
  203. package/dist/lib/infrastructure/services/prompts/extraction.d.ts.map +1 -0
  204. package/dist/lib/infrastructure/services/prompts/extraction.js +94 -0
  205. package/dist/lib/infrastructure/services/prompts/extraction.js.map +1 -0
  206. package/dist/lib/infrastructure/services/prompts/summarization.d.ts +25 -0
  207. package/dist/lib/infrastructure/services/prompts/summarization.d.ts.map +1 -0
  208. package/dist/lib/infrastructure/services/prompts/summarization.js +60 -0
  209. package/dist/lib/infrastructure/services/prompts/summarization.js.map +1 -0
  210. package/dist/lib/skills/common/type-mappings.d.ts.map +1 -1
  211. package/dist/lib/skills/common/type-mappings.js +5 -0
  212. package/dist/lib/skills/common/type-mappings.js.map +1 -1
  213. package/dist/lib/skills/memory/memory.d.ts +8 -2
  214. package/dist/lib/skills/memory/memory.d.ts.map +1 -1
  215. package/dist/lib/skills/memory/memory.js +27 -3
  216. package/dist/lib/skills/memory/memory.js.map +1 -1
  217. package/dist/lib/skills/shared/clients/Neo4jClient.d.ts.map +1 -1
  218. package/dist/lib/skills/shared/clients/Neo4jClient.js +75 -30
  219. package/dist/lib/skills/shared/clients/Neo4jClient.js.map +1 -1
  220. package/dist/lib/skills/shared/clients/interfaces/INeo4jClient.d.ts +16 -0
  221. package/dist/lib/skills/shared/clients/interfaces/INeo4jClient.d.ts.map +1 -1
  222. package/dist/lib/skills/shared/services/MemoryCliService.d.ts +28 -2
  223. package/dist/lib/skills/shared/services/MemoryCliService.d.ts.map +1 -1
  224. package/dist/lib/skills/shared/services/MemoryCliService.js +112 -5
  225. package/dist/lib/skills/shared/services/MemoryCliService.js.map +1 -1
  226. package/dist/lib/skills/shared/services/MemoryService.d.ts.map +1 -1
  227. package/dist/lib/skills/shared/services/MemoryService.js +410 -1
  228. package/dist/lib/skills/shared/services/MemoryService.js.map +1 -1
  229. package/dist/lib/skills/shared/services/index.d.ts +1 -1
  230. package/dist/lib/skills/shared/services/index.d.ts.map +1 -1
  231. package/dist/lib/skills/shared/services/index.js +2 -1
  232. package/dist/lib/skills/shared/services/index.js.map +1 -1
  233. package/dist/lib/skills/shared/services/interfaces/IMemoryService.d.ts +148 -0
  234. package/dist/lib/skills/shared/services/interfaces/IMemoryService.d.ts.map +1 -1
  235. package/dist/lib/skills/shared/services/interfaces/IMemoryService.js +0 -4
  236. package/dist/lib/skills/shared/services/interfaces/IMemoryService.js.map +1 -1
  237. package/dist/opencode/lisa.js +3904 -507
  238. package/dist/package.json +16 -16
  239. package/dist/project/.lisa/skills/git/SKILL.md +4 -13
  240. package/dist/project/.lisa/skills/init-review/SKILL.md +1 -1
  241. package/dist/project/.lisa/skills/jira/SKILL.md +1 -1
  242. package/dist/project/.lisa/skills/lisa/SKILL.md +1 -1
  243. package/dist/project/.lisa/skills/memory/SKILL.md +1 -2
  244. package/dist/project/.lisa/skills/tasks/SKILL.md +3 -4
  245. 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 || undefined,
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
- * Searches:
120
- * - ~/.claude/projects/<project>/transcript.jsonl
121
- * - ~/.claude/transcript.jsonl
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 possibleDirs = [
129
- path_1.default.join(homeDir, '.claude', 'projects'),
130
- path_1.default.join(homeDir, '.claude'),
131
- ];
132
- for (const dir of possibleDirs) {
133
- if (!fs_1.default.existsSync(dir))
134
- continue;
135
- // Check direct transcript in this directory
136
- const directPath = path_1.default.join(dir, 'transcript.jsonl');
137
- if (fs_1.default.existsSync(directPath)) {
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 stats = fs_1.default.statSync(directPath);
140
- candidates.push({
141
- path: directPath,
142
- mtime: stats.mtimeMs,
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
- // Skip if can't stat
203
+ // Ignore permission errors
147
204
  }
148
205
  }
149
- // Check subdirectories (project folders)
150
- try {
151
- const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
152
- for (const entry of entries) {
153
- if (entry.isDirectory()) {
154
- const subPath = path_1.default.join(dir, entry.name, 'transcript.jsonl');
155
- if (fs_1.default.existsSync(subPath)) {
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(subPath);
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
- catch {
171
- // Ignore permission errors on directory listing
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
- const content = msg.message?.content;
199
- if (typeof content === 'string') {
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' || msg.type === 'tool_result') {
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: [...new Set(filesCreated)],
236
- filesModified: [...new Set(filesModified)],
237
- duration: 0, // TODO: Could calculate from timestamps
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
  */