@ttsc/graph 0.19.2 → 0.20.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/lib/TtscGraphApplication.js +5 -1
- package/lib/TtscGraphApplication.js.map +1 -1
- package/lib/bin.js +15 -3
- package/lib/bin.js.map +1 -1
- package/lib/index.d.ts +13 -2
- package/lib/index.js +25 -30
- package/lib/index.js.map +1 -1
- package/lib/launcherArgs.d.ts +24 -0
- package/lib/launcherArgs.js +136 -0
- package/lib/launcherArgs.js.map +1 -0
- package/lib/model/TtscGraphMemory.d.ts +9 -5
- package/lib/model/TtscGraphMemory.js +35 -58
- package/lib/model/TtscGraphMemory.js.map +1 -1
- package/lib/model/TtscGraphNodeId.d.ts +17 -0
- package/lib/model/TtscGraphNodeId.js +69 -0
- package/lib/model/TtscGraphNodeId.js.map +1 -0
- package/lib/model/TtscGraphSession.d.ts +23 -7
- package/lib/model/TtscGraphSession.js +423 -171
- package/lib/model/TtscGraphSession.js.map +1 -1
- package/lib/model/TtscGraphSourceReader.d.ts +27 -0
- package/lib/model/TtscGraphSourceReader.js +97 -0
- package/lib/model/TtscGraphSourceReader.js.map +1 -0
- package/lib/model/loadGraph.d.ts +14 -0
- package/lib/model/loadGraph.js +256 -143
- package/lib/model/loadGraph.js.map +1 -1
- package/lib/reduce.d.ts +4 -1
- package/lib/reduce.js +112 -29
- package/lib/reduce.js.map +1 -1
- package/lib/resolveGraphBinary.d.ts +9 -3
- package/lib/resolveGraphBinary.js +9 -3
- package/lib/resolveGraphBinary.js.map +1 -1
- package/lib/server/createServer.js +22 -11
- package/lib/server/createServer.js.map +1 -1
- package/lib/server/pathPolicy.d.ts +6 -0
- package/lib/server/pathPolicy.js +10 -1
- package/lib/server/pathPolicy.js.map +1 -1
- package/lib/server/resolveHandle.js +22 -18
- package/lib/server/resolveHandle.js.map +1 -1
- package/lib/server/resultAudit.d.ts +56 -14
- package/lib/server/resultAudit.js +90 -24
- package/lib/server/resultAudit.js.map +1 -1
- package/lib/server/resultNext.d.ts +5 -0
- package/lib/server/resultNext.js.map +1 -1
- package/lib/server/runDetails.d.ts +9 -2
- package/lib/server/runDetails.js +132 -157
- package/lib/server/runDetails.js.map +1 -1
- package/lib/server/runEntrypoints.js +1 -1
- package/lib/server/runEntrypoints.js.map +1 -1
- package/lib/server/runLookup.js +1 -1
- package/lib/server/runLookup.js.map +1 -1
- package/lib/server/runTour.js +113 -30
- package/lib/server/runTour.js.map +1 -1
- package/lib/server/runTrace.d.ts +22 -0
- package/lib/server/runTrace.js +293 -63
- package/lib/server/runTrace.js.map +1 -1
- package/lib/structures/ITtscGraphApplication.d.ts +8 -7
- package/lib/structures/ITtscGraphDetails.d.ts +18 -8
- package/lib/structures/ITtscGraphDump.d.ts +13 -13
- package/lib/structures/ITtscGraphNode.d.ts +71 -0
- package/lib/structures/ITtscGraphTour.d.ts +1 -1
- package/lib/structures/ITtscGraphTrace.d.ts +1 -1
- package/lib/view.js +18 -28
- package/lib/view.js.map +1 -1
- package/package.json +7 -4
- package/src/TtscGraphApplication.ts +8 -1
- package/src/bin.ts +13 -3
- package/src/index.ts +35 -27
- package/src/launcherArgs.ts +168 -0
- package/src/model/TtscGraphMemory.ts +34 -60
- package/src/model/TtscGraphNodeId.ts +77 -0
- package/src/model/TtscGraphSession.ts +253 -53
- package/src/model/TtscGraphSourceReader.ts +117 -0
- package/src/model/loadGraph.ts +6 -1
- package/src/reduce.ts +136 -31
- package/src/resolveGraphBinary.ts +9 -3
- package/src/server/createServer.ts +12 -2
- package/src/server/pathPolicy.ts +10 -1
- package/src/server/resolveHandle.ts +21 -22
- package/src/server/resultAudit.ts +93 -23
- package/src/server/resultNext.ts +6 -0
- package/src/server/runDetails.ts +134 -165
- package/src/server/runEntrypoints.ts +1 -1
- package/src/server/runLookup.ts +1 -1
- package/src/server/runTour.ts +133 -39
- package/src/server/runTrace.ts +401 -65
- package/src/structures/ITtscGraphApplication.ts +8 -7
- package/src/structures/ITtscGraphDetails.ts +18 -8
- package/src/structures/ITtscGraphDump.ts +13 -13
- package/src/structures/ITtscGraphNode.ts +80 -0
- package/src/structures/ITtscGraphTour.ts +1 -1
- package/src/structures/ITtscGraphTrace.ts +1 -1
- package/src/view.ts +25 -23
package/src/server/runTour.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { resolveGraphHandle } from "./resolveHandle";
|
|
|
13
13
|
import { IRunnerOutput, resultNext } from "./resultNext";
|
|
14
14
|
import { decoratorsOf, docOf, runDetails, signatureOf } from "./runDetails";
|
|
15
15
|
import { runEntrypoints } from "./runEntrypoints";
|
|
16
|
-
import { runTrace } from "./runTrace";
|
|
16
|
+
import { hasDeclarationBody, runTrace } from "./runTrace";
|
|
17
17
|
|
|
18
18
|
const DEFAULT_LIMIT = 5;
|
|
19
19
|
const MAX_LIMIT = 5;
|
|
@@ -110,6 +110,24 @@ export function runTour(
|
|
|
110
110
|
// so keep the first and walk on to one that tells something else.
|
|
111
111
|
const primaryFlow: ITtscGraphTour.IFlow[] = [];
|
|
112
112
|
const told: Set<string>[] = [];
|
|
113
|
+
// A flow whose every hop the hub cut would remove is demoted, not deleted.
|
|
114
|
+
// Deleting it is what #809 reports: eleven callers kept the flow and a
|
|
115
|
+
// twelfth erased it, so the tour answered a question about a terminal action
|
|
116
|
+
// with nothing. Telling it regardless is what the cut was written against: a
|
|
117
|
+
// one-line wrapper around a logger became one of the tour's four flows.
|
|
118
|
+
//
|
|
119
|
+
// Demotion settles both without a rule that can tell a commit from a logger,
|
|
120
|
+
// which the graph cannot: the flow is held back, and told only if the tour
|
|
121
|
+
// finishes with nothing else to say. The first such candidate is kept because
|
|
122
|
+
// the seeds arrive ranked.
|
|
123
|
+
let demoted:
|
|
124
|
+
| {
|
|
125
|
+
start: ITtscGraphTrace.INode;
|
|
126
|
+
hops: ITtscGraphTrace.IHop[];
|
|
127
|
+
reached: ITtscGraphTrace.INode[];
|
|
128
|
+
truncated: boolean;
|
|
129
|
+
}
|
|
130
|
+
| undefined;
|
|
113
131
|
|
|
114
132
|
for (const id of flowSeedIdsOf(
|
|
115
133
|
tourSeedsOf(
|
|
@@ -132,11 +150,28 @@ export function runTour(
|
|
|
132
150
|
}).result;
|
|
133
151
|
const start = trace.start;
|
|
134
152
|
if (start === undefined) continue;
|
|
135
|
-
const
|
|
136
|
-
if (hops.length === 0)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
153
|
+
const { kept: hops, demotable } = tourHops(graph, trace.hops);
|
|
154
|
+
if (hops.length === 0) {
|
|
155
|
+
// Held back under the same rule the published path uses: a candidate that
|
|
156
|
+
// reached nothing is not a flow, so it is not worth holding either.
|
|
157
|
+
const demotedReach =
|
|
158
|
+
demotable.length === 0 ? [] : reachedOf(graph, trace, demotable);
|
|
159
|
+
if (demoted === undefined && demotedReach.length > 0)
|
|
160
|
+
demoted = {
|
|
161
|
+
start,
|
|
162
|
+
hops: demotable,
|
|
163
|
+
reached: demotedReach,
|
|
164
|
+
truncated: trace.truncated === true,
|
|
165
|
+
};
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const reached = reachedOf(graph, trace, hops);
|
|
169
|
+
// A flow that reached nothing is not a flow. It can still have a hop —
|
|
170
|
+
// `runTrace` records a back-edge to the start without adding a node,
|
|
171
|
+
// because the start travels separately — so `hops.length` does not answer
|
|
172
|
+
// this. What a flow is for is the handles a caller goes on with, and this
|
|
173
|
+
// one has none.
|
|
174
|
+
if (reached.length === 0) continue;
|
|
140
175
|
const landed = new Set(reached.map((node) => node.id));
|
|
141
176
|
if (told.some((earlier) => overlaps(landed, earlier))) continue;
|
|
142
177
|
told.push(landed);
|
|
@@ -160,6 +195,16 @@ export function runTour(
|
|
|
160
195
|
});
|
|
161
196
|
}
|
|
162
197
|
|
|
198
|
+
if (primaryFlow.length === 0 && demoted !== undefined)
|
|
199
|
+
primaryFlow.push({
|
|
200
|
+
start: flowStartOf(demoted.start),
|
|
201
|
+
steps: demoted.hops
|
|
202
|
+
.slice(0, MAX_FLOW_ANCHORS)
|
|
203
|
+
.map((hop) => flowStepOf(graph, hop)),
|
|
204
|
+
reached: demoted.reached.map(traceNodeOf),
|
|
205
|
+
...(demoted.truncated ? { truncated: true } : {}),
|
|
206
|
+
});
|
|
207
|
+
|
|
163
208
|
const details =
|
|
164
209
|
seedIds.length === 0
|
|
165
210
|
? undefined
|
|
@@ -354,8 +399,8 @@ function graphNodeOf(
|
|
|
354
399
|
node: ITtscGraphNode,
|
|
355
400
|
): ITtscGraphTour.INode {
|
|
356
401
|
const span = node.implementation ?? node.evidence;
|
|
357
|
-
const signature = signatureOf(graph
|
|
358
|
-
const doc = docOf(graph
|
|
402
|
+
const signature = signatureOf(graph, node);
|
|
403
|
+
const doc = docOf(graph, node);
|
|
359
404
|
const decorators = decoratorsOf(node);
|
|
360
405
|
return {
|
|
361
406
|
id: node.id,
|
|
@@ -561,13 +606,17 @@ function computeCentrality(graph: TtscGraphMemory): Map<string, number> {
|
|
|
561
606
|
|
|
562
607
|
const invoked = (id: string): string[] => {
|
|
563
608
|
const out: string[] = [];
|
|
564
|
-
let hasBody = false;
|
|
565
609
|
for (const edge of graph.outgoing(id)) {
|
|
566
|
-
if (
|
|
567
|
-
hasBody = true;
|
|
568
|
-
out.push(edge.to);
|
|
610
|
+
if (INVOKE_KINDS.has(edge.kind)) out.push(edge.to);
|
|
569
611
|
}
|
|
570
|
-
|
|
612
|
+
// Whether the declaration has a body is a declaration fact, not an edge
|
|
613
|
+
// count. A concrete method that returns a literal, does local arithmetic or
|
|
614
|
+
// throws has a body and no invocation edges at all, and counting edges
|
|
615
|
+
// ranked it as though it were an abstract member — then walked its
|
|
616
|
+
// implementations as if the call had to dispatch. `runTrace` stopped using
|
|
617
|
+
// that proxy in #828; this was the second copy of it.
|
|
618
|
+
const node = graph.node(id);
|
|
619
|
+
if (node !== undefined && !hasDeclarationBody(graph, node)) {
|
|
571
620
|
for (const edge of graph.incoming(id)) {
|
|
572
621
|
if (DISPATCH_KINDS.has(edge.kind)) out.push(edge.from);
|
|
573
622
|
}
|
|
@@ -691,11 +740,20 @@ function flowSeedIdsOf(seeds: ITtscGraphNode[]): string[] {
|
|
|
691
740
|
* twice. Overlap is measured against the smaller flow, so a short chain fully
|
|
692
741
|
* contained in a longer one counts as told, which is what a sibling entry
|
|
693
742
|
* (`parse` beside `safeParse`) actually is.
|
|
743
|
+
*
|
|
744
|
+
* Neither side matches when it is empty, and the two are empty for different
|
|
745
|
+
* reasons. An empty CANDIDATE reached nothing, so it is not a flow at all and
|
|
746
|
+
* the caller settles it before asking here. An empty TOLD would mean the tour
|
|
747
|
+
* had published a flow that reached nothing; it cannot now, and while it could,
|
|
748
|
+
* this function reported every later candidate as a synonym of that emptiness.
|
|
749
|
+
* One directly self-recursive function — a retry loop, a tree walk, a parser's
|
|
750
|
+
* descent — produced exactly that set, and the tour lost every real flow ranked
|
|
751
|
+
* after it.
|
|
694
752
|
*/
|
|
695
753
|
function overlaps(candidate: Set<string>, told: Set<string>): boolean {
|
|
754
|
+
if (candidate.size === 0 || told.size === 0) return false;
|
|
696
755
|
const smaller = candidate.size <= told.size ? candidate : told;
|
|
697
756
|
const larger = smaller === candidate ? told : candidate;
|
|
698
|
-
if (smaller.size === 0) return true;
|
|
699
757
|
let shared = 0;
|
|
700
758
|
for (const id of smaller) if (larger.has(id)) shared++;
|
|
701
759
|
return shared / smaller.size >= FLOW_OVERLAP;
|
|
@@ -705,13 +763,61 @@ function isTourTraceNode(
|
|
|
705
763
|
graph: TtscGraphMemory,
|
|
706
764
|
node: ITtscGraphTrace.INode,
|
|
707
765
|
): boolean {
|
|
708
|
-
return (
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
766
|
+
return graph.node(node.id)?.closure !== true && !isNoisePath(node.file);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* The hops a flow keeps, and the hops it would keep if the cut were not
|
|
771
|
+
* applied.
|
|
772
|
+
*
|
|
773
|
+
* Degree cannot tell a logger from a database commit. Both are called from many
|
|
774
|
+
* sites and call nothing onward, so no threshold on `isSharedUtility` separates
|
|
775
|
+
* the noise the cut exists to remove from the point where a runtime flow does
|
|
776
|
+
* its work. Rather than guess, this returns both readings and lets the caller
|
|
777
|
+
* demote instead of delete.
|
|
778
|
+
*
|
|
779
|
+
* A hub the flow continues past always keeps its inbound hop. Dropping it left
|
|
780
|
+
* the outbound step narrating a chain from a node the same flow reported it
|
|
781
|
+
* never reached, which is incoherent whatever that node means, so this one is
|
|
782
|
+
* not a judgement call and is applied unconditionally.
|
|
783
|
+
*
|
|
784
|
+
* `demotable` is non-empty exactly when the cut would empty the flow — when
|
|
785
|
+
* every hop it has lands on a hub. That is the shape the caller holds back and
|
|
786
|
+
* tells only if the tour finishes with nothing else, so a wrapper around a
|
|
787
|
+
* logger never displaces a real chain and a lone terminal action is never
|
|
788
|
+
* erased.
|
|
789
|
+
*/
|
|
790
|
+
/**
|
|
791
|
+
* The nodes a flow reached, derived from the hops that survived.
|
|
792
|
+
*
|
|
793
|
+
* Deriving it rather than filtering it beside them is what makes a dangling
|
|
794
|
+
* step impossible: a step can only name endpoints of a kept hop, and every such
|
|
795
|
+
* endpoint is here. Filtering the two independently is what let a step narrate
|
|
796
|
+
* a chain from a node the same flow reported it had never reached.
|
|
797
|
+
*/
|
|
798
|
+
function reachedOf(
|
|
799
|
+
graph: TtscGraphMemory,
|
|
800
|
+
trace: ITtscGraphTrace,
|
|
801
|
+
hops: readonly ITtscGraphTrace.IHop[],
|
|
802
|
+
): ITtscGraphTrace.INode[] {
|
|
803
|
+
const touched = new Set(hops.flatMap((hop) => [hop.from, hop.to]));
|
|
804
|
+
return trace.reached.filter(
|
|
805
|
+
(node) => touched.has(node.id) && isTourTraceNode(graph, node),
|
|
712
806
|
);
|
|
713
807
|
}
|
|
714
808
|
|
|
809
|
+
function tourHops(
|
|
810
|
+
graph: TtscGraphMemory,
|
|
811
|
+
hops: readonly ITtscGraphTrace.IHop[],
|
|
812
|
+
): { kept: ITtscGraphTrace.IHop[]; demotable: ITtscGraphTrace.IHop[] } {
|
|
813
|
+
const eligible = hops.filter((hop) => isTourHop(graph, hop));
|
|
814
|
+
const departures = new Set(eligible.map((hop) => hop.from));
|
|
815
|
+
const kept = eligible.filter(
|
|
816
|
+
(hop) => !isSharedUtility(graph, hop.to) || departures.has(hop.to),
|
|
817
|
+
);
|
|
818
|
+
return { kept, demotable: kept.length === 0 ? eligible : [] };
|
|
819
|
+
}
|
|
820
|
+
|
|
715
821
|
function isTourHop(graph: TtscGraphMemory, hop: ITtscGraphTrace.IHop): boolean {
|
|
716
822
|
const from = graph.node(hop.from);
|
|
717
823
|
const to = graph.node(hop.to);
|
|
@@ -722,8 +828,7 @@ function isTourHop(graph: TtscGraphMemory, hop: ITtscGraphTrace.IHop): boolean {
|
|
|
722
828
|
to.closure !== true &&
|
|
723
829
|
!STRUCTURAL_KINDS.has(hop.kind) &&
|
|
724
830
|
!isNoisePath(from.file) &&
|
|
725
|
-
!isNoisePath(to.file)
|
|
726
|
-
!isSharedUtility(graph, hop.to)
|
|
831
|
+
!isNoisePath(to.file)
|
|
727
832
|
);
|
|
728
833
|
}
|
|
729
834
|
|
|
@@ -739,8 +844,15 @@ function isTourHop(graph: TtscGraphMemory, hop: ITtscGraphTrace.IHop): boolean {
|
|
|
739
844
|
// "top few percent of hubs" band regardless of project size, while the absolute
|
|
740
845
|
// floor makes it a no-op on small graphs that have no genuine hub. The `out <= 1`
|
|
741
846
|
// guard keeps thin pass-throughs out but never prunes a real branching step.
|
|
847
|
+
//
|
|
848
|
+
// The fan-in counted is EXECUTION fan-in. `realDegree` also counts `type_ref`,
|
|
849
|
+
// `extends`, `decorates`, and `tests`, and a widely referenced type or a
|
|
850
|
+
// widely decorated symbol is not a call hub: a `Config` class named in twelve
|
|
851
|
+
// parameter positions and constructed once was read as a hub and cut out of the
|
|
852
|
+
// flow that constructs it. Popularity as a name is not popularity as a call.
|
|
742
853
|
function isSharedUtility(graph: TtscGraphMemory, id: string): boolean {
|
|
743
|
-
|
|
854
|
+
const execution = executionDegree(graph, id);
|
|
855
|
+
return execution.in >= 12 && execution.out <= 1;
|
|
744
856
|
}
|
|
745
857
|
|
|
746
858
|
function flowStepOf(graph: TtscGraphMemory, hop: ITtscGraphTrace.IHop): string {
|
|
@@ -767,24 +879,6 @@ function touchesClosure(graph: TtscGraphMemory, id: string): boolean {
|
|
|
767
879
|
return graph.node(id)?.closure === true;
|
|
768
880
|
}
|
|
769
881
|
|
|
770
|
-
function realDegree(
|
|
771
|
-
graph: TtscGraphMemory,
|
|
772
|
-
id: string,
|
|
773
|
-
): {
|
|
774
|
-
in: number;
|
|
775
|
-
out: number;
|
|
776
|
-
} {
|
|
777
|
-
let incoming = 0;
|
|
778
|
-
let outgoing = 0;
|
|
779
|
-
for (const edge of graph.outgoing(id))
|
|
780
|
-
if (!STRUCTURAL_KINDS.has(edge.kind) && !touchesClosure(graph, edge.to))
|
|
781
|
-
outgoing++;
|
|
782
|
-
for (const edge of graph.incoming(id))
|
|
783
|
-
if (!STRUCTURAL_KINDS.has(edge.kind) && !touchesClosure(graph, edge.from))
|
|
784
|
-
incoming++;
|
|
785
|
-
return { in: incoming, out: outgoing };
|
|
786
|
-
}
|
|
787
|
-
|
|
788
882
|
function executionDegree(
|
|
789
883
|
graph: TtscGraphMemory,
|
|
790
884
|
id: string,
|