xindex 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/.ai/research/2026-04-10-file-watching.md +79 -0
  2. package/.ai/research/2026-04-10-mcp-output-format.md +129 -0
  3. package/.ai/task/INDEX.md +12 -0
  4. package/.ai/task/done/INDEX.md +3 -0
  5. package/.ai/task/done/task.2026-04-09-local-ai-research-protos.log.md +98 -0
  6. package/.ai/task/done/task.2026-04-09-local-ai-research-protos.md +102 -0
  7. package/.ai/task/task.2026-04-10-cluster-config.log.md +19 -0
  8. package/.ai/task/task.2026-04-10-cluster-config.md +118 -0
  9. package/.ai/task/task.2026-04-10-dir-indexing.log.md +8 -0
  10. package/.ai/task/task.2026-04-10-dir-indexing.md +92 -0
  11. package/.ai/task/task.2026-04-10-line-clustering.log.md +50 -0
  12. package/.ai/task/task.2026-04-10-line-clustering.md +176 -0
  13. package/.ai/task/task.2026-04-10-object-store.log.md +7 -0
  14. package/.ai/task/task.2026-04-10-object-store.md +81 -0
  15. package/.ai/task/task.2026-04-10-search-config.log.md +46 -0
  16. package/.ai/task/task.2026-04-10-search-config.md +274 -0
  17. package/.ai/task/task.2026-04-10-watch-indexing.log.md +32 -0
  18. package/.ai/task/task.2026-04-10-watch-indexing.md +101 -0
  19. package/.ai/task/task.2026-04-10-xindex-mcp.log.md +5 -0
  20. package/.ai/task/task.2026-04-10-xindex-mcp.md +92 -0
  21. package/.ai/task/task.2026-04-10-xindex-mcp.report.md +113 -0
  22. package/.claude/settings.local.json +73 -0
  23. package/.claude/skills/make-hof/SKILL.md +8 -0
  24. package/.claude/skills/make-hof/playbook.md +38 -0
  25. package/.cursor/mcp.json +8 -0
  26. package/.mcp.json +8 -0
  27. package/.xindex.json +22 -0
  28. package/CLAUDE.md +54 -0
  29. package/README.md +206 -0
  30. package/apps/indexApp.ts +31 -0
  31. package/apps/mcpApp.ts +119 -0
  32. package/apps/run.index.ts +19 -0
  33. package/apps/run.mcp.ts +49 -0
  34. package/apps/run.reset.ts +10 -0
  35. package/apps/run.search.ts +21 -0
  36. package/apps/run.watch.ts +44 -0
  37. package/apps/searchApp.ts +9 -0
  38. package/apps/watchApp.ts +53 -0
  39. package/apps/watchFileEventsApp.ts +39 -0
  40. package/bin/xindex-index +2 -0
  41. package/bin/xindex-mcp +2 -0
  42. package/bin/xindex-reset +2 -0
  43. package/bin/xindex-search +2 -0
  44. package/bin/xindex-watch +2 -0
  45. package/componets/IType.ts +1 -0
  46. package/componets/appId.ts +3 -0
  47. package/componets/buildComponents.ts +27 -0
  48. package/componets/config/loadConfig.ts +43 -0
  49. package/componets/config/xindexConfig.ts +4 -0
  50. package/componets/index/contentIndexDriver.ts +39 -0
  51. package/componets/index/formatSearchResults.ts +18 -0
  52. package/componets/index/getIndexStats.ts +11 -0
  53. package/componets/index/handleFileEvent.ts +25 -0
  54. package/componets/index/indexApi.ts +45 -0
  55. package/componets/index/vectraIndex.ts +11 -0
  56. package/componets/index/watcherLock.ts +107 -0
  57. package/componets/keywords/cleanUpKeywords.ts +38 -0
  58. package/componets/keywords/extractKeywords.ts +14 -0
  59. package/componets/keywords/refineKeywords.ts +16 -0
  60. package/componets/llm/embed.ts +18 -0
  61. package/componets/llm/queryLLM.ts +20 -0
  62. package/componets/logger.ts +34 -0
  63. package/componets/walkFiles.ts +51 -0
  64. package/componets/watchFiles.ts +106 -0
  65. package/features/indexContent.ts +16 -0
  66. package/features/removeContent.ts +9 -0
  67. package/features/resetIndex.ts +9 -0
  68. package/features/searchIndex.ts +33 -0
  69. package/package.json +32 -0
  70. package/packages/fun/src/IType.ts +5 -0
  71. package/packages/fun/src/array-finder.ts +55 -0
  72. package/packages/fun/src/array-index.ts +35 -0
  73. package/packages/fun/src/array.ts +112 -0
  74. package/packages/fun/src/assert.ts +5 -0
  75. package/packages/fun/src/asyncRequest.ts +35 -0
  76. package/packages/fun/src/callsites.ts +18 -0
  77. package/packages/fun/src/case-never.ts +9 -0
  78. package/packages/fun/src/casting.ts +41 -0
  79. package/packages/fun/src/collect.ts +13 -0
  80. package/packages/fun/src/concurrency.ts +186 -0
  81. package/packages/fun/src/container.ts +86 -0
  82. package/packages/fun/src/counter.ts +45 -0
  83. package/packages/fun/src/create-map.ts +2 -0
  84. package/packages/fun/src/dedupe.ts +2 -0
  85. package/packages/fun/src/defer.ts +55 -0
  86. package/packages/fun/src/delay.ts +5 -0
  87. package/packages/fun/src/discriminate.ts +34 -0
  88. package/packages/fun/src/enum-values.ts +12 -0
  89. package/packages/fun/src/exponential-backoff.ts +20 -0
  90. package/packages/fun/src/flatten.ts +11 -0
  91. package/packages/fun/src/hash.ts +67 -0
  92. package/packages/fun/src/hash128.ts +6 -0
  93. package/packages/fun/src/hash256.ts +6 -0
  94. package/packages/fun/src/hub.ts +53 -0
  95. package/packages/fun/src/id.ts +10 -0
  96. package/packages/fun/src/interval.ts +76 -0
  97. package/packages/fun/src/is-non-nullable.ts +2 -0
  98. package/packages/fun/src/isIterable.ts +3 -0
  99. package/packages/fun/src/mailbox.ts +13 -0
  100. package/packages/fun/src/map-record.ts +19 -0
  101. package/packages/fun/src/match-collections.ts +57 -0
  102. package/packages/fun/src/match-left-and-right-arrays.ts +78 -0
  103. package/packages/fun/src/mem.ts +26 -0
  104. package/packages/fun/src/memos.ts +28 -0
  105. package/packages/fun/src/normalizeError.ts +25 -0
  106. package/packages/fun/src/nothing.ts +3 -0
  107. package/packages/fun/src/pipe.ts +18 -0
  108. package/packages/fun/src/prettyJson.ts +3 -0
  109. package/packages/fun/src/project.ts +8 -0
  110. package/packages/fun/src/promise.ts +27 -0
  111. package/packages/fun/src/pubsub.ts +128 -0
  112. package/packages/fun/src/randomId.ts +14 -0
  113. package/packages/fun/src/regexp-escape.ts +13 -0
  114. package/packages/fun/src/retry.ts +15 -0
  115. package/packages/fun/src/serial.test.ts +107 -0
  116. package/packages/fun/src/serial.ts +17 -0
  117. package/packages/fun/src/sleep.ts +3 -0
  118. package/packages/fun/src/sort-object.ts +46 -0
  119. package/packages/fun/src/speed-test.ts +56 -0
  120. package/packages/fun/src/tick.ts +37 -0
  121. package/packages/fun/src/time-behavior.ts +50 -0
  122. package/packages/fun/src/time.ts +22 -0
  123. package/packages/fun/src/timedFallback.ts +37 -0
  124. package/packages/fun/src/timer.ts +30 -0
  125. package/packages/fun/src/value.ts +33 -0
  126. package/packages/fun/src/waitForCounter.ts +15 -0
  127. package/packages/streamx/src/batch.ts +23 -0
  128. package/packages/streamx/src/batchTimed.ts +113 -0
  129. package/packages/streamx/src/buffer.ts +72 -0
  130. package/packages/streamx/src/concatenate.ts +33 -0
  131. package/packages/streamx/src/filter.ts +14 -0
  132. package/packages/streamx/src/flat.ts +19 -0
  133. package/packages/streamx/src/flatMap.ts +9 -0
  134. package/packages/streamx/src/from.ts +30 -0
  135. package/packages/streamx/src/index.ts +49 -0
  136. package/packages/streamx/src/interval.ts +58 -0
  137. package/packages/streamx/src/loop.ts +8 -0
  138. package/packages/streamx/src/map.ts +12 -0
  139. package/packages/streamx/src/merge.ts +89 -0
  140. package/packages/streamx/src/nodeReadable.ts +6 -0
  141. package/packages/streamx/src/nodeTransform.ts +9 -0
  142. package/packages/streamx/src/nodeWritable.ts +38 -0
  143. package/packages/streamx/src/objectReader.ts +16 -0
  144. package/packages/streamx/src/polyfill.ts +20 -0
  145. package/packages/streamx/src/reader.ts +38 -0
  146. package/packages/streamx/src/reduce.ts +15 -0
  147. package/packages/streamx/src/scale.ts +93 -0
  148. package/packages/streamx/src/scaleSync.ts +13 -0
  149. package/packages/streamx/src/sequence.ts +7 -0
  150. package/packages/streamx/src/tap.ts +9 -0
  151. package/packages/streamx/src/toArray.ts +9 -0
  152. package/packages/streamx/src/writer.ts +96 -0
  153. package/rnd/hf.ts +14 -0
  154. package/rnd/keywords-compromise.ts +18 -0
  155. package/rnd/keywords-pipeline.ts +79 -0
  156. package/rnd/keywords.ts +38 -0
  157. package/rnd/test-vectra-memory.ts +63 -0
  158. package/rnd/vectra-keywords.ts +95 -0
  159. package/rnd/vectra.ts +50 -0
  160. package/tsconfig.json +14 -0
