mdcontext 0.0.1 → 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/README.md +28 -0
- package/.changeset/config.json +11 -0
- package/.claude/settings.local.json +25 -0
- package/.github/workflows/ci.yml +83 -0
- package/.github/workflows/claude-code-review.yml +44 -0
- package/.github/workflows/claude.yml +85 -0
- package/.github/workflows/release.yml +113 -0
- package/.tldrignore +112 -0
- package/BACKLOG.md +338 -0
- package/CONTRIBUTING.md +186 -0
- package/NOTES/NOTES +44 -0
- package/README.md +434 -11
- package/biome.json +36 -0
- package/cspell.config.yaml +14 -0
- 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 +88 -0
- 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 +803 -0
- 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 +1629 -0
- 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.d.ts +1 -0
- package/dist/cli/main.js +5458 -0
- package/dist/index.d.ts +653 -0
- package/dist/index.js +79 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +472 -0
- 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 +625 -0
- package/docs/020-current-implementation.md +364 -0
- package/docs/021-DOGFOODING-FINDINGS.md +175 -0
- package/docs/BACKLOG.md +80 -0
- package/docs/CONFIG.md +1123 -0
- package/docs/DESIGN.md +439 -0
- package/docs/ERRORS.md +383 -0
- package/docs/PROJECT.md +88 -0
- package/docs/ROADMAP.md +407 -0
- package/docs/summarization.md +320 -0
- package/docs/test-links.md +9 -0
- package/justfile +40 -0
- package/package.json +74 -9
- package/pnpm-workspace.yaml +5 -0
- 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-analysis/01-current-implementation.md +470 -0
- package/research/config-analysis/02-strategy-recommendation.md +428 -0
- package/research/config-analysis/03-task-candidates.md +715 -0
- package/research/config-analysis/033-research-configuration-management.md +828 -0
- package/research/config-analysis/034-research-effect-cli-config.md +1504 -0
- package/research/config-analysis/04-consolidated-task-candidates.md +277 -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/dogfood/consolidated-tool-evaluation.md +373 -0
- package/research/dogfood/strategy-a/a-synthesis.md +184 -0
- package/research/dogfood/strategy-a/a1-docs.md +226 -0
- package/research/dogfood/strategy-a/a2-amorphic.md +156 -0
- package/research/dogfood/strategy-a/a3-llm.md +164 -0
- package/research/dogfood/strategy-b/b-synthesis.md +228 -0
- package/research/dogfood/strategy-b/b1-architecture.md +207 -0
- package/research/dogfood/strategy-b/b2-gaps.md +258 -0
- package/research/dogfood/strategy-b/b3-workflows.md +250 -0
- package/research/dogfood/strategy-c/c-synthesis.md +451 -0
- package/research/dogfood/strategy-c/c1-explorer.md +192 -0
- package/research/dogfood/strategy-c/c2-diver-memory.md +145 -0
- package/research/dogfood/strategy-c/c3-diver-control.md +148 -0
- package/research/dogfood/strategy-c/c4-diver-failure.md +151 -0
- package/research/dogfood/strategy-c/c5-diver-execution.md +221 -0
- package/research/dogfood/strategy-c/c6-diver-org.md +221 -0
- package/research/effect-cli-error-handling.md +845 -0
- package/research/effect-errors-as-values.md +943 -0
- package/research/errors-task-analysis/00-consolidated-tasks.md +207 -0
- package/research/errors-task-analysis/cli-commands-analysis.md +909 -0
- package/research/errors-task-analysis/embeddings-analysis.md +709 -0
- package/research/errors-task-analysis/index-search-analysis.md +812 -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-error-analysis.md +521 -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/npm_publish/011-npm-workflow-research-agent2.md +792 -0
- package/research/npm_publish/012-npm-workflow-research-agent1.md +530 -0
- package/research/npm_publish/013-npm-workflow-research-agent3.md +722 -0
- package/research/npm_publish/014-npm-workflow-synthesis.md +556 -0
- package/research/npm_publish/031-npm-workflow-task-analysis.md +134 -0
- package/research/research-quality-review.md +834 -0
- package/research/semantic-search/002-research-embedding-models.md +490 -0
- package/research/semantic-search/003-research-rag-alternatives.md +523 -0
- package/research/semantic-search/004-research-vector-search.md +841 -0
- package/research/semantic-search/032-research-semantic-search.md +427 -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/task-management-2026/00-synthesis-recommendations.md +295 -0
- package/research/task-management-2026/01-ai-workflow-tools.md +416 -0
- package/research/task-management-2026/02-agent-framework-patterns.md +476 -0
- package/research/task-management-2026/03-lightweight-file-based.md +567 -0
- package/research/task-management-2026/04-established-tools-ai-features.md +541 -0
- package/research/task-management-2026/linear/01-core-features-workflow.md +771 -0
- package/research/task-management-2026/linear/02-api-integrations.md +930 -0
- package/research/task-management-2026/linear/03-ai-features.md +368 -0
- package/research/task-management-2026/linear/04-pricing-setup.md +205 -0
- package/research/task-management-2026/linear/05-usage-patterns-best-practices.md +605 -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 +58 -0
- 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 +210 -0
- package/src/cli/argv-preprocessor.ts +202 -0
- package/src/cli/cli.test.ts +627 -0
- package/src/cli/commands/backlinks.ts +54 -0
- package/src/cli/commands/config-cmd.ts +642 -0
- package/src/cli/commands/context.ts +285 -0
- package/src/cli/commands/duplicates.ts +122 -0
- package/src/cli/commands/embeddings.ts +529 -0
- package/src/cli/commands/index-cmd.ts +480 -0
- package/src/cli/commands/index.ts +16 -0
- package/src/cli/commands/links.ts +52 -0
- package/src/cli/commands/search.ts +1281 -0
- package/src/cli/commands/stats.ts +149 -0
- package/src/cli/commands/tree.ts +128 -0
- 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 +341 -0
- package/src/cli/help.ts +588 -0
- package/src/cli/index.ts +9 -0
- package/src/cli/main.ts +435 -0
- package/src/cli/options.ts +41 -0
- package/src/cli/shared-error-handling.ts +199 -0
- package/src/cli/typo-suggester.test.ts +105 -0
- package/src/cli/typo-suggester.ts +130 -0
- package/src/cli/utils.ts +259 -0
- 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/index.ts +1 -0
- package/src/core/types.ts +113 -0
- 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 +10 -0
- package/src/embeddings/openai-provider.ts +414 -0
- 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 +1270 -0
- package/src/embeddings/types.ts +359 -0
- package/src/embeddings/vector-store.ts +708 -0
- 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/index.ts +4 -0
- package/src/index/indexer.ts +684 -0
- package/src/index/storage.ts +260 -0
- package/src/index/types.ts +147 -0
- package/src/index/watcher.ts +189 -0
- package/src/index.ts +30 -0
- package/src/integration/search-keyword.test.ts +678 -0
- package/src/mcp/server.ts +612 -0
- package/src/parser/index.ts +1 -0
- package/src/parser/parser.test.ts +291 -0
- package/src/parser/parser.ts +394 -0
- package/src/parser/section-filter.test.ts +277 -0
- package/src/parser/section-filter.ts +392 -0
- 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/query-parser.test.ts +260 -0
- package/src/search/query-parser.ts +319 -0
- package/src/search/searcher.test.ts +280 -0
- package/src/search/searcher.ts +724 -0
- 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/budget-bugs.test.ts +620 -0
- package/src/summarize/formatters.ts +419 -0
- package/src/summarize/index.ts +20 -0
- package/src/summarize/summarizer.test.ts +275 -0
- package/src/summarize/summarizer.ts +597 -0
- package/src/summarize/verify-bugs.test.ts +238 -0
- package/src/types/huggingface-transformers.d.ts +66 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/tokens.test.ts +142 -0
- package/src/utils/tokens.ts +186 -0
- package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
- package/tests/fixtures/cli/.mdcontext/config.json +8 -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 +33 -0
- package/tests/fixtures/cli/.mdcontext/indexes/links.json +12 -0
- package/tests/fixtures/cli/.mdcontext/indexes/sections.json +247 -0
- package/tests/fixtures/cli/README.md +9 -0
- package/tests/fixtures/cli/api-reference.md +11 -0
- package/tests/fixtures/cli/getting-started.md +11 -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/tsconfig.json +26 -0
- package/vitest.config.ts +16 -0
- package/vitest.setup.ts +12 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index storage operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as crypto from 'node:crypto'
|
|
6
|
+
import * as fs from 'node:fs/promises'
|
|
7
|
+
import * as path from 'node:path'
|
|
8
|
+
import { Effect } from 'effect'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
DirectoryCreateError,
|
|
12
|
+
FileReadError,
|
|
13
|
+
FileWriteError,
|
|
14
|
+
IndexCorruptedError,
|
|
15
|
+
} from '../errors/index.js'
|
|
16
|
+
import type {
|
|
17
|
+
DocumentIndex,
|
|
18
|
+
IndexConfig,
|
|
19
|
+
LinkIndex,
|
|
20
|
+
SectionIndex,
|
|
21
|
+
} from './types.js'
|
|
22
|
+
import { getIndexPaths, INDEX_VERSION } from './types.js'
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// File System Helpers
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
const ensureDir = (
|
|
29
|
+
dirPath: string,
|
|
30
|
+
): Effect.Effect<void, DirectoryCreateError> =>
|
|
31
|
+
Effect.tryPromise({
|
|
32
|
+
try: () => fs.mkdir(dirPath, { recursive: true }),
|
|
33
|
+
catch: (e) =>
|
|
34
|
+
new DirectoryCreateError({
|
|
35
|
+
path: dirPath,
|
|
36
|
+
message: e instanceof Error ? e.message : String(e),
|
|
37
|
+
cause: e,
|
|
38
|
+
}),
|
|
39
|
+
}).pipe(Effect.map(() => undefined))
|
|
40
|
+
|
|
41
|
+
const readJsonFile = <T>(
|
|
42
|
+
filePath: string,
|
|
43
|
+
): Effect.Effect<T | null, FileReadError | IndexCorruptedError> =>
|
|
44
|
+
Effect.gen(function* () {
|
|
45
|
+
// Try to read file content
|
|
46
|
+
const contentResult = yield* Effect.tryPromise({
|
|
47
|
+
try: () => fs.readFile(filePath, 'utf-8'),
|
|
48
|
+
catch: (e) => {
|
|
49
|
+
// File not found is not an error - return null
|
|
50
|
+
if (e && typeof e === 'object' && 'code' in e && e.code === 'ENOENT') {
|
|
51
|
+
return { notFound: true as const }
|
|
52
|
+
}
|
|
53
|
+
return new FileReadError({
|
|
54
|
+
path: filePath,
|
|
55
|
+
message: e instanceof Error ? e.message : String(e),
|
|
56
|
+
cause: e,
|
|
57
|
+
})
|
|
58
|
+
},
|
|
59
|
+
}).pipe(
|
|
60
|
+
Effect.map((content) =>
|
|
61
|
+
typeof content === 'string' ? { content } : content,
|
|
62
|
+
),
|
|
63
|
+
// Note: catchAll here filters out "file not found" as expected case (returns null),
|
|
64
|
+
// while other errors are re-thrown to propagate as typed FileReadError
|
|
65
|
+
Effect.catchAll((e) =>
|
|
66
|
+
e && 'notFound' in e
|
|
67
|
+
? Effect.succeed({ notFound: true as const })
|
|
68
|
+
: Effect.fail(e),
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
// Handle not found
|
|
73
|
+
if ('notFound' in contentResult) {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Parse JSON - corrupted files should fail with IndexCorruptedError
|
|
78
|
+
return yield* Effect.try({
|
|
79
|
+
try: () => JSON.parse(contentResult.content) as T,
|
|
80
|
+
catch: (e) =>
|
|
81
|
+
new IndexCorruptedError({
|
|
82
|
+
path: filePath,
|
|
83
|
+
reason: 'InvalidJson',
|
|
84
|
+
details: e instanceof Error ? e.message : String(e),
|
|
85
|
+
}),
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const writeJsonFile = <T>(
|
|
90
|
+
filePath: string,
|
|
91
|
+
data: T,
|
|
92
|
+
): Effect.Effect<void, DirectoryCreateError | FileWriteError> =>
|
|
93
|
+
Effect.gen(function* () {
|
|
94
|
+
const dir = path.dirname(filePath)
|
|
95
|
+
yield* ensureDir(dir)
|
|
96
|
+
yield* Effect.tryPromise({
|
|
97
|
+
try: () => fs.writeFile(filePath, JSON.stringify(data, null, 2)),
|
|
98
|
+
catch: (e) =>
|
|
99
|
+
new FileWriteError({
|
|
100
|
+
path: filePath,
|
|
101
|
+
message: e instanceof Error ? e.message : String(e),
|
|
102
|
+
cause: e,
|
|
103
|
+
}),
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// Hash Computation
|
|
109
|
+
// ============================================================================
|
|
110
|
+
|
|
111
|
+
export const computeHash = (content: string): string => {
|
|
112
|
+
return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// Index Storage Operations
|
|
117
|
+
// ============================================================================
|
|
118
|
+
|
|
119
|
+
export interface IndexStorage {
|
|
120
|
+
readonly rootPath: string
|
|
121
|
+
readonly paths: ReturnType<typeof getIndexPaths>
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const createStorage = (rootPath: string): IndexStorage => ({
|
|
125
|
+
rootPath: path.resolve(rootPath),
|
|
126
|
+
paths: getIndexPaths(path.resolve(rootPath)),
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
export const initializeIndex = (
|
|
130
|
+
storage: IndexStorage,
|
|
131
|
+
): Effect.Effect<
|
|
132
|
+
void,
|
|
133
|
+
DirectoryCreateError | FileReadError | FileWriteError | IndexCorruptedError
|
|
134
|
+
> =>
|
|
135
|
+
Effect.gen(function* () {
|
|
136
|
+
yield* ensureDir(storage.paths.root)
|
|
137
|
+
yield* ensureDir(storage.paths.parsed)
|
|
138
|
+
yield* ensureDir(path.dirname(storage.paths.documents))
|
|
139
|
+
|
|
140
|
+
// Create default config if it doesn't exist
|
|
141
|
+
const existingConfig = yield* loadConfig(storage)
|
|
142
|
+
if (!existingConfig) {
|
|
143
|
+
const config: IndexConfig = {
|
|
144
|
+
version: INDEX_VERSION,
|
|
145
|
+
rootPath: storage.rootPath,
|
|
146
|
+
include: ['**/*.md', '**/*.mdx'],
|
|
147
|
+
exclude: ['**/node_modules/**', '**/.*/**'],
|
|
148
|
+
createdAt: new Date().toISOString(),
|
|
149
|
+
updatedAt: new Date().toISOString(),
|
|
150
|
+
}
|
|
151
|
+
yield* saveConfig(storage, config)
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// Config Operations
|
|
157
|
+
// ============================================================================
|
|
158
|
+
|
|
159
|
+
export const loadConfig = (
|
|
160
|
+
storage: IndexStorage,
|
|
161
|
+
): Effect.Effect<IndexConfig | null, FileReadError | IndexCorruptedError> =>
|
|
162
|
+
readJsonFile<IndexConfig>(storage.paths.config)
|
|
163
|
+
|
|
164
|
+
export const saveConfig = (
|
|
165
|
+
storage: IndexStorage,
|
|
166
|
+
config: IndexConfig,
|
|
167
|
+
): Effect.Effect<void, DirectoryCreateError | FileWriteError> =>
|
|
168
|
+
writeJsonFile(storage.paths.config, {
|
|
169
|
+
...config,
|
|
170
|
+
updatedAt: new Date().toISOString(),
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
// ============================================================================
|
|
174
|
+
// Document Index Operations
|
|
175
|
+
// ============================================================================
|
|
176
|
+
|
|
177
|
+
export const loadDocumentIndex = (
|
|
178
|
+
storage: IndexStorage,
|
|
179
|
+
): Effect.Effect<DocumentIndex | null, FileReadError | IndexCorruptedError> =>
|
|
180
|
+
readJsonFile<DocumentIndex>(storage.paths.documents)
|
|
181
|
+
|
|
182
|
+
export const saveDocumentIndex = (
|
|
183
|
+
storage: IndexStorage,
|
|
184
|
+
index: DocumentIndex,
|
|
185
|
+
): Effect.Effect<void, DirectoryCreateError | FileWriteError> =>
|
|
186
|
+
writeJsonFile(storage.paths.documents, index)
|
|
187
|
+
|
|
188
|
+
export const createEmptyDocumentIndex = (rootPath: string): DocumentIndex => ({
|
|
189
|
+
version: INDEX_VERSION,
|
|
190
|
+
rootPath,
|
|
191
|
+
documents: {},
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// Section Index Operations
|
|
196
|
+
// ============================================================================
|
|
197
|
+
|
|
198
|
+
export const loadSectionIndex = (
|
|
199
|
+
storage: IndexStorage,
|
|
200
|
+
): Effect.Effect<SectionIndex | null, FileReadError | IndexCorruptedError> =>
|
|
201
|
+
readJsonFile<SectionIndex>(storage.paths.sections)
|
|
202
|
+
|
|
203
|
+
export const saveSectionIndex = (
|
|
204
|
+
storage: IndexStorage,
|
|
205
|
+
index: SectionIndex,
|
|
206
|
+
): Effect.Effect<void, DirectoryCreateError | FileWriteError> =>
|
|
207
|
+
writeJsonFile(storage.paths.sections, index)
|
|
208
|
+
|
|
209
|
+
export const createEmptySectionIndex = (): SectionIndex => ({
|
|
210
|
+
version: INDEX_VERSION,
|
|
211
|
+
sections: {},
|
|
212
|
+
byHeading: Object.create(null),
|
|
213
|
+
byDocument: Object.create(null),
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
// ============================================================================
|
|
217
|
+
// Link Index Operations
|
|
218
|
+
// ============================================================================
|
|
219
|
+
|
|
220
|
+
export const loadLinkIndex = (
|
|
221
|
+
storage: IndexStorage,
|
|
222
|
+
): Effect.Effect<LinkIndex | null, FileReadError | IndexCorruptedError> =>
|
|
223
|
+
readJsonFile<LinkIndex>(storage.paths.links)
|
|
224
|
+
|
|
225
|
+
export const saveLinkIndex = (
|
|
226
|
+
storage: IndexStorage,
|
|
227
|
+
index: LinkIndex,
|
|
228
|
+
): Effect.Effect<void, DirectoryCreateError | FileWriteError> =>
|
|
229
|
+
writeJsonFile(storage.paths.links, index)
|
|
230
|
+
|
|
231
|
+
export const createEmptyLinkIndex = (): LinkIndex => ({
|
|
232
|
+
version: INDEX_VERSION,
|
|
233
|
+
forward: Object.create(null),
|
|
234
|
+
backward: Object.create(null),
|
|
235
|
+
broken: [],
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// Index Existence Check
|
|
240
|
+
// ============================================================================
|
|
241
|
+
|
|
242
|
+
export const indexExists = (
|
|
243
|
+
storage: IndexStorage,
|
|
244
|
+
): Effect.Effect<boolean, FileReadError> =>
|
|
245
|
+
Effect.tryPromise({
|
|
246
|
+
try: async () => {
|
|
247
|
+
try {
|
|
248
|
+
await fs.access(storage.paths.config)
|
|
249
|
+
return true
|
|
250
|
+
} catch {
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
catch: (e) =>
|
|
255
|
+
new FileReadError({
|
|
256
|
+
path: storage.paths.config,
|
|
257
|
+
message: e instanceof Error ? e.message : String(e),
|
|
258
|
+
cause: e,
|
|
259
|
+
}),
|
|
260
|
+
})
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index data types for mdcontext
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Configuration
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
export interface IndexConfig {
|
|
10
|
+
readonly version: number
|
|
11
|
+
readonly rootPath: string
|
|
12
|
+
readonly include: readonly string[]
|
|
13
|
+
readonly exclude: readonly string[]
|
|
14
|
+
readonly createdAt: string
|
|
15
|
+
readonly updatedAt: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Document Index
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export interface DocumentIndex {
|
|
23
|
+
readonly version: number
|
|
24
|
+
readonly rootPath: string
|
|
25
|
+
readonly documents: Record<string, DocumentEntry>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DocumentEntry {
|
|
29
|
+
readonly id: string
|
|
30
|
+
readonly path: string
|
|
31
|
+
readonly title: string
|
|
32
|
+
readonly mtime: number
|
|
33
|
+
readonly hash: string
|
|
34
|
+
readonly tokenCount: number
|
|
35
|
+
readonly sectionCount: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Section Index
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
export interface SectionIndex {
|
|
43
|
+
readonly version: number
|
|
44
|
+
readonly sections: Record<string, SectionEntry>
|
|
45
|
+
readonly byHeading: Record<string, readonly string[]>
|
|
46
|
+
readonly byDocument: Record<string, readonly string[]>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface SectionEntry {
|
|
50
|
+
readonly id: string
|
|
51
|
+
readonly documentId: string
|
|
52
|
+
readonly documentPath: string
|
|
53
|
+
readonly heading: string
|
|
54
|
+
readonly level: number
|
|
55
|
+
readonly startLine: number
|
|
56
|
+
readonly endLine: number
|
|
57
|
+
readonly tokenCount: number
|
|
58
|
+
readonly hasCode: boolean
|
|
59
|
+
readonly hasList: boolean
|
|
60
|
+
readonly hasTable: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Link Index
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
export interface LinkIndex {
|
|
68
|
+
readonly version: number
|
|
69
|
+
readonly forward: Record<string, readonly string[]>
|
|
70
|
+
readonly backward: Record<string, readonly string[]>
|
|
71
|
+
readonly broken: readonly string[]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// Index Result
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Reason why a file was skipped during indexing
|
|
80
|
+
*/
|
|
81
|
+
export type SkipReason =
|
|
82
|
+
| 'unchanged' // File hash and mtime unchanged
|
|
83
|
+
| 'excluded' // Matches exclude pattern
|
|
84
|
+
| 'hidden' // Hidden file or directory
|
|
85
|
+
| 'not-markdown' // Not a markdown file
|
|
86
|
+
| 'binary' // Binary file detected
|
|
87
|
+
| 'oversized' // File too large
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Information about a skipped file
|
|
91
|
+
*/
|
|
92
|
+
export interface SkippedFile {
|
|
93
|
+
readonly path: string
|
|
94
|
+
readonly reason: SkipReason
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Summary of skipped files by reason
|
|
99
|
+
*/
|
|
100
|
+
export interface SkipSummary {
|
|
101
|
+
readonly unchanged: number
|
|
102
|
+
readonly excluded: number
|
|
103
|
+
readonly hidden: number
|
|
104
|
+
readonly total: number
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface IndexResult {
|
|
108
|
+
readonly documentsIndexed: number
|
|
109
|
+
readonly sectionsIndexed: number
|
|
110
|
+
readonly linksIndexed: number
|
|
111
|
+
readonly totalDocuments: number
|
|
112
|
+
readonly totalSections: number
|
|
113
|
+
readonly totalLinks: number
|
|
114
|
+
readonly duration: number
|
|
115
|
+
/** Non-fatal file processing errors (files that couldn't be indexed) */
|
|
116
|
+
readonly errors: readonly FileProcessingError[]
|
|
117
|
+
readonly skipped: SkipSummary
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Non-fatal error during file processing in index build.
|
|
122
|
+
* These are collected and reported but don't stop the build.
|
|
123
|
+
*
|
|
124
|
+
* Note: This is distinct from IndexBuildError in errors/index.ts,
|
|
125
|
+
* which is a TaggedError for fatal build failures.
|
|
126
|
+
*/
|
|
127
|
+
export interface FileProcessingError {
|
|
128
|
+
readonly path: string
|
|
129
|
+
readonly message: string
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// Index Paths
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
export const INDEX_DIR = '.mdcontext'
|
|
137
|
+
export const INDEX_VERSION = 1
|
|
138
|
+
|
|
139
|
+
export const getIndexPaths = (rootPath: string) => ({
|
|
140
|
+
root: `${rootPath}/${INDEX_DIR}`,
|
|
141
|
+
config: `${rootPath}/${INDEX_DIR}/config.json`,
|
|
142
|
+
documents: `${rootPath}/${INDEX_DIR}/indexes/documents.json`,
|
|
143
|
+
sections: `${rootPath}/${INDEX_DIR}/indexes/sections.json`,
|
|
144
|
+
links: `${rootPath}/${INDEX_DIR}/indexes/links.json`,
|
|
145
|
+
cache: `${rootPath}/${INDEX_DIR}/cache`,
|
|
146
|
+
parsed: `${rootPath}/${INDEX_DIR}/cache/parsed`,
|
|
147
|
+
})
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File watcher for automatic re-indexing
|
|
3
|
+
*
|
|
4
|
+
* ## Why Not Effect Streams?
|
|
5
|
+
*
|
|
6
|
+
* We evaluated using Effect Streams (ALP-101) but decided the current approach is better:
|
|
7
|
+
*
|
|
8
|
+
* 1. **chokidar is battle-tested** - Handles OS-specific quirks (FSEvents on macOS,
|
|
9
|
+
* inotify on Linux, ReadDirectoryChangesW on Windows)
|
|
10
|
+
*
|
|
11
|
+
* 2. **Debouncing handles backpressure** - The 300ms debounce already batches rapid
|
|
12
|
+
* changes, so Stream backpressure isn't needed
|
|
13
|
+
*
|
|
14
|
+
* 3. **Simple use case** - File change → rebuild index. No complex transformations
|
|
15
|
+
* or compositions that would benefit from Stream operators
|
|
16
|
+
*
|
|
17
|
+
* 4. **Already Effect-based** - The setup/teardown is wrapped in Effect for proper
|
|
18
|
+
* error handling, and we use typed errors (WatchError, IndexBuildError)
|
|
19
|
+
*
|
|
20
|
+
* If future requirements need more sophisticated event processing (filtering by
|
|
21
|
+
* content type, incremental updates, event replay), reconsider Streams then.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import * as path from 'node:path'
|
|
25
|
+
import { watch } from 'chokidar'
|
|
26
|
+
import { Effect } from 'effect'
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
type DirectoryCreateError,
|
|
30
|
+
type DirectoryWalkError,
|
|
31
|
+
type FileReadError,
|
|
32
|
+
type FileWriteError,
|
|
33
|
+
type IndexCorruptedError,
|
|
34
|
+
WatchError,
|
|
35
|
+
} from '../errors/index.js'
|
|
36
|
+
import { getChokidarIgnorePatterns } from './ignore-patterns.js'
|
|
37
|
+
import { buildIndex, type IndexOptions } from './indexer.js'
|
|
38
|
+
import { createStorage, indexExists } from './storage.js'
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Union of errors that can occur during watch operations
|
|
42
|
+
*/
|
|
43
|
+
export type WatchDirectoryError =
|
|
44
|
+
| WatchError
|
|
45
|
+
| DirectoryWalkError
|
|
46
|
+
| DirectoryCreateError
|
|
47
|
+
| FileReadError
|
|
48
|
+
| FileWriteError
|
|
49
|
+
| IndexCorruptedError
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Watcher Types
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
export interface WatcherOptions extends IndexOptions {
|
|
56
|
+
readonly debounceMs?: number
|
|
57
|
+
readonly onIndex?: (result: {
|
|
58
|
+
documentsIndexed: number
|
|
59
|
+
duration: number
|
|
60
|
+
}) => void
|
|
61
|
+
readonly onError?: (error: WatchError) => void
|
|
62
|
+
/** Whether to honor .gitignore for file watching (default: true) */
|
|
63
|
+
readonly honorGitignore?: boolean
|
|
64
|
+
/** Whether to honor .mdcontextignore for file watching (default: true) */
|
|
65
|
+
readonly honorMdcontextignore?: boolean
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface Watcher {
|
|
69
|
+
readonly stop: () => void
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Watcher Implementation
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
const isMarkdownFile = (filePath: string): boolean =>
|
|
77
|
+
filePath.endsWith('.md') || filePath.endsWith('.mdx')
|
|
78
|
+
|
|
79
|
+
export const watchDirectory = (
|
|
80
|
+
rootPath: string,
|
|
81
|
+
options: WatcherOptions = {},
|
|
82
|
+
): Effect.Effect<Watcher, WatchDirectoryError> =>
|
|
83
|
+
Effect.gen(function* () {
|
|
84
|
+
const resolvedRoot = path.resolve(rootPath)
|
|
85
|
+
const storage = createStorage(resolvedRoot)
|
|
86
|
+
const debounceMs = options.debounceMs ?? 300
|
|
87
|
+
|
|
88
|
+
// Ensure index exists
|
|
89
|
+
const exists = yield* indexExists(storage)
|
|
90
|
+
if (!exists) {
|
|
91
|
+
// Build initial index
|
|
92
|
+
const result = yield* buildIndex(resolvedRoot, options)
|
|
93
|
+
options.onIndex?.({
|
|
94
|
+
documentsIndexed: result.documentsIndexed,
|
|
95
|
+
duration: result.duration,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Create a debounce queue
|
|
100
|
+
const pendingPaths = new Set<string>()
|
|
101
|
+
let debounceTimer: NodeJS.Timeout | null = null
|
|
102
|
+
|
|
103
|
+
const scheduleReindex = () => {
|
|
104
|
+
if (debounceTimer) {
|
|
105
|
+
clearTimeout(debounceTimer)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
debounceTimer = setTimeout(async () => {
|
|
109
|
+
if (pendingPaths.size === 0) return
|
|
110
|
+
|
|
111
|
+
pendingPaths.clear()
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const result = await Effect.runPromise(
|
|
115
|
+
buildIndex(resolvedRoot, options),
|
|
116
|
+
)
|
|
117
|
+
options.onIndex?.({
|
|
118
|
+
documentsIndexed: result.documentsIndexed,
|
|
119
|
+
duration: result.duration,
|
|
120
|
+
})
|
|
121
|
+
} catch (error) {
|
|
122
|
+
options.onError?.(
|
|
123
|
+
new WatchError({
|
|
124
|
+
path: resolvedRoot,
|
|
125
|
+
message:
|
|
126
|
+
error instanceof Error ? error.message : 'Index rebuild failed',
|
|
127
|
+
cause: error,
|
|
128
|
+
}),
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
}, debounceMs)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Build ignore patterns for chokidar
|
|
135
|
+
const ignorePatterns = yield* getChokidarIgnorePatterns({
|
|
136
|
+
rootPath: resolvedRoot,
|
|
137
|
+
cliPatterns: options.exclude,
|
|
138
|
+
honorGitignore: options.honorGitignore ?? true,
|
|
139
|
+
honorMdcontextignore: options.honorMdcontextignore ?? true,
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Set up chokidar watcher with dynamic ignore patterns
|
|
143
|
+
const watcher = watch(resolvedRoot, {
|
|
144
|
+
ignored: ignorePatterns,
|
|
145
|
+
persistent: true,
|
|
146
|
+
ignoreInitial: true,
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
watcher.on('add', (filePath) => {
|
|
150
|
+
if (isMarkdownFile(filePath)) {
|
|
151
|
+
pendingPaths.add(filePath)
|
|
152
|
+
scheduleReindex()
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
watcher.on('change', (filePath) => {
|
|
157
|
+
if (isMarkdownFile(filePath)) {
|
|
158
|
+
pendingPaths.add(filePath)
|
|
159
|
+
scheduleReindex()
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
watcher.on('unlink', (filePath) => {
|
|
164
|
+
if (isMarkdownFile(filePath)) {
|
|
165
|
+
pendingPaths.add(filePath)
|
|
166
|
+
scheduleReindex()
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
watcher.on('error', (error: unknown) => {
|
|
171
|
+
options.onError?.(
|
|
172
|
+
new WatchError({
|
|
173
|
+
path: resolvedRoot,
|
|
174
|
+
message:
|
|
175
|
+
error instanceof Error ? error.message : 'File watcher error',
|
|
176
|
+
cause: error,
|
|
177
|
+
}),
|
|
178
|
+
)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
stop: () => {
|
|
183
|
+
if (debounceTimer) {
|
|
184
|
+
clearTimeout(debounceTimer)
|
|
185
|
+
}
|
|
186
|
+
watcher.close()
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mdcontext - Token-efficient markdown analysis for LLMs
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Config utilities for user config files
|
|
6
|
+
export type { PartialMdContextConfig } from './config/service.js'
|
|
7
|
+
export * from './core/index.js'
|
|
8
|
+
export * from './index/index.js'
|
|
9
|
+
export * from './parser/index.js'
|
|
10
|
+
export * from './utils/index.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Type-safe configuration helper for mdcontext.config.ts files.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { defineConfig } from 'mdcontext'
|
|
18
|
+
*
|
|
19
|
+
* export default defineConfig({
|
|
20
|
+
* index: {
|
|
21
|
+
* maxDepth: 5,
|
|
22
|
+
* },
|
|
23
|
+
* })
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export const defineConfig = <
|
|
27
|
+
T extends import('./config/service.js').PartialMdContextConfig,
|
|
28
|
+
>(
|
|
29
|
+
config: T,
|
|
30
|
+
): T => config
|