xindex 1.0.2 → 1.0.4
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/.xindex.json +2 -1
- package/CLAUDE.md +1 -0
- package/README.md +73 -0
- package/apps/indexApp.ts +9 -8
- package/apps/mcpApp.ts +8 -8
- package/apps/run.index.ts +2 -2
- package/apps/run.mcp.ts +6 -4
- package/apps/run.search.ts +1 -1
- package/apps/run.watch.ts +3 -3
- package/apps/searchApp.ts +5 -3
- package/apps/watchApp.ts +16 -8
- package/apps/watchFileEventsApp.ts +14 -4
- package/componets/buildComponents.ts +25 -9
- package/componets/config/DEFAULT_LOCATE_BATCH_SIZE.ts +1 -0
- package/componets/config/INDEXING_BATCH_SIZE.ts +1 -0
- package/componets/config/WATCH_FLUSH_MS.ts +1 -0
- package/componets/config/loadConfig.ts +10 -1
- package/componets/config/xindexConfig.ts +2 -0
- package/componets/ignore/loadIgnoreChain.ts +40 -0
- package/componets/index/contentIndexDriver.ts +7 -5
- package/componets/index/documentContentIndexDriver.ts +126 -0
- package/componets/index/documentIndex.ts +26 -0
- package/componets/index/formatSearchResults.ts +16 -2
- package/componets/index/handleFileEvent.ts +48 -3
- package/componets/index/indexApi.ts +39 -11
- package/componets/locate/bm25.ts +50 -0
- package/componets/locate/inMemoryIndex.ts +48 -0
- package/componets/locate/locateInFile.ts +148 -0
- package/componets/locate/windowsOf.ts +29 -0
- package/componets/watchFiles.ts +5 -16
- package/features/indexContent.ts +12 -5
- package/features/removeContent.ts +3 -3
- package/features/searchIndex.ts +22 -5
- package/package.json +15 -2
- package/packages/streamx/src/batchTimed.ts +1 -1
- package/packages/streamx/src/buffer.ts +1 -1
- package/packages/streamx/src/defer.ts +55 -0
- package/packages/streamx/src/interval.ts +1 -1
- package/packages/streamx/src/merge.ts +1 -1
- package/packages/streamx/src/nodeWritable.ts +1 -1
- package/packages/streamx/src/scale.ts +2 -2
- package/packages/streamx/src/writer.ts +1 -1
- package/.ai/research/.gitkeep +0 -0
- package/.ai/task/.gitkeep +0 -0
- package/.claude/settings.local.json +0 -73
- package/.claude/skills/make-hof/SKILL.md +0 -8
- package/.claude/skills/make-hof/playbook.md +0 -38
- package/.cursor/mcp.json +0 -8
- package/media/MEDIUM.md +0 -139
- package/media/SOCIAL.md +0 -102
- package/rnd/hf.ts +0 -14
- package/rnd/keywords-compromise.ts +0 -18
- package/rnd/keywords-pipeline.ts +0 -79
- package/rnd/keywords.ts +0 -38
- package/rnd/test-vectra-memory.ts +0 -63
- package/rnd/vectra-keywords.ts +0 -95
- package/rnd/vectra.ts +0 -50
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {IndexCommandType, IIndexApi} from "../componets/index/indexApi.js";
|
|
2
2
|
|
|
3
|
-
export type IRemoveContent = (
|
|
3
|
+
export type IRemoveContent = (ids: string[]) => Promise<void>;
|
|
4
4
|
|
|
5
5
|
export function RemoveContent({indexApi}: {indexApi: IIndexApi}): IRemoveContent {
|
|
6
|
-
return async function removeContent(
|
|
7
|
-
await indexApi({type: IndexCommandType.delete,
|
|
6
|
+
return async function removeContent(ids) {
|
|
7
|
+
await indexApi({type: IndexCommandType.delete, ids});
|
|
8
8
|
}
|
|
9
9
|
}
|
package/features/searchIndex.ts
CHANGED
|
@@ -2,26 +2,37 @@ import {LocalIndex} from "vectra";
|
|
|
2
2
|
import {IEmbed} from "../componets/llm/embed.js";
|
|
3
3
|
import {IExtractKeywords} from "../componets/keywords/extractKeywords.js";
|
|
4
4
|
import {ICleanUpKeywords} from "../componets/keywords/cleanUpKeywords.js";
|
|
5
|
+
import {ILocateInFile} from "../componets/locate/locateInFile.js";
|
|
5
6
|
|
|
6
|
-
export type IIndexRecord = {
|
|
7
|
+
export type IIndexRecord = {
|
|
8
|
+
score: number;
|
|
9
|
+
id: string;
|
|
10
|
+
keywords?: string;
|
|
11
|
+
startPos?: number;
|
|
12
|
+
endPos?: number;
|
|
13
|
+
snippet?: string;
|
|
14
|
+
startLine?: number;
|
|
15
|
+
endLine?: number;
|
|
16
|
+
};
|
|
7
17
|
|
|
8
18
|
export type ISearchIndex = (query: string, limit: number) => Promise<IIndexRecord[]>;
|
|
9
19
|
|
|
10
|
-
export function SearchIndex({extractKeywords, cleanUpKeywords, embed, index, scoreThreshold = 0.05}: {
|
|
20
|
+
export function SearchIndex({extractKeywords, cleanUpKeywords, embed, index, locateInFile, scoreThreshold = 0.05}: {
|
|
11
21
|
extractKeywords: IExtractKeywords,
|
|
12
22
|
cleanUpKeywords: ICleanUpKeywords,
|
|
13
23
|
embed: IEmbed,
|
|
14
24
|
index: LocalIndex,
|
|
15
|
-
|
|
25
|
+
locateInFile: ILocateInFile,
|
|
26
|
+
scoreThreshold: number
|
|
16
27
|
}): ISearchIndex {
|
|
17
28
|
return async function searchContentIndex(query, limit) {
|
|
18
29
|
const keywords = cleanUpKeywords(extractKeywords(query));
|
|
19
30
|
const searchText = keywords.length > 0 ? keywords.join(", ") : query;
|
|
20
31
|
const vector = await embed(searchText);
|
|
21
32
|
|
|
22
|
-
const results = await index.queryItems(vector, searchText, limit);
|
|
33
|
+
const results = await index.queryItems(vector, searchText, limit * 2);
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
const candidates = results
|
|
25
36
|
.filter(r => r.score >= scoreThreshold)
|
|
26
37
|
.sort((a, b) => b.score - a.score)
|
|
27
38
|
.map(r => ({
|
|
@@ -29,5 +40,11 @@ export function SearchIndex({extractKeywords, cleanUpKeywords, embed, index, sco
|
|
|
29
40
|
id: r.item.id,
|
|
30
41
|
keywords: typeof r.item.metadata?.keywords === "string" ? r.item.metadata.keywords : "",
|
|
31
42
|
}));
|
|
43
|
+
|
|
44
|
+
if (!locateInFile || candidates.length === 0) {
|
|
45
|
+
return candidates;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return locateInFile(query, vector, candidates, limit);
|
|
32
49
|
}
|
|
33
50
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xindex",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Local semantic code search — index codebase, search by meaning or keywords",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "xindex.ts",
|
|
@@ -16,9 +16,19 @@
|
|
|
16
16
|
"search": "tsx apps/run.search.ts",
|
|
17
17
|
"reset": "tsx apps/run.reset.ts",
|
|
18
18
|
"mcp": "tsx apps/run.mcp.ts",
|
|
19
|
-
"watch": "tsx apps/run.watch.ts"
|
|
19
|
+
"watch": "tsx apps/run.watch.ts",
|
|
20
|
+
"test.compilation": "npx -y tsc --ignoreConfig --noEmit --target ES2022 --module ESNext --moduleResolution bundler --esModuleInterop --skipLibCheck --strict false $(git ls-files '*.ts')"
|
|
20
21
|
},
|
|
21
22
|
"private": false,
|
|
23
|
+
"keywords": [
|
|
24
|
+
"semantic-search",
|
|
25
|
+
"code-search",
|
|
26
|
+
"mcp",
|
|
27
|
+
"claude-code",
|
|
28
|
+
"embeddings",
|
|
29
|
+
"local-first",
|
|
30
|
+
"developer-tools"
|
|
31
|
+
],
|
|
22
32
|
"dependencies": {
|
|
23
33
|
"@huggingface/transformers": "^4.0.1",
|
|
24
34
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -28,5 +38,8 @@
|
|
|
28
38
|
"tsx": "^4.21.0",
|
|
29
39
|
"vectra": "^0.14.0",
|
|
30
40
|
"zod": "^4.3.6"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"typescript": "^6.0.3"
|
|
31
44
|
}
|
|
32
45
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StreamXMapper } from './index';
|
|
2
2
|
import { IRead, read } from './reader';
|
|
3
3
|
import { IWriter, Writer } from './writer';
|
|
4
|
-
import { syncTick } from '
|
|
4
|
+
import { syncTick } from '../../fun/src/tick';
|
|
5
5
|
import { clearTimeout } from 'timers';
|
|
6
6
|
|
|
7
7
|
type Milliseconds = number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StreamX, StreamXMapper } from './index';
|
|
2
2
|
import { IRead, read } from './reader';
|
|
3
3
|
import { IWriter, Writer } from './writer';
|
|
4
|
-
import { syncTick } from '
|
|
4
|
+
import { syncTick } from '../../fun/src/tick';
|
|
5
5
|
|
|
6
6
|
export function buffer<Input>(size: number): StreamXMapper<Input, Input> {
|
|
7
7
|
let outputBuffer: IWriter<Input>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export type IDefer<ResultType = void> = {
|
|
2
|
+
promise: Promise<ResultType>;
|
|
3
|
+
resolved: boolean;
|
|
4
|
+
rejected: boolean;
|
|
5
|
+
pending: boolean;
|
|
6
|
+
resolve(value: ResultType | PromiseLike<ResultType>): void;
|
|
7
|
+
reject(reason?: unknown): void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function Defer<ResultType = void>(): IDefer<ResultType> {
|
|
11
|
+
const deferred = {
|
|
12
|
+
rejected: false,
|
|
13
|
+
resolved: false,
|
|
14
|
+
pending: true,
|
|
15
|
+
} as IDefer<ResultType>;
|
|
16
|
+
|
|
17
|
+
deferred.promise = new Promise<ResultType>((resolve, reject) => {
|
|
18
|
+
deferred.resolve = (data: any) => {
|
|
19
|
+
if (deferred.resolved || deferred.rejected) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
deferred.pending = false;
|
|
24
|
+
deferred.resolved = true;
|
|
25
|
+
resolve(data);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
deferred.reject = (error: any) => {
|
|
29
|
+
if (deferred.resolved || deferred.rejected) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
deferred.pending = false;
|
|
34
|
+
deferred.rejected = true;
|
|
35
|
+
reject(error);
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return deferred;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type IDataDefer<DataType = any, ResultType = void> = IDefer<ResultType> & {
|
|
43
|
+
data: DataType;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function DeferData<DataType = void, ResultType = void>(
|
|
47
|
+
data: DataType
|
|
48
|
+
): IDataDefer<DataType, ResultType> {
|
|
49
|
+
const deferred = Defer<ResultType>();
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
...deferred,
|
|
53
|
+
data,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StreamX } from './index';
|
|
2
2
|
import { IRead, read } from './reader';
|
|
3
3
|
import { IWriter, Writer } from './writer';
|
|
4
|
-
import { syncTick } from '
|
|
4
|
+
import { syncTick } from '../../fun/src/tick';
|
|
5
5
|
|
|
6
6
|
export function merge<T1, T2, T3, T4>(
|
|
7
7
|
stream1: StreamX<T1>,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Promised, StreamXMapper } from './index';
|
|
2
2
|
import { IRead, read } from './reader';
|
|
3
3
|
import { IWriter, Writer } from './writer';
|
|
4
|
-
import { Concurrency, IPublishToConcurrency } from '
|
|
5
|
-
import { syncTick } from '
|
|
4
|
+
import { Concurrency, IPublishToConcurrency } from '../../fun/src/concurrency';
|
|
5
|
+
import { syncTick } from '../../fun/src/tick';
|
|
6
6
|
|
|
7
7
|
export function scale<Input, Output>(
|
|
8
8
|
max: number,
|
package/.ai/research/.gitkeep
DELETED
|
File without changes
|
package/.ai/task/.gitkeep
DELETED
|
File without changes
|
|
@@ -1,73 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
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.
|
|
@@ -1,38 +0,0 @@
|
|
|
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)
|
package/.cursor/mcp.json
DELETED
package/media/MEDIUM.md
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
# xindex — local semantic code search, with an MCP server built in
|
|
2
|
-
|
|
3
|
-
> Index your repo, search by meaning, no cloud. Works with Claude Code out of the box.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## The problem
|
|
8
|
-
|
|
9
|
-
You land in a 50,000-line codebase someone else wrote. You need "the part that handles auth retries."
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
grep -r "retry" .
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
200 matches. Most unrelated. You try `auth`. 400 matches.
|
|
16
|
-
|
|
17
|
-
You know what you want. You just can't spell it in one string.
|
|
18
|
-
|
|
19
|
-
## Why I built this
|
|
20
|
-
|
|
21
|
-
I wanted grep's ergonomics with a real search engine's semantics — but running entirely on my laptop. Cloud code search solves the meaning problem, but I'm not uploading half-finished private repos to anyone's servers. IDE search works inside files you've already found. Neither answers *"where in this entire repo is X done?"* without the round-trip to the cloud.
|
|
22
|
-
|
|
23
|
-
So I built a small tool that indexes a repo once and lets me ask it questions. Then the MCP ecosystem happened, and the same index became the single most useful thing I could hand to Claude Code.
|
|
24
|
-
|
|
25
|
-
## What xindex is
|
|
26
|
-
|
|
27
|
-
A small CLI that indexes your codebase and lets you search it by natural-language meaning. It also runs as an MCP server, so Claude Code (or any MCP client) can call it directly.
|
|
28
|
-
|
|
29
|
-
- **Local** — nothing leaves your machine
|
|
30
|
-
- **Semantic** — natural-language queries, not substring matches
|
|
31
|
-
- **MCP built in** — four lines of JSON to wire into Claude Code
|
|
32
|
-
- **Watch mode** — keeps the index fresh as you edit
|
|
33
|
-
- **Gitignore-aware** — plus your own rules
|
|
34
|
-
|
|
35
|
-
What it's *not*: not a grep replacement (exact strings — grep wins), not code intelligence (no symbols/refs — your IDE wins). It's a focused semantic index. Nothing more.
|
|
36
|
-
|
|
37
|
-
## 30 seconds, end to end
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
npm i -g xindex
|
|
41
|
-
|
|
42
|
-
cd my-project
|
|
43
|
-
xindex-index .
|
|
44
|
-
xindex-search "rate limiter logic"
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
First run downloads a ~23MB embedding model (one time). Then you get ranked file paths back — enough to jump straight to the right place.
|
|
48
|
-
|
|
49
|
-
## The part I'm actually excited about
|
|
50
|
-
|
|
51
|
-
Semantic search is one of the highest-leverage tools you can give an AI assistant in an unfamiliar repo.
|
|
52
|
-
|
|
53
|
-
Drop this into `.mcp.json`:
|
|
54
|
-
|
|
55
|
-
```json
|
|
56
|
-
{
|
|
57
|
-
"mcpServers": {
|
|
58
|
-
"xindex": {
|
|
59
|
-
"command": "xindex-mcp",
|
|
60
|
-
"args": []
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Open the project in Claude Code. Ask about your codebase. Watch Claude call `xindex_search` and come back with real file references instead of invented ones.
|
|
67
|
-
|
|
68
|
-
The hallucinations drop. The round-trips drop. That alone was worth shipping.
|
|
69
|
-
|
|
70
|
-
## What I'm not pretending
|
|
71
|
-
|
|
72
|
-
v1 of a tool I built for myself:
|
|
73
|
-
|
|
74
|
-
- First run needs network (model download)
|
|
75
|
-
- One repo at a time; no cross-repo search
|
|
76
|
-
- No AST awareness — works on keywords, not structure
|
|
77
|
-
- Quality depends on descriptive names — `x1`, `foo`, `tmp` won't index well
|
|
78
|
-
|
|
79
|
-
If it breaks on your repo, that's the feedback I want most.
|
|
80
|
-
|
|
81
|
-
## Try it
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
npm i -g xindex
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
- **npm**: [npmjs.com/package/xindex](https://www.npmjs.com/package/xindex)
|
|
88
|
-
|
|
89
|
-
What I'd love to hear: does the quality hold up on your repo? What does it fumble? What would make it genuinely useful day-to-day?
|
|
90
|
-
|
|
91
|
-
DM me directly:
|
|
92
|
-
|
|
93
|
-
- **X / Twitter**: [@slavahatnuke](https://x.com/slavahatnuke)
|
|
94
|
-
- **LinkedIn**: [slava-xatnuk](https://www.linkedin.com/in/slava-xatnuk/)
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
<!-- APPENDIX — not part of the post, for screenshots only -->
|
|
99
|
-
|
|
100
|
-
## Screenshot queries (for the post)
|
|
101
|
-
|
|
102
|
-
### Terminal demo (`xindex-search` output)
|
|
103
|
-
|
|
104
|
-
Good candidates — run inside the xindex repo itself to get clean, relatable results:
|
|
105
|
-
|
|
106
|
-
1. `xindex-search "where is the MCP server registered"`
|
|
107
|
-
2. `xindex-search "file watcher debounce"`
|
|
108
|
-
3. `xindex-search "how keywords are extracted"`
|
|
109
|
-
4. `xindex-search "gitignore handling"`
|
|
110
|
-
5. `xindex-search "how is the vector index stored"`
|
|
111
|
-
|
|
112
|
-
Pick the one with the cleanest output. #1 tends to match well because "MCP" is distinctive.
|
|
113
|
-
|
|
114
|
-
### Claude Code + xindex (mid-post screenshot)
|
|
115
|
-
|
|
116
|
-
Open Claude Code in a project with xindex wired into `.mcp.json`. Ask it something where you can visibly see it invoke `xindex_search`:
|
|
117
|
-
|
|
118
|
-
1. *"Where does xindex handle the file watcher lock?"*
|
|
119
|
-
2. *"Show me how the MCP server wires up its tools."*
|
|
120
|
-
3. *"How does indexing decide what to skip?"*
|
|
121
|
-
|
|
122
|
-
The win is the screenshot showing the tool call panel — Claude asking `xindex_search` and getting real paths back. That's the image that sells MCP integration.
|
|
123
|
-
|
|
124
|
-
## Cover image — Gemini prompt
|
|
125
|
-
|
|
126
|
-
Pick one direction:
|
|
127
|
-
|
|
128
|
-
All options: **light background, airy, meaningful, minimal.**
|
|
129
|
-
|
|
130
|
-
**Option A — constellation of meaning:**
|
|
131
|
-
> A minimalist editorial illustration on a soft off-white background. A small cluster of delicate paper-thin file cards floats in the center, connected by fine pastel threads that converge toward one highlighted card in gentle focus. Hints of pale blue, soft coral, and warm sand. Lots of negative space. No text. Flat 2D style with subtle grain. 16:9.
|
|
132
|
-
|
|
133
|
-
**Option B — finding the thread:**
|
|
134
|
-
> A light, airy illustration on a pale cream background. A single thin glowing line weaves through a loose scatter of abstract document shapes and lands on one, softly illuminating it. Muted pastels: sky blue, soft peach, mint. Calm, almost meditative. Generous negative space. No text. Minimal editorial style. 16:9.
|
|
135
|
-
|
|
136
|
-
**Option C — lens on meaning:**
|
|
137
|
-
> A clean, bright illustration on white. A simple line-drawn magnifying glass hovers over a gently organized pattern of small abstract symbols; the symbols inside the lens rearrange into a neat constellation while those outside stay scattered. Warm pastel accents — peach, sage, sky. Thin lines, soft shadows, plenty of whitespace. No text. 16:9.
|
|
138
|
-
|
|
139
|
-
My pick: **B** — "finding the thread" maps directly to what xindex does (one connection through the noise), reads well at thumbnail size, and stays quiet enough not to fight the headline.
|
package/media/SOCIAL.md
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# xindex — social posts & announcements
|
|
2
|
-
|
|
3
|
-
Links:
|
|
4
|
-
- **npm**: https://www.npmjs.com/package/xindex
|
|
5
|
-
- **Medium**: https://medium.com/@slavahatnuke/xindex-local-semantic-code-search-with-an-mcp-server-built-in-4a74c24d62b7
|
|
6
|
-
- **Launch tweet (pinned)**: https://x.com/slavahatnuke/status/2045214244367470721
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## LinkedIn — announcement (main version)
|
|
11
|
-
|
|
12
|
-
> Just shipped **xindex** — a small tool I built to solve a problem I kept hitting: finding "the part that handles X" in unfamiliar codebases.
|
|
13
|
-
>
|
|
14
|
-
> grep matches text. xindex matches meaning. Fully local — your code never leaves your machine.
|
|
15
|
-
>
|
|
16
|
-
> It also runs as an MCP server, which means Claude Code (and any MCP-compatible assistant) can search your repo directly. The hallucinations drop. The round-trips drop. That's the part I'm most excited about.
|
|
17
|
-
>
|
|
18
|
-
> `npm i -g xindex`
|
|
19
|
-
>
|
|
20
|
-
> I wrote up the why, the how, and honest limitations here:
|
|
21
|
-
> 👉 https://medium.com/@slavahatnuke/xindex-local-semantic-code-search-with-an-mcp-server-built-in-4a74c24d62b7
|
|
22
|
-
>
|
|
23
|
-
> Would love feedback from anyone using Claude Code day-to-day — especially what breaks on your repo.
|
|
24
|
-
>
|
|
25
|
-
> #DeveloperTools #MCP #ClaudeCode #AI #OpenSource
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## LinkedIn — shorter variant (2–3 lines)
|
|
30
|
-
|
|
31
|
-
> Shipped **xindex** — local semantic code search for your codebase, with an MCP server built in so Claude Code can use it directly.
|
|
32
|
-
>
|
|
33
|
-
> `npm i -g xindex` · write-up 👉 https://medium.com/@slavahatnuke/xindex-local-semantic-code-search-with-an-mcp-server-built-in-4a74c24d62b7
|
|
34
|
-
>
|
|
35
|
-
> Feedback welcome 🙏
|
|
36
|
-
>
|
|
37
|
-
> #DeveloperTools #MCP #ClaudeCode
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## LinkedIn — longer narrative variant
|
|
42
|
-
|
|
43
|
-
> A few weeks ago I got tired of grepping through a codebase I didn't write, trying to find "the part that handles auth retries." `grep retry` gave me 200 matches. `grep auth` gave me 400. I knew what I wanted — I just couldn't spell it in one string.
|
|
44
|
-
>
|
|
45
|
-
> So I built **xindex**.
|
|
46
|
-
>
|
|
47
|
-
> It's a small tool that indexes a codebase and lets you search it by natural-language meaning. Fully local — nothing leaves your machine. And because it runs as an MCP server, Claude Code (or any MCP-compatible assistant) can call it directly to find relevant files without inventing paths.
|
|
48
|
-
>
|
|
49
|
-
> The assistant-integration part is what I'm most excited about. Semantic search is one of the highest-leverage tools you can hand to an AI working in an unfamiliar repo.
|
|
50
|
-
>
|
|
51
|
-
> `npm i -g xindex`
|
|
52
|
-
>
|
|
53
|
-
> Full write-up — why I built it, how it works, honest limitations:
|
|
54
|
-
> 👉 https://medium.com/@slavahatnuke/xindex-local-semantic-code-search-with-an-mcp-server-built-in-4a74c24d62b7
|
|
55
|
-
>
|
|
56
|
-
> If you use Claude Code day-to-day, I'd love to hear what breaks on your repo.
|
|
57
|
-
>
|
|
58
|
-
> #DeveloperTools #MCP #ClaudeCode #AI #OpenSource
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## X / Twitter — reply to pinned tweet
|
|
63
|
-
|
|
64
|
-
Reply thread under the pinned launch tweet to extend its reach:
|
|
65
|
-
|
|
66
|
-
> Wrote up the full story on Medium — why I built it, how it works, and what it won't do.
|
|
67
|
-
>
|
|
68
|
-
> https://medium.com/@slavahatnuke/xindex-local-semantic-code-search-with-an-mcp-server-built-in-4a74c24d62b7
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## X / Twitter — quote-tweet (24–48h after launch)
|
|
73
|
-
|
|
74
|
-
Quote your own pinned tweet to revive it:
|
|
75
|
-
|
|
76
|
-
> A few days in — some early feedback coming in. If you missed it, here's the write-up:
|
|
77
|
-
>
|
|
78
|
-
> https://medium.com/@slavahatnuke/xindex-local-semantic-code-search-with-an-mcp-server-built-in-4a74c24d62b7
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## Publishing checklist
|
|
83
|
-
|
|
84
|
-
- [ ] Pin launch tweet on X
|
|
85
|
-
- [ ] Reply to pinned tweet with Medium link
|
|
86
|
-
- [ ] Post LinkedIn announcement (main version)
|
|
87
|
-
- [ ] Pin LinkedIn post to profile
|
|
88
|
-
- [ ] Drop Medium link in any relevant Slack / Discord communities you're in
|
|
89
|
-
- [ ] Submit Medium post to a publication (Better Programming / Level Up Coding / ILLUMINATION) if you're a contributor
|
|
90
|
-
- [ ] 48h later: quote-tweet own pinned tweet for a second wave
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## Ongoing content ideas (post-launch)
|
|
95
|
-
|
|
96
|
-
For later, when you have something to say:
|
|
97
|
-
|
|
98
|
-
1. **Demo GIF** — record `xindex-index` + `xindex-search` on a real repo; post as standalone tweet
|
|
99
|
-
2. **"grep vs xindex" side-by-side** — the punchier tweet variant you considered, once you have usage to back it up
|
|
100
|
-
3. **Claude Code screencast** — record Claude invoking `xindex_search` and answering a real question; post on X + LinkedIn
|
|
101
|
-
4. **Lessons / numbers** — after a week: "xindex hit N installs, here's what I learned"
|
|
102
|
-
5. **Feature posts** — as you add capabilities, short posts on each
|
package/rnd/hf.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { pipeline } from "@huggingface/transformers";
|
|
2
|
-
|
|
3
|
-
const generator = await pipeline(
|
|
4
|
-
"text-generation",
|
|
5
|
-
"HuggingFaceTB/SmolLM2-135M-Instruct"
|
|
6
|
-
);
|
|
7
|
-
|
|
8
|
-
const messages = [
|
|
9
|
-
{ role: "system", content: "You are a helpful assistant." },
|
|
10
|
-
{ role: "user", content: "Who is Microsoft?" },
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
const output = await generator(messages, { max_new_tokens: 64 });
|
|
14
|
-
console.log(output[0].generated_text.at(-1).content);
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import nlp from "compromise";
|
|
2
|
-
import { readFile } from "fs/promises";
|
|
3
|
-
|
|
4
|
-
const filePath = process.argv[2];
|
|
5
|
-
if (!filePath) {
|
|
6
|
-
console.error("Usage: npx tsx keywords-compromise.ts <file>");
|
|
7
|
-
process.exit(1);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const text = await readFile(filePath, "utf8");
|
|
11
|
-
const doc = nlp(text);
|
|
12
|
-
|
|
13
|
-
console.log(`Keywords from: ${filePath}\n`);
|
|
14
|
-
console.log("Topics:", doc.topics().out("array"));
|
|
15
|
-
console.log("\nNouns:", doc.nouns().out("array"));
|
|
16
|
-
console.log("\nVerbs:", doc.verbs().out("array"));
|
|
17
|
-
console.log("\nPeople:", doc.people().out("array"));
|
|
18
|
-
console.log("\nOrganizations:", doc.organizations().out("array"));
|