{"id":50953030,"url":"https://github.com/psenger/lg2m","last_synced_at":"2026-06-18T03:34:04.499Z","repository":{"id":364998313,"uuid":"1269095402","full_name":"psenger/lg2m","owner":"psenger","description":"Keep a LangGraph/LangChain graph and its Mermaid diagram in sync both ways: detect drift in either direction and fail the build, or scaffold one side from the other. A Python CLI that treats the diagram as a checkable contract.","archived":false,"fork":false,"pushed_at":"2026-06-15T11:45:57.000Z","size":428,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-18T03:33:56.399Z","etag":null,"topics":["agent-workflows","ai-agents","cli","code-generation","developer-tools","diagram-as-code","docs-as-code","drift-detection","langchain","langgraph","llm","mermaid","mermaid-diagram","python","state-diagram","state-machine"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/psenger.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-14T09:54:26.000Z","updated_at":"2026-06-15T11:45:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/psenger/lg2m","commit_stats":null,"previous_names":["psenger/lg2m"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/psenger/lg2m","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psenger%2Flg2m","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psenger%2Flg2m/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psenger%2Flg2m/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psenger%2Flg2m/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/psenger","download_url":"https://codeload.github.com/psenger/lg2m/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psenger%2Flg2m/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34475375,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-workflows","ai-agents","cli","code-generation","developer-tools","diagram-as-code","docs-as-code","drift-detection","langchain","langgraph","llm","mermaid","mermaid-diagram","python","state-diagram","state-machine"],"created_at":"2026-06-18T03:34:00.894Z","updated_at":"2026-06-18T03:34:04.491Z","avatar_url":"https://github.com/psenger.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# langgraph to-from mermaid (lg2m)\n\n**Keep your LangGraph or LangChain graph and its Mermaid diagram in sync, both ways, and fail the build when they drift.**\n\n![version: 0.1.0](https://img.shields.io/badge/version-0.1.0-blue)\n![license: MIT](https://img.shields.io/badge/license-MIT-blue)\n\n[Installation](#installation) • [How it works](#how-it-works) • [The CLI](#the-cli) • [Scope](#langgraph-and-langchain-scope) • [Run an example](#try-the-runnable-example) • [Roadmap](#status-and-roadmap)\n\n\u003c/div\u003e\n\n---\n\n\u003e [!NOTE]\n\u003e **v0.1.0.** The package is built and green end-to-end: the intermediate\n\u003e representation, the config loader, the Markdown / Mermaid parsers, the annotation\n\u003e decorators and `router`, the diff and report engine, the real LangGraph introspector,\n\u003e the Typer CLI, code/contract generation (`scaffold/` + `gen`), and prose sync\n\u003e (`sync` verb with `.lg2m.lock` baseline). The CLI ships `check`, `validate`,\n\u003e `list`, `init`, `gen`, and `sync`. Install from PyPI or a GitHub tag — see\n\u003e [Installation](#installation). The annotated example checks clean under `lg2m check`;\n\u003e the plain native example ([below](#try-the-runnable-example)) runs today. The design\n\u003e lives in [`docs/design.md`](docs/design.md) and the competitive landscape in\n\u003e [`PRIOR-ART.md`](PRIOR-ART.md).\n\n`lg2m` (`langgraph_to_from_mermaid`) is a Python package and CLI that\ntreats a Mermaid `stateDiagram-v2`, written in Markdown, as a checkable contract\nfor a LangGraph or LangChain graph. This high fidelity app reads the real compiled \ngraph for topology truth, reads code annotations that link each symbol to the \ndiagram, and reports drift in either direction with file and line locations, or \nscaffolds one side from the other.\n\nA diagram drifts from the code the moment either changes. Tools that draw a graph\nfrom code are one-way; builders that generate code from a diagram throw the\ndiagram away afterward. `lg2m` keeps the two honest over time and makes\ndisagreement a build failure. See [`PRIOR-ART.md`](PRIOR-ART.md) for where it sits\namong existing tools.\n\n## Installation\n\n**From PyPI:**\n\n```bash\npip install langgraph-to-from-mermaid\n```\n\nAdd the `[langgraph]` extra to enable `lg2m check` and the real introspector:\n\n```bash\npip install \"langgraph-to-from-mermaid[langgraph]\"\n```\n\n**From a GitHub release tag** (no PyPI needed):\n\n```bash\npip install \"git+https://github.com/psenger/langgraph_to_from_mermaid.git@vX.Y.Z\"\n# with the langgraph extra:\npip install \"langgraph-to-from-mermaid[langgraph] @ git+https://github.com/psenger/langgraph_to_from_mermaid.git@vX.Y.Z\"\n```\n\nReplace `vX.Y.Z` with the tag you want (e.g. `v0.1.0`). Tags are listed on the\n[GitHub Releases page](https://github.com/psenger/langgraph_to_from_mermaid/releases).\n\n**From a clone** (for development or to track `main`):\n\n```bash\ngit clone https://github.com/psenger/langgraph_to_from_mermaid.git\ncd langgraph_to_from_mermaid\npython3 -m venv .venv \u0026\u0026 source .venv/bin/activate\npip install -e \".[dev]\"            # foundation layer only\n# pip install -e \".[langgraph,dev]\" # add the introspector\n```\n\nOnce installed, the `lg2m` command is on your `PATH`.\n\n## How it works\n\n`lg2m check` reconciles three sources and exits non-zero when they disagree:\n\n1. **Topology (introspection).** The real nodes, edges, conditional flags,\n   `path_map` targets, and the state schema with its reducers, read from\n   `compiled.get_graph(xray=True)`. This is the source of truth for graph shape.\n2. **Annotations.** `@node`, `@predicate`, `lg2m.router`, `@state_model`, and\n   `@data_model` link each symbol to the diagram and the Markdown. They record\n   metadata and return their target unchanged, so they do not alter runtime\n   behavior.\n3. **The Markdown contract.** A purely topological Mermaid `stateDiagram-v2`, plus\n   tables, hidden fences, and notes for the facts a diagram cannot draw (reducers,\n   `Command` destinations, `Send` width).\n\n### Routing cannot drift\n\nConditional routing is the part most likely to rot, so `lg2m` owns it. You declare\na fan-out as an ordered mapping of named predicates ending in a required `[else]`\ndefault; `lg2m` generates the router and the `path_map` from that one mapping.\n\n```python\nfrom lg2m import predicate, router, ELSE\n\n@predicate(\"should_escalate\")\ndef should_escalate(state) -\u003e bool:          # a whole leaf condition; you write this\n    f = state[\"flags\"]\n    return (f.get(\"urgent\") or f.get(\"vip\")) and not f.get(\"resolved\")\n\n# the fan-out as an ordered mapping; lg2m builds the selector and owns the path_map\nroute_after_classify = router(\"classify_intent\", [\n    (\"should_escalate\",     \"escalate_to_human\"),\n    (\"should_auto_resolve\", \"auto_resolve\"),\n    (ELSE,                  \"investigate\"),   # required default\n])\n```\n\nBecause the diagram labels, the runtime selector, and the `path_map` all come from\nthe same mapping, they cannot disagree. The only independently authored surface\nleft is the Markdown, which is exactly what `check` reconciles.\n\n### What the contract looks like\n\nThe diagram is plain Mermaid that renders on GitHub. The conditional edges are\nlabelled with the predicate names, and the default branch is labelled `[else]`:\n\n```mermaid\nstateDiagram-v2\n    [*] --\u003e ingest_ticket\n\n    state fork_enrich \u003c\u003cfork\u003e\u003e\n    state join_enrich \u003c\u003cjoin\u003e\u003e\n    ingest_ticket --\u003e fork_enrich\n    fork_enrich --\u003e fetch_history\n    fork_enrich --\u003e lookup_account\n    fetch_history --\u003e join_enrich\n    lookup_account --\u003e join_enrich\n    join_enrich --\u003e classify_intent\n\n    classify_intent --\u003e escalate_to_human: should_escalate\n    classify_intent --\u003e auto_resolve: should_auto_resolve\n    classify_intent --\u003e investigate: [else]\n\n    state investigate {\n        [*] --\u003e gather_logs\n        gather_logs --\u003e analyze\n        analyze --\u003e [*]\n    }\n\n    investigate --\u003e map_items\n    map_items --\u003e process_item\n    process_item --\u003e reduce_items\n    reduce_items --\u003e compose_reply\n\n    auto_resolve --\u003e compose_reply\n    escalate_to_human --\u003e compose_reply\n\n    compose_reply --\u003e [*]\n```\n\nA clean run reports each source as in agreement (illustrative output; run\n`lg2m check` for the real report):\n\n```text\nlg2m check\ngraph: support_pipeline  (support_pipeline.graph:build_graph -\u003e CompiledStateGraph)\n  introspected: 11 nodes, 15 edges (5 conditional), state PipelineState (8 fields)\n\n  nodes .......... 11/11  OK   (@node ids == graph nodes == diagram states)\n  routing ........ OK         (router mapping == path_map == get_graph labels == diagram)\n  reducers ....... 4/4    OK   (add_messages, operator.add x2, extend_unique)\n  diagram ........ OK\n  diagnostics .... none\n\n0 drift items. exit 0\n```\n\nThe full annotated source and contract are in\n[`examples/support_pipeline/`](examples/support_pipeline/).\n\n## The CLI\n\nThe CLI ships today (see [`docs/design.md`](docs/design.md) Section 11). Configuration lives in an\n`lg2m.toml` that maps a graph id to its entry point and Markdown contract.\n\n| command | what it does |\n| --- | --- |\n| `init` | scaffold a starting `lg2m.toml` |\n| `list` | list the configured graphs |\n| `validate` | each side parses, the entry point imports, every fan-out has an `[else]` |\n| `check` | reconcile topology, annotations, and the diagram; non-zero on drift |\n| `gen --from-doc` | scaffold annotated code from the Markdown contract |\n| `gen --from-code` | scaffold the Markdown contract from code plus annotations |\n| `sync` | write prose back across the code/doc boundary using a `.lg2m.lock` baseline |\n\nExit codes: `0` clean, `1` drift or structural error, `2` usage or config error.\n`gen` emits LangGraph today; LangChain emission is on the roadmap (the routing model\ncompiles to both). `gen` writes only where asked: `--out` writes files and refuses to\noverwrite, and without it the output is a stdout dry-run. `sync` supports `--prefer\ncode|doc` conflict resolution and `--dry-run`.\n\n## LangGraph and LangChain scope\n\nFull bidirectional fidelity is for **LangGraph**. For **LangChain**, `lg2m` covers\nthe LCEL-expressible slice (linear chains plus `RunnableBranch`). The routing model\nis portable to both frameworks; the wider graph (parallel fan-out with reducers,\n`Send` map-reduce, `Command(goto)`, subgraphs) has no LCEL equivalent and is\nLangGraph-only. The [examples](examples/) include a per-construct breakdown of\nwhat LangChain can and cannot express.\n\n## Try the runnable example\n\nThe plain native example runs today. It is the same graph in both frameworks, with\ndeterministic node bodies (no LLM calls, no API keys), so runs are reproducible.\n\n```bash\ncd examples/support_pipeline_native\npython3 -m venv .venv\nsource .venv/bin/activate\npython -m pip install -r requirements.txt\npython langgraph_app.py     # the full graph\npython langchain_app.py     # the slice LCEL can express\npython introspect.py        # the topology lg2m would read via get_graph(xray=True)\n```\n\n[`examples/support_pipeline/`](examples/support_pipeline/) is the same graph with\n`lg2m` applied (annotations plus the Mermaid and Markdown contract); run `lg2m check`\nagainst it for a clean reconcile.\n\n## Status and roadmap\n\nv0.1.0 — all layers shipped and green. The build order from [`docs/design.md`](docs/design.md)\nSection 13 is complete:\n\n1. **Done.** IR, config loader, and the Markdown / Mermaid parsers (no framework import).\n2. **Done.** Annotations and the `router`, the diff engine, and the reports; reconciliation\n   runs against a fake introspector and fixtures.\n3. **Done.** The LangGraph introspector behind the `[langgraph]` extra, and a runnable `check`.\n4. **Done.** The Typer CLI (`check`, `validate`, `list`, `init`, `gen`).\n5. **Done.** `gen --from-doc` / `--from-code` with round-trip golden tests (LangGraph emission).\n6. **Done.** `sync` verb: prose write-back with a `.lg2m.lock` baseline-hash store, 3-way\n   merge, conflict detection, `--prefer code|doc` resolution, and `--dry-run`.\n\nNext: LangChain code emission, full subgraph / `Send` / `Command` round-trip fidelity, and a\nversion-matrix CI across LangGraph / langchain-core releases.\n\n## Repository layout\n\n```\n.\n  docs/design.md               the design\n  PRIOR-ART.md                 competitive landscape and the novelty claim\n  src/lg2m/                    the package: IR, parsers, annotations, router, introspector,\n                               diff, report, CLI, and scaffold/ (gen)\n  tests/                       the pytest suite (framework-free, plus @langgraph-gated)\n  docs/prose-sync.md           design notes that preceded the sync/ implementation\n  examples/\n    support_pipeline_native/   runnable: the graph in LangGraph + LangChain, before lg2m\n    support_pipeline/          the same graph annotated, with its Mermaid/Markdown contract (checks clean)\n```\n\n## Prior art\n\n`lg2m`'s novelty is the combination of bidirectional topology sync,\ndrift-as-a-build-contract, and LangGraph awareness. Every individual capability\nexists in isolation elsewhere; the full landscape and what would falsify the claim\nare in [`PRIOR-ART.md`](PRIOR-ART.md).\n\n## Contributing\n\nThe design is settled in [`docs/design.md`](docs/design.md), and the \"Next\" items above are the natural\nfirst-issues list. Start by opening a bug or feature issue, then cut a branch from `main`\nand open a pull request back to `main`. The framework-free pieces (LangChain emission in\n`scaffold/`, the prose `sync` verb) are a good place to start. See\n[`CONTRIBUTING.md`](CONTRIBUTING.md) for the full workflow, local setup, and the release process.\n\n## License\n\nMIT. See [`LICENSE`](LICENSE).\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n**lg2m: one graph, one diagram, no drift.**\n\n[Design](docs/design.md) • [Prior art](PRIOR-ART.md) • [Examples](examples/)\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpsenger%2Flg2m","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpsenger%2Flg2m","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpsenger%2Flg2m/lists"}