diffninja 0.1.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.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +259 -0
  3. package/dist/calltree.d.ts +47 -0
  4. package/dist/calltree.js +296 -0
  5. package/dist/cli.d.ts +57 -0
  6. package/dist/cli.js +340 -0
  7. package/dist/diff.d.ts +7 -0
  8. package/dist/diff.js +114 -0
  9. package/dist/extract.d.ts +26 -0
  10. package/dist/extract.js +152 -0
  11. package/dist/git.d.ts +40 -0
  12. package/dist/git.js +288 -0
  13. package/dist/index.d.ts +9 -0
  14. package/dist/index.js +8 -0
  15. package/dist/infer.d.ts +21 -0
  16. package/dist/infer.js +189 -0
  17. package/dist/languages/bash.d.ts +2 -0
  18. package/dist/languages/bash.js +208 -0
  19. package/dist/languages/c.d.ts +2 -0
  20. package/dist/languages/c.js +218 -0
  21. package/dist/languages/call-syntax.d.ts +125 -0
  22. package/dist/languages/call-syntax.js +997 -0
  23. package/dist/languages/cpp.d.ts +2 -0
  24. package/dist/languages/cpp.js +321 -0
  25. package/dist/languages/csharp.d.ts +2 -0
  26. package/dist/languages/csharp.js +324 -0
  27. package/dist/languages/elixir.d.ts +2 -0
  28. package/dist/languages/elixir.js +331 -0
  29. package/dist/languages/go.d.ts +2 -0
  30. package/dist/languages/go.js +299 -0
  31. package/dist/languages/grammars.d.ts +50 -0
  32. package/dist/languages/grammars.js +351 -0
  33. package/dist/languages/haskell.d.ts +2 -0
  34. package/dist/languages/haskell.js +250 -0
  35. package/dist/languages/java.d.ts +2 -0
  36. package/dist/languages/java.js +351 -0
  37. package/dist/languages/javascript.d.ts +4 -0
  38. package/dist/languages/javascript.js +648 -0
  39. package/dist/languages/kotlin.d.ts +2 -0
  40. package/dist/languages/kotlin.js +368 -0
  41. package/dist/languages/lua.d.ts +2 -0
  42. package/dist/languages/lua.js +212 -0
  43. package/dist/languages/ocaml.d.ts +2 -0
  44. package/dist/languages/ocaml.js +291 -0
  45. package/dist/languages/perl.d.ts +2 -0
  46. package/dist/languages/perl.js +418 -0
  47. package/dist/languages/php.d.ts +2 -0
  48. package/dist/languages/php.js +397 -0
  49. package/dist/languages/python.d.ts +2 -0
  50. package/dist/languages/python.js +376 -0
  51. package/dist/languages/registry.d.ts +7 -0
  52. package/dist/languages/registry.js +69 -0
  53. package/dist/languages/ruby.d.ts +2 -0
  54. package/dist/languages/ruby.js +391 -0
  55. package/dist/languages/rust.d.ts +2 -0
  56. package/dist/languages/rust.js +261 -0
  57. package/dist/languages/scala.d.ts +2 -0
  58. package/dist/languages/scala.js +307 -0
  59. package/dist/languages/solidity.d.ts +2 -0
  60. package/dist/languages/solidity.js +240 -0
  61. package/dist/languages/swift.d.ts +2 -0
  62. package/dist/languages/swift.js +268 -0
  63. package/dist/languages/types.d.ts +36 -0
  64. package/dist/languages/types.js +74 -0
  65. package/dist/languages/typescript-contracts.d.ts +57 -0
  66. package/dist/languages/typescript-contracts.js +528 -0
  67. package/dist/languages/typescript-dispatch.d.ts +68 -0
  68. package/dist/languages/typescript-dispatch.js +710 -0
  69. package/dist/languages/typescript.d.ts +4 -0
  70. package/dist/languages/typescript.js +722 -0
  71. package/dist/languages/zig.d.ts +2 -0
  72. package/dist/languages/zig.js +243 -0
  73. package/dist/loc.d.ts +17 -0
  74. package/dist/loc.js +34 -0
  75. package/dist/reach.d.ts +17 -0
  76. package/dist/reach.js +65 -0
  77. package/dist/render.d.ts +18 -0
  78. package/dist/render.js +83 -0
  79. package/dist/review/brand.d.ts +8 -0
  80. package/dist/review/brand.js +25 -0
  81. package/dist/review/call-context.d.ts +27 -0
  82. package/dist/review/call-context.js +446 -0
  83. package/dist/review/call-flow-html.d.ts +32 -0
  84. package/dist/review/call-flow-html.js +1870 -0
  85. package/dist/review/call-flow-nav.d.ts +151 -0
  86. package/dist/review/call-flow-nav.js +317 -0
  87. package/dist/review/call-flow.d.ts +47 -0
  88. package/dist/review/call-flow.js +229 -0
  89. package/dist/review/change-facts.d.ts +69 -0
  90. package/dist/review/change-facts.js +729 -0
  91. package/dist/review/cli.d.ts +2 -0
  92. package/dist/review/cli.js +50 -0
  93. package/dist/review/connected-analysis.d.ts +100 -0
  94. package/dist/review/connected-analysis.js +163 -0
  95. package/dist/review/connected-html.d.ts +17 -0
  96. package/dist/review/connected-html.js +2853 -0
  97. package/dist/review/connected.d.ts +23 -0
  98. package/dist/review/connected.js +141 -0
  99. package/dist/review/escape-html.d.ts +2 -0
  100. package/dist/review/escape-html.js +9 -0
  101. package/dist/review/evidence-html.d.ts +21 -0
  102. package/dist/review/evidence-html.js +521 -0
  103. package/dist/review/evidence-syntax.d.ts +132 -0
  104. package/dist/review/evidence-syntax.js +478 -0
  105. package/dist/review/evidence-types.d.ts +62 -0
  106. package/dist/review/evidence-types.js +1 -0
  107. package/dist/review/evidence.d.ts +31 -0
  108. package/dist/review/evidence.js +1603 -0
  109. package/dist/review/file-role.d.ts +9 -0
  110. package/dist/review/file-role.js +29 -0
  111. package/dist/review/github.d.ts +204 -0
  112. package/dist/review/github.js +1245 -0
  113. package/dist/review/history.d.ts +101 -0
  114. package/dist/review/history.js +412 -0
  115. package/dist/review/html.d.ts +34 -0
  116. package/dist/review/html.js +1104 -0
  117. package/dist/review/input.d.ts +10 -0
  118. package/dist/review/input.js +113 -0
  119. package/dist/review/intent.d.ts +4 -0
  120. package/dist/review/intent.js +75 -0
  121. package/dist/review/mcp-cli.d.ts +2 -0
  122. package/dist/review/mcp-cli.js +25 -0
  123. package/dist/review/mcp.d.ts +12 -0
  124. package/dist/review/mcp.js +414 -0
  125. package/dist/review/module-resolution.d.ts +2 -0
  126. package/dist/review/module-resolution.js +86 -0
  127. package/dist/review/palette.d.ts +7 -0
  128. package/dist/review/palette.js +104 -0
  129. package/dist/review/pipeline.d.ts +77 -0
  130. package/dist/review/pipeline.js +227 -0
  131. package/dist/review/pr-input.d.ts +19 -0
  132. package/dist/review/pr-input.js +130 -0
  133. package/dist/review/questions.d.ts +201 -0
  134. package/dist/review/questions.js +174 -0
  135. package/dist/review/reference-check.d.ts +7 -0
  136. package/dist/review/reference-check.js +733 -0
  137. package/dist/review/report-pages.d.ts +109 -0
  138. package/dist/review/report-pages.js +328 -0
  139. package/dist/review/service.d.ts +23 -0
  140. package/dist/review/service.js +198 -0
  141. package/dist/review/setup.d.ts +112 -0
  142. package/dist/review/setup.js +549 -0
  143. package/dist/review/source.d.ts +26 -0
  144. package/dist/review/source.js +276 -0
  145. package/dist/review/toml.d.ts +38 -0
  146. package/dist/review/toml.js +565 -0
  147. package/dist/review/types.d.ts +179 -0
  148. package/dist/review/types.js +1 -0
  149. package/dist/run.d.ts +49 -0
  150. package/dist/run.js +311 -0
  151. package/dist/types.d.ts +366 -0
  152. package/dist/types.js +83 -0
  153. package/package.json +88 -0
  154. package/scripts/ensure-native-grammar.mjs +188 -0
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Navigation state for the Tree, Graph and Sequence call-flow views.
3
+ *
4
+ * This module is the only implementation of focus, branch, visited-trail, depth
5
+ * and graph-camera behaviour. The server renderer prints the mode and depth
6
+ * controls from `CALL_FLOW_MODES` / `CALL_FLOW_DEPTHS`, the page script runs this
7
+ * module's own source text (see `CALL_FLOW_NAV_SOURCE`, built from
8
+ * `callFlowsNav.toString()`) and the unit tests call the module directly, so the
9
+ * tested functions are the ones the browser executes.
10
+ *
11
+ * A visit is identified by the file index plus the occurrence path inside that
12
+ * file (`0-2-1`) — never by a label or a key — so two calls that share a name,
13
+ * or two identical paths in different files, stay distinct trail entries.
14
+ *
15
+ * Two calls are selected at once: the *selection* (`current`, the tail of the
16
+ * trail, the call the source panel and the views highlight) and the *branch* the
17
+ * graph is framed on. Reading source moves the selection only; `+`, an edge
18
+ * number, a tree label or a sequence chip moves the branch as well. The camera
19
+ * cache is keyed by the caller per graph frame, so panning one graph, switching
20
+ * modes and coming back never re-frames another graph.
21
+ *
22
+ * TypeScript erases the annotations before this self-contained function is
23
+ * embedded. Its body must not reference module-scope runtime values.
24
+ */
25
+ export declare const CALL_FLOW_MODES: readonly ["tree", "graph", "sequence"];
26
+ /** Graph depth is counted in call levels below the current focus. */
27
+ export declare const CALL_FLOW_DEPTHS: readonly [1, 2, 3, "all"];
28
+ /** Depth the control starts at and returns to whenever the focus changes. */
29
+ export declare const CALL_FLOW_DEFAULT_DEPTH: "all";
30
+ export type FlowMode = (typeof CALL_FLOW_MODES)[number];
31
+ export type FlowDepth = (typeof CALL_FLOW_DEPTHS)[number];
32
+ /** One focused call: the file section it lives in and its occurrence path. */
33
+ export interface CallFlowVisit {
34
+ readonly file: number;
35
+ readonly path: string;
36
+ }
37
+ /** World-space rectangle of a drawn graph, in SVG user units. */
38
+ export interface FlowRect {
39
+ readonly x: number;
40
+ readonly y: number;
41
+ readonly width: number;
42
+ readonly height: number;
43
+ }
44
+ /** Pixel size of the canvas a camera is drawn into. */
45
+ export interface FlowSize {
46
+ readonly width: number;
47
+ readonly height: number;
48
+ }
49
+ /**
50
+ * A graph camera: `x`/`y` is the world point drawn at the viewport's top-left
51
+ * corner, `scale` the zoom, so the SVG viewBox is
52
+ * `x y viewport.width / scale viewport.height / scale`.
53
+ */
54
+ export interface FlowCamera {
55
+ readonly x: number;
56
+ readonly y: number;
57
+ readonly scale: number;
58
+ }
59
+ /** Focus, branch, visited trail, mode, depth and cameras shared by every view. */
60
+ export interface CallFlowNavState {
61
+ readonly trail: ReadonlyArray<CallFlowVisit>;
62
+ readonly mode: FlowMode;
63
+ readonly depth: FlowDepth;
64
+ /**
65
+ * The branch the graph is framed on: the last explicit jump (`+`, edge number,
66
+ * tree label, sequence chip). `null` frames the whole graph from its roots.
67
+ */
68
+ readonly branch: CallFlowVisit | null;
69
+ /** Last camera seen per graph frame key; survives mode switches and `clear`. */
70
+ readonly cameras: Readonly<Record<string, FlowCamera>>;
71
+ }
72
+ export interface CallFlowNav {
73
+ /** Occurrence path `0-2-1` as numbers; `""` (no focus) parses to `[]`. */
74
+ parsePath(path: string): number[];
75
+ /** `path` is `at` or below it. An empty `at` means "no restriction". */
76
+ under(path: string, at: string): boolean;
77
+ /** `path` is `at` or one of its ancestors. An empty `at` means "no restriction". */
78
+ onChain(path: string, at: string): boolean;
79
+ /** Call levels between `at` (or the roots, when empty) and `path`. */
80
+ edgesBelow(path: string, at: string): number;
81
+ /** Should this node be drawn for focus `at` at this depth? */
82
+ visibleNode(path: string, at: string, depth: FlowDepth): boolean;
83
+ /** Should an incoming call edge into this node be drawn? */
84
+ visibleCall(path: string, at: string, depth: FlowDepth): boolean;
85
+ /** Distinct identity of a visit, for tests and for trail bookkeeping. */
86
+ key(file: number, path: string): string;
87
+ createState(): CallFlowNavState;
88
+ /** The selected visit, or null when the whole file list is shown. */
89
+ current(state: CallFlowNavState): CallFlowVisit | null;
90
+ /**
91
+ * Focus a call on an explicit jump, appending it to the trail when it is not
92
+ * already the selection and framing the graph on it. Re-visiting the branch
93
+ * that is also the selection is a no-op; the depth starts over whenever the
94
+ * branch changes, because it is read relative to the branch just opened.
95
+ */
96
+ visit(state: CallFlowNavState, file: number, path: string): CallFlowNavState;
97
+ /**
98
+ * Select a call for inspection — the source panel, not the graph frame. The
99
+ * branch and the depth stay as they are, unless the inspected call is outside
100
+ * the branch (another file, another subtree, an ancestor), which drops the
101
+ * branch and starts the depth over. Inspecting the current selection is a
102
+ * no-op.
103
+ */
104
+ inspect(state: CallFlowNavState, file: number, path: string): CallFlowNavState;
105
+ /**
106
+ * Go back to trail entry `index`: it becomes the branch, the later visits are
107
+ * dropped and the depth starts over. Works on earlier entries only.
108
+ */
109
+ truncate(state: CallFlowNavState, index: number): CallFlowNavState;
110
+ /** Drop trail and branch and start the depth over; cameras stay cached. */
111
+ clear(state: CallFlowNavState): CallFlowNavState;
112
+ /**
113
+ * Switch view. Selection, branch, trail, depth and cameras are preserved, so
114
+ * following one call through Tree, Graph and Sequence never loses the frame.
115
+ * Entering the graph with a bounded depth that would hide the selection
116
+ * widens the depth instead of drawing the graph without its selected call.
117
+ */
118
+ setMode(state: CallFlowNavState, mode: string): CallFlowNavState;
119
+ setDepth(state: CallFlowNavState, depth: string | number): CallFlowNavState;
120
+ /**
121
+ * Readable (scale 1) framing of `target`, or — with `overview` — the whole of
122
+ * `bounds` scaled down to fit. Both obey the same bounds as pan and zoom.
123
+ */
124
+ frame(bounds: FlowRect, viewport: FlowSize, target: FlowRect, overview?: boolean): FlowCamera;
125
+ /**
126
+ * Clamp a camera to `bounds`: content larger than the viewport stops at the
127
+ * graph edges, content smaller is centred. `scale` is only repaired, never
128
+ * raised to 1, so an overview below 1 survives.
129
+ */
130
+ constrain(camera: FlowCamera, bounds: FlowRect, viewport: FlowSize): FlowCamera;
131
+ /** Drag the canvas by screen pixels: the world follows the pointer, clamped. */
132
+ pan(camera: FlowCamera, dx: number, dy: number, bounds: FlowRect, viewport: FlowSize): FlowCamera;
133
+ /**
134
+ * Zoom by `factor` about a screen-space `anchor`, keeping the world point under
135
+ * that pixel, clamped to the readable range [1, 2.5] and to `bounds`.
136
+ */
137
+ zoom(camera: FlowCamera, factor: number, anchor: {
138
+ x: number;
139
+ y: number;
140
+ }, bounds: FlowRect, viewport: FlowSize): FlowCamera;
141
+ /** Remember one graph frame's camera. Unchanged or invalid writes are no-ops. */
142
+ setCamera(state: CallFlowNavState, key: string, camera: FlowCamera): CallFlowNavState;
143
+ readonly modes: ReadonlyArray<FlowMode>;
144
+ readonly depths: ReadonlyArray<FlowDepth>;
145
+ }
146
+ export declare function callFlowsNav(): CallFlowNav;
147
+ /**
148
+ * The navigation module as the page script receives it. Built from the function
149
+ * source, so the browser and the unit tests cannot drift apart.
150
+ */
151
+ export declare const CALL_FLOW_NAV_SOURCE: string;
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Navigation state for the Tree, Graph and Sequence call-flow views.
3
+ *
4
+ * This module is the only implementation of focus, branch, visited-trail, depth
5
+ * and graph-camera behaviour. The server renderer prints the mode and depth
6
+ * controls from `CALL_FLOW_MODES` / `CALL_FLOW_DEPTHS`, the page script runs this
7
+ * module's own source text (see `CALL_FLOW_NAV_SOURCE`, built from
8
+ * `callFlowsNav.toString()`) and the unit tests call the module directly, so the
9
+ * tested functions are the ones the browser executes.
10
+ *
11
+ * A visit is identified by the file index plus the occurrence path inside that
12
+ * file (`0-2-1`) — never by a label or a key — so two calls that share a name,
13
+ * or two identical paths in different files, stay distinct trail entries.
14
+ *
15
+ * Two calls are selected at once: the *selection* (`current`, the tail of the
16
+ * trail, the call the source panel and the views highlight) and the *branch* the
17
+ * graph is framed on. Reading source moves the selection only; `+`, an edge
18
+ * number, a tree label or a sequence chip moves the branch as well. The camera
19
+ * cache is keyed by the caller per graph frame, so panning one graph, switching
20
+ * modes and coming back never re-frames another graph.
21
+ *
22
+ * TypeScript erases the annotations before this self-contained function is
23
+ * embedded. Its body must not reference module-scope runtime values.
24
+ */
25
+ export const CALL_FLOW_MODES = ["tree", "graph", "sequence"];
26
+ /** Graph depth is counted in call levels below the current focus. */
27
+ export const CALL_FLOW_DEPTHS = [1, 2, 3, "all"];
28
+ /** Depth the control starts at and returns to whenever the focus changes. */
29
+ export const CALL_FLOW_DEFAULT_DEPTH = "all";
30
+ export function callFlowsNav() {
31
+ var MODES = ['tree', 'graph', 'sequence'];
32
+ var DEPTHS = [1, 2, 3, 'all'];
33
+ var DEFAULT_DEPTH = 'all';
34
+ /** Overview may shrink below this; `zoom` never does. */
35
+ var READABLE_SCALE = 1;
36
+ var MAX_SCALE = 2.5;
37
+ /** World units left above a framed box, so its label is not flush with the edge. */
38
+ var FRAME_HEADROOM = 16;
39
+ function finite(value) {
40
+ return Number.isFinite(value);
41
+ }
42
+ function parsePath(path) {
43
+ if (path === '')
44
+ return [];
45
+ var parts = path.split('-');
46
+ var out = [];
47
+ for (var i = 0; i < parts.length; i++) {
48
+ var step = Number(parts[i]);
49
+ if (!isFinite(step))
50
+ return [];
51
+ out.push(step);
52
+ }
53
+ return out;
54
+ }
55
+ function under(path, at) {
56
+ return at === '' || path === at || path.indexOf(at + '-') === 0;
57
+ }
58
+ function onChain(path, at) {
59
+ return at === '' || path === at || at.indexOf(path + '-') === 0;
60
+ }
61
+ function edgesBelow(path, at) {
62
+ var base = at === '' ? 1 : parsePath(at).length;
63
+ if (base === 0)
64
+ return 0;
65
+ var delta = parsePath(path).length - base;
66
+ return delta > 0 ? delta : 0;
67
+ }
68
+ function withinDepth(path, at, depth) {
69
+ return depth === 'all' || edgesBelow(path, at) <= Number(depth);
70
+ }
71
+ function visibleNode(path, at, depth) {
72
+ if (at === '')
73
+ return withinDepth(path, at, depth);
74
+ if (onChain(path, at))
75
+ return true;
76
+ return under(path, at) && withinDepth(path, at, depth);
77
+ }
78
+ function visibleCall(path, at, depth) {
79
+ if (at === '')
80
+ return withinDepth(path, at, depth);
81
+ if (onChain(path, at) || !under(path, at))
82
+ return false;
83
+ return withinDepth(path, at, depth);
84
+ }
85
+ function key(file, path) {
86
+ return String(file) + ':' + path;
87
+ }
88
+ /** Is this visit the one at `file`/`path`? */
89
+ function at(a, file, path) {
90
+ return a !== null && a.file === file && a.path === path;
91
+ }
92
+ function rebuild(state, trail, branch, depth) {
93
+ return { trail: trail, mode: state.mode, depth: depth, branch: branch, cameras: state.cameras };
94
+ }
95
+ /** The trail with `file`/`path` appended, unless it is already selected. */
96
+ function selected(trail, file, path) {
97
+ if (at(trail.length === 0 ? null : trail[trail.length - 1], file, path))
98
+ return trail;
99
+ var next = trail.slice();
100
+ next.push({ file: file, path: path });
101
+ return next;
102
+ }
103
+ function copyCameras(cameras) {
104
+ var next = {};
105
+ for (var name in cameras) {
106
+ if (Object.prototype.hasOwnProperty.call(cameras, name))
107
+ next[name] = cameras[name];
108
+ }
109
+ return next;
110
+ }
111
+ function createState() {
112
+ return { trail: [], mode: 'tree', depth: DEFAULT_DEPTH, branch: null, cameras: {} };
113
+ }
114
+ function current(state) {
115
+ return state.trail.length === 0 ? null : state.trail[state.trail.length - 1];
116
+ }
117
+ function visit(state, file, path) {
118
+ var index = Number(file);
119
+ if (!finite(index))
120
+ return state;
121
+ var known = at(state.branch, index, path);
122
+ if (known && at(current(state), index, path))
123
+ return state;
124
+ var depth = known ? state.depth : DEFAULT_DEPTH;
125
+ return rebuild(state, selected(state.trail, index, path), { file: index, path: path }, depth);
126
+ }
127
+ function inspect(state, file, path) {
128
+ var index = Number(file);
129
+ if (!finite(index))
130
+ return state;
131
+ if (at(current(state), index, path))
132
+ return state;
133
+ var trail = selected(state.trail, index, path);
134
+ var branch = state.branch;
135
+ var depth = state.depth;
136
+ if (branch !== null && (branch.file !== index || !under(path, branch.path))) {
137
+ branch = null;
138
+ depth = DEFAULT_DEPTH;
139
+ }
140
+ return rebuild(state, trail, branch, depth);
141
+ }
142
+ function truncate(state, index) {
143
+ var pos = Number(index);
144
+ if (!isFinite(pos) || pos < 0 || pos >= state.trail.length - 1)
145
+ return state;
146
+ var entry = state.trail[pos];
147
+ return rebuild(state, state.trail.slice(0, pos + 1), { file: entry.file, path: entry.path }, DEFAULT_DEPTH);
148
+ }
149
+ function clear(state) {
150
+ return rebuild(state, [], null, DEFAULT_DEPTH);
151
+ }
152
+ function setMode(state, mode) {
153
+ if (mode !== 'tree' && mode !== 'graph' && mode !== 'sequence')
154
+ return state;
155
+ if (mode === state.mode)
156
+ return state;
157
+ var depth = state.depth;
158
+ if (mode === 'graph') {
159
+ // The graph highlights the selection, so a depth that hid it would draw
160
+ // the branch without the call the reviewer is reading.
161
+ var selection = current(state);
162
+ var focus = state.branch === null ? '' : state.branch.path;
163
+ if (selection !== null &&
164
+ !withinDepth(selection.path, focus, depth) &&
165
+ under(selection.path, focus)) {
166
+ depth = DEFAULT_DEPTH;
167
+ }
168
+ }
169
+ return {
170
+ trail: state.trail,
171
+ mode: mode,
172
+ depth: depth,
173
+ branch: state.branch,
174
+ cameras: state.cameras,
175
+ };
176
+ }
177
+ function readDepth(value) {
178
+ for (var i = 0; i < DEPTHS.length; i++) {
179
+ if (String(DEPTHS[i]) === String(value))
180
+ return DEPTHS[i];
181
+ }
182
+ return null;
183
+ }
184
+ function setDepth(state, depth) {
185
+ var next = readDepth(depth);
186
+ if (next === null || next === state.depth)
187
+ return state;
188
+ return rebuild(state, state.trail, state.branch, next);
189
+ }
190
+ function cameraScale(camera) {
191
+ return finite(camera.scale) && camera.scale > 0 ? camera.scale : READABLE_SCALE;
192
+ }
193
+ function sameCamera(a, b) {
194
+ return a.x === b.x && a.y === b.y && a.scale === b.scale;
195
+ }
196
+ /**
197
+ * One axis of `constrain`: content wider than the viewport is windowed inside
198
+ * the graph, content narrower is centred on it.
199
+ */
200
+ function clampAxis(value, start, size, extent) {
201
+ if (!finite(extent) || !finite(size) || !finite(start))
202
+ return value;
203
+ if (extent >= size)
204
+ return start + (size - extent) / 2;
205
+ var max = start + size - extent;
206
+ return value < start ? start : value > max ? max : value;
207
+ }
208
+ function constrain(camera, bounds, viewport) {
209
+ var scale = cameraScale(camera);
210
+ var next = {
211
+ x: clampAxis(finite(camera.x) ? camera.x : bounds.x, bounds.x, bounds.width, viewport.width / scale),
212
+ y: clampAxis(finite(camera.y) ? camera.y : bounds.y, bounds.y, bounds.height, viewport.height / scale),
213
+ scale: scale,
214
+ };
215
+ return sameCamera(next, camera) ? camera : next;
216
+ }
217
+ function frame(bounds, viewport, target, overview) {
218
+ if (!finite(bounds.width) ||
219
+ !finite(bounds.height) ||
220
+ !finite(viewport.width) ||
221
+ !finite(viewport.height) ||
222
+ !finite(target.x) ||
223
+ !finite(target.y) ||
224
+ !finite(target.width) ||
225
+ !finite(target.height) ||
226
+ viewport.width <= 0 ||
227
+ viewport.height <= 0) {
228
+ return { x: bounds.x, y: bounds.y, scale: READABLE_SCALE };
229
+ }
230
+ if (overview) {
231
+ var fit = Math.min(READABLE_SCALE, viewport.width / bounds.width, viewport.height / bounds.height);
232
+ var scale = finite(fit) && fit > 0 ? fit : READABLE_SCALE;
233
+ return {
234
+ x: bounds.x + (bounds.width - viewport.width / scale) / 2,
235
+ y: bounds.y + (bounds.height - viewport.height / scale) / 2,
236
+ scale: scale,
237
+ };
238
+ }
239
+ return constrain({
240
+ x: target.x + target.width / 2 - viewport.width / 2,
241
+ y: target.y - FRAME_HEADROOM,
242
+ scale: READABLE_SCALE,
243
+ }, bounds, viewport);
244
+ }
245
+ function pan(camera, dx, dy, bounds, viewport) {
246
+ if (!finite(dx) || !finite(dy))
247
+ return camera;
248
+ var scale = cameraScale(camera);
249
+ return constrain({ x: camera.x - dx / scale, y: camera.y - dy / scale, scale: scale }, bounds, viewport);
250
+ }
251
+ function zoom(camera, factor, anchor, bounds, viewport) {
252
+ if (!finite(factor) || factor <= 0)
253
+ return camera;
254
+ if (!finite(anchor.x) || !finite(anchor.y)) {
255
+ return camera;
256
+ }
257
+ var scale = cameraScale(camera);
258
+ var next = scale * factor;
259
+ if (next < READABLE_SCALE)
260
+ next = READABLE_SCALE;
261
+ else if (next > MAX_SCALE)
262
+ next = MAX_SCALE;
263
+ // The world point under the anchor stays under the anchor.
264
+ var x = camera.x + anchor.x / scale - anchor.x / next;
265
+ var y = camera.y + anchor.y / scale - anchor.y / next;
266
+ var zoomed = constrain({ x: x, y: y, scale: next }, bounds, viewport);
267
+ return sameCamera(zoomed, camera) ? camera : zoomed;
268
+ }
269
+ function setCamera(state, name, camera) {
270
+ if (name === '')
271
+ return state;
272
+ if (!finite(camera.x) || !finite(camera.y) || !finite(camera.scale) || camera.scale <= 0) {
273
+ return state;
274
+ }
275
+ var stored = state.cameras[name];
276
+ if (stored !== undefined && sameCamera(stored, camera))
277
+ return state;
278
+ var cameras = copyCameras(state.cameras);
279
+ cameras[name] = { x: camera.x, y: camera.y, scale: camera.scale };
280
+ return {
281
+ trail: state.trail,
282
+ mode: state.mode,
283
+ depth: state.depth,
284
+ branch: state.branch,
285
+ cameras: cameras,
286
+ };
287
+ }
288
+ return {
289
+ parsePath: parsePath,
290
+ under: under,
291
+ onChain: onChain,
292
+ edgesBelow: edgesBelow,
293
+ visibleNode: visibleNode,
294
+ visibleCall: visibleCall,
295
+ key: key,
296
+ createState: createState,
297
+ current: current,
298
+ visit: visit,
299
+ inspect: inspect,
300
+ truncate: truncate,
301
+ clear: clear,
302
+ setMode: setMode,
303
+ setDepth: setDepth,
304
+ frame: frame,
305
+ constrain: constrain,
306
+ pan: pan,
307
+ zoom: zoom,
308
+ setCamera: setCamera,
309
+ modes: MODES,
310
+ depths: DEPTHS,
311
+ };
312
+ }
313
+ /**
314
+ * The navigation module as the page script receives it. Built from the function
315
+ * source, so the browser and the unit tests cannot drift apart.
316
+ */
317
+ export const CALL_FLOW_NAV_SOURCE = `var cfNav = (${callFlowsNav.toString()})();\n`;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Serialization of engine diff trees into the report's bounded per-file call
3
+ * flows. Everything here is a pure function of engine `DiffNode` results: the
4
+ * ASCII renderings stay untouched for the judgment context, and the structured
5
+ * trees are never parsed back out of that text.
6
+ */
7
+ import type { DiffNode, DiffTreeResult } from "../types.js";
8
+ import type { DefinitionDetail } from "./source.js";
9
+ import type { CallFlowFile, ReviewItem, ReviewUnit } from "./types.js";
10
+ /**
11
+ * Bounds per changed file. They exist so one huge file cannot push an unbounded
12
+ * report into the HTML, and every bound that drops content sets
13
+ * `CallFlowFile.truncated`.
14
+ */
15
+ export declare const CALL_FLOW_MAX_ROOTS = 8;
16
+ /** Edges below a root. The engine already stops expanding at the same depth. */
17
+ export declare const CALL_FLOW_MAX_DEPTH = 4;
18
+ export declare const CALL_FLOW_MAX_CHILDREN = 8;
19
+ /**
20
+ * Extra children allowed per node beyond {@link CALL_FLOW_MAX_CHILDREN} when the
21
+ * callee is defined in another file. Without it a wide call graph drops exactly
22
+ * the calls a reader cannot see in the file under review.
23
+ */
24
+ export declare const CALL_FLOW_MAX_CROSS_FILE_CHILDREN = 8;
25
+ export declare const CALL_FLOW_MAX_NODES = 160;
26
+ /**
27
+ * Definition source and prose for one node, supplied by the review service so
28
+ * this module stays a pure function of the engine result plus that lookup. It is
29
+ * only called for nodes that survive the bounds.
30
+ */
31
+ export type CallFlowNodeDetail = (node: DiffNode) => DefinitionDetail;
32
+ /** Distinct changed-hunk files in report rank order; metadata-only files are excluded. */
33
+ export declare function reportOrderedTextHunkFiles(items: readonly ReviewItem[], units: readonly ReviewUnit[]): string[];
34
+ /**
35
+ * Whether tree `node` is defined in `file`, calls into it anywhere below, or is
36
+ * a call the engine resolved to a definition in it. A caller several frames up
37
+ * is exactly the reach the report has to make visible, and a callee defined in
38
+ * `file` is a call this file's reader cannot see at all.
39
+ */
40
+ export declare function treeTouchesFile(node: DiffNode, file: string): boolean;
41
+ /**
42
+ * One bounded entry per file with actual text hunks that at least one tree
43
+ * reaches, in the order given. Files no tree reaches are omitted rather than
44
+ * reported as an empty tree. `detail` adds definition source and prose; without
45
+ * it the trees carry structure only.
46
+ */
47
+ export declare function buildCallFlows(files: readonly string[], trees: readonly DiffTreeResult[], detail?: CallFlowNodeDetail): CallFlowFile[];