Skip to main content

Define, validate, search and mutate a repository's markdown knowledge graph (OKF profile: epics, stories, seeds, knowledge, features).

Project description

ostler

Tend your documentation graph.

ostler is the single system-of-record for a repository's docs/ knowledge graph. It defines, validates, searches and mutates your planning docs — epics, stories, seeds, knowledge records and features — as plain markdown Concepts (a strict profile of the Open Knowledge Format).

Everything is markdown. An epic's seeds and its story dependency-DAG live inside its epic.md; there is no seed.json, dependencies.json, inventory.json or epics-todo.json. Ostler owns id allocation and is the one tool that reads and writes the graph — so structure stays consistent while humans (or agents) author the prose.

It is a standalone, repo-agnostic CLI that operates relative to the current working directory: roots default to <cwd>/docs/{epics,knowledge,features,specs} and the organization name to the repo folder name. Point it at any repo with -C/--chdir.

Install

pipx install ostler          # recommended: isolated CLI on your PATH
# or
pip install ostler

For development against a local checkout:

pipx install --editable /path/to/stablemate/ostler --force

The package installs one console entry point, ostler.

Quickstart

Ostler creates the structure and ids; you author the content into the skeletons it scaffolds.

# 1. See if the graph is healthy
ostler doctor

# 2. Scaffold an epic (allocates an id, writes docs/epics/checkout-flow/epic.md)
ostler create epic checkout-flow --title "Checkout Flow at Parity"

# 3. Record a seed (a unit of intended work) in that epic's ## Seeds
ostler seed add checkout-flow address-step --status researched \
  --surface checkout/address --summary "Collect & validate the shipping address"

# 4. Cut a story that covers the seed (adds it to the epic's ## Stories, scaffolds story.md)
ostler create story checkout-flow 01-address-step \
  --title "Address step" --covers address-step

# 5. Ask what to work on next, then list the epic's stories as JSON
ostler next-story checkout-flow
ostler list --type story --epic checkout-flow --json

Then open the scaffolded epic.md / story.md and write the narrative, acceptance criteria, and prose — ostler keeps the seeds, edges, ids and queue coherent around it.

The hierarchy

A repository's knowledge lives under docs/ as OKF bundles (directories of markdown Concepts). A Concept is one .md file with a YAML frontmatter block (whose only hard requirement is a non-empty type) and a markdown body using conventional headings.

Identity is the path. A Concept's id is its bundle-relative path without .md (docs/knowledge/profile/preference-summary.mdprofile/preference-summary). The reserved filenames index.md (an ordered listing of a bundle) and log.md (history) are not Concepts.

Entity types

