← Back to list

How I Ship an Open-Source Project Solo: The Docling Studio Playbook

A small commit, in January 2026

Pier-Jean Malandrino in Scub-Lab · 2026-05-13 12:43 · 60 claps · 9.7 min read
#docling #ai #data-science #ocr #software-development
Open on Medium ↗
Wiki topics: ML · Machine Learning AI · AI · General 🔓 · Open Source 🔬 · Science · General

Photo by Baptiste Buisson on Unsplash

Photo by Baptiste Buisson on Unsplash

How I Ship an Open-Source Project Solo: The Docling Studio Playbook

A small commit, in January 2026

Linus Torvalds pushed a hobby project to GitHub called AudioNoise, a repo for experimenting with digital guitar pedal effects. At the bottom of the README, he noted that the Python visualizer had basically been written by vibe-coding. He doesn’t know Python that well, so rather than typing it himself, he “cut out the middle-man” and used Google Antigravity to generate it directly.

Reporting: Phoronix, It’s FOSS, ZDNet via Slashdot.

This is the same Linus who had spent years dismissing AI hype as “hilarious to watch”. He still wrote the C audio filters himself (his domain). He delegated the Python (not his domain). Two parts of the same project, two completely different decisions. He’s vibe-coding the hobby visualizer. He is not vibe-coding the Linux kernel.

If even Linus is making that call explicitly, in writing, in a public README, the question for the rest of us gets concrete: how do you build with AI, solo, and still ship code that holds up?

This is how I do it for Docling Studio.

Context

I’m CTO of SCUB, an 80-person services company in France. The company allocates me a fraction of internal time for OSS and R&D work, and I extend that with personal time. Docling Studio is built within that envelope. It is not a full-time job, and it is not a side project I work on in evenings without a structure. It is a constrained, recurring bandwidth, which is exactly why discipline matters: the project has to ship without ever pretending I have a team behind me.

The stack I maintain

Docling Studio is an open-source visual inspection and debugging tool for Docling, the document AI engine maintained by IBM Research at the Linux Foundation.

Everything in this list is on me:

  • Backend: FastAPI, hexagonal architecture, Python 3.12
  • Frontend: Vue 3, TypeScript strict, Vite, Pinia
  • Tests: 446 backend (pytest), 202 frontend (Vitest)
  • E2E: Karate API and Karate UI, Chrome headless
  • Docker: multi-arch (amd64, arm64), two build targets (remote, local)
  • CI/CD: GitHub Actions
  • Releases, docs (MkDocs Material on GitHub Pages), community

The code is not a prototype. Every release passes through a 12-axis audit with a GO / NO-GO gate. Below 80, the merge doesn’t happen. And as I’ll show below, a passing aggregate score is not enough on its own.

The whole machinery is open-source:

👉 The playbook:

[embed]GitHub - pjmalandrino/docling-studio-playbook: The open-source maintainer's playbook - structured… The open-source maintainer's playbook - structured Markdown templates for every process you need to run a project solo…github.com

Why a playbook

When you’re solo, the bottleneck isn’t writing code. With an AI assistant, you can output a feature in an evening. The bottleneck is everything a team normally does without thinking about it: someone reviewing your PR, someone catching that the domain layer just imported FastAPI, someone asking if you updated the changelog, someone calling out the except: pass.

Nobody does that when you’re alone. The codebase degrades slowly enough that you don’t notice until you do.

The playbook writes that team down. Twelve processes, each with the same five sections:

  • When to trigger: what event activates this process
  • Steps to follow: the ordered checklist
  • Red flags: failure modes to detect early
  • Excuses not to accept: the bad arguments I know I’ll make against my own rules
  • How to verify: the audit trail

About that “excuses” section

When you’re solo, the person trying to skip a process and the person enforcing it are the same person. The fight is internal, and most of the time you lose it: the change is small, the tests pass, the diff looks fine, just merge.

