xindex 1.0.25 → 1.0.27

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/README.md CHANGED
@@ -144,7 +144,7 @@ Both skills assume the `xindex` MCP server is registered (see the section above)
144
144
 
145
145
  ## CLI reference
146
146
 
147
- All five binaries run from any directory; they index/search the current working directory.
147
+ All six binaries run from any directory; they index/search the current working directory.
148
148
 
149
149
  ### `xindex-index [paths...]`
150
150
 
@@ -157,7 +157,7 @@ xindex-index apps features
157
157
 
158
158
  ### `xindex-search <query...>`
159
159
 
160
- Search the index. Args are joined into one query. Default limit: 7.
160
+ Search the index. Args are joined into one query. Default limit: 7 (capped by `searchMaxLimit`, default 12).
161
161
 
162
162
  ```bash
163
163
  xindex-search "database migration logic"
@@ -182,13 +182,22 @@ Wipe and recreate the index. Destructive.
182
182
  xindex-reset
183
183
  ```
184
184
 
185
+ ### `xindex-reindex [paths...]`
186
+
187
+ Reset the index, then build it again. Same as `xindex-reset` followed by `xindex-index`. Defaults to `.`.
188
+
189
+ ```bash
190
+ xindex-reindex
191
+ xindex-reindex apps features
192
+ ```
193
+
185
194
  ### `xindex-mcp`
186
195
 
187
196
  MCP stdio server; background watcher by default. Flags: `--watch-disabled`, `--watch-dir=./src` — rare use cases; defaults are fine for most setups.
188
197
 
189
198
  ## MCP tools
190
199
 
191
- - **`xindex_search`** — semantic search. `query: string`, `limit?: number` (default 7, max 50).
200
+ - **`xindex_search`** — semantic search. `query: string`, `limit?: number` (default 7, capped at `searchMaxLimit` from `.xindex.json`, default 12).
192
201
  - **`xindex_index`** — index paths. `inputs: string[]` (at least one).
193
202
  - **`xindex_reset`** — wipe index (destructive). No input.
194
203
 
@@ -201,6 +210,8 @@ Project-root file. All fields optional; unknown keys ignored; missing/empty →
201
210
  - **`ignoreKeywords`** — `string[]`, default `[]`. Tokens stripped before embedding — add project slang/boilerplate polluting results. Entries ≤1 char warn.
202
211
  - **`ignoreFiles`** — `string[]`, default `['.xindex', 'node_modules']`. Extra globs excluded during walk/watch, on top of `.gitignore`. Setting this **replaces** the default — re-include `.xindex` and `node_modules` if you still want them skipped.
203
212
  - **`maxLines`** — `number`, default `30`. Lines per chunk — tune if chunks feel over/under-sized.
213
+ - **`searchDefaultLimit`** — `number`, default `7`. Results per search when no `limit` is passed.
214
+ - **`searchMaxLimit`** — `number`, default `12`. Hard cap on `limit` (MCP and CLI); values above this are clamped. `searchDefaultLimit` cannot exceed this.
204
215
  - **`maxFileBytes`** — `number`, default `50000`. Skip files over this (50 KB) — raise to index larger generated files.
205
216
  - **`followSymlinks`** — `boolean`, default `false`. When `false`, symlinks are skipped with a log line. `true` follows them (cycles broken via `realpath` dedup).
206
217
 
@@ -211,6 +222,8 @@ Full example with every option (copy-paste and trim what you don't need):
211
222
  "ignoreKeywords": ["import", "export", "function", "const"],
212
223
  "ignoreFiles": [".xindex", "node_modules", "dist/**"],
213
224
  "maxLines": 30,
225
+ "searchDefaultLimit": 7,
226
+ "searchMaxLimit": 12,
214
227
  "maxFileBytes": 50000,
215
228
  "followSymlinks": false
216
229
  }
