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,642 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CONFIG Command
|
|
3
|
+
*
|
|
4
|
+
* Configuration management commands: init, check, etc.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'node:fs'
|
|
8
|
+
import * as path from 'node:path'
|
|
9
|
+
import { Command, Options } from '@effect/cli'
|
|
10
|
+
import { Console, Effect, Option } from 'effect'
|
|
11
|
+
import {
|
|
12
|
+
CONFIG_FILE_NAMES,
|
|
13
|
+
defaultConfig,
|
|
14
|
+
findConfigFile,
|
|
15
|
+
loadConfigFile,
|
|
16
|
+
readEnvConfig,
|
|
17
|
+
} from '../../config/index.js'
|
|
18
|
+
import type { PartialMdContextConfig } from '../../config/service.js'
|
|
19
|
+
import { jsonOption, prettyOption } from '../options.js'
|
|
20
|
+
import { formatJson } from '../utils.js'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate the default config file content.
|
|
24
|
+
* Uses JavaScript with JSDoc types for type safety without requiring TypeScript loader.
|
|
25
|
+
*/
|
|
26
|
+
const generateConfigContent = (format: 'js' | 'json'): string => {
|
|
27
|
+
if (format === 'json') {
|
|
28
|
+
return `{
|
|
29
|
+
"$schema": "https://mdcontext.dev/schema.json",
|
|
30
|
+
"index": {
|
|
31
|
+
"maxDepth": 10,
|
|
32
|
+
"excludePatterns": ["node_modules", ".git", "dist", "build"],
|
|
33
|
+
"fileExtensions": [".md", ".mdx"],
|
|
34
|
+
"followSymlinks": false,
|
|
35
|
+
"indexDir": ".mdcontext"
|
|
36
|
+
},
|
|
37
|
+
"search": {
|
|
38
|
+
"defaultLimit": 10,
|
|
39
|
+
"maxLimit": 100,
|
|
40
|
+
"minSimilarity": 0.35,
|
|
41
|
+
"includeSnippets": true,
|
|
42
|
+
"snippetLength": 200,
|
|
43
|
+
"autoIndexThreshold": 10
|
|
44
|
+
},
|
|
45
|
+
"embeddings": {
|
|
46
|
+
"provider": "openai",
|
|
47
|
+
"model": "text-embedding-3-small",
|
|
48
|
+
"dimensions": 512,
|
|
49
|
+
"batchSize": 100,
|
|
50
|
+
"maxRetries": 3,
|
|
51
|
+
"retryDelayMs": 1000,
|
|
52
|
+
"timeoutMs": 30000
|
|
53
|
+
},
|
|
54
|
+
"summarization": {
|
|
55
|
+
"briefTokenBudget": 100,
|
|
56
|
+
"summaryTokenBudget": 500,
|
|
57
|
+
"compressionRatio": 0.3,
|
|
58
|
+
"minSectionTokens": 20,
|
|
59
|
+
"maxTopics": 10,
|
|
60
|
+
"minPartialBudget": 50
|
|
61
|
+
},
|
|
62
|
+
"output": {
|
|
63
|
+
"format": "text",
|
|
64
|
+
"color": true,
|
|
65
|
+
"prettyJson": true,
|
|
66
|
+
"verbose": false,
|
|
67
|
+
"debug": false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// JavaScript format with JSDoc type annotation for type safety
|
|
74
|
+
return `/**
|
|
75
|
+
* mdcontext Configuration
|
|
76
|
+
*
|
|
77
|
+
* This file configures mdcontext behavior for this project.
|
|
78
|
+
* See https://mdcontext.dev/config for full documentation.
|
|
79
|
+
*
|
|
80
|
+
* @type {import('mdcontext').PartialMdContextConfig}
|
|
81
|
+
*/
|
|
82
|
+
export default {
|
|
83
|
+
// Index settings - control how markdown files are discovered and parsed
|
|
84
|
+
index: {
|
|
85
|
+
// Maximum directory depth to traverse (default: 10)
|
|
86
|
+
maxDepth: 10,
|
|
87
|
+
|
|
88
|
+
// Patterns to exclude from indexing (default: common build/dep dirs)
|
|
89
|
+
excludePatterns: ['node_modules', '.git', 'dist', 'build'],
|
|
90
|
+
|
|
91
|
+
// File extensions to index (default: markdown files)
|
|
92
|
+
fileExtensions: ['.md', '.mdx'],
|
|
93
|
+
|
|
94
|
+
// Whether to follow symbolic links (default: false)
|
|
95
|
+
followSymlinks: false,
|
|
96
|
+
|
|
97
|
+
// Directory for index storage (default: '.mdcontext')
|
|
98
|
+
indexDir: '.mdcontext',
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// Search settings - configure search behavior and defaults
|
|
102
|
+
search: {
|
|
103
|
+
// Default number of results to return (default: 10)
|
|
104
|
+
defaultLimit: 10,
|
|
105
|
+
|
|
106
|
+
// Maximum results allowed (default: 100)
|
|
107
|
+
maxLimit: 100,
|
|
108
|
+
|
|
109
|
+
// Minimum similarity score for semantic search (default: 0.35)
|
|
110
|
+
minSimilarity: 0.35,
|
|
111
|
+
|
|
112
|
+
// Include content snippets in results (default: true)
|
|
113
|
+
includeSnippets: true,
|
|
114
|
+
|
|
115
|
+
// Maximum snippet length in characters (default: 200)
|
|
116
|
+
snippetLength: 200,
|
|
117
|
+
|
|
118
|
+
// Auto-create semantic index if under this many seconds (default: 10)
|
|
119
|
+
autoIndexThreshold: 10,
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// Embeddings settings - configure semantic search
|
|
123
|
+
embeddings: {
|
|
124
|
+
// Embedding provider: 'openai' (default), 'ollama', 'lm-studio', 'openrouter', or 'voyage'
|
|
125
|
+
provider: 'openai',
|
|
126
|
+
|
|
127
|
+
// Embedding model (varies by provider - default for OpenAI: 'text-embedding-3-small')
|
|
128
|
+
// Ollama: 'nomic-embed-text', LM Studio: depends on loaded model
|
|
129
|
+
model: 'text-embedding-3-small',
|
|
130
|
+
|
|
131
|
+
// Vector dimensions (lower = faster, higher = more accurate) (default: 512)
|
|
132
|
+
dimensions: 512,
|
|
133
|
+
|
|
134
|
+
// Batch size for API calls (default: 100)
|
|
135
|
+
batchSize: 100,
|
|
136
|
+
|
|
137
|
+
// Max retries for failed API calls (default: 3)
|
|
138
|
+
maxRetries: 3,
|
|
139
|
+
|
|
140
|
+
// Delay between retries in ms (default: 1000)
|
|
141
|
+
retryDelayMs: 1000,
|
|
142
|
+
|
|
143
|
+
// Request timeout in ms (default: 30000)
|
|
144
|
+
timeoutMs: 30000,
|
|
145
|
+
|
|
146
|
+
// API key - set via provider-specific environment variable:
|
|
147
|
+
// - OpenAI: OPENAI_API_KEY
|
|
148
|
+
// - OpenRouter: OPENROUTER_API_KEY
|
|
149
|
+
// - Ollama/LM Studio: No API key needed (local providers)
|
|
150
|
+
// apiKey: process.env.OPENAI_API_KEY,
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// Summarization settings - configure context assembly
|
|
154
|
+
summarization: {
|
|
155
|
+
// Token budget for 'brief' compression level (default: 100)
|
|
156
|
+
briefTokenBudget: 100,
|
|
157
|
+
|
|
158
|
+
// Token budget for 'summary' compression level (default: 500)
|
|
159
|
+
summaryTokenBudget: 500,
|
|
160
|
+
|
|
161
|
+
// Target compression ratio for summaries (default: 0.3)
|
|
162
|
+
compressionRatio: 0.3,
|
|
163
|
+
|
|
164
|
+
// Minimum tokens for any section summary (default: 20)
|
|
165
|
+
minSectionTokens: 20,
|
|
166
|
+
|
|
167
|
+
// Maximum topics to extract from a document (default: 10)
|
|
168
|
+
maxTopics: 10,
|
|
169
|
+
|
|
170
|
+
// Minimum remaining budget for partial content (default: 50)
|
|
171
|
+
minPartialBudget: 50,
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
// Output settings - configure CLI output formatting
|
|
175
|
+
output: {
|
|
176
|
+
// Default output format: 'text' or 'json' (default: 'text')
|
|
177
|
+
format: 'text',
|
|
178
|
+
|
|
179
|
+
// Use colors in terminal output (default: true)
|
|
180
|
+
color: true,
|
|
181
|
+
|
|
182
|
+
// Pretty-print JSON output (default: true)
|
|
183
|
+
prettyJson: true,
|
|
184
|
+
|
|
185
|
+
// Show verbose output (default: false)
|
|
186
|
+
verbose: false,
|
|
187
|
+
|
|
188
|
+
// Show debug information (default: false)
|
|
189
|
+
debug: false,
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
`
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Config init subcommand - creates a starter config file.
|
|
197
|
+
*/
|
|
198
|
+
const initCommand = Command.make(
|
|
199
|
+
'init',
|
|
200
|
+
{
|
|
201
|
+
format: Options.choice('format', ['js', 'json']).pipe(
|
|
202
|
+
Options.withAlias('f'),
|
|
203
|
+
Options.withDescription(
|
|
204
|
+
'Config file format (js recommended for type safety)',
|
|
205
|
+
),
|
|
206
|
+
Options.withDefault('js' as const),
|
|
207
|
+
),
|
|
208
|
+
force: Options.boolean('force').pipe(
|
|
209
|
+
Options.withDescription('Overwrite existing config file'),
|
|
210
|
+
Options.withDefault(false),
|
|
211
|
+
),
|
|
212
|
+
json: jsonOption,
|
|
213
|
+
pretty: prettyOption,
|
|
214
|
+
},
|
|
215
|
+
({ format, force, json, pretty }) =>
|
|
216
|
+
Effect.gen(function* () {
|
|
217
|
+
const cwd = process.cwd()
|
|
218
|
+
|
|
219
|
+
// Check if a config file already exists
|
|
220
|
+
const existingConfig = findConfigFile(cwd)
|
|
221
|
+
|
|
222
|
+
if (existingConfig && !force) {
|
|
223
|
+
if (json) {
|
|
224
|
+
yield* Console.log(
|
|
225
|
+
formatJson(
|
|
226
|
+
{
|
|
227
|
+
error: 'Config file already exists',
|
|
228
|
+
path: existingConfig.path,
|
|
229
|
+
hint: 'Use --force to overwrite',
|
|
230
|
+
},
|
|
231
|
+
pretty,
|
|
232
|
+
),
|
|
233
|
+
)
|
|
234
|
+
} else {
|
|
235
|
+
yield* Console.error(
|
|
236
|
+
`Config file already exists: ${existingConfig.path}`,
|
|
237
|
+
)
|
|
238
|
+
yield* Console.error('')
|
|
239
|
+
yield* Console.error('Use --force to overwrite.')
|
|
240
|
+
}
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Determine filename based on format
|
|
245
|
+
const filename =
|
|
246
|
+
format === 'json' ? 'mdcontext.config.json' : 'mdcontext.config.js'
|
|
247
|
+
const filepath = path.join(cwd, filename)
|
|
248
|
+
|
|
249
|
+
// Generate content
|
|
250
|
+
const content = generateConfigContent(format)
|
|
251
|
+
|
|
252
|
+
// Write the file
|
|
253
|
+
yield* Effect.try({
|
|
254
|
+
try: () => fs.writeFileSync(filepath, content, 'utf-8'),
|
|
255
|
+
catch: (e) => new Error(`Failed to write config file: ${e}`),
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
if (json) {
|
|
259
|
+
yield* Console.log(
|
|
260
|
+
formatJson(
|
|
261
|
+
{
|
|
262
|
+
created: filepath,
|
|
263
|
+
format,
|
|
264
|
+
},
|
|
265
|
+
pretty,
|
|
266
|
+
),
|
|
267
|
+
)
|
|
268
|
+
} else {
|
|
269
|
+
yield* Console.log(`Created ${filename}`)
|
|
270
|
+
yield* Console.log('')
|
|
271
|
+
if (format === 'js') {
|
|
272
|
+
yield* Console.log('The config file includes:')
|
|
273
|
+
yield* Console.log(
|
|
274
|
+
' - JSDoc type annotations for IDE autocompletion',
|
|
275
|
+
)
|
|
276
|
+
yield* Console.log(' - Documented default values')
|
|
277
|
+
yield* Console.log(
|
|
278
|
+
' - All available options including summarization',
|
|
279
|
+
)
|
|
280
|
+
yield* Console.log('')
|
|
281
|
+
yield* Console.log(
|
|
282
|
+
'Edit the file to customize mdcontext for your project.',
|
|
283
|
+
)
|
|
284
|
+
} else {
|
|
285
|
+
yield* Console.log(
|
|
286
|
+
'Edit the file to customize mdcontext for your project.',
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}),
|
|
291
|
+
).pipe(Command.withDescription('Create a starter config file'))
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Config show subcommand - displays current config.
|
|
295
|
+
*/
|
|
296
|
+
const showCommand = Command.make(
|
|
297
|
+
'show',
|
|
298
|
+
{
|
|
299
|
+
json: jsonOption,
|
|
300
|
+
pretty: prettyOption,
|
|
301
|
+
},
|
|
302
|
+
({ json, pretty }) =>
|
|
303
|
+
Effect.gen(function* () {
|
|
304
|
+
const cwd = process.cwd()
|
|
305
|
+
|
|
306
|
+
// Find existing config file
|
|
307
|
+
const configPath = findConfigFile(cwd)
|
|
308
|
+
|
|
309
|
+
if (!configPath) {
|
|
310
|
+
if (json) {
|
|
311
|
+
yield* Console.log(
|
|
312
|
+
formatJson(
|
|
313
|
+
{
|
|
314
|
+
error: 'No config file found',
|
|
315
|
+
searchedIn: cwd,
|
|
316
|
+
searchedFor: CONFIG_FILE_NAMES,
|
|
317
|
+
},
|
|
318
|
+
pretty,
|
|
319
|
+
),
|
|
320
|
+
)
|
|
321
|
+
} else {
|
|
322
|
+
yield* Console.log('No config file found.')
|
|
323
|
+
yield* Console.log('')
|
|
324
|
+
yield* Console.log('Searched for:')
|
|
325
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
326
|
+
yield* Console.log(` - ${name}`)
|
|
327
|
+
}
|
|
328
|
+
yield* Console.log('')
|
|
329
|
+
yield* Console.log("Run 'mdcontext config init' to create one.")
|
|
330
|
+
}
|
|
331
|
+
return
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (json) {
|
|
335
|
+
yield* Console.log(
|
|
336
|
+
formatJson(
|
|
337
|
+
{
|
|
338
|
+
configFile: configPath.path,
|
|
339
|
+
},
|
|
340
|
+
pretty,
|
|
341
|
+
),
|
|
342
|
+
)
|
|
343
|
+
} else {
|
|
344
|
+
yield* Console.log(`Config file: ${configPath.path}`)
|
|
345
|
+
}
|
|
346
|
+
}),
|
|
347
|
+
).pipe(Command.withDescription('Show config file location'))
|
|
348
|
+
|
|
349
|
+
// ============================================================================
|
|
350
|
+
// Config Check Types
|
|
351
|
+
// ============================================================================
|
|
352
|
+
|
|
353
|
+
type ConfigSource = 'default' | 'file' | 'env'
|
|
354
|
+
|
|
355
|
+
interface ConfigValueWithSource<T> {
|
|
356
|
+
value: T
|
|
357
|
+
source: ConfigSource
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
type ConfigSectionWithSources<T> = {
|
|
361
|
+
[K in keyof T]: ConfigValueWithSource<T[K]>
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
interface ConfigWithSources {
|
|
365
|
+
index: ConfigSectionWithSources<typeof defaultConfig.index>
|
|
366
|
+
search: ConfigSectionWithSources<typeof defaultConfig.search>
|
|
367
|
+
embeddings: ConfigSectionWithSources<typeof defaultConfig.embeddings>
|
|
368
|
+
summarization: ConfigSectionWithSources<typeof defaultConfig.summarization>
|
|
369
|
+
output: ConfigSectionWithSources<typeof defaultConfig.output>
|
|
370
|
+
paths: ConfigSectionWithSources<typeof defaultConfig.paths>
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
interface CheckResultJson {
|
|
374
|
+
valid: boolean
|
|
375
|
+
sourceFile: string | null
|
|
376
|
+
errors?: string[]
|
|
377
|
+
config: ConfigWithSources
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ============================================================================
|
|
381
|
+
// Config Check Helpers
|
|
382
|
+
// ============================================================================
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Determine the source of a config value by checking env, file, and defaults.
|
|
386
|
+
*/
|
|
387
|
+
const getValueSource = <T>(
|
|
388
|
+
key: string,
|
|
389
|
+
envConfig: Map<string, string>,
|
|
390
|
+
fileValue: T | undefined,
|
|
391
|
+
_defaultValue: T,
|
|
392
|
+
): ConfigSource => {
|
|
393
|
+
if (envConfig.has(key)) {
|
|
394
|
+
return 'env'
|
|
395
|
+
}
|
|
396
|
+
if (fileValue !== undefined) {
|
|
397
|
+
return 'file'
|
|
398
|
+
}
|
|
399
|
+
return 'default'
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Get effective value from the precedence chain.
|
|
404
|
+
*/
|
|
405
|
+
const getEffectiveValue = <T>(
|
|
406
|
+
key: string,
|
|
407
|
+
envConfig: Map<string, string>,
|
|
408
|
+
fileValue: T | undefined,
|
|
409
|
+
defaultValue: T,
|
|
410
|
+
): T => {
|
|
411
|
+
const envValue = envConfig.get(key)
|
|
412
|
+
if (envValue !== undefined) {
|
|
413
|
+
// Parse env value based on type of default
|
|
414
|
+
if (typeof defaultValue === 'boolean') {
|
|
415
|
+
return (envValue === 'true') as unknown as T
|
|
416
|
+
}
|
|
417
|
+
if (typeof defaultValue === 'number') {
|
|
418
|
+
return Number(envValue) as unknown as T
|
|
419
|
+
}
|
|
420
|
+
if (Array.isArray(defaultValue)) {
|
|
421
|
+
return envValue.split(',') as unknown as T
|
|
422
|
+
}
|
|
423
|
+
return envValue as unknown as T
|
|
424
|
+
}
|
|
425
|
+
if (fileValue !== undefined) {
|
|
426
|
+
return fileValue
|
|
427
|
+
}
|
|
428
|
+
return defaultValue
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Build config section with source annotations.
|
|
433
|
+
*/
|
|
434
|
+
const buildSectionWithSources = <T extends Record<string, unknown>>(
|
|
435
|
+
sectionName: string,
|
|
436
|
+
defaultSection: T,
|
|
437
|
+
fileSection: Partial<T> | undefined,
|
|
438
|
+
envConfig: Map<string, string>,
|
|
439
|
+
): ConfigSectionWithSources<T> => {
|
|
440
|
+
const result: Record<string, ConfigValueWithSource<unknown>> = {}
|
|
441
|
+
|
|
442
|
+
for (const [key, defaultValue] of Object.entries(defaultSection)) {
|
|
443
|
+
const envKey = `${sectionName}.${key}`
|
|
444
|
+
const fileValue = fileSection?.[key as keyof T]
|
|
445
|
+
|
|
446
|
+
result[key] = {
|
|
447
|
+
value: getEffectiveValue(envKey, envConfig, fileValue, defaultValue),
|
|
448
|
+
source: getValueSource(envKey, envConfig, fileValue, defaultValue),
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return result as ConfigSectionWithSources<T>
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Format a value for text display.
|
|
457
|
+
*/
|
|
458
|
+
const formatValue = (value: unknown): string => {
|
|
459
|
+
if (Option.isOption(value)) {
|
|
460
|
+
return Option.isSome(value) ? String(value.value) : '(not set)'
|
|
461
|
+
}
|
|
462
|
+
if (Array.isArray(value)) {
|
|
463
|
+
return JSON.stringify(value)
|
|
464
|
+
}
|
|
465
|
+
if (typeof value === 'string') {
|
|
466
|
+
return value
|
|
467
|
+
}
|
|
468
|
+
return String(value)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Format source annotation for text display.
|
|
473
|
+
*/
|
|
474
|
+
const formatSourceAnnotation = (source: ConfigSource): string => {
|
|
475
|
+
switch (source) {
|
|
476
|
+
case 'file':
|
|
477
|
+
return '(from config file)'
|
|
478
|
+
case 'env':
|
|
479
|
+
return '(from environment)'
|
|
480
|
+
case 'default':
|
|
481
|
+
return '(default)'
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Convert config with sources to JSON format.
|
|
487
|
+
* Handles Option values by converting them to their underlying value or null.
|
|
488
|
+
*/
|
|
489
|
+
const configToJsonFormat = (config: ConfigWithSources): ConfigWithSources => {
|
|
490
|
+
const convertSection = <
|
|
491
|
+
T extends Record<string, ConfigValueWithSource<unknown>>,
|
|
492
|
+
>(
|
|
493
|
+
section: T,
|
|
494
|
+
): T => {
|
|
495
|
+
const result: Record<string, ConfigValueWithSource<unknown>> = {}
|
|
496
|
+
for (const [key, entry] of Object.entries(section)) {
|
|
497
|
+
let value = entry.value
|
|
498
|
+
if (Option.isOption(value)) {
|
|
499
|
+
value = Option.isSome(value) ? value.value : null
|
|
500
|
+
}
|
|
501
|
+
result[key] = { value, source: entry.source }
|
|
502
|
+
}
|
|
503
|
+
return result as T
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return {
|
|
507
|
+
index: convertSection(config.index),
|
|
508
|
+
search: convertSection(config.search),
|
|
509
|
+
embeddings: convertSection(config.embeddings),
|
|
510
|
+
summarization: convertSection(config.summarization),
|
|
511
|
+
output: convertSection(config.output),
|
|
512
|
+
paths: convertSection(config.paths),
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Config check subcommand - validates config and shows effective values with sources.
|
|
518
|
+
*/
|
|
519
|
+
const checkCommand = Command.make(
|
|
520
|
+
'check',
|
|
521
|
+
{
|
|
522
|
+
json: jsonOption,
|
|
523
|
+
pretty: prettyOption,
|
|
524
|
+
},
|
|
525
|
+
({ json, pretty }) =>
|
|
526
|
+
Effect.gen(function* () {
|
|
527
|
+
const cwd = process.cwd()
|
|
528
|
+
const errors: string[] = []
|
|
529
|
+
|
|
530
|
+
// Load config file if present
|
|
531
|
+
const configResult = yield* loadConfigFile(cwd).pipe(
|
|
532
|
+
Effect.catchTag('ConfigError', (e) => {
|
|
533
|
+
errors.push(e.message)
|
|
534
|
+
return Effect.succeed({ found: false, searched: [] } as const)
|
|
535
|
+
}),
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
const sourceFile = configResult.found ? configResult.path : null
|
|
539
|
+
const fileConfig: PartialMdContextConfig = configResult.found
|
|
540
|
+
? configResult.config
|
|
541
|
+
: {}
|
|
542
|
+
|
|
543
|
+
// Read environment variables
|
|
544
|
+
const envConfig = readEnvConfig('MDCONTEXT')
|
|
545
|
+
|
|
546
|
+
// Build config with source annotations
|
|
547
|
+
const configWithSources: ConfigWithSources = {
|
|
548
|
+
index: buildSectionWithSources(
|
|
549
|
+
'index',
|
|
550
|
+
defaultConfig.index,
|
|
551
|
+
fileConfig.index,
|
|
552
|
+
envConfig,
|
|
553
|
+
),
|
|
554
|
+
search: buildSectionWithSources(
|
|
555
|
+
'search',
|
|
556
|
+
defaultConfig.search,
|
|
557
|
+
fileConfig.search,
|
|
558
|
+
envConfig,
|
|
559
|
+
),
|
|
560
|
+
embeddings: buildSectionWithSources(
|
|
561
|
+
'embeddings',
|
|
562
|
+
defaultConfig.embeddings,
|
|
563
|
+
fileConfig.embeddings,
|
|
564
|
+
envConfig,
|
|
565
|
+
),
|
|
566
|
+
summarization: buildSectionWithSources(
|
|
567
|
+
'summarization',
|
|
568
|
+
defaultConfig.summarization,
|
|
569
|
+
fileConfig.summarization,
|
|
570
|
+
envConfig,
|
|
571
|
+
),
|
|
572
|
+
output: buildSectionWithSources(
|
|
573
|
+
'output',
|
|
574
|
+
defaultConfig.output,
|
|
575
|
+
fileConfig.output,
|
|
576
|
+
envConfig,
|
|
577
|
+
),
|
|
578
|
+
paths: buildSectionWithSources(
|
|
579
|
+
'paths',
|
|
580
|
+
defaultConfig.paths,
|
|
581
|
+
fileConfig.paths,
|
|
582
|
+
envConfig,
|
|
583
|
+
),
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const isValid = errors.length === 0
|
|
587
|
+
|
|
588
|
+
if (json) {
|
|
589
|
+
const result: CheckResultJson = {
|
|
590
|
+
valid: isValid,
|
|
591
|
+
sourceFile,
|
|
592
|
+
config: configToJsonFormat(configWithSources),
|
|
593
|
+
}
|
|
594
|
+
if (errors.length > 0) {
|
|
595
|
+
result.errors = errors
|
|
596
|
+
}
|
|
597
|
+
yield* Console.log(formatJson(result, pretty))
|
|
598
|
+
} else {
|
|
599
|
+
// Text format output
|
|
600
|
+
if (isValid) {
|
|
601
|
+
yield* Console.log('Configuration validated successfully!')
|
|
602
|
+
} else {
|
|
603
|
+
yield* Console.log('Configuration has errors:')
|
|
604
|
+
for (const error of errors) {
|
|
605
|
+
yield* Console.log(` - ${error}`)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
yield* Console.log('')
|
|
609
|
+
|
|
610
|
+
if (sourceFile) {
|
|
611
|
+
yield* Console.log(`Source: ${sourceFile}`)
|
|
612
|
+
} else {
|
|
613
|
+
yield* Console.log('Source: No config file found (using defaults)')
|
|
614
|
+
}
|
|
615
|
+
yield* Console.log('')
|
|
616
|
+
|
|
617
|
+
yield* Console.log('Effective configuration:')
|
|
618
|
+
|
|
619
|
+
// Display each section
|
|
620
|
+
for (const [sectionName, section] of Object.entries(
|
|
621
|
+
configWithSources,
|
|
622
|
+
)) {
|
|
623
|
+
yield* Console.log(` ${sectionName}:`)
|
|
624
|
+
for (const [key, entry] of Object.entries(
|
|
625
|
+
section as Record<string, ConfigValueWithSource<unknown>>,
|
|
626
|
+
)) {
|
|
627
|
+
const valueStr = formatValue(entry.value)
|
|
628
|
+
const sourceStr = formatSourceAnnotation(entry.source)
|
|
629
|
+
yield* Console.log(` ${key}: ${valueStr} ${sourceStr}`)
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}),
|
|
634
|
+
).pipe(Command.withDescription('Validate and display effective configuration'))
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Main config command with subcommands.
|
|
638
|
+
*/
|
|
639
|
+
export const configCommand = Command.make('config').pipe(
|
|
640
|
+
Command.withDescription('Configuration management'),
|
|
641
|
+
Command.withSubcommands([initCommand, showCommand, checkCommand]),
|
|
642
|
+
)
|