@titan-design/code-read 0.1.8 → 0.2.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 CHANGED
@@ -16,22 +16,28 @@ Tier 2 of the titan-platform DAG (TP-184). Depends on `code-graph`, `registry`,
16
16
  | `@titan-design/code-read/query` | browser or Node | the contract (`CONTRACT`, `CODE_READ_API_VERSION`, zod schemas), `ReadModel` and `buildReadModel`, the `ReadSource` seam, `QUERIES`, `createQueryResolver` |
17
17
  | `@titan-design/code-read` | Node | everything in `./query`, plus `loadReadModel`, `createLiveSource` (SQLite plus an LRU of models), and `registerCodeReadCommands` |
18
18
 
19
- `./query` imports only its own files, `zod`, and `rpc-protocol`. Three rules in
19
+ `./query` imports only its own files, `zod`, `rpc-protocol`, and code-graph's browser-safe
20
+ `./analysis` subpath, never code-graph's root. Three rules in
20
21
  `.codewatch/check.json` (`code-read-query-*`) and `src/browser-safe.test.ts` enforce that.
21
22
  The test bundles the subpath with esbuild for `platform: "browser"` and expects no warnings.
22
23
 
23
- ## Commands (contract 0.1.2)
24
+ ## Commands (contract 0.1.8)
24
25
 
25
26
  | Command | Args | Result |
26
27
  | --- | --- | --- |
27
28
  | `api.describe` | none | `api`, `dataset`, `commands`, `newest`, `indexVersions`, `capabilities`, `metrics` (catalogue descriptors with provenance), `rules` |
28
29
  | `snapshot.list` | `ref?`, `limit` (1 to 500, default 50) | `snapshots`, newest first |
29
30
  | `hierarchy.get` | `snapshot?`, `root?`, `depth` (1 to 8, default 2), `metrics` (default `["loc"]`), `baseline?`, `include_symbols`, `exclude_roles` | `snapshotId`, `baselineSnapshotId?`, `comparable?`, `nodes` (flat, shallowest first, with `parentId`, `depth`, `childCount`, `values`, `missing?`, `deltas?`), `truncated` |
30
- | `node.get` | `snapshot?`, `id`, `baseline?`, `metrics` (default: every one that applies) | `node`, `ancestors` (repo first), `childCounts`, `metrics` (value, `direction`, `rollup`, `percentile`, `siblingMedian`, `siblingRank`, `siblingCount`, `baseline?`, `delta?`, `missing?`) |
31
+ | `node.get` | `snapshot?`, `id`, `baseline?`, `metrics` (default: every one that applies), `lenses` (any of `exports`, `score`, `centrality`, `coupling`, `tests`; default none), `window` (`30d` default, for `score`) | `node`, `ancestors` (repo first), `childCounts`, `metrics` (value, `direction`, `rollup`, `percentile`, `siblingMedian`, `siblingRank`, `siblingCount`, `baseline?`, `delta?`, `missing?`), `lenses?` (one key per lens asked for) |
31
32
  | `node.resolve` | exactly one of `query` or `path`, plus `line?` with `path`, `limit` (1 to 50, default 10) | `candidates`: `node`, `score`, `match` |
32
33
  | `findings.list` | `snapshot?`, `baseline?`, `scope?`, filters `rule`, `severity`, `tool`, `provenance`, `kind`, `status` (arrays, empty means all), `sort` (`severity` default, `excess`, `value`, `path`, `rule`), `order` (`desc` default), `offset`, `limit` (0 to 500, default 20), `facets` | `snapshotId`, `baselineSnapshotId?`, `comparable?`, `rows` (`Finding`), `total`, `facets?` |
33
34
  | `finding.get` | `snapshot?`, `id`, `baseline?`, `context_lines` (0 to 20, default 5) | `finding`, `rule` (with `text`), `measured`, `why`, `excerpt` (or null with `excerptMissing`), `related` (at most 10) |
34
35
  | `node.neighbors` | `snapshot?`, `id` (a stored node), `direction` (`both` default), `edge_kinds`, `metrics` (default `loc`, `utilization`), `offset`, `limit` (1 to 100, default 20) | `snapshotId`, `node`, `inbound`, `outbound` (each `node`, `kind`, `weight`, `specifier?`, `values`), `total` per side |
