@maxgfr/codeindex 2.9.0 → 2.11.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 maxgfr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -75,6 +75,32 @@ codeindex callers --repo . # per-symbol caller index
75
75
  codeindex grep 'pattern' --repo .
76
76
  ```
77
77
 
78
+ ## Docker
79
+
80
+ `ghcr.io/maxgfr/codeindex` ships the same zero-dependency bundle (`engine.mjs`
81
+ + `cli.mjs` + the AST grammars) with nothing else inside — just `node` and the
82
+ files above, no `npm install`. Multi-arch (`linux/amd64`, `linux/arm64`),
83
+ built and pushed on release. Mount the repo to index at `/work`:
84
+
85
+ ```sh
86
+ docker run --rm -v "$PWD":/work ghcr.io/maxgfr/codeindex scan --repo /work
87
+ docker run --rm -v "$PWD":/work ghcr.io/maxgfr/codeindex index --repo /work --out /work/.codeindex
88
+ ```
89
+
90
+ Pin by digest in CI or anywhere reproducibility matters, rather than a
91
+ mutable tag:
92
+
93
+ ```sh
94
+ docker run --rm -v "$PWD":/work ghcr.io/maxgfr/codeindex@sha256:... scan --repo /work
95
+ ```
96
+
97
+ Runs as an MCP server over stdio the same way as the npm CLI (see
98
+ **Use as an MCP server** below) — add `-i` so `docker run` keeps stdin open:
99
+
100
+ ```sh
101
+ docker run -i --rm -v "$PWD":/work ghcr.io/maxgfr/codeindex mcp
102
+ ```
103
+
78
104
  ## Search
79
105
 
80
106
  `codeindex search "<query>" --repo .` ranks files with keyless BM25 over symbol
@@ -89,6 +115,55 @@ default; disable with `--no-fuzzy` (CLI) or `fuzzy: false` (library/MCP
89
115
  `SearchOptions.fuzzy`); results carry an additive `fuzzyTerms` field when the
90
116
  fallback contributed.
91
117
 
118
+ ### Semantic search (deterministic static-embedding tier)
119
+
120
+ `codeindex search "<query>" --repo . --semantic` RRF-fuses lexical BM25 with a
121
+ **keyless, byte-deterministic** embedding tier. It uses a *static* embedding
122
+ model (a `token → vector` lookup table, no neural forward pass, no wasm): the
123
+ pure-JS encoder tokenizes → mean-pools → L2-normalizes → int8-quantizes
124
+ (round-half-to-even), and ranking is a **pure integer dot product** — so encode
125
+ and the `embeddings.bin` artifact are byte-identical across builds and platforms.
126
+
127
+ It is **opt-in by asset**: with no model on disk the engine silently stays
128
+ lexical, and `--semantic` without a model returns lexical results on **exit 0**
129
+ (a stderr note only). Models are **never** shipped in the package; a model is
130
+ resolved from `CODEINDEX_EMBED_DIR` or `<repo>/.codeindex/models/`.
131
+
132
+ ```sh
133
+ codeindex embed status --repo . # effective mode + reachability (JSON)
134
+ codeindex embed build --repo . --out .codeindex # write embeddings.bin
135
+ codeindex search "http client retry" --repo . --semantic
136
+ ```
137
+
138
+ `codeindex index` also writes `embeddings.bin` next to `graph.json` when a model
139
+ is present. Fusion reuses the engine's `rrf` helper (k=60); `SCHEMA_VERSION` is
140
+ untouched (a dedicated `EMBED_VERSION` keys the sidecar).
141
+
142
+ #### Three embedding modes (precedence: endpoint > static > none)
143
+
144
+ | mode | trigger | determinism |
145
+ |---|---|---|
146
+ | **none** | no model, no endpoint | — (pure lexical) |
147
+ | **static** | a `model.json` on disk | byte-deterministic (goldens) |
148
+ | **endpoint** | `CODEINDEX_EMBED_ENDPOINT` set | per **image digest** |
149
+
150
+ The **rich (endpoint) tier** points the engine at a local containerized
151
+ embedding server (all-MiniLM-L6-v2). The endpoint's float vectors flow through
152
+ the *same* L2 + int8-quantize + integer-ranking pipeline as the static tier.
153
+ Setting the env var is explicit intent, so it **wins over** a local model; an
154
+ unreachable endpoint degrades to lexical (exit 0), not to the static model.
155
+
156
+ ```sh
157
+ codeindex embed serve # print the docker run one-liner (or --run it)
158
+ docker run -d -p 8756:8756 ghcr.io/maxgfr/codeindex-embed:latest
159
+ # reproducible: pin the digest → ghcr.io/maxgfr/codeindex-embed@sha256:<digest>
160
+ CODEINDEX_EMBED_ENDPOINT=http://localhost:8756 \
161
+ codeindex search "auth token" --repo . --semantic
162
+ ```
163
+
164
+ Full details incl. the HTTP protocol (build your own server):
165
+ [docs/SEMANTIC.md](./docs/SEMANTIC.md).
166
+
92
167
  ## Use as an MCP server
93
168
 
94
169
  `codeindex mcp` (or `node scripts/cli.mjs mcp`) serves the engine over stdio —
package/docs/MIGRATION.md CHANGED
@@ -64,6 +64,35 @@ frequency in the corpus, so any query where every term already matched keeps
64
64
  producing byte-identical output — **no re-pin required**, no action needed
65
65
  from existing consumers. Pass `fuzzy: false` (or CLI `--no-fuzzy`) to opt out.
66
66
 
67
+ ## v2.10.0 — deterministic static-embedding tier (opt-in)
68
+
69
+ New, purely additive, and **opt-in by asset presence** — nothing changes for a
70
+ consumer that does not place a model. `SCHEMA_VERSION` is **untouched**;
71
+ embeddings live in a separate `embeddings.bin` sidecar keyed by a dedicated
72
+ `EMBED_VERSION`, so `graph.json` / `symbols.json` consumers are unaffected and
73
+ **no re-pin is required**.
74
+
75
+ - Activation mirrors the grammar tier: a model asset resolved via
76
+ `CODEINDEX_EMBED_DIR` or `<repo>/.codeindex/models/` (`resolveEmbedModelDir`).
77
+ No model → the engine stays lexical, silently. Models are **never** shipped in
78
+ the npm tarball (`files` unchanged; pack-smoke asserts no model asset).
79
+ - `codeindex index` writes `embeddings.bin` next to `graph.json` **only** when a
80
+ model is present. `codeindex embed {status,build,pull}` and
81
+ `codeindex search --semantic` are the CLI surface; MCP gains `embed_status`
82
+ and a `semantic` property on `search`.
83
+ - Determinism is the point: the pure-JS encoder (fold+lowercase → wordpiece →
84
+ mean-pool → L2-norm → int8 round-half-to-even at a fixed 1/127 scale) and the
85
+ **integer** dot-product ranking make encode and `embeddings.bin` byte-identical
86
+ across builds and platforms — goldens are possible. Fusion with lexical uses
87
+ the existing `rrf` helper (k=60), never a linear score blend.
88
+ - New exports: `EMBED_VERSION`, `resolveEmbedModelDir`, `hasEmbedModel`,
89
+ `loadEmbedModel`, `encode`, `buildEmbeddingIndex`, `serializeEmbeddings`,
90
+ `deserializeEmbeddings`, `searchSemantic`, plus the v2.11-preview
91
+ `embedViaEndpoint`. See `docs/SEMANTIC.md`.
92
+
93
+ `--semantic` without a model degrades to lexical results on **exit 0** (a stderr
94
+ note only) — so wiring it on is safe before an asset exists.
95
+
67
96
  ## Per-skill mapping (what to replace with what)
68
97
 
69
98
  | Skill | Replace | With (engine export) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxgfr/codeindex",
3
- "version": "2.9.0",
3
+ "version": "2.11.0",
4
4
  "description": "Self-contained, deterministic repo-indexing engine: walk + language detection + symbol/import extraction (tree-sitter AST with regex fallback) + import resolution + typed cross-file link-graph + analytics. Ships as a single zero-dependency engine.mjs that consumer tools vendor.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.33.0",
@@ -1,6 +1,6 @@
1
- declare const ENGINE_VERSION = "2.9.0";
1
+ declare const ENGINE_VERSION = "2.11.0";
2
2
  declare const SCHEMA_VERSION = 4;
3
- declare const EXTRACTOR_VERSION = 6;
3
+ declare const EXTRACTOR_VERSION = 7;
4
4
  type FileKind = "code" | "doc" | "config" | "asset" | "other";
5
5
  type EdgeKind = "contains" | "doc-link" | "import" | "call" | "use" | "mention";
6
6
  type Tier = 0 | 1 | 2;
@@ -499,6 +499,76 @@ interface SearchResult {
499
499
  declare function subtokens(raw: string): string[];
500
500
  declare function searchIndex(scan: RepoScan, query: string, opts?: SearchOptions): SearchResult[];
501
501
 
502
+ declare const EMBED_VERSION = 1;
503
+ interface StaticEmbedModel {
504
+ modelId: string;
505
+ dim: number;
506
+ unk: string;
507
+ unkId: number;
508
+ vocabSize: number;
509
+ vocab: Map<string, number>;
510
+ weights: Float64Array;
511
+ }
512
+ declare function resolveEmbedModelDir(repo?: string): string | undefined;
513
+ declare function hasEmbedModel(repo?: string): boolean;
514
+ declare function loadEmbedModel(dir?: string): StaticEmbedModel | undefined;
515
+ declare function resolveEmbedPullUrl(): string | undefined;
516
+
517
+ declare function basicTokenize(text: string): string[];
518
+ declare function wordpiece(word: string, model: StaticEmbedModel): number[];
519
+ declare function tokenize(text: string, model: StaticEmbedModel): number[];
520
+ declare function roundHalfToEven(x: number): number;
521
+ declare function quantize(vec: ArrayLike<number>): Int8Array;
522
+ declare function encode(model: StaticEmbedModel, text: string): Int8Array;
523
+ declare function intDot(a: Int8Array, b: Int8Array): number;
524
+
525
+ interface EmbeddingRecord {
526
+ file: string;
527
+ symbol?: string;
528
+ line?: number;
529
+ vec: Int8Array;
530
+ }
531
+ interface EmbeddingIndex {
532
+ embedVersion: number;
533
+ modelId: string;
534
+ dim: number;
535
+ records: EmbeddingRecord[];
536
+ }
537
+ interface EmbeddingUnit {
538
+ file: string;
539
+ symbol?: string;
540
+ line?: number;
541
+ text: string;
542
+ }
543
+ declare function embeddingUnits(scan: RepoScan): EmbeddingUnit[];
544
+ declare function buildEmbeddingIndex(scan: RepoScan, model: StaticEmbedModel): EmbeddingIndex;
545
+ declare function serializeEmbeddings(index: EmbeddingIndex): Uint8Array;
546
+ declare function deserializeEmbeddings(bytes: Uint8Array): EmbeddingIndex;
547
+
548
+ interface SemanticSearchOptions extends SearchOptions {
549
+ model?: StaticEmbedModel;
550
+ queryVec?: Int8Array;
551
+ rrfK?: number;
552
+ }
553
+ interface SemanticSearchResult extends SearchResult {
554
+ semanticSymbol?: string;
555
+ }
556
+ declare function searchSemantic(scan: RepoScan, query: string, index: EmbeddingIndex | undefined, opts?: SemanticSearchOptions): SemanticSearchResult[];
557
+
558
+ interface EmbedEndpointOptions {
559
+ url?: string;
560
+ timeoutMs?: number;
561
+ headers?: Record<string, string>;
562
+ batchSize?: number;
563
+ }
564
+ declare function resolveEmbedEndpoint(opts?: EmbedEndpointOptions): string | undefined;
565
+ declare function embedEndpointUrl(base: string): string;
566
+ declare function healthzUrl(base: string): string;
567
+ declare function embedViaEndpoint(texts: string[], opts?: EmbedEndpointOptions): Promise<number[][]>;
568
+ declare function probeEndpoint(base: string, opts?: EmbedEndpointOptions): Promise<boolean>;
569
+ declare function encodeQueryViaEndpoint(query: string, opts?: EmbedEndpointOptions): Promise<Int8Array>;
570
+ declare function buildEndpointIndex(scan: RepoScan, opts?: EmbedEndpointOptions): Promise<EmbeddingIndex>;
571
+
502
572
  type RuleSeverity = "error" | "warn";
503
573
  interface ForbiddenEdgeRule {
504
574
  name: string;
@@ -623,4 +693,4 @@ declare function rrf<T>(lists: T[][], keyOf: (item: T) => string, k?: number): M
623
693
 
624
694
  declare function runCli(argv: string[]): Promise<void>;
625
695
 
626
- export { type ArchRule, type BuildIndexOptions, type BuiltinRule, type CallerEntry, type CallerIndex, type CallerIndexOptions, type CallerSite, type ChangeCoupling, type CodeInfo, type CodeSymbol, type CouplingOptions, DEFAULT_MAX_FILES, type DeadSymbol, type DiffFile, type DiffSpec, ENGINE_VERSION, EXTRACTOR_VERSION, type Edge, type EdgeKind, type EditResult, type FileCategory, type FileKind, type FileNode, type FileRecord, type FindSymbolOptions, type ForbiddenEdgeRule, type Graph, type GrepOptions, type Hotspot, type Hunk, type IgnoreRule, type IndexArtifacts, MARKDOWN_EXT, type MarkdownInfo, type MermaidOptions, type ModuleInfo, type ModuleNode, type RawRef, type RenderScipOptions, type RepoMapOptions, type RepoScan, type Resolution, type ResolveContext, type RiskHotspot, type RuleSeverity, type RuleViolation, SCHEMA_VERSION, type ScanOptions, type SearchHit, type SearchOptions, type SearchResult, type ShResult, type SurpriseEdge, type SymbolComplexity, type SymbolIndex, type SymbolMatch, type SymbolReferences, type TestMap, type Tier, type WalkOptions, type WalkResult, type WalkedFile, type WorkspaceInfo, type WorkspaceKind, type WorkspacePackage, allGrammarKeys, applyCentrality, betweennessOf, buildCallerIndex, buildGraph, buildIndexArtifacts, buildModules, buildResolveContext, buildSymbolIndex, byKey, byStr, categorize, changeCoupling, changedSince, checkRules, classify, clip, clipInline, communityOf, compileGlobs, complexityOfSource, computeImportPairs, computeSurprises, computeSymbolRefs, computeTestMap, deleteMemory, detectCommunities, detectWorkspaces, diffFiles, diffHunks, enclosingSymbol, ensureGrammars, escapeRegExp, extToLang, extractAst, extractCode, extractMarkdown, extractSymbols, findDeadCode, findReferences, findSymbol, foldText, gitChurn, grammarKeyForExt, grammarReady, grepRepo, have, headCommit, insertAfterSymbol, insertBeforeSymbol, isCode, isDoc, isGitWorktree, isIgnored, isSurprising, isTestFile, isTestPath, keywords, languageOf, listMemories, pagerankOf, parseGitignore, parseRules, rankHotspots, rankedKeywords, readMemory, readText, renderGraphJson, renderMermaid, renderRepoMap, renderScip, renderSymbolsJson, replaceSymbolBody, resolveBaseRef, resolveCallEdges, resolveDocLink, resolveImport, resolveUniqueSymbol, riskHotspots, rrf, runCli, runMcpServer, scanRepo, searchIndex, sh, sha1, shortHash, slugify, subtokens, symbolComplexity, symbolsOverview, testsForModule, tierForPath, uniqueSymbolDefs, untestedModules, untrackedFiles, walk, writeMemory };
696
+ export { type ArchRule, type BuildIndexOptions, type BuiltinRule, type CallerEntry, type CallerIndex, type CallerIndexOptions, type CallerSite, type ChangeCoupling, type CodeInfo, type CodeSymbol, type CouplingOptions, DEFAULT_MAX_FILES, type DeadSymbol, type DiffFile, type DiffSpec, EMBED_VERSION, ENGINE_VERSION, EXTRACTOR_VERSION, type Edge, type EdgeKind, type EditResult, type EmbedEndpointOptions, type EmbeddingIndex, type EmbeddingRecord, type EmbeddingUnit, type FileCategory, type FileKind, type FileNode, type FileRecord, type FindSymbolOptions, type ForbiddenEdgeRule, type Graph, type GrepOptions, type Hotspot, type Hunk, type IgnoreRule, type IndexArtifacts, MARKDOWN_EXT, type MarkdownInfo, type MermaidOptions, type ModuleInfo, type ModuleNode, type RawRef, type RenderScipOptions, type RepoMapOptions, type RepoScan, type Resolution, type ResolveContext, type RiskHotspot, type RuleSeverity, type RuleViolation, SCHEMA_VERSION, type ScanOptions, type SearchHit, type SearchOptions, type SearchResult, type SemanticSearchOptions, type SemanticSearchResult, type ShResult, type StaticEmbedModel, type SurpriseEdge, type SymbolComplexity, type SymbolIndex, type SymbolMatch, type SymbolReferences, type TestMap, type Tier, type WalkOptions, type WalkResult, type WalkedFile, type WorkspaceInfo, type WorkspaceKind, type WorkspacePackage, allGrammarKeys, applyCentrality, basicTokenize, betweennessOf, buildCallerIndex, buildEmbeddingIndex, buildEndpointIndex, buildGraph, buildIndexArtifacts, buildModules, buildResolveContext, buildSymbolIndex, byKey, byStr, categorize, changeCoupling, changedSince, checkRules, classify, clip, clipInline, communityOf, compileGlobs, complexityOfSource, computeImportPairs, computeSurprises, computeSymbolRefs, computeTestMap, deleteMemory, deserializeEmbeddings, detectCommunities, detectWorkspaces, diffFiles, diffHunks, embedEndpointUrl, embedViaEndpoint, embeddingUnits, enclosingSymbol, encode, encodeQueryViaEndpoint, ensureGrammars, escapeRegExp, extToLang, extractAst, extractCode, extractMarkdown, extractSymbols, findDeadCode, findReferences, findSymbol, foldText, gitChurn, grammarKeyForExt, grammarReady, grepRepo, hasEmbedModel, have, headCommit, healthzUrl, insertAfterSymbol, insertBeforeSymbol, intDot, isCode, isDoc, isGitWorktree, isIgnored, isSurprising, isTestFile, isTestPath, keywords, languageOf, listMemories, loadEmbedModel, pagerankOf, parseGitignore, parseRules, probeEndpoint, quantize, rankHotspots, rankedKeywords, readMemory, readText, renderGraphJson, renderMermaid, renderRepoMap, renderScip, renderSymbolsJson, replaceSymbolBody, resolveBaseRef, resolveCallEdges, resolveDocLink, resolveEmbedEndpoint, resolveEmbedModelDir, resolveEmbedPullUrl, resolveImport, resolveUniqueSymbol, riskHotspots, roundHalfToEven, rrf, runCli, runMcpServer, scanRepo, searchIndex, searchSemantic, serializeEmbeddings, sh, sha1, shortHash, slugify, subtokens, symbolComplexity, symbolsOverview, testsForModule, tierForPath, tokenize, uniqueSymbolDefs, untestedModules, untrackedFiles, walk, wordpiece, writeMemory };