type Location (repo-relative) Identity Required frontmatter
epic docs/epics/<epic>/epic.md <epic> (dir name) type, id, title
story docs/epics/<epic>/stories/<slug>/story.md <slug> type, slug, status
knowledge docs/knowledge/<area>/<name>.md path (surface alias) type, surface
feature docs/features/<area>/<slug>.md (or flat docs/features/<slug>.md) <area>/<slug> type, slug, title
spec.plan / spec.review / spec.qa docs/specs/<slug>/*.md path type

spec.* Concepts are process artifacts: typed and conformance-checked, but ostler does not own their internal schema. Not Concepts (managed markdown, left in place): docs/backlog.md (an intake list) and docs/epics/index.md (the epics queue).

epic.md — single source of truth for an epic

An epic's epic.md carries the narrative and its seeds and story dependency-DAG. Ostler parses two canonical sections back out of the markdown by exact heading:

---
type: epic
id: pred-15
title: Account Credits "Aperçu" Billing Body at Legacy Parity
status: in-progress        # optional: planned | in-progress | done
---

Free narrative prose (any headings: ## Goal, ## Method, ## Acceptance, …).

## Seeds

### apercu-landing-body
- status: researched       # backlog | researched | covered | resolved | dropped | deferred
- surface: account-billing/apercu-billing-body
- legacySurface: /{_locale}/employe/profile/edit (BuyCreditsAction)
- backing: GET /billing/customer → CustomerDetails

The first paragraph after the metadata bullets is the seed summary; further prose is free markdown.

## Stories

### 01-apercu-billing-body
- title: Account Credits "Aperçu" Billing Body at Legacy Parity
- id: pred-16
- covers: apercu-landing-body, apercu-subscription-change-plan-link
- depends on: (none)
- phase: 1
- effort: 8-10 hours
  • ## Seeds### <seed-id> per seed (omit the whole section for a seedless epic).
  • ## Stories### <slug> per story, carrying the edges: covers: (seed ids) and depends on: (sibling slugs). The detailed spec lives in the story's own story.md.

See SPEC.md for the authoritative, formal definition of every field, status enum, and conformance rule.

Command interface

All read commands accept --json. Mutating commands allocate ids as needed and write canonical markdown in place.

Global

ostler --version
ostler -C, --chdir DIR <command>             # operate as if run from DIR

Inspect

ostler doctor [--epic SLUG] [--json] [--no-schema]   # conformance + referential integrity; non-zero on a break
ostler trace  <id|slug|gap|surface|path>             # walk the graph from any node

Retrieve

ostler list  --type epic|story|knowledge|feature|spec|seed|gap [--epic E] [--status S] [--json]
ostler search <query> [--type T] [--owner O] [--tag G] [--json]
ostler query  gaps-in-story|stories-covering-seed|surfaces-referenced-by-story <arg> [--json]
ostler next-epic [--json]                            # next queued epic with unfinished work
ostler next-story <epic> [--json]                    # next runnable story (deps satisfied, not done)

Mutate (allocates ids, writes markdown)

ostler create epic    <name>  --title T [--prefix P] [--json]
ostler create story   <epic> <slug> --title T [--covers a,b] [--depends a,b] [--prefix P] [--json]
ostler create feature <slug>  --title T [--area A] [--route R] [--prefix P] [--json]
ostler delete epic|story|feature <name>

ostler seed add    <epic> <id> [--status S] [--summary ] [--surface ] \
                               [--legacy-surface ] [--backing ] [--prerequisites ] [--source-bullet ]
ostler seed remove <epic> <id>
ostler set-status  <story> <status>

ostler backlog add <id> <text> [--section S] | ostler backlog prune <id> | ostler backlog list [--json]
ostler todo add <epic> [--front] | ostler todo prune <epic> | ostler todo reorder <e…> | ostler todo list [--json]

create … --json returns {"ok": true, "id": "<allocated-id>", "message": "…"}.

Repair / approve

ostler edit set-owner <gap> <story> [--write]   # dry-run by default; --write applies
ostler edit relink    <old-path> <new-path> [--write]
ostler edit rename    <old-slug> <new-slug> [--write]
ostler freeze   <ident> [--by WHO] [--note ]   # pin an approved story/seed as immutable ground truth
ostler unfreeze <ident>

The coverage model

knowledge gaps[].owner  ->  story (epic.md ## Stories)  ->  covers: seed (epic.md ## Seeds)

ostler doctor checks OKF conformance (every Concept has a non-empty type) plus the typed referential-integrity contract:

  • cross-epic references — an id/slug used inside epic E that only resolves in another epic;
  • orphan seeds — an active seed no story covers;
  • dangling references — a [gap:…] tag, knowledge path, or sibling slug that resolves to nothing;
  • stale owners — a non-resolved gap whose owner is empty or points at a missing story;
  • frozen drift — an approved (frozen) story/seed that changed or vanished.

It exits non-zero when any error-level finding is present, so it drops straight into CI or a pre-commit hook. Warning-level findings (e.g. story-covers-no-seed, ungrounded-surface) are reported but do not fail the check.

Id allocation

Ostler owns .agents/ids.json ({prefix, counter, frozen}). create epic|story|feature allocates the next <prefix>-<n> id atomically, scaffolds the canonical markdown, and (for stories) inserts the ### <slug> block into the epic's ## Stories. There is no external id allocator.

Profiles

ostler infers a profile from the tree: full when docs/epics exists (the epic/story/seed/ knowledge coverage graph), exploration otherwise (knowledge/docs only, no coverage graph). Override any default in an optional organization: block in ostler.yml / agents.yml at the repo root.

Versioning

The format is the OKF profile v1.0, versioned <major>.<minor>. Minor bumps add backward-compatible fields; major bumps may change required frontmatter or the epic.md grammar. A repo may record okf_version and ostler_profile in docs/epics/index.md.

License

See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ostler-0.1.0.tar.gz (39.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ostler-0.1.0-py3-none-any.whl (44.1 kB view details)

Uploaded Python 3

File details

Details for the file ostler-0.1.0.tar.gz.

File metadata

  • Download URL: ostler-0.1.0.tar.gz
  • Upload date:
  • Size: 39.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ostler-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ee2afb1f157b848e6bace6c18faa7efdd18813e37ee93ad594f55bce12751284
MD5 583e21ebf79d28088c39158674dddbfb
BLAKE2b-256 ef2a5c729bfad4db60063106d31882804727d2cac9eabd70a1e44c67296c11c1

See more details on using hashes here.

File details

Details for the file ostler-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: ostler-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 44.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ostler-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 335bbaebab9ee464224d1305a791e7470b92054501adf1c4683cfeb3c00e34f0
MD5 6ec398dc13764b1f50dda062780515f4
BLAKE2b-256 dd27f380f292f79304a2505314a848cf144a79ddb2740777b033d8c1f2f7c53b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page