@titan-design/code-graph 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  import { Db, Migration } from '@titan-design/store-sqlite';
2
- import { Tree } from 'web-tree-sitter';
2
+ import { Extractor, ParsedFile } from '@titan-design/code-parser';
3
+ export { Extractor, ParsedFile, getLanguageFromPath, getSupportedLanguages, parseFile, shouldIncludeFile } from '@titan-design/code-parser';
3
4
  import { Project } from 'ts-morph';
5
+ import { c as CoEditPair, C as ChurnEntry, a as ChurnWindow } from './change-coupling-CyqHgRsm.js';
6
+ import { Embedder } from '@titan-design/embed';
4
7
 
5
8
  type NodeKind = "package" | "module" | "file" | "symbol" | "external";
6
9
  type EdgeKind = "imports" | "re-exports" | "calls" | "extends" | "implements" | "references" | "depends-on";
7
- type IdAliasReason = "rename" | "move" | "merge";
10
+ /** `requalify` maps a bare-name symbol id from before index version 0.14.0 to its scope-qualified successor. */
11
+ type IdAliasReason = "rename" | "move" | "merge" | "requalify";
8
12
  type NodeRole = "test" | "fixture" | "barrel" | "types" | "config" | "script" | "entry" | "generated" | "source";
9
13
  interface GraphNode {
10
14
  id: string;
@@ -54,6 +58,25 @@ interface FileFingerprint {
54
58
  structuralHash?: string;
55
59
  }
56
60
 
61
+ /** One metric's values over the nodes of one kind in one snapshot; `count` excludes null values. */
62
+ interface MetricAggregate {
63
+ name: string;
64
+ nodeKind: NodeKind;
65
+ count: number;
66
+ sum: number | null;
67
+ min: number | null;
68
+ max: number | null;
69
+ }
70
+ /** One node's value for one metric, joined to the node's own vocabulary columns. */
71
+ interface TopMetricRow {
72
+ nodeId: string;
73
+ name: string;
74
+ kind: string;
75
+ role: string | null;
76
+ value: number | null;
77
+ unit: string | null;
78
+ }
79
+
57
80
  interface SnapshotInsert {
58
81
  ref: string;
59
82
  commitHash?: string;
@@ -69,11 +92,18 @@ interface SnapshotInsert {
69
92
  declare class CodeGraphStore {
70
93
  readonly db: Db;
71
94
  private readonly statements;
95
+ private readonly targeted;
72
96
  constructor(db: Db);
73
97
  createSnapshot(input: SnapshotInsert): number;
74
98
  insertNodes(snapshotId: number, nodes: readonly GraphNode[]): void;
75
99
  insertEdges(snapshotId: number, edges: readonly GraphEdge[]): void;
76
100
  insertMetrics(snapshotId: number, metrics: readonly GraphMetric[]): void;
101
+ /**
102
+ * Replace every metric of one `name` for a snapshot in a single transaction —
103
+ * for an overlay such as coverage, which is re-ingested wholesale and must not
104
+ * accumulate stale rows across runs.
105
+ */
106
+ replaceMetricsByName(snapshotId: number, name: string, metrics: readonly GraphMetric[]): void;
77
107
  insertAliases(snapshotId: number, aliases: readonly IdAlias[]): void;
78
108
  insertFingerprints(snapshotId: number, fingerprints: readonly FileFingerprint[]): void;
79
109
  getSnapshot(id: number): SnapshotRow | null;
@@ -96,8 +126,31 @@ declare class CodeGraphStore {
96
126
  includeReferences?: boolean;
97
127
  }): GraphEdge[];
98
128
  listMetrics(snapshotId: number): GraphMetric[];
129
+ /** Every metric on one node, from the primary key instead of a whole-snapshot read. */
130
+ listMetricsForNode(snapshotId: number, nodeId: string): GraphMetric[];
131
+ /** Distinct metric names stored for a snapshot, ascending. */
132
+ listMetricNames(snapshotId: number): string[];
133
+ /** The highest-valued nodes for one metric, descending; `limit` defaults to 20. */
134
+ topByMetric(opts: {
135
+ snapshotId: number;
136
+ metric: string;
137
+ limit?: number;
138
+ kind?: string;
139
+ }): TopMetricRow[];
140
+ /** Edges into or out of one node; `references` edges are excluded unless asked, as in {@link listEdges}. */
141
+ listEdgesTouching(snapshotId: number, nodeId: string, opts?: {
142
+ includeReferences?: boolean;
143
+ }): GraphEdge[];
144
+ /** Count, sum, min, and max per metric name and node kind; pass `name` for one metric. */
145
+ aggregateMetrics(snapshotId: number, opts?: {
146
+ name?: string;
147
+ }): MetricAggregate[];
99
148
  listAliases(snapshotId: number): IdAlias[];
100
149
  listFingerprints(snapshotId: number): FileFingerprint[];
150
+ /** Drop whole snapshots. The domain tables carry no foreign key, so each is cleared by hand. */
151
+ deleteSnapshots(ids: readonly number[]): void;
152
+ vacuum(): void;
153
+ countRowsByTable(tables: readonly string[]): Record<string, number>;
101
154
  close(): void;
102
155
  }
103
156
  /** Open (creating if absent) a code graph database and bring it to the current schema. */
@@ -122,14 +175,37 @@ declare const KIT: {
122
175
  * `file_fingerprint` is the reuse basis the incremental indexer diffs against.
123
176
  */
124
177
  declare const DOMAIN_DDL = "\n CREATE TABLE IF NOT EXISTS edge (\n snapshot_id INTEGER NOT NULL,\n src_id TEXT NOT NULL,\n dst_id TEXT NOT NULL,\n kind TEXT NOT NULL,\n attrs TEXT,\n PRIMARY KEY (snapshot_id, src_id, dst_id, kind)\n );\n CREATE INDEX IF NOT EXISTS idx_edge_dst ON edge(snapshot_id, dst_id);\n CREATE INDEX IF NOT EXISTS idx_edge_kind ON edge(snapshot_id, kind);\n\n CREATE TABLE IF NOT EXISTS metric (\n snapshot_id INTEGER NOT NULL,\n node_id TEXT NOT NULL,\n name TEXT NOT NULL,\n value REAL,\n unit TEXT,\n PRIMARY KEY (snapshot_id, node_id, name)\n );\n CREATE INDEX IF NOT EXISTS idx_metric_name ON metric(snapshot_id, name);\n\n CREATE TABLE IF NOT EXISTS id_alias (\n snapshot_id INTEGER NOT NULL,\n old_id TEXT NOT NULL,\n new_id TEXT NOT NULL,\n reason TEXT NOT NULL,\n PRIMARY KEY (snapshot_id, old_id, new_id)\n );\n\n CREATE TABLE IF NOT EXISTS file_fingerprint (\n snapshot_id INTEGER NOT NULL,\n file_id TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n structural_hash TEXT,\n PRIMARY KEY (snapshot_id, file_id)\n );\n";
178
+ /** Every table keyed by `snapshot_id`. No DDL above declares a foreign key, so dropping a snapshot walks this list. */
179
+ declare const SNAPSHOT_SCOPED_TABLES: readonly ["node", "edge", "metric", "id_alias", "file_fingerprint"];
125
180
  declare const MIGRATIONS: Migration[];
181
+ /** The top of this package's schema. A code graph database is never shared, so this is the top of one. */
182
+ declare const SCHEMA_VERSION: number;
183
+
184
+ interface PrunePlan {
185
+ keep: SnapshotRow[];
186
+ remove: SnapshotRow[];
187
+ }
188
+ interface PruneOptions {
189
+ keep?: number;
190
+ keepRefs?: readonly string[];
191
+ }
192
+ declare function planPrune(store: CodeGraphStore, options?: PruneOptions): PrunePlan;
193
+ interface PruneResult {
194
+ plan: PrunePlan;
195
+ rowsBefore: Record<string, number>;
196
+ rowsAfter: Record<string, number>;
197
+ vacuumed: boolean;
198
+ }
199
+ declare function runPrune(store: CodeGraphStore, options?: PruneOptions & {
200
+ vacuum?: boolean;
201
+ }): PruneResult;
126
202
 
127
203
  /**
128
204
  * Bumping this invalidates every reuse basis: a snapshot written by a different
129
- * index version is never reused, so a change to node/edge shape can never be
130
- * carried forward from an incompatible graph.
205
+ * index version is never reused, so a change to node/edge shape or to a metric's
206
+ * value for the same bytes can never be carried forward from an incompatible graph.
131
207
  */
132
- declare const INDEX_VERSION = "0.11.0";
208
+ declare const INDEX_VERSION = "0.15.0";
133
209
  interface IndexOptions {
134
210
  /** Roots to walk. Node ids are still rooted at the git toplevel, so importers across roots share an id space. */
135
211
  paths: string[];
@@ -145,6 +221,14 @@ interface IndexOptions {
145
221
  * reason to disable it is to rebuild from scratch.
146
222
  */
147
223
  incremental?: boolean;
224
+ /** Git churn, recency, and ownership metrics. Defaults to `true`; history is skipped silently outside git. */
225
+ computeChurn?: boolean;
226
+ /** Primary churn window in days (default 30); scopes ownership. */
227
+ churnWindowDays?: number;
228
+ /** Windows to store churn for (default 30, 90, 180); the primary window is always included. */
229
+ churnWindows?: number[];
230
+ /** Also store an all-time `lifetime` churn and ownership window over full git history. */
231
+ lifetime?: boolean;
148
232
  }
149
233
  interface IndexResult {
150
234
  snapshotId: number;
@@ -170,23 +254,6 @@ interface IndexResult {
170
254
  */
171
255
  declare function indexPaths(store: CodeGraphStore, options: IndexOptions): Promise<IndexResult>;
172
256
 
173
- interface ParsedFile {
174
- tree: Tree;
175
- content: string;
176
- filePath: string;
177
- language: string;
178
- }
179
- interface Extractor<T> {
180
- name: string;
181
- extract(file: ParsedFile): T[];
182
- }
183
-
184
- declare function parseFile(content: string, filePath: string, language: string): Promise<ParsedFile>;
185
- declare function getSupportedLanguages(): string[];
186
-
187
- declare function shouldIncludeFile(filePath: string, languages: string[]): boolean;
188
- declare function getLanguageFromPath(filePath: string): string | null;
189
-
190
257
  interface LanguageExtractorOptions {
191
258
  repoRoot: string;
192
259
  tsConfigPath?: string;
@@ -293,15 +360,60 @@ declare class TsMorphGraphExtractor implements Extractor<GraphFragment> {
293
360
  */
294
361
  declare function buildFileModuleNodes(repoRoot: string, absPath: string): GraphNode[];
295
362
 
363
+ /**
364
+ * C-81 — **deep AST on-pull**. Structural facts too heavy to persist at index
365
+ * time (class members, per-parameter types, return type) are recomputed lazily
366
+ * here from the working-tree source when a consumer actually pulls a target.
367
+ * Persisting them would bloat every symbol node and break the incremental reuse
368
+ * basis (inferred types embed absolute `import("…")` paths); computing on demand
369
+ * keeps the index lean while still answering "what are the members / params of
370
+ * this" for the one target being read.
371
+ *
372
+ * A single-file in-memory ts-morph project (no tsconfig, no dependency walk) —
373
+ * so the extraction is cheap and reads only explicit syntactic annotations.
374
+ */
375
+ interface ParamInfo {
376
+ name: string;
377
+ type: string | null;
378
+ }
379
+ interface MemberInfo {
380
+ name: string;
381
+ memberKind: string;
382
+ signature: string | null;
383
+ isStatic: boolean;
384
+ }
385
+ interface DeepAst {
386
+ target: string;
387
+ kind: "file" | "symbol";
388
+ declarationKind: string | null;
389
+ signature: string | null;
390
+ purpose: string | null;
391
+ params: ParamInfo[];
392
+ returnType: string | null;
393
+ /** Class/interface members for a symbol; exported declarations for a file. */
394
+ members: MemberInfo[];
395
+ note?: string;
396
+ }
397
+ interface DeepAstInput {
398
+ /** Repo-relative file id, e.g. `src/a.ts`. */
399
+ filePath: string;
400
+ /** Absolute path to the file on disk. */
401
+ absPath: string;
402
+ /** Symbol name for a symbol target; omit for a file target. */
403
+ symbolName?: string;
404
+ }
405
+ /** Deep structural facts for one target, or null when the source is unreadable. */
406
+ declare function computeDeepAst(input: DeepAstInput): DeepAst | null;
407
+
296
408
  declare function fileId(repoRoot: string, absPath: string): string;
297
409
  declare function moduleId(repoRoot: string, absPath: string): string;
298
410
  declare function parentModuleId(id: string): string | null;
299
411
  declare function packageId(name: string): string;
300
412
  declare const SYMBOL_ID_SEP = "#";
301
- declare function symbolId(fileId: string, exportName: string): string;
413
+ declare function symbolId(fileId: string, qualifiedName: string): string;
302
414
  /**
303
415
  * Inverse of {@link symbolId}: split a `<fileId>#<name>` symbol id back into its
304
- * declaring file and export name. Returns null for an id with no separator (a
416
+ * declaring file and qualified name. Returns null for an id with no separator (a
305
417
  * plain file id), so callers can filter the symbol layer cleanly. `#` is illegal
306
418
  * in both posix paths and JS identifiers, so the first occurrence is the split.
307
419
  */
@@ -368,15 +480,10 @@ interface LineSpan {
368
480
  endLine: number;
369
481
  }
370
482
  /**
371
- * Every function/method/class a file DECLARES, mapped to its 1-based line span
372
- * (the model-B symbol surface, C-64, now carrying spans for C-63 coverage
373
- * range-containment). A superset of the file's exports: internal helpers like
374
- * `mergeFragments` are included so they get a `symbol` node (and, by name match,
375
- * their complexity + coverage) even though nothing imports them. Names come from
376
- * the same tree-sitter walk that computes complexity, so they never drift.
377
- * Anonymous declarations (a default-exported arrow, inline callbacks) contribute
378
- * no name and are skipped. A name declared more than once keeps its last span
379
- * (overloads / same-named methods are rare; range lookup still resolves most).
483
+ * Qualified declared names mapped to their 1-based line span (C-63 coverage
484
+ * range-containment). One scope binds a name once, as TypeScript and Python do,
485
+ * so a getter/setter pair, a Python property's accessors, and `@overload` stubs
486
+ * share one entry; when a qualified name repeats, the last span wins.
380
487
  */
381
488
  declare function collectDeclaredSpans(file: ParsedFile): Map<string, LineSpan>;
382
489
  /** Declared names only — the Set view over {@link collectDeclaredSpans}. */
@@ -458,6 +565,8 @@ declare function canonicalMetricName(name: string): string;
458
565
  declare function canonicalRole(name: string): NodeRole;
459
566
  declare function canonicalEdgeKind(name: string): EdgeKind;
460
567
 
568
+ declare function detectGitToplevel(cwd: string): string | null;
569
+
461
570
  interface RenamePair {
462
571
  oldPath: string;
463
572
  newPath: string;
@@ -471,10 +580,85 @@ interface DetectRenamesOptions {
471
580
  }
472
581
  declare function detectGitHead(repoRoot: string): string | null;
473
582
  declare function isInsideGitRepo(repoRoot: string): boolean;
474
- declare function detectGitToplevel(cwd: string): string | null;
583
+
584
+ /** Resolve a ref (branch, tag, sha) to its current commit sha, or null. */
585
+ declare function resolveGitRef(repoRoot: string, ref: string): string | null;
475
586
  declare function detectRenames(options: DetectRenamesOptions): RenamePair[];
476
587
  declare function buildAliases(repoRoot: string, pairs: readonly RenamePair[]): IdAlias[];
477
588
 
589
+ /** Snapshot attrs key naming the snapshot its id aliases were computed against; written from index version 0.15.0. */
590
+ declare const ALIAS_BASE_ATTR = "aliasBase";
591
+ type LineageSnapshot = Pick<SnapshotRow, "id" | "commitHash" | "attrs">;
592
+ /** Each snapshot's alias base, or null for a root: the tree the id_alias rows form across snapshots. */
593
+ type Lineage = ReadonlyMap<number, number | null>;
594
+ interface LineageStep {
595
+ snapshotId: number;
596
+ /** `forward` applies the snapshot's aliases (base to snapshot); `backward` undoes them. */
597
+ direction: "forward" | "backward";
598
+ }
599
+ /**
600
+ * The recorded base per snapshot. A snapshot written before 0.15.0 records none,
601
+ * so it gets the newest earlier snapshot with a commit: the one the indexer diffed against then.
602
+ */
603
+ declare function buildLineage(snapshots: readonly LineageSnapshot[]): Lineage;
604
+ /** Steps from one snapshot to another through their nearest common base, or null when they share none. */
605
+ declare function lineagePath(lineage: Lineage, fromId: number, toId: number, maxHops?: number): LineageStep[] | null;
606
+
607
+ type AliasLoader = (snapshotId: number) => readonly IdAlias[];
608
+ interface AliasResolution {
609
+ requestedId: string;
610
+ /** The id in the target snapshot's id space; equal to `requestedId` when nothing moved. */
611
+ id: string;
612
+ /** Each alias applied in walk order; a backward step lists the alias it undid. */
613
+ hops: IdAlias[];
614
+ /** `move` when any hop changed directory, otherwise the last hop's reason; absent without hops. */
615
+ reason?: IdAliasReason;
616
+ }
617
+ interface AliasChain {
618
+ fromSnapshotId: number | null;
619
+ toSnapshotId: number;
620
+ /** False when the snapshots share no alias base; only the target's own aliases apply then, as before 0.15.0. */
621
+ connected: boolean;
622
+ steps: readonly LineageStep[];
623
+ resolve(id: string): string;
624
+ trace(id: string): AliasResolution;
625
+ }
626
+ interface AliasChainInput {
627
+ lineage: Lineage;
628
+ loadAliases: AliasLoader;
629
+ /** Null resolves an id from any ancestor of `to`. */
630
+ from: number | null;
631
+ to: number;
632
+ maxHops?: number;
633
+ }
634
+ /** Carry node ids from one snapshot into another across every rename between them. */
635
+ declare function createAliasChain(input: AliasChainInput): AliasChain;
636
+
637
+ interface AliasChainOptions {
638
+ maxHops?: number;
639
+ }
640
+ interface ResolveAliasOptions extends AliasChainOptions {
641
+ /** The snapshot the id was read in; omitted, the id may come from any ancestor of the target. */
642
+ fromSnapshotId?: number;
643
+ }
644
+ interface PriorSnapshotOptions {
645
+ /** Only snapshots older than this id count; omitted, every snapshot does. */
646
+ before?: number;
647
+ /** A git checkout to resolve `ref` in; without it only the snapshot's `ref` label matches. */
648
+ repoRoot?: string;
649
+ }
650
+ /** The alias-base tree over every snapshot in the store. */
651
+ declare function loadLineage(store: CodeGraphStore): Lineage;
652
+ /** Carry node ids from `fromSnapshotId` into `toSnapshotId` through every alias between them, in either direction. */
653
+ declare function aliasChain(store: CodeGraphStore, fromSnapshotId: number | null, toSnapshotId: number, options?: AliasChainOptions): AliasChain;
654
+ /** Resolve an id to its form in `toSnapshotId`: `a.ts` renamed to `b.ts` then `c.ts` resolves to `c.ts`. */
655
+ declare function resolveAlias(store: CodeGraphStore, id: string, toSnapshotId: number, options?: ResolveAliasOptions): AliasResolution;
656
+ /**
657
+ * The newest snapshot a ref denotes: first the snapshot of the commit git resolves
658
+ * `ref` to (with `repoRoot`), else the newest snapshot whose `ref` label is `ref`.
659
+ */
660
+ declare function priorSnapshotForRef(store: CodeGraphStore, ref: string, options?: PriorSnapshotOptions): SnapshotRow | null;
661
+
478
662
  declare function computeMetrics(nodes: readonly GraphNode[], edges: readonly GraphEdge[]): GraphMetric[];
479
663
 
480
664
  /**
@@ -494,6 +678,73 @@ declare const SOURCE_METRIC_NAMES: ReadonlySet<string>;
494
678
  */
495
679
  declare function computeSourceMetrics(files: readonly ParsedFile[], fileIdOf: (filePath: string) => string, symbolNamesByFile?: ReadonlyMap<string, ReadonlySet<string>>): GraphMetric[];
496
680
 
681
+ /** How a test↔source pairing was inferred. */
682
+ type LinkMethod = "path" | "coedit";
683
+ interface TestSourceLink {
684
+ /** Node id of the test file. */
685
+ testId: string;
686
+ /** Node id of the (non-test) file it covers. */
687
+ sourceId: string;
688
+ method: LinkMethod;
689
+ }
690
+ interface LinkTestsOptions {
691
+ /** Minimum co-edit count for a pass-2 (coedit) link. Default 2. */
692
+ minCoEditCount?: number;
693
+ }
694
+ /**
695
+ * Two-pass test↔source linker. Pass 1 pairs each test file with non-test files
696
+ * matching its path conventions (high confidence). Pass 2 supplements tests
697
+ * left unpaired by pass 1 with their strongest co-edited non-test partner from
698
+ * change-coupling. Handles orphan tests (no pairing), orphan/untested sources
699
+ * (no incoming link), and one-to-many pairings (a test matching several
700
+ * sources, or a source covered by several tests).
701
+ */
702
+ declare function linkTestsToSources(nodes: readonly GraphNode[], coEditPairs: readonly CoEditPair[], options?: LinkTestsOptions): TestSourceLink[];
703
+ /**
704
+ * Per-source coverage breadth: how many distinct test files link to each
705
+ * covered source. Emitted only for sources with at least one linked test.
706
+ */
707
+ declare function testCoverageCountMetrics(links: readonly TestSourceLink[]): GraphMetric[];
708
+ /** Map each covered source to the set of test files that link to it. */
709
+ declare function groupTestsBySource(links: readonly TestSourceLink[]): Map<string, Set<string>>;
710
+
711
+ interface HistoryMetricsOptions {
712
+ /** Primary window: scopes ownership. Default 30. */
713
+ churnWindowDays?: number;
714
+ /** Windows to store churn and recency for; default {@link DEFAULT_CHURN_WINDOWS}, primary always included. */
715
+ churnWindows?: number[];
716
+ /** Also store an all-time `lifetime` window over full git history. */
717
+ includeLifetime?: boolean;
718
+ /** Epoch seconds that windows end at; defaults to the current time. */
719
+ nowEpoch?: number;
720
+ }
721
+ /** Windows the dashboard switcher offers; churn is stored for each by default. */
722
+ declare const DEFAULT_CHURN_WINDOWS: number[];
723
+ /** De-duped, ascending windows including the primary, with `lifetime` last (widest) when requested. */
724
+ declare function resolveChurnWindows(requested: number[] | undefined, primaryWindow: number, includeLifetime: boolean): ChurnWindow[];
725
+ interface LoadedHistory {
726
+ metrics: GraphMetric[];
727
+ /** Churn entries inside the primary window, which also scopes test-coverage linking and ownership. */
728
+ primaryEntries: readonly ChurnEntry[];
729
+ }
730
+ /** {@link buildHistoryMetrics} plus the primary-window entries it read; null when git or history is unavailable. */
731
+ declare function loadHistoryMetrics(nodes: Iterable<GraphNode>, idRoot: string, options?: HistoryMetricsOptions): LoadedHistory | null;
732
+ interface TestCoverageOwnershipOptions {
733
+ /** Window named in the metric suffix. Default 30. */
734
+ windowDays?: ChurnWindow;
735
+ /** Coverage threshold for bus factor (default 0.5 = 50% of churn). */
736
+ busFactorThreshold?: number;
737
+ }
738
+ /**
739
+ * Bus-factor / top-author-share of the *test coverage* for each source, keyed
740
+ * on the source node. Aggregates churn authorship across all test files linked
741
+ * to a source (via the two-pass linker) and summarises it the same way as
742
+ * production ownership — so a file can read as well-spread on production code
743
+ * yet a single-author silo on its tests (or vice versa). Emitted only for
744
+ * sources with at least one linked test that has churn in the window.
745
+ */
746
+ declare function computeTestCoverageOwnership(entries: readonly ChurnEntry[], links: readonly TestSourceLink[], options?: TestCoverageOwnershipOptions): GraphMetric[];
747
+
497
748
  interface IndexerMetricsInput {
498
749
  nodes: Map<string, GraphNode>;
499
750
  edges: Map<string, GraphEdge>;
@@ -502,6 +753,8 @@ interface IndexerMetricsInput {
502
753
  /** Source metrics carried forward verbatim for reused (unchanged) files. */
503
754
  reusedSourceMetrics: GraphMetric[];
504
755
  idRoot: string;
756
+ /** Git-history metrics (churn, recency, ownership); omitted means none. */
757
+ history?: HistoryMetricsOptions;
505
758
  }
506
759
  /**
507
760
  * Assemble the metric set for a snapshot: graph-wide degree metrics over the
@@ -510,12 +763,324 @@ interface IndexerMetricsInput {
510
763
  * reused source metrics is recomputed over the full set, so the result matches a
511
764
  * full index regardless of how much was reused.
512
765
  *
513
- * Git-history metrics (churn, ownership, change coupling, test coverage) are
514
- * deliberately absent here; they are a separate analysis layer, deferred with
515
- * the rest of codewatch's analyses.
766
+ * Git-history metrics come from the path-based engine in `./history/` through
767
+ * the `history-metrics.ts` adapter. Test-coverage metrics run with or without it.
516
768
  */
517
769
  declare function buildIndexerMetrics(input: IndexerMetricsInput): GraphMetric[];
518
770
 
771
+ /**
772
+ * Function-local dead-code metrics (C-65 Phase 1). Pure functions of one file's
773
+ * bytes — the same class as the source metrics in source-metrics.ts — so they
774
+ * ride the content-hash incremental reuse gate. Kept in their own module (not
775
+ * appended to source-metrics.ts) so the already-churn-hot source-metrics.ts is
776
+ * not touched; the reuse machinery folds these names in alongside
777
+ * SOURCE_METRIC_NAMES (see incremental.ts) and index-metrics.ts computes them
778
+ * fresh for (re)parsed files.
779
+ */
780
+ declare const DEAD_CODE_METRIC_NAMES: ReadonlySet<string>;
781
+ /**
782
+ * Per-file dead-code metrics for TypeScript files. Emitted sparsely — only when
783
+ * a count is > 0 — so a clean file adds no rows (a full index and an incremental
784
+ * re-index therefore produce the identical metric set). Non-TypeScript files are
785
+ * skipped for now.
786
+ */
787
+ declare function computeDeadCodeMetrics(files: readonly ParsedFile[], fileIdOf: (filePath: string) => string): GraphMetric[];
788
+
789
+ /**
790
+ * Growth-risk / scaling-smell metrics (C-66 Phase 2). Cheap, function-local
791
+ * *heuristics* — NOT Big-O or a proven complexity class (real asymptotic
792
+ * inference is undecidable; see the roadmap's hard NO). Pure functions of one
793
+ * file's bytes, so they ride the content-hash incremental reuse gate, folded
794
+ * into the carry-forward set in incremental.ts alongside the source/dead-code
795
+ * metric names. Emitted sparsely (only when a smell is present).
796
+ */
797
+ declare const GROWTH_RISK_METRIC_NAMES: ReadonlySet<string>;
798
+ /**
799
+ * Per-file growth-risk metrics for TS/Python files, all SMELLS not bounds:
800
+ * - `loop_depth` — max *lexical* loop nesting (triple-nested → 3), emitted at
801
+ * depth ≥ 2 (depth-2 loops over two *different* collections are actually linear).
802
+ * - `recursive_functions` — named functions that call themselves directly.
803
+ * - `search_in_loop` — linear-scan method calls (`.includes`/`.find`/…) inside a
804
+ * loop (an O(n) scan per iteration; `.includes` on a `Set` is O(1)).
805
+ * All emitted sparsely (only when present). Non-TS/Python files are skipped.
806
+ */
807
+ declare function computeGrowthRiskMetrics(files: readonly ParsedFile[], fileIdOf: (filePath: string) => string): GraphMetric[];
808
+
809
+ /**
810
+ * Coverage is a whole-suite DYNAMIC artifact — a function of *which tests ran*,
811
+ * not of any file's bytes — so it must NOT ride the content-hash reuse gate. It
812
+ * is emitted as a `coverage_pct` metric that is deliberately absent from every
813
+ * *_METRIC_NAMES reuse set, so an incremental index never carries it forward: a
814
+ * later index produces a snapshot with NO coverage until re-ingested. That is the
815
+ * overlay semantics — coverage is attached to the snapshot it was measured
816
+ * against and never inferred for another.
817
+ */
818
+ declare const COVERAGE_METRIC_NAME = "coverage_pct";
819
+ /** Minimal Istanbul `coverage-final.json` shape we read (function-level). */
820
+ interface IstanbulFn {
821
+ loc: {
822
+ start: {
823
+ line: number;
824
+ };
825
+ end: {
826
+ line: number;
827
+ };
828
+ };
829
+ }
830
+ interface IstanbulFileCoverage {
831
+ fnMap: Record<string, IstanbulFn>;
832
+ /** Per-function hit counts, keyed to fnMap ids. */
833
+ f: Record<string, number>;
834
+ }
835
+ type IstanbulCoverage = Record<string, IstanbulFileCoverage>;
836
+ /** A symbol node's 1-based line span, for range-containment attribution. */
837
+ interface SymbolSpan {
838
+ id: string;
839
+ startLine: number;
840
+ endLine: number;
841
+ }
842
+ /**
843
+ * Attribute an Istanbul coverage report to graph nodes as `coverage_pct` metrics:
844
+ * one per covered FILE (covered functions / total functions), and one per SYMBOL
845
+ * a covered function maps into by RANGE containment (symbol spans) — matching
846
+ * by range, not name, so anonymous/mangled Istanbul function names are handled.
847
+ * A symbol spanning several functions (a class) reports its methods' coverage
848
+ * ratio. Files Istanbul reports but the graph doesn't know are skipped.
849
+ */
850
+ declare function attributeCoverage(coverage: IstanbulCoverage, fileIdOf: (absPath: string) => string | null, symbolsByFile: ReadonlyMap<string, readonly SymbolSpan[]>): GraphMetric[];
851
+
852
+ /** Metric-name suffix for a window: `30d`, `180d`, or `lifetime`. */
853
+ declare function windowSuffix(window: ChurnWindow): string;
854
+ /**
855
+ * Age-discount metrics: `recency_{w}` = min(1, age/w) for each file that churned
856
+ * in window `w`, plus one window-independent `file_age_days` per file. Multiplying
857
+ * a hotspot score by recency stops a young file's burst of churn from reading as
858
+ * decay. Recency is emitted (as 1) even when the age is unknown, so a rule that
859
+ * needs every hotspot factor is never silently disabled; `file_age_days` only when known.
860
+ */
861
+ declare function computeRecencyWindows(firstSeen: ReadonlyMap<string, number>, churnedIdsByWindow: ReadonlyMap<ChurnWindow, ReadonlySet<string>>, nowEpoch: number): GraphMetric[];
862
+
863
+ interface PageRankOptions {
864
+ /** Per-node teleport weight. Unset or empty → uniform teleport across all nodes. */
865
+ personalization?: ReadonlyMap<string, number>;
866
+ /** Probability of following an edge vs teleporting (default 0.85). */
867
+ damping?: number;
868
+ /** L1 convergence threshold (default 1e-6). */
869
+ tolerance?: number;
870
+ /** Cap on power-iteration steps (default 100). */
871
+ maxIterations?: number;
872
+ /** Per-kind edge weight; missing kinds default to 1.0. */
873
+ edgeWeights?: Partial<Record<EdgeKind, number>>;
874
+ }
875
+ interface PageRankRow {
876
+ nodeId: string;
877
+ score: number;
878
+ }
879
+ interface PageRankResult {
880
+ /** Sorted descending by score; ties broken by node id ascending. */
881
+ rows: PageRankRow[];
882
+ iterations: number;
883
+ converged: boolean;
884
+ }
885
+ /** Edge weight used by PageRank for a given edge kind, honoring user overrides. */
886
+ declare function getEdgeWeight(kind: EdgeKind, overrides?: Partial<Record<EdgeKind, number>>): number;
887
+ declare function computePageRank(nodes: readonly GraphNode[], edges: readonly GraphEdge[], options?: PageRankOptions): PageRankResult;
888
+
889
+ interface RelevanceOptions {
890
+ /** Probability of following an edge vs teleporting back to the seeds (default 0.85). */
891
+ damping?: number;
892
+ /** Per-kind edge weight override, forwarded to PageRank. */
893
+ edgeWeights?: PageRankOptions["edgeWeights"];
894
+ }
895
+ /**
896
+ * C-89 — **seeded (personalized) PageRank as a relevance-to-target proximity
897
+ * measure** (Aider RepoMap-style). The teleport vector is concentrated on
898
+ * `seedIds`, so the stationary distribution scores every node by how tightly it
899
+ * couples to the seeds through the resolved dependency graph: near neighbours
900
+ * score high, decaying with graph distance. This is "relevance to what I'm
901
+ * looking at," distinct from global PageRank centrality ("globally famous"),
902
+ * which stays the no-seed cold path.
903
+ *
904
+ * The edge set is **symmetrized** (each edge added in both directions) so
905
+ * relevance flows to a target's callers AND its dependencies — a purely forward
906
+ * walk from the seed would only reach what the seed depends on, never who
907
+ * depends on it. Edge kind (and therefore weight) is preserved on the reversed
908
+ * copy, so a heavier `calls` edge stays heavier in both directions.
909
+ *
910
+ * Returns a Map from node id to relevance score (seeds included). An empty seed
911
+ * set returns an empty map — callers then fall back to global centrality.
912
+ */
913
+ declare function computeRelevance(nodes: readonly GraphNode[], edges: readonly GraphEdge[], seedIds: readonly string[], options?: RelevanceOptions): Map<string, number>;
914
+
915
+ /**
916
+ * Symbol-level change coupling (C-60). Decomposes a god-file the file-level
917
+ * coupling view rolls up as one blob (e.g. `types.ts`) into *which symbol* is
918
+ * used where. Built on the C-53 `references` edge substrate: `src` is the
919
+ * importing file, `dst` is the imported symbol node id (`<fileId>#<name>`).
920
+ *
921
+ * Two slices, both pure functions of the assembled reference edge set (a
922
+ * whole-graph rollup like utilization/PageRank, sound under incremental reuse
923
+ * since it reads the reassembled edges, not a per-file cache):
924
+ *
925
+ * - **Slice C — per-symbol consumers**: invert the edges by `dst`, giving the
926
+ * set of files that import each symbol. Directly answers "what IN this file is
927
+ * used where"; covers span-less types/consts that a git-hunk approach cannot.
928
+ * - **Slice B — co-import coupling**: group edges by `src`, and every pair of
929
+ * symbols co-imported by the same file is a coupling pair. Two symbols that
930
+ * are always imported together travel together — structural (used-together),
931
+ * drift-free coupling, as opposed to the temporal (changed-together) git
932
+ * co-edit signal.
933
+ */
934
+ /** The minimal shape of a `references` edge this module consumes. */
935
+ interface ReferenceEdgeLite {
936
+ /** Importing file id. */
937
+ srcId: string;
938
+ /** Imported symbol node id (`<fileId>#<name>`). */
939
+ dstId: string;
940
+ }
941
+ /** One symbol's consumer set (Slice C). */
942
+ interface SymbolConsumers {
943
+ symbolId: string;
944
+ /** Declaring file, parsed from the symbol id. */
945
+ fileId: string;
946
+ /** Export name. */
947
+ name: string;
948
+ /** Distinct importing file ids, sorted. */
949
+ consumers: string[];
950
+ }
951
+ /** A pair of symbols co-imported by the same file (Slice B). */
952
+ interface SymbolCouplingPair {
953
+ aId: string;
954
+ aFile: string;
955
+ aName: string;
956
+ bId: string;
957
+ bFile: string;
958
+ bName: string;
959
+ /** Distinct files that import both symbols. */
960
+ coImports: number;
961
+ /** True when the two symbols are declared in different files. */
962
+ crossFile: boolean;
963
+ }
964
+ interface SymbolCouplingOptions {
965
+ /** Skip pairs co-imported by fewer than this many files. Default 2. */
966
+ minCoImports?: number;
967
+ /**
968
+ * Skip importing files that reference more than this many distinct symbols —
969
+ * a wide barrel-style importer would otherwise explode into O(n²) noise
970
+ * pairs, mirroring change-coupling's large-commit guard. Default 40.
971
+ */
972
+ largeImporterThreshold?: number;
973
+ }
974
+ /**
975
+ * Group reference edges by imported symbol, yielding each symbol's distinct
976
+ * consuming files (Slice C). Sorted by consumer count desc, then symbol id, so
977
+ * the most broadly-depended-on exports lead.
978
+ */
979
+ declare function computeSymbolConsumers(edges: readonly ReferenceEdgeLite[]): SymbolConsumers[];
980
+ /**
981
+ * Every pair of symbols co-imported by the same file becomes a coupling pair,
982
+ * counted by how many distinct files co-import them (Slice B). Wide importers
983
+ * are dropped to keep the pairing near-linear. Sorted by co-import count desc,
984
+ * cross-file pairs preferred at a tie (they are the actionable ones — a
985
+ * same-file pair is just cohesion within one module).
986
+ */
987
+ declare function computeSymbolCoupling(edges: readonly ReferenceEdgeLite[], options?: SymbolCouplingOptions): SymbolCouplingPair[];
988
+
989
+ /**
990
+ * Per-package and per-pair structural quality metrics, plus an overall
991
+ * Newman-Girvan modularity Q for the package partition.
992
+ *
993
+ * Operates on a barrel-resolved edge set by default: edges that land on a
994
+ * file with role="barrel" are rewritten to land on the underlying files
995
+ * the barrel re-exports from (transitively). The intent is to measure the
996
+ * real dependency surface, not the re-export plumbing.
997
+ *
998
+ * See C-8 task notes for the design rationale and empirical calibration
999
+ * data from the 2026-05-21 codewatch dogfood.
1000
+ */
1001
+ interface PartitionQualityInput {
1002
+ /** Logical packages — at minimum needs an id. */
1003
+ packages: ReadonlyArray<{
1004
+ id: string;
1005
+ }>;
1006
+ /** Package id → list of file ids assigned to that package. */
1007
+ fileByPackage: ReadonlyMap<string, ReadonlyArray<string>>;
1008
+ /** All file/module/external nodes for the snapshot. Used to identify role="barrel". */
1009
+ nodes: readonly GraphNode[];
1010
+ /** All edges for the snapshot. */
1011
+ edges: readonly GraphEdge[];
1012
+ /**
1013
+ * When true, edges landing on a barrel file are resolved through its
1014
+ * re-export chain to the underlying source files. The cheap implementation
1015
+ * fans each barrel import into N synthetic edges (one per re-export
1016
+ * target), which over-attributes — a single `import { x } from "./pkg"`
1017
+ * becomes N edges as if every re-export were used. Default false until
1018
+ * a weighted or symbol-tracking version is available.
1019
+ */
1020
+ resolveBarrels?: boolean;
1021
+ }
1022
+ type PackageFlag = "weak-boundary";
1023
+ type PairFlag = "tight" | "moderate" | "none";
1024
+ type PackageLayer = "top" | "middle" | "foundation";
1025
+ interface PackageStats {
1026
+ pkgId: string;
1027
+ fileCount: number;
1028
+ internalEdges: number;
1029
+ outgoingEdges: number;
1030
+ incomingEdges: number;
1031
+ /** internal / (internal + outgoing) — higher = more self-contained. */
1032
+ cohesion: number;
1033
+ /** outgoing / (outgoing + incoming) — Martin's I metric at the package level. */
1034
+ instability: number;
1035
+ /**
1036
+ * Abstractness proxy A ∈ [0,1]: share of the package's files with role
1037
+ * "types" (dedicated type/interface definitions). codewatch has no
1038
+ * symbol-level abstract/concrete counts, so this file-role ratio stands in
1039
+ * for Martin's A. Enables the instability×abstractness main-sequence plot.
1040
+ */
1041
+ abstractness: number;
1042
+ layer: PackageLayer;
1043
+ flags: PackageFlag[];
1044
+ }
1045
+ interface PairCoupling {
1046
+ from: string;
1047
+ to: string;
1048
+ edges: number;
1049
+ /** edges / files(from) — fraction of from-side files contributing dependencies into `to`. */
1050
+ intensity: number;
1051
+ flag: PairFlag;
1052
+ }
1053
+ interface PartitionQualityResult {
1054
+ modularityQ: number;
1055
+ totalEdges: number;
1056
+ perPackage: PackageStats[];
1057
+ pairCoupling: PairCoupling[];
1058
+ /** Total raised flags across packages + pairs (excluding "moderate"). */
1059
+ flagsCount: number;
1060
+ }
1061
+ declare function computePartitionQuality(input: PartitionQualityInput): PartitionQualityResult;
1062
+ /**
1063
+ * Invert a package→files bucket map into a file→package lookup, skipping the
1064
+ * empty-string "unassigned" bucket. Shared by partition-quality and the CLI's
1065
+ * arch/wiki package rollups, which all need the same file→package direction.
1066
+ */
1067
+ declare function invertBuckets(fileByPackage: ReadonlyMap<string, ReadonlyArray<string>>): Map<string, string>;
1068
+
1069
+ /**
1070
+ * PageRank over one snapshot's file-level graph (symbol nodes and `references`
1071
+ * edges excluded, as codewatch's `graph relevant` reads it). Pass
1072
+ * `personalization` to seed the walk toward target node ids.
1073
+ */
1074
+ declare function snapshotPageRank(store: CodeGraphStore, snapshotId: number, options?: PageRankOptions): PageRankResult;
1075
+ /** Relevance of every file-level node to `seedIds`, as codewatch's `graph context` computes it. */
1076
+ declare function snapshotRelevance(store: CodeGraphStore, snapshotId: number, seedIds: readonly string[], options?: RelevanceOptions): Map<string, number>;
1077
+ /** The snapshot's `references` edges: importing file to imported symbol. */
1078
+ declare function snapshotReferenceEdges(store: CodeGraphStore, snapshotId: number): ReferenceEdgeLite[];
1079
+ /** Per-symbol consumer files for one snapshot. */
1080
+ declare function snapshotSymbolConsumers(store: CodeGraphStore, snapshotId: number): SymbolConsumers[];
1081
+ /** Co-import symbol coupling pairs for one snapshot. */
1082
+ declare function snapshotSymbolCoupling(store: CodeGraphStore, snapshotId: number, options?: SymbolCouplingOptions): SymbolCouplingPair[];
1083
+
519
1084
  /**
520
1085
  * The free-function reading surface named in the package API. Each delegates to
521
1086
  * the store's method of the same name, so a caller that only reads a snapshot
@@ -528,5 +1093,343 @@ declare function listEdges(store: CodeGraphStore, snapshotId: number, opts?: {
528
1093
  includeReferences?: boolean;
529
1094
  }): GraphEdge[];
530
1095
  declare function listMetrics(store: CodeGraphStore, snapshotId: number): GraphMetric[];
1096
+ declare function listMetricsForNode(store: CodeGraphStore, snapshotId: number, nodeId: string): GraphMetric[];
1097
+ declare function listEdgesTouching(store: CodeGraphStore, snapshotId: number, nodeId: string, opts?: {
1098
+ includeReferences?: boolean;
1099
+ }): GraphEdge[];
1100
+ declare function aggregateMetrics(store: CodeGraphStore, snapshotId: number, opts?: {
1101
+ name?: string;
1102
+ }): MetricAggregate[];
1103
+
1104
+ /** The `unit` a metric row is stored with; every row of one metric name carries the same unit. */
1105
+ type MetricUnit = "count" | "lines" | "ratio" | "days" | "percent";
1106
+ /** How file values combine into a directory; `none` means no rollup reproduces the group's true value. */
1107
+ type MetricRollup = "sum" | "max" | "mean" | "none";
1108
+ /** Which way is worse, for ranking and colouring; `neutral` makes no value judgement. */
1109
+ type MetricDirection = "higher-worse" | "lower-worse" | "neutral";
1110
+ /** A missing row reads as zero (a sparse writer's floor) or is left out of means, percentiles, and ranks. */
1111
+ type MetricAbsence = "zero" | "exclude";
1112
+ /** The code-graph module that writes the metric, for provenance. */
1113
+ type MetricSource = "degree" | "source-metrics" | "lcom" | "dead-code" | "growth-risk" | "history" | "test-linker" | "coverage";
1114
+ interface MetricDescriptor {
1115
+ /** The stored name, or a template such as `churn_{w}` when {@link windowed} is set. */
1116
+ name: string;
1117
+ unit: MetricUnit;
1118
+ /** Node kinds the writer attaches this metric to. */
1119
+ appliesTo: readonly NodeKind[];
1120
+ rollup: MetricRollup;
1121
+ direction: MetricDirection;
1122
+ absent: MetricAbsence;
1123
+ source: MetricSource;
1124
+ description: string;
1125
+ /** Set on templates: `{w}` in `name` and `description` stands for a window such as `30d` or `lifetime`. */
1126
+ windowed?: true;
1127
+ /** Set on a resolved windowed descriptor: the window its name carries. */
1128
+ window?: string;
1129
+ }
1130
+
1131
+ /** Every metric name code-graph writes, with windowed names as `{w}` templates. */
1132
+ declare const METRIC_CATALOGUE: readonly MetricDescriptor[];
1133
+
1134
+ /** The descriptor for a stored metric name, resolving windowed names such as `churn_90d`; null when uncatalogued. */
1135
+ declare function describeMetric(name: string): MetricDescriptor | null;
1136
+ /** Descriptors for a set of stored names, in the order given; uncatalogued names are returned separately. */
1137
+ declare function describeMetrics(names: Iterable<string>): {
1138
+ described: MetricDescriptor[];
1139
+ unknown: string[];
1140
+ };
1141
+
1142
+ type Severity = "error" | "warning";
1143
+ interface MetricMaxRule {
1144
+ type: "metric-max";
1145
+ id: string;
1146
+ metric: string;
1147
+ max: number;
1148
+ kind?: NodeKind;
1149
+ severity?: Severity;
1150
+ exclude?: string[];
1151
+ excludeRoles?: NodeRole[];
1152
+ }
1153
+ interface MetricMinRule {
1154
+ type: "metric-min";
1155
+ id: string;
1156
+ metric: string;
1157
+ min: number;
1158
+ kind?: NodeKind;
1159
+ severity?: Severity;
1160
+ exclude?: string[];
1161
+ excludeRoles?: NodeRole[];
1162
+ }
1163
+ interface MetricProductMaxRule {
1164
+ type: "metric-product-max";
1165
+ id: string;
1166
+ metrics: string[];
1167
+ max: number;
1168
+ kind?: NodeKind;
1169
+ severity?: Severity;
1170
+ exclude?: string[];
1171
+ excludeRoles?: NodeRole[];
1172
+ }
1173
+ interface ForbidImportRule {
1174
+ type: "forbid-import";
1175
+ id: string;
1176
+ from: string;
1177
+ to: string;
1178
+ severity?: Severity;
1179
+ }
1180
+ interface LayeredDepsRule {
1181
+ type: "layered-deps";
1182
+ id: string;
1183
+ layers: string[][];
1184
+ severity?: Severity;
1185
+ }
1186
+ interface NoInternalOnlyBarrelsRule {
1187
+ type: "no-internal-only-barrels";
1188
+ id: string;
1189
+ /** Path prefixes marking package roots; node ids carry no intrinsic package membership. */
1190
+ packageRoots: string[];
1191
+ severity?: Severity;
1192
+ /** Globs or substrings to skip, such as CLI bin entries the role classifier calls barrels. */
1193
+ exclude?: string[];
1194
+ }
1195
+ type CheckRule = MetricMaxRule | MetricMinRule | MetricProductMaxRule | ForbidImportRule | LayeredDepsRule | NoInternalOnlyBarrelsRule;
1196
+ interface CheckRulesFile {
1197
+ rules: CheckRule[];
1198
+ }
1199
+ interface CheckViolation {
1200
+ ruleId: string;
1201
+ severity: Severity;
1202
+ nodeId: string;
1203
+ message: string;
1204
+ metric?: string;
1205
+ value?: number;
1206
+ threshold?: number;
1207
+ destinationId?: string;
1208
+ isCarryover?: boolean;
1209
+ }
1210
+ interface CheckResult {
1211
+ snapshotId: number;
1212
+ baselineSnapshotId?: number;
1213
+ rulesEvaluated: number;
1214
+ nodesEvaluated: number;
1215
+ violations: CheckViolation[];
1216
+ newErrors: number;
1217
+ newWarnings: number;
1218
+ carryoverErrors: number;
1219
+ carryoverWarnings: number;
1220
+ passed: boolean;
1221
+ }
1222
+
1223
+ /** The three whole-snapshot reads rule evaluation needs; any store with them can be checked. */
1224
+ type RuleStore = Pick<CodeGraphStore, "listNodes" | "listEdges" | "listMetrics">;
1225
+
1226
+ interface RunChecksOptions {
1227
+ snapshotId: number;
1228
+ rules: readonly CheckRule[];
1229
+ baselineSnapshotId?: number;
1230
+ }
1231
+ declare function runChecks(store: CodeGraphStore, options: RunChecksOptions): CheckResult;
1232
+ /** Every rule's violations in one snapshot with no baseline; needs only the three whole-snapshot reads. */
1233
+ declare function snapshotViolations(store: RuleStore, snapshotId: number, rules: readonly CheckRule[]): CheckViolation[];
1234
+ /** Identity of a violation across snapshots: severity, value and message may change, the key does not. */
1235
+ declare function violationKey(v: CheckViolation): string;
1236
+ /** {@link violationKey} with both node ids carried into another snapshot's id space; unmoved ids key as before. */
1237
+ declare function rebasedViolationKey(v: CheckViolation, resolve: (id: string) => string): string;
1238
+
1239
+ /** Glob when the pattern has a `*` (`**` crosses directories, `*` stays in a segment); otherwise a case-sensitive substring. */
1240
+ declare function patternToRegex(pattern: string): RegExp;
1241
+ declare function compilePatterns(patterns: readonly string[] | undefined): RegExp[];
1242
+ declare function matchesAny(value: string, patterns: readonly RegExp[]): boolean;
1243
+
1244
+ interface ValidateRulesOptions {
1245
+ /** Called with a human-readable message when a deprecated alias is healed. */
1246
+ onWarn?: (message: string) => void;
1247
+ }
1248
+ declare function validateRules(input: unknown, options?: ValidateRulesOptions): readonly CheckRule[];
1249
+
1250
+ /** A snapshot id, or a ref name resolved to that ref's newest snapshot. */
1251
+ type SnapshotSpec = number | string;
1252
+ interface CheckSnapshotOptions {
1253
+ snapshot: SnapshotSpec;
1254
+ rules: readonly CheckRule[];
1255
+ baseline?: SnapshotSpec;
1256
+ }
1257
+ interface CheckSnapshotResult {
1258
+ snapshot: SnapshotRow;
1259
+ baselineSnapshot?: SnapshotRow;
1260
+ result: CheckResult;
1261
+ }
1262
+ declare function resolveSnapshot(store: CodeGraphStore, spec: SnapshotSpec): SnapshotRow;
1263
+ /** Check one snapshot against a rule set, marking violations already present in the baseline as carryover. */
1264
+ declare function checkSnapshot(store: CodeGraphStore, options: CheckSnapshotOptions): CheckSnapshotResult;
1265
+ /** Read and validate a `check.json` rules file; deprecated aliases heal through `onWarn`. */
1266
+ declare function loadCheckRules(path: string, options?: ValidateRulesOptions): Promise<readonly CheckRule[]>;
1267
+
1268
+ interface MetricDelta {
1269
+ nodeId: string;
1270
+ name: string;
1271
+ before: number | null;
1272
+ after: number | null;
1273
+ delta: number | null;
1274
+ }
1275
+ interface NodeRename {
1276
+ oldId: string;
1277
+ newId: string;
1278
+ reason: IdAliasReason;
1279
+ node: GraphNode;
1280
+ }
1281
+ interface GraphDiffSummary {
1282
+ fromSnapshotId: number;
1283
+ toSnapshotId: number;
1284
+ addedNodes: number;
1285
+ removedNodes: number;
1286
+ renamedNodes: number;
1287
+ unchangedNodes: number;
1288
+ addedEdges: number;
1289
+ removedEdges: number;
1290
+ metricChanges: number;
1291
+ }
1292
+ interface GraphDiff {
1293
+ summary: GraphDiffSummary;
1294
+ addedNodes: GraphNode[];
1295
+ removedNodes: GraphNode[];
1296
+ renamedNodes: NodeRename[];
1297
+ addedEdges: GraphEdge[];
1298
+ removedEdges: GraphEdge[];
1299
+ metricDeltas: MetricDelta[];
1300
+ }
1301
+
1302
+ interface DiffSnapshotsOptions {
1303
+ fromSnapshotId: number;
1304
+ toSnapshotId: number;
1305
+ }
1306
+ /**
1307
+ * Structural diff of two snapshots. Ids follow the alias chain between them, across
1308
+ * every rename in between, so a move is not a delete plus an add.
1309
+ */
1310
+ declare function diffSnapshots(store: CodeGraphStore, options: DiffSnapshotsOptions): GraphDiff;
1311
+
1312
+ interface UnchangedViolation {
1313
+ from: CheckViolation;
1314
+ to: CheckViolation;
1315
+ delta: number | null;
1316
+ }
1317
+ interface CheckDiff {
1318
+ fromSnapshotId: number;
1319
+ toSnapshotId: number;
1320
+ rulesEvaluated: number;
1321
+ newViolations: CheckViolation[];
1322
+ resolvedViolations: CheckViolation[];
1323
+ unchanged: UnchangedViolation[];
1324
+ worsened: UnchangedViolation[];
1325
+ improved: UnchangedViolation[];
1326
+ }
1327
+ interface DiffCheckResultsOptions {
1328
+ fromSnapshotId: number;
1329
+ toSnapshotId: number;
1330
+ rules: readonly CheckRule[];
1331
+ }
1332
+ /**
1333
+ * Run the rules on both snapshots and bucket each violation as new, resolved, or
1334
+ * unchanged (worsened or improved by value). From-side ids follow the alias chain
1335
+ * into the to-snapshot, so a moved file's violations stay unchanged.
1336
+ */
1337
+ declare function diffCheckResults(store: CodeGraphStore, options: DiffCheckResultsOptions): CheckDiff;
1338
+
1339
+ interface EmbeddableSymbol {
1340
+ id: string;
1341
+ name: string;
1342
+ file: string;
1343
+ signature: string;
1344
+ purpose?: string;
1345
+ text: string;
1346
+ textHash: string;
1347
+ }
1348
+ interface EmbedCoverage {
1349
+ /** Embeddable exported symbols in the snapshot. */
1350
+ symbols: number;
1351
+ /** How many symbols have a stored vector for this model. */
1352
+ embedded: number;
1353
+ /** How many carry docstring purpose text (where recall is strongest). */
1354
+ withPurpose: number;
1355
+ }
1356
+ interface EmbedSnapshotResult extends EmbedCoverage {
1357
+ model: string;
1358
+ /** Unique new texts sent to the embedder (symbols can share a text). */
1359
+ newlyEmbedded: number;
1360
+ /** Symbols whose vector was already stored (content-addressed cache hits). */
1361
+ reused: number;
1362
+ }
1363
+ /** Outcome of the non-fatal indexing pass: a down backend is reported, never thrown. */
1364
+ type EmbedAttempt = {
1365
+ ok: true;
1366
+ result: EmbedSnapshotResult;
1367
+ } | {
1368
+ ok: false;
1369
+ model: string;
1370
+ error: string;
1371
+ };
1372
+ interface SimilarCandidate {
1373
+ id: string;
1374
+ name: string;
1375
+ file: string;
1376
+ signature: string;
1377
+ purpose?: string;
1378
+ /** Cosine similarity to the query, in [-1, 1]. */
1379
+ score: number;
1380
+ }
1381
+ interface SimilarResult {
1382
+ query: string;
1383
+ model: string;
1384
+ coverage: EmbedCoverage;
1385
+ candidates: SimilarCandidate[];
1386
+ }
1387
+ /** Query prefixes are the embedder's to apply; configure them with its `prefixes` option. */
1388
+ interface FindSimilarOptions {
1389
+ limit?: number;
1390
+ }
1391
+
1392
+ /** Mirrors the gate harness: purpose text is appended to the signature when present. */
1393
+ declare function buildEmbedText(signature: string, purpose?: string | null): string;
1394
+ declare function hashEmbedText(text: string): string;
1395
+ /**
1396
+ * The embeddable corpus of a snapshot: exported symbols that carry a signature,
1397
+ * excluding test/fixture/generated code (matching the validated gate corpus).
1398
+ */
1399
+ declare function listEmbeddableSymbols(store: CodeGraphStore, snapshotId: number): EmbeddableSymbol[];
1400
+
1401
+ /** The blob_cache namespace for symbol vectors; the model column holds `embedder.model`, which names the vector space including its prefixes. */
1402
+ declare const SYMBOL_EMBEDDING_NAMESPACE = "code-graph/symbol-embedding";
1403
+ interface CachedEmbedResult {
1404
+ /** L2-normalized vectors for every input text, keyed by its text hash. */
1405
+ byHash: Map<string, number[]>;
1406
+ /** Hashes that had no stored vector and were embedded on this call. */
1407
+ newHashes: Set<string>;
1408
+ }
1409
+ /**
1410
+ * Embed texts through the content-addressed store: only texts whose hash has
1411
+ * no vector for this model reach the embedder; new vectors are normalized and
1412
+ * persisted batch by batch so the next call (or a retry after a failure) is a cache hit.
1413
+ */
1414
+ declare function embedTextsCached(store: CodeGraphStore, embedder: Embedder, texts: readonly string[]): Promise<CachedEmbedResult>;
1415
+
1416
+ /**
1417
+ * Precompute capability embeddings for a snapshot. Only texts whose hash has no
1418
+ * stored vector are sent to the embedder, so re-runs and unchanged symbols cost
1419
+ * nothing: the content-addressed store is the incremental-reuse mechanism.
1420
+ */
1421
+ declare function embedSnapshot(store: CodeGraphStore, snapshotId: number, embedder: Embedder): Promise<EmbedSnapshotResult>;
1422
+ /**
1423
+ * The post-index pass. A backend failure (ollama down, model missing) must not
1424
+ * fail the index: the snapshot is already persisted and valid without vectors.
1425
+ */
1426
+ declare function tryEmbedSnapshot(store: CodeGraphStore, snapshotId: number, embedder: Embedder): Promise<EmbedAttempt>;
1427
+ /**
1428
+ * The query-time "about to write X: does it exist?" surface. Embeds the query
1429
+ * (an intent sentence, a pseudo-signature, or both as `sig -- intent`), ranks
1430
+ * the snapshot's embedded symbols by cosine similarity, and returns the top-K
1431
+ * with coverage so the caller can weigh how much of the repo was searchable.
1432
+ */
1433
+ declare function findSimilarCapability(store: CodeGraphStore, snapshotId: number, query: string, embedder: Embedder, opts?: FindSimilarOptions): Promise<SimilarResult>;
531
1434
 
532
- export { ALL_ROLES, CodeGraphStore, DOMAIN_DDL, type EdgeKind, type Extractor, type FileFingerprint, type GraphEdge, type GraphFragment, type GraphMetric, type GraphNode, INDEX_VERSION, type IdAlias, type IdAliasReason, type IndexOptions, type IndexResult, KIT, LanguageExtractor, type LanguageExtractorOptions, type LineSpan, MIGRATIONS, type NodeKind, type NodeRole, type ParsedFile, PythonGraphExtractor, type ReadFile, type ReuseBasis, SOURCE_METRIC_NAMES, SYMBOL_ID_SEP, type SnapshotInsert, type SnapshotRow, type SourceLanguage, TsMorphGraphExtractor, type TsMorphGraphExtractorOptions, annotateRoles, buildAliases, buildFileModuleNodes, buildIndexerMetrics, canonicalEdgeKind, canonicalMetricName, canonicalRole, classifyRole, collectDeclaredNames, collectDeclaredSpans, computeMetrics, computeRoleHints, computeSourceMetrics, detectGitHead, detectGitToplevel, detectRenames, edgeWeight, externalId, fileId, getLanguageFromPath, getSupportedLanguages, hashContent, indexPaths, isGeneratedByHeuristic, isGeneratedFile, isInsideGitRepo, listEdges, listMetrics, listNodes, loadGeneratedPatterns, moduleId, openCodeGraph, packageId, parentModuleId, parseFile, parseSymbolId, pruneDanglingReferences, readSourceFiles, resolveBarrelEdges, shouldIncludeFile, structuralSignature, symbolId, walkSourceFiles };
1435
+ export { ALIAS_BASE_ATTR, ALL_ROLES, type AliasChain, type AliasChainInput, type AliasChainOptions, type AliasLoader, type AliasResolution, COVERAGE_METRIC_NAME, type CachedEmbedResult, type CheckDiff, type CheckResult, type CheckRule, type CheckRulesFile, type CheckSnapshotOptions, type CheckSnapshotResult, type CheckViolation, CodeGraphStore, DEAD_CODE_METRIC_NAMES, DEFAULT_CHURN_WINDOWS, DOMAIN_DDL, type DeepAst, type DeepAstInput, type DiffCheckResultsOptions, type DiffSnapshotsOptions, type EdgeKind, type EmbedAttempt, type EmbedCoverage, type EmbedSnapshotResult, type EmbeddableSymbol, type FileFingerprint, type FindSimilarOptions, type ForbidImportRule, GROWTH_RISK_METRIC_NAMES, type GraphDiff, type GraphDiffSummary, type GraphEdge, type GraphFragment, type GraphMetric, type GraphNode, type HistoryMetricsOptions, INDEX_VERSION, type IdAlias, type IdAliasReason, type IndexOptions, type IndexResult, type IstanbulCoverage, KIT, LanguageExtractor, type LanguageExtractorOptions, type LayeredDepsRule, type LineSpan, type Lineage, type LineageSnapshot, type LineageStep, type LinkMethod, type LinkTestsOptions, type LoadedHistory, METRIC_CATALOGUE, MIGRATIONS, type MemberInfo, type MetricAbsence, type MetricAggregate, type MetricDelta, type MetricDescriptor, type MetricDirection, type MetricMaxRule, type MetricMinRule, type MetricProductMaxRule, type MetricRollup, type MetricSource, type MetricUnit, type NoInternalOnlyBarrelsRule, type NodeKind, type NodeRename, type NodeRole, type PackageFlag, type PackageLayer, type PackageStats, type PageRankOptions, type PageRankResult, type PageRankRow, type PairCoupling, type PairFlag, type ParamInfo, type PartitionQualityInput, type PartitionQualityResult, type PriorSnapshotOptions, type PruneOptions, type PrunePlan, type PruneResult, PythonGraphExtractor, type ReadFile, type ReferenceEdgeLite, type RelevanceOptions, type ResolveAliasOptions, type ReuseBasis, type RuleStore, type RunChecksOptions, SCHEMA_VERSION, SNAPSHOT_SCOPED_TABLES, SOURCE_METRIC_NAMES, SYMBOL_EMBEDDING_NAMESPACE, SYMBOL_ID_SEP, type Severity, type SimilarCandidate, type SimilarResult, type SnapshotInsert, type SnapshotRow, type SnapshotSpec, type SourceLanguage, type SymbolConsumers, type SymbolCouplingOptions, type SymbolCouplingPair, type SymbolSpan, type TestCoverageOwnershipOptions, type TestSourceLink, type TopMetricRow, TsMorphGraphExtractor, type TsMorphGraphExtractorOptions, type UnchangedViolation, type ValidateRulesOptions, aggregateMetrics, aliasChain, annotateRoles, attributeCoverage, buildAliases, buildEmbedText, buildFileModuleNodes, buildIndexerMetrics, buildLineage, canonicalEdgeKind, canonicalMetricName, canonicalRole, checkSnapshot, classifyRole, collectDeclaredNames, collectDeclaredSpans, compilePatterns, computeDeadCodeMetrics, computeDeepAst, computeGrowthRiskMetrics, computeMetrics, computePageRank, computePartitionQuality, computeRecencyWindows, computeRelevance, computeRoleHints, computeSourceMetrics, computeSymbolConsumers, computeSymbolCoupling, computeTestCoverageOwnership, createAliasChain, describeMetric, describeMetrics, detectGitHead, detectGitToplevel, detectRenames, diffCheckResults, diffSnapshots, edgeWeight, embedSnapshot, embedTextsCached, externalId, fileId, findSimilarCapability, getEdgeWeight, groupTestsBySource, hashContent, hashEmbedText, indexPaths, invertBuckets, isGeneratedByHeuristic, isGeneratedFile, isInsideGitRepo, lineagePath, linkTestsToSources, listEdges, listEdgesTouching, listEmbeddableSymbols, listMetrics, listMetricsForNode, listNodes, loadCheckRules, loadGeneratedPatterns, loadHistoryMetrics, loadLineage, matchesAny, moduleId, openCodeGraph, packageId, parentModuleId, parseSymbolId, patternToRegex, planPrune, priorSnapshotForRef, pruneDanglingReferences, readSourceFiles, rebasedViolationKey, resolveAlias, resolveBarrelEdges, resolveChurnWindows, resolveGitRef, resolveSnapshot, runChecks, runPrune, snapshotPageRank, snapshotReferenceEdges, snapshotRelevance, snapshotSymbolConsumers, snapshotSymbolCoupling, snapshotViolations, structuralSignature, symbolId, testCoverageCountMetrics, tryEmbedSnapshot, validateRules, violationKey, walkSourceFiles, windowSuffix };