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
@@ -131,6 +131,9 @@ export const statsCommand = Command.make(
131
131
  if (embeddingStats.hasEmbeddings) {
132
132
  yield* Console.log(` Vectors: ${embeddingStats.count}`)
133
133
  yield* Console.log(` Provider: ${embeddingStats.provider}`)
134
+ if (embeddingStats.model) {
135
+ yield* Console.log(` Model: ${embeddingStats.model}`)
136
+ }
134
137
  yield* Console.log(` Dimensions: ${embeddingStats.dimensions}`)
135
138
  yield* Console.log(
136
139
  ` Cost: $${embeddingStats.totalCost.toFixed(6)}`,
@@ -9,9 +9,10 @@ import * as path from 'node:path'
9
9
  import { Args, Command } from '@effect/cli'
10
10
  import { Console, Effect } from 'effect'
11
11
  import type { MdSection } from '../../core/types.js'
12
+ import { FileReadError, ParseError } from '../../errors/index.js'
12
13
  import { parseFile } from '../../parser/parser.js'
13
14
  import { jsonOption, prettyOption } from '../options.js'
14
- import { formatJson, walkDir } from '../utils.js'
15
+ import { formatJson, walkDirEffect } from '../utils.js'
15
16
 
16
17
  export const treeCommand = Command.make(
17
18
  'tree',
@@ -28,12 +29,32 @@ export const treeCommand = Command.make(
28
29
  const resolvedPath = path.resolve(pathArg)
29
30
 
30
31
  // Auto-detect: file or directory
31
- const stat = yield* Effect.try(() => fs.statSync(resolvedPath))
32
+ const stat = yield* Effect.try({
33
+ try: () => fs.statSync(resolvedPath),
34
+ catch: (e) =>
35
+ new FileReadError({
36
+ path: resolvedPath,
37
+ message: `Cannot access path: ${e instanceof Error ? e.message : String(e)}`,
38
+ cause: e,
39
+ }),
40
+ })
32
41
 
33
42
  if (stat.isFile()) {
34
43
  // Show document outline
35
44
  const result = yield* parseFile(resolvedPath).pipe(
36
- Effect.mapError((e) => new Error(`${e._tag}: ${e.message}`)),
45
+ Effect.mapError((e) =>
46
+ e._tag === 'ParseError'
47
+ ? new ParseError({
48
+ message: e.message,
49
+ path: resolvedPath,
50
+ ...(e.line !== undefined && { line: e.line }),
51
+ ...(e.column !== undefined && { column: e.column }),
52
+ })
53
+ : new FileReadError({
54
+ path: e.path,
55
+ message: e.message,
56
+ }),
57
+ ),
37
58
  )
38
59
 
39
60
  const extractStructure = (
@@ -84,9 +105,9 @@ export const treeCommand = Command.make(
84
105
  }
85
106
  } else {
86
107
  // Show file list
87
- const files = yield* Effect.promise(() => walkDir(resolvedPath))
108
+ const files = yield* walkDirEffect(resolvedPath)
88
109
 
89
- const tree = files.sort().map((f) => ({
110
+ const tree = [...files].sort().map((f) => ({
90
111
  path: f,
91
112
  relativePath: path.relative(resolvedPath, f),
92
113
  }))
@@ -0,0 +1,176 @@
1
+ /**
2
+ * CLI Configuration Layer
3
+ *
4
+ * Creates a configuration layer for use in CLI commands.
5
+ * Loads config with precedence: CLI flags > Environment > Config file > Defaults
6
+ */
7
+
8
+ import { Effect, Layer } from 'effect'
9
+ import {
10
+ type ConfigProviderOptions,
11
+ ConfigService,
12
+ ConfigServiceDefault,
13
+ createConfigProvider,
14
+ type MdContextConfig,
15
+ } from '../config/index.js'
16
+
17
+ /**
18
+ * Create a ConfigService layer from options.
19
+ *
20
+ * This loads configuration with the standard precedence chain:
21
+ * 1. CLI flags (highest priority)
22
+ * 2. Environment variables (MDCONTEXT_*)
23
+ * 3. Config file (if found)
24
+ * 4. Defaults
25
+ *
26
+ * @param options - Configuration provider options
27
+ * @returns A Layer that provides ConfigService
28
+ */
29
+ export const makeCliConfigLayer = (
30
+ options: ConfigProviderOptions = {},
31
+ ): Effect.Effect<Layer.Layer<ConfigService, never, never>, never, never> =>
32
+ Effect.gen(function* () {
33
+ // Create the config provider with precedence chain
34
+ const providerResult = yield* createConfigProvider(options).pipe(
35
+ Effect.catchAll(() =>
36
+ // If config loading fails, use empty provider (defaults will apply)
37
+ Effect.succeed(null),
38
+ ),
39
+ )
40
+
41
+ if (!providerResult) {
42
+ // Fall back to default config if provider creation failed
43
+ return ConfigServiceDefault
44
+ }
45
+
46
+ // Load the config using the provider
47
+ const configResult = yield* Effect.gen(function* () {
48
+ // Import the schema to load config
49
+ const { MdContextConfig: MdContextConfigSchema } = yield* Effect.promise(
50
+ async () => import('../config/schema.js'),
51
+ )
52
+ return yield* MdContextConfigSchema
53
+ }).pipe(
54
+ Effect.withConfigProvider(providerResult),
55
+ Effect.catchAll(() => Effect.succeed(null)),
56
+ )
57
+
58
+ if (!configResult) {
59
+ // Fall back to default config if loading failed
60
+ return ConfigServiceDefault
61
+ }
62
+
63
+ // Create a layer with the loaded config
64
+ return Layer.succeed(ConfigService, configResult)
65
+ })
66
+
67
+ /**
68
+ * Create the default CLI configuration layer.
69
+ *
70
+ * This loads configuration from:
71
+ * - Environment variables (MDCONTEXT_*)
72
+ * - Config file (mdcontext.config.ts/json)
73
+ * - Built-in defaults
74
+ *
75
+ * No CLI flags are applied at this level - commands handle their own flag overrides.
76
+ */
77
+ export const defaultCliConfigLayer: Effect.Effect<
78
+ Layer.Layer<ConfigService, never, never>,
79
+ never,
80
+ never
81
+ > = makeCliConfigLayer({
82
+ workingDir: process.cwd(),
83
+ })
84
+
85
+ /**
86
+ * Synchronously create a default config layer.
87
+ *
88
+ * For use in cases where async config loading isn't possible.
89
+ * Uses only environment variables and defaults.
90
+ */
91
+ export const defaultCliConfigLayerSync: Layer.Layer<
92
+ ConfigService,
93
+ never,
94
+ never
95
+ > = ConfigServiceDefault
96
+
97
+ /**
98
+ * Get config value with CLI flag override.
99
+ *
100
+ * Helper function for commands to get a config value, preferring
101
+ * an explicit CLI flag value if provided.
102
+ *
103
+ * @param cliValue - Value from CLI flag (may be undefined)
104
+ * @param configValue - Value from config
105
+ * @returns The CLI value if provided, otherwise the config value
106
+ */
107
+ export const withCliOverride = <T>(
108
+ cliValue: T | undefined,
109
+ configValue: T,
110
+ ): T => {
111
+ return cliValue !== undefined ? cliValue : configValue
112
+ }
113
+
114
+ /**
115
+ * Extract relevant config sections for a command.
116
+ */
117
+ export type SearchConfigValues = {
118
+ defaultLimit: number
119
+ maxLimit: number
120
+ minSimilarity: number
121
+ includeSnippets: boolean
122
+ snippetLength: number
123
+ }
124
+
125
+ export type IndexConfigValues = {
126
+ maxDepth: number
127
+ excludePatterns: readonly string[]
128
+ fileExtensions: readonly string[]
129
+ followSymlinks: boolean
130
+ indexDir: string
131
+ }
132
+
133
+ export type OutputConfigValues = {
134
+ format: 'text' | 'json'
135
+ color: boolean
136
+ prettyJson: boolean
137
+ verbose: boolean
138
+ debug: boolean
139
+ }
140
+
141
+ /**
142
+ * Extract search config from full config.
143
+ */
144
+ export const getSearchConfig = (
145
+ config: MdContextConfig,
146
+ ): SearchConfigValues => ({
147
+ defaultLimit: config.search.defaultLimit,
148
+ maxLimit: config.search.maxLimit,
149
+ minSimilarity: config.search.minSimilarity,
150
+ includeSnippets: config.search.includeSnippets,
151
+ snippetLength: config.search.snippetLength,
152
+ })
153
+
154
+ /**
155
+ * Extract index config from full config.
156
+ */
157
+ export const getIndexConfig = (config: MdContextConfig): IndexConfigValues => ({
158
+ maxDepth: config.index.maxDepth,
159
+ excludePatterns: config.index.excludePatterns,
160
+ fileExtensions: config.index.fileExtensions,
161
+ followSymlinks: config.index.followSymlinks,
162
+ indexDir: config.index.indexDir,
163
+ })
164
+
165
+ /**
166
+ * Extract output config from full config.
167
+ */
168
+ export const getOutputConfig = (
169
+ config: MdContextConfig,
170
+ ): OutputConfigValues => ({
171
+ format: config.output.format,
172
+ color: config.output.color,
173
+ prettyJson: config.output.prettyJson,
174
+ verbose: config.output.verbose,
175
+ debug: config.output.debug,
176
+ })
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Unit tests for CLI error handler
3
+ */
4
+
5
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
6
+ import { formatEffectCliError } from './error-handler.js'
7
+
8
+ describe('formatEffectCliError', () => {
9
+ let originalArgv: string[]
10
+
11
+ beforeEach(() => {
12
+ originalArgv = process.argv
13
+ })
14
+
15
+ afterEach(() => {
16
+ process.argv = originalArgv
17
+ })
18
+
19
+ describe('mode validation errors with suggestions', () => {
20
+ it('suggests "semantic" for "semantics"', () => {
21
+ process.argv = ['node', 'cli', 'search', '--mode', 'semantics', 'test']
22
+
23
+ const error = {
24
+ _tag: 'ValidationError',
25
+ error: {
26
+ _tag: 'Paragraph',
27
+ value: {
28
+ _tag: 'Text',
29
+ value:
30
+ 'Expected one of the following cases: hybrid, semantic, keyword',
31
+ },
32
+ },
33
+ }
34
+
35
+ const result = formatEffectCliError(error)
36
+ expect(result).toContain("Did you mean '--mode semantic'?")
37
+ })
38
+
39
+ it('suggests "keyword" for "keywords"', () => {
40
+ process.argv = ['node', 'cli', 'search', '--mode', 'keywords', 'test']
41
+
42
+ const error = {
43
+ _tag: 'ValidationError',
44
+ error: {
45
+ _tag: 'Paragraph',
46
+ value: {
47
+ _tag: 'Text',
48
+ value:
49
+ 'Expected one of the following cases: hybrid, semantic, keyword',
50
+ },
51
+ },
52
+ }
53
+
54
+ const result = formatEffectCliError(error)
55
+ expect(result).toContain("Did you mean '--mode keyword'?")
56
+ })
57
+
58
+ it('suggests "hybrid" for "hybrit"', () => {
59
+ process.argv = ['node', 'cli', 'search', '--mode', 'hybrit', 'test']
60
+
61
+ const error = {
62
+ _tag: 'ValidationError',
63
+ error: {
64
+ _tag: 'Paragraph',
65
+ value: {
66
+ _tag: 'Text',
67
+ value:
68
+ 'Expected one of the following cases: hybrid, semantic, keyword',
69
+ },
70
+ },
71
+ }
72
+
73
+ const result = formatEffectCliError(error)
74
+ expect(result).toContain("Did you mean '--mode hybrid'?")
75
+ })
76
+
77
+ it('suggests "semantic" for "semant"', () => {
78
+ process.argv = ['node', 'cli', 'search', '--mode', 'semant', 'test']
79
+
80
+ const error = {
81
+ _tag: 'ValidationError',
82
+ error: {
83
+ _tag: 'Paragraph',
84
+ value: {
85
+ _tag: 'Text',
86
+ value:
87
+ 'Expected one of the following cases: hybrid, semantic, keyword',
88
+ },
89
+ },
90
+ }
91
+
92
+ const result = formatEffectCliError(error)
93
+ expect(result).toContain("Did you mean '--mode semantic'?")
94
+ })
95
+
96
+ it('suggests "keyword" for "keywordd"', () => {
97
+ process.argv = ['node', 'cli', 'search', '--mode', 'keywordd', 'test']
98
+
99
+ const error = {
100
+ _tag: 'ValidationError',
101
+ error: {
102
+ _tag: 'Paragraph',
103
+ value: {
104
+ _tag: 'Text',
105
+ value:
106
+ 'Expected one of the following cases: hybrid, semantic, keyword',
107
+ },
108
+ },
109
+ }
110
+
111
+ const result = formatEffectCliError(error)
112
+ expect(result).toContain("Did you mean '--mode keyword'?")
113
+ })
114
+
115
+ it('does not suggest for typos too far off', () => {
116
+ process.argv = ['node', 'cli', 'search', '--mode', 'xyz', 'test']
117
+
118
+ const error = {
119
+ _tag: 'ValidationError',
120
+ error: {
121
+ _tag: 'Paragraph',
122
+ value: {
123
+ _tag: 'Text',
124
+ value:
125
+ 'Expected one of the following cases: hybrid, semantic, keyword',
126
+ },
127
+ },
128
+ }
129
+
130
+ const result = formatEffectCliError(error)
131
+ expect(result).not.toContain('Did you mean')
132
+ })
133
+
134
+ it('handles --mode=value syntax', () => {
135
+ process.argv = ['node', 'cli', 'search', '--mode=semantics', 'test']
136
+
137
+ const error = {
138
+ _tag: 'ValidationError',
139
+ error: {
140
+ _tag: 'Paragraph',
141
+ value: {
142
+ _tag: 'Text',
143
+ value:
144
+ 'Expected one of the following cases: hybrid, semantic, keyword',
145
+ },
146
+ },
147
+ }
148
+
149
+ const result = formatEffectCliError(error)
150
+ expect(result).toContain("Did you mean '--mode semantic'?")
151
+ })
152
+
153
+ it('handles -m short flag', () => {
154
+ process.argv = ['node', 'cli', 'search', '-m', 'semantics', 'test']
155
+
156
+ const error = {
157
+ _tag: 'ValidationError',
158
+ error: {
159
+ _tag: 'Paragraph',
160
+ value: {
161
+ _tag: 'Text',
162
+ value:
163
+ 'Expected one of the following cases: hybrid, semantic, keyword',
164
+ },
165
+ },
166
+ }
167
+
168
+ const result = formatEffectCliError(error)
169
+ expect(result).toContain("Did you mean '--mode semantic'?")
170
+ })
171
+
172
+ it('does not suggest for non-mode validation errors', () => {
173
+ process.argv = ['node', 'cli', 'search', 'test']
174
+
175
+ const error = {
176
+ _tag: 'ValidationError',
177
+ error: {
178
+ _tag: 'Paragraph',
179
+ value: {
180
+ _tag: 'Text',
181
+ value: 'Some other validation error',
182
+ },
183
+ },
184
+ }
185
+
186
+ const result = formatEffectCliError(error)
187
+ expect(result).toBe('Some other validation error')
188
+ expect(result).not.toContain('Did you mean')
189
+ })
190
+ })
191
+
192
+ describe('other error types', () => {
193
+ it('handles MissingValue errors', () => {
194
+ process.argv = ['node', 'cli', 'search']
195
+
196
+ const error = {
197
+ _tag: 'MissingValue',
198
+ error: {
199
+ _tag: 'Paragraph',
200
+ value: {
201
+ _tag: 'Text',
202
+ value: 'Missing required argument',
203
+ },
204
+ },
205
+ }
206
+
207
+ const result = formatEffectCliError(error)
208
+ expect(result).toBe('Missing required argument')
209
+ })
210
+
211
+ it('handles InvalidValue errors', () => {
212
+ process.argv = ['node', 'cli', 'search']
213
+
214
+ const error = {
215
+ _tag: 'InvalidValue',
216
+ error: {
217
+ _tag: 'Paragraph',
218
+ value: {
219
+ _tag: 'Text',
220
+ value: 'Invalid value provided',
221
+ },
222
+ },
223
+ }
224
+
225
+ const result = formatEffectCliError(error)
226
+ expect(result).toBe('Invalid value provided')
227
+ })
228
+
229
+ it('handles unknown error types', () => {
230
+ const error = { message: 'Unknown error' }
231
+ const result = formatEffectCliError(error)
232
+ expect(result).toBe('[object Object]')
233
+ })
234
+ })
235
+ })