mdcontext 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/.changeset/config.json +9 -9
  2. package/.claude/settings.local.json +25 -0
  3. package/.github/workflows/claude-code-review.yml +44 -0
  4. package/.github/workflows/claude.yml +85 -0
  5. package/CONTRIBUTING.md +186 -0
  6. package/NOTES/NOTES +44 -0
  7. package/README.md +206 -3
  8. package/biome.json +1 -1
  9. package/dist/chunk-23UPXDNL.js +3044 -0
  10. package/dist/chunk-2W7MO2DL.js +1366 -0
  11. package/dist/chunk-3NUAZGMA.js +1689 -0
  12. package/dist/chunk-7TOWB2XB.js +366 -0
  13. package/dist/chunk-7XOTOADQ.js +3065 -0
  14. package/dist/chunk-AH2PDM2K.js +3042 -0
  15. package/dist/chunk-BNXWSZ63.js +3742 -0
  16. package/dist/chunk-BTL5DJVU.js +3222 -0
  17. package/dist/chunk-HDHYG7E4.js +104 -0
  18. package/dist/chunk-HLR4KZBP.js +3234 -0
  19. package/dist/chunk-IP3FRFEB.js +1045 -0
  20. package/dist/chunk-KHU56VDO.js +3042 -0
  21. package/dist/chunk-KRYIFLQR.js +85 -89
  22. package/dist/chunk-LBSDNLEM.js +287 -0
  23. package/dist/chunk-MNTQ7HCP.js +2643 -0
  24. package/dist/chunk-MUJELQQ6.js +1387 -0
  25. package/dist/chunk-MXJGMSLV.js +2199 -0
  26. package/dist/chunk-N6QJGC3Z.js +2636 -0
  27. package/dist/chunk-OBELGBPM.js +1713 -0
  28. package/dist/chunk-OT7R5XTA.js +3192 -0
  29. package/dist/chunk-P7X4RA2T.js +106 -0
  30. package/dist/chunk-PIDUQNC2.js +3185 -0
  31. package/dist/chunk-POGCDIH4.js +3187 -0
  32. package/dist/chunk-PSIEOQGZ.js +3043 -0
  33. package/dist/chunk-PVRT3IHA.js +3238 -0
  34. package/dist/chunk-QNN4TT23.js +1430 -0
  35. package/dist/chunk-RE3R45RJ.js +3042 -0
  36. package/dist/chunk-S7E6TFX6.js +718 -657
  37. package/dist/chunk-SG6GLU4U.js +1378 -0
  38. package/dist/chunk-SJCDV2ST.js +274 -0
  39. package/dist/chunk-SYE5XLF3.js +104 -0
  40. package/dist/chunk-T5VLYBZD.js +103 -0
  41. package/dist/chunk-TOQB7VWU.js +3238 -0
  42. package/dist/chunk-VFNMZ4ZQ.js +3228 -0
  43. package/dist/chunk-VVTGZNBT.js +1533 -1423
  44. package/dist/chunk-W7Q4RFEV.js +104 -0
  45. package/dist/chunk-XTYYVRLO.js +3190 -0
  46. package/dist/chunk-Y6MDYVJD.js +3063 -0
  47. package/dist/cli/main.js +4072 -629
  48. package/dist/index.d.ts +420 -33
  49. package/dist/index.js +8 -15
  50. package/dist/mcp/server.js +103 -7
  51. package/dist/schema-BAWSG7KY.js +22 -0
  52. package/dist/schema-E3QUPL26.js +20 -0
  53. package/dist/schema-EHL7WUT6.js +20 -0
  54. package/docs/019-USAGE.md +44 -5
  55. package/docs/020-current-implementation.md +8 -8
  56. package/docs/021-DOGFOODING-FINDINGS.md +1 -1
  57. package/docs/CONFIG.md +1123 -0
  58. package/docs/ERRORS.md +383 -0
  59. package/docs/summarization.md +320 -0
  60. package/justfile +40 -0
  61. package/package.json +39 -33
  62. package/research/INDEX.md +315 -0
  63. package/research/code-review/README.md +90 -0
  64. package/research/code-review/cli-error-handling-review.md +979 -0
  65. package/research/code-review/code-review-validation-report.md +464 -0
  66. package/research/code-review/main-ts-review.md +1128 -0
  67. package/research/config-docs/SUMMARY.md +357 -0
  68. package/research/config-docs/TEST-RESULTS.md +776 -0
  69. package/research/config-docs/TODO.md +542 -0
  70. package/research/config-docs/analysis.md +744 -0
  71. package/research/config-docs/fix-validation.md +502 -0
  72. package/research/config-docs/help-audit.md +264 -0
  73. package/research/config-docs/help-system-analysis.md +890 -0
  74. package/research/frontmatter/COMMENTS-ARE-SKIPPED.md +149 -0
  75. package/research/frontmatter/LLM-CODE-NAVIGATION.md +276 -0
  76. package/research/issue-review.md +603 -0
  77. package/research/llm-summarization/agent-cli-tools-2026.md +1082 -0
  78. package/research/llm-summarization/alternative-providers-2026.md +1428 -0
  79. package/research/llm-summarization/anthropic-2026.md +367 -0
  80. package/research/llm-summarization/claude-cli-integration.md +1706 -0
  81. package/research/llm-summarization/cli-integration-patterns.md +3155 -0
  82. package/research/llm-summarization/openai-2026.md +473 -0
  83. package/research/llm-summarization/openai-compatible-providers-2026.md +1022 -0
  84. package/research/llm-summarization/opencode-cli-integration.md +1552 -0
  85. package/research/llm-summarization/prompt-engineering-2026.md +1426 -0
  86. package/research/llm-summarization/prototype-results.md +56 -0
  87. package/research/llm-summarization/provider-switching-patterns-2026.md +2153 -0
  88. package/research/llm-summarization/typescript-llm-libraries-2026.md +2436 -0
  89. package/research/mdcontext-pudding/00-EXECUTIVE-SUMMARY.md +282 -0
  90. package/research/mdcontext-pudding/01-index-embed.md +956 -0
  91. package/research/mdcontext-pudding/02-search-COMMANDS.md +142 -0
  92. package/research/mdcontext-pudding/02-search-SUMMARY.md +146 -0
  93. package/research/mdcontext-pudding/02-search.md +970 -0
  94. package/research/mdcontext-pudding/03-context.md +779 -0
  95. package/research/mdcontext-pudding/04-navigation-and-analytics.md +803 -0
  96. package/research/mdcontext-pudding/04-tree.md +704 -0
  97. package/research/mdcontext-pudding/05-config.md +1038 -0
  98. package/research/mdcontext-pudding/06-links-summary.txt +87 -0
  99. package/research/mdcontext-pudding/06-links.md +679 -0
  100. package/research/mdcontext-pudding/07-stats.md +693 -0
  101. package/research/mdcontext-pudding/BUG-FIX-PLAN.md +388 -0
  102. package/research/mdcontext-pudding/P0-BUG-VALIDATION.md +167 -0
  103. package/research/mdcontext-pudding/README.md +168 -0
  104. package/research/mdcontext-pudding/TESTING-SUMMARY.md +128 -0
  105. package/research/research-quality-review.md +834 -0
  106. package/research/semantic-search/embedding-text-analysis.md +156 -0
  107. package/research/semantic-search/multi-word-failure-reproduction.md +171 -0
  108. package/research/semantic-search/query-processing-analysis.md +207 -0
  109. package/research/semantic-search/root-cause-and-solution.md +114 -0
  110. package/research/semantic-search/threshold-validation-report.md +69 -0
  111. package/research/semantic-search/vector-search-analysis.md +63 -0
  112. package/research/test-path-issues.md +276 -0
  113. package/review/ALP-76/1-error-type-design.md +962 -0
  114. package/review/ALP-76/2-error-handling-patterns.md +906 -0
  115. package/review/ALP-76/3-error-presentation.md +624 -0
  116. package/review/ALP-76/4-test-coverage.md +625 -0
  117. package/review/ALP-76/5-migration-completeness.md +440 -0
  118. package/review/ALP-76/6-effect-best-practices.md +755 -0
  119. package/scripts/apply-branch-protection.sh +47 -0
  120. package/scripts/branch-protection-templates.json +79 -0
  121. package/scripts/prototype-summarization.ts +346 -0
  122. package/scripts/rebuild-hnswlib.js +32 -37
  123. package/scripts/setup-branch-protection.sh +64 -0
  124. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/active-provider.json +7 -0
  125. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.json +541 -0
  126. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.meta.json +5 -0
  127. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/config.json +8 -0
  128. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  129. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  130. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/documents.json +60 -0
  131. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/links.json +13 -0
  132. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/sections.json +1197 -0
  133. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/configuration-management.md +99 -0
  134. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/distributed-systems.md +92 -0
  135. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/error-handling.md +78 -0
  136. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/failure-automation.md +55 -0
  137. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/job-context.md +69 -0
  138. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/process-orchestration.md +99 -0
  139. package/src/cli/argv-preprocessor.test.ts +2 -2
  140. package/src/cli/cli.test.ts +230 -33
  141. package/src/cli/commands/config-cmd.ts +642 -0
  142. package/src/cli/commands/context.ts +97 -9
  143. package/src/cli/commands/duplicates.ts +122 -0
  144. package/src/cli/commands/embeddings.ts +529 -0
  145. package/src/cli/commands/index-cmd.ts +210 -30
  146. package/src/cli/commands/index.ts +3 -0
  147. package/src/cli/commands/search.ts +894 -64
  148. package/src/cli/commands/stats.ts +3 -0
  149. package/src/cli/commands/tree.ts +26 -5
  150. package/src/cli/config-layer.ts +176 -0
  151. package/src/cli/error-handler.test.ts +235 -0
  152. package/src/cli/error-handler.ts +655 -0
  153. package/src/cli/flag-schemas.ts +66 -0
  154. package/src/cli/help.ts +209 -7
  155. package/src/cli/main.ts +348 -58
  156. package/src/cli/options.ts +10 -0
  157. package/src/cli/shared-error-handling.ts +199 -0
  158. package/src/cli/utils.ts +150 -17
  159. package/src/config/file-provider.test.ts +320 -0
  160. package/src/config/file-provider.ts +273 -0
  161. package/src/config/index.ts +72 -0
  162. package/src/config/integration.test.ts +667 -0
  163. package/src/config/precedence.test.ts +277 -0
  164. package/src/config/precedence.ts +451 -0
  165. package/src/config/schema.test.ts +414 -0
  166. package/src/config/schema.ts +603 -0
  167. package/src/config/service.test.ts +320 -0
  168. package/src/config/service.ts +243 -0
  169. package/src/config/testing.test.ts +264 -0
  170. package/src/config/testing.ts +110 -0
  171. package/src/core/types.ts +6 -33
  172. package/src/duplicates/detector.test.ts +183 -0
  173. package/src/duplicates/detector.ts +414 -0
  174. package/src/duplicates/index.ts +18 -0
  175. package/src/embeddings/embedding-namespace.test.ts +300 -0
  176. package/src/embeddings/embedding-namespace.ts +947 -0
  177. package/src/embeddings/heading-boost.test.ts +222 -0
  178. package/src/embeddings/hnsw-build-options.test.ts +198 -0
  179. package/src/embeddings/hyde.test.ts +272 -0
  180. package/src/embeddings/hyde.ts +264 -0
  181. package/src/embeddings/index.ts +2 -0
  182. package/src/embeddings/openai-provider.ts +332 -83
  183. package/src/embeddings/pricing.json +22 -0
  184. package/src/embeddings/provider-constants.ts +204 -0
  185. package/src/embeddings/provider-errors.test.ts +967 -0
  186. package/src/embeddings/provider-errors.ts +565 -0
  187. package/src/embeddings/provider-factory.test.ts +240 -0
  188. package/src/embeddings/provider-factory.ts +225 -0
  189. package/src/embeddings/provider-integration.test.ts +788 -0
  190. package/src/embeddings/query-preprocessing.test.ts +187 -0
  191. package/src/embeddings/semantic-search-threshold.test.ts +508 -0
  192. package/src/embeddings/semantic-search.ts +780 -93
  193. package/src/embeddings/types.ts +293 -16
  194. package/src/embeddings/vector-store.ts +486 -77
  195. package/src/embeddings/voyage-provider.ts +313 -0
  196. package/src/errors/errors.test.ts +845 -0
  197. package/src/errors/index.ts +533 -0
  198. package/src/index/ignore-patterns.test.ts +354 -0
  199. package/src/index/ignore-patterns.ts +305 -0
  200. package/src/index/indexer.ts +286 -48
  201. package/src/index/storage.ts +94 -30
  202. package/src/index/types.ts +40 -2
  203. package/src/index/watcher.ts +67 -9
  204. package/src/index.ts +22 -0
  205. package/src/integration/search-keyword.test.ts +678 -0
  206. package/src/mcp/server.ts +135 -6
  207. package/src/parser/parser.ts +18 -19
  208. package/src/parser/section-filter.test.ts +277 -0
  209. package/src/parser/section-filter.ts +125 -3
  210. package/src/search/__tests__/hybrid-search.test.ts +650 -0
  211. package/src/search/bm25-store.ts +366 -0
  212. package/src/search/cross-encoder.test.ts +253 -0
  213. package/src/search/cross-encoder.ts +406 -0
  214. package/src/search/fuzzy-search.test.ts +419 -0
  215. package/src/search/fuzzy-search.ts +273 -0
  216. package/src/search/hybrid-search.ts +448 -0
  217. package/src/search/path-matcher.test.ts +276 -0
  218. package/src/search/path-matcher.ts +33 -0
  219. package/src/search/searcher.test.ts +99 -1
  220. package/src/search/searcher.ts +189 -67
  221. package/src/search/wink-bm25.d.ts +30 -0
  222. package/src/summarization/cli-providers/claude.ts +202 -0
  223. package/src/summarization/cli-providers/detection.test.ts +273 -0
  224. package/src/summarization/cli-providers/detection.ts +118 -0
  225. package/src/summarization/cli-providers/index.ts +8 -0
  226. package/src/summarization/cost.test.ts +139 -0
  227. package/src/summarization/cost.ts +102 -0
  228. package/src/summarization/error-handler.test.ts +127 -0
  229. package/src/summarization/error-handler.ts +111 -0
  230. package/src/summarization/index.ts +102 -0
  231. package/src/summarization/pipeline.test.ts +498 -0
  232. package/src/summarization/pipeline.ts +231 -0
  233. package/src/summarization/prompts.test.ts +269 -0
  234. package/src/summarization/prompts.ts +133 -0
  235. package/src/summarization/provider-factory.test.ts +396 -0
  236. package/src/summarization/provider-factory.ts +178 -0
  237. package/src/summarization/types.ts +184 -0
  238. package/src/summarize/summarizer.ts +104 -35
  239. package/src/types/huggingface-transformers.d.ts +66 -0
  240. package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
  241. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  242. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  243. package/tests/fixtures/cli/.mdcontext/indexes/documents.json +4 -4
  244. package/tests/fixtures/cli/.mdcontext/indexes/sections.json +14 -0
  245. package/tests/integration/embed-index.test.ts +712 -0
  246. package/tests/integration/search-context.test.ts +469 -0
  247. package/tests/integration/search-semantic.test.ts +522 -0
  248. package/vitest.config.ts +1 -6
  249. package/AGENTS.md +0 -46
  250. package/tests/fixtures/cli/.mdcontext/vectors.bin +0 -0
  251. package/tests/fixtures/cli/.mdcontext/vectors.meta.json +0 -1264
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Summary Generation Pipeline
3
+ *
4
+ * Orchestrates the full summarization flow:
5
+ * 1. Format search results for LLM input
6
+ * 2. Estimate cost (for API providers)
7
+ * 3. Get user consent (for paid operations)
8
+ * 4. Generate summary via provider
9
+ * 5. Return formatted output
10
+ */
11
+
12
+ import { Effect } from 'effect'
13
+ import type { CostEstimate } from './cost.js'
14
+ import { estimateSummaryCost } from './cost.js'
15
+ import {
16
+ buildPrompt,
17
+ type PromptTemplate,
18
+ type SearchContext,
19
+ } from './prompts.js'
20
+ import {
21
+ createSummarizer,
22
+ getBestAvailableSummarizer,
23
+ } from './provider-factory.js'
24
+ import type { AISummarizationConfig, SummarizationMode } from './types.js'
25
+ import { SummarizationError } from './types.js'
26
+
27
+ /**
28
+ * Search result that can be summarized.
29
+ */
30
+ export interface SummarizableResult {
31
+ readonly documentPath: string
32
+ readonly heading: string
33
+ readonly content?: string
34
+ readonly score?: number
35
+ readonly similarity?: number
36
+ }
37
+
38
+ /**
39
+ * Options for the summarization pipeline.
40
+ */
41
+ export interface PipelineOptions {
42
+ /** AI summarization configuration */
43
+ readonly config?: Partial<AISummarizationConfig>
44
+ /** Prompt template to use */
45
+ readonly template?: PromptTemplate
46
+ /** Enable streaming output */
47
+ readonly stream?: boolean
48
+ /** Callback for streaming chunks */
49
+ readonly onChunk?: (chunk: string) => void
50
+ /** Skip user consent for paid operations */
51
+ readonly skipConsent?: boolean
52
+ /** Callback for consent prompt (returns true to proceed) */
53
+ readonly onConsentPrompt?: (estimate: CostEstimate) => Promise<boolean>
54
+ }
55
+
56
+ /**
57
+ * Result from the summarization pipeline.
58
+ */
59
+ export interface PipelineResult {
60
+ /** Generated summary text */
61
+ readonly summary: string
62
+ /** Provider that generated the summary */
63
+ readonly provider: string
64
+ /** Mode used (cli or api) */
65
+ readonly mode: SummarizationMode
66
+ /** Cost estimate (before execution) */
67
+ readonly estimatedCost: CostEstimate
68
+ /** Actual cost (if available) */
69
+ readonly actualCost?: number
70
+ /** Time taken in milliseconds */
71
+ readonly durationMs: number
72
+ /** Was this a free operation? */
73
+ readonly isFree: boolean
74
+ }
75
+
76
+ /**
77
+ * Format search results as text for the LLM.
78
+ */
79
+ export const formatResultsForSummary = (
80
+ results: readonly SummarizableResult[],
81
+ ): string => {
82
+ return results
83
+ .map((r, i) => {
84
+ const lines: string[] = []
85
+ lines.push(`[${i + 1}] ${r.documentPath}`)
86
+ lines.push(` Heading: ${r.heading}`)
87
+ if (r.score !== undefined) {
88
+ lines.push(` Score: ${(r.score * 100).toFixed(1)}%`)
89
+ }
90
+ if (r.similarity !== undefined) {
91
+ lines.push(` Similarity: ${(r.similarity * 100).toFixed(1)}%`)
92
+ }
93
+ if (r.content) {
94
+ // Truncate content to avoid huge inputs
95
+ const truncated =
96
+ r.content.length > 500 ? `${r.content.slice(0, 500)}...` : r.content
97
+ lines.push(` Content: ${truncated}`)
98
+ }
99
+ return lines.join('\n')
100
+ })
101
+ .join('\n\n')
102
+ }
103
+
104
+ /**
105
+ * Run the summarization pipeline.
106
+ *
107
+ * This is the main entry point for generating summaries from search results.
108
+ */
109
+ export const runSummarizationPipeline = (
110
+ results: readonly SummarizableResult[],
111
+ searchContext: SearchContext,
112
+ options: PipelineOptions = {},
113
+ ): Effect.Effect<PipelineResult, SummarizationError> =>
114
+ Effect.gen(function* () {
115
+ // Get or create summarizer
116
+ const summarizerResult = options.config
117
+ ? yield* Effect.tryPromise({
118
+ try: () => {
119
+ // Build config object conditionally to avoid undefined values
120
+ const config: AISummarizationConfig = {
121
+ mode: options.config?.mode ?? 'cli',
122
+ provider: options.config?.provider ?? 'claude',
123
+ ...(options.config?.model && { model: options.config.model }),
124
+ ...((options.stream ?? options.config?.stream) && {
125
+ stream: true,
126
+ }),
127
+ ...(options.config?.baseURL && {
128
+ baseURL: options.config.baseURL,
129
+ }),
130
+ ...(options.config?.apiKey && { apiKey: options.config.apiKey }),
131
+ }
132
+ return createSummarizer(config)
133
+ },
134
+ catch: (e) =>
135
+ e instanceof SummarizationError
136
+ ? e
137
+ : new SummarizationError(
138
+ `Failed to create summarizer: ${e}`,
139
+ 'PROVIDER_NOT_FOUND',
140
+ ),
141
+ })
142
+ : yield* Effect.tryPromise({
143
+ try: async () => {
144
+ const result = await getBestAvailableSummarizer()
145
+ if (!result) {
146
+ throw new SummarizationError(
147
+ 'No summarization providers available',
148
+ 'PROVIDER_NOT_AVAILABLE',
149
+ )
150
+ }
151
+ return result.summarizer
152
+ },
153
+ catch: (e) =>
154
+ e instanceof SummarizationError
155
+ ? e
156
+ : new SummarizationError(
157
+ `Failed to find summarizer: ${e}`,
158
+ 'PROVIDER_NOT_FOUND',
159
+ ),
160
+ })
161
+
162
+ const mode = options.config?.mode ?? 'cli'
163
+ const provider = options.config?.provider ?? 'claude'
164
+
165
+ // Format results for input
166
+ const resultsText = formatResultsForSummary(results)
167
+
168
+ // Estimate cost
169
+ const costEstimate = estimateSummaryCost(resultsText, mode, provider)
170
+
171
+ // Handle consent for paid operations
172
+ if (costEstimate.isPaid && !options.skipConsent) {
173
+ if (options.onConsentPrompt) {
174
+ const consented = yield* Effect.tryPromise({
175
+ try: () => options.onConsentPrompt!(costEstimate),
176
+ catch: () =>
177
+ new SummarizationError(
178
+ 'Consent prompt failed',
179
+ 'CLI_EXECUTION_FAILED',
180
+ ),
181
+ })
182
+
183
+ if (!consented) {
184
+ return yield* Effect.fail(
185
+ new SummarizationError(
186
+ 'User declined summarization',
187
+ 'CLI_EXECUTION_FAILED',
188
+ ),
189
+ )
190
+ }
191
+ }
192
+ }
193
+
194
+ // Build prompt
195
+ const prompt = buildPrompt(searchContext, options.template)
196
+
197
+ // Generate summary
198
+ const summaryResult = yield* Effect.tryPromise({
199
+ try: () => summarizerResult.summarize(resultsText, prompt),
200
+ catch: (e) =>
201
+ e instanceof SummarizationError
202
+ ? e
203
+ : new SummarizationError(
204
+ `Summarization failed: ${e}`,
205
+ 'CLI_EXECUTION_FAILED',
206
+ ),
207
+ })
208
+
209
+ return {
210
+ summary: summaryResult.summary,
211
+ provider: summaryResult.provider,
212
+ mode: summaryResult.mode,
213
+ estimatedCost: costEstimate,
214
+ actualCost: summaryResult.estimatedCost,
215
+ durationMs: summaryResult.durationMs,
216
+ isFree: !costEstimate.isPaid,
217
+ }
218
+ })
219
+
220
+ /**
221
+ * Quick helper to run summarization with Effect.runPromise.
222
+ */
223
+ export const summarizeResults = async (
224
+ results: readonly SummarizableResult[],
225
+ searchContext: SearchContext,
226
+ options: PipelineOptions = {},
227
+ ): Promise<PipelineResult> => {
228
+ return Effect.runPromise(
229
+ runSummarizationPipeline(results, searchContext, options),
230
+ )
231
+ }
@@ -0,0 +1,269 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ ACTIONABLE_PROMPT,
4
+ buildPrompt,
5
+ CONCISE_PROMPT,
6
+ DEFAULT_PROMPT,
7
+ DETAILED_PROMPT,
8
+ getPromptTemplate,
9
+ type PromptTemplate,
10
+ type SearchContext,
11
+ TECHNICAL_PROMPT,
12
+ } from './prompts.js'
13
+
14
+ describe('prompts', () => {
15
+ describe('getPromptTemplate', () => {
16
+ it('returns DEFAULT_PROMPT for "default" template', () => {
17
+ expect(getPromptTemplate('default')).toBe(DEFAULT_PROMPT)
18
+ })
19
+
20
+ it('returns CONCISE_PROMPT for "concise" template', () => {
21
+ expect(getPromptTemplate('concise')).toBe(CONCISE_PROMPT)
22
+ })
23
+
24
+ it('returns DETAILED_PROMPT for "detailed" template', () => {
25
+ expect(getPromptTemplate('detailed')).toBe(DETAILED_PROMPT)
26
+ })
27
+
28
+ it('returns ACTIONABLE_PROMPT for "actionable" template', () => {
29
+ expect(getPromptTemplate('actionable')).toBe(ACTIONABLE_PROMPT)
30
+ })
31
+
32
+ it('returns TECHNICAL_PROMPT for "technical" template', () => {
33
+ expect(getPromptTemplate('technical')).toBe(TECHNICAL_PROMPT)
34
+ })
35
+
36
+ it('returns DEFAULT_PROMPT as fallback for unknown template', () => {
37
+ const unknownTemplate = 'unknown' as PromptTemplate
38
+ expect(getPromptTemplate(unknownTemplate)).toBe(DEFAULT_PROMPT)
39
+ })
40
+ })
41
+
42
+ describe('buildPrompt', () => {
43
+ const baseContext: SearchContext = {
44
+ query: 'test query',
45
+ resultCount: 5,
46
+ searchMode: 'hybrid',
47
+ }
48
+
49
+ it('includes the base prompt template in output', () => {
50
+ const result = buildPrompt(baseContext)
51
+ expect(result).toContain(DEFAULT_PROMPT)
52
+ })
53
+
54
+ it('includes the query in output', () => {
55
+ const result = buildPrompt(baseContext)
56
+ expect(result).toContain('Query: "test query"')
57
+ })
58
+
59
+ it('includes result count in output', () => {
60
+ const result = buildPrompt(baseContext)
61
+ expect(result).toContain('Results found: 5')
62
+ })
63
+
64
+ it('includes search mode in output', () => {
65
+ const result = buildPrompt(baseContext)
66
+ expect(result).toContain('Search mode: hybrid')
67
+ })
68
+
69
+ it('uses default template when not specified', () => {
70
+ const result = buildPrompt(baseContext)
71
+ expect(result).toContain(DEFAULT_PROMPT)
72
+ })
73
+
74
+ it('uses specified template when provided', () => {
75
+ const result = buildPrompt(baseContext, 'concise')
76
+ expect(result).toContain(CONCISE_PROMPT)
77
+ expect(result).not.toContain(DEFAULT_PROMPT)
78
+ })
79
+
80
+ it('has proper formatting with separators', () => {
81
+ const result = buildPrompt(baseContext)
82
+ expect(result).toContain('---')
83
+ expect(result).toContain('Search Results:')
84
+ })
85
+
86
+ it('handles semantic search mode', () => {
87
+ const context: SearchContext = {
88
+ ...baseContext,
89
+ searchMode: 'semantic',
90
+ }
91
+ const result = buildPrompt(context)
92
+ expect(result).toContain('Search mode: semantic')
93
+ })
94
+
95
+ it('handles keyword search mode', () => {
96
+ const context: SearchContext = {
97
+ ...baseContext,
98
+ searchMode: 'keyword',
99
+ }
100
+ const result = buildPrompt(context)
101
+ expect(result).toContain('Search mode: keyword')
102
+ })
103
+
104
+ it('handles zero result count', () => {
105
+ const context: SearchContext = {
106
+ ...baseContext,
107
+ resultCount: 0,
108
+ }
109
+ const result = buildPrompt(context)
110
+ expect(result).toContain('Results found: 0')
111
+ })
112
+
113
+ it('handles large result count', () => {
114
+ const context: SearchContext = {
115
+ ...baseContext,
116
+ resultCount: 1000,
117
+ }
118
+ const result = buildPrompt(context)
119
+ expect(result).toContain('Results found: 1000')
120
+ })
121
+
122
+ it('handles special characters in query', () => {
123
+ const context: SearchContext = {
124
+ ...baseContext,
125
+ query: 'how do I use "quotes" and <brackets>?',
126
+ }
127
+ const result = buildPrompt(context)
128
+ expect(result).toContain('Query: "how do I use "quotes" and <brackets>?"')
129
+ })
130
+
131
+ it('works with all template types', () => {
132
+ const templates: PromptTemplate[] = [
133
+ 'default',
134
+ 'concise',
135
+ 'detailed',
136
+ 'actionable',
137
+ 'technical',
138
+ ]
139
+ for (const template of templates) {
140
+ const result = buildPrompt(baseContext, template)
141
+ expect(result).toContain('Query: "test query"')
142
+ expect(result).toContain('Results found: 5')
143
+ expect(result).toContain('Search mode: hybrid')
144
+ }
145
+ })
146
+ })
147
+
148
+ describe('prompt content verification', () => {
149
+ describe('DEFAULT_PROMPT', () => {
150
+ it('contains guidelines section', () => {
151
+ expect(DEFAULT_PROMPT).toContain('Guidelines:')
152
+ })
153
+
154
+ it('mentions synthesizing results', () => {
155
+ expect(DEFAULT_PROMPT).toContain('Synthesize')
156
+ })
157
+
158
+ it('includes formatting instructions', () => {
159
+ expect(DEFAULT_PROMPT).toContain('Format your response')
160
+ })
161
+
162
+ it('mentions actionable insights', () => {
163
+ expect(DEFAULT_PROMPT).toContain('actionable insights')
164
+ })
165
+ })
166
+
167
+ describe('CONCISE_PROMPT', () => {
168
+ it('is relatively short', () => {
169
+ expect(CONCISE_PROMPT.length).toBeLessThan(200)
170
+ })
171
+
172
+ it('mentions 2-3 sentences', () => {
173
+ expect(CONCISE_PROMPT).toContain('2-3 sentences')
174
+ })
175
+
176
+ it('focuses on most important findings', () => {
177
+ expect(CONCISE_PROMPT).toContain('most important')
178
+ })
179
+ })
180
+
181
+ describe('DETAILED_PROMPT', () => {
182
+ it('is comprehensive (longer than default)', () => {
183
+ expect(DETAILED_PROMPT.length).toBeGreaterThan(CONCISE_PROMPT.length)
184
+ })
185
+
186
+ it('mentions executive summary', () => {
187
+ expect(DETAILED_PROMPT).toContain('Executive summary')
188
+ })
189
+
190
+ it('mentions code patterns', () => {
191
+ expect(DETAILED_PROMPT).toContain('code patterns')
192
+ })
193
+
194
+ it('mentions file references', () => {
195
+ expect(DETAILED_PROMPT).toContain('file references')
196
+ })
197
+
198
+ it('includes multiple numbered sections', () => {
199
+ expect(DETAILED_PROMPT).toContain('1.')
200
+ expect(DETAILED_PROMPT).toContain('2.')
201
+ expect(DETAILED_PROMPT).toContain('3.')
202
+ })
203
+ })
204
+
205
+ describe('ACTIONABLE_PROMPT', () => {
206
+ it('focuses on developer goals', () => {
207
+ expect(ACTIONABLE_PROMPT).toContain(
208
+ 'developer is likely trying to accomplish',
209
+ )
210
+ })
211
+
212
+ it('mentions specific steps', () => {
213
+ expect(ACTIONABLE_PROMPT).toContain('specific steps')
214
+ })
215
+
216
+ it('mentions code snippets', () => {
217
+ expect(ACTIONABLE_PROMPT).toContain('code snippets')
218
+ })
219
+
220
+ it('mentions pitfalls', () => {
221
+ expect(ACTIONABLE_PROMPT).toContain('pitfalls')
222
+ })
223
+
224
+ it('emphasizes being direct and practical', () => {
225
+ expect(ACTIONABLE_PROMPT).toContain('direct and practical')
226
+ })
227
+ })
228
+
229
+ describe('TECHNICAL_PROMPT', () => {
230
+ it('focuses on technical perspective', () => {
231
+ expect(TECHNICAL_PROMPT).toContain('technical perspective')
232
+ })
233
+
234
+ it('mentions code patterns', () => {
235
+ expect(TECHNICAL_PROMPT).toContain('Code patterns')
236
+ })
237
+
238
+ it('mentions API signatures', () => {
239
+ expect(TECHNICAL_PROMPT).toContain('API signatures')
240
+ })
241
+
242
+ it('mentions configuration options', () => {
243
+ expect(TECHNICAL_PROMPT).toContain('Configuration options')
244
+ })
245
+
246
+ it('mentions best practices', () => {
247
+ expect(TECHNICAL_PROMPT).toContain('Best practices')
248
+ })
249
+
250
+ it('emphasizes concrete technical guidance', () => {
251
+ expect(TECHNICAL_PROMPT).toContain('concrete technical guidance')
252
+ })
253
+ })
254
+ })
255
+
256
+ describe('prompt uniqueness', () => {
257
+ it('all prompts are distinct', () => {
258
+ const prompts = [
259
+ DEFAULT_PROMPT,
260
+ CONCISE_PROMPT,
261
+ DETAILED_PROMPT,
262
+ ACTIONABLE_PROMPT,
263
+ TECHNICAL_PROMPT,
264
+ ]
265
+ const uniquePrompts = new Set(prompts)
266
+ expect(uniquePrompts.size).toBe(prompts.length)
267
+ })
268
+ })
269
+ })
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Prompt Templates for Search Result Summarization
3
+ *
4
+ * These prompts are designed to transform raw search results
5
+ * into actionable insights for developers.
6
+ */
7
+
8
+ /**
9
+ * Available prompt templates for different summarization styles.
10
+ */
11
+ export type PromptTemplate =
12
+ | 'default'
13
+ | 'concise'
14
+ | 'detailed'
15
+ | 'actionable'
16
+ | 'technical'
17
+
18
+ /**
19
+ * Context about the search that generated these results.
20
+ */
21
+ export interface SearchContext {
22
+ /** The original search query */
23
+ readonly query: string
24
+ /** Number of results found */
25
+ readonly resultCount: number
26
+ /** Search mode used */
27
+ readonly searchMode: 'hybrid' | 'semantic' | 'keyword'
28
+ }
29
+
30
+ /**
31
+ * Default prompt for summarizing search results.
32
+ *
33
+ * Designed to produce actionable, developer-focused insights.
34
+ */
35
+ export const DEFAULT_PROMPT = `You are summarizing search results from a markdown documentation search tool.
36
+
37
+ Your task: Synthesize these results into actionable insights that help the developer understand the relevant content.
38
+
39
+ Guidelines:
40
+ - Focus on answering the user's implicit question
41
+ - Highlight the most relevant sections and their key points
42
+ - Note any patterns or connections between results
43
+ - Be concise but comprehensive
44
+ - Use bullet points for clarity
45
+ - If results seem incomplete, suggest what else the user might search for
46
+
47
+ Format your response as:
48
+ 1. A brief summary (2-3 sentences)
49
+ 2. Key findings (bullet points)
50
+ 3. Recommended next steps (if applicable)`
51
+
52
+ /**
53
+ * Concise prompt for quick summaries.
54
+ */
55
+ export const CONCISE_PROMPT = `Summarize these search results in 2-3 sentences. Focus only on the most important findings that directly answer the query.`
56
+
57
+ /**
58
+ * Detailed prompt for comprehensive analysis.
59
+ */
60
+ export const DETAILED_PROMPT = `Provide a comprehensive analysis of these search results.
61
+
62
+ Include:
63
+ 1. Executive summary (3-4 sentences)
64
+ 2. Detailed findings organized by topic
65
+ 3. Key code patterns or examples mentioned
66
+ 4. Relationships between different sections
67
+ 5. Gaps or areas that need more exploration
68
+ 6. Specific file references for follow-up`
69
+
70
+ /**
71
+ * Actionable prompt focused on next steps.
72
+ */
73
+ export const ACTIONABLE_PROMPT = `Analyze these search results and provide:
74
+
75
+ 1. What the developer is likely trying to accomplish
76
+ 2. The specific steps they should take based on these results
77
+ 3. Any code snippets or patterns they should use
78
+ 4. Potential pitfalls to avoid
79
+ 5. Related areas they might want to explore
80
+
81
+ Be direct and practical.`
82
+
83
+ /**
84
+ * Technical prompt for code-focused analysis.
85
+ */
86
+ export const TECHNICAL_PROMPT = `Analyze these search results from a technical perspective.
87
+
88
+ Focus on:
89
+ - Code patterns and implementations mentioned
90
+ - API signatures and usage
91
+ - Configuration options
92
+ - Dependencies and requirements
93
+ - Best practices and anti-patterns
94
+
95
+ Provide concrete technical guidance.`
96
+
97
+ /**
98
+ * Get a prompt template by name.
99
+ */
100
+ export const getPromptTemplate = (template: PromptTemplate): string => {
101
+ switch (template) {
102
+ case 'concise':
103
+ return CONCISE_PROMPT
104
+ case 'detailed':
105
+ return DETAILED_PROMPT
106
+ case 'actionable':
107
+ return ACTIONABLE_PROMPT
108
+ case 'technical':
109
+ return TECHNICAL_PROMPT
110
+ default:
111
+ return DEFAULT_PROMPT
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Build a complete prompt with search context.
117
+ */
118
+ export const buildPrompt = (
119
+ context: SearchContext,
120
+ template: PromptTemplate = 'default',
121
+ ): string => {
122
+ const basePrompt = getPromptTemplate(template)
123
+
124
+ return `${basePrompt}
125
+
126
+ ---
127
+ Query: "${context.query}"
128
+ Results found: ${context.resultCount}
129
+ Search mode: ${context.searchMode}
130
+ ---
131
+
132
+ Search Results:`
133
+ }