So I wrote the answers down in advance. For each process, a small table of recurring excuses and the response. Example, from the release audit process:

ExcuseResponse”It’s a small change, review is overkill”Small changes introduce subtle bugs. Review catches what tests miss.”We audited last release, nothing changed much”Code changed. Dependencies changed. The audit catches drift.”Score is 78, close enough to 80"78 is GO CONDITIONAL, not GO. Write the remediation plan.

The argument is resolved up front. At release time, I just look it up. I don’t get to re-litigate it in the moment.

The 12 processes

Each one is a markdown file in the repo:

  • Commits & PR: conventional commits, branch naming, PR template
  • Code review: solo checklist, layer by layer ; done locally before each PR’s
  • Merge policy: what must be green before merge
  • Release procedure: branching, versioning, audit, artifacts
  • Hotfix flow: emergency path with reduced but explicit gates
  • Rollback: what to revert, how to communicate
  • Incident response: triage, comms, post-mortem
  • Security response: CVE handling, dependency audit
  • ADRs: when to write one, the template
  • Issue triage: labels, prioritization
  • Documentation: changelog discipline, what gets a doc page
  • Audit: the 12-axis review, the scoring rubric

Same shape every time. Easy to fork, strip, adapt.

The 12-axis audit

Every release branch gets scored across:

  1. Hexagonal architecture: domain isolation, no layer leaks on my backend
  2. DDD: bounded contexts, value objects, ubiquitous language. Neccesary to interact properly in conception phases.
  3. Clean code: naming, file size, readability
  4. KISS: no over-engineering
  5. DRY: duplication detection
  6. SOLID: the five principles
  7. Decoupling: frontend/backend contracts, ports & adapters
  8. Security: OWASP, secrets, injection, CORS
  9. Tests: coverage, quality, determinism
  10. CI/Build: pipeline health, Docker checks
  11. Documentation: changelog, TODO tracking
  12. Performance: N+1 queries, async I/O, memory

Weighted score. Thresholds:

  • 80+ = GO
  • 60 to 79 = GO CONDITIONAL with remediation plan
  • Below 60 = NO-GO
  • Any critical finding = NO-GO regardless of score

Release 0.5.0, in practice

The full audit ran on 2026–04–28 against release/0.5.0. Global score: 84.2 / 100. Well above the 80 threshold, with 11 of the 12 axes already in GO or GO CONDITIONAL.

Verdict: NO-GO.

The Documentation axis came in at 44/100, with one CRITICAL finding: CHANGELOG.md had no [0.5.0] section. The last entry was still [0.4.0] from two weeks earlier. Tagging from that HEAD would have shipped a changelog that silently omitted everything new in the release: reasoning-trace viewer, Neo4j graph storage, RAG endpoints, feature flags. The code was solid. The story shipped to users would have been a lie.

This is exactly why the gate has the absolute rule. 84.2/100 sounds like a passing grade. A lying changelog isn’t a passing grade.

A couple of MAJOR findings caught in the same pass, to give a feel for the depth:

  • Performance (axis 12): synchronous file I/O sitting inside an async FastAPI endpoint. Path(...).read_bytes and generate_preview were being called directly from api/documents.py, blocking the event loop on every request. The async def signature made it look correct, the tests passed, the response came back. The server just stopped handling anything else while a document was being read. Fix: wrap both calls in asyncio.to_thread(...). This is the kind of finding that explains why the gate exists in the first place: locally correct, plausible to a reviewer skimming the diff, structurally wrong.
  • DDD (axis 02): ubiquitous language drift. HTTP path params said {job_id}, the domain talked about analyses. Fix: rename to {analysis_id} across api/analyses.py and api/ingestion.py. URLs stay identical for clients, the codebase stops being bilingual.

The next day, on fix/release-0.5.0-audit-remediation, every CRITICAL and MAJOR finding was resolved. The re-audit ran on the same scope. CRIT 1 → 0. MAJ 12 → 0. All twelve axes in GO. Aggregate score: ~94/100. Only then did the 0.5.0 tag get cleared to ship.

Part of the audit runs automated in CI: domain layer imports, file size, magic numbers, secret patterns, SQL injection patterns, test coverage. The rest is review against the playbook rubric, where design intent matters more than syntax. That’s the layer an LLM can’t audit on its own initiative, because it wrote the code in the first place. It can run the rubric. It can’t decide what the rubric should be.

The commands I run on every commit

Same commands every time. No improvisation.

Backend:

ruff check .          # lint
ruff format .         # format
pytest                # 446 tests

Frontend:

npm run lint:fix      # ESLint autofix
npm run format        # Prettier write
npm run lint          # ESLint verify
npm run format:check  # Prettier verify
npm run type-check    # vue-tsc --noEmit
npm run test:run      # Vitest, 202 tests

If any step fails, fix and re-run. No “I’ll fix the lint later.” The pipeline blocks the commit.

The same commands are pinned in CLAUDE.md files in each subproject, so when I use Claude Code, it runs the same pipeline. Same commands, same order, same failure handling.

The release gate

On a release branch, four phases run in CI, in sequence. Each phase must pass before the next starts.

Phase 1: Parallel checks

  • Backend lint (Ruff)
  • Frontend lint (ESLint)
  • Type-check (vue-tsc)
  • Backend tests (pytest)
  • Frontend tests (Vitest)
  • Dependency audit (pip-audit, npm audit)
  • Automated audit script (the parts of the 12-axis review that can be automated)

Phase 2: Docker builds

  • Build remote target (amd64, arm64)
  • Build local target (amd64, arm64)
  • Trivy security scan on each image
  • Image size tracking against baseline

Phase 3: End-to-end

  • Karate API tests against the built image
  • Karate UI tests in Chrome headless

E2E deserves a paragraph of its own, because it’s the part of the pipeline most under-invested in solo projects and it shouldn’t be. Non-regression testing is the single most expensive activity a solo maintainer can take on manually. Every release means clicking through the same flows, checking the same edge cases, verifying the same import and conversion paths. After three releases you stop doing it properly. After five you stop doing it at all. Automation isn’t a nice-to-have here, it’s the only reason the project survives past version 0.2. Karate API covers the contract surface (the FastAPI endpoints still behave the same way, the same payloads still come back). Karate UI covers the user-facing flows (the document still renders, the chunks still split, the export still produces the expected output). Together they replace the QA team I don’t have, and they catch the silent regressions that 446 backend unit tests will let through, because unit tests check that the parts work and E2E checks that the whole still works.

Phase 4: Verdict

  • Automated comment on the release PR: GO / GO CONDITIONAL / NO-GO
  • If GO CONDITIONAL or NO-GO, a remediation plan must be added before merge

Standard CI tells you “the code compiles and the known tests pass.” The release gate tells you “this is ready to be read, judged, and used by other humans.” For an open-source project, the second statement is the one that matters.

How AI fits, and what I’m changing about it

I use Claude Code as a development accelerator. I’ve been transparent about that in writing for over a year now.

What I’ve been doing. To keep the git history clean and readable, I’ve systematically stripped any Co-authored-by: Claude or similar AI attribution from commits before pushing. I mention the use of AI publicly, in articles, in READMEs, in conference talks. But the commit log itself looks like the code came from a single human author. The reasoning was practical: tooling and reviewers tend to overreact to AI attribution, the relevant accountability (who broke it, who fixes it) is mine regardless, and a noisy trailer block doesn't help anyone reading git log.

Why I’m changing that. In April 2026, the Linux kernel project merged a formal policy on AI-assisted code into [Documentation/process/coding-assistants.rst](https://docs.kernel.org/process/coding-assistants.html). The three rules, as covered by Tom's Hardware and the official kernel docs:

  1. AI-assisted code is allowed in the kernel.
  2. The human submitter signs the DCO and owns every line, including bugs and security flaws. AI cannot sign Signed-off-by.
  3. Meaningful AI use must be disclosed with an Assisted-by: tag in the commit trailer. Not Co-developed-by. Not Generated-by. Specifically Assisted-by, to reflect AI as a tool rather than a co-author.

That’s now the de-facto standard for serious OSS. Stripping AI attribution from commits, even with full public disclosure elsewhere, no longer holds up against that bar. Going forward, Docling Studio commits with meaningful Claude Code involvement will carry an Assisted-by: trailer. The DCO signoff stays mine. The accountability stays mine. The audit trail stops being aspirational and becomes precise.

The rest doesn’t change. The AI runs inside the rails: lint, format, type-check, test, audit. The CLAUDE.md files keep pinning the validation pipeline. The 12-axis review still gates the release. The AI doesn't replace engineering judgment, it amplifies whatever system is already in place. If the system is "write code and push," AI helps push more undisciplined code, faster. If the system is a structured pipeline with audit gates, the AI runs inside those rails.

This is also the practical version of the argument I made earlier on GitConnected: the real AI coding skill isn’t prompting, it’s architecture. Prompt quality has diminishing returns. The models improve on their own. The rails don’t.

The stack, in one table

LayerStackQualityBackendFastAPI, Python 3.12, aiosqliteRuff, pytest (446 tests)FrontendVue 3, TypeScript strict, Vite, PiniaESLint 9, Prettier, Vitest (202)E2EKarate API, Karate UIMaven, Chrome headlessInfraDocker multi-target, Nginx, docker-composeGitHub Actions CI/CDDocsMkDocs MaterialGitHub Pages

Two Docker build targets: remote (lightweight, delegates conversion to Docling Serve) and local (standalone, embedded AI models). Multi-arch: amd64, arm64. Semantic versioning auto-injected at build time. Three compose stacks: production, development (hot-reload), ingestion (OpenSearch and embedding service).

All of this runs from one developer’s machine and GitHub Actions. No DevOps team, no dedicated QA. Just the playbook and the gates.

👉 You can find detailed informations here :

[embed]Designing Docling Studio: Key Architecture Decisions Designing Docling Studio: a visual tool for inspecting document extraction, with key architecture decisions…dzone.com

If you’re building solo

Don’t skip the discipline because there’s no team to enforce it. Especially now that AI lets you skip it faster.

Write the playbook. Pin the validation commands. Define your audit axes. Decide your GO threshold. Automate what you can, manually review what you can’t. Make those decisions once, in writing, when calm.

The Docling Studio playbook is open-source. Fork it, strip what doesn’t apply, keep the structure:

👉 The playbook:

[embed]GitHub - pjmalandrino/docling-studio-playbook: The open-source maintainer's playbook - structured… The open-source maintainer's playbook - structured Markdown templates for every process you need to run a project solo…github.com

👉 The project:

[embed]GitHub - scub-france/docling-Studio: Documentation Documentation. Contribute to scub-france/docling-Studio development by creating an account on GitHub.github.com

I’m CTO at SCUB, a French digital services company, and AI Ambassador for the French Ministry of Economy (“Osez l’IA”). I design production AI systems, contribute to open-source tooling around document AI, and write about the intersection of architecture and AI-assisted development. Docling Studio is built at SCUB.

Thanks for reading. If this was useful, a 👏 helps others find it. Comments and contributions welcome.


메타데이터
post_id
3d374fb6514f
slug
how-i-ship-an-open-source-project-solo-the-docling-studio-playbook-3d374fb6514f
url
https://lab.scub.net/how-i-ship-an-open-source-project-solo-the-docling-studio-playbook-3d374fb6514f
canonical_url
https://lab.scub.net/how-i-ship-an-open-source-project-solo-the-docling-studio-playbook-3d374fb6514f
author_url
https://medium.com/@pmalandrino
status
ok
fetched_at
2026-06-09 15:37:30