Skip to content

Commit 1e2d5e0

Browse files
committed
fix(compiler): graceful scalar plan + rebuild malformed entity frontmatter
- _compile_concepts: guard a non-dict/non-list parsed plan (JSON scalar) before calling .get(), taking the empty-plan path (write v1 summary if applicable + update index + return) instead of risking AttributeError. - _write_entity: when an existing page has an opening --- but no closing delimiter (or no frontmatter), rebuild valid sources/type/brief frontmatter rather than writing a body-only page that drops the metadata.
1 parent 022aad4 commit 1e2d5e0

1 file changed

Lines changed: 38 additions & 18 deletions

File tree

openkb/agent/compiler.py

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -838,32 +838,37 @@ def _write_entity(
838838
if end != -1:
839839
clean = clean[end + 3:].lstrip("\n")
840840

841+
def _build_frontmatter(sources: list[str]) -> str:
842+
fm_lines = [_yaml_list_line("sources", sources)]
843+
fm_lines.append(_yaml_kv_line("type", type_ or "other"))
844+
if brief:
845+
fm_lines.append(_yaml_kv_line("brief", brief))
846+
if aliases:
847+
fm_lines.append(_yaml_list_line("aliases", aliases))
848+
return "---\n" + "\n".join(fm_lines) + "\n---\n\n"
849+
841850
if is_update and path.exists():
842851
existing = path.read_text(encoding="utf-8")
843852
if source_file not in existing:
844853
existing = _prepend_source_to_frontmatter(existing, source_file)
845-
if existing.startswith("---"):
846-
end = existing.find("---", 3)
847-
if end != -1:
848-
fm = existing[:end + 3]
849-
fm = _set_fm_line(fm, "brief", brief) if brief else fm
850-
fm = _set_fm_line(fm, "type", type_) if type_ else fm
851-
existing = fm + "\n\n" + clean
852-
else:
853-
existing = clean
854+
end = existing.find("---", 3) if existing.startswith("---") else -1
855+
if end != -1:
856+
fm = existing[:end + 3]
857+
fm = _set_fm_line(fm, "brief", brief) if brief else fm
858+
fm = _set_fm_line(fm, "type", type_) if type_ else fm
859+
existing = fm + "\n\n" + clean
854860
else:
855-
existing = clean
861+
# Malformed/absent frontmatter (opening ``---`` with no closing
862+
# delimiter, or no frontmatter at all): rebuild valid frontmatter
863+
# rather than writing a body-only page and dropping sources/type/
864+
# brief. ``_prepend_source_to_frontmatter`` already ensured the
865+
# new source is present in the (still-malformed) block, so seed
866+
# with it here.
867+
existing = _build_frontmatter([source_file]) + clean
856868
path.write_text(existing, encoding="utf-8")
857869
return
858870

859-
fm_lines = [_yaml_list_line("sources", [source_file])]
860-
fm_lines.append(_yaml_kv_line("type", type_ or "other"))
861-
if brief:
862-
fm_lines.append(_yaml_kv_line("brief", brief))
863-
if aliases:
864-
fm_lines.append(_yaml_list_line("aliases", aliases))
865-
frontmatter = "---\n" + "\n".join(fm_lines) + "\n---\n\n"
866-
path.write_text(frontmatter + clean, encoding="utf-8")
871+
path.write_text(_build_frontmatter([source_file]) + clean, encoding="utf-8")
867872

868873

869874
def _set_fm_line(fm: str, key: str, value: str) -> str:
@@ -1364,6 +1369,21 @@ def _write_v1_summary_stripped() -> None:
13641369
# The new plan contract nests concepts under a "concepts" key alongside
13651370
# an "entities" key; the legacy flat shape (create/update/related at top
13661371
# level) is still honored by falling back to ``parsed`` itself.
1372+
if not isinstance(parsed, (list, dict)):
1373+
# A JSON scalar (int/str/None/bool) is valid JSON but not a usable
1374+
# plan. ``_parse_json`` normally rejects scalars, but guard here too
1375+
# so ``parsed.get(...)`` can never raise AttributeError and abort the
1376+
# compile — treat it as an empty/unparseable plan.
1377+
logger.warning(
1378+
"Concepts plan parsed to a %s scalar, not an object/array — "
1379+
"treating as empty plan for %s.",
1380+
type(parsed).__name__, doc_name,
1381+
)
1382+
if rewrite_summary:
1383+
_write_v1_summary_stripped()
1384+
_update_index(wiki_dir, doc_name, [], doc_brief=doc_brief, doc_type=doc_type)
1385+
return
1386+
13671387
if isinstance(parsed, list):
13681388
plan = {"create": _filter_concept_items(parsed, "list"),
13691389
"update": [], "related": []}

0 commit comments

Comments
 (0)