36
+ | `hotspots.list` | `snapshot?`, `baseline?`, `grain` (`file` default, `symbol`), `window` (`30d` default, any `<n>d`, or `lifetime`), `cutoff?`, `offset`, `limit` (0 to 500, default 20) | `snapshotId`, `baselineSnapshotId?`, `comparable?`, `rows` (`node`, `churn`, `complexity`, `recency`, `score`, `utilization?`, `baselineScore?`, `mark?`), `total` |
37
+ | `overview.get` | `snapshot?`, `baseline?`, `window` (`30d` default), `cutoff` (default 3000), `weights` (per signal, defaults from code-graph's `DEFAULT_HEALTH_WEIGHTS`), `exclude_rules`, `combined` (default false), `reading_limit` (default 6), `look_limit` (default 8; both 0 to 50) | `snapshotId`, `baselineSnapshotId?`, `comparable?`, `kpis`, `signals` (`key`, `label`, `penalty`, `cap`, `measured`, `detail`), `combined?`, `readingOrder` (`node`, `centrality`), `lookFirst` (`node`, `score`, `churn`, `complexity`, `recency`, `reasons`) |
38
+ | `changes.get` | `baseline` (required), `snapshot?`, `window` (`30d` default), `cutoff` (default 3000), `limit` (rows per list, 0 to 500, default 20) | `snapshotId`, `baselineSnapshotId`, `comparable`, `files` (`crossedCutoff`, `added`), `findings` (`new`, `worsened`, `improved`, `resolved`), `coupling` (`measured`, `added`), `regressions` (`node`, `before`, `after`, `delta`, `findings`), `counts` |
39
+ | `paths.impact` | `paths` (up to 500), `snapshot?`, `baseline?`, `root?` (absolute checkout directory), `window` (`30d` default) | `snapshotId`, `baselineSnapshotId?`, `comparable?`, `rows` (by `status`: `indexed` with `node`, `complexity`, `hotspot` (`score`, `rank`), `findings`, `delta?`; `not-indexed` with `path`; `outside-repo`), `ranked`, `rollup` |
40
+ | `packages.stats` | `snapshot?`, `packages?` (package roots, up to 500; default every package the tier config declares) | `snapshotId`, `packagesFrom` (`args`, `tiers`, `none`), `modularity`, `totalEdges`, `unassignedFiles`, `packages` (`id`, `name`, `fileCount`, `internalEdges`, `outgoingEdges`, `incomingEdges`, `cohesion`, `instability`, `abstractness`, `band`, `flags`, `layer`), `crossEdges` (`from`, `to`, `edges`, `intensity`, `flag`) |
35
41
 
36
42
  Arguments are snake_case and results are camelCase. `snapshot` and `baseline` take an id, a
37
43
  digit string, or a ref name (that ref's newest snapshot). The rest of the design's 14
@@ -132,6 +138,108 @@ back. A finding's `status` also depends on the baseline you pass: the same row i
132
138
  `carryover` against one snapshot and `new` against another. Do not persist a derived id
133
139
  as if it were durable until the findings store lands.
134
140
 
141
+ ## Hotspots
142
+
143
+ `hotspots.list` computes nothing of its own. It runs code-graph's report derivations from
144
+ the browser-safe `@titan-design/code-graph/analysis` subpath, so the daemon and a static
145
+ dataset rank alike.
146
+
147
+ - **File grain** is `topHotspots`: `round(churn_<window> × complexity × recency_<window>)`,
148
+ where complexity is `cognitive_max`, else `cyclomatic_max`. A file with no churn or no
149
+ complexity has no row, and generated files are left out.
150
+ - **Symbol grain** is `buildBlastRadius`: `utilization × complexity × file churn`, with the
151
+ symbol's own `symbol_cognitive`, else its file's. It applies no recency, so `recency` is 1.
152
+ Symbols of generated files are left out.
153
+ - **Marks** come from `computeReportDrift` over every row: `new` when the node had no row at
154
+ the baseline, `worsened` when its score rose. `baselineScore` is null for a new row.
155
+ - **Cutoff** is the caller's policy, such as the dashboard's 3000. It filters before paging,
156
+ and `total` counts the rows it keeps. Marks do not depend on it.
157
+
158
+ ## Overview
159
+
160
+ `overview.get` answers "where do I look first" with named attention signals, not a risk or
161
+ defect score. It also reuses code-graph's derivations from `./analysis`:
162
+
163
+ - **Signals** are `computeHealth`'s components, weighted and capped by the caller's
164
+ `weights`: files over `cutoff`, open findings (new and carried over weigh apart), max
165
+ complexity over budget, and hidden coupling, which is unmeasured until co-change pairs
166
+ are stored. `combined` (100 minus the penalties) is optional and secondary.
167
+ - **Reading order** is `topCentralFiles`: PageRank over files and structural edges.
168
+ - **Look first** is the file-grain hotspots, highest first. Each row names its reasons:
169
+ `over-cutoff`, `findings`, or else `churn-complexity`.
170
+
171
+ ## Node lenses
172
+
173
+ `node.get` takes optional `lenses`. With none, the result has no `lenses` field and is the
174
+ same as before. Each lens reuses a code-graph derivation from `./analysis`, and answers
175
+ `null` on a node kind it does not describe: `score` describes files and symbols, the rest
176
+ describe files only.
177
+
178
+ - **exports** is `buildHotExports`: the file's top 8 exported and top 8 internal symbols,
179
+ each with utilization, its own cognitive complexity, and how many files reference it
180
+ (`computeSymbolConsumers`).
181
+ - **score** is the node's row in `hotspots.list` at its grain for `window`: the score, its
182
+ factors, and its rank among non-zero scores. A node scoring 0 has no factors and no rank.
183
+ - **centrality** is `topCentralFiles` with no limit, the ranking behind `overview.get`'s
184
+ reading order, so every file in it has a rank. Generated code is left out and gets null.
185
+ - **coupling** is `measured: false` until co-change pairs are stored.
186
+ - **tests** lists tests linked by path convention (`linkTestsToSources` with no co-edit
187
+ pairs) beside the index's `linked_test_count`, which also counts co-edit links.
188
+
189
+ ## Changes
190
+
191
+ `changes.get` answers "what moved since the baseline". It recomputes nothing code-graph
192
+ already derives:
193
+
194
+ - **Files** come from `computeReportDrift` over every file-grain hotspot. `crossedCutoff`
195
+ holds files below `cutoff` at the baseline and at or above it now; `added` holds files the
196
+ baseline does not hold, generated files left out.
197
+ - **Findings** are bucketed by `bucketViolations`, the store-free core of `diffCheckResults`:
198
+ new, worsened or improved by value, and resolved. Ids match as they are; following
199
+ renames is TP-187.
200
+ - **Coupling** is unmeasured (`measured: false`) until co-change pairs are stored.
201
+ - **Regressions** are files whose score rose that carry an open finding now.
202
+ - Across index versions `comparable` is false and every list is empty.
203
+
204
+ ## Path impact
205
+
206
+ `paths.impact` answers "what do these files weigh", for a set such as the files a change
207
+ touches. It ranks nothing a second way:
208
+
209
+ - **Complexity** is the factor the file hotspot score multiplies (`hotspotComplexityOf`:
210
+ max cognitive, else max cyclomatic), read even for a file with no churn.
211
+ - **Hotspot** is the file's row in `hotspots.list` at `window`: `rank` is its row number
212
+ there and `ranked` the list's total. A file scoring 0 has rank null.
213
+ - **Findings** are the open findings on the file or a symbol in it, worst first.
214
+ - **Delta**, only with `baseline`: score, complexity, and findings counts against the
215
+ baseline, findings bucketed by `bucketViolations` as `changes.get` buckets them, and each
216
+ open finding gets a `status`. Without a baseline `delta` is absent; across index versions
217
+ it is null.
218
+ - **Paths** are repo-relative. A leading `./`, `.` and `..` segments, and doubled slashes are
219
+ normalized; an absolute path counts only under `root`. A path the snapshot holds no file
220
+ for is a `not-indexed` row and one outside the repo an `outside-repo` row, never an error.
221
+ - The **rollup** counts each status, sums scores and open findings, and takes the top
222
+ complexity and best rank; with a comparable baseline it sums the deltas, a new file adding
223
+ its whole score.
224
+
225
+ ## Package stats
226
+
227
+ `packages.stats` is code-graph's `computePartitionQuality` over a set of package roots, the
228
+ numbers codewatch's `graph arch --health` prints:
229
+
230
+ - **Roots** are repo-relative path prefixes. Given as `packages`, those; otherwise every root a
231
+ `layered-deps` rule names, the repo's tier config. `packagesFrom` says which, and is `none`
232
+ when neither names one. A file sits in the longest root it falls under; the rest count as
233
+ `unassignedFiles`.
234
+ - **Edges** are the structural layer the store reads by default, so `references` and `calls`
235
+ edges are left out, as are test and fixture files, as `computeArch` leaves them out.
236
+ - **Per package**: file count, internal, outgoing, and incoming edges, `cohesion`,
237
+ `instability`, `abstractness` (the share of `types` files), the measured `band`, and flags.
238
+ - **Layer** is declared, not measured: the root's tier in the first `layered-deps` rule that
239
+ names it, 0 the lowest. A root no rule names has `layer: { status: "undeclared" }`.
240
+ - **Cross edges** are the package-to-package counts with their intensity and flag, and
241
+ `modularity` is the partition's Newman-Girvan Q.
242
+
135
243
  ## Serving the commands
136
244
 
137
245
  ```ts