@titan-design/code-graph 0.10.0 → 0.13.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 +135 -5
- package/dist/analysis/browser.d.ts +2 -0
- package/dist/analysis/browser.js +84 -0
- package/dist/analysis/browser.js.map +1 -0
- package/dist/browser-BBzShneN.d.ts +936 -0
- package/dist/{change-coupling-CyqHgRsm.d.ts → change-coupling-CT3bcZMD.d.ts} +5 -1
- package/dist/{chunk-PFI5XMUG.js → chunk-GX563F6R.js} +79 -89
- package/dist/chunk-GX563F6R.js.map +1 -0
- package/dist/chunk-HIVVDGWE.js +83 -0
- package/dist/chunk-HIVVDGWE.js.map +1 -0
- package/dist/chunk-L5KU543A.js +1395 -0
- package/dist/chunk-L5KU543A.js.map +1 -0
- package/dist/history/index.d.ts +25 -4
- package/dist/history/index.js +11 -3
- package/dist/index.d.ts +406 -353
- package/dist/index.js +1753 -889
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
- package/dist/chunk-PFI5XMUG.js.map +0 -1
package/README.md
CHANGED
|
@@ -73,19 +73,50 @@ index version 0.14.0:
|
|
|
73
73
|
first `#`. A top-level declaration's qualified name is its own name
|
|
74
74
|
(`src/a.ts#createThing`). A member or nested declaration is prefixed by its enclosing named
|
|
75
75
|
scopes, joined with `.`: `src/a.ts#Job.run`, `src/a.ts#outer.helper`, and
|
|
76
|
-
`src/a.ts#handlers.onClick` for a method of `const handlers = {…}`.
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
`src/a.ts#handlers.onClick` for a method of `const handlers = {…}`. An unbound callback
|
|
77
|
+
argument, object or class expression adds one `<anonymous>` segment, and consecutive
|
|
78
|
+
anonymous scopes collapse to one, so
|
|
79
|
+
`src/a.ts#App.<anonymous>.onHash` is a function declared inside a callback in `App`. The
|
|
80
|
+
segment keeps it apart from a member `App.onHash`, and ids do not depend on declaration
|
|
81
|
+
order. One scope binds a name once: a getter/setter pair, a Python property's
|
|
79
82
|
accessors, and overloads each share one node. Index versions before 0.14.0 keyed members by
|
|
80
83
|
bare name, so same-named methods in one file collapsed into one node (TP-182).
|
|
81
84
|
- An **external** id is `npm:<package>` (scope-aware) or the `node:` builtin verbatim.
|
|
82
85
|
|
|
83
|
-
`NodeKind
|
|
86
|
+
`NodeKind` and `EdgeKind` are unchanged, and the `role` vocabulary only grew (see
|
|
87
|
+
[File roles](#file-roles)). So is the property the DAG
|
|
84
88
|
check rests on: an import of a workspace package by its published name resolves to that
|
|
85
89
|
package's source file, not to an `npm:` external, by remapping the `dist/*.d.ts` entry
|
|
86
90
|
ts-morph resolves back onto `src/`. That remap needs the target package built, which is why
|
|
87
91
|
`pnpm build` precedes both `pnpm test` and `dag:check`.
|
|
88
92
|
|
|
93
|
+
## File roles
|
|
94
|
+
|
|
95
|
+
Every file and module node carries a `role`, one of `ALL_ROLES`: `test`, `fixture`, `story`,
|
|
96
|
+
`lab`, `barrel`, `types`, `config`, `script`, `entry`, `generated` and `source`. The first
|
|
97
|
+
match wins, in this order:
|
|
98
|
+
|
|
99
|
+
1. `generated`: a `.gitattributes linguist-generated` path or a `*.gen.*`/`generated/` path.
|
|
100
|
+
2. A configured glob from `.codewatch/roles.json` (below).
|
|
101
|
+
3. `test`, then `story` (`*.stories.{js,jsx,ts,tsx}` with an optional `c`/`m`, and `*.mdx`),
|
|
102
|
+
so a story under `fixtures/` is still a story.
|
|
103
|
+
4. `fixture`, `script`, `entry` (a `#!` shebang), `barrel`, `types`, `config`, else `source`.
|
|
104
|
+
|
|
105
|
+
`UNIMPORTED_ROLES` lists the roles nothing imports by design (`test`, `fixture`, `story`, `lab`,
|
|
106
|
+
`config`, `script`, `entry`). Dead-module reachability seeds from them plus `barrel`.
|
|
107
|
+
|
|
108
|
+
`lab` has no built-in rule, since a `lab/` directory name is too generic to guess. A repo
|
|
109
|
+
assigns it, or any other role, with `.codewatch/roles.json`, which maps roles to
|
|
110
|
+
`.gitattributes`-style globs matched against file ids:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{ "lab": ["packages/ui/src/lab/**"] }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`loadRoleGlobs(repoRoot)` reads that file (an absent file configures nothing, an unknown role
|
|
117
|
+
throws), and `computeRoleHints` passes the result to `annotateRoles` as `roleGlobs`. `story`,
|
|
118
|
+
`lab` and roles.json arrived in index version 0.21.0.
|
|
119
|
+
|
|
89
120
|
## Identity across renames
|
|
90
121
|
|
|
91
122
|
Added in index version 0.15.0 (TP-187). Git rename detection writes `id_alias` rows mapping a
|
|
@@ -227,7 +258,14 @@ the file is unreadable.
|
|
|
227
258
|
The rules engine turns a snapshot into pass/fail against a `check.json`. Seven rule types:
|
|
228
259
|
`metric-max`, `metric-min`, `metric-product-max`, `metric-outlier`, `forbid-import`,
|
|
229
260
|
`layered-deps`, and `no-internal-only-barrels`. Severity defaults to `error`; only new errors
|
|
230
|
-
fail a check.
|
|
261
|
+
fail a check. `layered-deps` takes `excludeRoles`: an import is dropped when its source or
|
|
262
|
+
destination file has an excluded role. `forbid-import` takes `except`: destination patterns
|
|
263
|
+
that `to` matches but the rule allows, such as one sanctioned entry file.
|
|
264
|
+
|
|
265
|
+
Validation rejects a rule whose `severity` is anything but `error` or `warning`, whose `kind`
|
|
266
|
+
is not a node kind (`package`, `module`, `file`, `symbol`, `external`), or whose `exclude` is
|
|
267
|
+
not an array of strings. Each of those mistakes used to load silently and disable the rule:
|
|
268
|
+
`"Error"` counted as a warning, `"files"` matched no node, and a string `exclude` was dropped.
|
|
231
269
|
|
|
232
270
|
`metric-outlier` takes its threshold from the snapshot instead of the rule:
|
|
233
271
|
`{ "type": "metric-outlier", "id": "long-fn", "metric": "symbol_body_lines", "kind": "symbol", "percentile": 95 }`
|
|
@@ -281,6 +319,12 @@ file is dropped from coupling too. `symbolSetHash({ unitId, symbolIds }, footpri
|
|
|
281
319
|
doc unit's sorted `[symbolId, footprint.hash]` pairs, so it ignores member order and changes when
|
|
282
320
|
a member is added, removed or changed.
|
|
283
321
|
|
|
322
|
+
`diffFootprints(store, { fromSnapshotId, toSnapshotId, ...options })` compares the footprints of
|
|
323
|
+
two snapshots. From-side ids follow the alias chain first, and a symbol under a moved file follows
|
|
324
|
+
its file, so a move reads as `changed` with `reasons: ["renamed"]` alone when its footprint held.
|
|
325
|
+
Each other change is `added`, `removed`, or `changed` with the differing parts (`signature`,
|
|
326
|
+
`consumers`, `coupling`). `files` rolls the changes up to their declaring files, never to consumers.
|
|
327
|
+
|
|
284
328
|
`scripts/dag-check-self.mjs` in the repo root runs this repo's DAG check on this engine
|
|
285
329
|
instead of codewatch's CLI.
|
|
286
330
|
|
|
@@ -390,6 +434,80 @@ snapshotSymbolCoupling(store, snapshotId); // symbol pairs co-imported by 2+ fil
|
|
|
390
434
|
The pure `computePageRank`, `computeRelevance`, `computeSymbolConsumers`, and
|
|
391
435
|
`computeSymbolCoupling` take node and edge arrays instead of a store.
|
|
392
436
|
|
|
437
|
+
### The `./analysis` subpath
|
|
438
|
+
|
|
439
|
+
`@titan-design/code-graph/analysis` re-exports the report, dashboard, and package-architecture
|
|
440
|
+
derivations below without the root's ts-morph, tree-sitter, and SQLite, so a browser bundle
|
|
441
|
+
can import it. `analysis/graph-report-browser-safe.test.ts` keeps its whole import closure
|
|
442
|
+
free of packages and Node builtins, and fails if the barrel re-exports a module it does not
|
|
443
|
+
check. Symbol coupling is not on it: `symbol-coupling.ts` still reaches `node:path`.
|
|
444
|
+
|
|
445
|
+
### Dashboard derivations
|
|
446
|
+
|
|
447
|
+
Ported with TP-918 from codewatch's `graph dashboard`, unchanged apart from import paths. All
|
|
448
|
+
are pure functions over rows the caller has already read, so they run in a browser:
|
|
449
|
+
|
|
450
|
+
- `collectNodeMetrics` folds metric rows into per-node `NodeMetrics`; `collectSymbolUtil`
|
|
451
|
+
pairs symbol nodes with their utilization; `buildNodeMetrics`, `buildCentralFiles`,
|
|
452
|
+
`buildHotExports`, and `buildBlastRadius` shape them for the files a `GraphReportResult`
|
|
453
|
+
references (`referencedNodes`).
|
|
454
|
+
- `buildSymbolCouplingPayload` caps `computeSymbolCoupling` and `computeSymbolConsumers`
|
|
455
|
+
into co-imported pairs and per-file consumer groups.
|
|
456
|
+
- `classifyCoupling` marks a co-changed pair hidden, expected, or unindexed against a
|
|
457
|
+
`SnapshotContext`; build its `linkedPairs` with `pairKey`.
|
|
458
|
+
- `computeHealth` sums four capped penalties into a score out of 100 with its breakdown.
|
|
459
|
+
Each component has a stable `key` and its `cap`. An optional second argument weighs
|
|
460
|
+
them the caller's way; `DEFAULT_HEALTH_WEIGHTS` is the dashboard's.
|
|
461
|
+
|
|
462
|
+
### Context dossier and bundle
|
|
463
|
+
|
|
464
|
+
Ported with TP-1454 from codewatch's `graph context`, unchanged apart from import paths. All
|
|
465
|
+
three are deterministic projections of rows the caller has already read; no LLM is involved.
|
|
466
|
+
|
|
467
|
+
- `buildContextDossier(input)` shapes one file or symbol into a `ContextDossier`: metrics,
|
|
468
|
+
churn, centrality, ownership, consumers split into source and test files, coupling
|
|
469
|
+
partners, and blast radius. A file target lists its symbols, exports first, each with an
|
|
470
|
+
`importance` that splits the file's centrality by utilization share. The record carries
|
|
471
|
+
`schemaVersion` (`CONTEXT_SCHEMA_VERSION`) so a store can invalidate old records.
|
|
472
|
+
- `renderContextMarkdown(dossier)` renders the same facts as markdown.
|
|
473
|
+
- `buildContextBundle(input)` wraps a dossier with the source text of the target's span (read
|
|
474
|
+
from `repoRoot`, so this one touches the filesystem), its `references` and `imports` edges as
|
|
475
|
+
explicit callers, dependencies, and coupling partners, and its `coverage_pct`. Pass
|
|
476
|
+
`relevanceByFile` (from `computeRelevance`) and `targetFileId` to order edges by relevance
|
|
477
|
+
to the target instead of by weight. `renderBundleText(bundle)` concatenates it for an
|
|
478
|
+
embedder or an LLM.
|
|
479
|
+
|
|
480
|
+
### Unused exports and dead modules
|
|
481
|
+
|
|
482
|
+
Ported with TP-1467 from codewatch's `graph report`, unchanged apart from import paths. Both
|
|
483
|
+
are leads, not verdicts, and both drop files that `keepNode` rejects:
|
|
484
|
+
|
|
485
|
+
- `topUnusedExports(symbolNodes, publicApi, ctx, limit)` lists exported symbols whose
|
|
486
|
+
`utilization` is 0 or absent, ranked internal first, then by `symbol_cognitive`
|
|
487
|
+
descending. `publicApiFiles(nodes, edges)` builds `publicApi`: the files a `barrel`-role
|
|
488
|
+
node re-exports one hop away, whose exports may still have npm consumers.
|
|
489
|
+
- `topDeadModules(nodes, edges, ctx, limit)` lists files that a forward walk over `imports`
|
|
490
|
+
and `re-exports` edges never reaches, ranked by `loc`. The walk starts from files with the
|
|
491
|
+
role `entry`, `barrel`, `test`, `script`, `config` or `fixture`, and from any
|
|
492
|
+
`main.{ts,tsx,js,jsx}`.
|
|
493
|
+
|
|
494
|
+
These differ from `pnpm dead:check`, which reads edges rather than `utilization` and follows
|
|
495
|
+
re-exports transitively from package-manifest entries.
|
|
496
|
+
|
|
497
|
+
### Growth and untested risks
|
|
498
|
+
|
|
499
|
+
Ported with TP-1468 from codewatch's `graph report`, unchanged apart from import paths. Both
|
|
500
|
+
take a `ReportContext` and a limit, return `GrowthRiskRow[]` and `UntestedRiskRow[]`, and drop
|
|
501
|
+
files that `keepNode` rejects:
|
|
502
|
+
|
|
503
|
+
- `topGrowthRisks(ctx, limit)` lists files with a structural scaling smell: loop nesting of
|
|
504
|
+
depth 2 or more, `recursive_functions`, or `search_in_loop`. It is a heuristic, not a Big-O
|
|
505
|
+
bound. Ranked by `loop_depth`, then smell count.
|
|
506
|
+
- `topUntestedRisks(ctx, limit)` ranks `hotspot × (1 − coverage_pct / 100)`, where the hotspot
|
|
507
|
+
score is churn × complexity, after Adam Tornhill and CodeScene. Files with no
|
|
508
|
+
`coverage_pct` metric or full coverage are left out, so a repo with no coverage overlay
|
|
509
|
+
gets an empty list.
|
|
510
|
+
|
|
393
511
|
### Partition quality
|
|
394
512
|
|
|
395
513
|
`computePartitionQuality` scores a package partition of the file graph: per-package cohesion,
|
|
@@ -409,6 +527,18 @@ invertBuckets(fileByPackage); // file id -> package id, skipping the "" unassign
|
|
|
409
527
|
re-exports, transitively. It over-attributes: one import of one name through a barrel becomes
|
|
410
528
|
one edge per re-export target, which is why it is off by default.
|
|
411
529
|
|
|
530
|
+
### Package architecture
|
|
531
|
+
|
|
532
|
+
Ported with TP-917 from codewatch's `graph arch`, unchanged apart from import paths.
|
|
533
|
+
`computeArch` aggregates a snapshot's file edges into package-to-package counts over a list
|
|
534
|
+
of `PackageRoot`s the caller supplies (codewatch detects them from `package.json` files; that
|
|
535
|
+
walk stays in codewatch). It drops test and fixture files, honours `exclude`, `excludeRole`,
|
|
536
|
+
`includeExternal` (one `EXTERNAL_BUCKET` node), and `minEdges`, and with `depth: "modules"` or
|
|
537
|
+
`maxPackageSize` drills packages over the threshold (`DEFAULT_MAX_PACKAGE_SIZE`, 30 files)
|
|
538
|
+
into sub-directory nodes. `bucketFilesByPackage` assigns file ids by longest package prefix,
|
|
539
|
+
with unmatched files under `""`. `filteredFileIds`, `aggregateEdges`, `toSortedEdges`, and
|
|
540
|
+
`packagesReferencedByEdges` are the steps it composes.
|
|
541
|
+
|
|
412
542
|
### Pruning snapshots
|
|
413
543
|
|
|
414
544
|
`planPrune` keeps the most recent `keep` snapshots (default 10) plus every snapshot whose ref
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { A as ArchEdge, m as ArchPackage, n as ArchResult, o as ArchSubNode, B as BlastRadiusEntry, p as BucketableViolation, q as BusFactorChange, r as BusFactorRow, s as CentralRow, u as ComputeArchInput, v as ComputeDriftInput, w as CouplingClass, x as CouplingDelta, y as CouplingRow, D as DEFAULT_HEALTH_WEIGHTS, z as DEFAULT_MAX_PACKAGE_SIZE, H as DeadModuleRow, J as EXTERNAL_BUCKET, L as GraphReportResult, M as GrowthRiskRow, O as HealthComponent, P as HealthComponentKey, Q as HealthInput, U as HealthWeights, W as HotExport, X as HotspotDelta, Y as HotspotRow, a4 as NewHotspot, f as NodeMetrics, a8 as PackageRoot, a9 as PackageStats, aa as PairCoupling, ac as PartitionQualityInput, ad as PartitionQualityResult, ae as PenaltyWeight, af as ReportContext, ag as ReportContextInput, ah as ReportDrift, ai as SnapshotContext, g as SymbolConsumers, an as SymbolUtil, ao as TestCoverageRow, T as TestSourceLink, ap as UnchangedViolation, aq as UntestedRiskRow, ar as UnusedExportRow, V as ViolationBuckets, at as aggregateEdges, au as bucketFilesByPackage, av as bucketViolations, aw as buildBlastRadius, ax as buildCentralFiles, ay as buildHotExports, az as buildNodeMetrics, aA as buildReportContext, aC as busFactorOf, aD as classifyCoupling, aE as collectNodeMetrics, aF as collectSymbolUtil, aG as computeArch, aH as computeHealth, aI as computePartitionQuality, aJ as computeReportDrift, aK as computeSymbolConsumers, aM as filteredFileIds, b7 as hotspotComplexityOf, aO as hotspotScoreOf, aQ as keepNode, aR as linkTestsToSources, aS as lookupMetric, aT as packagesReferencedByEdges, aU as pairKey, aV as publicApiFiles, aX as referencedNodes, aZ as toSortedEdges, a_ as topBusFactorRisks, a$ as topCentralFiles, b0 as topDeadModules, b1 as topGrowthRisks, b2 as topHotspots, b3 as topTestCoverageRisks, b4 as topUntestedRisks, b5 as topUnusedExports } from '../browser-BBzShneN.js';
|
|
2
|
+
import '../change-coupling-CT3bcZMD.js';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_HEALTH_WEIGHTS,
|
|
3
|
+
DEFAULT_MAX_PACKAGE_SIZE,
|
|
4
|
+
EXTERNAL_BUCKET,
|
|
5
|
+
aggregateEdges,
|
|
6
|
+
bucketFilesByPackage,
|
|
7
|
+
bucketViolations,
|
|
8
|
+
buildBlastRadius,
|
|
9
|
+
buildCentralFiles,
|
|
10
|
+
buildHotExports,
|
|
11
|
+
buildNodeMetrics,
|
|
12
|
+
buildReportContext,
|
|
13
|
+
busFactorOf,
|
|
14
|
+
classifyCoupling,
|
|
15
|
+
collectNodeMetrics,
|
|
16
|
+
collectSymbolUtil,
|
|
17
|
+
computeArch,
|
|
18
|
+
computeHealth,
|
|
19
|
+
computePartitionQuality,
|
|
20
|
+
computeReportDrift,
|
|
21
|
+
computeSymbolConsumers,
|
|
22
|
+
filteredFileIds,
|
|
23
|
+
hotspotComplexityOf,
|
|
24
|
+
hotspotScoreOf,
|
|
25
|
+
keepNode,
|
|
26
|
+
linkTestsToSources,
|
|
27
|
+
lookupMetric,
|
|
28
|
+
packagesReferencedByEdges,
|
|
29
|
+
pairKey,
|
|
30
|
+
publicApiFiles,
|
|
31
|
+
referencedNodes,
|
|
32
|
+
toSortedEdges,
|
|
33
|
+
topBusFactorRisks,
|
|
34
|
+
topCentralFiles,
|
|
35
|
+
topDeadModules,
|
|
36
|
+
topGrowthRisks,
|
|
37
|
+
topHotspots,
|
|
38
|
+
topTestCoverageRisks,
|
|
39
|
+
topUntestedRisks,
|
|
40
|
+
topUnusedExports
|
|
41
|
+
} from "../chunk-L5KU543A.js";
|
|
42
|
+
import "../chunk-HIVVDGWE.js";
|
|
43
|
+
export {
|
|
44
|
+
DEFAULT_HEALTH_WEIGHTS,
|
|
45
|
+
DEFAULT_MAX_PACKAGE_SIZE,
|
|
46
|
+
EXTERNAL_BUCKET,
|
|
47
|
+
aggregateEdges,
|
|
48
|
+
bucketFilesByPackage,
|
|
49
|
+
bucketViolations,
|
|
50
|
+
buildBlastRadius,
|
|
51
|
+
buildCentralFiles,
|
|
52
|
+
buildHotExports,
|
|
53
|
+
buildNodeMetrics,
|
|
54
|
+
buildReportContext,
|
|
55
|
+
busFactorOf,
|
|
56
|
+
classifyCoupling,
|
|
57
|
+
collectNodeMetrics,
|
|
58
|
+
collectSymbolUtil,
|
|
59
|
+
computeArch,
|
|
60
|
+
computeHealth,
|
|
61
|
+
computePartitionQuality,
|
|
62
|
+
computeReportDrift,
|
|
63
|
+
computeSymbolConsumers,
|
|
64
|
+
filteredFileIds,
|
|
65
|
+
hotspotComplexityOf,
|
|
66
|
+
hotspotScoreOf,
|
|
67
|
+
keepNode,
|
|
68
|
+
linkTestsToSources,
|
|
69
|
+
lookupMetric,
|
|
70
|
+
packagesReferencedByEdges,
|
|
71
|
+
pairKey,
|
|
72
|
+
publicApiFiles,
|
|
73
|
+
referencedNodes,
|
|
74
|
+
toSortedEdges,
|
|
75
|
+
topBusFactorRisks,
|
|
76
|
+
topCentralFiles,
|
|
77
|
+
topDeadModules,
|
|
78
|
+
topGrowthRisks,
|
|
79
|
+
topHotspots,
|
|
80
|
+
topTestCoverageRisks,
|
|
81
|
+
topUntestedRisks,
|
|
82
|
+
topUnusedExports
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|