fix(javascript): name heap and semantic ceilings (#6022)

* fix(javascript): name heap and semantic ceilings

Refs #5910. Heap exhaustion names max_heap_mb with its effective value,
range, and next-analysis worker restart. Semantic ceiling coverage names
the four max_semantic_* arguments. Wakaru and transfer ceilings say this
release has no runtime override.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): distinguish adjustable ceilings from fixed limits

The effective transfer file ceiling and the parent result budget follow
the CLI/MCP process heap, so recovery names NODE_OPTIONS instead of
denying an override. A provenance report over 8 MiB keeps the fixed
allowance guidance, and the Wakaru deadline says an unchanged retry may
hit it again.

Refs #5910

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): keep Bun OOM and checkpoint heap controls distinct

Bun heap-exhaustion exits no longer claim the ignored max_heap_mb V8
old-space limit. Checkpoint decode names the parent heap and Node
NODE_OPTIONS, the same control as the parent result budget.

Refs #5910

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): name semantic ceilings without claiming both were reached

Truncated semantic retention names the total and per-file controls as
effective ceilings. Callers only know that retention was truncated, so
the limitation no longer says both ceilings were reached.

Refs #5910

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): name only the semantic ceiling that stopped retention

Semantic ceiling limitations claimed both the application total and the
per-file control whenever either one truncated the graph. Record which
offered budget stopped node and unknown retention, and name only those
controls. Coverage limits still list both effective values.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): require a value in the NODE_OPTIONS heap example

A bare --max-old-space-size is an illegal V8 flag and prevents Node from
starting. Parent-heap and transfer-ceiling recovery now share
--max-old-space-size=<MiB>, and the Kuna heap remediation uses the same
complete form.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(javascript): limit the old-space placeholder to NODE_OPTIONS recovery

The review covers the CLI/MCP NODE_OPTIONS example and the transfer-ceiling
copy. The Kuna heap-headroom sentence stays unchanged.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
This commit is contained in:
morluto
2026-10-22 02:44:14 +08:00
committed by GitHub
parent 01788bac86
commit a0214e0e0e
22 changed files with 1166 additions and 87 deletions
@@ -748,6 +748,142 @@ it("bounds semantic node projection and reports the ceiling in coverage", () =>
({ name }) => name === "semantic_graph_node_ceiling",
),
).toBe(true);
expect(graph.coverage.limits).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: "max_semantic_nodes" }),
expect.objectContaining({ name: "max_semantic_nodes_per_file" }),
]),
);
expect(graph.limitations.join("\n")).toContain(
"max_semantic_nodes_per_file is 20000",
);
expect(graph.limitations.join("\n")).not.toContain(
"max_semantic_nodes is 100000",
);
expect(graph.limitations.join("\n")).toContain("1 to 1000000");
expect(graph.coverage.limits).toEqual(
expect.arrayContaining([
{ name: "semantic_graph_node_ceiling", value: 100_000, unit: "items" },
{
name: "semantic_graph_file_node_ceiling",
value: 20_000,
unit: "items",
},
{ name: "max_semantic_nodes", value: 100_000, unit: "items" },
{ name: "max_semantic_nodes_per_file", value: 20_000, unit: "items" },
]),
);
});
const semanticApplication = {
graph_id: `jag_${"b".repeat(64)}`,
nodes: [],
};
const semanticFile = (path: string, value: string): JavaScriptArtifactFile => ({
path,
container_sha256: SHA256,
sha256: SHA256,
bytes: Buffer.byteLength(value),
inventory_artifact_id: `art_${SHA256}`,
kind: "javascript",
unpacked: false,
text: { included: true, value },
});
const finishSources = (
limits: {
readonly totalNodes: number;
readonly fileNodes: number;
},
sources: readonly { readonly path: string; readonly text: string }[],
) => {
const projection = createJavaScriptSemanticGraphProjection(limits);
for (const source of sources)
projection.projectFile(
semanticFile(source.path, source.text),
analyzeJavaScriptSemantics(source.text),
);
return projection.finish(SHA256, semanticApplication);
};
it("names only the node ceiling that stopped retention", () => {
const references = (count: number) => "external;\n".repeat(count);
const perFile = finishSources({ totalNodes: 10, fileNodes: 3 }, [
{ path: "only.js", text: references(5) },
]);
expect(perFile.nodes).toHaveLength(3);
expect(perFile.limitations.join("\n")).toContain(
"max_semantic_nodes_per_file is 3",
);
expect(perFile.limitations.join("\n")).not.toContain(
"max_semantic_nodes is 10",
);
expect(perFile.coverage.limits).toEqual(
expect.arrayContaining([
{ name: "semantic_graph_node_ceiling", value: 10, unit: "items" },
{ name: "semantic_graph_file_node_ceiling", value: 3, unit: "items" },
{ name: "max_semantic_nodes", value: 10, unit: "items" },
{ name: "max_semantic_nodes_per_file", value: 3, unit: "items" },
]),
);
const skipped = finishSources({ totalNodes: 4, fileNodes: 10 }, [
{ path: "first.js", text: references(3) },
{ path: "second.js", text: references(3) },
]);
expect(skipped.nodes).toHaveLength(4);
expect(
skipped.nodes.filter((node) => node.identity.module_path === "second.js"),
).toHaveLength(0);
expect(skipped.limitations.join("\n")).toContain("max_semantic_nodes is 4");
expect(skipped.limitations.join("\n")).not.toContain(
"max_semantic_nodes_per_file is 10",
);
const remaining = finishSources({ totalNodes: 5, fileNodes: 4 }, [
{ path: "first.js", text: references(2) },
{ path: "second.js", text: references(3) },
]);
expect(remaining.nodes).toHaveLength(5);
expect(remaining.limitations.join("\n")).toContain("max_semantic_nodes is 5");
expect(remaining.limitations.join("\n")).not.toContain(
"max_semantic_nodes_per_file is 4",
);
const both = finishSources({ totalNodes: 5, fileNodes: 3 }, [
{ path: "first.js", text: references(5) },
{ path: "second.js", text: references(3) },
]);
expect(both.nodes).toHaveLength(5);
expect(both.limitations.join("\n")).toContain("max_semantic_nodes is 5");
expect(both.limitations.join("\n")).toContain(
"max_semantic_nodes_per_file is 3",
);
expect(both.limitations.join("\n")).toContain(
"Set max_semantic_nodes and max_semantic_nodes_per_file",
);
});
it("attributes an adopted file to the ceiling that bounded its offer", () => {
const text = "external;\n".repeat(5);
const projection = createJavaScriptSemanticGraphProjection({
totalNodes: 10,
fileNodes: 3,
});
const steps = projectJavaScriptSemanticFileSteps(
semanticFile("adopted.js", text),
analyzeJavaScriptSemantics(text),
projection.remainingFileCapacity(),
);
let step = steps.next();
while (!step.done) step = steps.next();
projection.adoptFileProjection(step.value);
const limitation = projection
.finish(SHA256, semanticApplication)
.limitations.join("\n");
expect(limitation).toContain("max_semantic_nodes_per_file is 3");
expect(limitation).not.toContain("max_semantic_nodes is 10");
});
it("leaves a small application complete and untruncated", () => {