@omfalos/mokosh 0.5.1 → 0.5.2
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 +92 -49
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +93 -50
- package/dist/cli.mjs.map +1 -1
- package/dist/duplication-worker.js +6 -6
- package/dist/duplication-worker.js.map +1 -1
- package/dist/duplication-worker.mjs +6 -6
- package/dist/duplication-worker.mjs.map +1 -1
- package/dist/index.d.mts +988 -40
- package/dist/index.d.ts +988 -40
- package/dist/index.js +47 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +48 -40
- package/dist/index.mjs.map +1 -1
- package/dist/mcp.js +52 -43
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.mjs +52 -43
- package/dist/mcp.mjs.map +1 -1
- package/dist/parse-worker.d.mts +1 -1
- package/dist/parse-worker.d.ts +1 -1
- package/dist/parse-worker.js +6 -6
- package/dist/parse-worker.js.map +1 -1
- package/dist/parse-worker.mjs +6 -6
- package/dist/parse-worker.mjs.map +1 -1
- package/dist/{types-DWARi8gF.d.ts → types-B4jVIqMD.d.ts} +3 -0
- package/dist/{types-Ba-BjVQG.d.mts → types-qfwcXiZS.d.mts} +3 -0
- package/package.json +1 -1
- package/templates/skill/SKILL.md +1 -1
- package/templates/skill/mokosh.md +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { F as FileNode, C as CallEdge, I as ImportEdge, P as ParseResult, S as StructuredTag } from './types-
|
|
2
|
-
export { E as ExportedSymbol } from './types-
|
|
1
|
+
import { F as FileNode, C as CallEdge, I as ImportEdge, P as ParseResult, S as StructuredTag } from './types-B4jVIqMD.js';
|
|
2
|
+
export { E as ExportedSymbol } from './types-B4jVIqMD.js';
|
|
3
3
|
import { N as NormalizedToken } from './tokenizer-D9P-y7lH.js';
|
|
4
|
-
import { F as FileType, N as NodeCategory } from './parse-yamNoaoL.js';
|
|
5
|
-
export { I as ImportType
|
|
4
|
+
import { F as FileType, N as NodeCategory, T as TagKind } from './parse-yamNoaoL.js';
|
|
5
|
+
export { I as ImportType } from './parse-yamNoaoL.js';
|
|
6
6
|
|
|
7
7
|
interface SerializedGraph {
|
|
8
8
|
nodes: FileNode[];
|
|
@@ -30,6 +30,9 @@ type CycleEdgeKind = "docReference" | "samePackage";
|
|
|
30
30
|
interface FindCyclesOptions {
|
|
31
31
|
/** Edge kinds to include in the walk that are otherwise skipped (see {@link CycleEdgeKind}). */
|
|
32
32
|
includeKinds?: CycleEdgeKind[] | undefined;
|
|
33
|
+
/** When true, return every raw elementary cycle instead of collapsing cycles that share a
|
|
34
|
+
* strongly-connected component to one representative each. Default false. */
|
|
35
|
+
expandComponents?: boolean | undefined;
|
|
33
36
|
}
|
|
34
37
|
/**
|
|
35
38
|
* @description Utility for analyzing the dependency graph for cycles and unused files.
|
|
@@ -61,15 +64,55 @@ declare class GraphAnalyzer {
|
|
|
61
64
|
maxExportUsage: number;
|
|
62
65
|
tightestDep: string;
|
|
63
66
|
}>;
|
|
67
|
+
/**
|
|
68
|
+
* @description The structural-import targets of `nodePath` — the outgoing edges a cycle walk
|
|
69
|
+
* follows. Excludes unresolved (`!toPath`) and external edges always, and by default the two
|
|
70
|
+
* non-`import` synthetic edge kinds: the JVM same-package sibling clique (`isSamePackage`) and
|
|
71
|
+
* Markdown link / code-span file references (`isDocReference`, ADR-009). Both are opt-in via
|
|
72
|
+
* `include`. Shared by {@link findCycles}' DFS and its strongly-connected-component pass so
|
|
73
|
+
* the two agree on exactly which edges exist.
|
|
74
|
+
* @param nodePath - The file whose outgoing edges to resolve.
|
|
75
|
+
* @param include - Edge kinds to walk that are otherwise skipped.
|
|
76
|
+
* @returns Deduplicated target paths, in first-seen order.
|
|
77
|
+
*/
|
|
78
|
+
private traversableTargets;
|
|
64
79
|
/**
|
|
65
80
|
* @description Detects all circular import chains using DFS with a recursion-stack back-edge check.
|
|
66
81
|
* Each returned array is one cycle as an ordered list of file paths ending at the entry that closes the loop.
|
|
67
82
|
* Non-import edge kinds — the synthetic JVM same-package clique and Markdown doc references
|
|
68
83
|
* (ADR-009) — are skipped by default; pass `includeKinds` to walk them anyway.
|
|
69
|
-
*
|
|
84
|
+
*
|
|
85
|
+
* Cycles that share a strongly-connected component are collapsed to one representative each
|
|
86
|
+
* (the shortest, ties broken lexicographically). A single hub file cyclically bound to N
|
|
87
|
+
* siblings — e.g. `ItemList.tsx` importing four `*CellRenderer.tsx` that each import a type
|
|
88
|
+
* back from it — is one dependency knot to untangle, not N findings; the DFS otherwise emits
|
|
89
|
+
* one back-edge cycle per sibling. Pass `expandComponents` to get every raw elementary cycle
|
|
90
|
+
* instead.
|
|
91
|
+
* @param {FindCyclesOptions} [opts] - `includeKinds` opts specific edge kinds back into the
|
|
92
|
+
* walk; `expandComponents` disables the same-component collapse.
|
|
70
93
|
* @returns {string[][]} Array of cycles; each cycle is an ordered list of file paths forming a loop.
|
|
71
94
|
*/
|
|
72
95
|
findCycles(opts?: FindCyclesOptions): string[][];
|
|
96
|
+
/**
|
|
97
|
+
* @description Collapses `cycles` so that every strongly-connected component contributes at
|
|
98
|
+
* most one — its shortest cycle, ties broken by the joined path string for determinism.
|
|
99
|
+
* Every elementary cycle lies wholly within one SCC, so cycles are grouped by the SCC id of
|
|
100
|
+
* any member; a graph with no repeated-SCC cycles passes through unchanged (order preserved
|
|
101
|
+
* by first appearance).
|
|
102
|
+
* @param cycles - Raw elementary cycles from the DFS.
|
|
103
|
+
* @param include - Edge kinds in scope, so the SCC pass walks the same edges the DFS did.
|
|
104
|
+
* @returns One representative cycle per SCC that had any, in first-appearance order.
|
|
105
|
+
*/
|
|
106
|
+
private collapseByComponent;
|
|
107
|
+
/**
|
|
108
|
+
* @description Tarjan's strongly-connected-components over the cycle-walk edge set, restricted
|
|
109
|
+
* to nodes that sit in a component of size ≥ 2 (the only ones that can be on a cycle). Runs
|
|
110
|
+
* iteratively — the graphs this analyzes reach tens of thousands of nodes, past a safe
|
|
111
|
+
* recursion depth.
|
|
112
|
+
* @param include - Edge kinds to walk, matching {@link traversableTargets}.
|
|
113
|
+
* @returns Map from file path to a numeric component id, only for nodes in a non-trivial SCC.
|
|
114
|
+
*/
|
|
115
|
+
private componentIds;
|
|
73
116
|
}
|
|
74
117
|
|
|
75
118
|
/** Graph class wrapping the raw node map with DFS traversal, cycle detection, serialization, and reverse-edge helpers. */
|
|
@@ -242,6 +285,16 @@ interface MokoshConfig {
|
|
|
242
285
|
*/
|
|
243
286
|
frameworkOverrides?: Record<string, TagFramework>;
|
|
244
287
|
};
|
|
288
|
+
/**
|
|
289
|
+
* Tunes which tags count as test-selection labels for `propose_tags`, `apply_tags`,
|
|
290
|
+
* `list_tags` (default view) and the `tag:` query filter. Both lists are case-insensitive.
|
|
291
|
+
*/
|
|
292
|
+
tags?: {
|
|
293
|
+
/** Tag names to treat as noise, added to the built-in curated blocklist. */
|
|
294
|
+
blocklist?: string[];
|
|
295
|
+
/** Tag names to keep even when built-in- or user-blocked (overrides `blocklist`). */
|
|
296
|
+
allowlist?: string[];
|
|
297
|
+
};
|
|
245
298
|
/** Path to the Istanbul/v8 `coverage-summary.json` file, relative to the project root. When set, `coveragePct` is populated on each node after the graph is built. */
|
|
246
299
|
coverageReportPath?: string;
|
|
247
300
|
/** Default line-coverage threshold (0–100) used by `find_uncovered`. Defaults to `80` when not specified. */
|
|
@@ -267,6 +320,26 @@ interface MokoshConfig {
|
|
|
267
320
|
/** When true, scan generated / vendored files too (default false). Matches involving one are
|
|
268
321
|
* tagged `signals: ["generated"]`. */
|
|
269
322
|
includeGenerated?: boolean;
|
|
323
|
+
/** When true, include matches where every occurrence is in a single file (default false) —
|
|
324
|
+
* a file's own naturally repetitive structure is usually not actionable copy-paste. Matches
|
|
325
|
+
* are always tagged `signals: ["same-file"]` regardless of this setting. */
|
|
326
|
+
includeSameFile?: boolean;
|
|
327
|
+
/** When true, include matches whose occurrences are all inline SVG / SVG-shaped JSX markup
|
|
328
|
+
* (default false) — two different icons sharing a literal-normalized skeleton (block matcher)
|
|
329
|
+
* or a byte-identical `<defs>`/`<filter>` block (`defKind: "jsxElement"`), neither of which is
|
|
330
|
+
* usually an authored clone. Matches are always tagged `signals: ["svg-markup"]` regardless
|
|
331
|
+
* of this setting. */
|
|
332
|
+
includeSvgMarkup?: boolean;
|
|
333
|
+
/** When true, include matches in the `markdown` family (default false) — mirrored prose
|
|
334
|
+
* docs (README ↔ *.mdx) are not code duplication. Matches are always tagged
|
|
335
|
+
* `signals: ["docs"]` regardless. */
|
|
336
|
+
includeDocs?: boolean;
|
|
337
|
+
/** Which duplicates to surface, by test-file involvement (default `"src"`). `"src"` drops
|
|
338
|
+
* every cluster with a test-file occurrence; `"tests"` returns only substantive test
|
|
339
|
+
* clusters (shared setup/mocks/assertions, not render/snapshot skeletons); `"all"` returns
|
|
340
|
+
* everything. The `--scope` CLI flag overrides this. Matches are always tagged
|
|
341
|
+
* `signals: ["test"]` regardless of this setting. */
|
|
342
|
+
scope?: "src" | "tests" | "all";
|
|
270
343
|
};
|
|
271
344
|
}
|
|
272
345
|
/**
|
|
@@ -314,15 +387,40 @@ declare function configToGraphOptions(config: MokoshConfig | undefined): {
|
|
|
314
387
|
declare const DEFAULT_CACHE_DIR = "mokosh-cache";
|
|
315
388
|
/** Filename for the disk-persisted `find_duplicates` token cache within `DEFAULT_CACHE_DIR`. */
|
|
316
389
|
declare const DEFAULT_DUPLICATION_TOKEN_CACHE_FILE = "duplication-tokens.json";
|
|
390
|
+
/** Filename for the disk-persisted `find_duplicates` *result* cache within `DEFAULT_CACHE_DIR`
|
|
391
|
+
* (or, on a monorepo, `<pkg-slug>-duplication-result.json` per package). Holds the full
|
|
392
|
+
* `{ groups, clusters }` from the last scan, keyed by a digest of every in-scope file's
|
|
393
|
+
* mtime/size plus the output-affecting scan params — a match lets a repeat call skip the scan
|
|
394
|
+
* entirely. See `src/graph/duplication/result-cache-store.ts`. */
|
|
395
|
+
declare const DEFAULT_DUPLICATION_RESULT_CACHE_FILE = "duplication-result.json";
|
|
317
396
|
/** Filename for the disk-persisted graph cache within `DEFAULT_CACHE_DIR`. Written by the CLI
|
|
318
397
|
* (`src/cli/graph-loader.ts`) after every build; read by the MCP server (`src/mcp/cache.ts`) to
|
|
319
398
|
* seed a session's first `analyze` call so it reuses unchanged nodes instead of parsing cold. */
|
|
320
399
|
declare const DEFAULT_GRAPH_CACHE_FILE = "graph.json";
|
|
321
|
-
/**
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
400
|
+
/** Legacy filename for the single-blob disk-persisted *workspace* (monorepo) graph cache within
|
|
401
|
+
* `DEFAULT_CACHE_DIR`. Superseded by `DEFAULT_WORKSPACE_CACHE_SUBDIR` (a manifest plus one file
|
|
402
|
+
* per package) — retained only so `src/graph/workspace/disk-cache.ts` can unlink a stale copy
|
|
403
|
+
* left by an older mokosh. A 190 MB+ single file here was `JSON.parse`d whole and OOM-killed the
|
|
404
|
+
* MCP server; see the manifest layout below. */
|
|
325
405
|
declare const DEFAULT_WORKSPACE_GRAPH_CACHE_FILE = "workspace-graph.json";
|
|
406
|
+
/** Subdirectory of `DEFAULT_CACHE_DIR` holding the workspace graph cache as a `manifest.json`
|
|
407
|
+
* ("the map file") plus one `<pkg-slug>.json` per package. Each package file is parsed on its
|
|
408
|
+
* own, so peak memory on hydrate is bounded by the largest single package rather than the whole
|
|
409
|
+
* serialized workspace. Written/read by `src/graph/workspace/disk-cache.ts`, driven from
|
|
410
|
+
* `src/mcp/cache.ts`. */
|
|
411
|
+
declare const DEFAULT_WORKSPACE_CACHE_SUBDIR = "workspace";
|
|
412
|
+
/** Filename of the workspace cache manifest within `DEFAULT_WORKSPACE_CACHE_SUBDIR`: the small
|
|
413
|
+
* index that carries the layout, the root (non-package-owned) source digest, and one entry per
|
|
414
|
+
* package (name, relative root, entry points, per-package digest, node count, cache filename). */
|
|
415
|
+
declare const WORKSPACE_MANIFEST_FILE = "manifest.json";
|
|
416
|
+
/** Schema version stamped into the workspace cache manifest. A mismatch on read discards the
|
|
417
|
+
* whole cache (full rebuild) — bump this whenever the serialized `FileNode` shape or the
|
|
418
|
+
* manifest structure changes incompatibly. */
|
|
419
|
+
declare const WORKSPACE_CACHE_VERSION = 1;
|
|
420
|
+
/** A single package whose serialized node array exceeds this is not written to the workspace
|
|
421
|
+
* cache (and never read back) — that package always rebuilds. Keeps any one per-package
|
|
422
|
+
* `JSON.parse` on hydrate within a safe memory envelope. */
|
|
423
|
+
declare const MAX_PACKAGE_CACHE_BYTES: number;
|
|
326
424
|
/** Subdirectory of `DEFAULT_CACHE_DIR` holding one JSON file per commit sha — graphs built for
|
|
327
425
|
* the "other" ref in a `compareBranches` call (`src/graph/branch-graph-cache.ts`). Keyed by sha
|
|
328
426
|
* rather than branch name so entries are immutable and never need invalidation. */
|
|
@@ -391,6 +489,57 @@ interface ApiSurface {
|
|
|
391
489
|
*/
|
|
392
490
|
testFiles: string[];
|
|
393
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* A compact, token-bounded projection of an {@link ApiSurface}, suitable as the default
|
|
494
|
+
* response of a "what's the public API?" query. Every unbounded list in `ApiSurface` is
|
|
495
|
+
* replaced by a count; `publicExports` is reduced to a capped `{ name, kind }` sample plus a
|
|
496
|
+
* `byKind` histogram over the full set. `unreachableFromEntry` — the one directly actionable
|
|
497
|
+
* signal (missed entry points / dead code) — keeps its list, capped.
|
|
498
|
+
*/
|
|
499
|
+
interface ApiSurfaceSummary {
|
|
500
|
+
/** Entry points, capped (see `entryPointsTruncated`). */
|
|
501
|
+
entryPoints: string[];
|
|
502
|
+
/** True number of entry points. */
|
|
503
|
+
entryPointCount: number;
|
|
504
|
+
/** Present and `true` when `entryPoints` was truncated. */
|
|
505
|
+
entryPointsTruncated?: true;
|
|
506
|
+
/** True number of accessible public exports. */
|
|
507
|
+
publicExportCount: number;
|
|
508
|
+
/** Capped `{ name, kind }` sample of the public exports, alphabetical. */
|
|
509
|
+
publicExports: Array<{
|
|
510
|
+
name: string;
|
|
511
|
+
kind: ExportKind;
|
|
512
|
+
}>;
|
|
513
|
+
/** Present and `true` when `publicExports` was truncated. */
|
|
514
|
+
publicExportsTruncated?: true;
|
|
515
|
+
/** Count of public exports per {@link ExportKind}, over the full (un-capped) set. */
|
|
516
|
+
byKind: Partial<Record<ExportKind, number>>;
|
|
517
|
+
/** Number of non-test implementation files reachable from an entry point. */
|
|
518
|
+
internalFileCount: number;
|
|
519
|
+
/** Number of non-test files not reachable from any entry point. */
|
|
520
|
+
unreachableFromEntryCount: number;
|
|
521
|
+
/** The unreachable non-test files, capped (see `unreachableFromEntryTruncated`). */
|
|
522
|
+
unreachableFromEntry: string[];
|
|
523
|
+
/** Present and `true` when `unreachableFromEntry` was truncated. */
|
|
524
|
+
unreachableFromEntryTruncated?: true;
|
|
525
|
+
/** Number of unreachable test files. */
|
|
526
|
+
testFileCount: number;
|
|
527
|
+
/** How to obtain the full lists this summary omits. */
|
|
528
|
+
hint: string;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Reduces a full {@link ApiSurface} to a token-bounded {@link ApiSurfaceSummary}.
|
|
532
|
+
*
|
|
533
|
+
* @param {ApiSurface} surface - The full surface report from {@link buildApiSurface}.
|
|
534
|
+
* @param {{ maxExports?: number; maxUnreachable?: number }} [opts] - `maxExports` caps the
|
|
535
|
+
* `publicExports` sample (default 30; pass `Infinity` to keep all names); `maxUnreachable`
|
|
536
|
+
* caps the `unreachableFromEntry` list (default 50).
|
|
537
|
+
* @returns {ApiSurfaceSummary} The compact projection.
|
|
538
|
+
*/
|
|
539
|
+
declare function summarizeApiSurface(surface: ApiSurface, opts?: {
|
|
540
|
+
maxExports?: number;
|
|
541
|
+
maxUnreachable?: number;
|
|
542
|
+
}): ApiSurfaceSummary;
|
|
394
543
|
/**
|
|
395
544
|
* Attempts to auto-detect the primary public entry point by reading `package.json` from `root`.
|
|
396
545
|
* Handles modern conditional-exports objects as well as plain `main`/`module` fields.
|
|
@@ -544,17 +693,27 @@ declare function loadChangeImpactCache(cachePath: string): ChangeImpactCache | n
|
|
|
544
693
|
|
|
545
694
|
/**
|
|
546
695
|
* Language-family partitioning for duplicate detection. `findDuplicates` never compares files
|
|
547
|
-
* across a family boundary — see docs/adr-013-duplicate-detection-noise-reduction.md.
|
|
548
|
-
*
|
|
696
|
+
* across a family boundary — see docs/adr-013-duplicate-detection-noise-reduction.md.
|
|
697
|
+
*
|
|
698
|
+
* The original driver was CSS: style declarations share a small, finite vocabulary (property
|
|
549
699
|
* names, common value keywords) that the shared `KEYWORDS` denylist in `tokenizer.ts` doesn't
|
|
550
|
-
* cover, so unrelated
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
700
|
+
* cover, so unrelated rules with the same declaration *shape* hashed identically to unrelated
|
|
701
|
+
* code shapes. The same argument applies, more weakly, *within* code: Java, Go, and TS/JS all use
|
|
702
|
+
* C-style `if (…) { … }` / `for (…; …; …)` blocks, so a `for` loop that pushes onto a list
|
|
703
|
+
* tokenizes almost identically in all three once identifiers collapse to `ID` — a polyglot repo
|
|
704
|
+
* then reports phantom control-flow "duplicates" across languages that share nothing. So code is
|
|
705
|
+
* split by paradigm too, not lumped into one family.
|
|
706
|
+
*
|
|
707
|
+
* Grouping (not one family per `FileType`) is deliberate: JavaScript↔TypeScript and
|
|
708
|
+
* CoffeeScript/LiveScript→JavaScript ports are real, common, cross-`FileType` duplication worth
|
|
709
|
+
* detecting, so those languages share the `"js"` family. The JVM languages interoperate and share
|
|
710
|
+
* idioms, so they share `"jvm"` — and, being their own family, never match against `"js"`, which
|
|
711
|
+
* is the case that motivated this split. Splitting by family also lets later phases tune
|
|
712
|
+
* `windowSize`/`minLines`/vocabulary per family without one family's tuning affecting another's.
|
|
554
713
|
*/
|
|
555
714
|
|
|
556
715
|
/** One partition of `findDuplicates` matching — files in different families are never compared. */
|
|
557
|
-
type DuplicateFamily = "style" | "
|
|
716
|
+
type DuplicateFamily = "style" | "js" | "jvm" | "python" | "go" | "lua" | "markdown" | "gherkin" | "other";
|
|
558
717
|
|
|
559
718
|
/**
|
|
560
719
|
* Sliding-window (shingle) hashing over a normalized token stream, plus chain-merging of
|
|
@@ -572,14 +731,32 @@ interface DuplicateOccurrence {
|
|
|
572
731
|
file: string;
|
|
573
732
|
startLine: number;
|
|
574
733
|
endLine: number;
|
|
734
|
+
/** The variable/type name declared at this location. Set only on `kind: "definition"` groups
|
|
735
|
+
* (`style-vars.ts`, `type-defs.ts`) — block matches have no single declared name. */
|
|
736
|
+
name?: string | undefined;
|
|
737
|
+
/** This occurrence's own normalized value. Set only on `defKind: "cssVar"` groups tagged
|
|
738
|
+
* `signals: ["value-drift"]`, where occurrences disagree and the value itself is the point. */
|
|
739
|
+
value?: string | undefined;
|
|
575
740
|
}
|
|
576
741
|
/**
|
|
577
742
|
* Advisory tags on a {@link DuplicateGroup} that help a caller (and issue 6's query layer) judge
|
|
578
743
|
* whether a match is worth acting on: `"same-file"` — every occurrence is in one file (an
|
|
579
744
|
* internally repeated block); `"generated"` — at least one occurrence is in a file only scanned
|
|
580
|
-
* because `includeGenerated: true` was passed
|
|
581
|
-
|
|
582
|
-
|
|
745
|
+
* because `includeGenerated: true` was passed; `"value-drift"` — a `defKind: "cssVar"` group
|
|
746
|
+
* where the same variable *name* holds different values across files (as opposed to the default
|
|
747
|
+
* consolidation case: different names, same value); `"svg-markup"` — every occurrence's source
|
|
748
|
+
* span is predominantly inline SVG / SVG-shaped JSX markup (a block match where two *different*
|
|
749
|
+
* icons share a literal-normalized skeleton, or a `defKind: "jsxElement"` match on an identical
|
|
750
|
+
* `<defs>`/`<filter>` block — see `svg-markup.ts`); `"test"` — at least one occurrence is in a
|
|
751
|
+
* test file (`__tests__/`, `__mocks__/`, `*.test.*`, `*.spec.*`, `__snapshots__/`); `"docs"` —
|
|
752
|
+
* the group is in the `markdown` family (mirrored prose docs, `README.md` ↔ `*.mdx`).
|
|
753
|
+
* `"same-file"`, `"svg-markup"` and `"docs"` groups are excluded from `findDuplicates`' results
|
|
754
|
+
* by default (opt back in with `includeSameFile` / `includeSvgMarkup` / `includeDocs`); `"test"`
|
|
755
|
+
* groups are filtered by the `scope` option (see `findDuplicates`), which separates substantive
|
|
756
|
+
* shared test logic from render/snapshot skeletons. The tag is always attached regardless of any
|
|
757
|
+
* filter. Designed to grow (issue 7 adds more).
|
|
758
|
+
*/
|
|
759
|
+
type DuplicateSignal = "same-file" | "generated" | "value-drift" | "svg-markup" | "test" | "docs";
|
|
583
760
|
interface DuplicateGroup {
|
|
584
761
|
/** Every location sharing this duplicated block (two or more) — locations that pairwise
|
|
585
762
|
* chain-match are clustered into one group instead of being reported once per pair, so a
|
|
@@ -587,10 +764,20 @@ interface DuplicateGroup {
|
|
|
587
764
|
occurrences: DuplicateOccurrence[];
|
|
588
765
|
/** Line span of the largest single pairwise match clustered into this group (each pair's own
|
|
589
766
|
* span is the shorter of its two occurrences) — the best-verified size for this block, not an
|
|
590
|
-
* average or a value shrunk by a more weakly-matching cluster member.
|
|
767
|
+
* average or a value shrunk by a more weakly-matching cluster member. For `kind: "definition"`
|
|
768
|
+
* groups this is the declaration's own line span instead (1 for a single-line `cssVar` decl). */
|
|
591
769
|
lines: number;
|
|
592
|
-
/** Token-window length backing this block, after chain-merging adjacent windows.
|
|
770
|
+
/** Token-window length backing this block, after chain-merging adjacent windows. For
|
|
771
|
+
* `kind: "definition"` groups this is the member/field count instead (1 for `cssVar`). */
|
|
593
772
|
tokens: number;
|
|
773
|
+
/** Count of *logic-bearing* tokens (keywords + operators, not `ID`/`NUM`/`STR`/punctuation —
|
|
774
|
+
* see {@link isSignificantToken}) in the verified span. `findDuplicates` ranks and (with
|
|
775
|
+
* `minScore`) filters on this so a large low-information block — a Flow `type Props`, a JSX
|
|
776
|
+
* icon wrapper, a schema object literal — no longer outranks real shared logic. Set on
|
|
777
|
+
* `kind: "block"` groups by `suffix-duplicates.ts`; absent on `kind: "definition"` groups
|
|
778
|
+
* (already content-verified) — those are ranked by `lines` instead. See
|
|
779
|
+
* docs/adr-019-logic-token-scoring.md. */
|
|
780
|
+
score?: number | undefined;
|
|
594
781
|
/** Which language family both occurrences belong to (set by `findDuplicates`, which never
|
|
595
782
|
* matches across families — see docs/adr-013-duplicate-detection-noise-reduction.md).
|
|
596
783
|
* Absent when called directly with a token stream that isn't family-scoped, e.g. in tests. */
|
|
@@ -598,6 +785,84 @@ interface DuplicateGroup {
|
|
|
598
785
|
/** Advisory tags for filtering — see {@link DuplicateSignal}. Absent (not `[]`) when no
|
|
599
786
|
* signal applies. */
|
|
600
787
|
signals?: DuplicateSignal[] | undefined;
|
|
788
|
+
/** `"block"` (the default, and absent on every group predating this field) is a token/structural
|
|
789
|
+
* match over a span of code; `"definition"` is a declaration-level match — see `defKind`. */
|
|
790
|
+
kind?: "block" | "definition" | undefined;
|
|
791
|
+
/** What kind of declaration a `kind: "definition"` group matched on. Absent for `kind: "block"`
|
|
792
|
+
* (or absent-`kind`) groups. */
|
|
793
|
+
defKind?: "cssVar" | "interface" | "type" | "objectLiteral" | "jsxElement" | undefined;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Groups `DuplicateGroup`s that share the exact same set of files into one `DuplicateCluster`,
|
|
798
|
+
* addressing the "window-splitting" noise class `applyDominanceFilter` (suffix-duplicates.ts)
|
|
799
|
+
* deliberately doesn't touch: two files that share several genuinely non-nested matches (different
|
|
800
|
+
* start positions, no containment relationship — see
|
|
801
|
+
* docs/adr-015-suffix-array-duplicate-detection.md's addendum) still surface as one `DuplicateGroup`
|
|
802
|
+
* per match, so a single real duplication between two files can legitimately report a dozen-plus
|
|
803
|
+
* rows. A file pair matched by 14 separate groups (same two files, non-nested spans) becomes one
|
|
804
|
+
* cluster with `matchCount: 14` instead of 14 rows a caller has to manually recognize as "the same
|
|
805
|
+
* underlying duplication" — the `ContentExplorer.tsx`/`ContentPicker.js` case from the dogfooding
|
|
806
|
+
* report this responds to.
|
|
807
|
+
*
|
|
808
|
+
* **This module previously clustered by connected file-component (union-find: any two files that
|
|
809
|
+
* co-occur in some group's occurrences are connected, transitively) instead of exact file-set
|
|
810
|
+
* equality, and that was wrong — caught by a follow-up dogfood pass before anyone relied on it.**
|
|
811
|
+
* Connected-component clustering is single-linkage clustering, and single-linkage is notorious for
|
|
812
|
+
* "chaining": on a real repo, one file sharing a single incidental block each with dozens of
|
|
813
|
+
* otherwise-unrelated files is enough to transitively merge all of them into one cluster, since
|
|
814
|
+
* connectivity only ever needs one bridge edge, never a strong one. Confirmed in practice on a
|
|
815
|
+
* production monorepo: 803 files and 4,853 groups collapsed into a single "cluster" — a regression,
|
|
816
|
+
* not a summary, and strictly less useful than the flat `groups` list it was meant to compress.
|
|
817
|
+
* Exact file-set equality can't chain this way: a group between `{A, B}` and a group between
|
|
818
|
+
* `{B, C}` (B is a genuine bridge — it duplicates code with both, but that doesn't mean A and C
|
|
819
|
+
* relate to each other) land in two separate clusters, not one, because their occurrence sets
|
|
820
|
+
* differ. The trade-off this accepts: a true N-way clone family reported as several exact-N-way
|
|
821
|
+
* groups already merges correctly (same set every time); one reported as several *different-sized*
|
|
822
|
+
* subsets (a 3-file match here, a 2-file subset of it there) does not merge into one cluster. That
|
|
823
|
+
* residual case is deliberately left alone rather than resolved with more graph theory — a
|
|
824
|
+
* conservative under-merge is a far safer default than the chaining failure mode above.
|
|
825
|
+
*
|
|
826
|
+
* **Coverage.** `matchCount: 14` still reads as "14 things," not "one relationship" — the same
|
|
827
|
+
* gap SonarQube's duplication check closes by reporting a merged duplication-*density* percentage
|
|
828
|
+
* per file instead of an enumerated match list. {@link DuplicateCluster.coverage} does the same
|
|
829
|
+
* here: union each cluster's occurrence spans per file (so overlapping/adjacent sub-matches count
|
|
830
|
+
* once, not once each) and divide by that file's total line count — turning
|
|
831
|
+
* `matchCount: 14, files: ["ContentExplorer.tsx", "ContentPicker.js"]` into
|
|
832
|
+
* `"ContentExplorer.tsx: 62% (312/501 lines) duplicated with ContentPicker.js"`, which is a much
|
|
833
|
+
* more decisive triage signal than the match count alone: high coverage on both sides says "these
|
|
834
|
+
* are near-duplicate files," low coverage says "one shared helper embedded in two otherwise-
|
|
835
|
+
* unrelated files."
|
|
836
|
+
*/
|
|
837
|
+
|
|
838
|
+
interface DuplicateClusterFileCoverage {
|
|
839
|
+
file: string;
|
|
840
|
+
/** Total lines this cluster's occurrences cover in `file`, after merging overlapping/adjacent
|
|
841
|
+
* spans across every group in the cluster — never double-counts a line two groups both touch. */
|
|
842
|
+
coveredLines: number;
|
|
843
|
+
/** `file`'s total line count, when known (the caller passed `fileLineCounts` and had an entry
|
|
844
|
+
* for it) — `undefined` otherwise, e.g. a cache-hit file scanned without re-reading its source. */
|
|
845
|
+
totalLines: number | undefined;
|
|
846
|
+
/** `coveredLines / totalLines * 100`, rounded to one decimal place — `undefined` when
|
|
847
|
+
* `totalLines` isn't known or is 0. */
|
|
848
|
+
coveragePct: number | undefined;
|
|
849
|
+
}
|
|
850
|
+
interface DuplicateCluster {
|
|
851
|
+
/** The exact set of files every group in this cluster shares occurrences in, sorted. */
|
|
852
|
+
files: string[];
|
|
853
|
+
/** This cluster's member groups, largest-`lines`-first — the same `DuplicateGroup` objects
|
|
854
|
+
* `findDuplicates` would otherwise report standalone, unmodified. */
|
|
855
|
+
groups: DuplicateGroup[];
|
|
856
|
+
/** `groups.length` — how many separate matches this cluster bundles. A cluster with
|
|
857
|
+
* `matchCount: 1` is just a single group reported through the same shape for consistency. */
|
|
858
|
+
matchCount: number;
|
|
859
|
+
/** The largest `lines` value across this cluster's groups — the single best-verified size for
|
|
860
|
+
* "how big is this duplication," independent of how many rows it fragmented into. */
|
|
861
|
+
longestMatch: number;
|
|
862
|
+
/** Per-file duplication coverage, same order as `files` — see this module's top-of-file
|
|
863
|
+
* comment. Every entry has `coveredLines`; `totalLines`/`coveragePct` are only present when
|
|
864
|
+
* `buildDuplicateClusters` was given a line count for that file. */
|
|
865
|
+
coverage: DuplicateClusterFileCoverage[];
|
|
601
866
|
}
|
|
602
867
|
|
|
603
868
|
/** One file's cached tokenize result, fingerprinted by `mtime`/`size`/`ignoreLiterals` — any
|
|
@@ -658,6 +923,156 @@ declare function isGeneratedPath(relPath: string, ignoreGlobs?: readonly string[
|
|
|
658
923
|
*/
|
|
659
924
|
declare function hasGeneratedMarker(source: string): boolean;
|
|
660
925
|
|
|
926
|
+
/** The output-affecting `findDuplicates` knobs — everything that changes which groups/clusters
|
|
927
|
+
* the scan produces. Folded into the cache key via {@link duplicationResultCacheKey}; a
|
|
928
|
+
* mismatch is a cache miss. `filter` is deliberately absent — the cache only ever holds an
|
|
929
|
+
* unfiltered scan. Any new output-affecting option wired into `findDuplicates` (e.g. a future
|
|
930
|
+
* `minScore`) must be added here too, or a run that changes it will read a stale result. */
|
|
931
|
+
interface DuplicationResultParams {
|
|
932
|
+
minLines: number;
|
|
933
|
+
ignoreLiterals: boolean;
|
|
934
|
+
maxPunctuationRatio: number;
|
|
935
|
+
/** The per-scan cap passed to `findDuplicates` — the stored result holds at most this many
|
|
936
|
+
* groups per package, so a later run wanting more must re-scan. */
|
|
937
|
+
limit: number;
|
|
938
|
+
scope: string | undefined;
|
|
939
|
+
includeGenerated: boolean;
|
|
940
|
+
includeSameFile: boolean;
|
|
941
|
+
includeSvgMarkup: boolean;
|
|
942
|
+
includeDocs: boolean;
|
|
943
|
+
ignoreDirs: string[];
|
|
944
|
+
ignoreGlobs: string[];
|
|
945
|
+
}
|
|
946
|
+
interface CachedDuplicationResult {
|
|
947
|
+
/** {@link duplicationDigest} of the in-scope files at write time. */
|
|
948
|
+
digest: string;
|
|
949
|
+
/** {@link duplicationResultCacheKey} of the scan params at write time. */
|
|
950
|
+
paramsKey: string;
|
|
951
|
+
groups: DuplicateGroup[];
|
|
952
|
+
clusters: DuplicateCluster[];
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* @description One `path\0mtime\0size` line per node, sha256'd over the sorted set — the same
|
|
956
|
+
* unit `computeWorkspaceSourceDigest` (`src/index.ts`) hashes, but sourced from the in-memory
|
|
957
|
+
* graph (every `FileNode` already carries `mtime`/`size`) so no `fs.stat` walk is needed.
|
|
958
|
+
* Passing the whole node set (a superset of what `findDuplicates` actually scans) is
|
|
959
|
+
* intentional: an out-of-scope change over-invalidates, which is safe.
|
|
960
|
+
* @param {Iterable<{ path: string; mtime: number; size: number }>} nodes - Graph file nodes.
|
|
961
|
+
* @returns {string} Hex sha-256 digest.
|
|
962
|
+
*/
|
|
963
|
+
declare function duplicationDigest(nodes: Iterable<{
|
|
964
|
+
path: string;
|
|
965
|
+
mtime: number;
|
|
966
|
+
size: number;
|
|
967
|
+
}>): string;
|
|
968
|
+
/**
|
|
969
|
+
* @description Stable string form of the scan params for the cache key — keys emitted in a fixed
|
|
970
|
+
* order, arrays sorted — so equivalent params always produce the same string regardless of how
|
|
971
|
+
* the caller assembled them.
|
|
972
|
+
* @param {DuplicationResultParams} params - The output-affecting scan knobs.
|
|
973
|
+
* @returns {string} A canonical JSON string to compare byte-for-byte.
|
|
974
|
+
*/
|
|
975
|
+
declare function duplicationResultCacheKey(params: DuplicationResultParams): string;
|
|
976
|
+
/**
|
|
977
|
+
* @description Reads a `CachedDuplicationResult` written by {@link saveDuplicationResult} and
|
|
978
|
+
* returns its `{ groups, clusters }` only when the stored `digest` and `paramsKey` both still
|
|
979
|
+
* match — i.e. no in-scope file changed and the scan params are identical. Any other outcome
|
|
980
|
+
* (missing file, corrupt JSON, wrong shape, stale digest, different params) returns `null`,
|
|
981
|
+
* which the caller treats as "run the scan". Never throws.
|
|
982
|
+
* @param {string} cachePath - Path to the JSON file written by `saveDuplicationResult`.
|
|
983
|
+
* @param {string} digest - The current {@link duplicationDigest} to validate against.
|
|
984
|
+
* @param {string} paramsKey - The current {@link duplicationResultCacheKey} to validate against.
|
|
985
|
+
* @returns {{ groups: DuplicateGroup[]; clusters: DuplicateCluster[] } | null} The cached result,
|
|
986
|
+
* or `null` on any miss.
|
|
987
|
+
*/
|
|
988
|
+
declare function loadDuplicationResult(cachePath: string, digest: string, paramsKey: string): {
|
|
989
|
+
groups: DuplicateGroup[];
|
|
990
|
+
clusters: DuplicateCluster[];
|
|
991
|
+
} | null;
|
|
992
|
+
/**
|
|
993
|
+
* @description Serializes the full scan result to `cachePath` (creating parent dirs), stamped
|
|
994
|
+
* with the `digest` / `paramsKey` it is valid for. A write failure is logged to stderr and
|
|
995
|
+
* otherwise swallowed — the result cache is pure acceleration and must never fail the call
|
|
996
|
+
* that produced it.
|
|
997
|
+
* @param {string} cachePath - Destination path; parent directories are created automatically.
|
|
998
|
+
* @param {string} digest - The {@link duplicationDigest} this result was computed against.
|
|
999
|
+
* @param {string} paramsKey - The {@link duplicationResultCacheKey} this result was computed
|
|
1000
|
+
* against.
|
|
1001
|
+
* @param {readonly DuplicateGroup[]} groups - The full pre-`limit` group list from the scan.
|
|
1002
|
+
* @param {readonly DuplicateCluster[]} clusters - The full cluster list from the scan.
|
|
1003
|
+
* @returns {void}
|
|
1004
|
+
*/
|
|
1005
|
+
declare function saveDuplicationResult(cachePath: string, digest: string, paramsKey: string, groups: readonly DuplicateGroup[], clusters: readonly DuplicateCluster[]): void;
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Response-shaping helpers shared by the `find_duplicates` MCP handler and the `--find-duplicates`
|
|
1009
|
+
* CLI command: a triage-first `summary` block and the compact ("slim") group/cluster projections.
|
|
1010
|
+
* Kept separate from `index.ts` (the scan) and `dup-filter.ts` (the predicate) so both consumers
|
|
1011
|
+
* present duplicate results identically.
|
|
1012
|
+
*/
|
|
1013
|
+
|
|
1014
|
+
/** First path segment of a project-relative path (`"packages/app/x.ts"` -> `"packages"`), or
|
|
1015
|
+
* `"."` when the file sits at the repo root — the bucket key for `summary.byTopDir`. */
|
|
1016
|
+
declare function topDir(relPath: string): string;
|
|
1017
|
+
/** The triage-first `summary` block. */
|
|
1018
|
+
interface DuplicatesSummary {
|
|
1019
|
+
/** How many groups matched (post-`filter`, before any response `limit`). A lower bound when a
|
|
1020
|
+
* per-package scan cap clipped on a very large monorepo; exact otherwise. */
|
|
1021
|
+
matched: number;
|
|
1022
|
+
/** Group count per language `family`. */
|
|
1023
|
+
byFamily: Record<string, number>;
|
|
1024
|
+
/** Group count per top-level directory — a group touching two dirs counts in both. */
|
|
1025
|
+
byTopDir: Record<string, number>;
|
|
1026
|
+
/** Occurrence-signal frequency across the matched groups. */
|
|
1027
|
+
bySignal: Record<string, number>;
|
|
1028
|
+
/** Largest `lines` value among the matched groups. */
|
|
1029
|
+
largestLines: number;
|
|
1030
|
+
/** Only set on a `view: "full"` response: how many groups were dropped from the returned
|
|
1031
|
+
* `groups` list because a returned multi-member cluster already represents them (see
|
|
1032
|
+
* {@link dedupeGroupsAgainstClusters}). */
|
|
1033
|
+
clusteredGroups?: number;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* @description Drops from `groups` every group that is a member of a multi-member cluster in
|
|
1037
|
+
* `clusters` — that cluster already represents it (with a better signal: per-file `coverage`),
|
|
1038
|
+
* so returning both is redundant. Groups whose file pair matched only once (no multi-member
|
|
1039
|
+
* cluster) are kept. Used only for the `view: "full"` response, the one path that returns both
|
|
1040
|
+
* lists.
|
|
1041
|
+
* @param {readonly DuplicateGroup[]} groups - The ordered, pre-`limit` group list.
|
|
1042
|
+
* @param {readonly DuplicateCluster[]} clusters - The clusters that will be returned alongside.
|
|
1043
|
+
* @returns {DuplicateGroup[]} `groups` minus the ones folded into a returned multi-member cluster.
|
|
1044
|
+
*/
|
|
1045
|
+
declare function dedupeGroupsAgainstClusters(groups: readonly DuplicateGroup[], clusters: readonly DuplicateCluster[]): DuplicateGroup[];
|
|
1046
|
+
/**
|
|
1047
|
+
* @description Builds the {@link DuplicatesSummary} for a `find_duplicates` response — the counts
|
|
1048
|
+
* a caller reads once to decide which targeted `filter` call to make next, without pulling the
|
|
1049
|
+
* full group list.
|
|
1050
|
+
* @param {readonly DuplicateGroup[]} groups - Every group that survived the `filter` predicate,
|
|
1051
|
+
* before the response `limit` truncates the list.
|
|
1052
|
+
* @returns {DuplicatesSummary} The aggregate counts.
|
|
1053
|
+
*/
|
|
1054
|
+
declare function summarizeDuplicates(groups: readonly DuplicateGroup[]): DuplicatesSummary;
|
|
1055
|
+
/** Compact `["path:startLine-endLine", …]` rendering of a group's occurrences. */
|
|
1056
|
+
declare function slimOccurrences(group: DuplicateGroup): string[];
|
|
1057
|
+
/**
|
|
1058
|
+
* @description Slim per-group projection: size / score / classification / signals plus
|
|
1059
|
+
* `"path:start-end"` occurrence strings — no duplicated source text, no per-occurrence
|
|
1060
|
+
* metadata objects. Preserves a `package` field when `findDuplicates`' caller tagged one.
|
|
1061
|
+
* @param {DuplicateGroup} group - A finalized duplicate group.
|
|
1062
|
+
* @returns {Record<string, unknown>} The compact object.
|
|
1063
|
+
*/
|
|
1064
|
+
declare function slimDupGroup(group: DuplicateGroup): Record<string, unknown>;
|
|
1065
|
+
/**
|
|
1066
|
+
* @description Slim per-cluster projection: the file set, match count, best-verified size,
|
|
1067
|
+
* per-file coverage, and the `"path:start-end"` span of the cluster's longest match — without
|
|
1068
|
+
* the nested member-group bodies. `longestMatchAt` is what makes a slim cluster actionable on
|
|
1069
|
+
* its own: it answers "where do I look" without the caller also pulling the `groups` list.
|
|
1070
|
+
* @param {DuplicateCluster} cluster - A built cluster. `cluster.groups` is largest-`lines`-first
|
|
1071
|
+
* (see `buildDuplicateClusters`), so `groups[0]` is the longest match.
|
|
1072
|
+
* @returns {Record<string, unknown>} The compact object.
|
|
1073
|
+
*/
|
|
1074
|
+
declare function slimDupCluster(cluster: DuplicateCluster): Record<string, unknown>;
|
|
1075
|
+
|
|
661
1076
|
/** Configures whether/how tokenizing is offloaded to a `piscina` worker pool. `false` always
|
|
662
1077
|
* tokenizes in-process. */
|
|
663
1078
|
type ParallelTokenizingOption = boolean | {
|
|
@@ -691,6 +1106,52 @@ interface FindDuplicatesOptions {
|
|
|
691
1106
|
* actionable copy-paste. Set true to scan them anyway; matches involving one are tagged
|
|
692
1107
|
* `signals: ["generated"]`. See docs/adr-013-duplicate-detection-noise-reduction.md. */
|
|
693
1108
|
includeGenerated?: boolean | undefined;
|
|
1109
|
+
/** When false (default), skip matches where every occurrence is in a single file — a file's
|
|
1110
|
+
* own naturally repetitive structure (a long class with many similar methods, a Markdown doc
|
|
1111
|
+
* table, a big object literal) shows up as "duplicating itself" far more often than it
|
|
1112
|
+
* represents an actionable copy-paste bug. Dogfooding found this was the single largest noise
|
|
1113
|
+
* class in `groups` (32% in one measurement). Set true to see them anyway; they're always
|
|
1114
|
+
* tagged `signals: ["same-file"]` regardless of this flag, so a caller already relying on that
|
|
1115
|
+
* tag to filter manually is unaffected either way. Mirrors `includeGenerated`'s
|
|
1116
|
+
* default-off/opt-in-back-in shape. */
|
|
1117
|
+
includeSameFile?: boolean | undefined;
|
|
1118
|
+
/** When false (default), skip matches where every occurrence's source span is predominantly
|
|
1119
|
+
* inline SVG / SVG-shaped JSX markup. Two mechanisms produce these: the token-shingle block
|
|
1120
|
+
* matcher (under the default `ignoreLiterals: true` the `d=` path string and filter constants
|
|
1121
|
+
* that actually tell two icons apart normalize to a placeholder, so *different* icons
|
|
1122
|
+
* token-match on their shared skeleton), and the `defKind: "jsxElement"` detector (two icons
|
|
1123
|
+
* sharing a byte-identical `<defs>`/`<filter>` block — boilerplate, not an authored clone).
|
|
1124
|
+
* Both are excluded here. Set true to see them anyway; they're always tagged
|
|
1125
|
+
* `signals: ["svg-markup"]` regardless. Mirrors `includeSameFile`'s default-off/opt-in shape.
|
|
1126
|
+
* See `svg-markup.ts`. */
|
|
1127
|
+
includeSvgMarkup?: boolean | undefined;
|
|
1128
|
+
/** When false (default), skip matches in the `markdown` family — prose docs (`README.md` ↔
|
|
1129
|
+
* `*.mdx`, `*.md` ↔ `*.md`) that mirror each other are not code duplication, and their
|
|
1130
|
+
* fenced code blocks tokenize densely enough to top the score-ranked list. Set true to see
|
|
1131
|
+
* them anyway; they're always tagged `signals: ["docs"]` regardless. Mirrors
|
|
1132
|
+
* `includeSameFile`'s default-off/opt-in shape. */
|
|
1133
|
+
includeDocs?: boolean | undefined;
|
|
1134
|
+
/** Minimum logic-token score (keywords + operators in the verified span — see
|
|
1135
|
+
* {@link significantTokenCount}) for a `kind: "block"` match to be reported. Default `0`
|
|
1136
|
+
* (off): every match is still returned, but `groups` and `clusters` are now *ranked* by score
|
|
1137
|
+
* regardless. A positive value drops boilerplate-shaped blocks — a Flow `type Props`, a JSX
|
|
1138
|
+
* icon-component wrapper, a schema object literal all score in the single digits; a real
|
|
1139
|
+
* 30-line function scores 25–40. Never filters `kind: "definition"` groups (already
|
|
1140
|
+
* content-verified). See docs/adr-019-logic-token-scoring.md. */
|
|
1141
|
+
minScore?: number | undefined;
|
|
1142
|
+
/** Which duplicates to surface, by whether test files are involved (default `"src"`). A group
|
|
1143
|
+
* with any occurrence in a test file (`__tests__/`, `__mocks__/`, `*.test.*`, `*.spec.*`,
|
|
1144
|
+
* `__snapshots__/`) is always tagged `signals: ["test"]`; this option decides which of those
|
|
1145
|
+
* reach `groups`/`clusters`:
|
|
1146
|
+
* - `"src"` (default) — drop every cluster that has a test-file occurrence. Auditing product
|
|
1147
|
+
* code shouldn't be buried under `render(<Icon/>); expect(…).toMatchSnapshot()` repeated
|
|
1148
|
+
* across hundreds of icon tests.
|
|
1149
|
+
* - `"tests"` — surface *only* test clusters, and only the substantive ones: a cluster with
|
|
1150
|
+
* ≥ {@link TEST_SUBSTANTIVE_BLOCKS} distinct shared blocks across ≤ {@link
|
|
1151
|
+
* TEST_SUBSTANTIVE_MAX_FILES} files, or one block ≥ {@link TEST_SUBSTANTIVE_TOKENS} tokens —
|
|
1152
|
+
* i.e. shared setup / mocks / assertions worth a helper, not a one-block render skeleton.
|
|
1153
|
+
* - `"all"` — every cluster, test or not, subject only to the other filters. */
|
|
1154
|
+
scope?: "src" | "tests" | "all" | undefined;
|
|
694
1155
|
/** Extra generated-file patterns merged with the built-in list (from
|
|
695
1156
|
* `MokoshConfig.duplication.ignoreGlobs`). Two shapes only — `**/name/**` (path segment) and
|
|
696
1157
|
* `*.suffix` (basename) — not full glob syntax. */
|
|
@@ -705,10 +1166,33 @@ interface FindDuplicatesOptions {
|
|
|
705
1166
|
* call's) skip tokenizing entirely. Mutated in place; omit for one-shot callers (e.g. the CLI).
|
|
706
1167
|
* See {@link DuplicationTokenCache} and docs/adr-014-duplicate-detection-scale.md. */
|
|
707
1168
|
tokenCache?: DuplicationTokenCache | undefined;
|
|
1169
|
+
/** Optional `key:value` result filter (see `src/query/dup-parser.ts` / `DuplicateQuery`).
|
|
1170
|
+
* Applied as a per-group predicate *before* clustering, so `clusters` stay consistent with the
|
|
1171
|
+
* narrowed `groups`. Only the predicate keys act here — the DSL's `sort`/`limit` are the
|
|
1172
|
+
* caller's to apply (via `sortLimitDupGroups`), since on a monorepo `findDuplicates` runs
|
|
1173
|
+
* once per package and a global order/cap can only be decided after the per-package results
|
|
1174
|
+
* merge. A parse error in the string is thrown, not swallowed. */
|
|
1175
|
+
filter?: string | undefined;
|
|
708
1176
|
}
|
|
709
1177
|
interface FindDuplicatesResult {
|
|
710
1178
|
/** Duplicate blocks, largest-first, capped at `limit`. */
|
|
711
1179
|
groups: DuplicateGroup[];
|
|
1180
|
+
/** `groups` (before `limit` truncation) bucketed by *exact* occurrence file set — every group
|
|
1181
|
+
* is merged into one cluster with every other group whose occurrences touch the identical set
|
|
1182
|
+
* of files, so N separate non-nested matches between the same two files (the "window-splitting"
|
|
1183
|
+
* case, see docs/known_issues/09-duplicate-clone-family-noise.md) read as one `matchCount: N`
|
|
1184
|
+
* entry instead of N rows a caller has to manually recognize as the same underlying
|
|
1185
|
+
* duplication. Deliberately *not* transitive across partially-overlapping file sets (a group
|
|
1186
|
+
* over `{A, B}` and one over `{B, C}` land in separate clusters) — an earlier connected-
|
|
1187
|
+
* component version was chaining unrelated files through shared bridge files into one
|
|
1188
|
+
* incomprehensible supercluster on real repos; see `src/graph/duplication/clusters.ts`'s
|
|
1189
|
+
* top-of-file comment. Largest-`longestMatch`-first, capped at `limit` clusters (each cluster's
|
|
1190
|
+
* own `groups` are never truncated). No group is dropped or altered to build this — every group
|
|
1191
|
+
* in `groups` (subject to `limit`) also appears inside exactly one cluster here. Each cluster
|
|
1192
|
+
* also carries per-file duplication `coverage` — merged occurrence spans divided by that
|
|
1193
|
+
* file's total line count — so `matchCount: 14` reads as "62% of this file" rather than just
|
|
1194
|
+
* a row count; see `src/graph/duplication/clusters.ts`'s `DuplicateClusterFileCoverage`. */
|
|
1195
|
+
clusters: DuplicateCluster[];
|
|
712
1196
|
}
|
|
713
1197
|
/**
|
|
714
1198
|
* @description Scans every file already present in `graph` for cross-file (and within-file)
|
|
@@ -720,8 +1204,11 @@ interface FindDuplicatesResult {
|
|
|
720
1204
|
* identifiers (and, by default, literals) are normalized to placeholders so renamed-variable
|
|
721
1205
|
* copies still match, then a sliding token window is hashed and chain-merged into contiguous
|
|
722
1206
|
* blocks. Token-shingled files are additionally partitioned into language families
|
|
723
|
-
* ({@link getDuplicateFamily} — `"
|
|
724
|
-
*
|
|
1207
|
+
* ({@link getDuplicateFamily} — `"js"` for JS/TS/Coffee/LS, `"jvm"` for Java/Kotlin/Scala/
|
|
1208
|
+
* Groovy, `"style"` for Stylus, and one family each for Python, Go, Lua, Markdown, Gherkin) so
|
|
1209
|
+
* matching never crosses a family boundary — a `for` loop tokenizes almost identically in Java,
|
|
1210
|
+
* Go and TS, so a single `"code"` family produced phantom cross-language matches on polyglot
|
|
1211
|
+
* repos. See docs/adr-013-duplicate-detection-noise-reduction.md.
|
|
725
1212
|
* @param graph - The graph to scan; its node paths (already ignore-rule-filtered) are the file
|
|
726
1213
|
* list, re-read from disk since duplication data isn't cached on `FileNode`.
|
|
727
1214
|
* @param rootDir - Absolute project root that graph paths are relative to.
|
|
@@ -730,13 +1217,24 @@ interface FindDuplicatesResult {
|
|
|
730
1217
|
* token-shingle path only (CSS/Less/SCSS always match on literal declaration content);
|
|
731
1218
|
* `maxPunctuationRatio` gates out token-shingle blocks that are mostly object/array-literal
|
|
732
1219
|
* structural punctuation (e.g. schema/object-literal boilerplate) rather than substantive
|
|
733
|
-
* shared logic; `ignoreDirs` excludes files under matching directory names; `
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
1220
|
+
* shared logic; `ignoreDirs` excludes files under matching directory names; `includeSameFile`
|
|
1221
|
+
* controls whether same-file-only matches are returned (excluded by default — see its doc
|
|
1222
|
+
* comment); `includeSvgMarkup` likewise controls whether inline-SVG-markup matches are returned
|
|
1223
|
+
* (excluded by default — two different icons share a literal-normalized skeleton); `scope`
|
|
1224
|
+
* filters whole clusters by test-file involvement — `"src"` (default) drops every test cluster,
|
|
1225
|
+
* `"tests"` returns only the substantive ones, `"all"` returns everything; `filter` is an
|
|
1226
|
+
* optional `key:value` DSL string (see `DuplicateQuery`) applied as a per-group predicate
|
|
1227
|
+
* before clustering; `limit` caps results; `parallelTokenizing` offloads per-file tokenizing
|
|
1228
|
+
* to a worker pool once the candidate file count is large enough to be worth it. Lock files and
|
|
1229
|
+
* `type: "unknown"` nodes (non-code assets like `.svg`/`.json` pulled in via an explicit
|
|
1230
|
+
* `import`) are always excluded, independent of `ignoreDirs`.
|
|
737
1231
|
* @returns `groups` — duplicate blocks (each tagged with its `family`), two or more occurrences
|
|
738
1232
|
* per block, every block that pairwise chain-matches another clustered into one group instead
|
|
739
|
-
* of one per pair, sorted largest-first across all families
|
|
1233
|
+
* of one per pair, sorted largest-first across all families (same-file-only and svg-markup
|
|
1234
|
+
* matches excluded unless `includeSameFile` / `includeSvgMarkup`; test matches filtered per
|
|
1235
|
+
* `scope`). `clusters` — the same groups
|
|
1236
|
+
* bucketed by exact file set, with
|
|
1237
|
+
* per-file duplication coverage, see {@link FindDuplicatesResult.clusters}.
|
|
740
1238
|
*/
|
|
741
1239
|
declare function findDuplicates(graph: Graph, rootDir: string, options?: FindDuplicatesOptions): Promise<FindDuplicatesResult>;
|
|
742
1240
|
|
|
@@ -1040,6 +1538,46 @@ declare function registerMonorepoDetector(detector: MonorepoDetector): void;
|
|
|
1040
1538
|
|
|
1041
1539
|
/** WorkspaceGraph holds one per-package Graph for a monorepo and exposes cross-package blast-radius queries. */
|
|
1042
1540
|
|
|
1541
|
+
/**
|
|
1542
|
+
* @description Whether `relPath` falls under `pkg`'s own `relativeRoot` — i.e. the package owns
|
|
1543
|
+
* that file, as opposed to merely importing it across a package boundary. Each per-package
|
|
1544
|
+
* `Graph` also carries "borrowed" nodes for cross-package files it imports (so outward
|
|
1545
|
+
* traversal reaches them); this predicate is how callers that iterate every package's graph
|
|
1546
|
+
* avoid double-counting a file that two packages both import.
|
|
1547
|
+
* @param {string} relPath - A monorepo-root-relative file path.
|
|
1548
|
+
* @param {Pick<WorkspacePackage, "relativeRoot">} pkg - The package to test ownership against.
|
|
1549
|
+
* @returns {boolean} `true` if `relPath` is `pkg.relativeRoot` or sits beneath it.
|
|
1550
|
+
*/
|
|
1551
|
+
declare function packageOwnsFile(relPath: string, pkg: Pick<WorkspacePackage, "relativeRoot">): boolean;
|
|
1552
|
+
/** @description A whole-workspace `Graph` (one namespace, all packages' own nodes, cross-package
|
|
1553
|
+
* edges intact) paired with a path → owning-package-name lookup. Produced by
|
|
1554
|
+
* {@link WorkspaceGraph.flatten}. */
|
|
1555
|
+
interface FlatWorkspaceGraph {
|
|
1556
|
+
graph: Graph;
|
|
1557
|
+
packageOf: Map<string, string>;
|
|
1558
|
+
}
|
|
1559
|
+
/** @description Compact, capped cross-package blast radius — the response shape of
|
|
1560
|
+
* {@link WorkspaceGraph.summarizeAffectedAcrossPackages}. */
|
|
1561
|
+
interface WorkspaceAffectedSummary {
|
|
1562
|
+
/** The changed file the blast radius was computed for. */
|
|
1563
|
+
file: string;
|
|
1564
|
+
/** Total affected files across every package (the real count, never capped). */
|
|
1565
|
+
totalAffected: number;
|
|
1566
|
+
/** Number of distinct packages with at least one affected file. */
|
|
1567
|
+
packageCount: number;
|
|
1568
|
+
/** Per-package breakdown, sorted by `count` descending. */
|
|
1569
|
+
byPackage: Array<{
|
|
1570
|
+
package: string;
|
|
1571
|
+
/** Real affected-file count for this package. */
|
|
1572
|
+
count: number;
|
|
1573
|
+
/** Up to `maxFilesPerPackage` example paths. */
|
|
1574
|
+
sample: string[];
|
|
1575
|
+
/** `count - sample.length` — files omitted from `sample`. */
|
|
1576
|
+
more: number;
|
|
1577
|
+
}>;
|
|
1578
|
+
/** `true` if any package's `sample` was capped (`more > 0` somewhere). */
|
|
1579
|
+
truncated: boolean;
|
|
1580
|
+
}
|
|
1043
1581
|
/** @description JSON-safe snapshot of a `WorkspaceGraph`, suitable for writing to disk and restoring via `WorkspaceGraph.deserialize`. */
|
|
1044
1582
|
interface SerializedWorkspaceGraph {
|
|
1045
1583
|
monorepoRoot: string;
|
|
@@ -1062,6 +1600,9 @@ declare class WorkspaceGraph {
|
|
|
1062
1600
|
graph: Graph;
|
|
1063
1601
|
pkg: WorkspacePackage;
|
|
1064
1602
|
}>;
|
|
1603
|
+
/** Lazily-built merged view; see {@link flatten}. Never invalidated — a rebuild replaces the
|
|
1604
|
+
* whole `WorkspaceGraph` instance rather than mutating this one. */
|
|
1605
|
+
private flattened?;
|
|
1065
1606
|
/**
|
|
1066
1607
|
* @param {string} monorepoRoot - Absolute path to the monorepo root directory.
|
|
1067
1608
|
* @param {string} type - Primary detected monorepo tool (e.g. `"turborepo"`, `"pnpm"`), or `"none"`.
|
|
@@ -1084,6 +1625,19 @@ declare class WorkspaceGraph {
|
|
|
1084
1625
|
* Call once after all packages are registered.
|
|
1085
1626
|
*/
|
|
1086
1627
|
annotateCrossPackageEdges(): void;
|
|
1628
|
+
/**
|
|
1629
|
+
* @description Merges every package's own nodes into a single `Graph` sharing one path
|
|
1630
|
+
* namespace, plus a `path → package name` lookup. "Borrowed" cross-package nodes are
|
|
1631
|
+
* dropped (each is contributed by its owning package instead), so no file appears twice.
|
|
1632
|
+
* Cross-package import edges already carry real monorepo-root-relative `toPath`s, so
|
|
1633
|
+
* `Graph.traverse` and `Graph.findCycles` span package boundaries on the returned graph
|
|
1634
|
+
* with no special-casing — this is the basis for whole-workspace blast radius, call
|
|
1635
|
+
* graphs, symbol search and queries. The graph is read-only: `FileNode`s are shared by
|
|
1636
|
+
* reference with the per-package graphs, never cloned.
|
|
1637
|
+
* Result is memoized for the life of this `WorkspaceGraph`.
|
|
1638
|
+
* @returns {FlatWorkspaceGraph} The merged graph and its package lookup.
|
|
1639
|
+
*/
|
|
1640
|
+
flatten(): FlatWorkspaceGraph;
|
|
1087
1641
|
/**
|
|
1088
1642
|
* @description Returns the workspace package whose `relativeRoot` is a path prefix of `relPath`.
|
|
1089
1643
|
* @param {string} relPath - A monorepo-root-relative file path to look up.
|
|
@@ -1098,9 +1652,10 @@ declare class WorkspaceGraph {
|
|
|
1098
1652
|
getPackageDependencies(): Map<string, string[]>;
|
|
1099
1653
|
/**
|
|
1100
1654
|
* @description Cross-package blast-radius analysis. Returns every file (with its package name)
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1103
|
-
*
|
|
1655
|
+
* transitively affected if the given monorepo-root-relative path changes — a full incoming
|
|
1656
|
+
* traversal over the flattened whole-workspace graph, so it follows real import edges across
|
|
1657
|
+
* package boundaries. The full, unbounded list: on a large monorepo this can be most of the
|
|
1658
|
+
* repo, so prefer {@link summarizeAffectedAcrossPackages} for an agent-facing response.
|
|
1104
1659
|
* @param {string} relPath - Monorepo-root-relative path of the changed file.
|
|
1105
1660
|
* @returns {Array<{ file: string; package: string }>} Each affected file paired with its package name.
|
|
1106
1661
|
*/
|
|
@@ -1108,6 +1663,19 @@ declare class WorkspaceGraph {
|
|
|
1108
1663
|
file: string;
|
|
1109
1664
|
package: string;
|
|
1110
1665
|
}>;
|
|
1666
|
+
/**
|
|
1667
|
+
* @description Agent-friendly form of {@link getAffectedAcrossPackages}: the blast radius
|
|
1668
|
+
* grouped by owning package, with each package's file list capped so the response stays
|
|
1669
|
+
* small even when thousands of files are affected. Packages are sorted by affected count
|
|
1670
|
+
* (descending).
|
|
1671
|
+
* @param {string} relPath - Monorepo-root-relative path of the changed file.
|
|
1672
|
+
* @param {{ maxFilesPerPackage?: number }} [opts] - `maxFilesPerPackage` caps each package's
|
|
1673
|
+
* `sample` list (default 10; `0` = counts only, no file lists).
|
|
1674
|
+
* @returns {WorkspaceAffectedSummary} Totals plus a capped per-package breakdown.
|
|
1675
|
+
*/
|
|
1676
|
+
summarizeAffectedAcrossPackages(relPath: string, opts?: {
|
|
1677
|
+
maxFilesPerPackage?: number;
|
|
1678
|
+
}): WorkspaceAffectedSummary;
|
|
1111
1679
|
/**
|
|
1112
1680
|
* @description Serializes the workspace graph to a plain JSON-safe object.
|
|
1113
1681
|
* `root` is omitted from package entries as it is not needed after build time.
|
|
@@ -1284,6 +1852,7 @@ interface SlimNode {
|
|
|
1284
1852
|
exports: string[];
|
|
1285
1853
|
tags: string[];
|
|
1286
1854
|
importsFiles: string[];
|
|
1855
|
+
package?: string;
|
|
1287
1856
|
description?: string;
|
|
1288
1857
|
testedBy?: string[];
|
|
1289
1858
|
coveragePct?: number;
|
|
@@ -1330,6 +1899,14 @@ declare function hasCoverageData(graph: Graph): boolean;
|
|
|
1330
1899
|
* @returns Whether any node has a defined `commitCount90d`.
|
|
1331
1900
|
*/
|
|
1332
1901
|
declare function hasChurnData(graph: Graph): boolean;
|
|
1902
|
+
/**
|
|
1903
|
+
* @description Returns `true` if at least one node has a last-commit timestamp — i.e. `analyze`
|
|
1904
|
+
* ran with `gitStats` enabled. `check_doc_drift` needs this: without it `enrichDocDrift`
|
|
1905
|
+
* populates no `staleFor`, so an empty result would be indistinguishable from "no drift".
|
|
1906
|
+
* @param graph - The graph to check.
|
|
1907
|
+
* @returns Whether any node has a defined `lastCommitAt`.
|
|
1908
|
+
*/
|
|
1909
|
+
declare function hasGitTimestampData(graph: Graph): boolean;
|
|
1333
1910
|
interface RiskHotspotEntry {
|
|
1334
1911
|
file: string;
|
|
1335
1912
|
name: string;
|
|
@@ -1376,6 +1953,13 @@ interface BuildGraphAtRefOptions {
|
|
|
1376
1953
|
pathAliases?: Record<string, string[]> | undefined;
|
|
1377
1954
|
/** Extra directory names to skip during test/doc discovery, on top of the built-in list. Sourced from `MokoshConfig.ignoreDirs`. */
|
|
1378
1955
|
additionalIgnoreDirs?: string[] | undefined;
|
|
1956
|
+
/** Monorepo roots only. When set, the base-ref graph is built by calling this with the
|
|
1957
|
+
* worktree directory — the caller builds a whole `WorkspaceGraph` there and returns its
|
|
1958
|
+
* flattened `Graph`, so cross-package edges survive. Bypasses the entry-point-seeded
|
|
1959
|
+
* `GraphBuilder` path (which has no `workspaceMap` and would miss `@org/*` imports).
|
|
1960
|
+
* Injected rather than imported to keep `compare.ts` free of the `createWorkspaceGraph`
|
|
1961
|
+
* dependency cycle. */
|
|
1962
|
+
workspaceBuilder?: ((worktreeDir: string) => Promise<Graph>) | undefined;
|
|
1379
1963
|
}
|
|
1380
1964
|
/**
|
|
1381
1965
|
* @description Builds the dependency graph as it existed at `ref`, rather than the current
|
|
@@ -1560,12 +2144,89 @@ declare const EXPORT_TRACKING_TYPES: ReadonlySet<FileType>;
|
|
|
1560
2144
|
declare const IMPORT_SYMBOL_TYPES: ReadonlySet<FileType>;
|
|
1561
2145
|
/** File types whose parser records function-level call edges (`FileNode.callEdges`). */
|
|
1562
2146
|
declare const CALL_EDGE_TYPES: ReadonlySet<FileType>;
|
|
2147
|
+
/** File types whose parser populates the per-function complexity breakdown (`FileNode.functions`)
|
|
2148
|
+
* plus file-level `complexity` / `cognitiveComplexity`. */
|
|
2149
|
+
declare const FUNCTION_COMPLEXITY_TYPES: ReadonlySet<FileType>;
|
|
2150
|
+
/** File types with a dedicated test-tag strategy (`src/tags/strategies/`) — a framework-aware
|
|
2151
|
+
* applier, not the generic path-glob fallback. */
|
|
2152
|
+
declare const TEST_TAG_STRATEGY_TYPES: ReadonlySet<FileType>;
|
|
2153
|
+
/** File types the type graph (`buildTypeGraph`) can extract interfaces/classes/enums/aliases from. */
|
|
2154
|
+
declare const TYPE_GRAPH_TYPES: ReadonlySet<FileType>;
|
|
2155
|
+
/** A graph-analysis capability that only some languages' parsers feed. */
|
|
2156
|
+
type LanguageFeature = "callEdges" | "functionComplexity" | "typeGraph";
|
|
2157
|
+
/**
|
|
2158
|
+
* @description Returns an explanatory note when none of the graphs contain a language whose
|
|
2159
|
+
* parser feeds `feature` — so an empty tool result (`count: 0`) reads as "language not
|
|
2160
|
+
* supported" rather than "nothing found". Returns `undefined` when at least one supported-
|
|
2161
|
+
* language file is present (the empty result is then genuine).
|
|
2162
|
+
* @param graphs - One graph, or the per-package graphs of a workspace.
|
|
2163
|
+
* @param feature - The capability the calling tool depends on.
|
|
2164
|
+
* @returns A one-sentence note, or `undefined`.
|
|
2165
|
+
*/
|
|
2166
|
+
declare function languageSupportNote(graphs: Graph | Graph[], feature: LanguageFeature): string | undefined;
|
|
2167
|
+
/** How completely a single analysis axis is implemented for one language:
|
|
2168
|
+
* - `"full"` — implemented with language-aware handling; results are call/symbol-level accurate.
|
|
2169
|
+
* - `"partial"` — implemented but known-lossy (index-based resolution, module-level exports,
|
|
2170
|
+
* constructor-only call edges, heuristic categories, the generic token duplicate pipeline).
|
|
2171
|
+
* - `"none"` — not implemented, or not applicable to this language. */
|
|
2172
|
+
type FidelityLevel = "full" | "partial" | "none";
|
|
2173
|
+
/** Per-language fidelity across every precision-relevant analysis axis. The four axes backed by
|
|
2174
|
+
* a `*_TYPES` set above are kept in exact sync with it by a test; the rest are a maintained
|
|
2175
|
+
* judgement, cross-checked against `docs/language-support.md`. */
|
|
2176
|
+
interface LanguageFidelity {
|
|
2177
|
+
/** Raw import/require specifiers → resolved graph edges. */
|
|
2178
|
+
importResolution: FidelityLevel;
|
|
2179
|
+
/** `FileNode.exports` populated with named symbols. Backed by {@link EXPORT_TRACKING_TYPES}. */
|
|
2180
|
+
exportSymbols: FidelityLevel;
|
|
2181
|
+
/** `ImportEdge.symbols` — which names each import pulls in. Backed by {@link IMPORT_SYMBOL_TYPES}. */
|
|
2182
|
+
importSymbols: FidelityLevel;
|
|
2183
|
+
/** Function-level call edges (`FileNode.callEdges`). Backed by {@link CALL_EDGE_TYPES}. */
|
|
2184
|
+
callEdges: FidelityLevel;
|
|
2185
|
+
/** Per-function + file-level complexity. Backed by {@link FUNCTION_COMPLEXITY_TYPES}. */
|
|
2186
|
+
complexity: FidelityLevel;
|
|
2187
|
+
/** Accuracy of the `logic`/`ui`/`test`/`config`/… category classification. */
|
|
2188
|
+
category: FidelityLevel;
|
|
2189
|
+
/** Duplicate detection: `"full"` = language-aware structural comparator (CSS family),
|
|
2190
|
+
* `"partial"` = the generic cross-language token pipeline (the norm — works, no language
|
|
2191
|
+
* semantics), `"none"` = not scanned. */
|
|
2192
|
+
duplication: FidelityLevel;
|
|
2193
|
+
/** A framework-aware test-tag strategy. Backed by {@link TEST_TAG_STRATEGY_TYPES}. */
|
|
2194
|
+
testTags: FidelityLevel;
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Authoritative per-language fidelity matrix — see `docs/language-support.md` for the same table
|
|
2198
|
+
* with per-language known-limitations prose and ADR links, and `docs/known_issues/08-cross-language-reliability.md`
|
|
2199
|
+
* for the plan this closes the first slice of. Every {@link FileType} has an entry.
|
|
2200
|
+
*/
|
|
2201
|
+
declare const LANGUAGE_FIDELITY: Record<FileType, LanguageFidelity>;
|
|
2202
|
+
/**
|
|
2203
|
+
* @description Returns one advisory sentence per language present in `graphs` whose `axis`
|
|
2204
|
+
* fidelity is `"partial"` or `"none"` — so a tool whose result is real but *lossy* for the
|
|
2205
|
+
* languages in play (e.g. `get_call_graph` on a Java repo: constructors only) can say so
|
|
2206
|
+
* instead of returning a confident-looking result. Complements {@link languageSupportNote},
|
|
2207
|
+
* which only fires when the result is fully empty *and* no supported-language file is present.
|
|
2208
|
+
* @param graphs - One graph, or the per-package graphs of a workspace.
|
|
2209
|
+
* @param axis - The fidelity axis the calling tool depends on.
|
|
2210
|
+
* @returns Sorted, de-duplicated caveat sentences; empty when every present language is `"full"`
|
|
2211
|
+
* for `axis` (or the only files are markdown/unknown).
|
|
2212
|
+
*/
|
|
2213
|
+
declare function languageCaveats(graphs: Graph | Graph[], axis: keyof LanguageFidelity): string[];
|
|
2214
|
+
/**
|
|
2215
|
+
* @description Aggregates {@link languageCaveats} across the precision-relevant axes
|
|
2216
|
+
* ({@link SUMMARY_AXES}) for an `analyze` response, so a caller sees upfront — from the first
|
|
2217
|
+
* call — where results for this repo's languages will be lossy.
|
|
2218
|
+
* @param graphs - One graph, or the per-package graphs of a workspace.
|
|
2219
|
+
* @returns Sorted, de-duplicated caveat sentences; empty for an all-`full` (e.g. all-TS) repo.
|
|
2220
|
+
*/
|
|
2221
|
+
declare function languageCaveatsSummary(graphs: Graph | Graph[]): string[];
|
|
1563
2222
|
interface LanguageCoverage {
|
|
1564
2223
|
type: FileType;
|
|
1565
2224
|
fileCount: number;
|
|
1566
2225
|
exportsTracked: boolean;
|
|
1567
2226
|
importSymbolsTracked: boolean;
|
|
1568
2227
|
callEdgesTracked: boolean;
|
|
2228
|
+
/** The full per-axis fidelity for this language — see {@link LANGUAGE_FIDELITY}. */
|
|
2229
|
+
fidelity: LanguageFidelity;
|
|
1569
2230
|
}
|
|
1570
2231
|
/**
|
|
1571
2232
|
* @description Reports which precision-relevant data mokosh actually tracks for each language
|
|
@@ -1612,6 +2273,90 @@ interface SymbolMatch {
|
|
|
1612
2273
|
*/
|
|
1613
2274
|
declare function findSymbol(graph: Graph, name: string): SymbolMatch[];
|
|
1614
2275
|
|
|
2276
|
+
/** Tag kinds worth surfacing for `tag:<name>` querying by default — the shared
|
|
2277
|
+
* {@link SELECTION_TAG_KINDS} (`comment-marker` + `import`). The other kinds (`function` /
|
|
2278
|
+
* `variable` / `library`) are declaration/dependency names, not test-selection labels. */
|
|
2279
|
+
declare const SUMMARY_TAG_KINDS: readonly TagKind[];
|
|
2280
|
+
/** Default `minCount` floor — drops the single-occurrence long tail. */
|
|
2281
|
+
declare const DEFAULT_TAG_MIN_COUNT = 2;
|
|
2282
|
+
/** Default `limit` when the caller gives none. 50 count-ranked tags ≈ ~1K tokens and covers
|
|
2283
|
+
* the discovery need; the tail past it is uniformly low-count. */
|
|
2284
|
+
declare const DEFAULT_TAG_LIMIT = 50;
|
|
2285
|
+
/** Absolute ceiling on the returned `tags` array. `limit` is clamped to this; no argument
|
|
2286
|
+
* combination can exceed it. ~250 `{name,count,kinds}` entries ≈ ~1.5K tokens. */
|
|
2287
|
+
declare const TAG_RESPONSE_HARD_CAP = 250;
|
|
2288
|
+
/** One distinct tag name, aggregated across every kind and node it appears on. */
|
|
2289
|
+
interface TagAggregateEntry {
|
|
2290
|
+
name: string;
|
|
2291
|
+
/** Total node occurrences, summed across every kind this name appears with. */
|
|
2292
|
+
count: number;
|
|
2293
|
+
/** Distinct kinds seen for this name, in `TagKind` declaration order. */
|
|
2294
|
+
kinds: TagKind[];
|
|
2295
|
+
}
|
|
2296
|
+
/** The full tag inventory for one or more graphs, before any filtering. */
|
|
2297
|
+
interface TagInventory {
|
|
2298
|
+
/** name → aggregate, first-seen insertion order. */
|
|
2299
|
+
entries: Map<string, TagAggregateEntry>;
|
|
2300
|
+
/** Distinct tag *names* per kind (a name carrying two kinds counts once under each). */
|
|
2301
|
+
byKind: Partial<Record<TagKind, number>>;
|
|
2302
|
+
/** `entries.size` — distinct tag names across all kinds. */
|
|
2303
|
+
totalDistinct: number;
|
|
2304
|
+
}
|
|
2305
|
+
/** Knobs for {@link summarizeTagInventory}. Each may be `undefined` (unset) so callers can
|
|
2306
|
+
* forward optional args straight through under `exactOptionalPropertyTypes`. */
|
|
2307
|
+
interface TagSummaryOptions {
|
|
2308
|
+
/** Restrict to one kind, or `"all"` for every kind. Default: {@link SUMMARY_TAG_KINDS}. */
|
|
2309
|
+
kind?: TagKind | "all" | undefined;
|
|
2310
|
+
/** Case-insensitive substring match on the tag name. */
|
|
2311
|
+
prefix?: string | undefined;
|
|
2312
|
+
/** Minimum node count for a tag to appear. Default: {@link DEFAULT_TAG_MIN_COUNT}. */
|
|
2313
|
+
minCount?: number | undefined;
|
|
2314
|
+
/** Max tags returned. Default: {@link DEFAULT_TAG_LIMIT}; hard-capped at
|
|
2315
|
+
* {@link TAG_RESPONSE_HARD_CAP}. */
|
|
2316
|
+
limit?: number | undefined;
|
|
2317
|
+
}
|
|
2318
|
+
/** The bounded `list_tags` response. */
|
|
2319
|
+
interface TagSummary {
|
|
2320
|
+
/** Filtered, sorted (count desc, then name asc), capped. */
|
|
2321
|
+
tags: Array<{
|
|
2322
|
+
name: string;
|
|
2323
|
+
count: number;
|
|
2324
|
+
kinds: TagKind[];
|
|
2325
|
+
}>;
|
|
2326
|
+
/** `tags.length` after the cap. */
|
|
2327
|
+
count: number;
|
|
2328
|
+
/** Names passing the `kind` + `prefix` + `minCount` filter, before the cap. */
|
|
2329
|
+
matched: number;
|
|
2330
|
+
/** Distinct tag names across ALL kinds, before any filter. */
|
|
2331
|
+
totalDistinct: number;
|
|
2332
|
+
/** Distinct names per kind over the FULL inventory (always every kind). */
|
|
2333
|
+
byKind: Partial<Record<TagKind, number>>;
|
|
2334
|
+
/** Set when `matched > count` (the cap or `limit` clipped the list). */
|
|
2335
|
+
truncated?: true;
|
|
2336
|
+
/** How to narrow further. */
|
|
2337
|
+
hint: string;
|
|
2338
|
+
}
|
|
2339
|
+
/**
|
|
2340
|
+
* @description Aggregates every tag on every node of the given graphs into one inventory:
|
|
2341
|
+
* per-name occurrence count, the set of kinds each name carries, and a `byKind` histogram
|
|
2342
|
+
* of distinct names per kind. Replaces the ad-hoc count loop the MCP handler and CLI command
|
|
2343
|
+
* each used to run.
|
|
2344
|
+
* @param {readonly Graph[]} graphs - Graphs to scan (one for a single package, many for a
|
|
2345
|
+
* workspace fan-out).
|
|
2346
|
+
* @returns {TagInventory} The full, unfiltered inventory.
|
|
2347
|
+
*/
|
|
2348
|
+
declare function buildTagInventory(graphs: readonly Graph[]): TagInventory;
|
|
2349
|
+
/**
|
|
2350
|
+
* @description Filters, sorts and caps a {@link TagInventory} into the bounded `list_tags`
|
|
2351
|
+
* response. The returned `tags` array never exceeds {@link TAG_RESPONSE_HARD_CAP}, whatever
|
|
2352
|
+
* the options; `byKind` and `totalDistinct` always describe the full inventory so a caller
|
|
2353
|
+
* can see what the cap hid.
|
|
2354
|
+
* @param {TagInventory} inventory - Output of {@link buildTagInventory}.
|
|
2355
|
+
* @param {TagSummaryOptions} [opts] - `kind` / `prefix` / `minCount` / `limit` knobs.
|
|
2356
|
+
* @returns {TagSummary} The bounded response object.
|
|
2357
|
+
*/
|
|
2358
|
+
declare function summarizeTagInventory(inventory: TagInventory, opts?: TagSummaryOptions): TagSummary;
|
|
2359
|
+
|
|
1615
2360
|
/** A config matcher: substring, regex, or predicate tested against the lowercase basename. */
|
|
1616
2361
|
type ConfigMatcher = string | RegExp | ((baseName: string) => boolean);
|
|
1617
2362
|
/**
|
|
@@ -1683,6 +2428,10 @@ interface NodeQuery {
|
|
|
1683
2428
|
/** AND match — all entries must be present (use `tag:a+b` syntax in query strings). */
|
|
1684
2429
|
allTags?: string[];
|
|
1685
2430
|
path?: string;
|
|
2431
|
+
/** Exact match on the node's owning workspace package name. Prefix with `"!"` to negate.
|
|
2432
|
+
* Only meaningful when a `packageOf` lookup is supplied to `filterGraph`/`matchNode`
|
|
2433
|
+
* (i.e. querying a flattened workspace graph); ignored otherwise. */
|
|
2434
|
+
package?: string;
|
|
1686
2435
|
isExternal?: boolean;
|
|
1687
2436
|
/** Substring match on `imp.toPath` — node must import a file whose path contains this string. */
|
|
1688
2437
|
importsFile?: string;
|
|
@@ -1726,23 +2475,202 @@ interface NodeQuery {
|
|
|
1726
2475
|
/** OR-group: node matches if it satisfies ANY sub-query in this array, ANDed with all other top-level fields on this NodeQuery. Populated by `any(key:val|key:val)` syntax in query strings. */
|
|
1727
2476
|
any?: NodeQuery[];
|
|
1728
2477
|
}
|
|
2478
|
+
/** Sortable axes for `find_duplicates` results (`DuplicateQuery.sort`). */
|
|
2479
|
+
type DupSortField = "lines" | "score" | "occurrences";
|
|
2480
|
+
/**
|
|
2481
|
+
* Criteria for filtering `find_duplicates` results — the duplicate-group/cluster analogue of
|
|
2482
|
+
* {@link NodeQuery}. Parsed from a `"key:value,key:value"` string by `parseDupQuery`
|
|
2483
|
+
* (AND across keys); applied per group by `matchDupGroup`. String fields support a `"!"` prefix
|
|
2484
|
+
* for negation. A group's occurrences never span more than one language family or `FileType`
|
|
2485
|
+
* (matching never crosses a family boundary), so `family`/`type` are effectively group-level.
|
|
2486
|
+
*/
|
|
2487
|
+
interface DuplicateQuery {
|
|
2488
|
+
/** Substring match on occurrence paths — matches when *at least one* occurrence's path
|
|
2489
|
+
* contains this. `"!substr"` matches when *no* occurrence's path contains it. */
|
|
2490
|
+
path?: string;
|
|
2491
|
+
/** Substring match — matches only when *every* occurrence's path contains this (an
|
|
2492
|
+
* entirely-within-one-module duplicate). `"!substr"` negates (no occurrence contains it). */
|
|
2493
|
+
allPaths?: string;
|
|
2494
|
+
/** Exact match on the group's `family` (`js`, `jvm`, `style`, `python`, `go`, `lua`,
|
|
2495
|
+
* `markdown`, `gherkin`, `other`). `"!"` to negate. */
|
|
2496
|
+
family?: string;
|
|
2497
|
+
/** Exact match on the `FileType` shared by every occurrence (derived from each occurrence's
|
|
2498
|
+
* path). `"!lang"` matches when no occurrence is of that type. */
|
|
2499
|
+
type?: string;
|
|
2500
|
+
/** `"block"` (a span match) or `"definition"` (a declaration-level match). */
|
|
2501
|
+
kind?: string;
|
|
2502
|
+
/** Exact match on a definition group's `defKind` (`cssVar` | `interface` | `type` |
|
|
2503
|
+
* `objectLiteral` | `jsxElement`). `"!"` to negate. */
|
|
2504
|
+
defKind?: string;
|
|
2505
|
+
/** Minimum / maximum duplicated block size in lines (`DuplicateGroup.lines`). */
|
|
2506
|
+
minLines?: number;
|
|
2507
|
+
maxLines?: number;
|
|
2508
|
+
/** Minimum / maximum logic-token score (`DuplicateGroup.score`, falling back to `lines` for
|
|
2509
|
+
* definition groups, which carry no score). */
|
|
2510
|
+
minScore?: number;
|
|
2511
|
+
maxScore?: number;
|
|
2512
|
+
/** Minimum number of occurrences (`DuplicateGroup.occurrences.length`). */
|
|
2513
|
+
minOccurrences?: number;
|
|
2514
|
+
/** `true` = occurrences span ≥2 distinct files; `false` = every occurrence is in one file. */
|
|
2515
|
+
crossFile?: boolean;
|
|
2516
|
+
/** OR match across positive entries (`DuplicateSignal` names); `"!name"` entries are
|
|
2517
|
+
* mandatory exclusions, evaluated independently. */
|
|
2518
|
+
signals?: string[];
|
|
2519
|
+
/** Result ordering for the flat `groups` list. */
|
|
2520
|
+
sort?: DupSortField;
|
|
2521
|
+
/** Direction for `sort`. Defaults to `"desc"`. */
|
|
2522
|
+
sortDir?: "asc" | "desc";
|
|
2523
|
+
/** Cap on the number of results. */
|
|
2524
|
+
limit?: number;
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
/** Applies DuplicateQuery predicates to `find_duplicates` groups: path, family, type, size, score,
|
|
2528
|
+
* occurrence count, cross-file, and signal filters, plus DSL-driven sort/limit. */
|
|
2529
|
+
|
|
2530
|
+
/**
|
|
2531
|
+
* @description Whether a single {@link DuplicateGroup} satisfies every clause in `query` (AND
|
|
2532
|
+
* across keys). An empty query matches every group.
|
|
2533
|
+
* @param {DuplicateGroup} group - The group to test.
|
|
2534
|
+
* @param {DuplicateQuery} query - Parsed filter criteria.
|
|
2535
|
+
* @returns {boolean} `true` when the group passes all set criteria.
|
|
2536
|
+
*/
|
|
2537
|
+
declare function matchDupGroup(group: DuplicateGroup, query: DuplicateQuery): boolean;
|
|
2538
|
+
/**
|
|
2539
|
+
* @description Applies the DSL's `sort` / `sortDir` / `limit` to an already-filtered group list.
|
|
2540
|
+
* When no `sort` is given the input order is preserved (callers pass groups already ranked by
|
|
2541
|
+
* `findDuplicates`). Pure — returns a new array. Split from {@link applyDupQuery} so consumers
|
|
2542
|
+
* that received predicate-filtered groups from `findDuplicates` can re-order/cap them without a
|
|
2543
|
+
* redundant second predicate pass.
|
|
2544
|
+
* @param {DuplicateGroup[]} groups - Groups to order and cap.
|
|
2545
|
+
* @param {DuplicateQuery} query - Parsed criteria; only `sort`/`sortDir`/`limit` are read.
|
|
2546
|
+
* @returns {DuplicateGroup[]} The groups, ordered and capped per the query.
|
|
2547
|
+
*/
|
|
2548
|
+
declare function sortLimitDupGroups(groups: DuplicateGroup[], query: DuplicateQuery): DuplicateGroup[];
|
|
2549
|
+
/**
|
|
2550
|
+
* @description Filters `groups` by `query`, then orders and caps them via
|
|
2551
|
+
* {@link sortLimitDupGroups}. Pure — returns a new array.
|
|
2552
|
+
* @param {DuplicateGroup[]} groups - Groups to filter and shape.
|
|
2553
|
+
* @param {DuplicateQuery} query - Parsed filter criteria.
|
|
2554
|
+
* @returns {DuplicateGroup[]} The matching groups, ordered and capped per the query.
|
|
2555
|
+
*/
|
|
2556
|
+
declare function applyDupQuery(groups: DuplicateGroup[], query: DuplicateQuery): DuplicateGroup[];
|
|
2557
|
+
|
|
2558
|
+
/** Parses a `key:value` filter string for `find_duplicates` results into a structured DuplicateQuery. */
|
|
2559
|
+
|
|
2560
|
+
/**
|
|
2561
|
+
* @description Parses a `"key:value,key:value"` filter string (AND across keys) into a
|
|
2562
|
+
* structured {@link DuplicateQuery} for use with `matchDupGroup` / `applyDupQuery`. An empty
|
|
2563
|
+
* or whitespace-only string yields an empty query (matches everything).
|
|
2564
|
+
* @param {string} queryString - Comma-separated `key:value` pairs, e.g.
|
|
2565
|
+
* `"crossFile:true,type:typescript,path:!test"`.
|
|
2566
|
+
* @returns {DuplicateQuery} The structured query object.
|
|
2567
|
+
* @throws {Error} On an unknown key, a malformed clause, a non-numeric numeric value, or an
|
|
2568
|
+
* invalid `sort` value — surfaced to the caller rather than silently ignored, so a typo'd
|
|
2569
|
+
* filter fails loudly instead of returning zero groups.
|
|
2570
|
+
*/
|
|
2571
|
+
declare function parseDupQuery(queryString: string): DuplicateQuery;
|
|
1729
2572
|
|
|
1730
2573
|
/** Filters a graph by applying NodeQuery predicates: category, type, tag, path, imports, coverage, and more. */
|
|
1731
2574
|
|
|
2575
|
+
/** @description Optional extras for {@link filterGraph}. */
|
|
2576
|
+
interface FilterGraphOptions {
|
|
2577
|
+
/** Path → owning-package-name lookup, from a flattened `WorkspaceGraph`. When supplied,
|
|
2578
|
+
* enables the `package:` query key and stamps each result node with its `package`. */
|
|
2579
|
+
packageOf?: Map<string, string> | undefined;
|
|
2580
|
+
}
|
|
1732
2581
|
/**
|
|
1733
2582
|
* @description Filters a serialized graph to only nodes matching all criteria in `query`,
|
|
1734
2583
|
* then trims each node's import list to edges whose target is also in the result set.
|
|
1735
2584
|
* Optionally sorts the result and applies a `limit`.
|
|
1736
2585
|
* @param {SerializedGraph} graph - The serialized graph to filter.
|
|
1737
2586
|
* @param {NodeQuery} query - Filter criteria; omitted fields are treated as wildcards.
|
|
2587
|
+
* @param {FilterGraphOptions} [options] - Optional `packageOf` lookup for workspace-scoped queries.
|
|
1738
2588
|
* @returns {SerializedGraph} A new `SerializedGraph` containing only the matching subgraph.
|
|
1739
2589
|
*/
|
|
1740
|
-
declare function filterGraph(graph: SerializedGraph, query: NodeQuery): SerializedGraph;
|
|
2590
|
+
declare function filterGraph(graph: SerializedGraph, query: NodeQuery, options?: FilterGraphOptions): SerializedGraph;
|
|
1741
2591
|
|
|
1742
2592
|
/** Parses a key:value query string into a structured NodeQuery for use with filterGraph. */
|
|
1743
2593
|
|
|
1744
2594
|
declare function parseQuery(queryString: string): NodeQuery;
|
|
1745
2595
|
|
|
2596
|
+
/**
|
|
2597
|
+
* Shared definition of a **test-selection tag** — a tag name that is a plausible
|
|
2598
|
+
* `vitest --grep` / native-framework-tag term for "given this source change, which tests run?".
|
|
2599
|
+
*
|
|
2600
|
+
* Graph tags are produced generously (every top-level declaration name, every imported symbol,
|
|
2601
|
+
* every `@word` in a test title, every third-party package). Most of those are noise for test
|
|
2602
|
+
* selection: a `library` tag like `vitest` matches every test under grep, a `function` tag is
|
|
2603
|
+
* the name of a helper in the test file itself, `test`/`barrel` are on every node of their
|
|
2604
|
+
* kind. This module is the one place that decides which tags survive, reused by
|
|
2605
|
+
* `propose_tags` (`src/tags/proposer.ts`), `apply_tags` (`src/tags/applier.ts`),
|
|
2606
|
+
* `list_tags` (`src/graph/tag-inventory.ts`) and the `tag:` query filter
|
|
2607
|
+
* (`src/query/matchers.ts`).
|
|
2608
|
+
*
|
|
2609
|
+
* The blocklist is configured from `mokosh.config` via {@link configureTagQuality}, called by
|
|
2610
|
+
* `applyConfig` — the same reset-then-apply lifecycle as the classify registries.
|
|
2611
|
+
*/
|
|
2612
|
+
|
|
2613
|
+
/** Tag kinds that can denote a test-selection label. `function` / `variable` are declaration
|
|
2614
|
+
* names (a helper in the test file, or a symbol on a source file — neither is a grep term);
|
|
2615
|
+
* `library` is a third-party package name (present on nearly every test). `class` / `type`
|
|
2616
|
+
* are never produced. */
|
|
2617
|
+
declare const SELECTION_TAG_KINDS: readonly TagKind[];
|
|
2618
|
+
/** Tag names that are just a file's `category` echoed as a tag — present on every node of that
|
|
2619
|
+
* category, so they have zero selectivity. */
|
|
2620
|
+
declare const CATEGORY_MARKER_TAGS: ReadonlySet<string>;
|
|
2621
|
+
/** A tag name must be a bare identifier: no `@`, `:`, `/`, spaces. (Moved here from
|
|
2622
|
+
* `src/tags/applier.ts`.) */
|
|
2623
|
+
declare const VALID_TAG_NAME_RE: RegExp;
|
|
2624
|
+
/**
|
|
2625
|
+
* Curated generic names that appear in almost every codebase and carry no domain signal, so
|
|
2626
|
+
* they never make a useful test-selection tag. Deliberately **conservative** — real subsystem
|
|
2627
|
+
* names (`graph`, `parser`, `resolver`, `cache`, …) are left queryable; a project narrows
|
|
2628
|
+
* further with `mokosh.config` `tags.blocklist`. Mirrors the `rename-singles` denylist
|
|
2629
|
+
* philosophy: small, evidence-driven, extended only when a name is shown to be noise.
|
|
2630
|
+
*
|
|
2631
|
+
* Seeded from `applier.ts`'s former `GENERIC_TAG_BLOCKLIST` plus the generic offenders seen
|
|
2632
|
+
* dominating `list_tags` on real repos (structural filenames + common code words).
|
|
2633
|
+
*/
|
|
2634
|
+
declare const DEFAULT_TAG_BLOCKLIST: ReadonlySet<string>;
|
|
2635
|
+
/** User overrides from `mokosh.config` `tags`. */
|
|
2636
|
+
interface TagQualityConfig {
|
|
2637
|
+
/** Names added to {@link DEFAULT_TAG_BLOCKLIST} (case-insensitive). */
|
|
2638
|
+
blocklist?: string[];
|
|
2639
|
+
/** Names removed from the effective blocklist — kept even if built-in or user-blocked. */
|
|
2640
|
+
allowlist?: string[];
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* @description Recomputes the process-wide effective tag blocklist:
|
|
2644
|
+
* `(DEFAULT_TAG_BLOCKLIST ∪ cfg.blocklist) \ cfg.allowlist`, all lower-cased. Called by
|
|
2645
|
+
* `applyConfig` after `resetTagQuality`, so each analyzed root fully replaces the previous
|
|
2646
|
+
* config rather than accumulating.
|
|
2647
|
+
* @param {TagQualityConfig} [cfg] - The `tags` section of the loaded `mokosh.config`.
|
|
2648
|
+
*/
|
|
2649
|
+
declare function configureTagQuality(cfg?: TagQualityConfig): void;
|
|
2650
|
+
/** @description Restores the built-in blocklist, dropping any prior {@link configureTagQuality}. */
|
|
2651
|
+
declare function resetTagQuality(): void;
|
|
2652
|
+
/**
|
|
2653
|
+
* @description The kind-independent half of {@link isSelectionTag}: a bare identifier, not a
|
|
2654
|
+
* `category` echo (`test`/`barrel`), and not blocklisted. Use when you already have just a
|
|
2655
|
+
* tag name (e.g. the `list_tags` inventory aggregates names across kinds).
|
|
2656
|
+
* @param {string} name - The tag name to test.
|
|
2657
|
+
* @returns {boolean} `true` when the name is not noise.
|
|
2658
|
+
*/
|
|
2659
|
+
declare function isSelectionTagName(name: string): boolean;
|
|
2660
|
+
/**
|
|
2661
|
+
* @description Whether a tag is usable as a test-selection label: a selection kind, plus
|
|
2662
|
+
* {@link isSelectionTagName}.
|
|
2663
|
+
* @param {StructuredTag} tag - The structured tag to test.
|
|
2664
|
+
* @returns {boolean} `true` when the tag should be surfaced for test selection.
|
|
2665
|
+
*/
|
|
2666
|
+
declare function isSelectionTag(tag: StructuredTag): boolean;
|
|
2667
|
+
/**
|
|
2668
|
+
* @description Distinct names of the selection-quality tags on a node, in first-seen order.
|
|
2669
|
+
* @param {readonly StructuredTag[]} tags - A node's `tags` array.
|
|
2670
|
+
* @returns {string[]} Deduplicated selection tag names.
|
|
2671
|
+
*/
|
|
2672
|
+
declare function selectionTagNames(tags: readonly StructuredTag[]): string[];
|
|
2673
|
+
|
|
1746
2674
|
/**
|
|
1747
2675
|
* @description Result for a single file processed by {@link applyTagsToFile}.
|
|
1748
2676
|
*/
|
|
@@ -1768,10 +2696,12 @@ interface ApplyTagsResult {
|
|
|
1768
2696
|
files: ApplyTagsFileResult[];
|
|
1769
2697
|
}
|
|
1770
2698
|
/**
|
|
1771
|
-
* @description Iterates every test node in the graph,
|
|
1772
|
-
*
|
|
1773
|
-
*
|
|
1774
|
-
*
|
|
2699
|
+
* @description Iterates every test node in the graph, keeps its selection-quality tags
|
|
2700
|
+
* (see {@link isSelectionTag} — `import` filename/symbol tags and deliberate `comment-marker`
|
|
2701
|
+
* markers, minus declaration/library names, `test`/`barrel`, and blocklisted generics), then
|
|
2702
|
+
* delegates writing to the strategy selected by `mokosh.config.*` (`tagApplier.framework`,
|
|
2703
|
+
* default `"vitest"`). Non-test nodes are skipped. Assumes `applyConfig` has already run for
|
|
2704
|
+
* this root so any `tags.blocklist` / `tags.allowlist` is active.
|
|
1775
2705
|
* @param {Graph} graph - The fully-enriched dependency graph.
|
|
1776
2706
|
* @param {string} rootDir - Absolute path to the project root.
|
|
1777
2707
|
* @param {{ dryRun: boolean }} options - Pass `dryRun: true` to preview changes without disk writes.
|
|
@@ -1809,9 +2739,10 @@ interface ProposeTagsOptions {
|
|
|
1809
2739
|
* @description Proposes Vitest tags to run based on which files changed.
|
|
1810
2740
|
*
|
|
1811
2741
|
* Traverses the incoming dependency graph from each changed file. Test nodes
|
|
1812
|
-
* that can reach the changed file contribute their tags
|
|
1813
|
-
*
|
|
1814
|
-
*
|
|
2742
|
+
* that can reach the changed file contribute their **selection-quality** tags
|
|
2743
|
+
* (see {@link selectionTagNames} — drops declaration/library names, `test`/`barrel`,
|
|
2744
|
+
* and blocklisted generics). Feature hubs act as boundaries: the hub's tag is
|
|
2745
|
+
* emitted and traversal stops there, preventing combinatorial blowup in large graphs.
|
|
1815
2746
|
* @param {Graph} graph - The full project dependency graph.
|
|
1816
2747
|
* @param {string[]} changedFiles - Relative paths of files that were modified (e.g. from git diff).
|
|
1817
2748
|
* @param {ProposeTagsOptions} [options] - Optional: custom test identifier and feature-detection settings.
|
|
@@ -1867,6 +2798,23 @@ declare function computeWorkspaceSourceDigest(rootDir: string): {
|
|
|
1867
2798
|
digest: string;
|
|
1868
2799
|
files: string[];
|
|
1869
2800
|
};
|
|
2801
|
+
/**
|
|
2802
|
+
* @description Buckets the monorepo's source files by owning package and digests each bucket
|
|
2803
|
+
* independently — plus a `rootDigest` over files owned by no package (top-level configs,
|
|
2804
|
+
* lockfiles, root docs). Lets the workspace disk cache (`src/graph/workspace/disk-cache.ts`)
|
|
2805
|
+
* tell which individual packages are still current, instead of one whole-tree digest that any
|
|
2806
|
+
* single edit invalidates.
|
|
2807
|
+
* @param rootDir - Absolute monorepo root.
|
|
2808
|
+
* @param packages - Packages to bucket into; each needs `name` and `relativeRoot`.
|
|
2809
|
+
* @param files - A pre-computed `getAllProjectFiles(rootDir)` result (e.g. from
|
|
2810
|
+
* `computeWorkspaceSourceDigest`), reused so the tree is walked only once.
|
|
2811
|
+
* @returns `rootDigest` and a `packageDigests` map keyed by package name (every package in
|
|
2812
|
+
* `packages` gets an entry, even if it owns no files).
|
|
2813
|
+
*/
|
|
2814
|
+
declare function computeWorkspacePackageDigests(rootDir: string, packages: ReadonlyArray<Pick<WorkspacePackage, "name" | "relativeRoot">>, files: string[]): {
|
|
2815
|
+
rootDigest: string;
|
|
2816
|
+
packageDigests: Map<string, string>;
|
|
2817
|
+
};
|
|
1870
2818
|
/**
|
|
1871
2819
|
* @description Auto-detects the monorepo layout under `rootDir` and builds a per-package
|
|
1872
2820
|
* dependency graph, stitching them together into a single WorkspaceGraph.
|
|
@@ -1897,4 +2845,4 @@ declare function createWorkspaceGraph(rootDir: string, options?: {
|
|
|
1897
2845
|
*/
|
|
1898
2846
|
declare function getAllProjectFiles(rootDir: string, options?: ScanOptions): string[];
|
|
1899
2847
|
|
|
1900
|
-
export { type ApiSurface, type ApplyTagsFileResult, type ApplyTagsResult, type BranchComparison, type BranchComparisonSummary, type BuildGraphAtRefOptions, CALL_EDGE_TYPES, type CachedFileTokens, CallEdge, type CalleeEntry, type CallerEntry$1 as CallerEntry, type ChangeImpactCache, type CompareBranchesOptions, type ComplexFunctionEntry, type ComplexityDelta, type CoverageDelta, type CycleEdgeKind, DEFAULT_BRANCH_GRAPH_CACHE_DIR, DEFAULT_CACHE_DIR, DEFAULT_DUPLICATION_TOKEN_CACHE_FILE, DEFAULT_EXTENSIONS, DEFAULT_GRAPH_CACHE_FILE, DEFAULT_IGNORE_DIRS, DEFAULT_WORKSPACE_GRAPH_CACHE_FILE, type DependencyGraph, type DocDriftDelta, type DuplicateFamily, type DuplicateGroup, type DuplicateOccurrence, type DuplicateSignal, type DuplicationDelta, type DuplicationTokenCache, EXPORT_TRACKING_TYPES, type ExportKind, type FeatureDetectionOptions, type FeatureDomain, type FeatureGraph, type FeatureGraphOptions, type FeatureInfo, type FileDiff, FileNode, FileType, type FindComplexFunctionsOptions, type FindCyclesOptions, type FindDuplicatesOptions, type FindDuplicatesResult, type FindRiskHotspotsOptions, type FunctionCallInfo, type GetAffectedOptions, type GetCallersOptions, Graph, GraphAnalyzer, type CallerEntry as GraphCallerEntry, type GraphExporter, IMPORT_SYMBOL_TYPES, ImportEdge, type LanguageCoverage, MermaidExporter, type ModuleResponsibility, type ModuleRole, type MokoshConfig, type MonorepoDetector, type MonorepoLayout, NodeCategory, type NodeMeta, type NodeQuery, type ParallelParsingOption, type PathWithSymbols, type ProposeTagsOptions, type PublicExport, type ResponsibilityGraph, type RiskHotspotEntry, type RiskHotspotsResult, type ScanOptions, type SerializedGraph, type SerializedWorkspaceGraph, type SlimNode, type SlimSerializedGraph, type StaleReference, StructuredTag, type SummarizeOptions, type SymbolCaller, type SymbolImporter, type SymbolMatch, type SymbolPrecision, SymbolTraversalContext, type TestNodeIdentifier, type TraversalOptions, type TraversalVisitor, type TypeEdge, type TypeGraph, type TypeKind, type TypeNode, type TypeQueryResult, WorkspaceGraph, type WorkspaceLayoutPackageSummary, type WorkspaceLayoutSummary, type WorkspacePackage, type WorkspacePackageSummary, type WorkspacePackagesSummary, applyConfig, applyTags, buildApiSurface, buildChangeImpactCache, buildFeatureGraph, buildGraphAtRef, buildResponsibilityGraph, buildTypeGraph, compareBranches, computeGraphHash, computeWorkspaceSourceDigest, configToGraphOptions, createImportMap, createWorkspaceGraph, detectAllEntryPoints, detectEntryPoint, detectFeatures, detectMonorepo, filterGraph, findComplexFunctions, findDuplicates, findRiskHotspots, findSymbol, getAffected, getAllProjectFiles, getCallers, getDependencies, getDependents, getLanguageCoverage, getNodeMeta, hasChurnData, hasCoverageData, hasGeneratedMarker, isChangeImpactCacheValid, isGeneratedPath, loadChangeImpactCache, loadCoverageMap, loadMokoshConfig, loadTokenCacheFromDisk, parseQuery, proposeAffectedTests, proposeTags, queryCallGraph, queryChangeImpact, queryTypeGraph, registerConfigMatcher, registerMonorepoDetector, registerParser, registerTestLibrary, registerTestPattern, resetClassifyRegistries, saveChangeImpactCache, saveTokenCacheToDisk, slimSerialize, summarizeBranchComparison, summarizeWorkspaceLayout, summarizeWorkspacePackages, toMermaid };
|
|
2848
|
+
export { type ApiSurface, type ApiSurfaceSummary, type ApplyTagsFileResult, type ApplyTagsResult, type BranchComparison, type BranchComparisonSummary, type BuildGraphAtRefOptions, CALL_EDGE_TYPES, CATEGORY_MARKER_TAGS, type CachedDuplicationResult, type CachedFileTokens, CallEdge, type CalleeEntry, type CallerEntry$1 as CallerEntry, type ChangeImpactCache, type CompareBranchesOptions, type ComplexFunctionEntry, type ComplexityDelta, type CoverageDelta, type CycleEdgeKind, DEFAULT_BRANCH_GRAPH_CACHE_DIR, DEFAULT_CACHE_DIR, DEFAULT_DUPLICATION_RESULT_CACHE_FILE, DEFAULT_DUPLICATION_TOKEN_CACHE_FILE, DEFAULT_EXTENSIONS, DEFAULT_GRAPH_CACHE_FILE, DEFAULT_IGNORE_DIRS, DEFAULT_TAG_BLOCKLIST, DEFAULT_TAG_LIMIT, DEFAULT_TAG_MIN_COUNT, DEFAULT_WORKSPACE_CACHE_SUBDIR, DEFAULT_WORKSPACE_GRAPH_CACHE_FILE, type DependencyGraph, type DocDriftDelta, type DuplicateCluster, type DuplicateClusterFileCoverage, type DuplicateFamily, type DuplicateGroup, type DuplicateOccurrence, type DuplicateQuery, type DuplicateSignal, type DuplicatesSummary, type DuplicationDelta, type DuplicationResultParams, type DuplicationTokenCache, EXPORT_TRACKING_TYPES, type ExportKind, FUNCTION_COMPLEXITY_TYPES, type FeatureDetectionOptions, type FeatureDomain, type FeatureGraph, type FeatureGraphOptions, type FeatureInfo, type FidelityLevel, type FileDiff, FileNode, FileType, type FindComplexFunctionsOptions, type FindCyclesOptions, type FindDuplicatesOptions, type FindDuplicatesResult, type FindRiskHotspotsOptions, type FlatWorkspaceGraph, type FunctionCallInfo, type GetAffectedOptions, type GetCallersOptions, Graph, GraphAnalyzer, type CallerEntry as GraphCallerEntry, type GraphExporter, IMPORT_SYMBOL_TYPES, ImportEdge, LANGUAGE_FIDELITY, type LanguageCoverage, type LanguageFeature, type LanguageFidelity, MAX_PACKAGE_CACHE_BYTES, MermaidExporter, type ModuleResponsibility, type ModuleRole, type MokoshConfig, type MonorepoDetector, type MonorepoLayout, NodeCategory, type NodeMeta, type NodeQuery, type ParallelParsingOption, type PathWithSymbols, type ProposeTagsOptions, type PublicExport, type ResponsibilityGraph, type RiskHotspotEntry, type RiskHotspotsResult, SELECTION_TAG_KINDS, SUMMARY_TAG_KINDS, type ScanOptions, type SerializedGraph, type SerializedWorkspaceGraph, type SlimNode, type SlimSerializedGraph, type StaleReference, StructuredTag, type SummarizeOptions, type SymbolCaller, type SymbolImporter, type SymbolMatch, type SymbolPrecision, SymbolTraversalContext, TAG_RESPONSE_HARD_CAP, TEST_TAG_STRATEGY_TYPES, TYPE_GRAPH_TYPES, type TagAggregateEntry, type TagInventory, TagKind, type TagQualityConfig, type TagSummary, type TagSummaryOptions, type TestNodeIdentifier, type TraversalOptions, type TraversalVisitor, type TypeEdge, type TypeGraph, type TypeKind, type TypeNode, type TypeQueryResult, VALID_TAG_NAME_RE, WORKSPACE_CACHE_VERSION, WORKSPACE_MANIFEST_FILE, type WorkspaceAffectedSummary, WorkspaceGraph, type WorkspaceLayoutPackageSummary, type WorkspaceLayoutSummary, type WorkspacePackage, type WorkspacePackageSummary, type WorkspacePackagesSummary, applyConfig, applyDupQuery, applyTags, buildApiSurface, buildChangeImpactCache, buildFeatureGraph, buildGraphAtRef, buildResponsibilityGraph, buildTagInventory, buildTypeGraph, compareBranches, computeGraphHash, computeWorkspacePackageDigests, computeWorkspaceSourceDigest, configToGraphOptions, configureTagQuality, createImportMap, createWorkspaceGraph, dedupeGroupsAgainstClusters, detectAllEntryPoints, detectEntryPoint, detectFeatures, detectMonorepo, duplicationDigest, duplicationResultCacheKey, filterGraph, findComplexFunctions, findDuplicates, findRiskHotspots, findSymbol, getAffected, getAllProjectFiles, getCallers, getDependencies, getDependents, getLanguageCoverage, getNodeMeta, hasChurnData, hasCoverageData, hasGeneratedMarker, hasGitTimestampData, isChangeImpactCacheValid, isGeneratedPath, isSelectionTag, isSelectionTagName, languageCaveats, languageCaveatsSummary, languageSupportNote, loadChangeImpactCache, loadCoverageMap, loadDuplicationResult, loadMokoshConfig, loadTokenCacheFromDisk, matchDupGroup, packageOwnsFile, parseDupQuery, parseQuery, proposeAffectedTests, proposeTags, queryCallGraph, queryChangeImpact, queryTypeGraph, registerConfigMatcher, registerMonorepoDetector, registerParser, registerTestLibrary, registerTestPattern, resetClassifyRegistries, resetTagQuality, saveChangeImpactCache, saveDuplicationResult, saveTokenCacheToDisk, selectionTagNames, slimDupCluster, slimDupGroup, slimOccurrences, slimSerialize, sortLimitDupGroups, summarizeApiSurface, summarizeBranchComparison, summarizeDuplicates, summarizeTagInventory, summarizeWorkspaceLayout, summarizeWorkspacePackages, toMermaid, topDir };
|