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.
- package/.changeset/config.json +9 -9
- package/.claude/settings.local.json +25 -0
- package/.github/workflows/claude-code-review.yml +44 -0
- package/.github/workflows/claude.yml +85 -0
- package/CONTRIBUTING.md +186 -0
- package/NOTES/NOTES +44 -0
- package/README.md +206 -3
- package/biome.json +1 -1
- package/dist/chunk-23UPXDNL.js +3044 -0
- package/dist/chunk-2W7MO2DL.js +1366 -0
- package/dist/chunk-3NUAZGMA.js +1689 -0
- package/dist/chunk-7TOWB2XB.js +366 -0
- package/dist/chunk-7XOTOADQ.js +3065 -0
- package/dist/chunk-AH2PDM2K.js +3042 -0
- package/dist/chunk-BNXWSZ63.js +3742 -0
- package/dist/chunk-BTL5DJVU.js +3222 -0
- package/dist/chunk-HDHYG7E4.js +104 -0
- package/dist/chunk-HLR4KZBP.js +3234 -0
- package/dist/chunk-IP3FRFEB.js +1045 -0
- package/dist/chunk-KHU56VDO.js +3042 -0
- package/dist/chunk-KRYIFLQR.js +85 -89
- package/dist/chunk-LBSDNLEM.js +287 -0
- package/dist/chunk-MNTQ7HCP.js +2643 -0
- package/dist/chunk-MUJELQQ6.js +1387 -0
- package/dist/chunk-MXJGMSLV.js +2199 -0
- package/dist/chunk-N6QJGC3Z.js +2636 -0
- package/dist/chunk-OBELGBPM.js +1713 -0
- package/dist/chunk-OT7R5XTA.js +3192 -0
- package/dist/chunk-P7X4RA2T.js +106 -0
- package/dist/chunk-PIDUQNC2.js +3185 -0
- package/dist/chunk-POGCDIH4.js +3187 -0
- package/dist/chunk-PSIEOQGZ.js +3043 -0
- package/dist/chunk-PVRT3IHA.js +3238 -0
- package/dist/chunk-QNN4TT23.js +1430 -0
- package/dist/chunk-RE3R45RJ.js +3042 -0
- package/dist/chunk-S7E6TFX6.js +718 -657
- package/dist/chunk-SG6GLU4U.js +1378 -0
- package/dist/chunk-SJCDV2ST.js +274 -0
- package/dist/chunk-SYE5XLF3.js +104 -0
- package/dist/chunk-T5VLYBZD.js +103 -0
- package/dist/chunk-TOQB7VWU.js +3238 -0
- package/dist/chunk-VFNMZ4ZQ.js +3228 -0
- package/dist/chunk-VVTGZNBT.js +1533 -1423
- package/dist/chunk-W7Q4RFEV.js +104 -0
- package/dist/chunk-XTYYVRLO.js +3190 -0
- package/dist/chunk-Y6MDYVJD.js +3063 -0
- package/dist/cli/main.js +4072 -629
- package/dist/index.d.ts +420 -33
- package/dist/index.js +8 -15
- package/dist/mcp/server.js +103 -7
- package/dist/schema-BAWSG7KY.js +22 -0
- package/dist/schema-E3QUPL26.js +20 -0
- package/dist/schema-EHL7WUT6.js +20 -0
- package/docs/019-USAGE.md +44 -5
- package/docs/020-current-implementation.md +8 -8
- package/docs/021-DOGFOODING-FINDINGS.md +1 -1
- package/docs/CONFIG.md +1123 -0
- package/docs/ERRORS.md +383 -0
- package/docs/summarization.md +320 -0
- package/justfile +40 -0
- package/package.json +39 -33
- package/research/INDEX.md +315 -0
- package/research/code-review/README.md +90 -0
- package/research/code-review/cli-error-handling-review.md +979 -0
- package/research/code-review/code-review-validation-report.md +464 -0
- package/research/code-review/main-ts-review.md +1128 -0
- package/research/config-docs/SUMMARY.md +357 -0
- package/research/config-docs/TEST-RESULTS.md +776 -0
- package/research/config-docs/TODO.md +542 -0
- package/research/config-docs/analysis.md +744 -0
- package/research/config-docs/fix-validation.md +502 -0
- package/research/config-docs/help-audit.md +264 -0
- package/research/config-docs/help-system-analysis.md +890 -0
- package/research/frontmatter/COMMENTS-ARE-SKIPPED.md +149 -0
- package/research/frontmatter/LLM-CODE-NAVIGATION.md +276 -0
- package/research/issue-review.md +603 -0
- package/research/llm-summarization/agent-cli-tools-2026.md +1082 -0
- package/research/llm-summarization/alternative-providers-2026.md +1428 -0
- package/research/llm-summarization/anthropic-2026.md +367 -0
- package/research/llm-summarization/claude-cli-integration.md +1706 -0
- package/research/llm-summarization/cli-integration-patterns.md +3155 -0
- package/research/llm-summarization/openai-2026.md +473 -0
- package/research/llm-summarization/openai-compatible-providers-2026.md +1022 -0
- package/research/llm-summarization/opencode-cli-integration.md +1552 -0
- package/research/llm-summarization/prompt-engineering-2026.md +1426 -0
- package/research/llm-summarization/prototype-results.md +56 -0
- package/research/llm-summarization/provider-switching-patterns-2026.md +2153 -0
- package/research/llm-summarization/typescript-llm-libraries-2026.md +2436 -0
- package/research/mdcontext-pudding/00-EXECUTIVE-SUMMARY.md +282 -0
- package/research/mdcontext-pudding/01-index-embed.md +956 -0
- package/research/mdcontext-pudding/02-search-COMMANDS.md +142 -0
- package/research/mdcontext-pudding/02-search-SUMMARY.md +146 -0
- package/research/mdcontext-pudding/02-search.md +970 -0
- package/research/mdcontext-pudding/03-context.md +779 -0
- package/research/mdcontext-pudding/04-navigation-and-analytics.md +803 -0
- package/research/mdcontext-pudding/04-tree.md +704 -0
- package/research/mdcontext-pudding/05-config.md +1038 -0
- package/research/mdcontext-pudding/06-links-summary.txt +87 -0
- package/research/mdcontext-pudding/06-links.md +679 -0
- package/research/mdcontext-pudding/07-stats.md +693 -0
- package/research/mdcontext-pudding/BUG-FIX-PLAN.md +388 -0
- package/research/mdcontext-pudding/P0-BUG-VALIDATION.md +167 -0
- package/research/mdcontext-pudding/README.md +168 -0
- package/research/mdcontext-pudding/TESTING-SUMMARY.md +128 -0
- package/research/research-quality-review.md +834 -0
- package/research/semantic-search/embedding-text-analysis.md +156 -0
- package/research/semantic-search/multi-word-failure-reproduction.md +171 -0
- package/research/semantic-search/query-processing-analysis.md +207 -0
- package/research/semantic-search/root-cause-and-solution.md +114 -0
- package/research/semantic-search/threshold-validation-report.md +69 -0
- package/research/semantic-search/vector-search-analysis.md +63 -0
- package/research/test-path-issues.md +276 -0
- package/review/ALP-76/1-error-type-design.md +962 -0
- package/review/ALP-76/2-error-handling-patterns.md +906 -0
- package/review/ALP-76/3-error-presentation.md +624 -0
- package/review/ALP-76/4-test-coverage.md +625 -0
- package/review/ALP-76/5-migration-completeness.md +440 -0
- package/review/ALP-76/6-effect-best-practices.md +755 -0
- package/scripts/apply-branch-protection.sh +47 -0
- package/scripts/branch-protection-templates.json +79 -0
- package/scripts/prototype-summarization.ts +346 -0
- package/scripts/rebuild-hnswlib.js +32 -37
- package/scripts/setup-branch-protection.sh +64 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/active-provider.json +7 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.json +541 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.meta.json +5 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/config.json +8 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/documents.json +60 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/links.json +13 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/sections.json +1197 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/configuration-management.md +99 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/distributed-systems.md +92 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/error-handling.md +78 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/failure-automation.md +55 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/job-context.md +69 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/process-orchestration.md +99 -0
- package/src/cli/argv-preprocessor.test.ts +2 -2
- package/src/cli/cli.test.ts +230 -33
- package/src/cli/commands/config-cmd.ts +642 -0
- package/src/cli/commands/context.ts +97 -9
- package/src/cli/commands/duplicates.ts +122 -0
- package/src/cli/commands/embeddings.ts +529 -0
- package/src/cli/commands/index-cmd.ts +210 -30
- package/src/cli/commands/index.ts +3 -0
- package/src/cli/commands/search.ts +894 -64
- package/src/cli/commands/stats.ts +3 -0
- package/src/cli/commands/tree.ts +26 -5
- package/src/cli/config-layer.ts +176 -0
- package/src/cli/error-handler.test.ts +235 -0
- package/src/cli/error-handler.ts +655 -0
- package/src/cli/flag-schemas.ts +66 -0
- package/src/cli/help.ts +209 -7
- package/src/cli/main.ts +348 -58
- package/src/cli/options.ts +10 -0
- package/src/cli/shared-error-handling.ts +199 -0
- package/src/cli/utils.ts +150 -17
- package/src/config/file-provider.test.ts +320 -0
- package/src/config/file-provider.ts +273 -0
- package/src/config/index.ts +72 -0
- package/src/config/integration.test.ts +667 -0
- package/src/config/precedence.test.ts +277 -0
- package/src/config/precedence.ts +451 -0
- package/src/config/schema.test.ts +414 -0
- package/src/config/schema.ts +603 -0
- package/src/config/service.test.ts +320 -0
- package/src/config/service.ts +243 -0
- package/src/config/testing.test.ts +264 -0
- package/src/config/testing.ts +110 -0
- package/src/core/types.ts +6 -33
- package/src/duplicates/detector.test.ts +183 -0
- package/src/duplicates/detector.ts +414 -0
- package/src/duplicates/index.ts +18 -0
- package/src/embeddings/embedding-namespace.test.ts +300 -0
- package/src/embeddings/embedding-namespace.ts +947 -0
- package/src/embeddings/heading-boost.test.ts +222 -0
- package/src/embeddings/hnsw-build-options.test.ts +198 -0
- package/src/embeddings/hyde.test.ts +272 -0
- package/src/embeddings/hyde.ts +264 -0
- package/src/embeddings/index.ts +2 -0
- package/src/embeddings/openai-provider.ts +332 -83
- package/src/embeddings/pricing.json +22 -0
- package/src/embeddings/provider-constants.ts +204 -0
- package/src/embeddings/provider-errors.test.ts +967 -0
- package/src/embeddings/provider-errors.ts +565 -0
- package/src/embeddings/provider-factory.test.ts +240 -0
- package/src/embeddings/provider-factory.ts +225 -0
- package/src/embeddings/provider-integration.test.ts +788 -0
- package/src/embeddings/query-preprocessing.test.ts +187 -0
- package/src/embeddings/semantic-search-threshold.test.ts +508 -0
- package/src/embeddings/semantic-search.ts +780 -93
- package/src/embeddings/types.ts +293 -16
- package/src/embeddings/vector-store.ts +486 -77
- package/src/embeddings/voyage-provider.ts +313 -0
- package/src/errors/errors.test.ts +845 -0
- package/src/errors/index.ts +533 -0
- package/src/index/ignore-patterns.test.ts +354 -0
- package/src/index/ignore-patterns.ts +305 -0
- package/src/index/indexer.ts +286 -48
- package/src/index/storage.ts +94 -30
- package/src/index/types.ts +40 -2
- package/src/index/watcher.ts +67 -9
- package/src/index.ts +22 -0
- package/src/integration/search-keyword.test.ts +678 -0
- package/src/mcp/server.ts +135 -6
- package/src/parser/parser.ts +18 -19
- package/src/parser/section-filter.test.ts +277 -0
- package/src/parser/section-filter.ts +125 -3
- package/src/search/__tests__/hybrid-search.test.ts +650 -0
- package/src/search/bm25-store.ts +366 -0
- package/src/search/cross-encoder.test.ts +253 -0
- package/src/search/cross-encoder.ts +406 -0
- package/src/search/fuzzy-search.test.ts +419 -0
- package/src/search/fuzzy-search.ts +273 -0
- package/src/search/hybrid-search.ts +448 -0
- package/src/search/path-matcher.test.ts +276 -0
- package/src/search/path-matcher.ts +33 -0
- package/src/search/searcher.test.ts +99 -1
- package/src/search/searcher.ts +189 -67
- package/src/search/wink-bm25.d.ts +30 -0
- package/src/summarization/cli-providers/claude.ts +202 -0
- package/src/summarization/cli-providers/detection.test.ts +273 -0
- package/src/summarization/cli-providers/detection.ts +118 -0
- package/src/summarization/cli-providers/index.ts +8 -0
- package/src/summarization/cost.test.ts +139 -0
- package/src/summarization/cost.ts +102 -0
- package/src/summarization/error-handler.test.ts +127 -0
- package/src/summarization/error-handler.ts +111 -0
- package/src/summarization/index.ts +102 -0
- package/src/summarization/pipeline.test.ts +498 -0
- package/src/summarization/pipeline.ts +231 -0
- package/src/summarization/prompts.test.ts +269 -0
- package/src/summarization/prompts.ts +133 -0
- package/src/summarization/provider-factory.test.ts +396 -0
- package/src/summarization/provider-factory.ts +178 -0
- package/src/summarization/types.ts +184 -0
- package/src/summarize/summarizer.ts +104 -35
- package/src/types/huggingface-transformers.d.ts +66 -0
- package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
- package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/indexes/documents.json +4 -4
- package/tests/fixtures/cli/.mdcontext/indexes/sections.json +14 -0
- package/tests/integration/embed-index.test.ts +712 -0
- package/tests/integration/search-context.test.ts +469 -0
- package/tests/integration/search-semantic.test.ts +522 -0
- package/vitest.config.ts +1 -6
- package/AGENTS.md +0 -46
- package/tests/fixtures/cli/.mdcontext/vectors.bin +0 -0
- 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
|
+
}
|