Skip to documentation
SLOP

tiny.smg.scan.scan_metrics

Reference tiny.smg scan scan_metrics

Defined in scan.

API (29)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callsscanscan metrics
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callsscan.CallMetricsIndexinittest; no linktools.smg.src.scan.metricstest: call metrics index capacity mat...scan.CallMetricsIndex.Capacityderive
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest; no linktools.smg.src.scan.metricstest: call metrics index fills and se...test; no linktools.smg.src.scan.metricstest: call metrics index initializati...scan.scan_metricsupdateCallMetricsscan.CallMetricsIndexactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate; no linktools.smg.src.scan.metricscheckCallMetricsIndexInitAllocationFa...test; no linktools.smg.src.scan.metricstest: call metrics index fills and se...test; no linktools.smg.src.scan.metricstest: call metrics index initializati...scan.scan_metricsupdateCallMetricsscan.CallMetricsIndexdeinit
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest; no linktools.smg.src.scan.metricstest: call metrics index fills and se...scan.scan_metricsupdateCallMetricsprivate; no linktools.smg.src.scan.metricsfileMeasuredprivate; no linktools.smg.src.scan.metricssameFailurescan.CallMetricsIndexfill
Static calls · unresolved targets: 1 · external targets: 1.
Called byCallsNo direct callstest; no linktools.smg.src.scan.metricstest: call metrics index fills and se...scan.scan_metricsupdateCallMetricsscan.CallMetricsIndexget
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.scan.metricscheckCallMetricsIndexInitAllocationFa...test; no linktools.smg.src.scan.metricstest: call metrics index fills and se...test; no linktools.smg.src.scan.metricstest: call metrics index initializati...scan.scan_metricsupdateCallMetricsscan.CallMetricsIndex.Capacityderivescan.CallMetricsIndexinit
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsprivate; no linktools.smg.src.scan.metricsopenBraceTextMetricDepthprivate; no linktools.smg.src.scan.pipeline.scancontinueSkippedCFunctionprivate; no linktools.smg.src.scan.pipeline.scanfinishCLineprivate; no linktools.smg.src.scan.pipeline.scanfinishCStructuralLineprivate; no linktools.smg.src.scan.pipeline.scanscanCDefineLine+5 morescan.scan_metricsbraceDelta
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.smg.src.scan.metricscontentHashprivate; no linktools.smg.src.scan.metricsstructureHashscan.scan_metricscDeclarationMetadata
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callersprivate; no linktools.smg.src.scan.metricscomputeCMetricsprivate; no linktools.smg.src.scan.metricscontentHashprivate; no linktools.smg.src.scan.metricsmetricsJsonprivate; no linktools.smg.src.scan.metricsstructureHashscan.scan_metricscFunctionMetadata
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callersprivate; no linktools.smg.src.scan.metricscontentHashprivate; no linktools.smg.src.scan.metricsstructureHashscan.scan_metricscMacroMetadata
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callersprivate; no linktools.smg.src.scan.metricscontentHashprivate; no linktools.smg.src.scan.metricsstructureHashscan.scan_metricscRecordMetadata
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsscan.scan_metricsupdateCallMetricsprivate; no linktools.smg.src.scan.metricswriteResolutionMetricsscan.scan_metricscallMetricsJson
Static calls · unresolved targets: 0 · external targets: 6.
Called byCallsNo direct callsprivate; no linktools.smg.src.scan.metricscountKeywordprivate; no linktools.smg.src.scan.metricscountKeywordOutsideQuotesprivate; no linktools.smg.src.scan.metricstextStructureHashscan.scan_metricsidentByte
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate; no linktools.smg.src.scan.metricspythonTextIndentLevelprivate; no linktools.smg.src.scan.pythonscanLinesscan.scan_metricsindentation
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.scan.scriptenhanceFunctionNodeprivate; no linktools.smg.src.scan.scriptenhanceMethodNodeprivate; no linktools.smg.src.scan.metricscomputeJavaScriptMetricsprivate; no linktools.smg.src.scan.metricscontentHashprivate; no linktools.smg.src.scan.metricsmetricsJsonprivate; no linktools.smg.src.scan.metricsstructureHashscan.scan_metricsjavascriptFunctionMetadata
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsprivate; no linktools.smg.src.scan.metricsupdatePythonTextMetricLineprivate; no linktools.smg.src.scan.pythonblockEndprivate; no linktools.smg.src.scan.pythonbodyLineActionprivate; no linktools.smg.src.scan.pythondocstringprivate; no linktools.smg.src.scan.pythonextractCallsText+2 morescan.scan_metricspythonCommentLine
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.scan.scriptenhanceClassNodeprivate; no linktools.smg.src.scan.scriptenhanceInterfaceNodeprivate; no linktools.smg.src.scan.metricscontentHashprivate; no linktools.smg.src.scan.metricsstructureHashscan.scan_metricsscriptSpanMetadata
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsprivate; no linktools.smg.src.scan.metricscountKeywordOutsideQuotesprivate; no linktools.smg.src.scan.metricscountNeedleOutsideQuotesprivate; no linktools.smg.src.scan.metricstextStructureHashprivate; no linktools.smg.src.scan.pythoncollectCallsInLineprivate; no linktools.smg.src.scan.pythonextractDynamicImportsTextscan.scan_metricsskipQuoted
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.scan.pipeline.scanaddZigAstFunctionNodeprivate; no linktools.smg.src.scan.pipeline.scanaddZigTreeFunctionNodeprivate; no linktools.smg.src.scan.pythonextractFunctionTextprivate; no linktools.smg.src.scan.scriptenhanceMetadataFromTextprivate; no linktools.smg.src.scan.metricscomputeTextMetricsprivate; no linktools.smg.src.scan.metricsmetricsJsonprivate; no linktools.smg.src.scan.metricstextContentHashprivate; no linktools.smg.src.scan.metricstextStructureHashscan.scan_metricstextFunctionMetadata
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsNo direct callersprivate; no linktools.smg.src.scan.metricstextContentHashprivate; no linktools.smg.src.scan.metricstextStructureHashscan.scan_metricstextMacroMetadata
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsprivate; no linktools.smg.src.scan.pipeline.scanaddZigAstContainerNodeprivate; no linktools.smg.src.scan.pipeline.scanscanZigTreeContainerprivate; no linktools.smg.src.scan.pythonaddClassNodeprivate; no linktools.smg.src.scan.scriptenhanceMetadataFromTextprivate; no linktools.smg.src.scan.metricstextContentHashprivate; no linktools.smg.src.scan.metricstextStructureHashscan.scan_metricstextSpanMetadata
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallstest; no linktools.smg.src.scan.metricstest: call metrics finalization trans...test; no linktools.smg.src.scan.metricstest: call metrics replace the owned ...private; no linktools.smg.src.scan.pipeline.scanfinishCachedProjectscanscanPreparedtest; no linktools.smg.src.scan.testtest: javascript fallback ignores con...+33 moremodelpairValuemodelreplaceOwnedPairValuescan.CallMetricsIndexactivatescan.CallMetricsIndexdeinitscan.CallMetricsIndexfill+5 morescan.scan_metricsupdateCallMetrics
Static calls · unresolved targets: 0 · external targets: 5.