@@ -0,0 +1,73 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(chmod +x:*)",
5
+ "Bash(./bin/xindex.index)",
6
+ "Bash(./bin/xindex.search)",
7
+ "Bash(npx tsx:*)",
8
+ "Bash(./bin/xindex-index)",
9
+ "Bash(./bin/xindex-search)",
10
+ "Bash(./bin/xindex-index:*)",
11
+ "Bash(./bin/xindex-search build components:*)",
12
+ "Bash(node:*)",
13
+ "Bash(./bin/xindex-search embedding model neural network:*)",
14
+ "Bash(./bin/xindex-search natural language processing keywords:*)",
15
+ "Bash(./bin/xindex-search vector database storage:*)",
16
+ "Bash(./bin/xindex-search file indexing pipeline:*)",
17
+ "Bash(./bin/xindex-search \"embedding model neural network\")",
18
+ "Bash(./bin/xindex-search \"natural language processing keywords\")",
19
+ "Bash(./bin/xindex-search \"vector database storage\")",
20
+ "Bash(./bin/xindex-search \"file indexing pipeline\")",
21
+ "Bash(./bin/xindex-search \"search query results\")",
22
+ "Bash(./bin/xindex-search \"embedding model\")",
23
+ "Bash(./bin/xindex-search \"natural language keywords\")",
24
+ "Bash(time ./bin/xindex-search embedding:*)",
25
+ "Bash(time ./bin/xindex-search \"stream batch processing\")",
26
+ "WebSearch",
27
+ "WebFetch(domain:github.com)",
28
+ "WebFetch(domain:www.npmjs.com)",
29
+ "Bash(npm install:*)",
30
+ "Bash(./bin/xindex-search \"embedding\")",
31
+ "mcp__xindex__xindex_search",
32
+ "mcp__xindex__xindex_index",
33
+ "Bash(grep -r \"IIndexRecord\\\\|IIndexStats\\\\|keywords.*join\\\\|metadata\" /Users/slava/project/xindex/componets/index/*.ts)",
34
+ "mcp__xindex__xindex_reset",
35
+ "Bash(xargs cat:*)",
36
+ "Bash(npx tsc:*)",
37
+ "Bash(npx:*)",
38
+ "Bash(node_modules/.bin/tsc --noEmit)",
39
+ "Bash(./node_modules/.bin/tsc --noEmit)",
40
+ "Bash(grep:*)",
41
+ "Bash(./bin/xindex-reset:*)",
42
+ "Bash(bin/xindex-search hey:*)",
43
+ "Bash(bin/xindex-search \"test\")",
44
+ "Bash(bin/xindex-index .:*)",
45
+ "Bash(bin/xindex-search search index:*)",
46
+ "Bash(bin/xindex-search stream:*)",
47
+ "Bash(bin/xindex-search nonexistent_xyz:*)",
48
+ "Bash(timeout 25 bash -c ':*)",
49
+ "Bash(node_modules/.bin/tsx --version)",
50
+ "Bash(node_modules/.bin/tsx --eval \"import './apps/mcpApp.js'\")",
51
+ "Bash(node_modules/.bin/tsx --eval \"import './apps/mcpApp.ts'\")",
52
+ "Bash(node_modules/.bin/tsx --eval \"import './apps/run.mcp.ts'\")",
53
+ "Bash(node_modules/.bin/tsx --eval \"import './apps/mcpApp.ts'; import './apps/watchApp.ts'; import './apps/indexApp.ts'; import './componets/indexFileContent.ts'\")",
54
+ "Bash(node_modules/.bin/tsx --eval \"import './componets/handleFileEvent.ts'; import './componets/indexFileContent.ts'; import './apps/watchApp.ts'; import './apps/indexApp.ts'\")",
55
+ "Bash(find /Users/slava/project/xindex/.xindex/objects -name \"*.json\" -exec grep -l '\"type\":\"meta\"' {} \\\\;)",
56
+ "Bash(find /Users/slava/project/xindex/.xindex/objects -name \"*.json\" -exec grep -l '\"type\":\"cluster\"' {} \\\\;)",
57
+ "Bash(find /Users/slava/project/xindex/.xindex/objects -name \"*.json\" -exec grep -l '\"type\":\"manifest\"' {} \\\\;)",
58
+ "Bash(read f:*)",
59
+ "Bash(python3 -c ':*)",
60
+ "Bash(wc -l /Users/slava/project/xindex/componets/index/*.ts)",
61
+ "Bash(yarn search:*)",
62
+ "Bash(npm run *)",
63
+ "Skill(with-cursor)",
64
+ "Bash(git mv *)",
65
+ "Bash(./bin/xindex-search \"where is MCP server\")",
66
+ "Bash(xindex-search where *)"
67
+ ]
68
+ },
69
+ "enableAllProjectMcpServers": true,
70
+ "enabledMcpjsonServers": [
71
+ "xindex"
72
+ ]
73
+ }
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: make-hof
3
+ description: Refactors a function/module to the HOF component pattern, or creates a new HOF from a description. Use when converting plain functions to factory + instance pattern.
4
+ argument-hint: "[file or description]"
5
+ ---
6
+ Refactor or create HOF component: $ARGUMENTS
7
+
8
+ Read [playbook.md](playbook.md) for full instructions, then follow them.
@@ -0,0 +1,38 @@
1
+ ## Pattern
2
+
3
+ HOF component = factory function that returns a configured function instance.
4
+
5
+ ```ts
6
+ // 1. Type — named I<Name>, describes the returned function signature
7
+ export type IDoThing = (a: string, b: number) => Promise<string>;
8
+
9
+ // 2. Factory — PascalCase, takes config, returns typed function
10
+ export function DoThing(config: Config): IDoThing {
11
+ return async function doThing(a, b) { /* uses config */ }
12
+ }
13
+
14
+ // Caller constructs their own:
15
+ // const doThing = DoThing(myConfig);
16
+ ```
17
+
18
+ Exports: `IDoThing` (type), `DoThing` (factory). No default instance — callers configure at point of use.
19
+
20
+ ## Steps
21
+
22
+ 1. **Read** the target file (or understand the description if creating new)
23
+ 2. **Identify** what becomes config — values that are currently hardcoded, passed at init time, or vary between use cases
24
+ 3. **Apply pattern**:
25
+ - Extract `type I<Name>` for the inner function signature
26
+ - Wrap in `function <Name>(config): I<Name>` factory
27
+ - Move hardcoded values to factory params
28
+ - Inner function uses closure over factory params
29
+ 4. **Preserve** module-level init (e.g. `await pipeline(...)`) outside the factory — these are singletons
30
+ 5. **Refactor callers** — find all imports of the old function, replace with factory import + local construction (`const fn = Factory(config)`)
31
+
32
+ ## Rules
33
+
34
+ - Keep module-level side effects (model loading, index creation) outside the factory
35
+ - Factory params should be the minimal set that varies — don't over-parameterize
36
+ - Inner function name = camelCase of factory name
37
+ - If the file has multiple related functions, group under one factory only if they share config
38
+ - Use specific types over `string` when possible (e.g. union types for known values)
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "xindex": {
4
+ "command": "xindex-mcp",
5
+ "args": []
6
+ }
7
+ }
8
+ }
package/.mcp.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "xindex": {
4
+ "command": "xindex-mcp",
5
+ "args": []
6
+ }
7
+ }
8
+ }
package/.xindex.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "ignoreKeywords": [
3
+ "import", "export", "const", "function", "return", "async", "await", "type", "string", "number",
4
+ "from", "default", "let", "var",
5
+ "if", "else", "switch", "case", "break", "continue",
6
+ "for", "of", "in",
7
+ "try", "catch", "finally", "throw",
8
+ "new", "delete", "as", "is", "typeof", "instanceof", "void",
9
+ "this", "super",
10
+ "interface", "class", "extends", "implements",
11
+ "public", "private", "protected", "static", "abstract", "readonly",
12
+ "declare", "namespace", "module", "enum",
13
+ "boolean", "any", "unknown", "never",
14
+ "undefined", "null", "object",
15
+ "record", "array", "readonlyarray", "promise",
16
+ "true", "false",
17
+ "src", "packages", "fun", "componets", "streamx", "apps", "run", "rnd", "tsx",
18
+ "utf8", "length", "map", "slice", "push", "join", "resolve", "stringify",
19
+ "json", "settimeout", "path", "readfile"
20
+ ],
21
+ "ignoreFiles": ["rnd/**", ".xindex"]
22
+ }
package/CLAUDE.md ADDED
@@ -0,0 +1,54 @@
1
+ # CLAUDE.md
2
+
3
+ ## Shorthand
4
+
5
+ - **NxM** — N parts × M sub-parts, title + 1 sentence each. Include text diagrams when M ≥ 3. E.g., `3x3` = 3 parts × 3 sub-parts
6
+ - **NxMxD** — same as NxM but D sentences per sub-part. E.g., `3x3x2` = 3×3 with 2 sentences each
7
+ - **Nxx** — N parts, title + 1 sentence each (no sub-parts). E.g., `3xx`
8
+ - **unpack** — 3-5 parts × 3-5 sub-parts with text diagrams
9
+ - **Text diagrams** — ASCII flows, hierarchies, tables. Keep minimal.
10
+ - **research/search/ground** — search the Internet using DuckDuckGo MCP
11
+ - **plan dev / go / dev / implement** — start implementation → triggers Pre-implementation check
12
+ - **recover** — find most recent `task.*.md` in `.ai/task/` (by date+mtime, exclude `*.log.md`/`*.report.md`), summarize state and next steps
13
+ - **pull details / expand / flesh out** — enrich task with full detail while preserving shape (see Detail expansion)
14
+
15
+
16
+ ## Task Management
17
+
18
+ - **Location**: `.ai/task/`. Naming: `task.<yyyy-mm-dd>-<slug>.md` (date = creation date, slug includes roadmap number when applicable)
19
+ - **Structure**: Context (why + links) → Goal → Diagram → Steps (NxM plan). Default: text diagram + 3x3 plan; scale up only if complexity demands it
20
+ - **Diagram ↔ Steps consistency**: must always reflect each other. Update both in the same pass. Inconsistency = bug.
21
+ - **Task kickoff**: user provides notes → create task file + log → notes into Context → draft Goal + structure → ask 3–5 clarifying questions (`[MUST]` = blocker, `[SHOULD]` = design risk) → iterate until MUSTs resolved, SHOULDs answered or defaulted → finalize with diagram + steps
22
+ - **Pre-implementation**: reread task → identify gaps → research (parallel agents for independent questions) → verify diagram ↔ steps → ask user if changes are significant → identify parallel tracks → start
23
+ - **Post-implementation**: recheck task vs actual code. Mark completed steps, note deviations, sync diagram. Drift = bug.
24
+ - **Logs**: `task.<...slug>.log.md` alongside task. Format: `### <date>` + bullets. Log decisions, blockers, outcomes, deviations — sufficient to reconstruct context. Parallel agents get own logs: `task.<...slug>.track-<name>.log.md`; merge outcomes back to main log.
25
+ - **Log splitting**: at ~500+ lines, split into `task.<...slug>.1.log.md`, `*.2.log.md`, etc. Original becomes index. Use bash `sed -n` for extraction.
26
+ - **Reports**: `task.<...slug>.report.md` — consolidates research findings after completion
27
+ - **Section splitting**: large `.md` files → `*.1.*`, `*.2.*` parts. Original becomes index with bullet summaries.
28
+ - **Detail expansion**: reread task → research every step (files, APIs, deps) → expand with concrete details (paths, functions, tables, exact changes) → preserve NxM shape → no info loss → update diagram → keep balanced detail density
29
+ - **Context recovery**: when chat compacts, reread active task's log to recover direction
30
+ - **Completed tasks**: move done tasks (+ their logs, maps, reports) to `.ai/task/done/`. Update `done/INDEX.md` (relative links, newest-first). Root `INDEX.md` lists only active/pending tasks + a link to `done/INDEX.md`.
31
+
32
+ ## Code Patterns
33
+
34
+ - **HOF component** — factory function that returns a configured function instance. Export only the factory (`DoThing`) and type (`IDoThing`). Callers construct their own instance — no default export.
35
+ ```ts
36
+ export type IDoThing = (a: string, b: number) => Promise<string>;
37
+ export function DoThing(config: Config): IDoThing {
38
+ return async function doThing(a, b) { ... }
39
+ }
40
+ // caller: const doThing = DoThing({dep1, dep2});
41
+ ```
42
+ - **HOF dependencies** — when a factory depends on other components, pass them as a destructured object: `DoThing({embed, index}: {embed: IEmbed, index: LocalIndex})`. This makes dependencies explicit and order-independent.
43
+ - **Tagged union** — use `IType<{type: string, ...}>` from `componets/IType.ts` for discriminated unions. Each variant gets a `type` literal, the union is `|`-joined.
44
+ ```ts
45
+ export type IFileEvent =
46
+ | IType<{ type: "index", path: string }>
47
+ | IType<{ type: "remove", path: string }>;
48
+ ```
49
+ - **Logger** — never use `console.log`/`console.error` directly in components or apps. Use `ILogger` from `componets/logger.ts`. Create at the `run.*` entry point level: `Logger(console.log)` for CLI apps, `Logger(console.error)` for MCP (stdout is the protocol channel). Inject as a dependency where needed.
50
+
51
+ ## Git Rules
52
+
53
+ - **No Co-Authored-By** — never add `Co-Authored-By` lines to commit messages
54
+ - **No tool-origin footers** — never add lines like `Made-with: Cursor`, `Generated with …`, or similar branding/attribution footers to commit messages, PR descriptions, or docs unless the user explicitly asks
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # xindex
2
+
3
+ Local semantic code search. Index your codebase, search by meaning — no cloud, no API keys. Also runs as an MCP server so Claude Code (and other MCP clients) can search your repo directly.
4
+
5
+ ## Features
6
+
7
+ - **Local** — everything runs on your machine; embeddings cached on disk
8
+ - **Semantic search** — natural-language queries, not just substring match
9
+ - **MCP server** — plug into Claude Code via `.mcp.json`
10
+ - **Watch mode** — keep the index warm while you code
11
+ - **Gitignore-aware** — respects `.gitignore` + custom ignore rules
12
+ - **Zero config** — works with defaults; `.xindex.json` is optional
13
+
14
+ ## How it fits together
15
+
16
+ ```
17
+ your repo xindex
18
+ ───────── ──────
19
+ *.ts / *.md ──► walk ──► keywords ──► embed ──► .xindex/
20
+ .gitignore (vectra index)
21
+
22
+ CLI / MCP ◄── search ◄── embed query ◄── "question" ┘
23
+ ```
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ git clone <repo-url> xindex
29
+ cd xindex
30
+ yarn install # or npm install
31
+ npm link # makes xindex-* binaries + xindex-mcp available on PATH
32
+ ```
33
+
34
+ Requires Node.js. First run downloads the embedding model (`all-MiniLM-L6-v2`, ~25MB) — after that, fully offline.
35
+
36
+ ## Quick start
37
+
38
+ ```bash
39
+ cd /path/to/your/project
40
+ xindex-index . # build the index
41
+ xindex-search "where is auth handled" # ask a question
42
+ ```
43
+
44
+ Index lives in `./.xindex/` — add it to `.gitignore`.
45
+
46
+ ## CLI
47
+
48
+ All five binaries run from any directory; they index/search the current working directory.
49
+
50
+ ### `xindex-index [paths...]`
51
+ Build or update the index. Defaults to `.` if no paths given.
52
+ ```bash
53
+ xindex-index .
54
+ xindex-index apps features
55
+ ```
56
+
57
+ ### `xindex-search <query...>`
58
+ Search the index. All args are joined into one query. Default limit: 10.
59
+ ```bash
60
+ xindex-search "database migration logic"
61
+ xindex-search file watcher debounce
62
+ ```
63
+
64
+ ### `xindex-watch [paths...]`
65
+ Initial index + filesystem watch for incremental updates. Uses a lock file so only one watcher owns updates at a time. Ctrl+C releases cleanly.
66
+ ```bash
67
+ xindex-watch .
68
+ ```
69
+
70
+ ### `xindex-reset`
71
+ Wipe and recreate the index. Destructive.
72
+ ```bash
73
+ xindex-reset
74
+ ```
75
+
76
+ ### `xindex-mcp`
77
+ Run as an MCP stdio server. Starts a background watcher by default.
78
+ ```bash
79
+ xindex-mcp # with watch
80
+ xindex-mcp --watch-disabled # no watch
81
+ xindex-mcp --watch-dir=./src # watch a specific dir
82
+ ```
83
+
84
+ ## MCP (Claude Code & others)
85
+
86
+ Drop this into `.mcp.json` at your project root:
87
+
88
+ ```json
89
+ {
90
+ "mcpServers": {
91
+ "xindex": {
92
+ "command": "xindex-mcp",
93
+ "args": []
94
+ }
95
+ }
96
+ }
97
+ ```
98
+
99
+ Requires `xindex-mcp` on PATH (`npm link` inside this repo does it). If you'd rather pin to an absolute path, use `/absolute/path/to/bin/xindex-mcp`.
100
+
101
+ ### Tools exposed
102
+
103
+ | Tool | What it does | Input |
104
+ |------|--------------|-------|
105
+ | `xindex_search` | Semantic search | `query: string`, `limit?: number` (default 5, max 100) |
106
+ | `xindex_index` | Index paths | `inputs: string[]` (at least one) |
107
+ | `xindex_reset` | Wipe index (destructive) | — |
108
+
109
+ Note: CLI `xindex-search` defaults to 10 results; MCP `xindex_search` defaults to 5.
110
+
111
+ ### Typical Claude Code flow
112
+
113
+ 1. Commit `.mcp.json` to your repo.
114
+ 2. Open the project in Claude Code — it picks up the xindex MCP server.
115
+ 3. Ask it to call `xindex_index` once with `inputs: ["."]`.
116
+ 4. From then on, it uses `xindex_search` for natural-language lookups.
117
+ 5. Watch mode keeps the index fresh as you edit.
118
+
119
+ ## Configuration
120
+
121
+ ### `.xindex.json` (optional)
122
+
123
+ Place at your project root. Both fields are optional arrays; unknown keys are ignored.
124
+
125
+ ```json
126
+ {
127
+ "ignoreKeywords": ["import", "export", "function", "const"],
128
+ "ignoreFiles": ["dist/**", "node_modules/**", ".xindex"]
129
+ }
130
+ ```
131
+
132
+ - **`ignoreKeywords`** — tokens stripped before embedding (noise words, language keywords, project slang). Keeps search focused on meaningful terms.
133
+ - **`ignoreFiles`** — extra glob patterns excluded during walk/watch, on top of `.gitignore`.
134
+
135
+ ### `.xindex/` folder
136
+
137
+ Created automatically. Contains:
138
+ - `semantic/` — the vectra index (vectors + metadata)
139
+ - `lock.json` — watcher lock (coordinates watch/MCP processes)
140
+
141
+ **Always gitignore it.**
142
+
143
+ ### `.gitignore`
144
+
145
+ Minimum:
146
+ ```
147
+ .xindex
148
+ node_modules/
149
+ ```
150
+
151
+ ## Examples
152
+
153
+ ### Index + search from the terminal
154
+
155
+ ```bash
156
+ cd my-app
157
+ xindex-index .
158
+ xindex-search "rate limiter implementation"
159
+ ```
160
+
161
+ ### Keep the index warm while coding
162
+
163
+ ```bash
164
+ xindex-watch .
165
+ # edit files in another terminal; index updates incrementally
166
+ # Ctrl+C to stop
167
+ ```
168
+
169
+ ### Use from Claude Code via MCP
170
+
171
+ ```bash
172
+ # one-time setup
173
+ cd xindex && npm link
174
+
175
+ # in your project
176
+ echo '{"mcpServers":{"xindex":{"command":"xindex-mcp","args":[]}}}' > .mcp.json
177
+
178
+ # open project in Claude Code — xindex tools are available
179
+ ```
180
+
181
+ ### Run MCP without watching
182
+
183
+ ```bash
184
+ xindex-mcp --watch-disabled
185
+ ```
186
+
187
+ You control when reindexing happens via explicit `xindex_index` calls.
188
+
189
+ ## Project layout
190
+
191
+ ```
192
+ apps/ entry points (run.*.ts) + app composers (IndexApp, SearchApp, McpApp, ...)
193
+ bin/ shebang wrappers invoked by npm/yarn and .mcp.json
194
+ componets/ shared building blocks: config, walk, watch, embed, vectra adapter, logger
195
+ features/ domain operations: indexContent, searchIndex, removeContent, resetIndex
196
+ packages/ small internal libs (streamx, fun)
197
+ .xindex/ runtime data (gitignored)
198
+ .xindex.json optional config
199
+ .mcp.json MCP registration for Claude Code / compatible clients
200
+ ```
201
+
202
+ See [CLAUDE.md](CLAUDE.md) for contributor conventions (HOF pattern, logger rules, task workflow).
203
+
204
+ ## License
205
+
206
+ MIT
@@ -0,0 +1,31 @@
1
+ import {readFile} from "fs/promises";
2
+ import {from} from "../packages/streamx/src/from.js";
3
+ import {map} from "../packages/streamx/src/map.js";
4
+ import {tap} from "../packages/streamx/src/tap.js";
5
+ import {run} from "../packages/streamx/src/index.js";
6
+ import {IWalkFiles} from "../componets/walkFiles.js";
7
+ import {IIndexContent} from "../features/indexContent.js";
8
+ import {IRemoveContent} from "../features/removeContent.js";
9
+ import {ILogger} from "../componets/logger.js";
10
+
11
+ export type IIndexApp = (inputs: string[]) => Promise<void>;
12
+
13
+ export function IndexApp({walkFiles, indexContent, removeContent, log}: {
14
+ walkFiles: IWalkFiles,
15
+ indexContent: IIndexContent,
16
+ removeContent: IRemoveContent,
17
+ log: ILogger,
18
+ }): IIndexApp {
19
+ return async function indexApp(inputs) {
20
+ await run(
21
+ from(walkFiles(inputs))
22
+ .pipe(tap(id => log(`indexing: ${id}`)))
23
+ .pipe(map<string, string>(async (id) => {
24
+ try { await removeContent(id); } catch (e) { log(`remove failed: ${id} — ${(e as any)?.message ?? e}`); }
25
+ const text = await readFile(id, "utf8");
26
+ await indexContent(id, `${text}. ${id}`);
27
+ return id;
28
+ }))
29
+ );
30
+ }
31
+ }
package/apps/mcpApp.ts ADDED
@@ -0,0 +1,119 @@
1
+ import {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import {z} from "zod";
4
+ import {ISearchIndex} from "../features/searchIndex.js";
5
+ import {IIndexApp} from "./indexApp.js";
6
+ import {IGetIndexStats} from "../componets/index/getIndexStats.js";
7
+ import {IResetIndex} from "../features/resetIndex.js";
8
+ import {IWatchFiles} from "../componets/watchFiles.js";
9
+ import {IHandleFileEvent} from "../componets/index/handleFileEvent.js";
10
+ import {ILogger} from "../componets/logger.js";
11
+ import {WatchFileEventsApp} from "./watchFileEventsApp.js";
12
+ import {IWatcherLock} from "../componets/index/watcherLock.js";
13
+ import {IXindexConfig} from "../componets/config/xindexConfig.js";
14
+ import {FormatSearchResults} from "../componets/index/formatSearchResults.js";
15
+
16
+ export type IMcpApp = () => Promise<void>;
17
+
18
+ export type IMcpWatch = {
19
+ watchFiles: IWatchFiles,
20
+ watchDir: string,
21
+ handleFileEvent: IHandleFileEvent,
22
+ watcherLock: IWatcherLock,
23
+ };
24
+
25
+ export function McpApp({
26
+ searchContentIndex, indexApp, getIndexStats, resetIndex, log, watch, config,
27
+ }: {
28
+ searchContentIndex: ISearchIndex,
29
+ indexApp: IIndexApp,
30
+ getIndexStats: IGetIndexStats,
31
+ resetIndex: IResetIndex,
32
+ log: ILogger,
33
+ watch?: IMcpWatch,
34
+ config: IXindexConfig,
35
+ }): IMcpApp {
36
+ return async function mcpApp() {
37
+ const server = new McpServer({name: "xindex", version: "0.1.0"});
38
+
39
+ // --- search ---
40
+
41
+ server.registerTool("xindex_search", {
42
+ title: "Search codebase",
43
+ description: "Semantic search over indexed codebase files. Returns scored results with file paths and keywords.",
44
+ inputSchema: z.object({
45
+ query: z.string()
46
+ .describe("Natural language search query"),
47
+ limit: z.number().int().min(1).max(100).default(5)
48
+ .describe("Max results to return, 5 by default, 100 max"),
49
+ }),
50
+ annotations: {readOnlyHint: true},
51
+ }, async ({query, limit}) => {
52
+ try {
53
+ const format = FormatSearchResults();
54
+ const results = await searchContentIndex(query, limit);
55
+ const text = await format(query, results);
56
+ return {content: [{type: "text" as const, text}]};
57
+ } catch (e) {
58
+ const msg = e instanceof Error ? e.message : String(e);
59
+ log("xindex_search error:", msg);
60
+ return {content: [{type: "text" as const, text: msg}], isError: true};
61
+ }
62
+ });
63
+
64
+ // --- index ---
65
+
66
+ server.registerTool("xindex_index", {
67
+ title: "Index files or directories",
68
+ description: "Index files or directories for semantic search. " +
69
+ "Accepts file paths or directory paths. Directories are walked recursively, respecting .gitignore.",
70
+ inputSchema: z.object({
71
+ inputs: z.array(z.string()).min(1)
72
+ .describe("File or directory paths to index"),
73
+ }),
74
+ }, async ({inputs}) => {
75
+ try {
76
+ await indexApp(inputs);
77
+ const stats = await getIndexStats();
78
+ return {content: [{
79
+ type: "text" as const,
80
+ text: `Indexed successfully. Total files: ${stats.indexedAmount}`,
81
+ }]};
82
+ } catch (e) {
83
+ const msg = e instanceof Error ? e.message : String(e);
84
+ log("xindex_index error:", msg);
85
+ return {content: [{type: "text" as const, text: msg}], isError: true};
86
+ }
87
+ });
88
+
89
+ // --- reset ---
90
+
91
+ server.registerTool("xindex_reset", {
92
+ title: "Reset index",
93
+ description: "Delete and recreate the search index. " +
94
+ "Always ask the user for explicit confirmation before running this tool.",
95
+ inputSchema: z.object({}),
96
+ annotations: {destructiveHint: true},
97
+ }, async () => {
98
+ try {
99
+ await resetIndex();
100
+ return {content: [{type: "text" as const, text: "Index reset successfully"}]};
101
+ } catch (e) {
102
+ const msg = e instanceof Error ? e.message : String(e);
103
+ log("xindex_reset error:", msg);
104
+ return {content: [{type: "text" as const, text: msg}], isError: true};
105
+ }
106
+ });
107
+
108
+ // --- start ---
109
+
110
+ const transport = new StdioServerTransport();
111
+ await server.connect(transport);
112
+ log("xindex MCP server running on stdio");
113
+
114
+ if (watch) {
115
+ const startWatch = WatchFileEventsApp({...watch, log});
116
+ startWatch();
117
+ }
118
+ };
119
+ }
@@ -0,0 +1,19 @@
1
+ import {BuildComponents} from "../componets/buildComponents.js";
2
+ import {BufferedLoggerToStdOut} from "../componets/logger.js";
3
+ import {WalkFiles} from "../componets/walkFiles.js";
4
+ import {IndexApp} from "./indexApp.js";
5
+ import {AppId} from "../componets/appId.js";
6
+
7
+ const appId = AppId();
8
+ const cwd = process.cwd();
9
+ const log = BufferedLoggerToStdOut();
10
+ const {indexContent, removeContent, getIndexStats, config} = await BuildComponents({log});
11
+ const walkFiles = WalkFiles({cwd, log, ignoreFiles: config.ignoreFiles});
12
+ const indexApp = IndexApp({walkFiles, indexContent, removeContent, log});
13
+
14
+ const inputs = process.argv.slice(2);
15
+ if (!inputs.length) inputs.push(".");
16
+
17
+ log(`[${appId}] started, indexing: ${inputs.join(", ")}`);
18
+ await indexApp(inputs);
19
+ log(`[${appId}] done:`, await getIndexStats());
@@ -0,0 +1,49 @@
1
+ import {BuildComponents} from "../componets/buildComponents.js";
2
+ import {HandleFileEvent} from "../componets/index/handleFileEvent.js";
3
+ import {BufferedLoggerToStdErr} from "../componets/logger.js";
4
+ import {WalkFiles} from "../componets/walkFiles.js";
5
+ import {WatchFiles} from "../componets/watchFiles.js";
6
+ import {WatcherLock} from "../componets/index/watcherLock.js";
7
+ import {IndexApp} from "./indexApp.js";
8
+ import {McpApp} from "./mcpApp.js";
9
+ import {join} from "path";
10
+ import {AppId} from "../componets/appId.js";
11
+
12
+ const args = process.argv.slice(2);
13
+ const watchDirArg = args.find(a => a.startsWith("--watch-dir="));
14
+ const watchDisabled = args.includes("--watch-disabled");
15
+
16
+ const cwd = process.cwd();
17
+ const log = BufferedLoggerToStdErr();
18
+ const {indexContent, removeContent, getIndexStats, searchContentIndex, resetIndex, config} = await BuildComponents({log});
19
+ const walkFiles = WalkFiles({cwd, log, ignoreFiles: config.ignoreFiles});
20
+ const indexApp = IndexApp({walkFiles, indexContent, removeContent, log});
21
+
22
+ const appId = AppId();
23
+ const watcherLock = WatcherLock({
24
+ lockPath: join(cwd, ".xindex", "lock.json"),
25
+ appId,
26
+ log,
27
+ });
28
+
29
+ const watch = watchDisabled ? undefined : {
30
+ watchFiles: WatchFiles({cwd, log, ignoreFiles: config.ignoreFiles}),
31
+ watchDir: watchDirArg ? watchDirArg.split("=")[1] : ".",
32
+ handleFileEvent: HandleFileEvent({indexContent, removeContent, log}),
33
+ watcherLock,
34
+ };
35
+
36
+ process.on("SIGINT", async () => {
37
+ log(`[${appId}] shutting down — stopping heartbeat...`);
38
+ watcherLock.stopHeartbeat();
39
+ log(`[${appId}] releasing lock...`);
40
+ await watcherLock.release();
41
+ log(`[${appId}] waiting 7s for another watcher to take over...`);
42
+ await new Promise(r => setTimeout(r, 7000));
43
+ log(`[${appId}] exiting`);
44
+ process.exit(0);
45
+ });
46
+
47
+ log(`[${appId}] started`);
48
+ const mcpApp = McpApp({searchContentIndex, indexApp, getIndexStats, resetIndex, log, watch, config});
49
+ await mcpApp();
@@ -0,0 +1,10 @@
1
+ import {BuildComponents} from "../componets/buildComponents.js";
2
+ import {BufferedLoggerToStdOut} from "../componets/logger.js";
3
+ import {AppId} from "../componets/appId.js";
4
+
5
+ const appId = AppId();
6
+ const log = BufferedLoggerToStdOut();
7
+ const {resetIndex} = await BuildComponents({log});
8
+ log(`[${appId}] resetting index...`);
9
+ await resetIndex();
10
+ log(`[${appId}] done`);