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,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for ignore patterns module
|
|
3
|
+
*
|
|
4
|
+
* Tests verify:
|
|
5
|
+
* - Pattern matching (globs, negation, comments, directory-only)
|
|
6
|
+
* - Precedence (CLI > .mdcontextignore > .gitignore > defaults)
|
|
7
|
+
* - Edge cases (missing files, empty files, invalid patterns)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from 'node:fs/promises'
|
|
11
|
+
import * as os from 'node:os'
|
|
12
|
+
import * as path from 'node:path'
|
|
13
|
+
import { Effect } from 'effect'
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
15
|
+
import {
|
|
16
|
+
createFilterFunction,
|
|
17
|
+
createIgnoreFilter,
|
|
18
|
+
DEFAULT_IGNORE_PATTERNS,
|
|
19
|
+
getChokidarIgnorePatterns,
|
|
20
|
+
shouldIgnore,
|
|
21
|
+
} from './ignore-patterns.js'
|
|
22
|
+
|
|
23
|
+
describe('Ignore Patterns Module', () => {
|
|
24
|
+
let testDir: string
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mdcontext-ignore-test-'))
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
await fs.rm(testDir, { recursive: true, force: true })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// ==========================================================================
|
|
35
|
+
// Pattern Matching
|
|
36
|
+
// ==========================================================================
|
|
37
|
+
|
|
38
|
+
describe('Pattern Matching', () => {
|
|
39
|
+
it('matches glob patterns (*.log)', async () => {
|
|
40
|
+
await fs.writeFile(path.join(testDir, '.gitignore'), '*.log\n')
|
|
41
|
+
|
|
42
|
+
const result = await Effect.runPromise(
|
|
43
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
expect(shouldIgnore('debug.log', result.filter)).toBe(true)
|
|
47
|
+
expect(shouldIgnore('app.log', result.filter)).toBe(true)
|
|
48
|
+
expect(shouldIgnore('app.txt', result.filter)).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('matches deep glob patterns (**/*.tmp)', async () => {
|
|
52
|
+
await fs.writeFile(path.join(testDir, '.gitignore'), '**/*.tmp\n')
|
|
53
|
+
|
|
54
|
+
const result = await Effect.runPromise(
|
|
55
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
expect(shouldIgnore('file.tmp', result.filter)).toBe(true)
|
|
59
|
+
expect(shouldIgnore('src/file.tmp', result.filter)).toBe(true)
|
|
60
|
+
expect(shouldIgnore('src/deep/file.tmp', result.filter)).toBe(true)
|
|
61
|
+
expect(shouldIgnore('src/file.md', result.filter)).toBe(false)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('handles negation patterns (!important.log)', async () => {
|
|
65
|
+
await fs.writeFile(
|
|
66
|
+
path.join(testDir, '.gitignore'),
|
|
67
|
+
'*.log\n!important.log\n',
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const result = await Effect.runPromise(
|
|
71
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
expect(shouldIgnore('debug.log', result.filter)).toBe(true)
|
|
75
|
+
expect(shouldIgnore('important.log', result.filter)).toBe(false)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('ignores comments (# ignore this)', async () => {
|
|
79
|
+
await fs.writeFile(
|
|
80
|
+
path.join(testDir, '.gitignore'),
|
|
81
|
+
'# This is a comment\n*.log\n# Another comment\n',
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const result = await Effect.runPromise(
|
|
85
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
// Comments should not affect pattern matching
|
|
89
|
+
expect(shouldIgnore('debug.log', result.filter)).toBe(true)
|
|
90
|
+
expect(shouldIgnore('# This is a comment', result.filter)).toBe(false)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('matches directory-only patterns (node_modules/)', async () => {
|
|
94
|
+
await fs.writeFile(path.join(testDir, '.gitignore'), 'build/\n')
|
|
95
|
+
|
|
96
|
+
const result = await Effect.runPromise(
|
|
97
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
expect(shouldIgnore('build/output.js', result.filter)).toBe(true)
|
|
101
|
+
expect(shouldIgnore('build', result.filter)).toBe(true)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// ==========================================================================
|
|
106
|
+
// Precedence
|
|
107
|
+
// ==========================================================================
|
|
108
|
+
|
|
109
|
+
describe('Precedence', () => {
|
|
110
|
+
it('CLI patterns override .mdcontextignore', async () => {
|
|
111
|
+
await fs.writeFile(
|
|
112
|
+
path.join(testDir, '.mdcontextignore'),
|
|
113
|
+
'*.md\n!important.md\n',
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
const result = await Effect.runPromise(
|
|
117
|
+
createIgnoreFilter({
|
|
118
|
+
rootPath: testDir,
|
|
119
|
+
cliPatterns: ['important.md'], // CLI says ignore it
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
// CLI takes precedence - important.md should be ignored
|
|
124
|
+
expect(shouldIgnore('important.md', result.filter)).toBe(true)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('.mdcontextignore overrides .gitignore', async () => {
|
|
128
|
+
await fs.writeFile(path.join(testDir, '.gitignore'), '*.md\n')
|
|
129
|
+
await fs.writeFile(
|
|
130
|
+
path.join(testDir, '.mdcontextignore'),
|
|
131
|
+
'!README.md\n', // Allow README.md
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const result = await Effect.runPromise(
|
|
135
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
// .mdcontextignore negation should override .gitignore
|
|
139
|
+
expect(shouldIgnore('README.md', result.filter)).toBe(false)
|
|
140
|
+
expect(shouldIgnore('other.md', result.filter)).toBe(true)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('later rules override earlier (gitignore behavior)', async () => {
|
|
144
|
+
await fs.writeFile(
|
|
145
|
+
path.join(testDir, '.gitignore'),
|
|
146
|
+
'*.md\n!important.md\nimportant.md\n', // Re-ignore important.md
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
const result = await Effect.runPromise(
|
|
150
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
// Last rule wins - important.md should be ignored again
|
|
154
|
+
expect(shouldIgnore('important.md', result.filter)).toBe(true)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// ==========================================================================
|
|
159
|
+
// Edge Cases
|
|
160
|
+
// ==========================================================================
|
|
161
|
+
|
|
162
|
+
describe('Edge Cases', () => {
|
|
163
|
+
it('handles missing .gitignore gracefully', async () => {
|
|
164
|
+
// No .gitignore file exists
|
|
165
|
+
const result = await Effect.runPromise(
|
|
166
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
// Should still work with defaults
|
|
170
|
+
expect(result.filter).toBeDefined()
|
|
171
|
+
expect(result.sources).not.toContain('.gitignore')
|
|
172
|
+
expect(shouldIgnore('node_modules/pkg/file.js', result.filter)).toBe(true)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('handles missing .mdcontextignore gracefully', async () => {
|
|
176
|
+
await fs.writeFile(path.join(testDir, '.gitignore'), '*.log\n')
|
|
177
|
+
// No .mdcontextignore file exists
|
|
178
|
+
|
|
179
|
+
const result = await Effect.runPromise(
|
|
180
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
expect(result.sources).toContain('.gitignore')
|
|
184
|
+
expect(result.sources).not.toContain('.mdcontextignore')
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('handles empty files', async () => {
|
|
188
|
+
await fs.writeFile(path.join(testDir, '.gitignore'), '')
|
|
189
|
+
await fs.writeFile(path.join(testDir, '.mdcontextignore'), ' \n\n')
|
|
190
|
+
|
|
191
|
+
const result = await Effect.runPromise(
|
|
192
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
// Should work with just defaults
|
|
196
|
+
expect(result.filter).toBeDefined()
|
|
197
|
+
expect(result.sources).not.toContain('.gitignore')
|
|
198
|
+
expect(result.sources).not.toContain('.mdcontextignore')
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('handles whitespace-only patterns (skipped)', async () => {
|
|
202
|
+
await fs.writeFile(
|
|
203
|
+
path.join(testDir, '.gitignore'),
|
|
204
|
+
' \n\n*.log\n \n',
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
const result = await Effect.runPromise(
|
|
208
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
// Should skip whitespace lines
|
|
212
|
+
expect(shouldIgnore('debug.log', result.filter)).toBe(true)
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
// ==========================================================================
|
|
217
|
+
// Default Patterns
|
|
218
|
+
// ==========================================================================
|
|
219
|
+
|
|
220
|
+
describe('Default Patterns', () => {
|
|
221
|
+
it('includes node_modules in defaults', () => {
|
|
222
|
+
expect(DEFAULT_IGNORE_PATTERNS).toContain('node_modules')
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('includes .git in defaults', () => {
|
|
226
|
+
expect(DEFAULT_IGNORE_PATTERNS).toContain('.git')
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('includes dist in defaults', () => {
|
|
230
|
+
expect(DEFAULT_IGNORE_PATTERNS).toContain('dist')
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('includes build in defaults', () => {
|
|
234
|
+
expect(DEFAULT_IGNORE_PATTERNS).toContain('build')
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('applies defaults without any ignore files', async () => {
|
|
238
|
+
const result = await Effect.runPromise(
|
|
239
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
expect(shouldIgnore('node_modules/pkg/index.js', result.filter)).toBe(
|
|
243
|
+
true,
|
|
244
|
+
)
|
|
245
|
+
expect(shouldIgnore('dist/bundle.js', result.filter)).toBe(true)
|
|
246
|
+
expect(shouldIgnore('build/output.js', result.filter)).toBe(true)
|
|
247
|
+
expect(shouldIgnore('.git/config', result.filter)).toBe(true)
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
// ==========================================================================
|
|
252
|
+
// Honor Flags
|
|
253
|
+
// ==========================================================================
|
|
254
|
+
|
|
255
|
+
describe('Honor Flags', () => {
|
|
256
|
+
it('respects honorGitignore=false', async () => {
|
|
257
|
+
await fs.writeFile(path.join(testDir, '.gitignore'), '*.secret\n')
|
|
258
|
+
|
|
259
|
+
const result = await Effect.runPromise(
|
|
260
|
+
createIgnoreFilter({
|
|
261
|
+
rootPath: testDir,
|
|
262
|
+
honorGitignore: false,
|
|
263
|
+
}),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
// .gitignore patterns should not be applied
|
|
267
|
+
expect(shouldIgnore('password.secret', result.filter)).toBe(false)
|
|
268
|
+
expect(result.sources).not.toContain('.gitignore')
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('respects honorMdcontextignore=false', async () => {
|
|
272
|
+
await fs.writeFile(path.join(testDir, '.mdcontextignore'), 'drafts/\n')
|
|
273
|
+
|
|
274
|
+
const result = await Effect.runPromise(
|
|
275
|
+
createIgnoreFilter({
|
|
276
|
+
rootPath: testDir,
|
|
277
|
+
honorMdcontextignore: false,
|
|
278
|
+
}),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
// .mdcontextignore patterns should not be applied
|
|
282
|
+
expect(shouldIgnore('drafts/doc.md', result.filter)).toBe(false)
|
|
283
|
+
expect(result.sources).not.toContain('.mdcontextignore')
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
// ==========================================================================
|
|
288
|
+
// Filter Function
|
|
289
|
+
// ==========================================================================
|
|
290
|
+
|
|
291
|
+
describe('createFilterFunction', () => {
|
|
292
|
+
it('creates a function suitable for Array.filter', async () => {
|
|
293
|
+
await fs.writeFile(path.join(testDir, '.gitignore'), '*.log\n')
|
|
294
|
+
|
|
295
|
+
const result = await Effect.runPromise(
|
|
296
|
+
createIgnoreFilter({ rootPath: testDir }),
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
const filterFn = createFilterFunction(result.filter)
|
|
300
|
+
const files = ['app.md', 'debug.log', 'src/index.ts', 'error.log']
|
|
301
|
+
const filtered = files.filter(filterFn)
|
|
302
|
+
|
|
303
|
+
expect(filtered).toEqual(['app.md', 'src/index.ts'])
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
// ==========================================================================
|
|
308
|
+
// Chokidar Patterns
|
|
309
|
+
// ==========================================================================
|
|
310
|
+
|
|
311
|
+
describe('getChokidarIgnorePatterns', () => {
|
|
312
|
+
it('returns patterns suitable for chokidar', async () => {
|
|
313
|
+
await fs.writeFile(path.join(testDir, '.gitignore'), '*.log\n')
|
|
314
|
+
|
|
315
|
+
const patterns = await Effect.runPromise(
|
|
316
|
+
getChokidarIgnorePatterns({ rootPath: testDir }),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
expect(patterns.length).toBeGreaterThan(0)
|
|
320
|
+
// Should include a regex string for dotfiles
|
|
321
|
+
expect(patterns.some((p) => typeof p === 'string')).toBe(true)
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it('includes default patterns', async () => {
|
|
325
|
+
const patterns = await Effect.runPromise(
|
|
326
|
+
getChokidarIgnorePatterns({ rootPath: testDir }),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
// Should have patterns for node_modules, etc.
|
|
330
|
+
expect(patterns.some((p) => p.includes('node_modules'))).toBe(true)
|
|
331
|
+
})
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
// ==========================================================================
|
|
335
|
+
// Pattern Count
|
|
336
|
+
// ==========================================================================
|
|
337
|
+
|
|
338
|
+
describe('Pattern Count', () => {
|
|
339
|
+
it('counts patterns from all sources', async () => {
|
|
340
|
+
await fs.writeFile(path.join(testDir, '.gitignore'), '*.log\n*.tmp\n')
|
|
341
|
+
await fs.writeFile(path.join(testDir, '.mdcontextignore'), 'drafts/\n')
|
|
342
|
+
|
|
343
|
+
const result = await Effect.runPromise(
|
|
344
|
+
createIgnoreFilter({
|
|
345
|
+
rootPath: testDir,
|
|
346
|
+
cliPatterns: ['*.bak'],
|
|
347
|
+
}),
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
// 4 defaults + 2 gitignore + 1 mdcontextignore + 1 CLI = 8
|
|
351
|
+
expect(result.patternCount).toBe(8)
|
|
352
|
+
})
|
|
353
|
+
})
|
|
354
|
+
})
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ignore Pattern Support Module
|
|
3
|
+
*
|
|
4
|
+
* Provides .gitignore and .mdcontextignore support using the battle-tested `ignore` npm package.
|
|
5
|
+
* Implements the following precedence (highest to lowest):
|
|
6
|
+
*
|
|
7
|
+
* 1. CLI --exclude flag
|
|
8
|
+
* 2. MDCONTEXT_INDEX_EXCLUDEPATTERNS env var
|
|
9
|
+
* 3. Config file excludePatterns
|
|
10
|
+
* 4. .mdcontextignore file
|
|
11
|
+
* 5. .gitignore file
|
|
12
|
+
* 6. Built-in defaults: ['node_modules', '.git', 'dist', 'build']
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as fs from 'node:fs/promises'
|
|
16
|
+
import * as path from 'node:path'
|
|
17
|
+
import { Effect } from 'effect'
|
|
18
|
+
import ignore, { type Ignore } from 'ignore'
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Types
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Options for building the ignore filter
|
|
26
|
+
*/
|
|
27
|
+
export interface IgnoreOptions {
|
|
28
|
+
/** Root directory to search for ignore files */
|
|
29
|
+
readonly rootPath: string
|
|
30
|
+
/** CLI/config exclude patterns (highest priority) */
|
|
31
|
+
readonly cliPatterns?: readonly string[] | undefined
|
|
32
|
+
/** Whether to honor .gitignore (default: true) */
|
|
33
|
+
readonly honorGitignore?: boolean | undefined
|
|
34
|
+
/** Whether to honor .mdcontextignore (default: true) */
|
|
35
|
+
readonly honorMdcontextignore?: boolean | undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Result of loading ignore patterns
|
|
40
|
+
*/
|
|
41
|
+
export interface IgnoreFilterResult {
|
|
42
|
+
/** The ignore filter instance */
|
|
43
|
+
readonly filter: Ignore
|
|
44
|
+
/** Source files that were loaded */
|
|
45
|
+
readonly sources: readonly string[]
|
|
46
|
+
/** Total number of patterns loaded */
|
|
47
|
+
readonly patternCount: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// Constants
|
|
52
|
+
// ============================================================================
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Default patterns always applied (lowest priority)
|
|
56
|
+
*/
|
|
57
|
+
export const DEFAULT_IGNORE_PATTERNS: readonly string[] = [
|
|
58
|
+
'node_modules',
|
|
59
|
+
'.git',
|
|
60
|
+
'dist',
|
|
61
|
+
'build',
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// File Loading
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Try to read an ignore file, returning empty string if it doesn't exist or is unreadable.
|
|
70
|
+
*/
|
|
71
|
+
const tryReadIgnoreFile = (filePath: string): Effect.Effect<string, never> =>
|
|
72
|
+
Effect.tryPromise({
|
|
73
|
+
try: () => fs.readFile(filePath, 'utf-8'),
|
|
74
|
+
catch: () => '',
|
|
75
|
+
}).pipe(Effect.catchAll(() => Effect.succeed('')))
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Parse ignore file contents, filtering out empty lines and comments.
|
|
79
|
+
* Returns the number of valid patterns found.
|
|
80
|
+
*/
|
|
81
|
+
const countPatterns = (content: string): number => {
|
|
82
|
+
if (!content.trim()) return 0
|
|
83
|
+
return content.split('\n').filter((line) => {
|
|
84
|
+
const trimmed = line.trim()
|
|
85
|
+
return trimmed.length > 0 && !trimmed.startsWith('#')
|
|
86
|
+
}).length
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// Main API
|
|
91
|
+
// ============================================================================
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create an ignore filter with proper precedence.
|
|
95
|
+
*
|
|
96
|
+
* Loads patterns from (in order, lower priority first):
|
|
97
|
+
* 1. Built-in defaults
|
|
98
|
+
* 2. .gitignore (if exists and honorGitignore is true)
|
|
99
|
+
* 3. .mdcontextignore (if exists and honorMdcontextignore is true)
|
|
100
|
+
* 4. CLI/config patterns (highest priority)
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* const result = yield* createIgnoreFilter({
|
|
105
|
+
* rootPath: '/my/project',
|
|
106
|
+
* cliPatterns: ['*.log', 'temp/'],
|
|
107
|
+
* })
|
|
108
|
+
*
|
|
109
|
+
* // Check if a file should be ignored
|
|
110
|
+
* if (result.filter.ignores('node_modules/package/file.md')) {
|
|
111
|
+
* // Skip this file
|
|
112
|
+
* }
|
|
113
|
+
*
|
|
114
|
+
* // Or filter an array of paths
|
|
115
|
+
* const includedFiles = files.filter(result.filter.createFilter())
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export const createIgnoreFilter = (
|
|
119
|
+
options: IgnoreOptions,
|
|
120
|
+
): Effect.Effect<IgnoreFilterResult, never> =>
|
|
121
|
+
Effect.gen(function* () {
|
|
122
|
+
const {
|
|
123
|
+
rootPath,
|
|
124
|
+
cliPatterns = [],
|
|
125
|
+
honorGitignore = true,
|
|
126
|
+
honorMdcontextignore = true,
|
|
127
|
+
} = options
|
|
128
|
+
|
|
129
|
+
const ig = ignore()
|
|
130
|
+
const sources: string[] = []
|
|
131
|
+
let patternCount = 0
|
|
132
|
+
|
|
133
|
+
// 1. Add defaults (lowest priority)
|
|
134
|
+
ig.add(DEFAULT_IGNORE_PATTERNS as string[])
|
|
135
|
+
patternCount += DEFAULT_IGNORE_PATTERNS.length
|
|
136
|
+
|
|
137
|
+
// 2. Load .gitignore if enabled
|
|
138
|
+
if (honorGitignore) {
|
|
139
|
+
const gitignorePath = path.join(rootPath, '.gitignore')
|
|
140
|
+
const gitignoreContent = yield* tryReadIgnoreFile(gitignorePath)
|
|
141
|
+
if (gitignoreContent.trim()) {
|
|
142
|
+
ig.add(gitignoreContent)
|
|
143
|
+
const count = countPatterns(gitignoreContent)
|
|
144
|
+
patternCount += count
|
|
145
|
+
sources.push('.gitignore')
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 3. Load .mdcontextignore if enabled
|
|
150
|
+
if (honorMdcontextignore) {
|
|
151
|
+
const mdcontextignorePath = path.join(rootPath, '.mdcontextignore')
|
|
152
|
+
const mdcontextignoreContent =
|
|
153
|
+
yield* tryReadIgnoreFile(mdcontextignorePath)
|
|
154
|
+
if (mdcontextignoreContent.trim()) {
|
|
155
|
+
ig.add(mdcontextignoreContent)
|
|
156
|
+
const count = countPatterns(mdcontextignoreContent)
|
|
157
|
+
patternCount += count
|
|
158
|
+
sources.push('.mdcontextignore')
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 4. Add CLI/config patterns (highest priority)
|
|
163
|
+
if (cliPatterns.length > 0) {
|
|
164
|
+
ig.add(cliPatterns as string[])
|
|
165
|
+
patternCount += cliPatterns.length
|
|
166
|
+
sources.push('CLI/config')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
filter: ig,
|
|
171
|
+
sources,
|
|
172
|
+
patternCount,
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check if a path should be ignored.
|
|
178
|
+
*
|
|
179
|
+
* This is a convenience wrapper around createIgnoreFilter for single-path checks.
|
|
180
|
+
* For checking multiple paths, prefer creating the filter once and reusing it.
|
|
181
|
+
*
|
|
182
|
+
* @param relativePath - Path relative to root (e.g., 'src/foo/bar.md')
|
|
183
|
+
* @param filter - The ignore filter instance
|
|
184
|
+
* @returns true if the path should be ignored
|
|
185
|
+
*/
|
|
186
|
+
export const shouldIgnore = (relativePath: string, filter: Ignore): boolean => {
|
|
187
|
+
// The ignore package requires paths without leading slash
|
|
188
|
+
const normalized = relativePath.replace(/^\//, '')
|
|
189
|
+
return filter.ignores(normalized)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Create a filter function suitable for Array.filter().
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* const result = yield* createIgnoreFilter({ rootPath })
|
|
198
|
+
* const filterFn = createFilterFunction(result.filter)
|
|
199
|
+
* const includedFiles = files.filter(filterFn)
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
export const createFilterFunction = (
|
|
203
|
+
filter: Ignore,
|
|
204
|
+
): ((relativePath: string) => boolean) => {
|
|
205
|
+
const innerFilter = filter.createFilter()
|
|
206
|
+
return (relativePath: string) => {
|
|
207
|
+
// The ignore package's createFilter returns true for non-ignored files
|
|
208
|
+
const normalized = relativePath.replace(/^\//, '')
|
|
209
|
+
return innerFilter(normalized)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get ignore patterns as an array of strings for chokidar.
|
|
215
|
+
*
|
|
216
|
+
* Chokidar uses anymatch which accepts globs, so we convert
|
|
217
|
+
* the ignore patterns to glob format.
|
|
218
|
+
*
|
|
219
|
+
* @param options - Ignore options
|
|
220
|
+
* @returns Array of patterns suitable for chokidar's `ignored` option
|
|
221
|
+
*/
|
|
222
|
+
export const getChokidarIgnorePatterns = (
|
|
223
|
+
options: IgnoreOptions,
|
|
224
|
+
): Effect.Effect<string[], never> =>
|
|
225
|
+
Effect.gen(function* () {
|
|
226
|
+
const {
|
|
227
|
+
rootPath,
|
|
228
|
+
cliPatterns = [],
|
|
229
|
+
honorGitignore = true,
|
|
230
|
+
honorMdcontextignore = true,
|
|
231
|
+
} = options
|
|
232
|
+
|
|
233
|
+
const patterns: string[] = []
|
|
234
|
+
|
|
235
|
+
// Always ignore dotfiles (chokidar regex format)
|
|
236
|
+
patterns.push(/(^|[/\\])\./.source)
|
|
237
|
+
|
|
238
|
+
// Add defaults
|
|
239
|
+
for (const p of DEFAULT_IGNORE_PATTERNS) {
|
|
240
|
+
patterns.push(`**/${p}/**`)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Load .gitignore patterns
|
|
244
|
+
if (honorGitignore) {
|
|
245
|
+
const gitignorePath = path.join(rootPath, '.gitignore')
|
|
246
|
+
const content = yield* tryReadIgnoreFile(gitignorePath)
|
|
247
|
+
if (content.trim()) {
|
|
248
|
+
const parsed = parseIgnoreFile(content)
|
|
249
|
+
for (const p of parsed) {
|
|
250
|
+
patterns.push(convertToGlob(p))
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Load .mdcontextignore patterns
|
|
256
|
+
if (honorMdcontextignore) {
|
|
257
|
+
const mdcontextignorePath = path.join(rootPath, '.mdcontextignore')
|
|
258
|
+
const content = yield* tryReadIgnoreFile(mdcontextignorePath)
|
|
259
|
+
if (content.trim()) {
|
|
260
|
+
const parsed = parseIgnoreFile(content)
|
|
261
|
+
for (const p of parsed) {
|
|
262
|
+
patterns.push(convertToGlob(p))
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Add CLI patterns
|
|
268
|
+
for (const p of cliPatterns) {
|
|
269
|
+
patterns.push(convertToGlob(p))
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return patterns
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
// ============================================================================
|
|
276
|
+
// Helpers
|
|
277
|
+
// ============================================================================
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Parse ignore file content into individual patterns
|
|
281
|
+
*/
|
|
282
|
+
const parseIgnoreFile = (content: string): string[] => {
|
|
283
|
+
return content
|
|
284
|
+
.split('\n')
|
|
285
|
+
.map((line) => line.trim())
|
|
286
|
+
.filter((line) => line.length > 0 && !line.startsWith('#'))
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Convert a gitignore pattern to glob format for chokidar
|
|
291
|
+
*/
|
|
292
|
+
const convertToGlob = (pattern: string): string => {
|
|
293
|
+
// Negation patterns - keep as is for now (chokidar handles them differently)
|
|
294
|
+
if (pattern.startsWith('!')) {
|
|
295
|
+
return pattern
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Already a glob pattern
|
|
299
|
+
if (pattern.includes('*') || pattern.includes('/')) {
|
|
300
|
+
return pattern.startsWith('/') ? pattern.slice(1) : `**/${pattern}`
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Simple name - match anywhere
|
|
304
|
+
return `**/${pattern}/**`
|
|
305
|
+
}
|