TL;DR: I had 50+ markdown files across reports/, products/, INBOX/. Maintaining INDEX.md by hand was painful. Replacing it with YAML frontmatter on every file + a 200-line Python scanner cut indie dev overhead by ~30 min/day. Here's the schema and the scanner.
The pain
Every indie hacker hits this point: you have ~30 markdown files across categories. You write an INDEX.md to catalog them. By the time you ship 5 more files, INDEX.md is stale.
You update INDEX.md. Two days later, stale again.
You write a script to auto-generate INDEX.md from filenames. It works. But filenames don't carry semantic info — priority, ETA, revenue forecast, status. The auto-INDEX is shallow.
The fix: stop maintaining a separate index file. Put metadata IN each file as YAML frontmatter. Have a scanner read all files, parse frontmatter, generate index views on demand.
This is the "manifest-first" pattern. Every file IS its own manifest entry.
The schema (5 required + 8 optional fields)
---
id: ios-pricing-decision # URL-safe slug, unique
title: iOS Pricing Decision Report
category: ios-pricing # one of N enums
priority: P0 # P0 / P1 / P2 / P3
status: ready # ready / scaffold / draft / done / archived
# Optional below
eta_min: 30 # minutes user-action time
revenue_usd_month: "200-500" # forecast revenue contribution
actions: [preview, copy-clipboard] # dashboard buttons to surface
tags: [ios, pricing, paywall] , free-form
ice_score: 6.48 # Impact × Confidence × Ease
tier_price_usd: 19.0 # if it's a paid SKU
command: python orchestrator/foo.py # if runnable
depends_on: [other-asset-id] # graph relationships
live_url: https://gumroad.com/... # if it's been published
---
That's 13 fields covering 95% of indie asset metadata. New types extend; the scanner reads only what's defined.
The scanner (200-line Python)
@dataclass
class Asset:
id: str
title: str
category: str
priority: str
status: str
path: str
file_type: str
eta_min: int | None = None
revenue_usd_month: str | None = None
actions: list[str] = field(default_factory=list)
tags: list[str] = field(default_factory=list)
ice_score: float | None = None
tier_price_usd: float | None = None
command: str | None = None
depends_on: list[str] = field(default_factory=list)
live_url: str | None = None
def scan_assets() -> list[Asset]:
discovered = []
for current_dir, dirnames, filenames in os.walk(ROOT):
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
for filename in filenames:
file_path = Path(current_dir) / filename
manifest = parse_frontmatter(file_path)
if manifest:
asset = manifest_to_asset(manifest, file_path)
if asset:
discovered.append(asset)
return discovered
The full scanner handles .md (YAML frontmatter), .py (manifest in docstring), .sh (manifest in comment block), .yaml (manifest.yaml at directory level).
The 3-panel dashboard
Once assets are scanned, the dashboard groups them three ways:
-
TODO panel — assets with
category: todoorpriority: P0and not yetstatus: done -
Check panel —
category: ideaandcategory: roadmapfor review-but-not-act -
Run panel — assets with
command:field, one-click execution
Plus aggregations:
- Categories grid — every asset by category enum
-
LIVE panel — every asset with
live_url:field (auto-shows public URLs) - Revenue tracker — pulls actual MRR per channel via dedicated aggregator
Why this beats a separate INDEX.md
1. Single source of truth
The asset's metadata lives WITH the asset. Move the file, metadata moves with it. Update the title, INDEX is automatically updated next scan.
2. New asset types extend, don't break
Add a new category? Just use the new value. Scanner doesn't care unless you add validation logic. Existing assets stay valid.
3. Programmatic queries
"Show me all P0 todos with status != done ordered by eta_min" is one line of Python. With INDEX.md you'd write a parser.
4. Dashboard is a view, not a maintenance burden
The dashboard renders on demand from the current state. Never out of sync.
5. Cross-references via depends_on
If asset-B depends on asset-A, you can build a dependency graph. Useful for: "what's blocking the iOS launch?" → traces to "asc-iap-pricing" being P0 and undone.
What I'd add if I were starting fresh
Asset versioning
YAML doesn't carry versions. If schema evolves, I'd add schema_version: 1 to enable scanner-side migration.
Asset trait inheritance
If category: gumroad-sku, certain fields should be required (tier_price_usd, etc.). Currently I rely on convention. A trait system would enforce.
Cross-asset health checks
Right now: each asset is parsed independently. If I have 10 assets that should all link to a canonical asc-iap-launch-sop, but one of them references a typo'd ID, no error. A health check would catch that.
What it took to migrate
I had ~50 markdown files. Migration:
- Wrote the schema doc (1 hour)
- Wrote the scanner (3 hours)
- Migrated 10 most-active files manually (~2 hours)
- Wrote a migration helper that grepped existing INDEX.md entries and added frontmatter to matching files (1 hour)
- Migrated remaining files via the helper, fixed edge cases (2 hours)
Total: ~9 hours. Payback: ~30 min/day in saved INDEX maintenance + dashboard utility = ~9 hours of net savings per month.
When NOT to do this
If you have <10 markdown files, hand-maintained INDEX.md is fine. The break-even is ~20-30 files.
If you don't need programmatic queries (you just want a static rendered list), a generated INDEX.md from filenames is simpler.
If your team is multi-person, syncing the schema becomes overhead. This pattern is best for solo indie scale.
Source
Full schema doc: github.com/jiejuefuyou/autoapp-toolkit/blob/main/MANIFEST_SCHEMA.md
Scanner code: github.com/jiejuefuyou/autoapp-toolkit/blob/main/dashboard/app.py
The dashboard itself is now a Gumroad SKU at $39: AutoApp Dashboard — Manifest-Driven Indie Tool Stack. Includes Flask backend + 3-panel UI + cron-friendly cleanup. MIT-friendly licensing for buyer use.
I wrote up the full 60-day indie iOS dev experiment that produced this dashboard pattern. iOS Indie Launch Playbook ($19) — 50pp PDF, real numbers, the manifest-first chapter included.
Top comments (0)