1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Add multi-language logic exploration

This commit is contained in:
Andraxion 2026-07-25 22:29:15 -04:00
parent 9b4258c852
commit 9161889492
18 changed files with 1639 additions and 76 deletions

View file

@ -133,7 +133,7 @@ class DocForgeMcpTests(unittest.IsolatedAsyncioTestCase):
visualization = results[12].structuredContent["visualization"]
self.assertTrue(visualization["read_only"])
self.assertTrue(visualization["project_bound"])
self.assertEqual("graph-browser@16", visualization["template"])
self.assertEqual("graph-browser@17", visualization["template"])
self.assertEqual("managed_idle", visualization["lifetime"]["policy"])
self.assertEqual("docforge_stop_visualization", visualization["lifetime"]["stop_tool"])
self.assertTrue(visualization["url"].startswith("http://127.0.0.1:"))

View file

@ -0,0 +1,154 @@
from __future__ import annotations
import unittest
from docforge.errors import DocForgeError
from docforge.treesitter_logic import (
TreeSitterLogicOwner,
analyze_cpp_source,
analyze_javascript_source,
)
class JavaScriptLogicTests(unittest.TestCase):
def test_branches_short_circuit_and_converge(self) -> None:
source = """
function choose(enabled, ready) {
if (enabled && ready()) {
accept();
} else {
reject();
}
return enabled;
}
""".strip()
projection = analyze_javascript_source(
source,
source_id="source.javascript",
owners=(TreeSitterLogicOwner("js.symbol.choose", "choose", 1),),
)[0]
kinds = {node.kind for node in projection.nodes}
labels = {node.label for node in projection.nodes}
relations = {edge.relation for edge in projection.edges}
self.assertIn("condition", kinds)
self.assertIn("convergence", kinds)
self.assertIn("Decision convergence", labels)
self.assertIn("when_true", relations)
self.assertIn("when_false", relations)
self.assertIn("return", relations)
def test_arrow_function_expression_is_a_returning_projection(self) -> None:
source = "const choose = (enabled) => enabled ? accept() : reject();"
projection = analyze_javascript_source(
source,
source_id="source.javascript",
owners=(TreeSitterLogicOwner("js.symbol.choose", "choose", 1),),
)[0]
labels = {node.label for node in projection.nodes}
self.assertIn("enabled", labels)
self.assertIn("return accept()", labels)
self.assertIn("return reject()", labels)
def test_loops_switch_and_exception_paths_are_preserved(self) -> None:
source = """
function process(items, mode) {
for (const item of items) {
if (!item.ready) continue;
use(item);
}
switch (mode) {
case 1:
one();
break;
default:
fallback();
}
try {
risk();
} catch (error) {
recover(error);
} finally {
clean();
}
}
""".strip()
projection = analyze_javascript_source(
source,
source_id="source.javascript",
owners=(TreeSitterLogicOwner("js.symbol.process", "process", 1),),
)[0]
kinds = {node.kind for node in projection.nodes}
labels = {node.label for node in projection.nodes}
relations = {edge.relation for edge in projection.edges}
self.assertTrue({"loop", "continue", "case", "try", "except", "finally"} <= kinds)
self.assertIn("Case convergence", labels)
self.assertIn("Exception convergence", labels)
self.assertTrue({"loop", "continue", "case", "exception"} <= relations)
class CppLogicTests(unittest.TestCase):
def test_cpp_function_branches_and_throws(self) -> None:
source = """
int choose(bool enabled) {
if (enabled) {
return 1;
}
throw Error();
}
""".strip()
projection = analyze_cpp_source(
source,
source_id="source.cpp",
owners=(TreeSitterLogicOwner("cpp.symbol.choose", "choose", 1),),
)[0]
kinds = {node.kind for node in projection.nodes}
relations = {edge.relation for edge in projection.edges}
self.assertTrue({"entry", "condition", "return", "raise", "exit"} <= kinds)
self.assertTrue({"when_true", "when_false", "return", "raise"} <= relations)
def test_cpp_qualified_method_owner_is_resolved(self) -> None:
source = """
class Worker {
public:
int run(bool ready) {
while (ready) {
ready = tick();
}
return 0;
}
};
""".strip()
projection = analyze_cpp_source(
source,
source_id="source.cpp",
owners=(TreeSitterLogicOwner("cpp.symbol.worker.run", "Worker.run", 3),),
)[0]
labels = {node.label for node in projection.nodes}
self.assertIn("Loop exit", labels)
self.assertIn("return 0", labels)
def test_invalid_source_and_missing_owner_fail_closed(self) -> None:
with self.assertRaises(DocForgeError) as invalid:
analyze_cpp_source(
"int broken( {",
source_id="source.cpp",
owners=(),
)
self.assertEqual(invalid.exception.code, "invalid_logic_source")
with self.assertRaises(DocForgeError) as missing:
analyze_javascript_source(
"function exists() {}",
source_id="source.javascript",
owners=(TreeSitterLogicOwner("js.symbol.missing", "missing", 1),),
)
self.assertEqual(missing.exception.code, "missing_logic_owner")
if __name__ == "__main__":
unittest.main()

