TL;DR: Stop maintaining INDEX.md by hand. Add YAML frontmatter to every paste-ready file, then a 30-line scanner generates the index automatically. I have 50+ articles, 14 newsletters, and 6 SKU files all auto-indexed via this pattern.
The problem with INDEX.md
You start writing article 1. You add it to INDEX.md. Article 2 — you add it. Article 3, 4, 5...
By article 30, INDEX.md is out of sync. You forgot to add 3 articles. You added one with the wrong status. The status: ready flag is wrong because you published it but didn't update INDEX.md.
This is the classic "human-maintained source of truth" anti-pattern. Don't do this.
The frontmatter pattern
Every paste-ready file gets a YAML block at the top:
---
id: devto-55-manifest-frontmatter-content
title: "dev.to #55 - YAML Frontmatter for Content Pipelines"
category: content
priority: P1
status: ready
eta_min: 5
actions: [preview, copy-clipboard, open-devto]
tags: [content, yaml, automation]
created: 2026-05-07
publish_target_date: 2026-05-12
---
# YAML Frontmatter for Content Pipelines
(article body)
The frontmatter has 9 fields:
-
id— unique kebab-case ID -
title— display title -
category— content / product / roadmap / kit -
priority— P0 (today) / P1 (this week) / P2 (next month) -
status— draft / ready / live / published / archived -
eta_min— how long to publish/use it -
actions— array of dashboard buttons to render -
tags— for searchability -
created/publish_target_date— calendar tracking
The 30-line scanner
import yaml
from pathlib import Path
def scan_assets(root: Path):
assets = []
for md_file in root.rglob("*.md"):
content = md_file.read_text(encoding='utf-8-sig')
if not content.startswith("---"):
continue
try:
_, yaml_block, _ = content.split("---", 2)
except ValueError:
continue
try:
meta = yaml.safe_load(yaml_block)
except yaml.YAMLError:
continue
if not isinstance(meta, dict) or "id" not in meta:
continue
meta['_path'] = str(md_file.relative_to(root))
assets.append(meta)
return sorted(assets, key=lambda a: a.get('publish_target_date', '0'))
# Usage
assets = scan_assets(Path("reports"))
for a in assets:
print(f"[{a['category']:8}] {a['priority']} {a['id']:40} → {a['_path']}")
Output:
[content ] P0 devto-50-50-articles-checkpoint → devto-article-50-paste-ready.md
[content ] P0 substack-23-day-60-milestone → substack-issue-23-day-60-milestone-paste-ready.md
[content ] P1 devto-55-manifest-frontmatter-content → devto-article-55-paste-ready.md
...
No manual updates. Adding a new file with frontmatter automatically appears in the index.
Dashboard rendering
The dashboard reads the frontmatter and renders it as a table:
from flask import render_template_string
@app.route('/assets')
def assets():
rows = scan_assets(Path("reports"))
return render_template_string(TABLE_HTML, rows=rows)
TABLE_HTML = """
<table>
{% for r in rows %}
<tr class="{{r.priority}}">
<td>{{r.category}}</td>
<td>{{r.id}}</td>
<td>{{r.status}}</td>
<td>{{r.publish_target_date}}</td>
<td>
{% if 'preview' in r.actions %}<a href="/preview/{{r._path}}">preview</a>{% endif %}
{% if 'copy-clipboard' in r.actions %}<button onclick="copy('{{r._path}}')">copy</button>{% endif %}
{% if 'open-devto' in r.actions %}<a href="https://dev.to/new" target="_blank">dev.to</a>{% endif %}
</td>
</tr>
{% endfor %}
</table>
"""
Three columns: ID, status, publish target date. Three buttons: preview, copy clipboard, open editor. Auto-generated from frontmatter.
Status transitions
The status field has 5 values that transition:
draft → ready → live (Substack/Gumroad) | published (dev.to) → archived
Update via shell one-liner:
sed -i 's/^status: ready$/status: published/' reports/devto-article-50-paste-ready.md
Or your dashboard exposes a button per row that does it.
What this saves
Manual INDEX.md update flow:
- Write article → save file → open INDEX.md → add row → fix sort → save → commit
Frontmatter flow:
- Write article (with frontmatter at top) → save file → done
Per-article time saved: 60 seconds.
For 50 articles: 50 minutes saved + zero index drift.
Bonus: stale-file detection
Once frontmatter is in place, you can write a stale-check:
from datetime import datetime, timedelta
stale = []
for a in scan_assets(Path("reports")):
target = datetime.strptime(a.get('publish_target_date', '2099-01-01'), '%Y-%m-%d')
if a['status'] in ['draft', 'ready'] and target < datetime.now() - timedelta(days=7):
stale.append(a)
if stale:
print(f"⚠ {len(stale)} stale assets:")
for s in stale:
print(f" {s['id']} (target {s['publish_target_date']})")
Run this in cron daily. Flags any draft you forgot to publish.
Source
Full manifest scanner + dashboard + stale checker:
AutoApp Dashboard ($39) includes:
-
manifest_scan.py(this article) -
dashboard/app.py(Flask + frontmatter rendering) -
stale_checker.py(cron-friendly) - 60+ paste-ready files with frontmatter as examples
If you have 10+ markdown files and a hand-maintained INDEX.md, you have a frontmatter-shaped problem. Solve it once.
Top comments (0)