{"record":{"id":"1974407015efae43","repo":"bmad-code-org/BMAD-METHOD","slug":"memlog-md-frontmatter-is-not-terminated","errorCode":null,"errorMessage":".memlog.md frontmatter is not terminated","messagePattern":"\\.memlog\\.md frontmatter is not terminated","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/scripts/memlog.py","lineNumber":101,"sourceCode":"\n\ndef resolve(args) -> Path:\n    \"\"\"The memlog file, from either addressing mode: {workspace}/.memlog.md or an explicit --path.\"\"\"\n    return Path(args.path) if args.path else Path(args.workspace) / MEMLOG\n\n\ndef split(text: str) -> tuple[dict, str]:\n    \"\"\"Return (frontmatter dict in source order, body str). Frontmatter is plain key: value.\n\n    The closing fence is the first line that is *exactly* `---`, so a `---` inside a\n    field value (topic/goal are free user text) never truncates the frontmatter.\n    \"\"\"\n    lines = text.splitlines()\n    if not lines or lines[0] != \"---\":\n        raise ValueError(\".memlog.md has no frontmatter\")\n    end = next((i for i in range(1, len(lines)) if lines[i] == \"---\"), None)\n    if end is None:\n        raise ValueError(\".memlog.md frontmatter is not terminated\")\n    meta: dict[str, str] = {}\n    for line in lines[1:end]:\n        if \":\" in line:\n            k, v = line.split(\":\", 1)\n            meta[k.strip()] = v.strip()\n    return meta, \"\\n\".join(lines[end + 1:]).lstrip(\"\\n\")\n\n\ndef render(meta: dict, body: str) -> str:\n    # Neutralize newlines in values so a multi-line field can't break the fence on re-read.\n    fm = \"\\n\".join(f\"{k}: {' '.join(str(v).splitlines())}\" for k, v in meta.items())\n    return \"---\\n\" + fm + \"\\n---\\n\\n\" + body.rstrip(\"\\n\") + \"\\n\"\n\n\ndef touch(meta: dict) -> None:\n    \"\"\"Stamp `updated` and keep it last so the field order stays predictable.\"\"\"\n    meta.pop(\"updated\", None)\n    meta[\"updated\"] = now()","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/src/scripts/memlog.py#L83-L119","documentation":"After confirming the opening `---`, `split` searches for the next line that is exactly `---`. If none exists before EOF, the frontmatter is unterminated and the body cannot be located, so it raises. The closing fence must be a full line of exactly three dashes; a fence with trailing characters or indentation is not recognised, which is deliberate so that `---` inside a free-text topic/goal value cannot truncate the header.","triggerScenarios":"A file with an opening fence but no closing one (truncated write, partial paste); the closing line has trailing spaces, an indented fence, or four dashes; a frontmatter value contains a line that looks like a fence but the real closer was deleted.","commonSituations":"An interrupted write that left the file half-finished; an editor that 'helpfully' reformatted the fence; a manual edit that removed the closing line.","solutions":["Add a line that is exactly `---` after the last frontmatter field.","Strip trailing whitespace from the intended closing-fence line.","If a frontmatter value legitimately contains `---`, that is fine — just ensure a real closing fence exists later; the parser takes the first exact match.","Re-initialise with `memlog init` if the file is unrecoverable."],"exampleFix":"# before (.memlog.md)\n---\ntopic: demo\ngoal: show the fix\n- (note) hello   # no closing fence\n\n# after\n---\ntopic: demo\ngoal: show the fix\nupdated: 2026-08-12T10:00\n---\n\n- (note) hello","handlingStrategy":"validation","validationCode":"lines = text.splitlines()\nassert lines[0] == '---'\nassert any(ln == '---' for ln in lines[1:]), '.memlog.md frontmatter is not terminated'","typeGuard":"def frontmatter_is_terminated(text: str) -> bool:\n    lines = text.splitlines()\n    return bool(lines) and lines[0] == '---' and any(ln == '---' for ln in lines[1:])","tryCatchPattern":"from memlog import split\ntry:\n    meta, body = split(text)\nexcept ValueError as e:\n    print(f\"error: {e}\", file=sys.stderr); sys.exit(2)","preventionTips":["Always close the frontmatter with a line of exactly three dashes.","Avoid trailing whitespace on the closing fence.","Use `memlog init` and `memlog append`/`set` so the file shape stays valid."],"tags":["memlog","frontmatter","validation","filesystem","config"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}