Skip to content
kselvaPublic

About

File-based Epic/Story/Task tracking CLI for markdown docs — a lightweight, git-style alternative to a JIRA board.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

md-sdlc

CLI for tracking Epics/Stories/Tasks/Proposals/Ad hoc work as markdown files with frontmatter, across one or more project ai-docs/-style trees. A file-based, lightweight alternative to a JIRA board.

  • This file — setup and command usage
  • CONVENTIONS.md — the enforceable naming/folder/status/schema rules (md_sdlc conventions prints this)
  • DESIGN_NOTES.md — why it's built this way, what's deliberately not built, known rough edges
  • CHANGELOG.md — what shipped in each version

How it works

Every tracked file has a YAML frontmatter header (id, type, kind, status, parent, ...). The tool never guesses — it reads and validates these fields, and generates rollup views (backlog.md) from them. Nothing is ever hand-edited by the tool except through the commands below; backlog.md files are always fully regenerated, never merged.

Setup

Option A — compiled binary (recommended, no Python required):

Build once, then use dist\md_sdlc.exe directly, or add its folder to PATH so md_sdlc works from any shell without a path prefix.

Option B — run from source (for development):

pip install python-frontmatter pyyaml jinja2
python sdlc_tool.py validate

Building the exe:

pip install pyinstaller
python -m PyInstaller md_sdlc.spec --distpath dist --workpath build

If contributing to this repo: install the pre-commit leak check once after cloning — git clone never copies .git/hooks/, so this doesn't happen automatically:

bash scripts/install-hooks.sh

It scans staged files against .leakcheck (internal project names that must never reach this public repo) before every commit and blocks the commit if it finds a match.

Initializing a project

Targeting is git-style: no central registry of project paths. Each project marks itself with a .sdlc/config.yml inside its own tracked folder (e.g. ai-docs/), and every command auto-detects the nearest one by walking up from the current directory — same as git finding .git/.

cd /path/to/your-project/ai-docs
md_sdlc init --name "Your Project" --prefix YP

This creates ai-docs/.sdlc/config.yml and ai-docs/hist/. Nothing else is touched. Run any other command from inside (or under) that folder afterward — no flags needed to say which project you mean:

cd /path/to/your-project/ai-docs
md_sdlc validate

Running a command outside any initialized project fails clearly:

ERROR - not initialized - no .sdlc/ found in this folder or any parent.
  Run 'md_sdlc init' inside your project's folder first.

Adding a second, third, ... project to track is just running init inside that project's own folder too — no code change, no shared config file to edit or keep in sync.

(Examples below use md_sdlc; substitute python sdlc_tool.py if running from source.)

Commands

init

md_sdlc init --name "Project Name" --prefix ABC [--path .]
md_sdlc init --name "Project Name" --prefix ABC --id-style short --work-dir plan   # 0.4.0 short layout

conventions

Print the full naming/folder/status/schema rules this tool enforces (the contents of CONVENTIONS.md) — the canonical reference, not a summary.

md_sdlc conventions

validate

Walk the whole tree, check frontmatter schema, vocabulary, and that every parent/related/originated_from/supersedes/reverts link resolves.

md_sdlc validate

backlog

Regenerate the rollup. No args regenerates the root Epic-level backlog.md; --epic regenerates one Epic's own Story+Task level backlog.md.

md_sdlc backlog
md_sdlc backlog --epic EPIC-01-checkout-redesign

query

Filtered counts/listings — the "how many open", "MVP remaining", "stale items" questions, without opening files by hand.

md_sdlc query --type task --status in-progress
md_sdlc query --owner YourName --stale-days 14
md_sdlc query --mvp-remaining
md_sdlc query --active                       # in-progress / in-review Stories + branch + touches
md_sdlc query --unresolved-reviews           # every open finding in the project (one table)
md_sdlc query --unresolved-reviews --story STORY-01-first-slice   # scoped to one Story

stats

Status statistics — the spread, not just a single filtered list.

md_sdlc stats                                # project-wide type x status matrix + stability + open findings
md_sdlc stats --epic EPIC-01-checkout-redesign   # one Epic: its Stories and Tasks broken down
md_sdlc stats --by owner                     # counts grouped by owner (or: stability, scenario)
md_sdlc stats --json                         # same numbers as JSON, for scripting

new

Scaffold a new item from a template, at the correct nested path, with the next sequence number computed automatically for Story/Task.

md_sdlc new proposal --title "Idea for X" --owner YourName
md_sdlc new epic --title "Big Initiative" --originated-from PROPOSAL-idea-for-x
md_sdlc new story --parent EPIC-big-initiative --title "First Slice" --owner YourName
md_sdlc new task --parent STORY-01-first-slice --title "Do the thing" --owner YourName --scenario bug
md_sdlc new adhoc --title "Prod hotfix" --owner YourName --scenario hotfix

Refuses to create a child under a parent that doesn't exist or has a terminal status (done/abandoned/superseded).

promote

Two promotion paths — acceptance is a spawning event, not just a status change:

# a tasks.md row outgrew a row - give it its own file
md_sdlc promote TASK-01 --to-file --story STORY-01-first-slice

# a Proposal was accepted - spawn an Epic, link lineage both ways
md_sdlc promote PROPOSAL-idea-for-x --to-epic

A promoted Proposal gets status: done (its job — leading to a decision — is finished) rather than a new vocabulary word; the resulting Epic carries originated_from: and the Proposal carries promoted:.

review

Multi-agent (or multi-person) handoff: one agent designs/codes a Story, a second reviews it and writes findings to STORY-xx/review.md, the first agent fixes and resolves them.

# reviewer reports a finding
md_sdlc review report --story STORY-01-first-slice \
  --summary "Race condition in retry logic" --severity critical --reported-by agent-2

# author resolves it after fixing
md_sdlc review resolve RVW-01 --story STORY-01-first-slice --status fixed

--status accepts fixed, wontfix, or changes-requested (sends it back for another look). Findings use their own status vocabulary (open -> changes-requested -> fixed/wontfix), separate from task/story status — see CONVENTIONS.md §5. The Story's own status can be in-review while its review.md still has open rows.

handover

Print a self-contained context pack for one Story — branch name, the project's AI-RULES.md, the Story body, its open task rows, its touches: list, linked artifacts, and open review findings — for pasting into an AI coding agent. Only that Story: no siblings, no parent Epic body.

md_sdlc handover STORY-01-first-slice
md_sdlc handover STORY-01-first-slice --full     # also inline linked artifact bodies
md_sdlc handover STORY-01-first-slice --strict   # refuse if `validate` currently fails

archive

Move a terminal-status item to hist/. Refuses on any non-terminal status — no --force in Phase 1.

md_sdlc archive TASK-01-do-the-thing

status (0.4.0)

The whole project on one screen: done / now / later counts, tracks (steps done/total, open items, next, waits on), on site, waiting for check, blocked, decisions for the owner, waiting on others, experiments, recent, not touched N days, ideas, parked. In short layout backlog writes the same view to backlog.md.

md_sdlc status
md_sdlc status PAY1          # one item, everything under it, and its rows
md_sdlc status --json        # same data for scripts / a UI

set / item / log / adopt (0.4.0)

md_sdlc set PAY1-A --status in-review --next "field check" --waits-on site
md_sdlc set EXP-cache-size --result "64 MB hit 97%" --verdict yes
md_sdlc set PAY1 --deployed "release-4 2026-10-25"
md_sdlc set PAY1-A --waits-on ""                    # empty value removes the field
md_sdlc item add PAY1-A "check postcode field" --waits site
md_sdlc item set PAY1-A.1 done
md_sdlc log PAY1 "kickoff done"                     # dated line on top of log.md
md_sdlc adopt plan/old-notes.md --type adhoc --id ADHOC-old-notes --status parked

set keeps every other header field, the key order and the body. adopt adds a header to an existing file without moving it. See CONVENTIONS §14.

Architecture

A BaseCommand ABC, one plugin per subcommand under plugins/<name>/plugin.py. In dev mode, plugins/__init__.py auto-discovers plugins by walking the folder — dropping in a new plugin folder is the entire integration step. In the compiled exe, plugin discovery falls back to a fixed import list (sys.frozen check), since a frozen binary has no real plugins/ directory to walk and PyInstaller's static analysis can't see dynamically-imported modules — that list must be kept in sync with plugins/*/plugin.py when a plugin is added or removed.

core/repo.py's AiDocsRepo is the only class that touches the filesystem; every plugin goes through it. core/profile.py resolves the current project by walking up for .sdlc/config.yml (see "Initializing a project" above).

md-sdlc/
  md_sdlc.spec               # PyInstaller build spec (bundles plugins/new/templates as data)
  sdlc_tool.py                # CLI entry point
  core/
    profile.py                 # git-style .sdlc/ marker discovery
    frontmatter.py               # parse/write YAML frontmatter (pure serialization)
    models.py                     # WorkItemFile, TaskRow, ReviewRow dataclasses
    vocab.py                       # fixed type/status/scenario vocabulary + slugify
    repo.py                         # AiDocsRepo - the only filesystem touchpoint
  plugins/
    base_plugin.py                # BaseCommand(ABC)
    validate/ backlog/ query/ stats/ new/ promote/ archive/ init/ review/
    conventions/ handover/
  tests/
    fixtures/                     # committed regression fixtures, incl. one
                                   # deliberate broken-link violation for validate
  dist/
    md_sdlc.exe                    # built binary (gitignored by default - see .gitignore)

Non-goals (Phase 1)

  • No Jira/ADO sync (the plugin architecture leaves room for a future jira_sync/ plugin, but it isn't built yet)
  • No migration of existing docs into this convention — applies going forward only
  • No web UI — CLI output only
  • No automatic/scheduled archival
  • No cross-project aggregation queries (each project is targeted independently)

About

File-based Epic/Story/Task tracking CLI for markdown docs — a lightweight, git-style alternative to a JIRA board.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages