@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/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.18.0" : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
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-KAPV2YMP.js");
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-YEFTNG7E.js");
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-YEFTNG7E.js");
359
- const { crossSearch } = await import("./cross-search-FVKDMGP7.js");
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-YEFTNG7E.js");
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-KAPV2YMP.js");
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-YEFTNG7E.js");
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-N6D66ASM.js";
4
- import "./chunk-6LHZCHEB.js";
3
+ } from "./chunk-HMKVMRLF.js";
4
+ import "./chunk-GBEJHETS.js";
5
5
  import "./chunk-TSURHRFF.js";
6
- import "./chunk-KQRS5P4U.js";
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-YEFTNG7E.js.map
14
+ //# sourceMappingURL=code-index-MWSJKV3G.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  crossSearch
3
- } from "./chunk-KUV6PKKL.js";
4
- import "./chunk-KQRS5P4U.js";
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-FVKDMGP7.js.map
10
+ //# sourceMappingURL=cross-search-ERBFC25T.js.map
@@ -3,7 +3,7 @@ import {
3
3
  readIndexErrorCounts,
4
4
  readIndexErrors,
5
5
  resetIndexState
6
- } from "./chunk-6LHZCHEB.js";
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-KAPV2YMP.js.map
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-N6D66ASM.js";
10
+ } from "./chunk-HMKVMRLF.js";
11
11
  import {
12
12
  readIndexErrors
13
- } from "./chunk-6LHZCHEB.js";
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-KUV6PKKL.js";
22
- import "./chunk-KQRS5P4U.js";
21
+ } from "./chunk-P57ZJREE.js";
22
+ import "./chunk-5BO2LLXC.js";
23
23
  import {
24
24
  JinaEmbeddingProvider
25
25
  } from "./chunk-IQU3YVZ3.js";
@@ -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.18.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":";;;;;;;;;;;;;;AAAA,SAAS,UAAU,YAAY;AAC/B,OAAOA,WAAU;;;ACDjB,OAAO,UAAU;AAsBjB,eAAsB,gBAAgB,SAA2D;AAC/F,QAAM,SAAS,QAAQ,OAAO,OAAO;AACrC,QAAM,UAAU,mBAAmB,MAAM;AACzC,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,eAAe,QAAQ,gBAAgB,EAAE,MAAM,MAAM;AAC3D,wBAAsB,WAAW,WAAW;AAC5C,wBAAsB,OAAO,OAAO;AACpC,wBAAsB,UAAU,UAAU;AAC1C,MAAI,CAAC,OAAO,SAAS,aAAa,KAAK,gBAAgB,MAAM,iBAAiB,GAAG;AAC/E,UAAM,IAAI,eAAe,oDAAoD;AAAA,EAC/E;AACA,MAAI,QAAQ,kBAAkB,WACxB,CAAC,OAAO,SAAS,QAAQ,aAAa,KAAK,QAAQ,iBAAiB,gBAAgB;AACxF,UAAM,IAAI,eAAe,mDAAmD;AAAA,EAC9E;AACA,QAAM,YAAY,iBAAiB,QAAQ,SAAS;AACpD,QAAM,qBAAqB,QAAQ,OAAO,aAAa,EACpD,OAAO,CAAC,aAAa,SAAS,aAAa,aAAa,CAAC,aAAa,UAAU,KAAK,SAAS,aAAa,EAAE;AAChH,QAAM,eAAe,IAAI,IAAI,mBAAmB,IAAI,CAAC,aAAa,SAAS,EAAE,CAAC;AAC9E,QAAM,mBAAmB,MAAM,QAAQ,OAAO,gBAAgB,YAAY,GACvE,OAAO,CAAC,aAAa,aAAa,IAAI,SAAS,EAAE,CAAC;AAErD,QAAM,gBAAgB,oBAAI,IAAyB;AACnD,QAAM,WAAW,CAAC,QAAQ,OAAO;AACjC,MAAI,UAAU;AACZ,UAAM,QAAQ,OAAO,uBAAuB;AAAA,MAC1C,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,IAAI,YAAY,CAAC,CAAC;AAAA,MAChD;AAAA,MACA,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,MACnD,GAAI,QAAQ,kBAAkB,EAAE,YAAY,QAAQ,gBAAgB,IAAI,CAAC;AAAA,IAC3E,CAAC;AAAA,EACH;AAGA,QAAM,cAAc,WAAW,QAAQ,OAAO,uBAAuB;AAAA,IACnE,qBAAqB,QAAQ,mBAAmB;AAAA,EAClD,CAAC,IAAI;AACL,QAAM,eAAe,CAAC,aAAkD;AACtE,mBAAe,QAAQ,MAAM;AAC7B,UAAM,QAAQ;AAAA,MACZ,qBAAqB,QAAQ,mBAAmB;AAAA,MAChD,OAAO;AAAA,MACP;AAAA,MACA,GAAI,QAAQ,kBAAkB,SAAY,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;AAAA,MACtF;AAAA,MACA,GAAI,QAAQ,cAAc,SAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,IAC5E;AACA,UAAM,WAAW,cAAc,YAAY,kBAAkB,SAAS,IAAI,KAAK,IAC3E,QAAQ,OAAO,kBAAkB,SAAS,IAAI,KAAK,GACpD,OAAO,CAAC,UAAU,aAAa,IAAI,MAAM,SAAS,EAAE,CAAC;AACxD,kBAAc,IAAI,SAAS,IAAI,IAAI,IAAI,QAAQ,IAAI,CAAC,UAAU,MAAM,SAAS,EAAE,CAAC,CAAC;AACjF,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,oBAAI,IAA2B;AAC7C,WAAS,QAAQ,GAAG,QAAQ,gBAAgB,QAAQ,SAAS,GAAG;AAC9D,UAAM,SAAS,gBAAgB,KAAK;AACpC,eAAW,SAAS,aAAa,MAAM,GAAG;AACxC,YAAM,CAAC,MAAM,KAAK,IAAI,iBAAiB,QAAQ,MAAM,QAAQ;AAC7D,YAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,EAAE;AACrC,YAAM,WAAW,MAAM,IAAI,GAAG;AAC9B,UAAI,CAAC,YAAY,MAAM,aAAa,SAAS,YAAY;AACvD,cAAM,EAAE,UAAU,WAAW,GAAG,OAAO,IAAI;AAC3C,cAAM,IAAI,KAAK,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC;AAAA,MAC3C;AAAA,IACF;AACA,YAAQ,aAAa,EAAE,WAAW,QAAQ,GAAG,OAAO,gBAAgB,OAAO,CAAC;AAAA,EAC9E;AAEA,QAAM,cAAc,CAAC,GAAG,MAAM,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS;AACpD,UAAM,gBAAgB,cAAc,IAAI,KAAK,KAAK,EAAE;AACpD,UAAM,iBAAiB,cAAc,IAAI,KAAK,MAAM,EAAE;AACtD,UAAM,aAAa,iBAAiB,iBAChC,cAAc,IAAI,KAAK,MAAM,EAAE,KAAK,eAAe,IAAI,KAAK,KAAK,EAAE,IACnE;AACJ,WAAO,UAAU,MAAM,YAAY,aAAa;AAAA,EAClD,CAAC,EAAE,KAAK,YAAY;AACpB,cAAY,QAAQ,CAAC,MAAM,UAAU;AACnC,SAAK,OAAO,QAAQ;AAAA,EACtB,CAAC;AAED,QAAM,kBAAkB,aAAa,SAAS,SAAS,aAAa,SAAS,UAAa,aAAa,cAAc;AACrH,QAAM,UAAU;AAAA,IACd,kBAAkB,eAAe;AAAA,IACjC,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,IACnB;AAAA,EACF;AACA,QAAM,gBAAgB,YAAY,MAAM,GAAG,KAAK;AAChD,QAAM,QAAQ,eAAe,iBAAiB,WAAW,EACtD,OAAO,CAAC,SAAS,KAAK,mBAAmB,KAAK,qBAAqB,KAAK,mBAAmB,CAAC,EAC5F,MAAM,GAAG,KAAK;AACjB,QAAM,SAAS,YAAY,aAAa;AACxC,SAAO;AAAA,IACL,eAAe;AAAA,IACf,YAAY;AAAA,MACV,YAAY,OAAO;AAAA,MACnB,eAAe,OAAO;AAAA,MACtB,kBAAkB,OAAO;AAAA,MACzB,oBAAoB,OAAO;AAAA,IAC7B;AAAA,IACA,YAAY;AAAA,MACV,GAAG;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,QAAQ,kBAAkB,SAAY,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;AAAA,MACtF;AAAA,MACA,GAAI,QAAQ,cAAc,SAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,MAC1E;AAAA,IACF;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,iBAAiB,UAAkB,WAAqC;AACtF,QAAM,iBAAiB,SAAS,WAAW,MAAM,GAAG;AACpD,QAAM,kBAAkB,UAAU,WAAW,MAAM,GAAG;AACtD,QAAM,gBAAgB,eAAe,cAAc;AACnD,QAAM,iBAAiB,eAAe,eAAe;AACrD,MAAI,eAAe;AACnB,SAAO,eAAe,cAAc,UAC/B,eAAe,eAAe,UAC9B,cAAc,YAAY,MAAM,eAAe,YAAY,GAAG;AACjE,oBAAgB;AAAA,EAClB;AACA,QAAM,cAAc,cAAc,MAAM,GAAG,YAAY;AACvD,QAAM,iBAAiB,YAAY,SAAS,IAAI,YAAY,KAAK,GAAG,IAAI;AACxE,QAAM,WAAW,mBAAmB;AACpC,QAAM,aAAa,WACf,IACA,cAAc,SAAS,eAAe,eAAe,SAAS;AAClE,SAAO;AAAA,IACL,UAAU,WAAW,cAAc,eAAe,IAAI,gBAAgB;AAAA,IACtE,kBAAkB,WAAW,IAAI,IAAI;AAAA,IACrC;AAAA,IACA;AAAA,IACA,gBAAgB,WAAW,cAAc,MAAM,WAAW,eAAe;AAAA,EAC3E;AACF;AAEA,SAAS,UAAU,MAAqB,YAA4B,eAAqC;AACvG,QAAM,WAAW,iBAAiB,KAAK,KAAK,MAAM,KAAK,MAAM,IAAI;AACjE,QAAM,iBAAiB,OAAO,KAAK,aAAa,kBAAkB,IAAI,cAAc;AACpF,QAAM,mBAAmB,IAAI,KAAK,IAAI,CAAC,SAAS,mBAAmB,CAAC;AACpE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA,IACZ,YAAY,KAAK;AAAA,IACjB,GAAI,KAAK,mBAAmB,SAAY,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;AAAA,IACnF,GAAI,KAAK,0BAA0B,SAAY,EAAE,uBAAuB,KAAK,sBAAsB,IAAI,CAAC;AAAA,IACxG,GAAI,KAAK,8BAA8B,SAAY,EAAE,2BAA2B,KAAK,0BAA0B,IAAI,CAAC;AAAA,IACpH;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,iBAAiB;AAAA,IAC9B;AAAA,EACF;AACF;AAEA,SAAS,iBACP,OACA,mBACA,oBACA,OAC2B;AAC3B,QAAM,cAAc,MAAM,OAAO,CAAC,KAAK,SAAS,MAAM,KAAK,gBAAgB,CAAC;AAC5E,QAAM,iBAAiB,MACpB,OAAO,CAAC,SAAS,KAAK,SAAS,aAAa,WAAW,EACvD,OAAO,CAAC,KAAK,SAAS,MAAM,KAAK,gBAAgB,CAAC;AACrD,QAAM,mBAAmB,MACtB,OAAO,CAAC,SAAS,KAAK,SAAS,aAAa,aAAa,EACzD,OAAO,CAAC,KAAK,SAAS,MAAM,KAAK,gBAAgB,CAAC;AACrD,QAAM,eAAe,MAClB,OAAO,CAAC,SAAS,KAAK,SAAS,aAAa,kBAAkB,EAC9D,OAAO,CAAC,KAAK,SAAS,MAAM,KAAK,gBAAgB,CAAC;AACrD,QAAM,mBAAmB,MAAM;AAAA,IAC7B,CAAC,KAAK,SAAS,MAAM,KAAK,iBAAiB,KAAK,SAAS;AAAA,IACzD;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,MAAM;AAAA,IACrB,eAAe,MAAM,gBAAgB,WAAW;AAAA,IAChD,iBAAiB,MAAM,kBAAkB,WAAW;AAAA,IACpD,aAAa,MAAM,cAAc,WAAW;AAAA,IAC5C,sBAAsB,MAAM,kBAAkB,WAAW;AAAA,EAC3D;AACF;AAEA,SAAS,eACP,iBACA,OACsB;AACtB,QAAM,YAAY,IAAI,IAAI,gBAAgB,IAAI,CAAC,aAAa,SAAS,EAAE,CAAC;AACxE,QAAM,SAAS,oBAAI,IAAoB;AACvC,aAAW,YAAY,gBAAiB,QAAO,IAAI,SAAS,OAAO,OAAO,IAAI,SAAS,IAAI,KAAK,KAAK,CAAC;AACtG,QAAM,UAAU,oBAAI,IAAgC;AACpD,aAAW,CAAC,UAAU,aAAa,KAAK,QAAQ;AAC9C,YAAQ,IAAI,UAAU;AAAA,MACpB,MAAM;AAAA,MACN;AAAA,MACA,kBAAkB;AAAA,MAClB,oBAAoB;AAAA,MACpB,kBAAkB;AAAA,MAClB,uBAAuB;AAAA,IACzB,CAAC;AAAA,EACH;AACA,aAAW,QAAQ,OAAO;AACxB,QAAI,UAAU,IAAI,KAAK,KAAK,EAAE,EAAG,iBAAgB,QAAQ,IAAI,KAAK,KAAK,IAAI,GAAI,KAAK,OAAO,IAAI;AAC/F,QAAI,UAAU,IAAI,KAAK,MAAM,EAAE,EAAG,iBAAgB,QAAQ,IAAI,KAAK,MAAM,IAAI,GAAI,KAAK,MAAM,IAAI;AAAA,EAClG;AACA,aAAW,UAAU,QAAQ,OAAO,GAAG;AACrC,UAAM,QAAQ,OAAO,mBAAmB,OAAO,qBAAqB,OAAO;AAC3E,WAAO,wBAAwB,MAAM,OAAO,kBAAkB,KAAK;AAAA,EACrE;AACA,SAAO,CAAC,GAAG,QAAQ,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,UAAU,MAAM,wBAAwB,KAAK,yBACjF,MAAM,mBAAmB,KAAK,oBAC9B,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC;AAC1C;AAEA,SAAS,gBACP,QACA,OACA,MACM;AACN,MAAI,KAAK,SAAS,aAAa,YAAa,QAAO,oBAAoB,KAAK;AAAA,WACnE,KAAK,SAAS,aAAa,cAAe,QAAO,sBAAsB,KAAK;AAAA,OAChF;AACH,WAAO,oBAAoB,KAAK;AAChC,4BAAwB,QAAQ,OAAO,IAAI;AAAA,EAC7C;AACF;AAEA,SAAS,wBACP,QACA,UACA,MACM;AACN,MAAI,CAAC,OAAO,0BAA0B,KAAK,cAAc,OAAO,uBAAuB,aAAa;AAClG,WAAO,yBAAyB;AAAA,MAC9B,UAAU;AAAA,MACV,YAAY,KAAK;AAAA,MACjB,GAAI,KAAK,mBAAmB,SAAY,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;AAAA,MACnF,GAAI,KAAK,0BAA0B,SAAY,EAAE,uBAAuB,KAAK,sBAAsB,IAAI,CAAC;AAAA,MACxG,GAAI,KAAK,8BAA8B,SAAY,EAAE,2BAA2B,KAAK,0BAA0B,IAAI,CAAC;AAAA,MACpH,aAAa,KAAK;AAAA,IACpB;AAAA,EACF;AACF;AAEA,SAAS,YAAY,OAAiD;AACpE,QAAM,YAAY,oBAAI,IAA6B;AACnD,QAAM,YAAY,oBAAI,IAAyB;AAC/C,aAAW,QAAQ,OAAO;AACxB,cAAU,IAAI,KAAK,KAAK,IAAI,KAAK,IAAI;AACrC,cAAU,IAAI,KAAK,MAAM,IAAI,KAAK,KAAK;AACvC,gBAAY,WAAW,KAAK,KAAK,IAAI,KAAK,MAAM,EAAE;AAClD,gBAAY,WAAW,KAAK,MAAM,IAAI,KAAK,KAAK,EAAE;AAAA,EACpD;AACA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAA0B,CAAC;AACjC,aAAW,SAAS,UAAU,KAAK,GAAG;AACpC,QAAI,KAAK,IAAI,KAAK,EAAG;AACrB,UAAM,UAAU,CAAC,KAAK;AACtB,UAAM,YAAY,oBAAI,IAAY;AAClC,WAAO,QAAQ,SAAS,GAAG;AACzB,YAAM,KAAK,QAAQ,IAAI;AACvB,UAAI,KAAK,IAAI,EAAE,EAAG;AAClB,WAAK,IAAI,EAAE;AACX,gBAAU,IAAI,EAAE;AAChB,iBAAW,YAAY,UAAU,IAAI,EAAE,KAAK,CAAC,EAAG,SAAQ,KAAK,QAAQ;AAAA,IACvE;AACA,UAAM,aAAa,MAAM,OAAO,CAAC,SAAS,UAAU,IAAI,KAAK,KAAK,EAAE,KAAK,UAAU,IAAI,KAAK,MAAM,EAAE,CAAC;AACrG,UAAM,UAAU,CAAC,GAAG,SAAS,EAAE,IAAI,CAAC,OAAO,UAAU,IAAI,EAAE,CAAE,EAAE,KAAK,gBAAgB;AACpF,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,aAAa,QAAQ;AAAA,MACrB,WAAW,IAAI,IAAI,QAAQ,IAAI,CAAC,WAAW,OAAO,IAAI,CAAC,EAAE;AAAA,MACzD,uBAAuB,KAAK,IAAI,GAAG,WAAW,IAAI,CAAC,SAAS,KAAK,UAAU,CAAC;AAAA,MAC5E,uBAAuB,KAAK,IAAI,GAAG,WAAW,IAAI,CAAC,SAAS,KAAK,UAAU,CAAC;AAAA,MAC5E,yBAAyB,KAAK,IAAI,GAAG,WAAW,IAAI,CAAC,SAAS,KAAK,SAAS,gBAAgB,CAAC;AAAA,MAC7F,oBAAoB,KAAK,IAAI,GAAG,WAAW,IAAI,CAAC,SAAS,KAAK,WAAW,CAAC;AAAA,MAC1E;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO,KAAK,CAAC,MAAM,UAAU,MAAM,qBAAqB,KAAK,sBACxD,MAAM,cAAc,KAAK,eACzB,iBAAiB,KAAK,QAAQ,CAAC,GAAI,MAAM,QAAQ,CAAC,CAAE,CAAC;AAC1D,SAAO,QAAQ,CAAC,OAAO,UAAU;AAC/B,UAAM,OAAO,QAAQ;AAAA,EACvB,CAAC;AACD,SAAO;AACT;AAEA,SAAS,eAAe,UAA4B;AAClD,QAAM,YAAY,KAAK,MAAM,QAAQ,SAAS,WAAW,MAAM,GAAG,CAAC;AACnE,SAAO,cAAc,MAAM,CAAC,IAAI,UAAU,MAAM,GAAG,EAAE,OAAO,OAAO;AACrE;AAEA,SAAS,WAAW,UAA2B;AAC7C,QAAM,WAAW,SAAS,WAAW,MAAM,GAAG;AAC9C,QAAM,aAAa,SAAS,YAAY;AACxC,QAAM,WAAW,WAAW,MAAM,GAAG;AACrC,QAAM,WAAW,SAAS,GAAG,EAAE,KAAK;AACpC,SAAO,SAAS,KAAK,CAAC,YAAY,YAAY,UAAU,YAAY,WAAW,YAAY,WAAW,KACjG,iCAAiC,KAAK,QAAQ,KAC9C,aAAa,KAAK,QAAQ,KAC1B,2CAA2C,KAAK,QAAQ,KACxD,yCAAyC,KAAK,SAAS,MAAM,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE;AACrF;AAEA,SAAS,iBAAiB,MAAuB,OAA4D;AAC3G,SAAO,iBAAiB,MAAM,KAAK,KAAK,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,OAAO,IAAI;AAC1E;AAEA,SAAS,iBAAiB,MAAuB,OAAgC;AAC/E,SAAO,KAAK,KAAK,cAAc,MAAM,IAAI,KACpC,KAAK,YAAY,MAAM,aACvB,KAAK,cAAc,MAAM,eACzB,KAAK,cAAc,cAAc,MAAM,aAAa,KACpD,KAAK,KAAK,MAAM;AACvB;AAEA,SAAS,aAAa,MAAoB,OAA6B;AACrE,SAAO,MAAM,cAAc,KAAK,eAC3B,MAAM,aAAa,KAAK,cACxB,OAAO,MAAM,eAAe,IAAI,IAAI,OAAO,KAAK,eAAe,IAAI,KACnE,iBAAiB,KAAK,MAAM,MAAM,IAAI,KACtC,iBAAiB,KAAK,OAAO,MAAM,KAAK;AAC/C;AAEA,SAAS,QAAQ,QAAgB,SAAyB;AACxD,SAAO,SAAS,UAAU,GAAG,MAAM,IAAI,OAAO,KAAK,GAAG,OAAO,IAAI,MAAM;AACzE;AAEA,SAAS,YAAY,WAAqC,QAAgB,QAAsB;AAC9F,QAAM,SAAS,UAAU,IAAI,MAAM,KAAK,oBAAI,IAAY;AACxD,SAAO,IAAI,MAAM;AACjB,YAAU,IAAI,QAAQ,MAAM;AAC9B;AAEA,SAAS,MAAM,WAAmB,aAA6B;AAC7D,SAAO,gBAAgB,IAAI,IAAI,YAAY;AAC7C;AAEA,SAAS,MAAM,OAAuB;AACpC,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AACvC;;;ADlXA,gBAAuB,YAAY,SAAgE;AACjG,QAAM,SAAS,QAAQ,UAAU,QAAQ;AACzC,QAAM,eAAe,QAAQ,OAAO,OAAO;AAC3C,QAAM,eAAe,OAAO,OAAO;AACnC,QAAM,gBAAgB,KAAK,UAAU,aAAa,gBAAgB;AAClE,QAAM,gBAAgB,KAAK,UAAU,aAAa,gBAAgB;AAClE,MAAI,kBAAkB,eAAe;AACnC,UAAM,IAAI,uBAAuB,qDAAqD;AAAA,EACxF;AACA,QAAM,UAAU;AAAA,IACd,GAAG,mBAAmB,cAAc,YAAY;AAAA,IAChD,0BAA0B,aAAa;AAAA,IACvC,0BAA0B,aAAa;AAAA,EACzC;AACA,QAAM,sBAAsB,QAAQ,mBAAmB;AAEvD,QAAM,QAAQ,QAAQ,oBAAoB;AAC1C,wBAAsB,OAAO,kBAAkB;AAC/C,QAAM,WAAW,QAAQ,YAAY;AACrC,wBAAsB,UAAU,UAAU;AAC1C,QAAM,YAAY,iBAAiB,QAAQ,SAAS;AACpD,QAAM,mBAAmB,MAAM,QAAQ,OAAO,gBAAgB,QAAQ,gBAAgB,EAAE,MAAM,MAAM,CAAC,GAClG,OAAO,CAAC,aAAa,SAAS,aAAa,aAAa,CAAC,aAAa,UAAU,KAAK,SAAS,aAAa,EAAE;AAChH,QAAM,YAAY,OAAO,cAAc,QAAQ,OAAO,aACjD,MAAM,aAAa,OAAO,SAAS,MAAM,MAAM,aAAa,QAAQ,OAAO,SAAS;AACzF,QAAM,aAAa,QAAQ,gBAAgB,MAAM,cAAc,QAAQ,OAAO,OAAO,IAAI;AACzF,QAAM,aAAa,QAAQ,gBACvB,YAAY,aAAc,MAAM,cAAc,OAAO,OAAO,IAC5D;AACJ,QAAM,cAAc,QAAQ,YAAY,CAAC,YACrC,MAAM,cAAc,QAAQ,OAAO,OAAO,MAAM,MAAM,cAAc,OAAO,OAAO,IAClF;AACJ,QAAM,iBAAiB,oBAAI,IAA6B;AACxD,QAAM,gBAAgB,CAAC,MAAc,aAAsC;AACzE,UAAM,eAAeC,MAAK,QAAQ,MAAM,QAAQ;AAChD,QAAI,SAAS,eAAe,IAAI,YAAY;AAC5C,QAAI,CAAC,QAAQ;AACX,eAAS,aAAa,YAAY;AAClC,qBAAe,IAAI,cAAc,MAAM;AAAA,IACzC;AACA,WAAO;AAAA,EACT;AACA,QAAM,6BAA6B,oBAAI,IAAsB;AAC7D,MAAI,YAAY;AACd,UAAM,cAAc,CAAC,GAAG,IAAI,IAAI,OAAO,aAAa,EAAE,IAAI,CAAC,aAAa,SAAS,IAAI,CAAC,CAAC;AACvF,UAAM,QAAQ,IAAI,YAAY,IAAI,OAAO,eAAe;AACtD,YAAM,gBAAgB,MAAM,cAAc,YAAY,UAAU;AAChE,YAAM,QAAQ,2BAA2B,IAAI,aAAa,KAAK,CAAC;AAChE,YAAM,KAAK,UAAU;AACrB,iCAA2B,IAAI,eAAe,KAAK;AAAA,IACrD,CAAC,CAAC;AAAA,EACJ;AACA,QAAM,YAAY,oBAAI,IAAY;AAClC,QAAM,WAAW,aAAa,CAAC,QAAQ,OAAO;AAC9C,MAAI,UAAU;AACZ,UAAM,QAAQ,OAAO,uBAAuB;AAAA,MAC1C,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,CAAC;AAAA,MAC5C,eAAe,QAAQ,iBAAiB;AAAA,MACxC,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,MACnD,GAAI,QAAQ,kBAAkB,EAAE,YAAY,QAAQ,gBAAgB,IAAI,CAAC;AAAA,IAC3E,CAAC;AAAA,EACH;AAGA,QAAM,cAAc,WAAW,QAAQ,OAAO,uBAAuB,EAAE,oBAAoB,CAAC,IAAI;AAChG,WAAS,QAAQ,GAAG,QAAQ,gBAAgB,QAAQ,SAAS,GAAG;AAC9D,mBAAe,QAAQ,MAAM;AAC7B,UAAM,SAAS,gBAAgB,KAAK;AACpC,UAAM,sBAAsB,aAAa,MAAM,cAAc,YAAY,OAAO,IAAI,IAAI;AACxF,UAAM,sBAAsB,sBAAsB,2BAA2B,IAAI,mBAAmB,IAAI;AACxG,UAAM,eAAe,sBAAsB,EAAE,cAAc,oBAAoB,IAAI,CAAC;AACpF,UAAM,UAAU,cAAc,YAAY,oBAAoB,QAAQ,OAAO,kBAAkB,KAAK,QAAQ,MAAM;AAClH,UAAM,aAAa,YACf,QAAQ,OAAO,IAAI;AAAA,MACnB;AAAA,MACA;AAAA,MACA,eAAe,QAAQ,iBAAiB;AAAA,MACxC,GAAI,QAAQ,kBAAkB,SAAY,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;AAAA,MACtF;AAAA,MACA,GAAI,QAAQ,cAAc,SAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,MAC1E,GAAG;AAAA,IACL,CAAC,IACC,OAAO,eAAe,QAAQ,OAAO,kBAAkB,OAAO,EAAE,GAAG;AAAA,MACnE,GAAI,sBAAsB;AAAA,QACxB,mBAAmB,QAAQ,OAAO,kBAAkB,OAAO,IAAI,aAAa;AAAA,QAC5E,uBAAuB,QAAQ,OAAO,cAAc,OAAO,IAAI;AAAA,MACjE,IAAI,CAAC;AAAA,MACL;AAAA,MACA,eAAe,QAAQ,iBAAiB;AAAA,MACxC,GAAI,QAAQ,kBAAkB,SAAY,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;AAAA,MACtF;AAAA,MACA,GAAI,QAAQ,cAAc,SAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,MAC1E,GAAG;AAAA,IACL,CAAC;AACH,UAAM,mBAAmB,QAAQ,WAC7B,WAAW,IAAI,CAAC,WAAW;AAAA,MAC3B,GAAG;AAAA,MACH,kBAAkB,iBAAiB,OAAO,MAAM,MAAM,SAAS,IAAI,EAAE,mBAAmB,OAAO,WAAW;AAAA,IAC5G,EAAE,EAAE,KAAK,CAAC,MAAM,UAAU,MAAM,mBAAmB,KAAK,oBACnD,MAAM,aAAa,KAAK,cACxB,KAAK,SAAS,KAAK,cAAc,MAAM,SAAS,IAAI,KACpD,KAAK,SAAS,YAAY,MAAM,SAAS,aACzC,KAAK,SAAS,KAAK,MAAM,SAAS,EAAE,IACvC;AACJ,UAAM,UAAU,aAAa,CAAC,QAAQ,6BAClC,iBAAiB,OAAO,CAAC,UAAU;AACnC,YAAM,OAAO,OAAO,KAAK,MAAM,SAAS,KACpC,GAAG,OAAO,EAAE,IAAI,MAAM,SAAS,EAAE,KACjC,GAAG,MAAM,SAAS,EAAE,IAAI,OAAO,EAAE;AACrC,UAAI,UAAU,IAAI,IAAI,EAAG,QAAO;AAChC,gBAAU,IAAI,IAAI;AAClB,aAAO;AAAA,IACT,CAAC,IACC;AACJ,QAAI,QAAQ,SAAS,EAAG,OAAM,EAAE,QAAQ,SAAS,QAAQ;AACzD,YAAQ,aAAa,EAAE,WAAW,QAAQ,GAAG,OAAO,gBAAgB,OAAO,CAAC;AAAA,EAC9E;AACF;AAEA,eAAe,cAAc,MAA+B;AAC1D,MAAI;AACF,WAAO,MAAM,SAAS,IAAI;AAAA,EAC5B,QAAQ;AACN,WAAOA,MAAK,QAAQ,IAAI;AAAA,EAC1B;AACF;AAEA,eAAe,aAAa,UAAmC;AAC7D,MAAI;AACF,UAAM,WAAW,MAAM,KAAK,QAAQ;AACpC,WAAO,SAAS,SAAS,GAAG,IAAI,SAAS,GAAG;AAAA,EAC9C,QAAQ;AACN,WAAO,QAAQA,MAAK,QAAQ,QAAQ,CAAC;AAAA,EACvC;AACF;","names":["path","path"]}