Skip to content

MaksimZinovev/docfence

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

53 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

docfence

Validate markdown specs from the inside out.

β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•
β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ•”β•β•β•
β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
β•šβ•β•β•β•β•β•  β•šβ•β•β•β•β•β•  β•šβ•β•β•β•β•β•β•šβ•β•     β•šβ•β•β•β•β•β•β•β•šβ•β•  β•šβ•β•β•β• β•šβ•β•β•β•β•β•β•šβ•β•β•β•β•β•β•

What β€’ Install β€’ Quick Start β€’ Usage β€’ Rules β€’ Types


What

A tiny CLI that validates markdown spec docs against rules embedded directly in the document. No external dependencies. Drop it in your project and your AI agent can use it immediately.

Why

AI assistants drift. They sneak in TODOs, break links, forget required sections, and blow past length limits. docfence lets you define the rules inside the doc itself β€” per section or for the whole document β€” and catch issues before they compound.

Install

pip install -e .

After that, docfence is available globally β€” run it from any folder.

Quick Start

# scaffold a new doc
docfence new feature > docs/my-feature.md

# validate one file
docfence validate docs/my-feature.md

# validate a whole folder
docfence validate docs/

# stamp a clean file with a timestamp
docfence stamp docs/my-feature.md

# list available types
docfence types

Note: docfence looks for .docfence/types/ relative to the target path. Run validate from your project root so type definitions are found.

How it Works

πŸ“„ See sample doc β€” bad-feature.md (has errors)
---
id: F-002
type: feature
status: brainstorm
owner:
depends_on: []
last_validated: ~
---

```spec
scope: document
type: feature
required_sections: [Overview, Implementation]
max_chars: 500
banned_words: [TODO, TBD]
```

We want to let users export data. TODO: figure out formats. TBD for now.

```spec
type: feature
max_chars: 200
banned_words: [TODO, TBD]
```

We will build background jobs. TODO add progress tracking.
$ docfence validate sample-docs/
#              ↑ run on a folder to validate all .md files inside

sample-docs/                                   									# ← folder root
β”œβ”€β”€ βœ— bad-feature.md                           									# βœ— = has errors (stamp blocked)
β”‚   β”œβ”€β”€ L1 frontmatter (type: feature)         									# ← L1 = line 1; frontmatter checks come from the type definition
β”‚   β”‚   β”œβ”€β”€ βœ— frontmatter: missing required field 'owner'       # 'owner' is required_fields in the type .toml
β”‚   β”‚   └── βœ— status: 'brainstorm' not valid β†’ allowed: draft, active, frozen, done  # status must be in type's statuses list
β”‚   β”œβ”€β”€ L4 spec block (type: feature, scope: document)           # ← L4 = line 4; scope: document = rules apply to whole file
β”‚   β”‚   β”œβ”€β”€ βœ— banned_words: 'TODO' found in content              # banned_words rule caught 'TODO' in the document body
β”‚   β”‚   └── βœ— banned_words: 'TBD' found in content               # same rule, second hit β€” each banned word is a separate issue
β”‚   └── L18 spec block (type: feature)         # ← no scope = section-level; rules only apply to text after this fence
β”‚       └── βœ— banned_words: 'TODO' found in content
β”œβ”€β”€ βœ“ exploration-auth.md                       # βœ“ = clean, no issues found
β”œβ”€β”€ βœ“ good-feature.md
└── ⚠ test-match.md                           # ⚠ = warnings only (non-blocking)
    └── L18 spec block (type: feature)
        └── ⚠ inherited: uses inherited defaults for banned_words  # rule wasn't set in the block; fell back to type defaults

4 files  5 errors  1 warning                    # ← summary: errors block stamp, warnings are advisory

Usage

docfence validate <file|folder>         # validate one file or all .md in folder
docfence validate <file|folder> --verbose  # show passing checks + section headings
docfence new <type>                       # scaffold a doc with df-todo placeholders
docfence new <type> --output <path>        # scaffold and write to file
docfence new <type> --set owner=alice     # override frontmatter defaults
docfence types                             # list all available types
docfence stamp <file>                      # write last_validated timestamp (only if clean)

Verbose Mode

Pass --verbose to see passing checks and section headings. β†’ VERBOSE.md

Doc Types

Built-in fallback: story task feature design exploration research persona pov brainstorm roadmap flow wireframe prototype test brand handoff

Adding a new type β€” create .docfence/types/mytype.toml:

name = "mytype"
statuses = ["draft", "active", "done"]
required_fields = ["id", "status", "owner"]

[defaults]
max_chars = 1500
banned_words = ["TODO", "TBD"]

No core changes needed. docfence types will pick it up automatically.

Spec Block Syntax

Place ```spec ``` fences in your markdown to embed validation rules inline.

Section-level β€” rules apply to the text that follows the block:

```spec
type: feature
max_chars: 800
banned_words: [TODO, TBD, placeholder]
validate: [file_exists]
```

Your content here...

- src/auth/login.py

Document-wide β€” rules apply to the whole file (put near the top):

```spec
scope: document
type: feature
required_sections: [Overview, Acceptance Criteria]
max_chars: 5000
```

Section-level spec block placement

Section-level spec blocks validate the text after them. Place them at the top of the section (immediately after the ## heading), before any content or df-todo blocks:

## Context

```spec
type: plan
banned_words: [possibly, perhaps]
match:
  has_problem: '(problem|issue|bug)'
```

The actual section content goes here...

If a spec block is at the end of a section, the validator sees empty text and all match rules produce false positives. docfence will emit a πŸ’‘ hint when it detects this:

πŸ’‘ spec-placement: spec block has no content after it β€” move to top of section
  so validation sees the section content. See README: Section-level spec blocks

The scaffold generator (docfence new <type>) already places spec blocks at the top of each section β€” you only need to worry about this when editing existing documents.

All Rules

field example what it checks
max_chars max_chars: 800 sibling text must be shorter
banned_words banned_words: [TODO, TBD] none of these appear in sibling text
match match: + indented label: "regex" at least one line matches each named pattern
validate: [file_exists] every line in sibling text is a real path
validate: [valid_url] every http line in sibling text is reachable
placeholders placeholders: ["```df-todo"] unfilled placeholder blocks remain in doc
required_sections required_sections: [Overview] document-scope only; heading must exist

match example:

```spec
type: feature
max_chars: 1000
match:
  data_point: "^\\- .{30,}$"
  source_link: "Source: https?://.+"
```

Each label: pattern entry must match at least one line. Use (?i) for case-insensitive matching. Errors reference the label, not the raw regex.

Warnings vs Errors vs Hints

  • βœ— ERR β€” rule violation; stamp is blocked until fixed
  • ⚠ WARN β€” inherited defaults or unknown type; worth reviewing, not a blocker
  • πŸ’‘ HINT β€” advisory suggestion; not a blocker, often a migration nudge

Block IDs

Each spec block gets a bid (8-char sha256 of its sibling text). If content changes between runs, the bid changes β€” useful for AI agents to detect drift.

About

🌡Tiny CLI that validates markdown spec docs against rules embedded directly in the document

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages