maniator / maniator/verticopolis
[Proposal]: Simplify agentic scaffolding, CI tests & repo
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 5
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
In the process of doing various changes and getting the agent to come up with a debugger framework it has been interesting to see how the agentic tooling approach works for projects like this. However also I think there is some overhead that can be trimmed back without introducing more regressions and this can both save a lot of tokens and reduce the time involved per task and patch. I think even adopting part of these ideas would be a nice time saver. Hopefully this isn't coming off as too nitpicky - i was puzzled about what exactly claude was even trying to do a fair bit of the time (altho I am not very savvy on these meta agentic workflows).
Some key points: It warned that screenshot regeneration, while helpful for preventing regressions, is also a big overhead on the CI process compared to most other things. The screenshots are also making the repo grow pretty quickly; they could be moved to a separate repo and processed on nightly only. It also found there are unnecessary agent files for platforms like roblox that are not used in the project. The backlog tracker being inside the repo also makes more churn for agents and apparently can drift - it may be better to just use gh and keep it all inside github not in the repo itself.
So here is the report I would say worth considering the various ideas.
Here is what I got out of claude when it meta analyzed the CI and agentic framework with a goal of maximizing time available for volunteers, i think it is helpful.
- Further idea: Move screenshots out of the repo #825
Reducing process overhead
Status: proposal, not adopted. Written by a volunteer contributor as a
discussion document for the maintainer.
Date: 2026-08-10
This is an argument that the project's contribution process consumes excessive volunteer hours, and a ranked list of changes that would cut the cost without lowering the quality bar on the code that matters.
Nothing here has been changed. The measurements are reproducible; each one
carries the command that produced it so they can be checked rather than
believed.
How the numbers were taken
Run from the repo root on feat/debug-surface at 2026-08-10. Issue counts come
from the upstream repo, maniator/verticopolis. (note from hongpong i am about to submit the PR for this)
# churn by area, last 200 commits
git log -200 --name-only --pretty=format: | grep -v '^$' \
| awk -F/ '{if(NF==1)print "ROOT:"$1; else print $1}' | sort | uniq -c | sort -rn
# is the agent scaffolding duplicated?
diff -rq .agents/skills .claude/skills && echo IDENTICAL
git ls-files .agents | wc -l ; git ls-files .claude | wc -l
# repo weight
git count-objects -vH | grep size-pack
du -sh docs/screenshots
# workflow weight
wc -l .github/workflows/*
# backlog rows by state, and the issues they claim to mirror
node -e '
const fs=require("fs");
const H="| Date | Story | GH | Epic | Type | Priority | Severity | Owner | Status | Notes |";
const U=new Set(["open","in-progress","idea","parked","partial","next","impl-review"]);
const L=fs.readFileSync("_bmad-output/implementation-artifacts/backlog.md","utf8").split("\n");
const s=L.indexOf(H); const rows=[];
for(let i=s+2;i<L.length&&L[i].trim()!=="";i++) rows.push(L[i].split(/(?<!\\)\|/).map(c=>c.trim()));
const un=rows.filter(c=>c.length===12&&U.has(c[9].toLowerCase()));
const refs=new Set(un.map(c=>(/^#(\d+)$/.exec(c[3])||[])[1]).filter(Boolean));
(async()=>{
const all=new Map();
for(let p=1;p<=30;p++){
const r=await fetch(`https://api.github.com/repos/maniator/verticopolis/issues?state=all&per_page=100&page=${p}`,
{headers:{"User-Agent":"audit"}});
const b=await r.json(); if(!b.length) break;
for(const i of b) if(!i.pull_request) all.set(String(i.number),i);
if(b.length<100) break;
}
const closedButOpenRow=[...refs].filter(n=>all.get(n)?.state==="closed");
const openNoRow=[...all.values()].filter(i=>i.state==="open"&&!refs.has(String(i.number)));
console.log("unresolved rows:",un.length,"| open issues:",
[...all.values()].filter(i=>i.state==="open").length);
console.log("rows citing a CLOSED issue:",closedButOpenRow.length);
console.log("open issues with no row:",openNoRow.length,
"(of which mirror-style:",openNoRow.filter(i=>/\[P[123]\]/.test(i.title)).length,")");
})();'
Finding 1: screenshot bookkeeping is the largest single source of churn
Of the last 200 commits, 262 file-touches landed in docs/. 250 of those were
in docs/screenshots. The gallery is 92 files and 17MB, committed to git. The
pack is now 488 MiB, which every contributor clones.
Four workflows exist to manage that gallery:
| Workflow | Lines |
|---|---|
pr-drift-check.yml |
406 |
screenshot-capture.yml |
235 |
update-visual-baselines.yml |
161 |
screenshot-approval-reaper.yml |
91 |
| total | 893 |
For comparison, test.yml, which runs the actual test suite and e2e, is 138
lines. There is roughly six times as much CI configuration serving screenshot
management as serving testing.
That machinery then reaches back into the contributor rules: CLAUDE.md spends
one of its non-negotiables on pinned Playwright container digests, host browsers
being forbidden, and the approval flow for regenerating pixels.
In fairness to what is already there. The drift check is carefully built.
It path-gates the expensive capture through the changes job so version-only
PRs do not trigger a render, it masks the app version so a bump does not move
pixels, it fails closed when the detector itself breaks, and it exempts fork PRs
that could never clear the gate. This is not sloppy work. The problem is not
quality of implementation, it is that a merge-blocking pixel-equality check is a
heavy instrument for a simulation game maintained by volunteers, and the
container pinning rules exist only because that check blocks merges.
Proposal
- Get the PNGs out of git history. Git LFS, a
gh-pagesbranch, or a
Vercel-hosted gallery. This alone addresses clone weight and most of the
commit noise, and it does not require giving up any check. - Demote
drift-gatefrom a required check to a nightly job onmain.
Keep the determinism guard (the hard-fail half, which catches real
nondeterminism leaks like the one PR #188 chased) on PRs. Move the staleness
half to a scheduled run that opens a PR when the gallery drifts. A stale
gallery onmainfor a few hours is a documentation lag, not a defect in the
game.
Finding 2: the agent scaffolding is duplicated and largely inapplicable
.agents/skills and .claude/skills are byte-identical. diff -rq returns
clean. Each is 515 tracked files and about 5MB, so the repo carries roughly
1,030 files and 10MB of vendored scaffolding where it needs one copy.
Within that scaffolding are 16 knowledge files covering Unreal, Unity, Godot,
Roblox, and Phaser. The number of files in the skills tree that mention
Excalibur, the engine this game actually uses, is zero.
find .claude .agents -path '*knowledge*' \
\( -name '*unreal*' -o -name '*unity*' -o -name '*godot*' \
-o -name '*roblox*' -o -name '*phaser*' \) | wc -l # 16
grep -rl "excalibur" .claude/skills | wc -l # 0
Proposal
Keep one copy and symlink the other, or generate the second at install time.
Delete the engine knowledge files for engines the project does not use. This is
pure deletion with no behavior change and no process argument attached, which
makes it the natural first PR.
Finding 3: the mirror guard enforces the half that does not drift
_bmad-output/implementation-artifacts/backlog.md holds 222 curated rows: 130
unresolved and 92 finished. The standing rule (2026-07-15) requires every
unfinished row to carry a matching GitHub issue number in its GH column, and
src/tests/backlogIssueMirror.test.ts enforces that in CI. _bmad-output as a
whole is 353 files and 8.1MB of generated planning artifacts.
The guard does its job on the half it can see. All 130 unresolved rows carry a
well-formed issue reference, no two rows share a number, and every status is a
known token.
But the guard is five text assertions against a markdown file. It never asks
GitHub anything. So it cannot check the half of the invariant that actually
drifts, and that half has drifted. Audited against upstream on 2026-08-10:
| Check | Count |
|---|---|
| Open issues upstream (excluding PRs) | 150 |
| Unresolved backlog rows | 130 |
| Rows marked open work whose issue is already closed | 8 |
| Mirror-style open issues with no backlog row | 19 |
| Plain user-filed open issues with no backlog row (expected, fine) | 9 |
The 8 include #545, the issue for the one-time triage pass that adopted this
very cadence. The row still says open; the issue is closed.
The 19 are not user reports. They carry the review template's [P1]-[P3]
title tag, so they came out of the review process, which is exactly the path the
rule says must produce a curated row. Recent ones (#812, #813, #814) are
live art and rendering work.
So 27 records disagree across the two systems, and CI is green, because the only
enforceable direction is the one that does not fail. The manual reconciliation
happens on every PR, it blocks merges, and it does not deliver the consistency
it appears to guarantee.
The rule's own history, recorded in the backlog, points the same way: a weekly
batch sweep was tried, the inbox accreted about 90 dated sections in two weeks,
and the response was to add per-PR enforcement rather than to reduce the number
of places a finding has to be recorded.
Proposal
Pick one system of record.
- GitHub issues are canonical. Drop the
GHcolumn, the mirror rule, and
the guard test. Use labels for priority and epic. The backlog becomes a
generated read-only view, or goes away. - Or
backlog.mdis canonical and issues get opened only for work someone
intends to pick up, with no requirement that the two stay in step.
If the mirror is kept, the guard needs to check the direction that actually
drifts, which means a scheduled job that queries the API and opens a PR when the
two disagree. That is more machinery, which argues for the first option.
Whichever is chosen, the 27 inconsistencies above want a one-time reconciliation
pass first, so the decision starts from a clean state.
Finding 4: the mandatory deep review runs on changes that do not need it
CLAUDE.md requires /gds-code-review or /bmad-code-review on every
non-trivial change, explicitly including small and test-only ones, with the
three adversarial layers plus triage. That review is genuinely valuable where
the project has real domain canon to protect: build caps in
src/engine/facilities.ts, transport pooling, economy math, and TDT save
round-trip fidelity. Those are places where a plausible-looking change silently
breaks parity with the 1994 original, and no ordinary review catches it.
It is much less valuable on a debug panel, a docs edit, or a rename. The rule
currently makes no distinction, and it is the largest consumer of time and
tokens per contribution.
Proposal
Scope the mandatory deep review by path. Require it for changes touching:
src/engine/**src/storage/tdt*and save import/export- economy, transport, and population code wherever it lives
For everything else, the four quality gates plus ordinary review. Contributors
can still run the deep review by choice when a change feels risky.
What should not change
- The four quality gates (
typecheck,lint,test,build). Cheap,
fast, load-bearing. - The canon rules in CLAUDE.md about build caps and transport pooling.
That is hard-won domain knowledge and the warning against "fixing" express
elevators out of the shared pool is exactly the kind of note that saves a
future contributor a bad afternoon. - The determinism half of the screenshot check. Nondeterministic rendering
is a real bug class and this catches it. - Most guard tests in
src/tests/. They encode invariants that are hard to
hold in your head.
Suggested sequencing
- PR 1, deletions only. Deduplicate the skills trees, remove the unused
engine knowledge files. No process change, nothing to argue about. - PR 2, reconciliation. Fix the 27 backlog/issue inconsistencies in
Finding 3. This is worth doing regardless of what gets decided about the
rule, since 19 pieces of real work (including#812, a P1) are currently
tracked in only one of the two places. - PR 3, repo weight. Move
docs/screenshotsout of git history. - Discussion, then PR 4. Demote the drift gate to nightly; scope the deep
review by path; choose one system of record for the backlog. These change
CLAUDE.md and CONTRIBUTING.md and need the maintainer's agreement first.
The underlying pattern
Each of these mechanisms was a reasonable response to a real incident. The drift
gate followed a nondeterminism bug. The mirror rule followed findings getting
lost. The mandatory review followed something shipping broken. None of them was
ever retired, and each new one was added on top of the last.
That accumulation works when the scarce resource is reviewer attention on a paid
team, because process converts attention into guarantees. It inverts when the
scarce resource is volunteer hours, because every mechanism taxes the people
there are fewest of. A volunteer who spends an evening reconciling a backlog row
with a GitHub issue and approving a screenshot regeneration job has not made the
game better that evening.
The question worth asking about each rule is not whether it prevents a real
problem, since they all do. It is whether the problem it prevents costs more
than the hours it consumes, priced in volunteer time rather than in CI minutes.
Finding 3 adds a sharper version of that question. A rule that is enforced only
where enforcement is cheap can end up costing full price while delivering a
guarantee it does not actually hold. The mirror rule bills every PR for manual
reconciliation and still permits 27 disagreements, because the assertion that
would catch them needs a network call the test suite cannot make. Before adding
a check, it is worth asking which half of the invariant it can see, and whether
that is the half that breaks.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the named CI workflows, .agents/skills, .claude/skills, _bmad-output/implementation-artifacts/backlog.md, and src/tests/backlogIssueMirror.test.ts, then run the listed measurements from the repository root. This proposal needs a single agreed scope before implementation; done should be a focused change with the four quality gates still passing and the selected process overhead demonstrably reduced.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, github, github-actions, node.js, playwright, typescript
- Domain
- ci-cd, developer-experience, tooling
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100