Metadata-Version: 2.4
Name: plan-doc
Version: 0.1.1
Summary: Typed parser for plan-file YAML frontmatter (schema v1) — strict validation, first-block-only extraction, foreign-document discrimination
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Development Status :: 4 - Beta
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=9.0.3; extra == "dev"
Dynamic: license-file

# plan-doc

Typed parser for plan-file YAML frontmatter (schema v1). Extracts the
machine-readable fields a plan-driven-PR workflow needs — `template`,
`disposal`, `branch`, a planner-handoff `dossier` block — from markdown
plan documents. Validation is strict on plan-doc's own fields (loud failures
on bad values and typos) but lenient on foreign top-level fields a host
project carries alongside the schema (captured in `.extra`).

```python
from pathlib import Path
from plan_doc import PlanDoc, PlanDocError, NonDraftPrFrontmatterError

doc = PlanDoc.from_path(Path("docs/plans/my-plan.md"))
if doc.disposal == "delete-on-merge":
    ...
if doc.dossier is not None:
    files = doc.dossier.get("files", [])
```

## What it enforces

- **Schema v1.** `plan_schema: 1` is required; `template`, `disposal`,
  `phase`, `cleanup_trigger`, `ticket`, `branch`, `tdd_mode`, `dossier`
  are plan-doc's own top-level keys. Foreign top-level keys (a host
  project's own frontmatter convention — `id`, `title`, `status`, …) are
  tolerated and captured in `PlanDoc.extra`, so a plan can carry both
  schemas at once. The typo guard survives: an unknown key within
  edit-distance 1 of a known field still raises `PlanDocError` (`dispozal`
  fails loud), so a misspelling can't silently disable disposal/minimal
  handling. (Lenient policy since 0.1.1; 0.1.0 rejected every non-schema
  key.)
- **First-block-only parsing.** Only a `---`...`---` block at the very
  top of the file is frontmatter. Body-level `---` horizontal rules are
  never split on.
- **Foreign-document discrimination.** Valid frontmatter with no
  `plan_schema` and no plan indicator fields raises the typed subclass
  `NonDraftPrFrontmatterError`, so callers can treat non-plan documents
  leniently without swallowing real validation errors.
- **No frontmatter at all** raises `PlanDocError` with an actionable
  message (add `plan_schema: 1`).

## Dependency note

`pyyaml` is a hard dependency, and the module *also* keeps a minimal
no-yaml fallback parser (`_parse_yaml_minimal`) for simple `key: value`
frontmatter. Both halves are deliberate: the upstream source of this
module runs in a zero-pip environment and relies on the fallback, while
the package declares full functionality. The fallback is covered by
tests here — don't remove either half.

## Provenance

Originally extracted (code byte-identical, module docstring reframed) from
the draft-pr skill of the m0j0d portfolio plugin. As of 2026-06-14 this
package is the canonical source of truth: the plugin's vendored copy was
deleted and plugin now consumes this package via editable install.

Pool purity rule: transport-free, LLM-agnostic, no secrets. Pure parsing;
the only I/O is `Path.read_text` in `PlanDoc.from_path`.

## License

MIT
