The fork is the wrong unit of work.
The usual open-source answer is to fork the repo and start deleting things.
That works for a week. Then upstream changes, and now your fork has two jobs. It has to preserve the upstream improvements you still care about, and it has to remember every local decision you made about what not to inherit.
Most forks are bad at remembering intent. They remember files.
Upstreamer treats the local intent as the thing to preserve. Each downstream gets an upstreamer.md contract that says what to keep, what to adapt, what to remove, and how to tell whether the result is any good.
The generated downstream is output. The contract is the product spec.
GStack has amazing skills. And a lot of other stuff.
I like a lot of what is in GStack. The skills are useful. The taste is good. There is real workflow knowledge in there.
I do not want the setup machinery, templating, installer behavior, generated files, telemetry-shaped plumbing, and host-specific assumptions. For my use case, the best version of that project is a directory of markdown skills.
The first concrete example is tstack. Upstream is garrytan/gstack. Downstream is a markdown-only skill collection.
The contract keeps
- Portable workflows, checklists, rubrics, prompts, review criteria, and safety gates.
- One
SKILL.mdper skill directory. - A downstream installable as plain agent skills.
The contract drops
- Helper binaries, package infrastructure, telemetry, generated templates, browser daemons, and session state.
- GStack-specific setup and host assumptions.
- Anything that makes TStack more than the portable workflows.
Mechanically valid. Spiritually dead.
That is not a fork in the normal sense. It is a translation.
The upstream project can keep being itself. It can have its installer, templates, local runtime assumptions, and whatever complexity makes sense for that project.
TStack can keep being a much smaller thing: the portable agent workflows, rewritten as markdown.
Mechanical checks are not enough. Evals make sure the downstream stays coherent, useful, and valid as upstream changes.
Last30Days had the right idea in the wrong shape for us.
The second example is last30days-ts.
Upstream is mvanhorn/last30days-skill. The concept is excellent: research what people have said about a topic recently, across public sources, then produce a concise brief with citations.
The upstream implementation is Python. It worked, but for my use case it was clunky to run, and I wanted the result to match a TypeScript/Bun workflow. I also wanted to choose different source trade-offs.
So the downstream contract converts it into an installable agent skill with bundled Bun/TypeScript source.
last30days/
├── SKILL.md
├── references/
│ ├── INSTALL.md
│ ├── planning.md
│ └── reranking.md
└── scripts/
└── last30days/
├── package.json
├── bun.lock
├── src/
└── test/
Installing the skill gives you the instructions and the code. No separate global package. No "go find the real project somewhere else" step.
Upstream projects should not have to become kitchen sinks.
Open-source maintainers get pulled toward kitchen-sink repos because users ask for every runtime, every integration, every deployment target, every packaging style, every auth model, every adapter.
Sometimes those features belong upstream. Often they do not.
With a contract-driven downstream, upstream can stay focused. It can expose the behavior, concepts, tests, examples, and docs that matter. Downstream users can create their own feature streams without asking upstream to absorb every preference.
The Python project can stay Python. The TypeScript downstream can exist anyway.
The full framework can keep its installer. The markdown-only skill collection can exist anyway.
Open source should look more like a graph.
We usually talk about open-source projects as if there is one canonical codebase and everything else is a fork, a plugin, or a package.
That model is too narrow.
A healthier ecosystem looks more like a graph. The edges are not random copies. They are contracts.
Each edge says how to translate upstream into a downstream that has its own purpose. That is the promise of synthetic codebases. Not AI-generated forks for the sake of more code. Maintained translations with explicit intent.
How upstreamer implements this today.
The current implementation is small on purpose.
./scripts/upstream <name>
That wrapper reads codebases/<name>/upstreamer.md, fetches upstream, invokes the converter skill when needed, runs mechanical verification, runs qualitative eval when configured, and writes sync state only after success.
If eval fails, the converter is supposed to fix and rerun it. If it cannot make the eval pass, it writes .upstreamer/eval-report.md and leaves state unchanged.
Try the generated skills.
Then read the contracts. The contracts are where the interesting product decisions live.
The goal is not to remove judgment from downstream maintenance. The goal is to write the judgment down so it can run again when upstream changes.
npx skills add mountgram/upstreamer/codebases/tstack/downstream --skill '*'
npx skills add mountgram/upstreamer/codebases/last30days-ts/downstream --skill last30days
A short explainer.
The same idea, with more movement: one source repository, one written contract, and a downstream codebase that keeps its own shape.