@titan-design/code-graph 0.9.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/index.d.ts +117 -1
- package/dist/index.js +202 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -271,6 +271,22 @@ snapshots and buckets each violation as new, resolved, or unchanged; unchanged m
|
|
|
271
271
|
violations are further split into worsened and improved by value. From-side ids follow the
|
|
272
272
|
alias chain, as in the ratchet.
|
|
273
273
|
|
|
274
|
+
`computeFootprints({ nodes, edges }, options)` gives each symbol node a footprint built from
|
|
275
|
+
structural inputs only, so a moved declaration or a churn window never reads as a change. It
|
|
276
|
+
has three parts, each a `hashText` over canonical JSON: `signature` (name, exported, signature,
|
|
277
|
+
purpose; no span), `consumers` (sorted distinct files with a `references` edge into the symbol),
|
|
278
|
+
and `coupling` (sorted co-import partners at or above `minCoImports`, default 2). `hash` combines
|
|
279
|
+
the three. Consumers include test files unless `ignoreConsumer(fileId)` rejects them; a rejected
|
|
280
|
+
file is dropped from coupling too. `symbolSetHash({ unitId, symbolIds }, footprints)` hashes a
|
|
281
|
+
doc unit's sorted `[symbolId, footprint.hash]` pairs, so it ignores member order and changes when
|
|
282
|
+
a member is added, removed or changed.
|
|
283
|
+
|
|
284
|
+
`diffFootprints(store, { fromSnapshotId, toSnapshotId, ...options })` compares the footprints of
|
|
285
|
+
two snapshots. From-side ids follow the alias chain first, and a symbol under a moved file follows
|
|
286
|
+
its file, so a move reads as `changed` with `reasons: ["renamed"]` alone when its footprint held.
|
|
287
|
+
Each other change is `added`, `removed`, or `changed` with the differing parts (`signature`,
|
|
288
|
+
`consumers`, `coupling`). `files` rolls the changes up to their declaring files, never to consumers.
|
|
289
|
+
|
|
274
290
|
`scripts/dag-check-self.mjs` in the repo root runs this repo's DAG check on this engine
|
|
275
291
|
instead of codewatch's CLI.
|
|
276
292
|
|
package/dist/index.d.ts
CHANGED
|
@@ -307,8 +307,12 @@ declare class TsMorphGraphExtractor implements Extractor<GraphFragment> {
|
|
|
307
307
|
private readonly repoRoot;
|
|
308
308
|
private readonly tsConfigPath?;
|
|
309
309
|
private project?;
|
|
310
|
+
private readonly ownsProject;
|
|
311
|
+
private readonly retired;
|
|
310
312
|
constructor(options: TsMorphGraphExtractorOptions);
|
|
311
313
|
extract(file: ParsedFile): GraphFragment[];
|
|
314
|
+
private retire;
|
|
315
|
+
private extractFrom;
|
|
312
316
|
private ensureProject;
|
|
313
317
|
private loadSourceFile;
|
|
314
318
|
private buildFileAndModuleNodes;
|
|
@@ -1423,6 +1427,27 @@ interface GraphDiffSummary {
|
|
|
1423
1427
|
removedEdges: number;
|
|
1424
1428
|
metricChanges: number;
|
|
1425
1429
|
}
|
|
1430
|
+
/** Why a symbol present in both snapshots changed; `renamed` alone means its footprint held. */
|
|
1431
|
+
type FootprintChangeReason = "signature" | "consumers" | "coupling" | "renamed";
|
|
1432
|
+
interface FootprintChange {
|
|
1433
|
+
/** The to-snapshot id; a removed symbol keeps its from-snapshot id. */
|
|
1434
|
+
symbolId: string;
|
|
1435
|
+
/** The from-snapshot id, set only when it differs from `symbolId`. */
|
|
1436
|
+
previousId?: string;
|
|
1437
|
+
status: "added" | "removed" | "changed";
|
|
1438
|
+
/** Empty for an added or removed symbol. */
|
|
1439
|
+
reasons: FootprintChangeReason[];
|
|
1440
|
+
/** The declaring file, in the to-snapshot's id space where it still exists there. */
|
|
1441
|
+
fileId: string;
|
|
1442
|
+
}
|
|
1443
|
+
interface FootprintDiff {
|
|
1444
|
+
fromSnapshotId: number;
|
|
1445
|
+
toSnapshotId: number;
|
|
1446
|
+
/** Sorted by `symbolId`; an unchanged symbol is absent. */
|
|
1447
|
+
changes: FootprintChange[];
|
|
1448
|
+
/** Sorted distinct declaring files of `changes`; consumer files never appear here. */
|
|
1449
|
+
files: string[];
|
|
1450
|
+
}
|
|
1426
1451
|
interface GraphDiff {
|
|
1427
1452
|
summary: GraphDiffSummary;
|
|
1428
1453
|
addedNodes: GraphNode[];
|
|
@@ -1470,6 +1495,97 @@ interface DiffCheckResultsOptions {
|
|
|
1470
1495
|
*/
|
|
1471
1496
|
declare function diffCheckResults(store: CodeGraphStore, options: DiffCheckResultsOptions): CheckDiff;
|
|
1472
1497
|
|
|
1498
|
+
/**
|
|
1499
|
+
* A symbol's documentable footprint (C-75): structural inputs only, so a moved declaration,
|
|
1500
|
+
* a churn window or a centrality shift never reads as a change.
|
|
1501
|
+
*/
|
|
1502
|
+
interface FootprintParts {
|
|
1503
|
+
/** Hash of `{name, exported, signature, purpose}`; no span. */
|
|
1504
|
+
signature: string;
|
|
1505
|
+
/** Hash of the sorted distinct file ids that reference the symbol. */
|
|
1506
|
+
consumers: string;
|
|
1507
|
+
/** Hash of the sorted co-import partner symbol ids at or above `minCoImports`. */
|
|
1508
|
+
coupling: string;
|
|
1509
|
+
}
|
|
1510
|
+
interface SymbolFootprint {
|
|
1511
|
+
symbolId: string;
|
|
1512
|
+
parts: FootprintParts;
|
|
1513
|
+
/** Hash of the three part hashes. */
|
|
1514
|
+
hash: string;
|
|
1515
|
+
}
|
|
1516
|
+
interface FootprintGraph {
|
|
1517
|
+
nodes: readonly GraphNode[];
|
|
1518
|
+
edges: readonly GraphEdge[];
|
|
1519
|
+
}
|
|
1520
|
+
interface FootprintOptions {
|
|
1521
|
+
/** Drop a referencing file from consumers and co-import coupling, e.g. test files. Default keeps all. */
|
|
1522
|
+
ignoreConsumer?: (fileId: string) => boolean;
|
|
1523
|
+
/** Co-import threshold for a coupling partner. Default 2. */
|
|
1524
|
+
minCoImports?: number;
|
|
1525
|
+
}
|
|
1526
|
+
/** A doc target: the default unit is one file holding its symbols. */
|
|
1527
|
+
interface FootprintUnit {
|
|
1528
|
+
unitId: string;
|
|
1529
|
+
symbolIds: readonly string[];
|
|
1530
|
+
}
|
|
1531
|
+
/** Footprint of every symbol node in the graph, keyed by symbol id. */
|
|
1532
|
+
declare function computeFootprints(graph: FootprintGraph, options?: FootprintOptions): Map<string, SymbolFootprint>;
|
|
1533
|
+
/**
|
|
1534
|
+
* Order-independent hash of a unit's `[symbolId, footprint.hash]` pairs. A member with no
|
|
1535
|
+
* footprint hashes as null, so a symbol that disappears still changes the set.
|
|
1536
|
+
*/
|
|
1537
|
+
declare function symbolSetHash(unit: FootprintUnit, footprints: ReadonlyMap<string, SymbolFootprint>): string;
|
|
1538
|
+
|
|
1539
|
+
interface DiffFootprintsOptions extends FootprintOptions {
|
|
1540
|
+
fromSnapshotId: number;
|
|
1541
|
+
toSnapshotId: number;
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Symbols whose documentable footprint differs between two snapshots. From-snapshot ids
|
|
1545
|
+
* follow the alias chain first, so a moved file reads as `renamed`, not a remove plus an add.
|
|
1546
|
+
*/
|
|
1547
|
+
declare function diffFootprints(store: CodeGraphStore, options: DiffFootprintsOptions): FootprintDiff;
|
|
1548
|
+
|
|
1549
|
+
/**
|
|
1550
|
+
* What a consumer generated a unit's doc from. Provenance lives with the consumer, not the
|
|
1551
|
+
* store, so it survives snapshot prune.
|
|
1552
|
+
*/
|
|
1553
|
+
interface UnitProvenance {
|
|
1554
|
+
unitId: string;
|
|
1555
|
+
/** The to-snapshot's commit hash; null for a working-tree snapshot. */
|
|
1556
|
+
commit: string | null;
|
|
1557
|
+
symbolSetHash: string;
|
|
1558
|
+
/** The generator the consumer used; the gate records it but never calls one. */
|
|
1559
|
+
model: string | null;
|
|
1560
|
+
}
|
|
1561
|
+
interface UnitProvenanceInput {
|
|
1562
|
+
unit: FootprintUnit;
|
|
1563
|
+
footprints: ReadonlyMap<string, SymbolFootprint>;
|
|
1564
|
+
snapshot: Pick<SnapshotRow, "commitHash">;
|
|
1565
|
+
model?: string | null;
|
|
1566
|
+
}
|
|
1567
|
+
type RegenerateReason = "new" | "changed";
|
|
1568
|
+
interface UnitRegeneration {
|
|
1569
|
+
unitId: string;
|
|
1570
|
+
reason: RegenerateReason;
|
|
1571
|
+
}
|
|
1572
|
+
interface GateUnitsInput {
|
|
1573
|
+
prior: readonly UnitProvenance[];
|
|
1574
|
+
units: readonly FootprintUnit[];
|
|
1575
|
+
footprints: ReadonlyMap<string, SymbolFootprint>;
|
|
1576
|
+
}
|
|
1577
|
+
/** An empty `regenerate` means the caller makes no LLM call. */
|
|
1578
|
+
interface GateResult {
|
|
1579
|
+
regenerate: UnitRegeneration[];
|
|
1580
|
+
skip: string[];
|
|
1581
|
+
/** Prior units that no longer exist. */
|
|
1582
|
+
orphaned: string[];
|
|
1583
|
+
}
|
|
1584
|
+
/** A fresh provenance record for one unit at the given snapshot. */
|
|
1585
|
+
declare function unitProvenance(input: UnitProvenanceInput): UnitProvenance;
|
|
1586
|
+
/** Splits units into those whose symbol-set hash moved since the prior record and those that did not. */
|
|
1587
|
+
declare function gateUnits(input: GateUnitsInput): GateResult;
|
|
1588
|
+
|
|
1473
1589
|
interface EmbeddableSymbol {
|
|
1474
1590
|
id: string;
|
|
1475
1591
|
name: string;
|
|
@@ -1686,4 +1802,4 @@ declare function getConventionMap(store: CodeGraphStore, snapshotId: number, mod
|
|
|
1686
1802
|
*/
|
|
1687
1803
|
declare function findConventions(store: CodeGraphStore, snapshotId: number, query: string, embedder: Embedder, model: string, opts?: FindConventionsOptions): Promise<ConventionQueryResult>;
|
|
1688
1804
|
|
|
1689
|
-
export { ALIAS_BASE_ATTR, ALL_ROLES, type AliasChain, type AliasChainInput, type AliasChainOptions, type AliasLoader, type AliasResolution, COMMUNITY_SUMMARY_NAMESPACE, COVERAGE_METRIC_NAME, type CachedEmbedResult, type CheckDiff, type CheckResult, type CheckRule, type CheckRulesFile, type CheckSnapshotOptions, type CheckSnapshotResult, type CheckViolation, CodeGraphStore, type ConventionArea, type ConventionCorpus, type ConventionCoverage, type ConventionMap, type ConventionMatch, type ConventionOptions, type ConventionQueryResult, type ConventionSymbol, DEAD_CODE_METRIC_NAMES, DEFAULT_CHURN_WINDOWS, DEFAULT_OUTLIER_MIN_SAMPLE, DOMAIN_DDL, type DeepAst, type DeepAstInput, type DiffCheckResultsOptions, type DiffSnapshotsOptions, EXCEPTION_METRIC_NAMES, type EdgeKind, type EmbedAttempt, type EmbedCoverage, type EmbedSnapshotResult, type EmbeddableSymbol, type ExternalDiagnostic, FINDING_DDL, type FileFingerprint, type FindConventionsOptions, type FindSimilarOptions, type Finding, type FindingKeyInput, 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 MetricOutlierRule, 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, SYMBOL_METRIC_NAMES, type Severity, type SimilarCandidate, type SimilarResult, type SnapshotInsert, type SnapshotRow, type SnapshotSpec, type SourceLanguage, type StoredFinding, type StoredVerdict, type SummarizeConventionsResult, type Summarizer, type SymbolConsumers, type SymbolCouplingOptions, type SymbolCouplingPair, type SymbolSpan, type TestCoverageOwnershipOptions, type TestSourceLink, type TopMetricRow, TsMorphGraphExtractor, type TsMorphGraphExtractorOptions, type UnchangedViolation, type ValidateRulesOptions, type VerdictCitation, type VerdictLabel, aggregateMetrics, aliasChain, annotateRoles, attributeCoverage, buildAliases, buildConventionAreas, buildEmbedText, buildFileModuleNodes, buildIndexerMetrics, buildLineage, canonicalEdgeKind, canonicalMetricName, canonicalRole, carryForwardVerdicts, checkSnapshot, classifyRole, collectDeclaredNames, collectDeclaredSpans, compilePatterns, computeDeadCodeMetrics, computeDeepAst, computeGrowthRiskMetrics, computeMetrics, computePageRank, computePartitionQuality, computeRecencyWindows, computeRelevance, computeRoleHints, computeSourceMetrics, computeSymbolConsumers, computeSymbolCoupling, computeTestCoverageOwnership, createAliasChain, defaultTargetCount, describeMetric, describeMetrics, detectCommunities, detectGitHead, detectGitToplevel, detectRenames, diffCheckResults, diffSnapshots, edgeWeight, embedSnapshot, embedTextsCached, externalId, externalToFinding, fileId, findConventions, findSimilarCapability, findingKey, getConventionMap, getEdgeWeight, groupTestsBySource, hashContent, hashEmbedText, hashText, indexPaths, invertBuckets, isGeneratedByHeuristic, isGeneratedFile, isInsideGitRepo, keyFindings, lineagePath, linkTestsToSources, listEdges, listEdgesTouching, listEmbeddableSymbols, listFindings, listMetrics, listMetricsForNode, listNodes, listVerdicts, loadCheckRules, loadGeneratedPatterns, loadHistoryMetrics, loadLineage, matchesAny, moduleId, normalizeFlaggedText, openCodeGraph, packageId, parentModuleId, parseSymbolId, patternToRegex, percentileOf, planPrune, priorSnapshotForRef, pruneDanglingReferences, readSourceFiles, rebasedViolationKey, resolveAlias, resolveBarrelEdges, resolveChurnWindows, resolveGitRef, resolveSnapshot, runChecks, runPrune, saveFindings, saveVerdicts, snapshotPageRank, snapshotReferenceEdges, snapshotRelevance, snapshotSymbolConsumers, snapshotSymbolCoupling, snapshotViolations, structuralSignature, summarizeConventions, symbolId, testCoverageCountMetrics, toFindings, tryEmbedSnapshot, validateRules, violationKey, walkSourceFiles, windowSuffix };
|
|
1805
|
+
export { ALIAS_BASE_ATTR, ALL_ROLES, type AliasChain, type AliasChainInput, type AliasChainOptions, type AliasLoader, type AliasResolution, COMMUNITY_SUMMARY_NAMESPACE, COVERAGE_METRIC_NAME, type CachedEmbedResult, type CheckDiff, type CheckResult, type CheckRule, type CheckRulesFile, type CheckSnapshotOptions, type CheckSnapshotResult, type CheckViolation, CodeGraphStore, type ConventionArea, type ConventionCorpus, type ConventionCoverage, type ConventionMap, type ConventionMatch, type ConventionOptions, type ConventionQueryResult, type ConventionSymbol, DEAD_CODE_METRIC_NAMES, DEFAULT_CHURN_WINDOWS, DEFAULT_OUTLIER_MIN_SAMPLE, DOMAIN_DDL, type DeepAst, type DeepAstInput, type DiffCheckResultsOptions, type DiffFootprintsOptions, type DiffSnapshotsOptions, EXCEPTION_METRIC_NAMES, type EdgeKind, type EmbedAttempt, type EmbedCoverage, type EmbedSnapshotResult, type EmbeddableSymbol, type ExternalDiagnostic, FINDING_DDL, type FileFingerprint, type FindConventionsOptions, type FindSimilarOptions, type Finding, type FindingKeyInput, type FootprintChange, type FootprintChangeReason, type FootprintDiff, type FootprintGraph, type FootprintOptions, type FootprintParts, type FootprintUnit, type ForbidImportRule, GROWTH_RISK_METRIC_NAMES, type GateResult, type GateUnitsInput, 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 MetricOutlierRule, 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 RegenerateReason, type RelevanceOptions, type ResolveAliasOptions, type ReuseBasis, type RuleStore, type RunChecksOptions, SCHEMA_VERSION, SNAPSHOT_SCOPED_TABLES, SOURCE_METRIC_NAMES, SYMBOL_EMBEDDING_NAMESPACE, SYMBOL_ID_SEP, SYMBOL_METRIC_NAMES, type Severity, type SimilarCandidate, type SimilarResult, type SnapshotInsert, type SnapshotRow, type SnapshotSpec, type SourceLanguage, type StoredFinding, type StoredVerdict, type SummarizeConventionsResult, type Summarizer, type SymbolConsumers, type SymbolCouplingOptions, type SymbolCouplingPair, type SymbolFootprint, type SymbolSpan, type TestCoverageOwnershipOptions, type TestSourceLink, type TopMetricRow, TsMorphGraphExtractor, type TsMorphGraphExtractorOptions, type UnchangedViolation, type UnitProvenance, type UnitProvenanceInput, type UnitRegeneration, type ValidateRulesOptions, type VerdictCitation, type VerdictLabel, aggregateMetrics, aliasChain, annotateRoles, attributeCoverage, buildAliases, buildConventionAreas, buildEmbedText, buildFileModuleNodes, buildIndexerMetrics, buildLineage, canonicalEdgeKind, canonicalMetricName, canonicalRole, carryForwardVerdicts, checkSnapshot, classifyRole, collectDeclaredNames, collectDeclaredSpans, compilePatterns, computeDeadCodeMetrics, computeDeepAst, computeFootprints, computeGrowthRiskMetrics, computeMetrics, computePageRank, computePartitionQuality, computeRecencyWindows, computeRelevance, computeRoleHints, computeSourceMetrics, computeSymbolConsumers, computeSymbolCoupling, computeTestCoverageOwnership, createAliasChain, defaultTargetCount, describeMetric, describeMetrics, detectCommunities, detectGitHead, detectGitToplevel, detectRenames, diffCheckResults, diffFootprints, diffSnapshots, edgeWeight, embedSnapshot, embedTextsCached, externalId, externalToFinding, fileId, findConventions, findSimilarCapability, findingKey, gateUnits, getConventionMap, getEdgeWeight, groupTestsBySource, hashContent, hashEmbedText, hashText, indexPaths, invertBuckets, isGeneratedByHeuristic, isGeneratedFile, isInsideGitRepo, keyFindings, lineagePath, linkTestsToSources, listEdges, listEdgesTouching, listEmbeddableSymbols, listFindings, listMetrics, listMetricsForNode, listNodes, listVerdicts, loadCheckRules, loadGeneratedPatterns, loadHistoryMetrics, loadLineage, matchesAny, moduleId, normalizeFlaggedText, openCodeGraph, packageId, parentModuleId, parseSymbolId, patternToRegex, percentileOf, planPrune, priorSnapshotForRef, pruneDanglingReferences, readSourceFiles, rebasedViolationKey, resolveAlias, resolveBarrelEdges, resolveChurnWindows, resolveGitRef, resolveSnapshot, runChecks, runPrune, saveFindings, saveVerdicts, snapshotPageRank, snapshotReferenceEdges, snapshotRelevance, snapshotSymbolConsumers, snapshotSymbolCoupling, snapshotViolations, structuralSignature, summarizeConventions, symbolId, symbolSetHash, testCoverageCountMetrics, toFindings, tryEmbedSnapshot, unitProvenance, validateRules, violationKey, walkSourceFiles, windowSuffix };
|
package/dist/index.js
CHANGED
|
@@ -1223,20 +1223,36 @@ function literalText(arg) {
|
|
|
1223
1223
|
}
|
|
1224
1224
|
|
|
1225
1225
|
// src/extractors/ts-morph-extractor.ts
|
|
1226
|
+
var RETIRE_BATCH = 100;
|
|
1226
1227
|
var TsMorphGraphExtractor = class {
|
|
1227
1228
|
name = "ts-morph-graph";
|
|
1228
1229
|
repoRoot;
|
|
1229
1230
|
tsConfigPath;
|
|
1230
1231
|
project;
|
|
1232
|
+
ownsProject;
|
|
1233
|
+
retired = [];
|
|
1231
1234
|
constructor(options) {
|
|
1232
1235
|
this.repoRoot = options.repoRoot;
|
|
1233
1236
|
this.tsConfigPath = options.tsConfigPath;
|
|
1234
1237
|
this.project = options.project;
|
|
1238
|
+
this.ownsProject = options.project === void 0;
|
|
1235
1239
|
}
|
|
1236
1240
|
extract(file) {
|
|
1237
1241
|
if (!isTypeScriptFile(file)) return [];
|
|
1238
1242
|
const project = this.ensureProject();
|
|
1239
1243
|
const sourceFile = this.loadSourceFile(project, file);
|
|
1244
|
+
try {
|
|
1245
|
+
return this.extractFrom(sourceFile, file);
|
|
1246
|
+
} finally {
|
|
1247
|
+
if (this.ownsProject) this.retire(project, sourceFile);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
retire(project, sourceFile) {
|
|
1251
|
+
this.retired.push(sourceFile);
|
|
1252
|
+
if (this.retired.length < RETIRE_BATCH) return;
|
|
1253
|
+
for (const sf of this.retired.splice(0)) project.removeSourceFile(sf);
|
|
1254
|
+
}
|
|
1255
|
+
extractFrom(sourceFile, file) {
|
|
1240
1256
|
const nodes = this.buildFileAndModuleNodes(sourceFile);
|
|
1241
1257
|
const symbolNodes2 = buildSymbolNodes(this.repoRoot, sourceFile, file);
|
|
1242
1258
|
const { edges, externalNodes } = this.collectEdges(sourceFile);
|
|
@@ -3461,16 +3477,16 @@ function targetFileId(edge) {
|
|
|
3461
3477
|
}
|
|
3462
3478
|
function computeDeltaAffected(basis, currentFileIds) {
|
|
3463
3479
|
const affected = /* @__PURE__ */ new Set();
|
|
3464
|
-
const
|
|
3480
|
+
const removed2 = /* @__PURE__ */ new Set();
|
|
3465
3481
|
for (const id of basis.fingerprints.keys()) {
|
|
3466
|
-
if (!currentFileIds.has(id))
|
|
3482
|
+
if (!currentFileIds.has(id)) removed2.add(id);
|
|
3467
3483
|
}
|
|
3468
|
-
const added = currentFileIds.size !== basis.fingerprints.size ||
|
|
3484
|
+
const added = currentFileIds.size !== basis.fingerprints.size || removed2.size > 0;
|
|
3469
3485
|
if (!added) return affected;
|
|
3470
3486
|
for (const [srcId, edges] of basis.edgesBySrc) {
|
|
3471
3487
|
if (!currentFileIds.has(srcId)) continue;
|
|
3472
3488
|
for (const edge of edges) {
|
|
3473
|
-
if (isEdgeAffected(edge, srcId,
|
|
3489
|
+
if (isEdgeAffected(edge, srcId, removed2, currentFileIds)) {
|
|
3474
3490
|
affected.add(srcId);
|
|
3475
3491
|
break;
|
|
3476
3492
|
}
|
|
@@ -3478,8 +3494,8 @@ function computeDeltaAffected(basis, currentFileIds) {
|
|
|
3478
3494
|
}
|
|
3479
3495
|
return affected;
|
|
3480
3496
|
}
|
|
3481
|
-
function isEdgeAffected(edge, srcId,
|
|
3482
|
-
if (
|
|
3497
|
+
function isEdgeAffected(edge, srcId, removed2, currentFileIds) {
|
|
3498
|
+
if (removed2.has(targetFileId(edge))) return true;
|
|
3483
3499
|
const specifier = edge.attrs?.specifier;
|
|
3484
3500
|
if (!specifier || !isRelative(specifier)) return false;
|
|
3485
3501
|
return resolveRelativeId(srcId, specifier, currentFileIds) !== targetFileId(edge);
|
|
@@ -6071,13 +6087,13 @@ function indexEdges(edges) {
|
|
|
6071
6087
|
function diffNodes(fromNodes, toNodes, aliases) {
|
|
6072
6088
|
const toIndex = new Map(toNodes.map((n) => [n.id, n]));
|
|
6073
6089
|
const matchedToIds = /* @__PURE__ */ new Set();
|
|
6074
|
-
const
|
|
6090
|
+
const removed2 = [];
|
|
6075
6091
|
const renamed = [];
|
|
6076
6092
|
for (const fromNode of fromNodes) {
|
|
6077
6093
|
const newId = resolveId(fromNode.id, aliases);
|
|
6078
6094
|
const toNode = toIndex.get(newId);
|
|
6079
6095
|
if (!toNode) {
|
|
6080
|
-
|
|
6096
|
+
removed2.push(fromNode);
|
|
6081
6097
|
continue;
|
|
6082
6098
|
}
|
|
6083
6099
|
matchedToIds.add(newId);
|
|
@@ -6086,7 +6102,7 @@ function diffNodes(fromNodes, toNodes, aliases) {
|
|
|
6086
6102
|
}
|
|
6087
6103
|
}
|
|
6088
6104
|
const added = toNodes.filter((toNode) => !matchedToIds.has(toNode.id));
|
|
6089
|
-
return { added, removed, renamed, commonNewIds: matchedToIds };
|
|
6105
|
+
return { added, removed: removed2, renamed, commonNewIds: matchedToIds };
|
|
6090
6106
|
}
|
|
6091
6107
|
function diffEdges(fromEdges, toEdges, aliases) {
|
|
6092
6108
|
const remappedKey = (e) => edgeKey2(resolveId(e.srcId, aliases), resolveId(e.dstId, aliases), e.kind);
|
|
@@ -6096,8 +6112,8 @@ function diffEdges(fromEdges, toEdges, aliases) {
|
|
|
6096
6112
|
for (const [k, e] of toIndex) {
|
|
6097
6113
|
if (!fromKeys.has(k)) added.push(e);
|
|
6098
6114
|
}
|
|
6099
|
-
const
|
|
6100
|
-
return { added, removed };
|
|
6115
|
+
const removed2 = fromEdges.filter((e) => !toIndex.has(remappedKey(e)));
|
|
6116
|
+
return { added, removed: removed2 };
|
|
6101
6117
|
}
|
|
6102
6118
|
function indexFromMetrics(metrics, aliases) {
|
|
6103
6119
|
const fromIndex = /* @__PURE__ */ new Map();
|
|
@@ -6207,6 +6223,176 @@ function computeDelta(from, to) {
|
|
|
6207
6223
|
return to.value - from.value;
|
|
6208
6224
|
}
|
|
6209
6225
|
|
|
6226
|
+
// src/diff/footprint.ts
|
|
6227
|
+
var DEFAULT_MIN_CO_IMPORTS2 = 2;
|
|
6228
|
+
function computeFootprints(graph, options = {}) {
|
|
6229
|
+
const edges = referenceEdges(graph.edges, options.ignoreConsumer);
|
|
6230
|
+
const consumers = consumersBySymbol(edges);
|
|
6231
|
+
const partners = partnersBySymbol(edges, options.minCoImports ?? DEFAULT_MIN_CO_IMPORTS2);
|
|
6232
|
+
const out = /* @__PURE__ */ new Map();
|
|
6233
|
+
for (const node of graph.nodes) {
|
|
6234
|
+
if (node.kind !== "symbol") continue;
|
|
6235
|
+
const parts = {
|
|
6236
|
+
signature: hashJson(signatureOf2(node)),
|
|
6237
|
+
consumers: hashJson(consumers.get(node.id) ?? []),
|
|
6238
|
+
coupling: hashJson(partners.get(node.id) ?? [])
|
|
6239
|
+
};
|
|
6240
|
+
const hash = hashJson([parts.signature, parts.consumers, parts.coupling]);
|
|
6241
|
+
out.set(node.id, { symbolId: node.id, parts, hash });
|
|
6242
|
+
}
|
|
6243
|
+
return out;
|
|
6244
|
+
}
|
|
6245
|
+
function symbolSetHash(unit, footprints) {
|
|
6246
|
+
const pairs = [...new Set(unit.symbolIds)].sort().map((id) => [id, footprints.get(id)?.hash ?? null]);
|
|
6247
|
+
return hashJson(pairs);
|
|
6248
|
+
}
|
|
6249
|
+
function hashJson(value) {
|
|
6250
|
+
return hashText(JSON.stringify(value));
|
|
6251
|
+
}
|
|
6252
|
+
function signatureOf2(node) {
|
|
6253
|
+
const attrs = node.attrs ?? {};
|
|
6254
|
+
return {
|
|
6255
|
+
name: node.name,
|
|
6256
|
+
exported: attrs.exported === true,
|
|
6257
|
+
signature: typeof attrs.signature === "string" ? attrs.signature : null,
|
|
6258
|
+
purpose: typeof attrs.purpose === "string" ? attrs.purpose : null
|
|
6259
|
+
};
|
|
6260
|
+
}
|
|
6261
|
+
function referenceEdges(edges, ignoreConsumer) {
|
|
6262
|
+
return edges.filter(
|
|
6263
|
+
(e) => canonicalEdgeKind(e.kind) === "references" && !ignoreConsumer?.(e.srcId)
|
|
6264
|
+
);
|
|
6265
|
+
}
|
|
6266
|
+
function consumersBySymbol(edges) {
|
|
6267
|
+
return new Map(computeSymbolConsumers(edges).map((c) => [c.symbolId, c.consumers]));
|
|
6268
|
+
}
|
|
6269
|
+
function partnersBySymbol(edges, minCoImports) {
|
|
6270
|
+
const partners = /* @__PURE__ */ new Map();
|
|
6271
|
+
const add = (from, to) => {
|
|
6272
|
+
const set = partners.get(from) ?? /* @__PURE__ */ new Set();
|
|
6273
|
+
set.add(to);
|
|
6274
|
+
partners.set(from, set);
|
|
6275
|
+
};
|
|
6276
|
+
for (const pair of computeSymbolCoupling(edges, { minCoImports })) {
|
|
6277
|
+
add(pair.aId, pair.bId);
|
|
6278
|
+
add(pair.bId, pair.aId);
|
|
6279
|
+
}
|
|
6280
|
+
return new Map([...partners].map(([id, set]) => [id, [...set].sort()]));
|
|
6281
|
+
}
|
|
6282
|
+
|
|
6283
|
+
// src/diff/footprint-diff.ts
|
|
6284
|
+
var PART_REASONS = ["signature", "consumers", "coupling"];
|
|
6285
|
+
function diffFootprints(store, options) {
|
|
6286
|
+
const { fromSnapshotId, toSnapshotId } = options;
|
|
6287
|
+
const resolve4 = symbolAwareResolver(aliasChain(store, fromSnapshotId, toSnapshotId));
|
|
6288
|
+
const fromGraph = loadSymbolLayer(store, fromSnapshotId);
|
|
6289
|
+
const toGraph = loadSymbolLayer(store, toSnapshotId);
|
|
6290
|
+
const { previousIds, mergedAway } = mergeSources(fromGraph.nodes, resolve4);
|
|
6291
|
+
const before = computeFootprints(remapGraph(fromGraph, resolve4, new Set(mergedAway)), options);
|
|
6292
|
+
const after = computeFootprints(toGraph, options);
|
|
6293
|
+
const changes = [
|
|
6294
|
+
...changedOrAdded(after, before, previousIds),
|
|
6295
|
+
...removed(before, after, previousIds, resolve4),
|
|
6296
|
+
...mergedAway.map((id) => ({ symbolId: id, status: "removed", reasons: [], fileId: resolve4(declaringFile(id)) }))
|
|
6297
|
+
].sort((a, b) => compare2(a.symbolId, b.symbolId));
|
|
6298
|
+
const files = [...new Set(changes.map((c) => c.fileId))].sort(compare2);
|
|
6299
|
+
return { fromSnapshotId, toSnapshotId, changes, files };
|
|
6300
|
+
}
|
|
6301
|
+
function loadSymbolLayer(store, snapshotId) {
|
|
6302
|
+
return {
|
|
6303
|
+
nodes: store.listNodes(snapshotId, { includeSymbols: true }).filter((n) => n.kind === "symbol"),
|
|
6304
|
+
edges: store.listEdges(snapshotId, { includeReferences: true })
|
|
6305
|
+
};
|
|
6306
|
+
}
|
|
6307
|
+
function symbolAwareResolver(aliases) {
|
|
6308
|
+
return (id) => {
|
|
6309
|
+
const direct = aliases.resolve(id);
|
|
6310
|
+
if (direct !== id) return direct;
|
|
6311
|
+
const parsed = parseSymbolId(id);
|
|
6312
|
+
return parsed ? symbolId(aliases.resolve(parsed.fileId), parsed.name) : id;
|
|
6313
|
+
};
|
|
6314
|
+
}
|
|
6315
|
+
function remapGraph(graph, resolve4, losers) {
|
|
6316
|
+
return {
|
|
6317
|
+
nodes: graph.nodes.filter((n) => !losers.has(n.id)).map((n) => ({ ...n, id: resolve4(n.id) })),
|
|
6318
|
+
edges: graph.edges.filter((e) => !losers.has(e.srcId) && !losers.has(e.dstId)).map((e) => ({ ...e, srcId: resolve4(e.srcId), dstId: resolve4(e.dstId) }))
|
|
6319
|
+
};
|
|
6320
|
+
}
|
|
6321
|
+
function mergeSources(nodes, resolve4) {
|
|
6322
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6323
|
+
for (const node of nodes) {
|
|
6324
|
+
const id = resolve4(node.id);
|
|
6325
|
+
groups.set(id, [...groups.get(id) ?? [], node.id]);
|
|
6326
|
+
}
|
|
6327
|
+
const previousIds = /* @__PURE__ */ new Map();
|
|
6328
|
+
const mergedAway = [];
|
|
6329
|
+
for (const [id, sources] of groups) {
|
|
6330
|
+
const keeper = sources.includes(id) ? id : [...sources].sort(compare2)[0];
|
|
6331
|
+
if (keeper !== id) previousIds.set(id, keeper);
|
|
6332
|
+
mergedAway.push(...sources.filter((source) => source !== keeper));
|
|
6333
|
+
}
|
|
6334
|
+
return { previousIds, mergedAway };
|
|
6335
|
+
}
|
|
6336
|
+
function changedOrAdded(after, before, previousIds) {
|
|
6337
|
+
const out = [];
|
|
6338
|
+
for (const [id, footprint] of after) {
|
|
6339
|
+
const prior = before.get(id);
|
|
6340
|
+
const previousId = previousIds.get(id);
|
|
6341
|
+
if (!prior) {
|
|
6342
|
+
out.push({ symbolId: id, status: "added", reasons: [], fileId: declaringFile(id) });
|
|
6343
|
+
continue;
|
|
6344
|
+
}
|
|
6345
|
+
const reasons = reasonsFor(prior, footprint, previousId !== void 0);
|
|
6346
|
+
if (reasons.length === 0) continue;
|
|
6347
|
+
out.push({ symbolId: id, ...previousId ? { previousId } : {}, status: "changed", reasons, fileId: declaringFile(id) });
|
|
6348
|
+
}
|
|
6349
|
+
return out;
|
|
6350
|
+
}
|
|
6351
|
+
function reasonsFor(prior, current, renamed) {
|
|
6352
|
+
const reasons = PART_REASONS.filter((part) => prior.parts[part] !== current.parts[part]);
|
|
6353
|
+
if (renamed) reasons.push("renamed");
|
|
6354
|
+
return reasons;
|
|
6355
|
+
}
|
|
6356
|
+
function removed(before, after, previousIds, resolve4) {
|
|
6357
|
+
const out = [];
|
|
6358
|
+
for (const id of before.keys()) {
|
|
6359
|
+
if (after.has(id)) continue;
|
|
6360
|
+
const fromId = previousIds.get(id) ?? id;
|
|
6361
|
+
out.push({ symbolId: fromId, status: "removed", reasons: [], fileId: resolve4(declaringFile(fromId)) });
|
|
6362
|
+
}
|
|
6363
|
+
return out;
|
|
6364
|
+
}
|
|
6365
|
+
function declaringFile(id) {
|
|
6366
|
+
return parseSymbolId(id)?.fileId ?? id;
|
|
6367
|
+
}
|
|
6368
|
+
function compare2(a, b) {
|
|
6369
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
6370
|
+
}
|
|
6371
|
+
|
|
6372
|
+
// src/diff/gate.ts
|
|
6373
|
+
function unitProvenance(input) {
|
|
6374
|
+
return {
|
|
6375
|
+
unitId: input.unit.unitId,
|
|
6376
|
+
commit: input.snapshot.commitHash,
|
|
6377
|
+
symbolSetHash: symbolSetHash(input.unit, input.footprints),
|
|
6378
|
+
model: input.model ?? null
|
|
6379
|
+
};
|
|
6380
|
+
}
|
|
6381
|
+
function gateUnits(input) {
|
|
6382
|
+
const priorHash = new Map(input.prior.map((p) => [p.unitId, p.symbolSetHash]));
|
|
6383
|
+
const current = new Set(input.units.map((u) => u.unitId));
|
|
6384
|
+
const regenerate = [];
|
|
6385
|
+
const skip = [];
|
|
6386
|
+
for (const unit of input.units) {
|
|
6387
|
+
const before = priorHash.get(unit.unitId);
|
|
6388
|
+
if (before === void 0) regenerate.push({ unitId: unit.unitId, reason: "new" });
|
|
6389
|
+
else if (before !== symbolSetHash(unit, input.footprints)) regenerate.push({ unitId: unit.unitId, reason: "changed" });
|
|
6390
|
+
else skip.push(unit.unitId);
|
|
6391
|
+
}
|
|
6392
|
+
const orphaned = [...priorHash.keys()].filter((id) => !current.has(id));
|
|
6393
|
+
return { regenerate, skip, orphaned };
|
|
6394
|
+
}
|
|
6395
|
+
|
|
6210
6396
|
// src/embeddings/corpus.ts
|
|
6211
6397
|
import { contentHashOf } from "@titan-design/store-sqlite";
|
|
6212
6398
|
function buildEmbedText(signature, purpose) {
|
|
@@ -6705,6 +6891,7 @@ export {
|
|
|
6705
6891
|
compilePatterns,
|
|
6706
6892
|
computeDeadCodeMetrics,
|
|
6707
6893
|
computeDeepAst,
|
|
6894
|
+
computeFootprints,
|
|
6708
6895
|
computeGrowthRiskMetrics,
|
|
6709
6896
|
computeMetrics,
|
|
6710
6897
|
computePageRank,
|
|
@@ -6725,6 +6912,7 @@ export {
|
|
|
6725
6912
|
detectGitToplevel,
|
|
6726
6913
|
detectRenames,
|
|
6727
6914
|
diffCheckResults,
|
|
6915
|
+
diffFootprints,
|
|
6728
6916
|
diffSnapshots,
|
|
6729
6917
|
edgeWeight,
|
|
6730
6918
|
embedSnapshot,
|
|
@@ -6735,6 +6923,7 @@ export {
|
|
|
6735
6923
|
findConventions,
|
|
6736
6924
|
findSimilarCapability,
|
|
6737
6925
|
findingKey,
|
|
6926
|
+
gateUnits,
|
|
6738
6927
|
getConventionMap,
|
|
6739
6928
|
getEdgeWeight,
|
|
6740
6929
|
getLanguageFromPath3 as getLanguageFromPath,
|
|
@@ -6797,9 +6986,11 @@ export {
|
|
|
6797
6986
|
structuralSignature,
|
|
6798
6987
|
summarizeConventions,
|
|
6799
6988
|
symbolId,
|
|
6989
|
+
symbolSetHash,
|
|
6800
6990
|
testCoverageCountMetrics,
|
|
6801
6991
|
toFindings,
|
|
6802
6992
|
tryEmbedSnapshot,
|
|
6993
|
+
unitProvenance,
|
|
6803
6994
|
validateRules,
|
|
6804
6995
|
violationKey,
|
|
6805
6996
|
walkSourceFiles,
|