ts-knowledge-graph 0.1.2 → 0.1.6
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 +99 -41
- package/contribs/webview/README.md +83 -0
- package/contribs/webview/web/css/style.css +310 -0
- package/contribs/webview/web/index.html +109 -0
- package/contribs/webview/web/js/app.js +1249 -0
- package/contribs/webview/web/js_autogenerated/.gitignore +3 -0
- package/contribs/webview/web/js_autogenerated/kind_descriptions.js +39 -0
- package/contribs/webview/web/types/app_globals.d.ts +154 -0
- package/dist/benchmark/benchmark_stats.d.ts +41 -0
- package/dist/benchmark/benchmark_stats.d.ts.map +1 -0
- package/dist/benchmark/benchmark_stats.js +61 -0
- package/dist/benchmark/benchmark_stats.js.map +1 -0
- package/dist/benchmark/node_benchmark.d.ts +78 -0
- package/dist/benchmark/node_benchmark.d.ts.map +1 -0
- package/dist/benchmark/node_benchmark.js +112 -0
- package/dist/benchmark/node_benchmark.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +16 -4
- package/dist/cli.js.map +1 -1
- package/dist/cluster/cluster_weights.d.ts +20 -0
- package/dist/cluster/cluster_weights.d.ts.map +1 -0
- package/dist/cluster/cluster_weights.js +32 -0
- package/dist/cluster/cluster_weights.js.map +1 -0
- package/dist/cluster/community_detector.d.ts +61 -0
- package/dist/cluster/community_detector.d.ts.map +1 -0
- package/dist/cluster/community_detector.js +120 -0
- package/dist/cluster/community_detector.js.map +1 -0
- package/dist/cluster/community_labeler.d.ts +84 -0
- package/dist/cluster/community_labeler.d.ts.map +1 -0
- package/dist/cluster/community_labeler.js +194 -0
- package/dist/cluster/community_labeler.js.map +1 -0
- package/dist/cluster/graph_clusterer.d.ts +47 -0
- package/dist/cluster/graph_clusterer.d.ts.map +1 -0
- package/dist/cluster/graph_clusterer.js +126 -0
- package/dist/cluster/graph_clusterer.js.map +1 -0
- package/dist/commands/benchmark_command.d.ts +11 -0
- package/dist/commands/benchmark_command.d.ts.map +1 -0
- package/dist/commands/benchmark_command.js +94 -0
- package/dist/commands/benchmark_command.js.map +1 -0
- package/dist/commands/blast_radius_command.d.ts.map +1 -1
- package/dist/commands/blast_radius_command.js +7 -6
- package/dist/commands/blast_radius_command.js.map +1 -1
- package/dist/commands/cluster_command.d.ts +7 -0
- package/dist/commands/cluster_command.d.ts.map +1 -0
- package/dist/commands/cluster_command.js +55 -0
- package/dist/commands/cluster_command.js.map +1 -0
- package/dist/commands/command_helpers.d.ts +9 -4
- package/dist/commands/command_helpers.d.ts.map +1 -1
- package/dist/commands/command_helpers.js +13 -8
- package/dist/commands/command_helpers.js.map +1 -1
- package/dist/commands/cost_command.d.ts +13 -0
- package/dist/commands/cost_command.d.ts.map +1 -0
- package/dist/commands/cost_command.js +139 -0
- package/dist/commands/cost_command.js.map +1 -0
- package/dist/commands/{load.d.ts → enrich_command.d.ts} +3 -2
- package/dist/commands/enrich_command.d.ts.map +1 -0
- package/dist/commands/enrich_command.js +64 -0
- package/dist/commands/enrich_command.js.map +1 -0
- package/dist/commands/extract_command.d.ts.map +1 -1
- package/dist/commands/extract_command.js +12 -6
- package/dist/commands/extract_command.js.map +1 -1
- package/dist/commands/hotspots_command.d.ts +7 -0
- package/dist/commands/hotspots_command.d.ts.map +1 -0
- package/dist/commands/hotspots_command.js +68 -0
- package/dist/commands/hotspots_command.js.map +1 -0
- package/dist/commands/install_command.d.ts +15 -6
- package/dist/commands/install_command.d.ts.map +1 -1
- package/dist/commands/install_command.js +62 -25
- package/dist/commands/install_command.js.map +1 -1
- package/dist/commands/load_command.d.ts.map +1 -1
- package/dist/commands/load_command.js +20 -13
- package/dist/commands/load_command.js.map +1 -1
- package/dist/commands/neighbors_command.d.ts.map +1 -1
- package/dist/commands/neighbors_command.js +6 -5
- package/dist/commands/neighbors_command.js.map +1 -1
- package/dist/commands/references_command.d.ts.map +1 -1
- package/dist/commands/references_command.js +6 -5
- package/dist/commands/references_command.js.map +1 -1
- package/dist/commands/report_command.d.ts +16 -0
- package/dist/commands/report_command.d.ts.map +1 -0
- package/dist/commands/report_command.js +115 -0
- package/dist/commands/report_command.js.map +1 -0
- package/dist/commands/verify_command.d.ts +8 -0
- package/dist/commands/verify_command.d.ts.map +1 -0
- package/dist/commands/verify_command.js +57 -0
- package/dist/commands/verify_command.js.map +1 -0
- package/dist/commands/web_command.d.ts +27 -0
- package/dist/commands/web_command.d.ts.map +1 -1
- package/dist/commands/web_command.js +109 -3
- package/dist/commands/web_command.js.map +1 -1
- package/dist/commands/webview_command.d.ts +36 -0
- package/dist/commands/webview_command.d.ts.map +1 -0
- package/dist/commands/webview_command.js +186 -0
- package/dist/commands/webview_command.js.map +1 -0
- package/dist/enrich/cpu_profile.d.ts +160 -0
- package/dist/enrich/cpu_profile.d.ts.map +1 -0
- package/dist/enrich/cpu_profile.js +185 -0
- package/dist/enrich/cpu_profile.js.map +1 -0
- package/dist/enrich/runtime_enricher.d.ts +64 -0
- package/dist/enrich/runtime_enricher.d.ts.map +1 -0
- package/dist/enrich/runtime_enricher.js +98 -0
- package/dist/enrich/runtime_enricher.js.map +1 -0
- package/dist/enrich/runtime_join.d.ts +124 -0
- package/dist/enrich/runtime_join.d.ts.map +1 -0
- package/dist/enrich/runtime_join.js +270 -0
- package/dist/enrich/runtime_join.js.map +1 -0
- package/dist/extract/api_extractor.d.ts +24 -0
- package/dist/extract/api_extractor.d.ts.map +1 -0
- package/dist/extract/api_extractor.js +71 -0
- package/dist/extract/api_extractor.js.map +1 -0
- package/dist/extract/config_extractor.d.ts +22 -0
- package/dist/extract/config_extractor.d.ts.map +1 -0
- package/dist/extract/config_extractor.js +61 -0
- package/dist/extract/config_extractor.js.map +1 -0
- package/dist/extract/endpoint_extractor.d.ts +36 -0
- package/dist/extract/endpoint_extractor.d.ts.map +1 -0
- package/dist/extract/endpoint_extractor.js +117 -0
- package/dist/extract/endpoint_extractor.js.map +1 -0
- package/dist/extract/git_source.d.ts +23 -0
- package/dist/extract/git_source.d.ts.map +1 -0
- package/dist/extract/git_source.js +75 -0
- package/dist/extract/git_source.js.map +1 -0
- package/dist/extract/graph_builder.d.ts +8 -0
- package/dist/extract/graph_builder.d.ts.map +1 -1
- package/dist/extract/graph_builder.js +23 -1
- package/dist/extract/graph_builder.js.map +1 -1
- package/dist/extract/node_id.d.ts +16 -0
- package/dist/extract/node_id.d.ts.map +1 -1
- package/dist/extract/node_id.js +22 -0
- package/dist/extract/node_id.js.map +1 -1
- package/dist/extract/scope_resolver.d.ts +22 -0
- package/dist/extract/scope_resolver.d.ts.map +1 -0
- package/dist/extract/scope_resolver.js +53 -0
- package/dist/extract/scope_resolver.js.map +1 -0
- package/dist/extract/semantic_extractor.d.ts +25 -0
- package/dist/extract/semantic_extractor.d.ts.map +1 -1
- package/dist/extract/semantic_extractor.js +96 -2
- package/dist/extract/semantic_extractor.js.map +1 -1
- package/dist/extract/structural_extractor.d.ts +6 -0
- package/dist/extract/structural_extractor.d.ts.map +1 -1
- package/dist/extract/structural_extractor.js +22 -12
- package/dist/extract/structural_extractor.js.map +1 -1
- package/dist/project_root.d.ts +7 -0
- package/dist/project_root.d.ts.map +1 -0
- package/dist/project_root.js +9 -0
- package/dist/project_root.js.map +1 -0
- package/dist/query/graph_query.d.ts +269 -0
- package/dist/query/graph_query.d.ts.map +1 -1
- package/dist/query/graph_query.js +585 -11
- package/dist/query/graph_query.js.map +1 -1
- package/dist/report/graph_report.d.ts +51 -0
- package/dist/report/graph_report.d.ts.map +1 -0
- package/dist/report/graph_report.js +312 -0
- package/dist/report/graph_report.js.map +1 -0
- package/dist/report/pdf_renderer.d.ts +22 -0
- package/dist/report/pdf_renderer.d.ts.map +1 -0
- package/dist/report/pdf_renderer.js +54 -0
- package/dist/report/pdf_renderer.js.map +1 -0
- package/dist/report/report_data.d.ts +128 -0
- package/dist/report/report_data.d.ts.map +1 -0
- package/dist/report/report_data.js +191 -0
- package/dist/report/report_data.js.map +1 -0
- package/dist/schema/edge.d.ts +40 -5
- package/dist/schema/edge.d.ts.map +1 -1
- package/dist/schema/edge.js +73 -0
- package/dist/schema/edge.js.map +1 -1
- package/dist/schema/node.d.ts +20 -5
- package/dist/schema/node.d.ts.map +1 -1
- package/dist/schema/node.js +36 -0
- package/dist/schema/node.js.map +1 -1
- package/dist/schema/runtime_manifest.d.ts +36 -0
- package/dist/schema/runtime_manifest.d.ts.map +1 -0
- package/dist/schema/runtime_manifest.js +23 -0
- package/dist/schema/runtime_manifest.js.map +1 -0
- package/dist/schema/source_manifest.d.ts +30 -0
- package/dist/schema/source_manifest.d.ts.map +1 -0
- package/dist/schema/source_manifest.js +21 -0
- package/dist/schema/source_manifest.js.map +1 -0
- package/dist/store/jsonl_reader.d.ts +4 -0
- package/dist/store/jsonl_reader.d.ts.map +1 -1
- package/dist/store/jsonl_reader.js +13 -1
- package/dist/store/jsonl_reader.js.map +1 -1
- package/dist/store/jsonl_store.d.ts +2 -1
- package/dist/store/jsonl_store.d.ts.map +1 -1
- package/dist/store/jsonl_store.js +4 -1
- package/dist/store/jsonl_store.js.map +1 -1
- package/dist/store/kuzu_store.d.ts +59 -0
- package/dist/store/kuzu_store.d.ts.map +1 -1
- package/dist/store/kuzu_store.js +124 -5
- package/dist/store/kuzu_store.js.map +1 -1
- package/dist/store/output_folder.d.ts +43 -0
- package/dist/store/output_folder.d.ts.map +1 -0
- package/dist/store/output_folder.js +61 -0
- package/dist/store/output_folder.js.map +1 -0
- package/dist/verify/project_verifier.d.ts +85 -0
- package/dist/verify/project_verifier.d.ts.map +1 -0
- package/dist/verify/project_verifier.js +138 -0
- package/dist/verify/project_verifier.js.map +1 -0
- package/dotclaude_folder/commands/code-graph-interview.md +123 -0
- package/dotclaude_folder/commands/code-graph-optimize.md +65 -0
- package/{skills/ts-knowledge-graph → dotclaude_folder/skills/code-graph-query}/SKILL.md +6 -6
- package/package.json +99 -10
- package/.env-sample +0 -34
- package/contribs/web_visualisation/README.md +0 -55
- package/contribs/web_visualisation/web/css/style.css +0 -115
- package/contribs/web_visualisation/web/data/.gitignore +0 -2
- package/contribs/web_visualisation/web/index.html +0 -58
- package/contribs/web_visualisation/web/js/app.js +0 -364
- package/dist/agent/agent-tools.d.ts +0 -13
- package/dist/agent/agent-tools.d.ts.map +0 -1
- package/dist/agent/agent-tools.js +0 -153
- package/dist/agent/agent-tools.js.map +0 -1
- package/dist/agent/agent_tools.d.ts +0 -13
- package/dist/agent/agent_tools.d.ts.map +0 -1
- package/dist/agent/agent_tools.js +0 -153
- package/dist/agent/agent_tools.js.map +0 -1
- package/dist/agent/code-editor.d.ts +0 -18
- package/dist/agent/code-editor.d.ts.map +0 -1
- package/dist/agent/code-editor.js +0 -43
- package/dist/agent/code-editor.js.map +0 -1
- package/dist/agent/code_editor.d.ts +0 -18
- package/dist/agent/code_editor.d.ts.map +0 -1
- package/dist/agent/code_editor.js +0 -43
- package/dist/agent/code_editor.js.map +0 -1
- package/dist/agent/optimizer-agent.d.ts +0 -30
- package/dist/agent/optimizer-agent.d.ts.map +0 -1
- package/dist/agent/optimizer-agent.js +0 -97
- package/dist/agent/optimizer-agent.js.map +0 -1
- package/dist/agent/optimizer_agent.d.ts +0 -30
- package/dist/agent/optimizer_agent.d.ts.map +0 -1
- package/dist/agent/optimizer_agent.js +0 -97
- package/dist/agent/optimizer_agent.js.map +0 -1
- package/dist/agent/verifier.d.ts +0 -9
- package/dist/agent/verifier.d.ts.map +0 -1
- package/dist/agent/verifier.js +0 -19
- package/dist/agent/verifier.js.map +0 -1
- package/dist/commands/blast-radius.d.ts +0 -5
- package/dist/commands/blast-radius.d.ts.map +0 -1
- package/dist/commands/blast-radius.js +0 -18
- package/dist/commands/blast-radius.js.map +0 -1
- package/dist/commands/blast_radius.d.ts +0 -5
- package/dist/commands/blast_radius.d.ts.map +0 -1
- package/dist/commands/blast_radius.js +0 -18
- package/dist/commands/blast_radius.js.map +0 -1
- package/dist/commands/calls.d.ts +0 -5
- package/dist/commands/calls.d.ts.map +0 -1
- package/dist/commands/calls.js +0 -7
- package/dist/commands/calls.js.map +0 -1
- package/dist/commands/command-helpers.d.ts +0 -15
- package/dist/commands/command-helpers.d.ts.map +0 -1
- package/dist/commands/command-helpers.js +0 -61
- package/dist/commands/command-helpers.js.map +0 -1
- package/dist/commands/dead-exports.d.ts +0 -5
- package/dist/commands/dead-exports.d.ts.map +0 -1
- package/dist/commands/dead-exports.js +0 -7
- package/dist/commands/dead-exports.js.map +0 -1
- package/dist/commands/dead_exports.d.ts +0 -5
- package/dist/commands/dead_exports.d.ts.map +0 -1
- package/dist/commands/dead_exports.js +0 -7
- package/dist/commands/dead_exports.js.map +0 -1
- package/dist/commands/extract.d.ts +0 -8
- package/dist/commands/extract.d.ts.map +0 -1
- package/dist/commands/extract.js +0 -49
- package/dist/commands/extract.js.map +0 -1
- package/dist/commands/find.d.ts +0 -5
- package/dist/commands/find.d.ts.map +0 -1
- package/dist/commands/find.js +0 -7
- package/dist/commands/find.js.map +0 -1
- package/dist/commands/load.d.ts.map +0 -1
- package/dist/commands/load.js +0 -28
- package/dist/commands/load.js.map +0 -1
- package/dist/commands/neighbors.d.ts +0 -5
- package/dist/commands/neighbors.d.ts.map +0 -1
- package/dist/commands/neighbors.js +0 -17
- package/dist/commands/neighbors.js.map +0 -1
- package/dist/commands/optimize.d.ts +0 -6
- package/dist/commands/optimize.d.ts.map +0 -1
- package/dist/commands/optimize.js +0 -59
- package/dist/commands/optimize.js.map +0 -1
- package/dist/commands/optimize_command.d.ts +0 -6
- package/dist/commands/optimize_command.d.ts.map +0 -1
- package/dist/commands/optimize_command.js +0 -59
- package/dist/commands/optimize_command.js.map +0 -1
- package/dist/commands/references.d.ts +0 -5
- package/dist/commands/references.d.ts.map +0 -1
- package/dist/commands/references.js +0 -17
- package/dist/commands/references.js.map +0 -1
- package/dist/commands/web.d.ts +0 -19
- package/dist/commands/web.d.ts.map +0 -1
- package/dist/commands/web.js +0 -120
- package/dist/commands/web.js.map +0 -1
- package/dist/commands/who-calls.d.ts +0 -5
- package/dist/commands/who-calls.d.ts.map +0 -1
- package/dist/commands/who-calls.js +0 -7
- package/dist/commands/who-calls.js.map +0 -1
- package/dist/commands/who_calls.d.ts +0 -5
- package/dist/commands/who_calls.d.ts.map +0 -1
- package/dist/commands/who_calls.js +0 -7
- package/dist/commands/who_calls.js.map +0 -1
- package/dist/extract/graph-builder.d.ts +0 -16
- package/dist/extract/graph-builder.d.ts.map +0 -1
- package/dist/extract/graph-builder.js +0 -39
- package/dist/extract/graph-builder.js.map +0 -1
- package/dist/extract/node-id.d.ts +0 -8
- package/dist/extract/node-id.d.ts.map +0 -1
- package/dist/extract/node-id.js +0 -22
- package/dist/extract/node-id.js.map +0 -1
- package/dist/extract/project-loader.d.ts +0 -5
- package/dist/extract/project-loader.d.ts.map +0 -1
- package/dist/extract/project-loader.js +0 -19
- package/dist/extract/project-loader.js.map +0 -1
- package/dist/extract/semantic-extractor.d.ts +0 -22
- package/dist/extract/semantic-extractor.d.ts.map +0 -1
- package/dist/extract/semantic-extractor.js +0 -254
- package/dist/extract/semantic-extractor.js.map +0 -1
- package/dist/extract/structural-extractor.d.ts +0 -18
- package/dist/extract/structural-extractor.d.ts.map +0 -1
- package/dist/extract/structural-extractor.js +0 -97
- package/dist/extract/structural-extractor.js.map +0 -1
- package/dist/query/graph-query.d.ts +0 -28
- package/dist/query/graph-query.d.ts.map +0 -1
- package/dist/query/graph-query.js +0 -93
- package/dist/query/graph-query.js.map +0 -1
- package/dist/store/jsonl-reader.d.ts +0 -11
- package/dist/store/jsonl-reader.d.ts.map +0 -1
- package/dist/store/jsonl-reader.js +0 -19
- package/dist/store/jsonl-reader.js.map +0 -1
- package/dist/store/jsonl-store.d.ts +0 -7
- package/dist/store/jsonl-store.d.ts.map +0 -1
- package/dist/store/jsonl-store.js +0 -13
- package/dist/store/jsonl-store.js.map +0 -1
- package/dist/store/kuzu-store.d.ts +0 -14
- package/dist/store/kuzu-store.d.ts.map +0 -1
- package/dist/store/kuzu-store.js +0 -52
- package/dist/store/kuzu-store.js.map +0 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { EDGE_KINDS } from '../schema/edge.js';
|
|
2
|
+
import { CommunityDetector, DEFAULT_COMMUNITY_OPTIONS } from './community_detector.js';
|
|
3
|
+
import { CommunityLabeler } from './community_labeler.js';
|
|
4
|
+
/** Namespaced key under which a node's community index is stored on its metadata. */
|
|
5
|
+
export const COMMUNITY_METADATA_KEY = 'community';
|
|
6
|
+
/** Namespaced key under which a node's human-readable community label is stored on its metadata. */
|
|
7
|
+
export const COMMUNITY_LABEL_METADATA_KEY = 'communityLabel';
|
|
8
|
+
/** Graph-level metadata key under which the clustering manifest is recorded. */
|
|
9
|
+
export const CLUSTERING_MANIFEST_KEY = 'clustering';
|
|
10
|
+
/** The runtime call-graph edge kind (`enrich`); its weight comes from sample count, not call-site count. */
|
|
11
|
+
const RUNTIME_CALL_EDGE_KIND = 'CALLS_RUNTIME';
|
|
12
|
+
/**
|
|
13
|
+
* Orchestrates a clustering pass over a loaded graph: read the weighted edges,
|
|
14
|
+
* detect communities with {@link CommunityDetector}, and attach the community
|
|
15
|
+
* index onto each node's metadata. Mirrors {@link RuntimeEnricher} — the pure
|
|
16
|
+
* algorithm lives in {@link CommunityDetector}; this class owns the store I/O.
|
|
17
|
+
*
|
|
18
|
+
* Existing node metadata is preserved; only the `community` key is written, so
|
|
19
|
+
* re-running is idempotent for an unchanged graph.
|
|
20
|
+
*/
|
|
21
|
+
export class GraphClusterer {
|
|
22
|
+
static async cluster(store, weights = {}, options = DEFAULT_COMMUNITY_OPTIONS) {
|
|
23
|
+
const edges = await GraphClusterer.readWeightedEdges(store, weights);
|
|
24
|
+
const result = CommunityDetector.detect(edges, options);
|
|
25
|
+
const nodes = await store.readNodes();
|
|
26
|
+
const labels = CommunityLabeler.label({
|
|
27
|
+
communityOf: result.communityOf,
|
|
28
|
+
nodes: nodes.map((node) => ({ id: node.id, name: node.name, kind: node.kind, filePath: node.filePath })),
|
|
29
|
+
edges,
|
|
30
|
+
});
|
|
31
|
+
const updates = [];
|
|
32
|
+
for (const node of nodes) {
|
|
33
|
+
const index = result.communityOf.get(node.id);
|
|
34
|
+
if (index === undefined) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
updates.push({
|
|
38
|
+
id: node.id,
|
|
39
|
+
metadata: {
|
|
40
|
+
...node.metadata,
|
|
41
|
+
[COMMUNITY_METADATA_KEY]: index,
|
|
42
|
+
[COMMUNITY_LABEL_METADATA_KEY]: labels.get(index) ?? `community ${index}`,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
await store.writeNodeMetadata(updates);
|
|
47
|
+
const orderedLabels = GraphClusterer.orderLabels(labels, result.communityCount);
|
|
48
|
+
await store.writeGraphMeta(CLUSTERING_MANIFEST_KEY, {
|
|
49
|
+
algorithm: 'leiden-cpm',
|
|
50
|
+
resolution: options.resolution,
|
|
51
|
+
communityCount: result.communityCount,
|
|
52
|
+
quality: result.quality,
|
|
53
|
+
labels: orderedLabels,
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
nodesAssigned: updates.length,
|
|
57
|
+
communityCount: result.communityCount,
|
|
58
|
+
quality: result.quality,
|
|
59
|
+
resolution: options.resolution,
|
|
60
|
+
sizes: result.sizes,
|
|
61
|
+
labels: orderedLabels,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Flattens the per-index label map into an array aligned with community indices `0..count-1`. */
|
|
65
|
+
static orderLabels(labels, count) {
|
|
66
|
+
return Array.from({ length: count }, (_, index) => labels.get(index) ?? `community ${index}`);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Reads every edge whose kind carries a positive weight, resolving each to a
|
|
70
|
+
* {@link WeightedEdge} whose weight is the kind's coefficient times the edge's
|
|
71
|
+
* call-site `count`.
|
|
72
|
+
*/
|
|
73
|
+
static async readWeightedEdges(store, weights) {
|
|
74
|
+
const kinds = EDGE_KINDS.filter((kind) => (weights[kind] ?? 0) > 0);
|
|
75
|
+
if (kinds.length === 0) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
const kindList = `[${kinds.map((kind) => `'${kind}'`).join(', ')}]`;
|
|
79
|
+
const rows = await store.run(`MATCH (source:GraphNode)-[e:Edge]->(target:GraphNode)
|
|
80
|
+
WHERE e.kind IN ${kindList}
|
|
81
|
+
RETURN source.id AS fromId, target.id AS toId, e.kind AS kind, e.metadata AS metadata`);
|
|
82
|
+
// Runtime call edges carry a sample count that dwarfs static call-site counts;
|
|
83
|
+
// normalize them by the hottest runtime edge so the runtime coefficient stays
|
|
84
|
+
// on the same scale as the static ones — the peak call path contributes its
|
|
85
|
+
// full coefficient, cooler paths proportionally less.
|
|
86
|
+
const maxRuntimeSamples = rows.reduce((max, row) => String(row.kind) === RUNTIME_CALL_EDGE_KIND
|
|
87
|
+
? Math.max(max, GraphClusterer.edgeSamples(row.metadata))
|
|
88
|
+
: max, 0);
|
|
89
|
+
return rows.map((row) => {
|
|
90
|
+
const kind = String(row.kind);
|
|
91
|
+
const coefficient = weights[kind] ?? 0;
|
|
92
|
+
const strength = kind === RUNTIME_CALL_EDGE_KIND
|
|
93
|
+
? (maxRuntimeSamples > 0 ? GraphClusterer.edgeSamples(row.metadata) / maxRuntimeSamples : 0)
|
|
94
|
+
: GraphClusterer.callCount(row.metadata);
|
|
95
|
+
return {
|
|
96
|
+
from: String(row.fromId),
|
|
97
|
+
to: String(row.toId),
|
|
98
|
+
weight: coefficient * strength,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/** Decodes an edge's call-site `count`, defaulting to 1 (the minimum the builder records). */
|
|
103
|
+
static callCount(value) {
|
|
104
|
+
const count = GraphClusterer.parseMetadata(value).count;
|
|
105
|
+
return typeof count === 'number' && count > 0 ? count : 1;
|
|
106
|
+
}
|
|
107
|
+
/** Decodes a runtime call edge's `samples` weight, defaulting to 0. */
|
|
108
|
+
static edgeSamples(value) {
|
|
109
|
+
const samples = GraphClusterer.parseMetadata(value).samples;
|
|
110
|
+
return typeof samples === 'number' && samples > 0 ? samples : 0;
|
|
111
|
+
}
|
|
112
|
+
/** Decodes the JSON `metadata` column into a record; a missing, empty, or malformed value yields an empty record. */
|
|
113
|
+
static parseMetadata(value) {
|
|
114
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const parsed = JSON.parse(value);
|
|
119
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : {};
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=graph_clusterer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph_clusterer.js","sourceRoot":"","sources":["../../src/cluster/graph_clusterer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export declare class BenchmarkCommand {
|
|
3
|
+
static register(program: Command): void;
|
|
4
|
+
private static run;
|
|
5
|
+
private static readBaselineMedian;
|
|
6
|
+
private static writeBaseline;
|
|
7
|
+
private static print;
|
|
8
|
+
private static printDelta;
|
|
9
|
+
private static round;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=benchmark_command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"benchmark_command.d.ts","sourceRoot":"","sources":["../../src/commands/benchmark_command.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBpC,qBAAa,gBAAgB;IAC5B,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;mBAuBlB,GAAG;mBAwBH,kBAAkB;mBAKlB,aAAa;IAMlC,OAAO,CAAC,MAAM,CAAC,KAAK;IAsBpB,OAAO,CAAC,MAAM,CAAC,UAAU;IAQzB,OAAO,CAAC,MAAM,CAAC,KAAK;CAGpB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { BenchmarkStats } from '../benchmark/benchmark_stats.js';
|
|
7
|
+
import { NodeBenchmark } from '../benchmark/node_benchmark.js';
|
|
8
|
+
import { KuzuStore } from '../store/kuzu_store.js';
|
|
9
|
+
import { OutputFolder } from '../store/output_folder.js';
|
|
10
|
+
import { CommandHelpers } from './command_helpers.js';
|
|
11
|
+
const METRICS = ['self-time', 'inclusive-time', 'samples'];
|
|
12
|
+
/** A saved baseline is just a prior report; we only need its median back. */
|
|
13
|
+
const BaselineFileSchema = z.object({ stats: z.object({ median: z.number() }) });
|
|
14
|
+
export class BenchmarkCommand {
|
|
15
|
+
static register(program) {
|
|
16
|
+
const command = program
|
|
17
|
+
.command('benchmark')
|
|
18
|
+
.description('measure a target node\'s runtime metric over N profiling runs and report the median + spread (advisory)')
|
|
19
|
+
.argument('<target>', 'symbol name to benchmark; resolved against the current graph like `find`')
|
|
20
|
+
.requiredOption('--workload <path>', 'repeatable workload entry (.ts/.js) that exercises the target under load');
|
|
21
|
+
CommandHelpers.addOutputFolderOption(command)
|
|
22
|
+
.option('-r, --root <path>', 'project root the profile paths resolve against', process.cwd())
|
|
23
|
+
.option('--by <metric>', `metric: ${METRICS.join(', ')}`, 'self-time')
|
|
24
|
+
.option('--runs <n>', 'number of profiling runs to take the median of', '5')
|
|
25
|
+
.option('--baseline', 'compare against the saved baseline for <target> (advisory delta)', false)
|
|
26
|
+
.option('--save-baseline', 'save this run as the baseline for <target>', false)
|
|
27
|
+
.option('--json', 'emit the benchmark report as JSON', false)
|
|
28
|
+
.action(async (target, options) => {
|
|
29
|
+
if (METRICS.includes(options.by) === false) {
|
|
30
|
+
console.error(chalk.red(`unknown metric '${options.by}' — choose one of: ${METRICS.join(', ')}`));
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
await BenchmarkCommand.run(target, options);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
static async run(target, options) {
|
|
38
|
+
const folder = new OutputFolder(options.outputFolder);
|
|
39
|
+
const baselineFile = folder.baselinePath(target);
|
|
40
|
+
const store = new KuzuStore(folder.dbPath);
|
|
41
|
+
await store.initSchema();
|
|
42
|
+
const profileDir = await mkdtemp(join(tmpdir(), 'tkg-bench-'));
|
|
43
|
+
try {
|
|
44
|
+
const baselineMedian = options.baseline === true ? await BenchmarkCommand.readBaselineMedian(baselineFile) : undefined;
|
|
45
|
+
const config = { workload: resolve(options.workload), root: resolve(options.root), profileDir };
|
|
46
|
+
const report = await NodeBenchmark.measure(store, { target, metric: options.by, runs: Number(options.runs), baselineMedian }, () => NodeBenchmark.profileAndEnrich(store, config));
|
|
47
|
+
if (options.saveBaseline === true) {
|
|
48
|
+
await BenchmarkCommand.writeBaseline(baselineFile, report);
|
|
49
|
+
}
|
|
50
|
+
BenchmarkCommand.print(report, options.json === true);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await store.close();
|
|
54
|
+
await rm(profileDir, { recursive: true, force: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
static async readBaselineMedian(file) {
|
|
58
|
+
const parsed = BaselineFileSchema.parse(JSON.parse(await readFile(resolve(file), 'utf8')));
|
|
59
|
+
return parsed.stats.median;
|
|
60
|
+
}
|
|
61
|
+
static async writeBaseline(file, report) {
|
|
62
|
+
const path = resolve(file);
|
|
63
|
+
await mkdir(dirname(path), { recursive: true });
|
|
64
|
+
await writeFile(path, JSON.stringify(report, null, 2), 'utf8');
|
|
65
|
+
}
|
|
66
|
+
static print(report, json) {
|
|
67
|
+
if (json === true) {
|
|
68
|
+
console.log(JSON.stringify(report, null, 2));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const target = report.target;
|
|
72
|
+
console.log(chalk.bold(target.name) + chalk.gray(` ${target.kind} · ${target.filePath}:${target.startLine}`));
|
|
73
|
+
const stats = report.stats;
|
|
74
|
+
const median = chalk.cyan(`${BenchmarkCommand.round(stats.median)} ${report.unit}`);
|
|
75
|
+
const spread = chalk.gray(`(min ${BenchmarkCommand.round(stats.min)} · max ${BenchmarkCommand.round(stats.max)} · ` +
|
|
76
|
+
`spread ${BenchmarkCommand.round(stats.spread)} · mean ${BenchmarkCommand.round(stats.mean)} · ${stats.runs} runs)`);
|
|
77
|
+
console.log(` ${chalk.bold(report.metric)} median ${median} ${spread}`);
|
|
78
|
+
if (report.delta !== null) {
|
|
79
|
+
BenchmarkCommand.printDelta(report, report.delta);
|
|
80
|
+
}
|
|
81
|
+
console.log(chalk.yellow(` ${report.advisory}`));
|
|
82
|
+
}
|
|
83
|
+
static printDelta(report, delta) {
|
|
84
|
+
const direction = BenchmarkStats.direction(delta, report.stats.spread);
|
|
85
|
+
const colour = direction === 'improved' ? chalk.green : direction === 'regressed' ? chalk.red : chalk.gray;
|
|
86
|
+
const absolute = `${delta.absolute > 0 ? '+' : ''}${BenchmarkCommand.round(delta.absolute)} ${report.unit}`;
|
|
87
|
+
const percent = BenchmarkStats.formatPercent(delta.percent);
|
|
88
|
+
console.log(` ${chalk.bold('Δ vs baseline')} ${colour(`${absolute} (${percent})`)} ${colour(direction)}`);
|
|
89
|
+
}
|
|
90
|
+
static round(value) {
|
|
91
|
+
return Math.round(value * 1000) / 1000;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=benchmark_command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"benchmark_command.js","sourceRoot":"","sources":["../../src/commands/benchmark_command.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blast_radius_command.d.ts","sourceRoot":"","sources":["../../src/commands/blast_radius_command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"blast_radius_command.d.ts","sourceRoot":"","sources":["../../src/commands/blast_radius_command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,qBAAa,kBAAkB;IAC9B,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;CAcvC"}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OutputFolder } from '../store/output_folder.js';
|
|
2
|
+
import { CommandHelpers } from './command_helpers.js';
|
|
2
3
|
export class BlastRadiusCommand {
|
|
3
4
|
static register(program) {
|
|
4
|
-
program
|
|
5
|
+
const command = program
|
|
5
6
|
.command('blast-radius')
|
|
6
7
|
.description('list every symbol transitively impacted by changing <id>')
|
|
7
|
-
.argument('<id>', 'node id to analyse')
|
|
8
|
-
|
|
9
|
-
.option('--depth <n>', 'maximum traversal depth', '10')
|
|
8
|
+
.argument('<id>', 'node id to analyse');
|
|
9
|
+
CommandHelpers.addOutputFolderOption(command)
|
|
10
|
+
.option('--depth <n>', 'maximum traversal depth (clamped to 1–30)', '10')
|
|
10
11
|
.option('--json', 'emit raw JSON', false)
|
|
11
12
|
.action(async (id, options) => {
|
|
12
|
-
await CommandHelpers.withQuery(options.
|
|
13
|
+
await CommandHelpers.withQuery(new OutputFolder(options.outputFolder), async (query) => {
|
|
13
14
|
CommandHelpers.printRefs(await query.blastRadius(id, Number(options.depth)), options.json === true);
|
|
14
15
|
});
|
|
15
16
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blast_radius_command.js","sourceRoot":"","sources":["../../src/commands/blast_radius_command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"blast_radius_command.js","sourceRoot":"","sources":["../../src/commands/blast_radius_command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAgB,MAAM,sBAAsB,CAAC;AAMpE,MAAM,OAAO,kBAAkB;IAC9B,MAAM,CAAC,QAAQ,CAAC,OAAgB;QAC/B,MAAM,OAAO,GAAG,OAAO;aACrB,OAAO,CAAC,cAAc,CAAC;aACvB,WAAW,CAAC,0DAA0D,CAAC;aACvE,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;QACzC,cAAc,CAAC,qBAAqB,CAAC,OAAO,CAAC;aAC3C,MAAM,CAAC,aAAa,EAAE,2CAA2C,EAAE,IAAI,CAAC;aACxE,MAAM,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,CAAC;aACxC,MAAM,CAAC,KAAK,EAAE,EAAU,EAAE,OAAqB,EAAE,EAAE;YACnD,MAAM,cAAc,CAAC,SAAS,CAAC,IAAI,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;gBACtF,cAAc,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YACrG,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;CACD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster_command.d.ts","sourceRoot":"","sources":["../../src/commands/cluster_command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAcpC,qBAAa,cAAc;IAC1B,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;mBAYlB,GAAG;IAoBxB,OAAO,CAAC,MAAM,CAAC,KAAK;CAcpB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { CLUSTER_EDGE_WEIGHTS } from '../cluster/cluster_weights.js';
|
|
3
|
+
import { DEFAULT_COMMUNITY_OPTIONS } from '../cluster/community_detector.js';
|
|
4
|
+
import { GraphClusterer } from '../cluster/graph_clusterer.js';
|
|
5
|
+
import { KuzuStore } from '../store/kuzu_store.js';
|
|
6
|
+
import { OutputFolder } from '../store/output_folder.js';
|
|
7
|
+
import { CommandHelpers } from './command_helpers.js';
|
|
8
|
+
export class ClusterCommand {
|
|
9
|
+
static register(program) {
|
|
10
|
+
const command = program
|
|
11
|
+
.command('cluster')
|
|
12
|
+
.description('detect code communities with the Leiden algorithm and attach metadata.community onto nodes');
|
|
13
|
+
CommandHelpers.addOutputFolderOption(command)
|
|
14
|
+
.option('--resolution <n>', 'CPM resolution (higher → more, smaller communities)', String(DEFAULT_COMMUNITY_OPTIONS.resolution))
|
|
15
|
+
.option('--json', 'emit the clustering report as JSON', false)
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
await ClusterCommand.run(options);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
static async run(options) {
|
|
21
|
+
const resolution = Number(options.resolution);
|
|
22
|
+
if (Number.isFinite(resolution) === false || resolution <= 0) {
|
|
23
|
+
console.error(chalk.red(`invalid --resolution '${options.resolution}' — expected a positive number`));
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const store = new KuzuStore(new OutputFolder(options.outputFolder).dbPath);
|
|
28
|
+
await store.initSchema();
|
|
29
|
+
try {
|
|
30
|
+
const report = await GraphClusterer.cluster(store, CLUSTER_EDGE_WEIGHTS, {
|
|
31
|
+
...DEFAULT_COMMUNITY_OPTIONS,
|
|
32
|
+
resolution,
|
|
33
|
+
});
|
|
34
|
+
ClusterCommand.print(report, options.json === true);
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await store.close();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
static print(report, json) {
|
|
41
|
+
if (json === true) {
|
|
42
|
+
console.log(JSON.stringify(report, null, 2));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (report.nodesAssigned === 0) {
|
|
46
|
+
console.log(chalk.yellow('! no weighted edges in graph — run `extract --semantic` and `load` first.'));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
console.log(chalk.green(`✓ assigned ${report.nodesAssigned} node(s) to ${report.communityCount} communities`));
|
|
50
|
+
console.log(` resolution ${report.resolution}, CPM quality ${report.quality.toFixed(4)}`);
|
|
51
|
+
const top = report.labels.slice(0, 8).map((label, index) => `${label} (${report.sizes[index]})`);
|
|
52
|
+
console.log(` largest communities: ${top.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=cluster_command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster_command.js","sourceRoot":"","sources":["../../src/commands/cluster_command.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { GraphQuery, NeighborRef, SymbolRef } from '../query/graph_query.js';
|
|
3
|
-
|
|
4
|
-
export declare const
|
|
3
|
+
import { OutputFolder } from '../store/output_folder.js';
|
|
4
|
+
export declare const DEFAULT_OUTPUT_FOLDER = "./.ts_knowledge_graph";
|
|
5
5
|
export type QueryOptions = {
|
|
6
|
-
|
|
6
|
+
outputFolder: string;
|
|
7
7
|
json?: boolean;
|
|
8
8
|
};
|
|
9
9
|
export declare class CommandHelpers {
|
|
10
|
+
/**
|
|
11
|
+
* Register the shared `-o, --output-folder` option — the single source of
|
|
12
|
+
* every output path. Returns the command so callers can keep chaining.
|
|
13
|
+
*/
|
|
14
|
+
static addOutputFolderOption(command: Command): Command;
|
|
10
15
|
static registerSymbolQuery(program: Command, name: string, argSpec: string, description: string, run: (query: GraphQuery, arg: string) => Promise<SymbolRef[]>): void;
|
|
11
|
-
static withQuery(
|
|
16
|
+
static withQuery(folder: OutputFolder, fn: (query: GraphQuery) => Promise<void>): Promise<void>;
|
|
12
17
|
static printRefs(refs: SymbolRef[], json: boolean): void;
|
|
13
18
|
static printNeighbors(neighbors: NeighborRef[], json: boolean): void;
|
|
14
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command_helpers.d.ts","sourceRoot":"","sources":["../../src/commands/command_helpers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"command_helpers.d.ts","sourceRoot":"","sources":["../../src/commands/command_helpers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAE7E,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAE7D,MAAM,MAAM,YAAY,GAAG;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,qBAAa,cAAc;IAC1B;;;OAGG;IACH,MAAM,CAAC,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;IAQvD,MAAM,CAAC,mBAAmB,CACzB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC,GAC3D,IAAI;WAaM,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAUrG,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;IAexD,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;CAepE"}
|
|
@@ -1,25 +1,30 @@
|
|
|
1
|
-
import { resolve } from 'node:path';
|
|
2
1
|
import chalk from 'chalk';
|
|
3
2
|
import { GraphQuery } from '../query/graph_query.js';
|
|
4
3
|
import { KuzuStore } from '../store/kuzu_store.js';
|
|
5
|
-
|
|
6
|
-
export const
|
|
4
|
+
import { OutputFolder } from '../store/output_folder.js';
|
|
5
|
+
export const DEFAULT_OUTPUT_FOLDER = './.ts_knowledge_graph';
|
|
7
6
|
export class CommandHelpers {
|
|
7
|
+
/**
|
|
8
|
+
* Register the shared `-o, --output-folder` option — the single source of
|
|
9
|
+
* every output path. Returns the command so callers can keep chaining.
|
|
10
|
+
*/
|
|
11
|
+
static addOutputFolderOption(command) {
|
|
12
|
+
return command.option('-o, --output-folder <dir>', 'output folder holding graph/, graph.kuzu, prof/, bench/', DEFAULT_OUTPUT_FOLDER);
|
|
13
|
+
}
|
|
8
14
|
static registerSymbolQuery(program, name, argSpec, description, run) {
|
|
9
15
|
const command = program.command(argSpec === '' ? name : `${name} ${argSpec}`).description(description);
|
|
10
|
-
command
|
|
11
|
-
.option('-d, --db <path>', 'Kùzu database path', DEFAULT_DB_PATH)
|
|
16
|
+
CommandHelpers.addOutputFolderOption(command)
|
|
12
17
|
.option('--json', 'emit raw JSON', false)
|
|
13
18
|
.action(async (...args) => {
|
|
14
19
|
const options = args[args.length - 2];
|
|
15
20
|
const arg = argSpec === '' ? '' : args[0];
|
|
16
|
-
await CommandHelpers.withQuery(options.
|
|
21
|
+
await CommandHelpers.withQuery(new OutputFolder(options.outputFolder), async (query) => {
|
|
17
22
|
CommandHelpers.printRefs(await run(query, arg), options.json === true);
|
|
18
23
|
});
|
|
19
24
|
});
|
|
20
25
|
}
|
|
21
|
-
static async withQuery(
|
|
22
|
-
const store = new KuzuStore(
|
|
26
|
+
static async withQuery(folder, fn) {
|
|
27
|
+
const store = new KuzuStore(folder.dbPath);
|
|
23
28
|
await store.initSchema();
|
|
24
29
|
try {
|
|
25
30
|
await fn(new GraphQuery(store));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command_helpers.js","sourceRoot":"","sources":["../../src/commands/command_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"command_helpers.js","sourceRoot":"","sources":["../../src/commands/command_helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export declare class CostCommand {
|
|
3
|
+
static register(program: Command): void;
|
|
4
|
+
private static printRanking;
|
|
5
|
+
private static printAttribution;
|
|
6
|
+
private static printFlows;
|
|
7
|
+
private static formatAmount;
|
|
8
|
+
private static formatShare;
|
|
9
|
+
private static formatCoverage;
|
|
10
|
+
/** A one-line gloss under the ranking: what coverage means, or how to record it when missing. */
|
|
11
|
+
private static printCoverageHint;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=cost_command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost_command.d.ts","sourceRoot":"","sources":["../../src/commands/cost_command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiBpC,qBAAa,WAAW;IACvB,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAmCvC,OAAO,CAAC,MAAM,CAAC,YAAY;IA+B3B,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAmC/B,OAAO,CAAC,MAAM,CAAC,UAAU;IAazB,OAAO,CAAC,MAAM,CAAC,YAAY;IAK3B,OAAO,CAAC,MAAM,CAAC,WAAW;IAI1B,OAAO,CAAC,MAAM,CAAC,cAAc;IAI7B,iGAAiG;IACjG,OAAO,CAAC,MAAM,CAAC,iBAAiB;CAUhC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { OutputFolder } from '../store/output_folder.js';
|
|
3
|
+
import { CommandHelpers } from './command_helpers.js';
|
|
4
|
+
const METRICS = ['self-time', 'samples'];
|
|
5
|
+
const FLOWS = ['static', 'runtime'];
|
|
6
|
+
const NO_RUNTIME_NOTICE = '! no runtime data in graph — run `enrich` first. Inclusive cost needs measured self cost to propagate.';
|
|
7
|
+
const FELLBACK_NOTICE = '! no runtime call edges — run `enrich` first. Propagating along the static call graph instead.';
|
|
8
|
+
export class CostCommand {
|
|
9
|
+
static register(program) {
|
|
10
|
+
const command = program
|
|
11
|
+
.command('cost')
|
|
12
|
+
.description('propagate runtime self cost into inclusive cost and rank nodes by share of total')
|
|
13
|
+
.argument('[id]', 'node id to break down causally; omit to rank the whole graph');
|
|
14
|
+
CommandHelpers.addOutputFolderOption(command)
|
|
15
|
+
.option('--by <metric>', `cost metric: ${METRICS.join(', ')}`, 'self-time')
|
|
16
|
+
.option('--edges <graph>', `call graph to propagate along: ${FLOWS.join(', ')}`, 'static')
|
|
17
|
+
.option('--limit <n>', 'maximum number of ranked nodes to return', '20')
|
|
18
|
+
.option('--json', 'emit raw JSON', false)
|
|
19
|
+
.action(async (id, options) => {
|
|
20
|
+
if (options.by !== undefined && METRICS.includes(options.by) === false) {
|
|
21
|
+
console.error(chalk.red(`unknown metric '${options.by}' — choose one of: ${METRICS.join(', ')}`));
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (options.edges !== undefined && FLOWS.includes(options.edges) === false) {
|
|
26
|
+
console.error(chalk.red(`unknown call graph '${options.edges}' — choose one of: ${FLOWS.join(', ')}`));
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const by = options.by;
|
|
31
|
+
const edges = options.edges;
|
|
32
|
+
await CommandHelpers.withQuery(new OutputFolder(options.outputFolder), async (query) => {
|
|
33
|
+
if (id === undefined) {
|
|
34
|
+
const report = await query.costRanking({ by, edges, limit: Number(options.limit) });
|
|
35
|
+
CostCommand.printRanking(report, options.json === true);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const attribution = await query.costAttribution(id, { by, edges });
|
|
39
|
+
CostCommand.printAttribution(attribution, options.json === true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
static printRanking(report, json) {
|
|
44
|
+
if (json === true) {
|
|
45
|
+
console.log(JSON.stringify(report, null, 2));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (report.enriched === false) {
|
|
49
|
+
console.log(chalk.yellow(NO_RUNTIME_NOTICE));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (report.fellBack === true) {
|
|
53
|
+
console.log(chalk.yellow(FELLBACK_NOTICE));
|
|
54
|
+
}
|
|
55
|
+
const total = CostCommand.formatAmount(report.metric, report.totalSelf);
|
|
56
|
+
const coverage = CostCommand.formatCoverage(report.coverage);
|
|
57
|
+
const flow = report.edges === 'runtime' ? ' along the runtime call graph' : '';
|
|
58
|
+
console.log(chalk.bold(`Inclusive cost by ${report.metric}${flow}`) + chalk.gray(` (total self ${total} · coverage ${coverage})`));
|
|
59
|
+
if (report.nodes.length === 0) {
|
|
60
|
+
console.log(chalk.yellow('(no results)'));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
report.nodes.forEach((node, index) => {
|
|
64
|
+
const rank = chalk.gray(`${String(index + 1).padStart(2)}.`);
|
|
65
|
+
const inclusive = chalk.cyan(CostCommand.formatAmount(report.metric, node.inclusiveCost).padStart(14));
|
|
66
|
+
const share = chalk.green(CostCommand.formatShare(node.shareOfTotal).padStart(7));
|
|
67
|
+
const mark = node.cyclic === true ? chalk.magenta(' ↺') : '';
|
|
68
|
+
console.log(`${rank} ${inclusive} ${share} ${chalk.gray(node.kind.padEnd(10))} ${chalk.bold(node.name)}${mark} ${chalk.gray(`${node.filePath}:${node.startLine}`)}`);
|
|
69
|
+
});
|
|
70
|
+
console.log(chalk.gray(`\n${report.nodes.length} node(s) · share is of total self cost · ↺ = in a call cycle`));
|
|
71
|
+
CostCommand.printCoverageHint(report.coverage, report.metric);
|
|
72
|
+
}
|
|
73
|
+
static printAttribution(attribution, json) {
|
|
74
|
+
if (json === true) {
|
|
75
|
+
console.log(JSON.stringify(attribution, null, 2));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (attribution.node === null) {
|
|
79
|
+
console.log(chalk.yellow('(no such node — resolve an id with `find` first)'));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (attribution.enriched === false) {
|
|
83
|
+
console.log(chalk.yellow(NO_RUNTIME_NOTICE));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (attribution.fellBack === true) {
|
|
87
|
+
console.log(chalk.yellow(FELLBACK_NOTICE));
|
|
88
|
+
}
|
|
89
|
+
const node = attribution.node;
|
|
90
|
+
const metric = attribution.metric;
|
|
91
|
+
console.log(chalk.bold(node.name) + chalk.gray(` ${node.kind} · ${node.filePath}:${node.startLine}`));
|
|
92
|
+
console.log(` self ${chalk.cyan(CostCommand.formatAmount(metric, node.selfCost))}`
|
|
93
|
+
+ ` · inclusive ${chalk.cyan(CostCommand.formatAmount(metric, node.inclusiveCost))}`
|
|
94
|
+
+ ` · share of total ${chalk.green(CostCommand.formatShare(node.shareOfTotal))}`);
|
|
95
|
+
const coverageLine = attribution.coverage === null
|
|
96
|
+
? 'graph coverage: unknown — re-run `enrich` to record it'
|
|
97
|
+
: `graph coverage: ${CostCommand.formatCoverage(attribution.coverage)} of profiled ${metric} is attributed to nodes`;
|
|
98
|
+
console.log(chalk.gray(` ${coverageLine}`));
|
|
99
|
+
if (node.cyclic === true) {
|
|
100
|
+
console.log(chalk.magenta(` ↺ part of a ${node.cycleSize}-node call cycle — inclusive cost is the cycle total, shared by its members`));
|
|
101
|
+
}
|
|
102
|
+
CostCommand.printFlows('Cost flows into (callees)', attribution.callees, metric, '->');
|
|
103
|
+
CostCommand.printFlows('Attributed to callers', attribution.callers, metric, '<-');
|
|
104
|
+
}
|
|
105
|
+
static printFlows(title, flows, metric, arrow) {
|
|
106
|
+
console.log(chalk.bold(`\n${title}`));
|
|
107
|
+
if (flows.length === 0) {
|
|
108
|
+
console.log(chalk.gray(' (none)'));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
for (const flow of flows) {
|
|
112
|
+
const amount = chalk.cyan(CostCommand.formatAmount(metric, flow.amount).padStart(14));
|
|
113
|
+
const share = chalk.green(CostCommand.formatShare(flow.share).padStart(7));
|
|
114
|
+
console.log(` ${chalk.gray(arrow)} ${amount} ${share} ${chalk.bold(flow.name)} ${chalk.gray(`${flow.filePath}:${flow.startLine}`)}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
static formatAmount(metric, value) {
|
|
118
|
+
const rounded = Math.round(value * 1000) / 1000;
|
|
119
|
+
return metric === 'self-time' ? `${rounded} ms` : `${rounded} samples`;
|
|
120
|
+
}
|
|
121
|
+
static formatShare(share) {
|
|
122
|
+
return `${(share * 100).toFixed(1)}%`;
|
|
123
|
+
}
|
|
124
|
+
static formatCoverage(coverage) {
|
|
125
|
+
return coverage === null ? 'unknown' : `${(coverage * 100).toFixed(0)}%`;
|
|
126
|
+
}
|
|
127
|
+
/** A one-line gloss under the ranking: what coverage means, or how to record it when missing. */
|
|
128
|
+
static printCoverageHint(coverage, metric) {
|
|
129
|
+
if (coverage === null) {
|
|
130
|
+
console.log(chalk.gray('coverage unknown — re-run `enrich` to record what fraction of the profile lands on the graph'));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const outsidePct = Math.round((1 - coverage) * 100);
|
|
134
|
+
if (outsidePct > 0) {
|
|
135
|
+
console.log(chalk.gray(`coverage = ${CostCommand.formatCoverage(coverage)} of profiled ${metric} attributed to the graph; ${outsidePct}% fell outside it (dropped by the join)`));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=cost_command.js.map
|