package/apps/mcpApp.ts CHANGED
@@ -50,8 +50,9 @@ export function McpApp({
50
50
  inputSchema: z.object({
51
51
  query: z.string()
52
52
  .describe("Natural language search query"),
53
- limit: z.number().int().min(1).max(50).default(config.searchDefaultLimit)
54
- .describe(`Max results to return default ${config.searchDefaultLimit}, max 50)`),
53
+ limit: z.number().int().min(1).default(config.searchDefaultLimit)
54
+ .transform(v => Math.min(v, config.searchMaxLimit))
55
+ .describe(`Max results (default ${config.searchDefaultLimit}, capped at ${config.searchMaxLimit})`),
55
56
  windowLines: z.number().int().min(1).default(config.maxLines).optional()
56
57
  .describe("Optional line-window size for per-file snippet locating"),
57
58
  includePaths: z.array(z.string()).optional()
package/apps/run.mcp.ts CHANGED
@@ -29,7 +29,11 @@ const indexApp = async (inputs: string[]) => {
29
29
  await rawIndexApp(inputs);
30
30
  await flush();
31
31
  };
32
- const search = SearchApp({searchIndex: searchContentIndex, searchDefaultLimit: config.searchDefaultLimit});
32
+ const search = SearchApp({
33
+ searchIndex: searchContentIndex,
34
+ searchDefaultLimit: config.searchDefaultLimit,
35
+ searchMaxLimit: config.searchMaxLimit,
36
+ });
33
37
 
34
38
  const appId = AppId();
35
39
  const watcherLock = WatcherLock({
@@ -0,0 +1,23 @@
1
+ import {BuildComponents} from "../components/buildComponents.js";
2
+ import {BufferedLoggerToStdOut} from "../components/logger.js";
3
+ import {WalkFiles} from "../components/walkFiles.js";
4
+ import {IndexApp} from "./indexApp.js";
5
+ import {AppId} from "../components/appId.js";
6
+ import {INDEXING_COALESCE_MS} from "../components/config/INDEXING_COALESCE_MS.js";
7
+
8
+ const appId = AppId();
9
+ const cwd = process.cwd();
10
+ const log = BufferedLoggerToStdOut();
11
+ const {indexContentBatch, getIndexStats, resetIndex, config, flush} = await BuildComponents({log, indexingCoalesceMs: INDEXING_COALESCE_MS});
12
+ const walkFiles = WalkFiles({cwd, log, ignoreFiles: config.ignoreFiles, followSymlinks: config.followSymlinks});
13
+ const indexApp = IndexApp({walkFiles, indexContent: indexContentBatch, log, maxFileBytes: config.maxFileBytes});
14
+
15
+ const inputs = process.argv.slice(2);
16
+ if (!inputs.length) inputs.push(".");
17
+
18
+ log(`[${appId}] resetting index...`);
19
+ await resetIndex();
20
+ log(`[${appId}] started, indexing: ${inputs.join(", ")}`);
21
+ await indexApp(inputs);
22
+ await flush();
23
+ log(`[${appId}] done:`, await getIndexStats());
@@ -5,7 +5,11 @@ import {FormatSearchResults} from "../components/index/formatSearchResults.js";
5
5
 
6
6
  const log = BufferedLoggerToStdOut();
7
7
  const {searchContentIndex, config} = await BuildComponents({log});
8
- const search = SearchApp({searchIndex: searchContentIndex, searchDefaultLimit: config.searchDefaultLimit});
8
+ const search = SearchApp({
9
+ searchIndex: searchContentIndex,
10
+ searchDefaultLimit: config.searchDefaultLimit,
11
+ searchMaxLimit: config.searchMaxLimit,
12
+ });
9
13
 
10
14
  const query = process.argv.slice(2).join(" ");
11
15
  if (!query) {
package/apps/searchApp.ts CHANGED
@@ -1,14 +1,20 @@
1
+ import {clampSearchLimit, SEARCH_DEFAULT_LIMIT} from "../components/config/searchLimits.js";
1
2
  import {IIndexRecord, ISearchIndex, ISearchIndexOptions} from "../features/searchIndex.js";
2
3
 
3
4
  export type ISearchApp = (query: string, options?: Partial<ISearchIndexOptions>) => Promise<IIndexRecord[]>;
4
5
 
5
- export function SearchApp({searchIndex, searchDefaultLimit = 7}: {
6
+ export function SearchApp({
7
+ searchIndex,
8
+ searchDefaultLimit = SEARCH_DEFAULT_LIMIT,
9
+ searchMaxLimit,
10
+ }: {
6
11
  searchIndex: ISearchIndex;
7
12
  searchDefaultLimit?: number;
13
+ searchMaxLimit: number;
8
14
  }): ISearchApp {
9
15
  return async function search(query, options = {}) {
10
16
  const normalizedOptions: ISearchIndexOptions = {
11
- limit: options.limit ?? searchDefaultLimit,
17
+ limit: clampSearchLimit(options.limit ?? searchDefaultLimit, searchMaxLimit),
12
18
  prefetchMultiplier: options.prefetchMultiplier,
13
19
  scoreThreshold: options.scoreThreshold,
14
20
  locateInFiles: options.locateInFiles,
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env -S npx --yes tsx
2
+ import "../apps/run.reindex.js";
@@ -1,9 +1,9 @@
1
1
  import {readFile} from "fs/promises";
2
+ import {clampSearchLimit, SEARCH_DEFAULT_LIMIT, SEARCH_MAX_LIMIT} from "./searchLimits.js";
2
3
  import {IXindexConfig} from "./xindexConfig.js";
3
4
  import {ILogger} from "../logger.js";
4
5
 
5
6
  const DEFAULT_MAX_LINES = 30;
6
- const DEFAULT_SEARCH_DEFAULT_LIMIT = 7;
7
7
  const DEFAULT_SEARCH_CONCURRENCY = 4;
8
8
  const DEFAULT_MAX_FILE_BYTES = 50_000;
9
9
  const DEFAULT_FOLLOW_SYMLINKS = false;
@@ -13,7 +13,8 @@ const DEFAULTS: IXindexConfig = {
13
13
  ignoreKeywords: [],
14
14
  ignoreFiles: DEFAULT_IGNORE_FILES,
15
15
  maxLines: DEFAULT_MAX_LINES,
16
- searchDefaultLimit: DEFAULT_SEARCH_DEFAULT_LIMIT,
16
+ searchDefaultLimit: SEARCH_DEFAULT_LIMIT,
17
+ searchMaxLimit: SEARCH_MAX_LIMIT,
17
18
  searchConcurrency: DEFAULT_SEARCH_CONCURRENCY,
18
19
  maxFileBytes: DEFAULT_MAX_FILE_BYTES,
19
20
  followSymlinks: DEFAULT_FOLLOW_SYMLINKS,
@@ -45,11 +46,13 @@ export function LoadConfig({configPath, log}: { configPath: string, log: ILogger
45
46
  const toStrings = (v: unknown) => Array.isArray(v) ? v.filter((e): e is string => typeof e === "string") : [];
46
47
  const toNum = (v: unknown, def: number): number => typeof v === "number" ? v : def;
47
48
 
49
+ const searchMaxLimit = clampSearchLimit(toNum(parsed.searchMaxLimit, SEARCH_MAX_LIMIT));
48
50
  const config: IXindexConfig = {
49
51
  ignoreKeywords: toStrings(parsed.ignoreKeywords),
50
52
  ignoreFiles: toStrings(parsed.ignoreFiles),
51
53
  maxLines: toNum(parsed.maxLines, DEFAULT_MAX_LINES),
52
- searchDefaultLimit: Math.max(1, toNum(parsed.searchDefaultLimit, DEFAULT_SEARCH_DEFAULT_LIMIT)),
54
+ searchMaxLimit,
55
+ searchDefaultLimit: clampSearchLimit(toNum(parsed.searchDefaultLimit, SEARCH_DEFAULT_LIMIT), searchMaxLimit),
53
56
  searchConcurrency: Math.max(1, toNum(parsed.searchConcurrency, DEFAULT_SEARCH_CONCURRENCY)),
54
57
  maxFileBytes: toNum(parsed.maxFileBytes, DEFAULT_MAX_FILE_BYTES),
55
58
  followSymlinks: typeof parsed.followSymlinks === "boolean" ? parsed.followSymlinks : DEFAULT_FOLLOW_SYMLINKS,
@@ -0,0 +1,6 @@
1
+ export const SEARCH_DEFAULT_LIMIT = 7;
2
+ export const SEARCH_MAX_LIMIT = 12;
3
+
4
+ export function clampSearchLimit(limit: number, max = SEARCH_MAX_LIMIT): number {
5
+ return Math.min(max, Math.max(1, limit));
6
+ }
@@ -3,6 +3,7 @@ export type IXindexConfig = {
3
3
  ignoreFiles: string[];
4
4
  maxLines: number;
5
5
  searchDefaultLimit: number;
6
+ searchMaxLimit: number;
6
7
  searchConcurrency: number;
7
8
  maxFileBytes: number;
8
9
  followSymlinks: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xindex",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "Local semantic code search — index codebase, search by meaning or keywords",
5
5
  "type": "module",
6
6
  "main": "xindex.ts",
@@ -10,12 +10,14 @@
10
10
  "xindex-search": "bin/xindex-search",
11
11
  "xindex-mcp": "bin/xindex-mcp",
12
12
  "xindex-reset": "bin/xindex-reset",
13
+ "xindex-reindex": "bin/xindex-reindex",
13
14
  "xindex-watch": "bin/xindex-watch"
14
15
  },
15
16
  "scripts": {
16
17
  "index": "tsx apps/run.index.ts",
17
18
  "search": "tsx apps/run.search.ts",
18
19
  "reset": "tsx apps/run.reset.ts",
20
+ "reindex": "tsx apps/run.reindex.ts",
19
21
  "test": "npm run test.functional && npm run test.compilation",
20
22
  "dev.ci": "npm run test && npm run test.npx",
21
23
  "mcp": "tsx apps/run.mcp.ts",