View file

@ -72,6 +72,14 @@ class VisualizationTests(unittest.TestCase):
first = snapshot.node("guide.workflow", depth=2, limit=2)
second = snapshot.node("guide.workflow", depth=2, limit=2)
filtered = snapshot.filter_nodes(category="tag", value="canonical", limit=2)
searched = snapshot.search(
query="",
family="guide",
kind="canonical",
language=None,
capability="source",
limit=2,
)
source = snapshot.source("guide.workflow")
flow = snapshot.lineage("guide.workflow", limit=20)
web = snapshot.web("guide.workflow", depth=2, limit=20)
@ -91,6 +99,11 @@ class VisualizationTests(unittest.TestCase):
self.assertEqual(1, filtered["total"])
self.assertFalse(filtered["truncated"])
self.assertEqual("guide.foundation", filtered["results"][0]["node_id"])
self.assertEqual(1, searched["count"])
self.assertEqual("guide.foundation", searched["results"][0]["node_id"])
self.assertIn({"value": "canonical", "count": 1}, overview["tags"])
self.assertIn({"value": "source", "count": 3}, overview["capabilities"])
self.assertIn({"value": "logic", "count": 0}, overview["capabilities"])
self.assertLessEqual(len(first["edges"]), 2)
self.assertEqual("docs/content/workflow.md", source["source_path"])
self.assertIn("Editors change canonical nodes", source["content"])
@ -238,10 +251,17 @@ The test verifies the default behavior.
self.assertIn('id="restore-hidden"', _GRAPH_BROWSER_HTML)
self.assertIn('id="view-web"', _GRAPH_BROWSER_HTML)
self.assertIn('id="view-logic"', _GRAPH_BROWSER_HTML)
self.assertIn('id="kind"', _GRAPH_BROWSER_HTML)
self.assertIn('id="language"', _GRAPH_BROWSER_HTML)
self.assertIn('id="capability"', _GRAPH_BROWSER_HTML)
self.assertIn('data-preset="logic"', _GRAPH_BROWSER_HTML)
self.assertIn('id="open-node-source"', _GRAPH_BROWSER_HTML)
self.assertIn('id="hide-node"', _GRAPH_BROWSER_HTML)
self.assertIn('id="source-dialog"', _GRAPH_BROWSER_HTML)
self.assertIn("state.hiddenNodes.add(nodeId)", _GRAPH_BROWSER_JAVASCRIPT)
self.assertIn("applyTraceHighlight", _GRAPH_BROWSER_JAVASCRIPT)
self.assertIn("layoutLogic", _GRAPH_BROWSER_JAVASCRIPT)
self.assertIn("trace-connected", _GRAPH_BROWSER_CSS)
self.assertIn(
"grid-template-rows: auto minmax(0, 1fr) auto",
_GRAPH_BROWSER_CSS,