Source: tools/smg/src/scan/metrics.zig

zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const pretty_json = @import("pretty").json;const smg = @import("../root.zig");const core = @import("core/root.zig");const graph_mod = smg.graph;const lines_mod = smg.lines;const model = smg.model;const spans = smg.span;const text = smg.text;const tree = smg.tree;const SourceLines = lines_mod.SourceLines;pub fn pythonCommentLine(line: []const u8) bool {    return std.mem.startsWith(u8, line, "#");}pub fn braceDelta(line: []const u8) i64 {    var delta: i64 = 0;    for (line) |byte| {        if (byte == '{') delta += 1;        if (byte == '}') delta -= 1;    }    return delta;}pub fn indentation(line: []const u8) usize {    var spaces: usize = 0;    for (line) |byte| {        if (byte == ' ') spaces += 1 else if (byte == '\t') spaces += 4 else break;    }    return spaces;}fn zigBranch(kind: []const u8) bool {    const branches = [_][]const u8{ "if_statement", "if_expression", "else_clause", "for_statement", "while_statement", "while_expression", "switch_expression", "switch_case", "catch_expression", "try_expression" };    for (branches) |branch| if (std.mem.eql(u8, kind, branch)) return true;    return false;}fn zigNesting(kind: []const u8) bool {    const nested = [_][]const u8{ "if_statement", "if_expression", "for_statement", "while_statement", "while_expression", "switch_expression" };    for (nested) |item| if (std.mem.eql(u8, kind, item)) return true;    return false;}pub fn scriptSpanMetadata(allocator: std.mem.Allocator, scratch: std.mem.Allocator, source: []const u8, node: tree.Node) ![]const model.Pair {    const pairs = try allocator.alloc(model.Pair, 3);    pairs[0] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try contentHash(allocator, source, node) };    pairs[1] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try structureHash(allocator, scratch, node) };    pairs[2] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") };    return pairs;}pub fn javascriptFunctionMetadata(allocator: std.mem.Allocator, scratch: std.mem.Allocator, metric_inputs: std.mem.Allocator, source: []const u8, node: tree.Node) ![]const model.Pair {    const metrics = try computeJavaScriptMetrics(scratch, source, node);    const pairs = try allocator.alloc(model.Pair, 4);    pairs[0] = .{ .key = try allocator.dupe(u8, "metrics"), .value = try metricsJson(metric_inputs, metrics, 0, 0), .json = true };    pairs[1] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try contentHash(allocator, source, node) };    pairs[2] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try structureHash(allocator, scratch, node) };    pairs[3] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") };    return pairs;}fn computeJavaScriptMetrics(allocator: std.mem.Allocator, source: []const u8, node: tree.Node) !FunctionMetrics {    var metrics = FunctionMetrics{ .lines_of_code = tree.endLine(node) - tree.startLine(node) + 1 };    if (tree.childByField(node, "parameters")) |parameters| metrics.parameter_count = countNamedNonComment(parameters);    if (metrics.parameter_count == 0) {        if (tree.childByField(node, "formal_parameters")) |parameters| metrics.parameter_count = countNamedNonComment(parameters);    }    if (tree.childByField(node, "body")) |body| {        var increments: i64 = 0;        const children = tree.childCount(body);        for (0..children) |raw_index| try walkMetricTree(allocator, source, tree.child(body, @intCast(raw_index)), .javascript, 0, &metrics, &increments);        metrics.cyclomatic_complexity = 1 + increments;    }    return metrics;}fn countNamedNonComment(node: tree.Node) i64 {    var count_value: i64 = 0;    const children = tree.childCount(node);    for (0..children) |raw_index| {        const child_node = tree.child(node, @intCast(raw_index));        if (tree.isNamed(child_node) and !hashSkip(tree.kind(child_node))) count_value += 1;    }    return count_value;}fn javascriptBranch(kind: []const u8) bool {    const branches = [_][]const u8{ "if_statement", "else_clause", "for_statement", "while_statement", "do_statement", "for_in_statement", "for_of_statement", "switch_case", "catch_clause", "ternary_expression" };    for (branches) |branch| if (std.mem.eql(u8, kind, branch)) return true;    return false;}fn javascriptNesting(kind: []const u8) bool {    const nested = [_][]const u8{ "if_statement", "for_statement", "while_statement", "do_statement", "for_in_statement", "for_of_statement", "try_statement", "switch_statement" };    for (nested) |item| if (std.mem.eql(u8, kind, item)) return true;    return false;}fn javascriptLogicalOperator(source: []const u8, node: tree.Node) bool {    if (!std.mem.eql(u8, tree.kind(node), "binary_expression")) return false;    const children = tree.childCount(node);    for (0..children) |raw_index| {        const child_node = tree.child(node, @intCast(raw_index));        if (tree.isNamed(child_node)) continue;        const token = tree.slice(source, child_node);        if (std.mem.eql(u8, token, "&&") or std.mem.eql(u8, token, "||") or std.mem.eql(u8, token, "??")) return true;    }    const span = tree.slice(source, node);    return countNeedleOutsideQuotes(span, "&&") != 0 or        countNeedleOutsideQuotes(span, "||") != 0 or        countNeedleOutsideQuotes(span, "??") != 0;}pub fn cFunctionMetadata(allocator: std.mem.Allocator, scratch: std.mem.Allocator, metric_inputs: std.mem.Allocator, source: []const u8, node: tree.Node) ![]const model.Pair {    const metrics = try computeCMetrics(scratch, source, node);    const pairs = try allocator.alloc(model.Pair, 4);    pairs[0] = .{ .key = try allocator.dupe(u8, "metrics"), .value = try metricsJson(metric_inputs, metrics, 0, 0), .json = true };    pairs[1] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try contentHash(allocator, source, node) };    pairs[2] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try structureHash(allocator, scratch, node) };    pairs[3] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") };    return pairs;}pub fn cDeclarationMetadata(allocator: std.mem.Allocator, scratch: std.mem.Allocator, source: []const u8, node: tree.Node) ![]const model.Pair {    const pairs = try allocator.alloc(model.Pair, 3);    pairs[0] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try contentHash(allocator, source, node) };    pairs[1] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try structureHash(allocator, scratch, node) };    pairs[2] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") };    return pairs;}pub fn cRecordMetadata(allocator: std.mem.Allocator, scratch: std.mem.Allocator, source: []const u8, node: tree.Node, c_kind: []const u8) ![]const model.Pair {    const pairs = try allocator.alloc(model.Pair, 4);    pairs[0] = .{ .key = try allocator.dupe(u8, "c_kind"), .value = try allocator.dupe(u8, c_kind) };    pairs[1] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try contentHash(allocator, source, node) };    pairs[2] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try structureHash(allocator, scratch, node) };    pairs[3] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") };    return pairs;}pub fn cMacroMetadata(allocator: std.mem.Allocator, scratch: std.mem.Allocator, source: []const u8, node: tree.Node) ![]const model.Pair {    const pairs = try allocator.alloc(model.Pair, 4);    pairs[0] = .{ .key = try allocator.dupe(u8, "macro"), .value = try allocator.dupe(u8, "true"), .json = true };    pairs[1] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try contentHash(allocator, source, node) };    pairs[2] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try structureHash(allocator, scratch, node) };    pairs[3] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") };    return pairs;}fn computeCMetrics(allocator: std.mem.Allocator, source: []const u8, node: tree.Node) !FunctionMetrics {    var metrics = FunctionMetrics{ .lines_of_code = tree.endLine(node) - tree.startLine(node) + 1 };    if (tree.childByField(node, "parameters")) |parameters| metrics.parameter_count = countNamedNonComment(parameters);    if (metrics.parameter_count == 0) {        if (tree.childByField(node, "formal_parameters")) |parameters| metrics.parameter_count = countNamedNonComment(parameters);    }    if (tree.childByField(node, "body")) |body| {        var increments: i64 = 0;        const children = tree.childCount(body);        for (0..children) |raw_index| try walkMetricTree(allocator, source, tree.child(body, @intCast(raw_index)), .c, 0, &metrics, &increments);        metrics.cyclomatic_complexity = 1 + increments;    }    return metrics;}fn cBranch(kind: []const u8) bool {    const branches = [_][]const u8{ "if_statement", "else_clause", "for_statement", "while_statement", "do_statement", "switch_statement", "case_statement", "conditional_expression" };    for (branches) |branch| if (std.mem.eql(u8, kind, branch)) return true;    return false;}fn cNesting(kind: []const u8) bool {    const nested = [_][]const u8{ "if_statement", "for_statement", "while_statement", "do_statement", "switch_statement" };    for (nested) |item| if (std.mem.eql(u8, kind, item)) return true;    return false;}fn cLogicalOperator(source: []const u8, node: tree.Node) bool {    if (!std.mem.eql(u8, tree.kind(node), "binary_expression")) return false;    const children = tree.childCount(node);    for (0..children) |raw_index| {        const child_node = tree.child(node, @intCast(raw_index));        if (tree.isNamed(child_node)) continue;        const token = tree.slice(source, child_node);        if (std.mem.eql(u8, token, "&&") or std.mem.eql(u8, token, "||")) return true;    }    return false;}pub fn textFunctionMetadata(allocator: std.mem.Allocator, metric_inputs: std.mem.Allocator, source_lines: SourceLines, line: i64, end_line: i64, metric_language: MetricLanguage) ![]const model.Pair {    const source_span = spans.trimmed(source_lines, line, end_line);    const metrics = computeTextMetrics(source_span, metric_language);    const pairs = try allocator.alloc(model.Pair, 4);    pairs[0] = .{ .key = try allocator.dupe(u8, "metrics"), .value = try metricsJson(metric_inputs, metrics, 0, 0), .json = true };    pairs[1] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try textContentHash(allocator, source_span) };    pairs[2] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try textStructureHash(allocator, source_span) };    pairs[3] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") };    return pairs;}pub fn textSpanMetadata(allocator: std.mem.Allocator, source_lines: SourceLines, line: i64, end_line: i64, c_kind: ?[]const u8) ![]const model.Pair {    const source_span = spans.trimmed(source_lines, line, end_line);    const include_kind = c_kind != null and !std.mem.eql(u8, c_kind.?, "class");    const count_value: usize = if (include_kind) 4 else 3;    const pairs = try allocator.alloc(model.Pair, count_value);    var index: usize = 0;    if (include_kind) {        pairs[index] = .{ .key = try allocator.dupe(u8, "c_kind"), .value = try allocator.dupe(u8, c_kind.?) };        index += 1;    }    pairs[index] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try textContentHash(allocator, source_span) };    index += 1;    pairs[index] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try textStructureHash(allocator, source_span) };    index += 1;    pairs[index] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") };    return pairs;}pub fn textMacroMetadata(allocator: std.mem.Allocator, source_lines: SourceLines, line: i64, end_line: i64) ![]const model.Pair {    const source_span = spans.trimmed(source_lines, line, end_line);    const pairs = try allocator.alloc(model.Pair, 4);    pairs[0] = .{ .key = try allocator.dupe(u8, "macro"), .value = try allocator.dupe(u8, "true"), .json = true };    pairs[1] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try textContentHash(allocator, source_span) };    pairs[2] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try textStructureHash(allocator, source_span) };    pairs[3] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") };    return pairs;}fn computeTextMetrics(span: []const u8, metric_language: MetricLanguage) FunctionMetrics {    if (metric_language == .python) return computePythonTextMetrics(span);    var state = textMetricState(span);    var lines = std.mem.splitScalar(u8, span, '\n');    while (lines.next()) |raw_line| {        updateBraceTextMetricLine(&state, raw_line, metric_language);    }    return finishTextMetricState(&state);}fn computePythonTextMetrics(span: []const u8) FunctionMetrics {    var state = textMetricState(span);    var lines = std.mem.splitScalar(u8, span, '\n');    while (lines.next()) |raw_line| {        updatePythonTextMetricLine(&state, raw_line);    }    return finishTextMetricState(&state);}fn textMetricState(span: []const u8) TextMetricState {    return .{        .metrics = .{            .lines_of_code = textLineCount(span),            .parameter_count = textParameterCount(span),        },    };}fn finishTextMetricState(state: *TextMetricState) FunctionMetrics {    state.metrics.cyclomatic_complexity = 1 + state.increments;    return state.metrics;}fn updateBraceTextMetricLine(state: *TextMetricState, raw_line: []const u8, metric_language: MetricLanguage) void {    const trimmed = text.trim(raw_line);    if (trimmed.len == 0) return;    closeBraceTextMetricDepth(state, trimmed);    const nesting_depth = if (state.depth > 0) state.depth - 1 else 0;    applyBraceTextBranches(state, trimmed, metric_language, nesting_depth);    applyTextLogicalMetrics(state, trimmed, metric_language);    if (metric_language != .zig) state.metrics.return_count += countKeyword(trimmed, "return");    openBraceTextMetricDepth(state, trimmed);}fn updatePythonTextMetricLine(state: *TextMetricState, raw_line: []const u8) void {    const trimmed = text.trim(raw_line);    if (trimmed.len == 0 or pythonCommentLine(trimmed)) return;    const level = pythonTextIndentLevel(raw_line);    const branch_depth = if (level > 0) level - 1 else 0;    applyPythonTextBranches(state, trimmed, level, branch_depth);    applyTextLogicalMetrics(state, trimmed, .python);    state.metrics.return_count += countKeywordOutsideQuotes(trimmed, "return");}fn closeBraceTextMetricDepth(state: *TextMetricState, trimmed: []const u8) void {    state.depth -= leadingCloseBraces(trimmed);    if (state.depth < 0) state.depth = 0;}fn openBraceTextMetricDepth(state: *TextMetricState, trimmed: []const u8) void {    state.depth += braceDelta(trimmed);    if (state.depth < 0) state.depth = 0;}fn applyBraceTextBranches(state: *TextMetricState, trimmed: []const u8, metric_language: MetricLanguage, nesting_depth: i64) void {    const branches = textBranchCount(trimmed, metric_language);    if (branches == 0) return;    state.increments += branches;    state.metrics.cognitive_complexity += branches * (1 + nesting_depth);    const branch_depth = nesting_depth + branches;    if (branch_depth > state.metrics.max_nesting_depth) state.metrics.max_nesting_depth = branch_depth;}fn applyPythonTextBranches(state: *TextMetricState, trimmed: []const u8, level: i64, branch_depth: i64) void {    const branches = pythonTextBranchCount(trimmed);    if (branches == 0) return;    state.increments += branches;    state.metrics.cognitive_complexity += branches * (1 + branch_depth);    if (level > state.metrics.max_nesting_depth) state.metrics.max_nesting_depth = level;}fn applyTextLogicalMetrics(state: *TextMetricState, trimmed: []const u8, metric_language: MetricLanguage) void {    const logical = textLogicalCount(trimmed, metric_language);    state.increments += logical;    state.metrics.cognitive_complexity += logical;}fn pythonTextIndentLevel(raw_line: []const u8) i64 {    return @intCast(indentation(raw_line) / 4);}fn pythonTextBranchCount(line: []const u8) i64 {    return countKeywordOutsideQuotes(line, "if") +        countKeywordOutsideQuotes(line, "elif") +        countKeywordOutsideQuotes(line, "for") +        countKeywordOutsideQuotes(line, "while") +        countKeywordOutsideQuotes(line, "except") +        countKeywordOutsideQuotes(line, "with") +        countKeywordOutsideQuotes(line, "match") +        countKeywordOutsideQuotes(line, "case");}fn textLineCount(span: []const u8) i64 {    if (span.len == 0) return 0;    var count_value: i64 = 1;    for (span) |byte| {        if (byte == '\n') count_value += 1;    }    return count_value;}fn textParameterCount(span: []const u8) i64 {    const open = std.mem.indexOfScalar(u8, span, '(') orelse return 0;    const rest = span[open + 1 ..];    const close_rel = std.mem.indexOfScalar(u8, rest, ')') orelse return 0;    const raw_parameters = text.trim(rest[0..close_rel]);    if (raw_parameters.len == 0 or std.mem.eql(u8, raw_parameters, "void")) return 0;    var count_value: i64 = 0;    var split = std.mem.splitScalar(u8, raw_parameters, ',');    while (split.next()) |raw| {        const parameter = text.trim(raw);        if (parameter.len == 0 or std.mem.eql(u8, parameter, "void")) continue;        count_value += 1;    }    return count_value;}fn textBranchCount(line: []const u8, metric_language: MetricLanguage) i64 {    _ = metric_language;    return countKeyword(line, "if") +        countKeyword(line, "for") +        countKeyword(line, "while") +        countKeyword(line, "catch") +        countKeyword(line, "case") +        countKeyword(line, "switch");}fn textLogicalCount(line: []const u8, metric_language: MetricLanguage) i64 {    var count_value = countNeedleOutsideQuotes(line, "&&") + countNeedleOutsideQuotes(line, "||");    if (metric_language == .javascript) count_value += countNeedleOutsideQuotes(line, "??");    if (metric_language == .zig or metric_language == .python) count_value += countKeywordOutsideQuotes(line, "and") + countKeywordOutsideQuotes(line, "or");    return count_value;}fn countNeedleOutsideQuotes(source: []const u8, needle: []const u8) i64 {    var count_value: i64 = 0;    var offset: usize = 0;    while (offset < source.len) {        if (source[offset] == '"' or source[offset] == '\'') {            offset = skipQuoted(source, offset);            continue;        }        if (offset + needle.len <= source.len and std.mem.eql(u8, source[offset .. offset + needle.len], needle)) {            count_value += 1;            offset += needle.len;            continue;        }        offset += 1;    }    return count_value;}fn countKeywordOutsideQuotes(source: []const u8, keyword: []const u8) i64 {    var count_value: i64 = 0;    var offset: usize = 0;    while (offset < source.len) {        if (source[offset] == '"' or source[offset] == '\'') {            offset = skipQuoted(source, offset);            continue;        }        if (offset + keyword.len <= source.len and std.mem.eql(u8, source[offset .. offset + keyword.len], keyword)) {            const before_ok = offset == 0 or !identByte(source[offset - 1]);            const after = offset + keyword.len;            const after_ok = after == source.len or !identByte(source[after]);            if (before_ok and after_ok) count_value += 1;            offset = after;            continue;        }        offset += 1;    }    return count_value;}fn countNeedle(source: []const u8, needle: []const u8) i64 {    var count_value: i64 = 0;    var offset: usize = 0;    while (offset < source.len) {        const rel = std.mem.indexOf(u8, source[offset..], needle) orelse break;        count_value += 1;        offset += rel + needle.len;    }    return count_value;}fn countKeyword(source: []const u8, keyword: []const u8) i64 {    var count_value: i64 = 0;    var offset: usize = 0;    while (offset < source.len) {        const rel = std.mem.indexOf(u8, source[offset..], keyword) orelse break;        const index = offset + rel;        const before_ok = index == 0 or !identByte(source[index - 1]);        const after = index + keyword.len;        const after_ok = after == source.len or !identByte(source[after]);        if (before_ok and after_ok) count_value += 1;        offset = after;    }    return count_value;}fn leadingCloseBraces(line: []const u8) i64 {    var count_value: i64 = 0;    for (line) |byte| {        if (byte == '}') {            count_value += 1;        } else if (!std.ascii.isWhitespace(byte)) {            break;        }    }    return count_value;}fn textContentHash(allocator: std.mem.Allocator, span: []const u8) ![]const u8 {    var hasher = std.hash.XxHash64.init(0);    hasher.update(span);    return try hex64(allocator, hasher.final());}fn textStructureHash(allocator: std.mem.Allocator, span: []const u8) ![]const u8 {    var hasher = std.hash.XxHash64.init(0);    var index: usize = 0;    while (index < span.len) {        const byte = span[index];        if (std.ascii.isWhitespace(byte)) {            index += 1;        } else if (identByte(byte)) {            hasher.update("_");            index += 1;            while (index < span.len and identByte(span[index])) index += 1;        } else if (std.ascii.isDigit(byte)) {            hasher.update("_");            index += 1;            while (index < span.len and (std.ascii.isDigit(span[index]) or span[index] == '.')) index += 1;        } else if (byte == '"' or byte == '\'') {            hasher.update("_");            index = skipQuoted(span, index);        } else {            var single = [_]u8{byte};            hasher.update(&single);            index += 1;        }    }    return try hex64(allocator, hasher.final());}pub fn skipQuoted(source: []const u8, start: usize) usize {    const quote = source[start];    var index = start + 1;    while (index < source.len) : (index += 1) {        if (source[index] == '\\') {            if (index + 1 < source.len) index += 1;        } else if (source[index] == quote) {            return index + 1;        }    }    return source.len;}fn zigLogicalOperator(source: []const u8, node: tree.Node) bool {    if (!std.mem.eql(u8, tree.kind(node), "binary_expression")) return false;    const children = tree.childCount(node);    for (0..children) |raw_index| {        const child_node = tree.child(node, @intCast(raw_index));        if (tree.isNamed(child_node)) continue;        const token = tree.slice(source, child_node);        if (std.mem.eql(u8, token, "and") or std.mem.eql(u8, token, "or")) return true;    }    return false;}pub const FunctionMetrics = struct {    cyclomatic_complexity: i64 = 1,    cognitive_complexity: i64 = 0,    max_nesting_depth: i64 = 0,    lines_of_code: i64 = 0,    parameter_count: i64 = 0,    return_count: i64 = 0,    unresolved_call_targets: ?i64 = null,    external_call_targets: ?i64 = null,};const TextMetricState = struct {    metrics: FunctionMetrics,    increments: i64 = 0,    depth: i64 = 0,};pub const MetricLanguage = enum {    zig,    javascript,    c,    python,};const MetricFrame = struct {    node: tree.Node,    depth: i64,};fn walkMetricTree(allocator: std.mem.Allocator, source: []const u8, root: tree.Node, metric_language: MetricLanguage, depth: i64, metrics: *FunctionMetrics, increments: *i64) !void {    var stack: std.ArrayList(MetricFrame) = .empty;    defer stack.deinit(allocator);    try stack.append(allocator, .{ .node = root, .depth = depth });    while (stack.pop()) |frame| {        const node = frame.node;        const node_kind = tree.kind(node);        if (metricSkip(metric_language, node_kind)) continue;        if (metricBranch(metric_language, node_kind)) {            increments.* += 1;            metrics.cognitive_complexity += 1 + frame.depth;        }        if (metricLogicalOperator(metric_language, source, node, node_kind)) {            increments.* += 1;            metrics.cognitive_complexity += 1;        }        if (metricReturn(metric_language, node_kind)) metrics.return_count += 1;        const child_depth = if (metricNesting(metric_language, node_kind)) frame.depth + 1 else frame.depth;        if (child_depth > metrics.max_nesting_depth) metrics.max_nesting_depth = child_depth;        var index = tree.childCount(node);        while (index != 0) {            index -= 1;            try stack.append(allocator, .{ .node = tree.child(node, @intCast(index)), .depth = child_depth });        }    }}fn metricSkip(metric_language: MetricLanguage, kind: []const u8) bool {    return switch (metric_language) {        .zig => std.mem.eql(u8, kind, "function_declaration"),        .javascript => std.mem.eql(u8, kind, "function_declaration") or std.mem.eql(u8, kind, "method_definition") or std.mem.eql(u8, kind, "arrow_function") or std.mem.eql(u8, kind, "class_declaration"),        .c => std.mem.eql(u8, kind, "function_definition") or std.mem.eql(u8, kind, "class_definition") or std.mem.eql(u8, kind, "class_declaration"),        .python => hashSkip(kind) or hashNormalize(kind) or std.mem.eql(u8, kind, "function_definition") or std.mem.eql(u8, kind, "class_definition") or std.mem.eql(u8, kind, "decorated_definition"),    };}fn metricBranch(metric_language: MetricLanguage, kind: []const u8) bool {    return switch (metric_language) {        .zig => zigBranch(kind),        .javascript => javascriptBranch(kind),        .c => cBranch(kind),        .python => pythonBranch(kind),    };}fn metricLogicalOperator(metric_language: MetricLanguage, source: []const u8, node: tree.Node, kind: []const u8) bool {    return switch (metric_language) {        .zig => zigLogicalOperator(source, node),        .javascript => javascriptLogicalOperator(source, node),        .c => cLogicalOperator(source, node),        .python => pythonLogicalOperator(kind),    };}fn metricReturn(metric_language: MetricLanguage, kind: []const u8) bool {    return switch (metric_language) {        .zig => false,        .javascript, .c, .python => std.mem.eql(u8, kind, "return_statement"),    };}fn metricNesting(metric_language: MetricLanguage, kind: []const u8) bool {    return switch (metric_language) {        .zig => zigNesting(kind),        .javascript => javascriptNesting(kind),        .c => cNesting(kind),        .python => pythonNesting(kind),    };}fn pythonBranch(kind: []const u8) bool {    const branches = [_][]const u8{ "if_statement", "elif_clause", "for_statement", "while_statement", "except_clause", "with_statement", "conditional_expression", "match_statement", "case_clause" };    for (branches) |branch| if (std.mem.eql(u8, kind, branch)) return true;    return false;}fn pythonLogicalOperator(kind: []const u8) bool {    return std.mem.eql(u8, kind, "boolean_operator");}fn pythonNesting(kind: []const u8) bool {    const nested = [_][]const u8{ "if_statement", "for_statement", "while_statement", "try_statement", "with_statement", "match_statement" };    for (nested) |item| if (std.mem.eql(u8, kind, item)) return true;    return false;}fn metricsJson(    allocator: std.mem.Allocator,    metrics: FunctionMetrics,    fan_in: i64,    fan_out: i64,) ![]const u8 {    var out: std.Io.Writer.Allocating = .init(allocator);    errdefer out.deinit();    var json = pretty_json.Writer.init(&out.writer, .minified);    const object = try json.object();    try object.field("cyclomatic_complexity", metrics.cyclomatic_complexity);    try object.field("cognitive_complexity", metrics.cognitive_complexity);    try object.field("max_nesting_depth", metrics.max_nesting_depth);    try object.field("lines_of_code", metrics.lines_of_code);    try object.field("parameter_count", metrics.parameter_count);    try object.field("return_count", metrics.return_count);    try object.field("fan_in", fan_in);    try object.field("fan_out", fan_out);    try writeResolutionMetrics(object, metrics);    try object.end();    return try out.toOwnedSlice();}fn metricsJsonWithCalls(    allocator: std.mem.Allocator,    metrics: FunctionMetrics,    fan_in: i64,    fan_out: i64,) ![]const u8 {    return try metricsJson(allocator, metrics, fan_in, fan_out);}pub fn callMetricsJson(    allocator: std.mem.Allocator,    fan_in: i64,    fan_out: i64,    metrics: FunctionMetrics,) ![]const u8 {    var out: std.Io.Writer.Allocating = .init(allocator);    errdefer out.deinit();    var json = pretty_json.Writer.init(&out.writer, .minified);    const object = try json.object();    try object.field("fan_in", fan_in);    try object.field("fan_out", fan_out);    try writeResolutionMetrics(object, metrics);    try object.end();    return try out.toOwnedSlice();}fn writeResolutionMetrics(    object: pretty_json.Object,    metrics: FunctionMetrics,) !void {    if (metrics.unresolved_call_targets) |count| {        try object.field("unresolved_call_targets", count);    }    if (metrics.external_call_targets) |count| {        try object.field("external_call_targets", count);    }}fn contentHash(allocator: std.mem.Allocator, source: []const u8, node: tree.Node) ![]const u8 {    var hasher = std.hash.XxHash64.init(0);    hasher.update(source[tree.startByte(node)..tree.endByte(node)]);    return try hex64(allocator, hasher.final());}fn structureHash(allocator: std.mem.Allocator, scratch: std.mem.Allocator, node: tree.Node) ![]const u8 {    var hasher = std.hash.XxHash64.init(0);    try structureHashWalk(scratch, node, &hasher);    return try hex64(allocator, hasher.final());}const HashFrame = struct {    node: tree.Node,    close: bool = false,};fn structureHashWalk(allocator: std.mem.Allocator, root: tree.Node, hasher: *std.hash.XxHash64) !void {    var stack: std.ArrayList(HashFrame) = .empty;    defer stack.deinit(allocator);    try stack.append(allocator, .{ .node = root });    while (stack.pop()) |frame| {        if (frame.close) {            hasher.update(")");            continue;        }        const node = frame.node;        const node_kind = tree.kind(node);        if (hashSkip(node_kind)) continue;        if (hashNormalize(node_kind)) {            hasher.update("_");            continue;        }        hasher.update(node_kind);        hasher.update("(");        try stack.append(allocator, .{ .node = node, .close = true });        var index = tree.childCount(node);        while (index != 0) {            index -= 1;            const child_node = tree.child(node, @intCast(index));            if (!hashSkip(tree.kind(child_node))) try stack.append(allocator, .{ .node = child_node });        }    }}fn hashSkip(kind: []const u8) bool {    return std.mem.eql(u8, kind, "comment") or std.mem.eql(u8, kind, "line_comment") or std.mem.eql(u8, kind, "block_comment");}fn hashNormalize(kind: []const u8) bool {    const normalized = [_][]const u8{ "identifier", "type_identifier", "field_identifier", "string", "string_content", "string_literal", "integer", "integer_literal", "float", "float_literal", "number", "true", "false", "none", "null" };    for (normalized) |item| if (std.mem.eql(u8, kind, item)) return true;    return false;}fn hex64(allocator: std.mem.Allocator, value: u64) ![]const u8 {    var buf: [16]u8 = undefined;    const hex = "0123456789abcdef";    var current = value;    var index = buf.len;    while (index > 0) {        index -= 1;        buf[index] = hex[@as(usize, @intCast(current & 0xf))];        current >>= 4;    }    return try allocator.dupe(u8, &buf);}pub fn identByte(byte: u8) bool {    return std.ascii.isAlphanumeric(byte) or byte == '_';}const CallMetricsCounts = struct {    fan_in: usize = 0,    fan_out: usize = 0,    unresolved_targets: usize = 0,    external_targets: usize = 0,    resolution_known: bool = false,};fn fileMeasured(    measured_files: []const []const u8,    file: ?[]const u8,) bool {    const name = file orelse return false;    var lower: usize = 0;    var upper = measured_files.len;    while (lower < upper) {        const middle = lower + (upper - lower) / 2;        switch (std.mem.order(u8, measured_files[middle], name)) {            .eq => return true,            .lt => lower = middle + 1,            .gt => upper = middle,        }    }    return false;}fn failureLess(    _: void,    left: core.ResolutionFailure,    right: core.ResolutionFailure,) bool {    const source_order = std.mem.order(u8, left.source, right.source);    if (source_order != .eq) return source_order == .lt;    const left_bucket = @backingInt(left.bucket);    const right_bucket = @backingInt(right.bucket);    if (left_bucket != right_bucket) return left_bucket < right_bucket;    return std.mem.lessThan(u8, left.target, right.target);}fn sameFailure(    left: core.ResolutionFailure,    right: core.ResolutionFailure,) bool {    return left.bucket == right.bucket and        std.mem.eql(u8, left.source, right.source) and        std.mem.eql(u8, left.target, right.target);}pub const CallMetricsIndex = struct {    pub const Limits = struct {        nodes: usize,        failures: usize,    };    pub const Capacity = struct {        counts: usize,        failures: usize,        bytes: usize,        pub fn derive(limits: Limits) error{CapacityOverflow}!Capacity {            const count_bytes = std.math.mul(                usize,                limits.nodes,                @sizeOf(CallMetricsCounts),            ) catch return error.CapacityOverflow;            const failure_bytes = std.math.mul(                usize,                limits.failures,                @sizeOf(core.ResolutionFailure),            ) catch return error.CapacityOverflow;            return .{                .counts = limits.nodes,                .failures = limits.failures,                .bytes = std.math.add(                    usize,                    count_bytes,                    failure_bytes,                ) catch return error.CapacityOverflow,            };        }    };    pub const InitError = std.mem.Allocator.Error || error{CapacityOverflow};    pub const claim: alloc_phase.capacity.Declaration = .{        .source = .{            .id = "smg.call_metrics_index",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{                    .{                        .id = "one_exact_call_and_resolution_count_record_per_final_graph_node",                        .lifetime = .steady,                        .detail = "one exact call and resolution count record per final graph node",                    },                    .{                        .id = "one_sortable_view_record_per_unresolved_deferred_call_target",                        .lifetime = .steady,                        .detail = "one sortable view record per unresolved deferred call target",                    },                },                .excluded = &.{                    "retained graph nodes edges names and metadata",                    "reset-per-node metrics JSON parse scratch",                    "final metrics JSON and metadata pair storage",                },            },            .capacity = .{                .inputs = &.{                    alloc_phase.capacity.bindInput(Limits, "nodes", "nodes"),                    alloc_phase.capacity.bindInput(Limits, "failures", "failures"),                },                .type_selectors = &.{                    alloc_phase.capacity.bindType(CallMetricsCounts, "callmetricscounts"),                    alloc_phase.capacity.bindType(core.ResolutionFailure, "resolution_failure"),                },                .nodes = &.{                    .{ .input = 0 },                    .{ .scale = .{ .node = 0, .coefficient = .{ .size_of_concrete_type = 0 } } },                    .{ .input = 1 },                    .{ .scale = .{ .node = 2, .coefficient = .{ .size_of_concrete_type = 1 } } },                    .{ .add = .{ .left = 1, .right = 3 } },                },                .assertions = &.{.{                    .scope = .closure_total,                    .measure = .retained,                    .relation = .exact,                    .expression = 4,                }},            },            .overload = .{                .kind = .reject_before_seal,                .detail = "checked byte multiplication plus one exact acquisition reject overflow or OOM before activation",            },            .risks = .{                .transitive = .{                    .status = .open,                    .detail = "standard graph hash lookup is allocation-free in the witness but lacks a transitive allocation-closure certificate",                },                .foreign = .{                    .status = .excluded,                    .detail = "the index is process-local caller-owned memory with no operating-system or callback edge",                },            },            .obligations = &.{                .{ .key = "smg_call_metrics_index_capacity_capacity_model", .role = .capacity_model },                .{ .key = "smg_call_metrics_index_capacity_overload", .role = .overload },                .{ .key = "smg_call_metrics_index_oom", .role = .overload },                .{ .key = "smg_call_metrics_index_sealed_transitive_risk", .role = .transitive_risk },                .{ .key = "smg_call_metrics_index_sealed_foreign_risk", .role = .foreign_risk },            },        },        .bindings = .{            .owner = @This(),            .seal = .{                .family = alloc_phase.capacity.selector(@This().activate),                .premise = .{                    .class = .checked_semantic_fact,                    .authority = .checker,                },            },            .teardown = .{                .family = alloc_phase.capacity.selector(@This().deinit),                .premise = .{                    .class = .checked_semantic_fact,                    .authority = .checker,                },            },        },    };    phase: alloc_phase.capacity.Phase,    capacity: Capacity,    counts: []CallMetricsCounts,    failures: []core.ResolutionFailure,    pub fn init(allocator: std.mem.Allocator, limits: Limits) InitError!CallMetricsIndex {        const capacity = try Capacity.derive(limits);        const counts = if (capacity.counts == 0)            @constCast((&[_]CallMetricsCounts{})[0..])        else            try allocator.alloc(CallMetricsCounts, capacity.counts);        errdefer if (counts.len != 0) allocator.free(counts);        const failures = if (capacity.failures == 0)            @constCast((&[_]core.ResolutionFailure{})[0..])        else            try allocator.alloc(core.ResolutionFailure, capacity.failures);        return .{            .phase = .initialization,            .capacity = capacity,            .counts = counts,            .failures = failures,        };    }    pub fn fill(        self: *CallMetricsIndex,        graph: *const graph_mod.Graph,        measured_files: []const []const u8,        failures: []const core.ResolutionFailure,    ) void {        std.debug.assert(self.phase == .initialization);        std.debug.assert(graph.nodes.items.len == self.capacity.counts);        std.debug.assert(failures.len == self.failures.len);        @memset(self.counts, .{});        for (graph.nodes.items, 0..) |node, index| {            const callable = std.mem.eql(                u8,                node.type,                model.NodeType.function,            ) or std.mem.eql(u8, node.type, model.NodeType.method);            self.counts[index].resolution_known = callable and                graph_mod.isScan(node.metadata) and                fileMeasured(measured_files, node.file);        }        for (graph.edges.items) |edge| {            if (!std.mem.eql(u8, edge.rel, model.RelType.calls)) continue;            const source = graph.node_index.get(edge.source).?;            const target = graph.node_index.get(edge.target).?;            self.counts[source].fan_out += 1;            self.counts[target].fan_in += 1;        }        @memcpy(self.failures, failures);        std.mem.sort(            core.ResolutionFailure,            self.failures,            {},            failureLess,        );        for (self.failures, 0..) |failure, index| {            if (index != 0 and sameFailure(self.failures[index - 1], failure)) {                continue;            }            const source = graph.node_index.get(failure.source) orelse continue;            switch (failure.bucket) {                .unresolved => self.counts[source].unresolved_targets += 1,                .external => self.counts[source].external_targets += 1,            }            self.counts[source].resolution_known = true;        }    }    pub fn activate(self: *CallMetricsIndex) void {        std.debug.assert(self.phase == .initialization);        self.phase = .steady;    }    pub fn get(self: *const CallMetricsIndex, node: usize) CallMetricsCounts {        std.debug.assert(self.phase == .steady);        std.debug.assert(node < self.counts.len);        return self.counts[node];    }    pub fn deinit(self: *CallMetricsIndex, allocator: std.mem.Allocator) void {        std.debug.assert(self.phase != .teardown);        self.phase = .teardown;        if (self.counts.len != 0) allocator.free(self.counts);        if (self.failures.len != 0) allocator.free(self.failures);        self.* = undefined;    }};comptime {    alloc_phase.capacity.requireAllocatorExactOwnerShape(CallMetricsIndex);}pub fn updateCallMetrics(    allocator: std.mem.Allocator,    scratch: std.mem.Allocator,    graph: *graph_mod.Graph,    measured_files: []const []const u8,    failures: []const core.ResolutionFailure,) !void {    var counts = try CallMetricsIndex.init(scratch, .{        .nodes = graph.nodes.items.len,        .failures = failures.len,    });    defer counts.deinit(scratch);    counts.fill(graph, measured_files, failures);    counts.activate();    var parse_arena = std.heap.ArenaAllocator.init(scratch);    defer parse_arena.deinit();    for (graph.nodes.items, 0..) |*node, node_index| {        const existing = model.pairValue(node.metadata, "metrics");        if (!std.mem.eql(u8, node.type, model.NodeType.function) and !std.mem.eql(u8, node.type, model.NodeType.method)) {            if (existing) |raw| {                const value = try allocator.dupe(u8, raw);                std.debug.assert(model.replaceOwnedPairValue(node.metadata, "metrics", value, true));            }            continue;        }        _ = parse_arena.reset(.retain_capacity);        var metrics = if (existing) |raw|            parseMetrics(parse_arena.allocator(), raw) catch FunctionMetrics{}        else            FunctionMetrics{ .cyclomatic_complexity = 0 };        const call_counts = counts.get(node_index);        if (call_counts.resolution_known) {            metrics.unresolved_call_targets = @intCast(                call_counts.unresolved_targets,            );            metrics.external_call_targets = @intCast(                call_counts.external_targets,            );        }        const fan_in = call_counts.fan_in;        const fan_out = call_counts.fan_out;        if (existing != null) {            const value = try metricsJsonWithCalls(                allocator,                metrics,                @intCast(fan_in),                @intCast(fan_out),            );            std.debug.assert(model.replaceOwnedPairValue(node.metadata, "metrics", value, true));        } else {            const pairs = try allocator.alloc(model.Pair, node.metadata.len + 1);            @memcpy(pairs[0..node.metadata.len], node.metadata);            pairs[node.metadata.len] = .{                .key = try allocator.dupe(u8, "metrics"),                .value = try callMetricsJson(                    allocator,                    @intCast(fan_in),                    @intCast(fan_out),                    metrics,                ),                .json = true,            };            node.metadata = pairs;        }    }}fn parseMetrics(allocator: std.mem.Allocator, raw: []const u8) !FunctionMetrics {    const parsed = try std.json.parseFromSlice(std.json.Value, allocator, raw, .{});    defer parsed.deinit();    const object = switch (parsed.value) {        .object => |object| object,        else => return error.InvalidMetrics,    };    return .{        .cyclomatic_complexity = jsonInt(object, "cyclomatic_complexity"),        .cognitive_complexity = jsonInt(object, "cognitive_complexity"),        .max_nesting_depth = jsonInt(object, "max_nesting_depth"),        .lines_of_code = jsonInt(object, "lines_of_code"),        .parameter_count = jsonInt(object, "parameter_count"),        .return_count = jsonInt(object, "return_count"),        .unresolved_call_targets = jsonOptionalInt(            object,            "unresolved_call_targets",        ),        .external_call_targets = jsonOptionalInt(            object,            "external_call_targets",        ),    };}fn jsonOptionalInt(object: std.json.ObjectMap, key: []const u8) ?i64 {    const value = object.get(key) orelse return null;    const integer = switch (value) {        .integer => |integer| integer,        else => return null,    };    if (integer < 0) return null;    return integer;}fn jsonInt(object: std.json.ObjectMap, key: []const u8) i64 {    const value = object.get(key) orelse return 0;    return switch (value) {        .integer => |integer| integer,        .float => |float| @intFromFloat(float),        else => 0,    };}test "call metrics index capacity matches an independent node storage model" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(CallMetricsIndex, "smg_call_metrics_index_capacity_capacity_model"),            null,            null,            null,            null,            null,            null,        );    }    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(CallMetricsIndex, "smg_call_metrics_index_capacity_overload"),            null,            null,            null,            null,            null,            null,        );    }    for (0..64) |nodes| {        const failures = nodes / 2;        const capacity = try CallMetricsIndex.Capacity.derive(.{            .nodes = nodes,            .failures = failures,        });        try std.testing.expectEqual(nodes, capacity.counts);        try std.testing.expectEqual(failures, capacity.failures);        try std.testing.expectEqual(            nodes * @sizeOf(CallMetricsCounts) +                failures * @sizeOf(core.ResolutionFailure),            capacity.bytes,        );    }    try std.testing.expectError(        error.CapacityOverflow,        CallMetricsIndex.Capacity.derive(.{            .nodes = std.math.maxInt(usize),            .failures = 0,        }),    );}fn checkCallMetricsIndexInitAllocationFailures(allocator: std.mem.Allocator) !void {    var index = try CallMetricsIndex.init(allocator, .{        .nodes = 3,        .failures = 2,    });    index.deinit(allocator);}test "call metrics index initialization cleans allocation failure and retries" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(CallMetricsIndex, "smg_call_metrics_index_oom"),            null,            null,            null,            null,            null,            null,        );    }    try std.testing.checkAllAllocationFailures(        std.testing.allocator,        checkCallMetricsIndexInitAllocationFailures,        .{},    );    var index = try CallMetricsIndex.init(std.testing.allocator, .{        .nodes = 1,        .failures = 0,    });    defer index.deinit(std.testing.allocator);    index.activate();    try std.testing.expectEqual(alloc_phase.capacity.Phase.steady, index.phase);}test "call metrics index fills and serves exact call counts while sealed" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(CallMetricsIndex, "smg_call_metrics_index_sealed_transitive_risk"),            null,            null,            null,            null,            null,            null,        );    }    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(CallMetricsIndex, "smg_call_metrics_index_sealed_foreign_risk"),            null,            null,            null,            null,            null,            null,        );    }    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var graph = graph_mod.init(allocator);    defer graph_mod.deinit(&graph);    try graph_mod.addNode(&graph, .{ .name = "app", .type = model.NodeType.module });    try graph_mod.addNode(&graph, .{ .name = "app.run", .type = model.NodeType.function });    try graph_mod.addNode(&graph, .{ .name = "app.help", .type = model.NodeType.function });    try graph_mod.addEdge(&graph, .{ .source = "app", .rel = model.RelType.contains, .target = "app.run" });    try graph_mod.addEdge(&graph, .{ .source = "app.run", .rel = model.RelType.calls, .target = "app.help" });    var phase_allocator = try alloc_phase.SealedPhaseAllocator.init(std.testing.allocator);    var index = try CallMetricsIndex.init(        phase_allocator.initializationAllocator(),        .{            .nodes = graph.nodes.items.len,            .failures = 0,        },    );    defer {        if (phase_allocator.phase() == .initialization) phase_allocator.abortInitialization();        if (phase_allocator.phase() == .steady) phase_allocator.beginTeardown();        index.deinit(phase_allocator.teardownAllocator());        phase_allocator.deinit();    }    phase_allocator.seal();    index.fill(&graph, &.{}, &.{});    index.activate();    try std.testing.expectEqual(@as(usize, 0), index.get(0).fan_in);    try std.testing.expectEqual(@as(usize, 1), index.get(1).fan_out);    try std.testing.expectEqual(@as(usize, 1), index.get(2).fan_in);}test "call metrics replace the owned metadata value without replacing pair storage" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var graph = graph_mod.init(allocator);    const metadata = try allocator.alloc(model.Pair, 2);    metadata[0] = .{ .key = try allocator.dupe(u8, "metrics"), .value = try allocator.dupe(u8, "{\"cyclomatic_complexity\":2,\"cognitive_complexity\":1,\"max_nesting_depth\":1,\"lines_of_code\":3,\"parameter_count\":0,\"return_count\":1,\"fan_in\":0,\"fan_out\":0}"), .json = true };    metadata[1] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") };    try graph_mod.addNode(&graph, .{ .name = "app.run", .type = model.NodeType.function, .metadata = metadata });    const metadata_pointer = graph.nodes.items[0].metadata.ptr;    try updateCallMetrics(        allocator,        std.testing.allocator,        &graph,        &.{},        &.{},    );    try std.testing.expectEqual(metadata_pointer, graph.nodes.items[0].metadata.ptr);    const metrics = model.pairValue(graph.nodes.items[0].metadata, "metrics").?;    try std.testing.expect(std.mem.indexOf(u8, metrics, "\"fan_in\":0") != null);    try std.testing.expect(std.mem.indexOf(u8, metrics, "\"fan_out\":0") != null);}test "resolution metrics keep missing and malformed evidence unknown" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const missing = try parseMetrics(allocator, "{}");    try std.testing.expectEqual(        @as(?i64, null),        missing.unresolved_call_targets,    );    try std.testing.expectEqual(        @as(?i64, null),        missing.external_call_targets,    );    const malformed = try parseMetrics(        allocator,        "{\"unresolved_call_targets\":\"2\",\"external_call_targets\":-1}",    );    try std.testing.expectEqual(        @as(?i64, null),        malformed.unresolved_call_targets,    );    try std.testing.expectEqual(        @as(?i64, null),        malformed.external_call_targets,    );}test "call metrics finalization transfers metrics retained by a non-callable node" {    var graph_arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer graph_arena.deinit();    const allocator = graph_arena.allocator();    var graph = graph_mod.init(allocator);    var input_arena = std.heap.ArenaAllocator.init(std.testing.allocator);    var input_live = true;    defer if (input_live) input_arena.deinit();    const input = input_arena.allocator();    const metadata = try allocator.alloc(model.Pair, 1);    metadata[0] = .{ .key = try allocator.dupe(u8, "metrics"), .value = try input.dupe(u8, "{\"lines_of_code\":6}"), .json = true };    try graph_mod.addNode(&graph, .{ .name = "app.tool", .type = model.NodeType.module, .metadata = metadata });    try updateCallMetrics(        allocator,        std.testing.allocator,        &graph,        &.{},        &.{},    );    input_arena.deinit();    input_live = false;    try std.testing.expectEqualStrings("{\"lines_of_code\":6}", model.pairValue(graph.nodes.items[0].metadata, "metrics").?);}

Source: tools/smg/src/scan/root.zig:6

zig
pub const scan_metrics = @import("metrics.zig");

Complete caller list for scan.scan_metrics.braceDelta

10 direct callers.

Complete caller list for scan.scan_metrics.pythonCommentLine

7 direct callers.

Complete caller list for scan.scan_metrics.updateCallMetrics

38 direct callers.

Complete call list for scan.scan_metrics.updateCallMetrics

10 direct calls.

Audit

Definitions30
Public names41
Members21
Version26.7.0
Revisiondaab053ee433