@ninjaxtools/slopdex 0.18.0 → 0.19.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/README.md +2 -2
- package/dist/{chunk-KQRS5P4U.js → chunk-5BO2LLXC.js} +6 -1
- package/dist/chunk-5BO2LLXC.js.map +1 -0
- package/dist/{chunk-6LHZCHEB.js → chunk-GBEJHETS.js} +6 -1
- package/dist/{chunk-6LHZCHEB.js.map → chunk-GBEJHETS.js.map} +1 -1
- package/dist/{chunk-N6D66ASM.js → chunk-HMKVMRLF.js} +34 -30
- package/dist/chunk-HMKVMRLF.js.map +1 -0
- package/dist/{chunk-KUV6PKKL.js → chunk-P57ZJREE.js} +6 -5
- package/dist/chunk-P57ZJREE.js.map +1 -0
- package/dist/cli.js +8 -8
- package/dist/{code-index-YEFTNG7E.js → code-index-MWSJKV3G.js} +4 -4
- package/dist/{cross-search-FVKDMGP7.js → cross-search-ERBFC25T.js} +3 -3
- package/dist/{database-KAPV2YMP.js → database-P5XWJWQL.js} +2 -2
- package/dist/index.js +4 -4
- package/docs/implementation.md +1 -1
- package/package.json +1 -2
- package/.agents/skills/slopdex/SKILL.md +0 -241
- package/dist/chunk-KQRS5P4U.js.map +0 -1
- package/dist/chunk-KUV6PKKL.js.map +0 -1
- package/dist/chunk-N6D66ASM.js.map +0 -1
- package/scripts/install-opencode-skill.mjs +0 -11
- /package/dist/{code-index-YEFTNG7E.js.map → code-index-MWSJKV3G.js.map} +0 -0
- /package/dist/{cross-search-FVKDMGP7.js.map → cross-search-ERBFC25T.js.map} +0 -0
- /package/dist/{database-KAPV2YMP.js.map → database-P5XWJWQL.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -157,7 +157,7 @@ var parsed = (() => {
|
|
|
157
157
|
var [command, ...positionals] = parsed.positionals;
|
|
158
158
|
var descriptionsAction = command === "descriptions" ? positionals[0] : void 0;
|
|
159
159
|
if (parsed.values.version) {
|
|
160
|
-
const version = true ? "0.
|
|
160
|
+
const version = true ? "0.19.0" : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
161
161
|
process.stdout.write(`${version}
|
|
162
162
|
`);
|
|
163
163
|
process.exit(0);
|
|
@@ -220,7 +220,7 @@ async function main() {
|
|
|
220
220
|
const config = loadConfig(rootDir, parsed.values.config);
|
|
221
221
|
if (command === "index-errors") {
|
|
222
222
|
const indexPath = path.resolve(parsed.values.index ?? config.indexPath ?? path.join(rootDir, ".slopdex/index.sqlite"));
|
|
223
|
-
const { readIndexErrors } = await import("./database-
|
|
223
|
+
const { readIndexErrors } = await import("./database-P5XWJWQL.js");
|
|
224
224
|
const errors = existsSync(indexPath) ? readIndexErrors(indexPath) : [];
|
|
225
225
|
if (outputFormat("summary") === "summary") {
|
|
226
226
|
process.stdout.write(errors.length === 0 ? "No indexing errors.\n" : `${errors.map(
|
|
@@ -262,7 +262,7 @@ async function main() {
|
|
|
262
262
|
parsed.values["no-reindex"],
|
|
263
263
|
descriptionRefresh(config)
|
|
264
264
|
);
|
|
265
|
-
const { CodeIndex } = await import("./code-index-
|
|
265
|
+
const { CodeIndex } = await import("./code-index-MWSJKV3G.js");
|
|
266
266
|
const index = new CodeIndex(indexOptions);
|
|
267
267
|
try {
|
|
268
268
|
switch (command) {
|
|
@@ -355,8 +355,8 @@ async function runCrossSearch(source, sourceOptions, provider) {
|
|
|
355
355
|
descriptionRefresh(targetConfig)
|
|
356
356
|
);
|
|
357
357
|
}
|
|
358
|
-
const { CodeIndex: CodeIndexClass } = await import("./code-index-
|
|
359
|
-
const { crossSearch } = await import("./cross-search-
|
|
358
|
+
const { CodeIndex: CodeIndexClass } = await import("./code-index-MWSJKV3G.js");
|
|
359
|
+
const { crossSearch } = await import("./cross-search-ERBFC25T.js");
|
|
360
360
|
const target = targetOptions ? new CodeIndexClass({ ...targetOptions, readOnly: true }) : void 0;
|
|
361
361
|
const format = outputFormat(parsed.values.cohesion ? "summary" : "clusters");
|
|
362
362
|
const threshold = similarityThreshold();
|
|
@@ -410,7 +410,7 @@ async function ensureIndexUpdated(options, label, target, rebuildOnDivergence, f
|
|
|
410
410
|
diagnosticIndexes.add(resolveIndexPath(options));
|
|
411
411
|
const initialized = await initializeMissingIndex(options, label, target, noReindex, hooks);
|
|
412
412
|
if (initialized) return initialized;
|
|
413
|
-
const { CodeIndex } = await import("./code-index-
|
|
413
|
+
const { CodeIndex } = await import("./code-index-MWSJKV3G.js");
|
|
414
414
|
try {
|
|
415
415
|
const index = new CodeIndex(options);
|
|
416
416
|
try {
|
|
@@ -430,7 +430,7 @@ async function ensureIndexUpdated(options, label, target, rebuildOnDivergence, f
|
|
|
430
430
|
const indexPath = resolveIndexPath(options);
|
|
431
431
|
const descriptionProfile = descriptionProfileForRebuild(indexPath, options.rootDir);
|
|
432
432
|
try {
|
|
433
|
-
const { resetIndexState } = await import("./database-
|
|
433
|
+
const { resetIndexState } = await import("./database-P5XWJWQL.js");
|
|
434
434
|
resetIndexState(indexPath, options.rootDir, options.provider.profile);
|
|
435
435
|
} catch (resetError) {
|
|
436
436
|
if (!(resetError instanceof IncompatibleIndexError)) throw resetError;
|
|
@@ -449,7 +449,7 @@ async function initializeMissingIndex(options, label, target, noReindex, hooks)
|
|
|
449
449
|
return initializeIndex(options, indexPath, label, target, noReindex, null, hooks);
|
|
450
450
|
}
|
|
451
451
|
async function initializeIndex(options, indexPath, label, target, noReindex, descriptionProfile = null, hooks) {
|
|
452
|
-
const { CodeIndex } = await import("./code-index-
|
|
452
|
+
const { CodeIndex } = await import("./code-index-MWSJKV3G.js");
|
|
453
453
|
const rebuiltDescriptionProvider = descriptionProfile && !options.descriptionProvider ? await createDescriptionProvider({
|
|
454
454
|
descriptionProvider: descriptionProfile.provider,
|
|
455
455
|
descriptionModel: descriptionProfile.model,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CodeIndex
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-HMKVMRLF.js";
|
|
4
|
+
import "./chunk-GBEJHETS.js";
|
|
5
5
|
import "./chunk-TSURHRFF.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-5BO2LLXC.js";
|
|
7
7
|
import "./chunk-BXWO2KMC.js";
|
|
8
8
|
import "./chunk-S225GYCL.js";
|
|
9
9
|
import "./chunk-VH5VGRCI.js";
|
|
@@ -11,4 +11,4 @@ import "./chunk-DKRB2XT5.js";
|
|
|
11
11
|
export {
|
|
12
12
|
CodeIndex
|
|
13
13
|
};
|
|
14
|
-
//# sourceMappingURL=code-index-
|
|
14
|
+
//# sourceMappingURL=code-index-MWSJKV3G.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
crossSearch
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-P57ZJREE.js";
|
|
4
|
+
import "./chunk-5BO2LLXC.js";
|
|
5
5
|
import "./chunk-VH5VGRCI.js";
|
|
6
6
|
import "./chunk-DKRB2XT5.js";
|
|
7
7
|
export {
|
|
8
8
|
crossSearch
|
|
9
9
|
};
|
|
10
|
-
//# sourceMappingURL=cross-search-
|
|
10
|
+
//# sourceMappingURL=cross-search-ERBFC25T.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
readIndexErrorCounts,
|
|
4
4
|
readIndexErrors,
|
|
5
5
|
resetIndexState
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GBEJHETS.js";
|
|
7
7
|
import "./chunk-DKRB2XT5.js";
|
|
8
8
|
export {
|
|
9
9
|
IndexDatabase,
|
|
@@ -11,4 +11,4 @@ export {
|
|
|
11
11
|
readIndexErrors,
|
|
12
12
|
resetIndexState
|
|
13
13
|
};
|
|
14
|
-
//# sourceMappingURL=database-
|
|
14
|
+
//# sourceMappingURL=database-P5XWJWQL.js.map
|
package/dist/index.js
CHANGED
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
} from "./chunk-OIKBO3NJ.js";
|
|
8
8
|
import {
|
|
9
9
|
CodeIndex
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-HMKVMRLF.js";
|
|
11
11
|
import {
|
|
12
12
|
readIndexErrors
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GBEJHETS.js";
|
|
14
14
|
import {
|
|
15
15
|
OpenAIDescriptionProvider
|
|
16
16
|
} from "./chunk-TSURHRFF.js";
|
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
analyzeCohesion,
|
|
19
19
|
cohesionLocation,
|
|
20
20
|
crossSearch
|
|
21
|
-
} from "./chunk-
|
|
22
|
-
import "./chunk-
|
|
21
|
+
} from "./chunk-P57ZJREE.js";
|
|
22
|
+
import "./chunk-5BO2LLXC.js";
|
|
23
23
|
import {
|
|
24
24
|
JinaEmbeddingProvider
|
|
25
25
|
} from "./chunk-IQU3YVZ3.js";
|
package/docs/implementation.md
CHANGED
|
@@ -100,7 +100,7 @@ Cross-search selects sources, queries neighbors per source, and deduplicates uno
|
|
|
100
100
|
|
|
101
101
|
### Similarity cache
|
|
102
102
|
|
|
103
|
-
Same-index cross-search and cohesion analysis read through a persisted pairwise-similarity cache instead of issuing one vector query per source function on every run. `CodeIndex.refreshSimilarityCache({ width, minSimilarity })` runs first with the caller's effective threshold as the floor: it compares each function's current code/description/file-description embedding triple against `similarity_cache_state` and re-queries only new or changed functions (full neighbor scans for the dirty set, then an exact merge-trim pass over clean functions reusing the symmetric dirty scans), storing only pairs at or above the floor. A lower floor than previously cached re-queries every function, since the missing band cannot be rebuilt incrementally; a higher floor reuses the cache for free. Each state row records the embedding triple, floor, stored row count, and a completeness flag, so a sparse row set is never mistaken for an unfinished computation, and row loss from deletions is detected by comparing the live row count against the stored count. `cachedSimilarToFunction` then serves each source's top-`width` neighbors with the same threshold/line-count/name-regex/path filters as a live query, falling back to a live vector query on any miss, staleness, higher-floor request, or filter truncation; short results from a complete entry are exact. A fallback scan doubles as a read-repair: when the entry is fresh but unusable, the scan is widened to the full cache width and written back (never narrowing the recorded floor), so later lookups hit and stale flags heal on contact; dense-at-max-width entries are left to live queries. Per-function analysis loops use `cachedSimilarityReader`, which snapshots the validity state once per run and pushes the query filters and limit into the neighbor query, instead of re-reading whole tables and materializing full widths for every source. Cross-index search still queries live vectors; read-only indexes read (but never refresh or repair) the cache. The fill reports a `similarity-cache` progress phase and the source loop a `cross-search` phase (both silent when non-interactive), surfaced through the index-level `onProgress` callback — or the per-call `onCacheProgress` override on cross-search/cohesion options — so the CLI renders the same terminal bar as vector/description generation. Unchanged pairs are never recomputed, deleted functions disappear through foreign-key cascades plus a backfill check, and cache writes never touch the index generation. `cachedSimilarToFunction` then serves each source's top-`width` neighbors with the same threshold/line-count/name-regex/path filters as a live query, falling back to a live vector query on any miss, staleness, narrow entry, or filter truncation. Cross-index search still queries live vectors; read-only indexes read (but never refresh) the cache.
|
|
103
|
+
Same-index cross-search and cohesion analysis read through a persisted pairwise-similarity cache instead of issuing one vector query per source function on every run. `CodeIndex.refreshSimilarityCache({ width, minSimilarity })` runs first with the caller's effective threshold as the floor (cross-search and cohesion anchor it to a shared 0.3 band unless a lower threshold is given, so sweeping thresholds never rebuilds the cache): it compares each function's current code/description/file-description embedding triple against `similarity_cache_state` and re-queries only new or changed functions (full neighbor scans for the dirty set, then an exact merge-trim pass over clean functions reusing the symmetric dirty scans), storing only pairs at or above the floor. A lower floor than previously cached re-queries every function, since the missing band cannot be rebuilt incrementally; a higher floor reuses the cache for free. The merge never narrows an entry the read-repair widened: each row keeps its lowest served floor and widest stored width, and completeness is evaluated per row, so one incomplete entry cannot mark the whole index incomplete and force every later lookup to re-scan. Each state row records the embedding triple, floor, stored row count, and a completeness flag, so a sparse row set is never mistaken for an unfinished computation, and row loss from deletions is detected by comparing the live row count against the stored count. `cachedSimilarToFunction` then serves each source's top-`width` neighbors with the same threshold/line-count/name-regex/path filters as a live query, falling back to a live vector query on any miss, staleness, higher-floor request, or filter truncation; short results from a complete entry are exact. A fallback scan doubles as a read-repair: when the entry is fresh but unusable, the scan is widened to the full cache width and written back (never narrowing the recorded floor), so later lookups hit and stale flags heal on contact; dense-at-max-width entries are left to live queries. Per-function analysis loops use `cachedSimilarityReader`, which snapshots the validity state once per run and pushes the query filters and limit into the neighbor query, instead of re-reading whole tables and materializing full widths for every source. Cross-index search still queries live vectors; read-only indexes read (but never refresh or repair) the cache. The fill reports a `similarity-cache` progress phase and the source loop a `cross-search` phase (both silent when non-interactive), surfaced through the index-level `onProgress` callback — or the per-call `onCacheProgress` override on cross-search/cohesion options — so the CLI renders the same terminal bar as vector/description generation. Unchanged pairs are never recomputed, deleted functions disappear through foreign-key cascades plus a backfill check, and cache writes never touch the index generation. `cachedSimilarToFunction` then serves each source's top-`width` neighbors with the same threshold/line-count/name-regex/path filters as a live query, falling back to a live vector query on any miss, staleness, narrow entry, or filter truncation. Cross-index search still queries live vectors; read-only indexes read (but never refresh) the cache.
|
|
104
104
|
|
|
105
105
|
### Cohesion metrics
|
|
106
106
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ninjaxtools/slopdex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Tree-sitter callable-level semantic indexing, search, and duplicate discovery for Python, JavaScript, TypeScript, Rust, Go, Java, and C",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
"test": "vitest",
|
|
38
38
|
"test:run": "vitest run",
|
|
39
39
|
"smoke": "node scripts/smoke.mjs",
|
|
40
|
-
"install:skill:opencode": "node scripts/install-opencode-skill.mjs",
|
|
41
40
|
"install:local": "npm run build && npm install -g .",
|
|
42
41
|
"publish:minor": "npm version minor && npm publish --access public",
|
|
43
42
|
"prepack": "npm run build && npm run smoke",
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: slopdex
|
|
3
|
-
description: Semantic code search, duplicate-function candidates, and physical-distance re-ranking
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Slopdex operator guide for agents
|
|
7
|
-
|
|
8
|
-
Slopdex does semantic code search, finds similar-code candidates, and identifies related functions stored far apart.
|
|
9
|
-
|
|
10
|
-
### Find code by meaning
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
slopdex search "validate an authenticated session" --format summary --limit 10
|
|
14
|
-
slopdex search "persist user data" -e 'save|persist' --format summary --limit 5
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Describe behavior rather than guessing a symbol name. `-e` is a regex that restricts which symbols (functions) are searched.
|
|
18
|
-
|
|
19
|
-
Hosted reranking is optional and persists in repository config:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
slopdex config reranker cohere
|
|
23
|
-
# Or: slopdex config reranker jina
|
|
24
|
-
# Or use an LLM: slopdex config reranker openai
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Set `COHERE_API_KEY`, `JINA_API_KEY`, or `OPENAI_API_KEY` respectively. The OpenAI LLM reranker defaults to `gpt-5.6-luna`, high reasoning, and the top 10 embedding candidates; configure the pool with `slopdex config reranker openai --reranker-candidates 20`. Disable reranking with `slopdex config reranker disable`. Reranking applies to `search` and `search-description`, not cross-search. It preserves embedding `similarity`, adds `rerankScore`, and orders a wider candidate set by that score.
|
|
28
|
-
|
|
29
|
-
### Search function purpose
|
|
30
|
-
|
|
31
|
-
Code purpose-description generation needs to be enabled once:
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
slopdex descriptions enable # only needed once
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Then purpose descriptions can be searched:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
slopdex search-description "keep the repository index synchronized" --format summary --limit 10
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Enabling needs `OPENAI_API_KEY` by default. Select OpenCode Zen or Go with
|
|
44
|
-
`--description-provider opencode` or `--description-provider opencode-go` and set `OPENCODE_API_KEY`
|
|
45
|
-
or sign in with `opencode auth login`, which stores the key in `~/.local/share/opencode/auth.json`.
|
|
46
|
-
|
|
47
|
-
To select another model:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
slopdex descriptions enable --description-model <model-id>
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
List and persist a published OpenCode model without creating an index:
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
slopdex models opencode-go
|
|
57
|
-
slopdex config model opencode-go/gpt-5.6-luna
|
|
58
|
-
slopdex config descriptions enable
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
The next index-using command applies the configured description state. A bare model ID passed to
|
|
62
|
-
`config model` resolves automatically only when it belongs to one of Zen or Go; qualify shared IDs.
|
|
63
|
-
|
|
64
|
-
### Find duplicate candidates
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
slopdex cross-search --cross-file-only --min-lines 4 --threshold 0.9 --matches 5 --limit 5
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
The default output is connected clusters. This excludes same-file matches and short functions, keeps 5 matches per source, and emits at most 5 clusters. For source-by-source matches, add `--format summary`. `--limit` caps emitted clusters/sources (unlimited by default); `--matches` caps matches per source (default 5).
|
|
71
|
-
|
|
72
|
-
Broaden discovery through adjacent score bands when needed:
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
slopdex cross-search --cross-file-only --min-lines 4 --threshold 0.85-0.9 --matches 5
|
|
76
|
-
slopdex cross-search --cross-file-only --min-lines 4 --threshold 0.8-0.85 --matches 5
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Ranges include the lower bound and exclude the upper bound. Use `--min-lines 1` when one-line wrappers are relevant.
|
|
80
|
-
|
|
81
|
-
### Review changes or a module
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
slopdex cross-search --uncommitted --cross-file-only --min-lines 4 --threshold 0.9
|
|
85
|
-
slopdex cross-search --changed-since origin/main --format summary --threshold 0.9
|
|
86
|
-
slopdex cross-search --source-path src/services -e '^UserService\.' --format summary --threshold 0.9
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
These restrict sources while searching the full eligible index. All supplied restrictions intersect:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
slopdex cross-search --source-path src -e 'validate' \
|
|
93
|
-
--changed-since origin/main --uncommitted \
|
|
94
|
-
--cross-file-only --min-lines 4 --threshold 0.9
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Here a source must have changed since the commit and belong to an uncommitted file, within the selected path/name scope. `--regex` is an alias for `-e/--regexp`.
|
|
98
|
-
|
|
99
|
-
### Review physical cohesion
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
slopdex cross-search --cohesion --threshold 0.8 --matches 20 --format summary
|
|
103
|
-
slopdex cross-search --cohesion --source-path src/services --threshold 0.8 --format summary
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
`--cohesion` keeps cross-search's semantic matches and orders each source's matches from greatest to least physical path distance. Similarity breaks distance ties. Summary output includes the distance; JSONL matches include `physicalDistance`.
|
|
107
|
-
|
|
108
|
-
### Compare repositories
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
slopdex cross-search \
|
|
112
|
-
--target-root /path/to/other/repo \
|
|
113
|
-
--target-index /path/to/other/repo/.slopdex/index.sqlite \
|
|
114
|
-
--threshold 0.9 --format summary
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Both target options are required. Both indexes refresh and must have identical embedding profiles. The target refresh uses the source command's embedding provider and the target's file-selection/description configuration. Use `--target-config <path>` for a custom target config.
|
|
118
|
-
|
|
119
|
-
### Inspect or maintain the index
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
slopdex status
|
|
123
|
-
slopdex index-errors --format summary
|
|
124
|
-
slopdex update-git
|
|
125
|
-
slopdex update-files src/service.ts src/model.ts
|
|
126
|
-
slopdex reindex-files
|
|
127
|
-
slopdex reindex-files --callables
|
|
128
|
-
slopdex delete-files src/removed.ts
|
|
129
|
-
slopdex --version
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
- `status` refreshes, then reports coverage, checkpoint, profiles, and error counts; use when metadata is requested.
|
|
133
|
-
- `index-errors` reads saved failures without refreshing or needing API credentials.
|
|
134
|
-
- `update-git` explicitly refreshes HEAD and working-tree changes.
|
|
135
|
-
- `update-files` reparses specified working-tree files after automatic refresh, even when their contents are unchanged.
|
|
136
|
-
- `reindex-files` regenerates stale file descriptions and embeddings. Add `--callables` to also replace callable descriptions in those files.
|
|
137
|
-
- `delete-files` removes index entries after automatic refresh, not source files. Eligible files can return on later refresh.
|
|
138
|
-
- `--version` prints the built package version. `--help` describes available commands/options.
|
|
139
|
-
|
|
140
|
-
## Command-line reference
|
|
141
|
-
|
|
142
|
-
Usage: `slopdex <command> [arguments] [options]`. Quote queries and regexes. Boolean flags default to off. Use options only with their applicable commands.
|
|
143
|
-
|
|
144
|
-
### General settings
|
|
145
|
-
|
|
146
|
-
| Argument | Meaning / default |
|
|
147
|
-
| --- | --- |
|
|
148
|
-
| `--root <path>` | Repository root; current directory by default. |
|
|
149
|
-
| `--config <path>` | Config; `<root>/.slopdex/config.json`. |
|
|
150
|
-
| `--index <path>` | Index; `<root>/.slopdex/index.sqlite`. Overrides config `indexPath`. |
|
|
151
|
-
| `--provider <openai\|jina>` | Embedding provider; `openai`. |
|
|
152
|
-
| `--model <name>` | Embedding model; OpenAI `text-embedding-3-large`, Jina `jina-embeddings-v4`. |
|
|
153
|
-
| `--dimensions <number>` | Positive dimensions supported by the model; OpenAI `3072`, Jina `1024`. |
|
|
154
|
-
| `--description-provider <openai\|opencode\|opencode-go>` | Description provider; OpenAI by default. OpenCode values use `OPENCODE_API_KEY` or `~/.local/share/opencode/auth.json`. |
|
|
155
|
-
| `--description-model <name>` | Description model; `gpt-5.6-luna` for OpenAI and `muse-spark-1.3-contributor` for Zen/Go. |
|
|
156
|
-
| `--reranker-candidates <number>` | With `config reranker openai`, embedding-ranked functions sent to the LLM; range `1`-`100`, default `10`. |
|
|
157
|
-
| `--ignore-errors` | Silence saved-diagnostic warnings without deleting records. |
|
|
158
|
-
| `--verbose` | Report every external model request on stderr instead of once per call kind/provider/model. Config `"verbose": true` has the same effect. |
|
|
159
|
-
| `-h`, `--help` | Usage; no refresh. |
|
|
160
|
-
| `--version` | Package version; exits without refresh or saved-diagnostic warnings. |
|
|
161
|
-
|
|
162
|
-
Explicit relative config/index paths resolve from the current directory. Source paths and explicit file arguments resolve within `--root`. Prefer absolute paths when operating across repositories. CLI settings override config.
|
|
163
|
-
|
|
164
|
-
### Query and analysis options
|
|
165
|
-
|
|
166
|
-
| Argument | Applies to / behavior |
|
|
167
|
-
| --- | --- |
|
|
168
|
-
| `--limit <number>` | Positive integer output limit; unlimited unless passed. Query matches, or cross-search clusters (`clusters`) / matched sources (`summary`/JSONL). Threshold filters results. |
|
|
169
|
-
| `--matches <number>` | Cross-search only: matches kept per source function; default `5`. |
|
|
170
|
-
| `--threshold <number\|min-max>` | Both query searches and cross-search. Inclusive minimum or half-open range; default `0.3`. |
|
|
171
|
-
| `--format <json\|summary\|clusters>` | Both query searches, cross-search, and index-errors. Cohesion-ranked cross-search supports summary or JSONL, not clusters. |
|
|
172
|
-
| `-e <regex>`, `--regexp <regex>`, `--regex <regex>` | Equivalent case-sensitive JavaScript regex options on qualified names. Query searches filter results before limiting; cross-search filters sources only. |
|
|
173
|
-
| `--min-lines <number>` | Cross-search: positive source/candidate length minimum, default `2`. |
|
|
174
|
-
| `--source-path <path>` | Cross-search: source file or recursive directory within the root. |
|
|
175
|
-
| `--changed-since <commit>` | Cross-search: added, modified, or moved functions since an ancestor of the indexed Git checkpoint, including working-tree changes. Requires Git. |
|
|
176
|
-
| `--uncommitted` | Cross-search: functions indexed from working-tree files; in Git these are staged, unstaged, or untracked changes. Without Git this selects all working-tree functions. |
|
|
177
|
-
| `--cross-file-only` | Cross-search: exclude same-physical-file matches. |
|
|
178
|
-
| `--include-symmetric-duplicates` | Cross-search: allow both directions of same-index matches; otherwise each unordered pair appears once. |
|
|
179
|
-
| `--cohesion` | Cross-search: add physical distance and order each source's matches from farthest to nearest. Defaults to summary output. |
|
|
180
|
-
| `--target-root <path>` | Cross-search: second repository; requires `--target-index`. |
|
|
181
|
-
| `--target-index <path>` | Cross-search: second index file; requires `--target-root`. |
|
|
182
|
-
| `--target-config <path>` | Cross-search: target config, default `<target-root>/.slopdex/config.json`; requires both target options. |
|
|
183
|
-
|
|
184
|
-
### Refresh and recovery options
|
|
185
|
-
|
|
186
|
-
| Argument | Behavior |
|
|
187
|
-
| --- | --- |
|
|
188
|
-
| `--target <ref>` | `update-git` snapshot, default `HEAD`. Non-HEAD targets are committed-only; later commands normally return to HEAD. |
|
|
189
|
-
| `--rebuild-on-divergence` | Permit reconciliation after non-descendant history changes, such as a rebase/branch switch. |
|
|
190
|
-
| `--force-reindex` | Recreate an incompatible index. Compatible indexes still use normal refresh; this is not an unconditional reparse flag. |
|
|
191
|
-
| `--no-reindex` | With Git, reconcile the committed snapshot but omit working-tree overlays. Without Git, reuse a non-empty index; missing/empty indexes still populate. Not an offline mode. |
|
|
192
|
-
| `--callables` | With `reindex-files`, continue after the file description and regenerate every callable description in each stale file. |
|
|
193
|
-
|
|
194
|
-
Use `--no-reindex` when the task calls for committed-only results or reuse of an existing non-Git index, rather than silently weakening freshness.
|
|
195
|
-
|
|
196
|
-
## Interpret and report results
|
|
197
|
-
|
|
198
|
-
### Output formats
|
|
199
|
-
|
|
200
|
-
| Command | Default | Alternatives |
|
|
201
|
-
| --- | --- | --- |
|
|
202
|
-
| `search`, `search-description` | `summary` | JSON array; purpose search includes generated description text |
|
|
203
|
-
| `cross-search` | `clusters` | `summary`, or `json` for JSONL with one row per matched source |
|
|
204
|
-
| `index-errors` | `summary` | JSON array |
|
|
205
|
-
| `status`, update commands, `descriptions` | JSON object | — |
|
|
206
|
-
|
|
207
|
-
Prefer summary output for compact source review, clusters for duplicate families, and JSON/JSONL for structured processing. Stdout carries results; stderr carries notices and warnings. External vector, description, and reranking requests identify their provider and model once per combination, or for every request with `--verbose`. Cross-search omits sources without emitted matches. Empty output means no findings under the chosen coverage/filters, not proof that no similar code exists.
|
|
208
|
-
|
|
209
|
-
With reranking enabled, query summaries display both reranker relevance and embedding similarity. Similarity thresholds filter candidates before reranking; limits apply to the reranked output. LLM candidate documents include descriptions when available and function metadata/source code.
|
|
210
|
-
|
|
211
|
-
### Similarity and clusters
|
|
212
|
-
|
|
213
|
-
```text
|
|
214
|
-
Cluster 1 (3 functions, similarity 0.9124-0.9568)
|
|
215
|
-
src/auth/session.ts:18:1 :: validateSession
|
|
216
|
-
src/http/middleware.ts:42:1 :: authenticate
|
|
217
|
-
src/users/user-service.ts:27:3 :: UserService.authenticate
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
- Similarity is a model-dependent resemblance score, not a duplication probability.
|
|
221
|
-
- The range describes observed links. Members can be connected transitively; not all pairs necessarily match.
|
|
222
|
-
- Cluster numbers reflect ordering by member count and name, not severity.
|
|
223
|
-
- Inspect listed locations and callers. Tests, facades, adapters, and intentional layers can resemble each other without being redundant.
|
|
224
|
-
|
|
225
|
-
When reporting candidates, identify paths/symbols, summarize the shared behavior you verified, and explain whether consolidation is appropriate. Do not infer equivalence from the score alone.
|
|
226
|
-
|
|
227
|
-
### Purpose-aware scoring
|
|
228
|
-
|
|
229
|
-
When descriptions are complete, `search` and cross-search average code, callable-description, and file-description similarity with equal one-third weights. `search-description` averages callable and file descriptions. Cross-repository analysis needs completeness on both sides; otherwise all scores are code-only. Stale file descriptions remain in scoring until `reindex-files` refreshes them. Description-generator models may differ even though embedding profiles must match.
|
|
230
|
-
|
|
231
|
-
Thresholds and limits apply to the selected score. Text labels combined scoring; JSON includes component scores and mode/weights in `scoring`. Compare runs only with matching scoring mode, weights, embedding and description-generator profiles, threshold, and source/candidate filters.
|
|
232
|
-
|
|
233
|
-
### Cohesion
|
|
234
|
-
|
|
235
|
-
```text
|
|
236
|
-
src/auth/session.ts :: validateSession
|
|
237
|
-
0.9400 packages/http/middleware.ts :: authenticate [distance 4]
|
|
238
|
-
0.9300 src/auth/token.ts :: validateToken [distance 1]
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
Physical distance is `0` within one file, `1` between files in one folder, and `1` plus directory-tree hops across folders. The option only reorders the selected semantic matches; it does not change similarity or prove that distant code should be moved. Review architectural layers, tests, adapters, and other intentional separation before recommending consolidation.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/search/similarity.ts"],"sourcesContent":["import type { AnalysisSimilarity, IndexStatus } from \"../types.js\";\n\n/** Select one scoring mode for the entire analysis, never a per-pair fallback. */\nexport function analysisSimilarity(source: IndexStatus, target: IndexStatus = source): AnalysisSimilarity {\n const complete = (status: IndexStatus): boolean => status.descriptionsEnabled\n && status.descriptionProfile !== null\n && status.descriptionCount === status.functionCount\n && status.fileDescriptionCount === status.describableFileCount;\n return complete(source) && complete(target)\n ? { similarityMode: \"code-description-file-average\", similarityWeights: { code: 1 / 3, description: 1 / 3, fileDescription: 1 / 3 } }\n : { similarityMode: \"code\", similarityWeights: { code: 1, description: 0, fileDescription: 0 } };\n}\n"],"mappings":";AAGO,SAAS,mBAAmB,QAAqB,SAAsB,QAA4B;AACxG,QAAM,WAAW,CAAC,WAAiC,OAAO,uBACrD,OAAO,uBAAuB,QAC9B,OAAO,qBAAqB,OAAO,iBACnC,OAAO,yBAAyB,OAAO;AAC5C,SAAO,SAAS,MAAM,KAAK,SAAS,MAAM,IACtC,EAAE,gBAAgB,iCAAiC,mBAAmB,EAAE,MAAM,IAAI,GAAG,aAAa,IAAI,GAAG,iBAAiB,IAAI,EAAE,EAAE,IAClI,EAAE,gBAAgB,QAAQ,mBAAmB,EAAE,MAAM,GAAG,aAAa,GAAG,iBAAiB,EAAE,EAAE;AACnG;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/search/cross-search.ts","../src/analysis/cohesion.ts"],"sourcesContent":["import { realpath, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { IncompatibleIndexError } from \"../errors.js\";\nimport { cohesionLocation } from \"../analysis/cohesion.js\";\nimport type { CrossSearchOptions, CrossSearchResult } from \"../types.js\";\nimport { assertPositiveInteger, compileNameRegex, throwIfAborted } from \"../utils.js\";\nimport { analysisSimilarity } from \"./similarity.js\";\n\nexport async function* crossSearch(options: CrossSearchOptions): AsyncGenerator<CrossSearchResult> {\n const target = options.target ?? options.source;\n const sourceStatus = options.source.status();\n const targetStatus = target.status();\n const sourceProfile = JSON.stringify(sourceStatus.embeddingProfile);\n const targetProfile = JSON.stringify(targetStatus.embeddingProfile);\n if (sourceProfile !== targetProfile) {\n throw new IncompatibleIndexError(\"Cross-search requires identical embedding profiles.\");\n }\n const scoring = {\n ...analysisSimilarity(sourceStatus, targetStatus),\n sourceDescriptionProfile: sourceStatus.descriptionProfile,\n targetDescriptionProfile: targetStatus.descriptionProfile,\n };\n const includeDescriptions = scoring.similarityMode === \"code-description-file-average\";\n\n const limit = options.limitPerFunction ?? 5;\n assertPositiveInteger(limit, \"limitPerFunction\");\n const minLines = options.minLines ?? 2;\n assertPositiveInteger(minLines, \"minLines\");\n const nameRegex = compileNameRegex(options.nameRegex);\n const sourceFunctions = (await options.source.sourceFunctions(options.sourceFilter ?? { type: \"all\" }))\n .filter((callable) => callable.lineCount >= minLines && (!nameRegex || nameRegex.test(callable.qualifiedName)));\n const sameIndex = target.indexPath === options.source.indexPath\n || await fileIdentity(target.indexPath) === await fileIdentity(options.source.indexPath);\n const sourceRoot = options.crossFileOnly ? await canonicalRoot(options.source.rootDir) : undefined;\n const targetRoot = options.crossFileOnly\n ? sameIndex ? sourceRoot! : await canonicalRoot(target.rootDir)\n : undefined;\n const rootsDiffer = options.cohesion && !sameIndex\n ? await canonicalRoot(options.source.rootDir) !== await canonicalRoot(target.rootDir)\n : false;\n const canonicalFiles = new Map<string, Promise<string>>();\n const canonicalFile = (root: string, filePath: string): Promise<string> => {\n const absolutePath = path.resolve(root, filePath);\n let result = canonicalFiles.get(absolutePath);\n if (!result) {\n result = fileIdentity(absolutePath);\n canonicalFiles.set(absolutePath, result);\n }\n return result;\n };\n const targetPathsByCanonicalFile = new Map<string, string[]>();\n if (targetRoot) {\n const targetPaths = [...new Set(target.allFunctions().map((callable) => callable.path))];\n await Promise.all(targetPaths.map(async (targetPath) => {\n const canonicalPath = await canonicalFile(targetRoot, targetPath);\n const paths = targetPathsByCanonicalFile.get(canonicalPath) ?? [];\n paths.push(targetPath);\n targetPathsByCanonicalFile.set(canonicalPath, paths);\n }));\n }\n const seenPairs = new Set<string>();\n const useCache = sameIndex && !options.source.readOnly;\n if (useCache) {\n await options.source.refreshSimilarityCache({\n width: Math.min(200, Math.max(50, limit * 5)),\n minSimilarity: options.minSimilarity ?? -1,\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.onCacheProgress ? { onProgress: options.onCacheProgress } : {}),\n });\n }\n // One validity snapshot for the whole run: per-function lookups must not\n // re-read the state tables for every source.\n const cacheReader = useCache ? options.source.cachedSimilarityReader({ includeDescriptions }) : undefined;\n for (let index = 0; index < sourceFunctions.length; index += 1) {\n throwIfAborted(options.signal);\n const source = sourceFunctions[index]!;\n const canonicalSourceFile = sourceRoot ? await canonicalFile(sourceRoot, source.path) : undefined;\n const excludedTargetPaths = canonicalSourceFile ? targetPathsByCanonicalFile.get(canonicalSourceFile) : undefined;\n const excludePaths = excludedTargetPaths ? { excludePaths: excludedTargetPaths } : {};\n const similar = cacheReader ? cacheReader.similarToFunction : options.source.similarToFunction.bind(options.source);\n const candidates = sameIndex\n ? similar(source.id, {\n includeDescriptions,\n limit,\n minSimilarity: options.minSimilarity ?? -1,\n ...(options.maxSimilarity !== undefined ? { maxSimilarity: options.maxSimilarity } : {}),\n minLines,\n ...(options.nameRegex !== undefined ? { nameRegex: options.nameRegex } : {}),\n ...excludePaths,\n })\n : target.searchByVector(options.source.vectorForFunction(source.id), {\n ...(includeDescriptions ? {\n descriptionVector: options.source.vectorForFunction(source.id, \"description\"),\n fileDescriptionVector: options.source.vectorForFile(source.path),\n } : {}),\n limit,\n minSimilarity: options.minSimilarity ?? -1,\n ...(options.maxSimilarity !== undefined ? { maxSimilarity: options.maxSimilarity } : {}),\n minLines,\n ...(options.nameRegex !== undefined ? { nameRegex: options.nameRegex } : {}),\n ...excludePaths,\n });\n const rankedCandidates = options.cohesion\n ? candidates.map((match) => ({\n ...match,\n physicalDistance: cohesionLocation(source.path, match.function.path).physicalDistance + Number(rootsDiffer),\n })).sort((left, right) => right.physicalDistance - left.physicalDistance\n || right.similarity - left.similarity\n || left.function.path.localeCompare(right.function.path)\n || left.function.startLine - right.function.startLine\n || left.function.id - right.function.id)\n : candidates;\n const matches = sameIndex && !options.includeSymmetricDuplicates\n ? rankedCandidates.filter((match) => {\n const pair = source.id < match.function.id\n ? `${source.id}:${match.function.id}`\n : `${match.function.id}:${source.id}`;\n if (seenPairs.has(pair)) return false;\n seenPairs.add(pair);\n return true;\n })\n : rankedCandidates;\n if (matches.length > 0) yield { source, matches, scoring };\n options.onProgress?.({ completed: index + 1, total: sourceFunctions.length });\n }\n}\n\nasync function canonicalRoot(root: string): Promise<string> {\n try {\n return await realpath(root);\n } catch {\n return path.resolve(root);\n }\n}\n\nasync function fileIdentity(filePath: string): Promise<string> {\n try {\n const metadata = await stat(filePath);\n return `inode:${metadata.dev}:${metadata.ino}`;\n } catch {\n return `path:${path.resolve(filePath)}`;\n }\n}\n","import path from \"node:path\";\n\nimport { CodeIndexError } from \"../errors.js\";\nimport { analysisSimilarity } from \"../search/similarity.js\";\nimport type {\n CohesionAnalysisOptions,\n CohesionFileReport,\n CohesionGroup,\n CohesionLocation,\n CohesionPair,\n CohesionReport,\n IndexedFunction,\n SimilarityResult,\n SimilarityScores,\n} from \"../types.js\";\nimport { assertPositiveInteger, compileNameRegex, throwIfAborted } from \"../utils.js\";\n\ninterface CandidateEdge extends SimilarityScores {\n left: IndexedFunction;\n right: IndexedFunction;\n}\n\nexport async function analyzeCohesion(options: CohesionAnalysisOptions): Promise<CohesionReport> {\n const status = options.source.status();\n const scoring = analysisSimilarity(status);\n const neighbors = options.neighbors ?? 20;\n const limit = options.limit ?? 50;\n const minSimilarity = options.minSimilarity ?? 0.8;\n const minLines = options.minLines ?? 2;\n const sourceFilter = options.sourceFilter ?? { type: \"all\" };\n assertPositiveInteger(neighbors, \"neighbors\");\n assertPositiveInteger(limit, \"limit\");\n assertPositiveInteger(minLines, \"minLines\");\n if (!Number.isFinite(minSimilarity) || minSimilarity < -1 || minSimilarity >= 1) {\n throw new CodeIndexError(\"minSimilarity must be at least -1 and less than 1.\");\n }\n if (options.maxSimilarity !== undefined\n && (!Number.isFinite(options.maxSimilarity) || options.maxSimilarity <= minSimilarity)) {\n throw new CodeIndexError(\"maxSimilarity must be greater than minSimilarity.\");\n }\n const nameRegex = compileNameRegex(options.nameRegex);\n const candidateFunctions = options.source.allFunctions()\n .filter((callable) => callable.lineCount >= minLines && (!nameRegex || nameRegex.test(callable.qualifiedName)));\n const candidateIds = new Set(candidateFunctions.map((callable) => callable.id));\n const sourceFunctions = (await options.source.sourceFunctions(sourceFilter))\n .filter((callable) => candidateIds.has(callable.id));\n\n const neighborCache = new Map<number, Set<number>>();\n const useCache = !options.source.readOnly;\n if (useCache) {\n await options.source.refreshSimilarityCache({\n width: Math.min(200, Math.max(50, neighbors * 5)),\n minSimilarity,\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.onCacheProgress ? { onProgress: options.onCacheProgress } : {}),\n });\n }\n // One validity snapshot for the whole run: per-function lookups must not\n // re-read the state tables for every source.\n const cacheReader = useCache ? options.source.cachedSimilarityReader({\n includeDescriptions: scoring.similarityMode === \"code-description-file-average\",\n }) : undefined;\n const neighborsFor = (callable: IndexedFunction): SimilarityResult[] => {\n throwIfAborted(options.signal);\n const query = {\n includeDescriptions: scoring.similarityMode === \"code-description-file-average\",\n limit: neighbors,\n minSimilarity,\n ...(options.maxSimilarity !== undefined ? { maxSimilarity: options.maxSimilarity } : {}),\n minLines,\n ...(options.nameRegex !== undefined ? { nameRegex: options.nameRegex } : {}),\n };\n const matches = (cacheReader ? cacheReader.similarToFunction(callable.id, query)\n : options.source.similarToFunction(callable.id, query))\n .filter((match) => candidateIds.has(match.function.id));\n neighborCache.set(callable.id, new Set(matches.map((match) => match.function.id)));\n return matches;\n };\n\n const edges = new Map<string, CandidateEdge>();\n for (let index = 0; index < sourceFunctions.length; index += 1) {\n const source = sourceFunctions[index]!;\n for (const match of neighborsFor(source)) {\n const [left, right] = orderedFunctions(source, match.function);\n const key = pairKey(left.id, right.id);\n const existing = edges.get(key);\n if (!existing || match.similarity > existing.similarity) {\n const { function: _function, ...scores } = match;\n edges.set(key, { left, right, ...scores });\n }\n }\n options.onProgress?.({ completed: index + 1, total: sourceFunctions.length });\n }\n\n const scoredPairs = [...edges.values()].map((edge) => {\n const leftNeighbors = neighborCache.get(edge.left.id);\n const rightNeighbors = neighborCache.get(edge.right.id);\n const reciprocal = leftNeighbors && rightNeighbors\n ? leftNeighbors.has(edge.right.id) && rightNeighbors.has(edge.left.id)\n : null;\n return scorePair(edge, reciprocal, minSimilarity);\n }).sort(comparePairs);\n scoredPairs.forEach((pair, index) => {\n pair.rank = index + 1;\n });\n\n const repositoryScope = sourceFilter.type === \"all\" && sourceFilter.path === undefined && sourceFilter.nameRegex === undefined;\n const metrics = calculateMetrics(\n repositoryScope ? \"repository\" : \"selected-sources\",\n sourceFunctions.length,\n candidateFunctions.length,\n scoredPairs,\n );\n const reportedPairs = scoredPairs.slice(0, limit);\n const files = aggregateFiles(sourceFunctions, scoredPairs)\n .filter((file) => file.internalAffinity + file.sameFolderAffinity + file.externalAffinity > 0)\n .slice(0, limit);\n const groups = buildGroups(reportedPairs);\n return {\n schemaVersion: 3,\n repository: {\n generation: status.generation,\n gitCheckpoint: status.gitCheckpoint,\n embeddingProfile: status.embeddingProfile,\n descriptionProfile: status.descriptionProfile,\n },\n parameters: {\n ...scoring,\n neighbors,\n limit,\n minSimilarity,\n ...(options.maxSimilarity !== undefined ? { maxSimilarity: options.maxSimilarity } : {}),\n minLines,\n ...(options.nameRegex !== undefined ? { nameRegex: options.nameRegex } : {}),\n sourceFilter,\n },\n metrics,\n pairs: reportedPairs,\n files,\n groups,\n };\n}\n\nexport function cohesionLocation(leftPath: string, rightPath: string): CohesionLocation {\n const normalizedLeft = leftPath.replaceAll(\"\\\\\", \"/\");\n const normalizedRight = rightPath.replaceAll(\"\\\\\", \"/\");\n const leftDirectory = directoryParts(normalizedLeft);\n const rightDirectory = directoryParts(normalizedRight);\n let commonLength = 0;\n while (commonLength < leftDirectory.length\n && commonLength < rightDirectory.length\n && leftDirectory[commonLength] === rightDirectory[commonLength]) {\n commonLength += 1;\n }\n const commonParts = leftDirectory.slice(0, commonLength);\n const commonAncestor = commonParts.length > 0 ? commonParts.join(\"/\") : null;\n const sameFile = normalizedLeft === normalizedRight;\n const folderHops = sameFile\n ? 0\n : leftDirectory.length - commonLength + rightDirectory.length - commonLength;\n return {\n category: sameFile ? \"same-file\" : folderHops === 0 ? \"same-folder\" : \"different-folder\",\n physicalDistance: sameFile ? 0 : 1 + folderHops,\n folderHops,\n commonAncestor,\n sourceTestPair: isTestPath(normalizedLeft) !== isTestPath(normalizedRight),\n };\n}\n\nfunction scorePair(edge: CandidateEdge, reciprocal: boolean | null, minSimilarity: number): CohesionPair {\n const location = cohesionLocation(edge.left.path, edge.right.path);\n const semanticWeight = clamp((edge.similarity - minSimilarity) / (1 - minSimilarity));\n const separationWeight = 1 - Math.exp(-location.physicalDistance / 2);\n return {\n rank: 0,\n left: edge.left,\n right: edge.right,\n similarity: edge.similarity,\n ...(edge.codeSimilarity !== undefined ? { codeSimilarity: edge.codeSimilarity } : {}),\n ...(edge.descriptionSimilarity !== undefined ? { descriptionSimilarity: edge.descriptionSimilarity } : {}),\n ...(edge.fileDescriptionSimilarity !== undefined ? { fileDescriptionSimilarity: edge.fileDescriptionSimilarity } : {}),\n reciprocal,\n semanticWeight,\n separationWeight,\n cohesionGap: semanticWeight * separationWeight,\n location,\n };\n}\n\nfunction calculateMetrics(\n scope: CohesionReport[\"metrics\"][\"scope\"],\n functionsAnalyzed: number,\n candidateFunctions: number,\n pairs: readonly CohesionPair[],\n): CohesionReport[\"metrics\"] {\n const totalWeight = pairs.reduce((sum, pair) => sum + pair.semanticWeight, 0);\n const sameFileWeight = pairs\n .filter((pair) => pair.location.category === \"same-file\")\n .reduce((sum, pair) => sum + pair.semanticWeight, 0);\n const sameFolderWeight = pairs\n .filter((pair) => pair.location.category === \"same-folder\")\n .reduce((sum, pair) => sum + pair.semanticWeight, 0);\n const remoteWeight = pairs\n .filter((pair) => pair.location.category === \"different-folder\")\n .reduce((sum, pair) => sum + pair.semanticWeight, 0);\n const weightedDistance = pairs.reduce(\n (sum, pair) => sum + pair.semanticWeight * pair.location.physicalDistance,\n 0,\n );\n return {\n scope,\n functionsAnalyzed,\n candidateFunctions,\n semanticEdges: pairs.length,\n sameFileRatio: ratio(sameFileWeight, totalWeight),\n sameFolderRatio: ratio(sameFolderWeight, totalWeight),\n remoteRatio: ratio(remoteWeight, totalWeight),\n weightedMeanDistance: ratio(weightedDistance, totalWeight),\n };\n}\n\nfunction aggregateFiles(\n sourceFunctions: readonly IndexedFunction[],\n pairs: readonly CohesionPair[],\n): CohesionFileReport[] {\n const sourceIds = new Set(sourceFunctions.map((callable) => callable.id));\n const counts = new Map<string, number>();\n for (const callable of sourceFunctions) counts.set(callable.path, (counts.get(callable.path) ?? 0) + 1);\n const reports = new Map<string, CohesionFileReport>();\n for (const [filePath, functionCount] of counts) {\n reports.set(filePath, {\n path: filePath,\n functionCount,\n internalAffinity: 0,\n sameFolderAffinity: 0,\n externalAffinity: 0,\n externalAffinityRatio: 0,\n });\n }\n for (const pair of pairs) {\n if (sourceIds.has(pair.left.id)) addFileAffinity(reports.get(pair.left.path)!, pair.right, pair);\n if (sourceIds.has(pair.right.id)) addFileAffinity(reports.get(pair.right.path)!, pair.left, pair);\n }\n for (const report of reports.values()) {\n const total = report.internalAffinity + report.sameFolderAffinity + report.externalAffinity;\n report.externalAffinityRatio = ratio(report.externalAffinity, total);\n }\n return [...reports.values()].sort((left, right) => right.externalAffinityRatio - left.externalAffinityRatio\n || right.externalAffinity - left.externalAffinity\n || left.path.localeCompare(right.path));\n}\n\nfunction addFileAffinity(\n report: CohesionFileReport,\n match: IndexedFunction,\n pair: CohesionPair,\n): void {\n if (pair.location.category === \"same-file\") report.internalAffinity += pair.semanticWeight;\n else if (pair.location.category === \"same-folder\") report.sameFolderAffinity += pair.semanticWeight;\n else {\n report.externalAffinity += pair.semanticWeight;\n updateStrongestExternal(report, match, pair);\n }\n}\n\nfunction updateStrongestExternal(\n report: CohesionFileReport,\n callable: IndexedFunction,\n pair: CohesionPair,\n): void {\n if (!report.strongestExternalMatch || pair.cohesionGap > report.strongestExternalMatch.cohesionGap) {\n report.strongestExternalMatch = {\n function: callable,\n similarity: pair.similarity,\n ...(pair.codeSimilarity !== undefined ? { codeSimilarity: pair.codeSimilarity } : {}),\n ...(pair.descriptionSimilarity !== undefined ? { descriptionSimilarity: pair.descriptionSimilarity } : {}),\n ...(pair.fileDescriptionSimilarity !== undefined ? { fileDescriptionSimilarity: pair.fileDescriptionSimilarity } : {}),\n cohesionGap: pair.cohesionGap,\n };\n }\n}\n\nfunction buildGroups(pairs: readonly CohesionPair[]): CohesionGroup[] {\n const functions = new Map<number, IndexedFunction>();\n const neighbors = new Map<number, Set<number>>();\n for (const pair of pairs) {\n functions.set(pair.left.id, pair.left);\n functions.set(pair.right.id, pair.right);\n addNeighbor(neighbors, pair.left.id, pair.right.id);\n addNeighbor(neighbors, pair.right.id, pair.left.id);\n }\n const seen = new Set<number>();\n const groups: CohesionGroup[] = [];\n for (const start of neighbors.keys()) {\n if (seen.has(start)) continue;\n const pending = [start];\n const memberIds = new Set<number>();\n while (pending.length > 0) {\n const id = pending.pop()!;\n if (seen.has(id)) continue;\n seen.add(id);\n memberIds.add(id);\n for (const neighbor of neighbors.get(id) ?? []) pending.push(neighbor);\n }\n const groupPairs = pairs.filter((pair) => memberIds.has(pair.left.id) && memberIds.has(pair.right.id));\n const members = [...memberIds].map((id) => functions.get(id)!).sort(compareFunctions);\n groups.push({\n rank: 0,\n memberCount: members.length,\n fileCount: new Set(members.map((member) => member.path)).size,\n minimumEdgeSimilarity: Math.min(...groupPairs.map((pair) => pair.similarity)),\n maximumEdgeSimilarity: Math.max(...groupPairs.map((pair) => pair.similarity)),\n maximumPhysicalDistance: Math.max(...groupPairs.map((pair) => pair.location.physicalDistance)),\n maximumCohesionGap: Math.max(...groupPairs.map((pair) => pair.cohesionGap)),\n members,\n });\n }\n groups.sort((left, right) => right.maximumCohesionGap - left.maximumCohesionGap\n || right.memberCount - left.memberCount\n || compareFunctions(left.members[0]!, right.members[0]!));\n groups.forEach((group, index) => {\n group.rank = index + 1;\n });\n return groups;\n}\n\nfunction directoryParts(filePath: string): string[] {\n const directory = path.posix.dirname(filePath.replaceAll(\"\\\\\", \"/\"));\n return directory === \".\" ? [] : directory.split(\"/\").filter(Boolean);\n}\n\nfunction isTestPath(filePath: string): boolean {\n const original = filePath.replaceAll(\"\\\\\", \"/\");\n const normalized = original.toLowerCase();\n const segments = normalized.split(\"/\");\n const fileName = segments.at(-1) ?? \"\";\n return segments.some((segment) => segment === \"test\" || segment === \"tests\" || segment === \"__tests__\")\n || /\\.(?:test|spec)\\.[cm]?[jt]sx?$/.test(fileName)\n || /_test\\.go$/.test(fileName)\n || /^(?:test_.+|.+_test)\\.(?:py|pyw|rs|c|h)$/.test(fileName)\n || /^(?:Test.+|.+Tests?|.+TestCase)\\.java$/.test(original.split(\"/\").at(-1) ?? \"\");\n}\n\nfunction orderedFunctions(left: IndexedFunction, right: IndexedFunction): [IndexedFunction, IndexedFunction] {\n return compareFunctions(left, right) <= 0 ? [left, right] : [right, left];\n}\n\nfunction compareFunctions(left: IndexedFunction, right: IndexedFunction): number {\n return left.path.localeCompare(right.path)\n || left.startLine - right.startLine\n || left.startColumn - right.startColumn\n || left.qualifiedName.localeCompare(right.qualifiedName)\n || left.id - right.id;\n}\n\nfunction comparePairs(left: CohesionPair, right: CohesionPair): number {\n return right.cohesionGap - left.cohesionGap\n || right.similarity - left.similarity\n || Number(right.reciprocal === true) - Number(left.reciprocal === true)\n || compareFunctions(left.left, right.left)\n || compareFunctions(left.right, right.right);\n}\n\nfunction pairKey(leftId: number, rightId: number): string {\n return leftId < rightId ? `${leftId}:${rightId}` : `${rightId}:${leftId}`;\n}\n\nfunction addNeighbor(neighbors: Map<number, Set<number>>, source: number, target: number): void {\n const values = neighbors.get(source) ?? new Set<number>();\n values.add(target);\n neighbors.set(source, values);\n}\n\nfunction ratio(numerator: number, denominator: number): number {\n return denominator === 0 ? 0 : numerator / denominator;\n}\n\nfunction clamp(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n"],"mappings":"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","names":["path","path"]}
|