Adopt the existing safe_json_parse + share _truncate
Two small consolidations:
1. Adopt _json_utils.safe_json_parse (already exists + tested, used by orchestration/patterns) at the hand-rolled "strip code fences then json.loads" sites:
src/selectools/knowledge_graph.py:391 (line-filter fences) — swap, default=[]
src/selectools/entity_memory.py:154 (regex fences) — swap, default=[]
src/selectools/evals/generator.py:95 — a near-line-for-line reimplementation of safe_json_parse
⚠️ Check before touching serve/app.py:718,795 — they currently let JSONDecodeError propagate, while safe_json_parse swallows it and returns the default (error-semantics change). Leave structured.py:45 (deliberate stricter variant) and all non-LLM json.loads.
2. Share _truncate — toolbox/search_tools.py and toolbox/code_tools.py each define a byte-identical 10 KB truncator (_truncate + _MAX_OUTPUT_BYTES). Move to toolbox/_common.py.
Acceptance
- The 3 clean LLM-JSON sites use
safe_json_parse; _truncate lives in one place.
- Tests pass; no behavior change.
Adopt the existing
safe_json_parse+ share_truncateTwo small consolidations:
1. Adopt
_json_utils.safe_json_parse(already exists + tested, used by orchestration/patterns) at the hand-rolled "strip code fences thenjson.loads" sites:src/selectools/knowledge_graph.py:391(line-filter fences) — swap,default=[]src/selectools/entity_memory.py:154(regex fences) — swap,default=[]src/selectools/evals/generator.py:95— a near-line-for-line reimplementation ofsafe_json_parseserve/app.py:718,795— they currently letJSONDecodeErrorpropagate, whilesafe_json_parseswallows it and returns the default (error-semantics change). Leavestructured.py:45(deliberate stricter variant) and all non-LLMjson.loads.2. Share
_truncate—toolbox/search_tools.pyandtoolbox/code_tools.pyeach define a byte-identical 10 KB truncator (_truncate+_MAX_OUTPUT_BYTES). Move totoolbox/_common.py.Acceptance
safe_json_parse;_truncatelives in one place.