Leaf of #18985 (AC-4), authored here rather than under it because the measurement is mine and a ticket has to be one-PR-resolvable. Refs #18985. @neo-opus-ada keeps AC-2 (topics), AC-3 (curation) and AC-5 (review/label/spam path), and marked this one as going first: twenty curated issues behind a setup path a clean clone cannot follow is twenty curated bounces.
Hacktoberfest starts October 1. The contributor this door is for has a fork, Node and an evening.
The Problem
Every published door routes a would-be engine contributor into the consumer scaffold instead, and the loop that actually works is written down nowhere.
Door
What it documents
What an engine contributor gets
README.md:152 → "Getting Started"
neomjs.com/#/learn/gettingstarted/Setup
→ the row below
learn/gettingstarted/Setup.md
npx neo-app@latest — scaffolds a workspace that consumes neo.mjs as a dependency
the engine in node_modules, which is not a checkout you can change
CONTRIBUTING.md
issue etiquette and PR rules
no build, no server, no test command
CONTRIBUTING.md contains exactly one command in the whole file — npm ci or npm install at §3.7 — and it is justified there as "register the Husky hooks", not as setup. README.md names no install step at all.
Two further staleness defects in the same file, both verified:
**§3.4 says "create PRs for the dev branch and not for master".** git ls-remote --heads origin answers dev and main. master does not exist.
**§3.1 leads with "always create a new issue inside our Issues Tracker first and wait for approval".** That is the wrong first instruction in a month when twenty curated issues are waiting to be taken.
The Architectural Reality
Measured on a genuinely clean clone — git clone --depth 1 --branch dev, then every command run through /usr/bin/env -i with only HOME, PATH, USER, TMPDIR and LANG, so no NEO_*, MCP_*, ANTHROPIC_* or CLAUDE_* variable from a maintainer shell could reach it:
Step
Result
git clone --depth 1 --branch dev
338 MB on disk
npm install
exit 0, 3 s, 589 packages, 0 vulnerabilities
↳ postinstall → neo-agent-skills-materialize
succeeds with no Agent OS present: symlinks 37 skills into .claude/skills and prints that the directory is a projection artifact
↳ prepare → buildScripts/util/prepare.mjs
runs; Husky pre-commit and pre-push are registered, so §3.7's claim is accurate
npm run test-unit -- test/playwright/unit/util
87 passed in 671 ms
↳ with HOME pointed at an empty directory
87 passed again — and ~/.cache/ms-playwright does not exist on the measuring host at all
npm run server-start
binds :8082 and serves / and /apps/portal/index.html at 200, with no build step first
🔴 npm run test-unit (no path — the whole suite)
exit 1, ZERO tests selected. 41 lines of Cannot find module '<repo>/dist/marked.mjs' imported from src/component/Markdown.mjs, and the same for dist/parse5.mjs from src/functional/util/HtmlTemplateProcessor.mjs
↳ after npm run bundle-browser-deps (6 s, exit 0)
4305 tests selected, 4302 passed
npm run test-components -- test/playwright/component/button
Two corrections to my own premise, both from measurement rather than reading.
The scary one was wrong. I filed this expecting the Agent-OS postinstall to block an outside install. It does not — it symlinks and exits clean with no Agent OS present.
The one I did not expect is real, and it is not documentation.dist/ is git-ignored (.gitignore:68), so no clone has dist/marked.mjs, dist/parse5.mjs or dist/mermaid.mjs — and src/component/Markdown.mjs and src/functional/util/HtmlTemplateProcessor.mjs import them at load. The result is not a failing test; it is zero tests selected, which reads like a broken checkout rather than a missing step. npm run bundle-browser-deps fixes it in six seconds.
Every maintainer's working checkout already has dist/, built at some point and never removed. That is exactly why this has survived: the one condition that exposes it is the one condition no maintainer is ever in.
One real wrinkle, worth a line and not a gate: npm install emits npm warn EBADENGINE on Node v25.9.0 for a dependency wanting ^22.22.3 || ^24.15.0 || >=26.0. The repository's own engines.node is >=24.0.0, and CONTRIBUTING.md names no Node requirement anywhere.
The scripts a contributor needs already exist in package.json and none of them is mentioned in CONTRIBUTING.md: server-start, test-unit, test-components, test-e2e, build-all, build-themes.
The Fix
Rewrite CONTRIBUTING.md so the contributor loop comes first and the etiquette follows it:
A "Set up the engine from a clone" section at the top, with the verified loop: Node >=24, git clone, npm install, npm run bundle-browser-deps, npm run server-start, and the test commands — each exactly as measured above, including which suites need npx playwright install and which do not.
Separate the two audiences explicitly.Building an app with Neo? → npx neo-app@latest and the portal Setup guide. Changing the engine itself? → this loop. Today the file silently assumes the second and documents neither.
§3.1's approval-first sentence gains its exception: an issue already labelled good first issue or help wanted is claimable by comment, no prior approval round.
§3.4's master → dev.
A Node-version line, since engines.node is >=24.0.0 and nothing says so.
README.md's "Getting Started" link keeps pointing at the app-user path — that is correct for its audience — and gains a sibling line pointing contributors at CONTRIBUTING.md.
Acceptance Criteria
AC-1 — CONTRIBUTING.md opens with the engine-contributor loop, and every command in it is one this ticket measured on a clean clone.
AC-1b — the loop names bundle-browser-deps and says why it exists (dist/ is git-ignored), so a contributor who skips it can recognise the zero-tests-selected symptom rather than concluding the checkout is broken.
AC-1c — the test section states the browser prerequisite per suite, because the three differ: test-unit needs none, test-components needs npx playwright install (~1 GB, bundled Chromium), and test-e2e is pinned to channel: 'chrome' (playwright.config.e2e.mjs:69, :118) so it needs system Google Chrome, which a default npx playwright install does not provide. The two live exemplars #18995 / #18996 are unit tests and must not appear to carry any of it.
AC-1d — the loop builds the development theme CSS (npm run build-themes -- -n -e dev -t all) before the first server-start, and says why: dist/ is git-ignored, so a fresh clone renders unstyled and the server only warns. Added 2026-09-19 from @neo-gpt-emmy's review of PR #19000 — the original loop omitted it, and my own first clean-clone run could not catch it because the CSS in that tree had been written as a side effect of a test-components invocation that failed.
AC-1e — the "no build needed" claim is scoped to JavaScript; SCSS is stated as compiling into dist/development/css with build-themes / watch-themes named.
AC-2 — the file distinguishes the app-building audience (npx neo-app@latest) from the engine-changing audience, and routes each.
AC-3 — master no longer appears; the named branch is dev.
AC-4 — the approval-first rule states its good first issue / help wanted exception.
AC-5 — the Node requirement is stated and matches package.json's engines.node.
AC-6 — README.md carries one line routing contributors to CONTRIBUTING.md, leaving its app-user link intact.
AC-7 — the documented loop is re-verified end to end on a fresh clone with a scrubbed environment, and the run is reported in the PR body. This is the AC that makes the others true; a prose review does not satisfy it.
⚠️ Sharpened 2026-09-19, because the first attempt satisfied the letter and missed the defect. Two method rules, both earned:
The clone must be uncontaminated by the verification itself. A failed test-components run builds theme CSS as a side effect before it fails, so any tree that has seen one is no longer a clean rig. Re-verify on a clone that has run only the documented commands, in order.
Assert on the artifact the step produces, never on a status code downstream of it.GET / returned 200 with every stylesheet missing. The passing form asserts the stylesheet itself (GET /dist/development/css/src/Global.css → 200, non-zero bytes).
⚠️ A host-provided dependency cannot be verified away by scrubbing.test-e2e resolves system Google Chrome, which this host has and env -i does not hide — so a green e2e run here would say nothing about a contributor's machine. Where the loop depends on something installed outside the repo, the AC is satisfied by reading the config that requires it (channel: 'chrome') and documenting it, not by a local pass.
Out of Scope
Curating the issues themselves (#18985 AC-3, @neo-opus-ada) and the review/label/spam path (AC-5).
The hacktoberfest topic on any repository (#18985 AC-2).
.github/AI_QUICK_START.md and the agent-facing §5 of CONTRIBUTING.md. They address a reader who has the Agent OS; this ticket is about the reader who does not, and the two must not be merged into one path.
The 338 MB clone. Repository weight is #17238 / #17376 territory, not a documentation fix.
Any change to learn/gettingstarted/Setup.md. It is correct for the audience it serves.
Avoided Traps
⛔ Do not document a loop you have not run. This ticket earned that twice. The premise it was filed on (the Agent-OS postinstall blocks an outside install) was wrong, and the step that actually blocks one (bundle-browser-deps) was absent from the premise entirely. A draft of the fix that listed test-components alongside test-unit was written before either suite had been run on a cold cache; running them is what caught both.
⛔ Do not delete the agent-facing section to make room. Agent-assisted contributors are explicitly in scope for Hacktoberfest 2026 (@tobiu, 2026-09-19, #18985): neo-agent-skills is a published package, so an outside agent can use the skills with no Knowledge Base and no Memory Core.
⛔ Do not add a per-event rule to CONTRIBUTING.md. 2025 needed #7304 to take a temporary protocol back out; what lands here has to be true in November too.
Related
#18985 (parent; this is its AC-4) · #18995 · #18996 (the two live exemplars, both unit-test tickets — the loop above is exactly what they need) · #14230 · #7296 · #7304
Live latest-open sweep: latest 20 open issues in neomjs/neo at 2026-09-19T19:07Z — no equivalent; #18985 is the parent and the only adjacent filing. A2A in-flight sweep: @neo-opus-ada's 19:00Z message assigns this AC to me and names the three she keeps, so the lane is de-conflicted at source. Memory Core rationale sweep on the symptom: the only prior CONTRIBUTING.md work is the 2026-03 addition of the agent-facing §5; no prior decision on the contributor setup loop. Own open assignments in this repository (#18532, #17918, #16358, #16212, #15202): none on this surface.
Decision Record impact: none
Origin Session ID: 3c85c7c8-8961-43cc-b331-a053ea1d46d5
Retrieval Hint: "engine contributor clean clone setup loop CONTRIBUTING no build or test command, every door routes to npx neo-app scaffold"
Context
Leaf of #18985 (AC-4), authored here rather than under it because the measurement is mine and a ticket has to be one-PR-resolvable.
Refs #18985. @neo-opus-ada keeps AC-2 (topics), AC-3 (curation) and AC-5 (review/label/spam path), and marked this one as going first: twenty curated issues behind a setup path a clean clone cannot follow is twenty curated bounces.Hacktoberfest starts October 1. The contributor this door is for has a fork, Node and an evening.
The Problem
Every published door routes a would-be engine contributor into the consumer scaffold instead, and the loop that actually works is written down nowhere.
README.md:152→ "Getting Started"neomjs.com/#/learn/gettingstarted/Setuplearn/gettingstarted/Setup.mdnpx neo-app@latest— scaffolds a workspace that consumesneo.mjsas a dependencynode_modules, which is not a checkout you can changeCONTRIBUTING.mdCONTRIBUTING.mdcontains exactly one command in the whole file —npm ciornpm installat §3.7 — and it is justified there as "register the Husky hooks", not as setup.README.mdnames no install step at all.Two further staleness defects in the same file, both verified:
git ls-remote --heads originanswersdevandmain.masterdoes not exist.The Architectural Reality
Measured on a genuinely clean clone —
git clone --depth 1 --branch dev, then every command run through/usr/bin/env -iwith onlyHOME,PATH,USER,TMPDIRandLANG, so noNEO_*,MCP_*,ANTHROPIC_*orCLAUDE_*variable from a maintainer shell could reach it:git clone --depth 1 --branch devnpm installpostinstall→neo-agent-skills-materialize.claude/skillsand prints that the directory is a projection artifactprepare→buildScripts/util/prepare.mjspre-commitandpre-pushare registered, so §3.7's claim is accuratenpm run test-unit -- test/playwright/unit/utilHOMEpointed at an empty directory~/.cache/ms-playwrightdoes not exist on the measuring host at allnpm run server-start:8082and serves/and/apps/portal/index.htmlat 200, with no build step firstnpm run test-unit(no path — the whole suite)Cannot find module '<repo>/dist/marked.mjs' imported from src/component/Markdown.mjs, and the same fordist/parse5.mjsfromsrc/functional/util/HtmlTemplateProcessor.mjsnpm run bundle-browser-deps(6 s, exit 0)npm run test-components -- test/playwright/component/buttonbrowserType.launch: Executable doesn't exist, "Please run:npx playwright install"npx playwright install(18 s, 1.1 GB)Two corrections to my own premise, both from measurement rather than reading.
The scary one was wrong. I filed this expecting the Agent-OS
postinstallto block an outside install. It does not — it symlinks and exits clean with no Agent OS present.The one I did not expect is real, and it is not documentation.
dist/is git-ignored (.gitignore:68), so no clone hasdist/marked.mjs,dist/parse5.mjsordist/mermaid.mjs— andsrc/component/Markdown.mjsandsrc/functional/util/HtmlTemplateProcessor.mjsimport them at load. The result is not a failing test; it is zero tests selected, which reads like a broken checkout rather than a missing step.npm run bundle-browser-depsfixes it in six seconds.Every maintainer's working checkout already has
dist/, built at some point and never removed. That is exactly why this has survived: the one condition that exposes it is the one condition no maintainer is ever in.One real wrinkle, worth a line and not a gate:
npm installemitsnpm warn EBADENGINEon Node v25.9.0 for a dependency wanting^22.22.3 || ^24.15.0 || >=26.0. The repository's ownengines.nodeis>=24.0.0, andCONTRIBUTING.mdnames no Node requirement anywhere.The scripts a contributor needs already exist in
package.jsonand none of them is mentioned inCONTRIBUTING.md:server-start,test-unit,test-components,test-e2e,build-all,build-themes.The Fix
Rewrite
CONTRIBUTING.mdso the contributor loop comes first and the etiquette follows it:>=24,git clone,npm install,npm run bundle-browser-deps,npm run server-start, and the test commands — each exactly as measured above, including which suites neednpx playwright installand which do not.npx neo-app@latestand the portal Setup guide. Changing the engine itself? → this loop. Today the file silently assumes the second and documents neither.good first issueorhelp wantedis claimable by comment, no prior approval round.master→dev.engines.nodeis>=24.0.0and nothing says so.README.md's "Getting Started" link keeps pointing at the app-user path — that is correct for its audience — and gains a sibling line pointing contributors atCONTRIBUTING.md.Acceptance Criteria
CONTRIBUTING.mdopens with the engine-contributor loop, and every command in it is one this ticket measured on a clean clone.bundle-browser-depsand says why it exists (dist/is git-ignored), so a contributor who skips it can recognise the zero-tests-selected symptom rather than concluding the checkout is broken.test-unitneeds none,test-componentsneedsnpx playwright install(~1 GB, bundled Chromium), andtest-e2eis pinned tochannel: 'chrome'(playwright.config.e2e.mjs:69,:118) so it needs system Google Chrome, which a defaultnpx playwright installdoes not provide. The two live exemplars #18995 / #18996 are unit tests and must not appear to carry any of it.npm run build-themes -- -n -e dev -t all) before the firstserver-start, and says why:dist/is git-ignored, so a fresh clone renders unstyled and the server only warns. Added 2026-09-19 from @neo-gpt-emmy's review of PR #19000 — the original loop omitted it, and my own first clean-clone run could not catch it because the CSS in that tree had been written as a side effect of atest-componentsinvocation that failed.dist/development/csswithbuild-themes/watch-themesnamed.npx neo-app@latest) from the engine-changing audience, and routes each.masterno longer appears; the named branch isdev.good first issue/help wantedexception.package.json'sengines.node.README.mdcarries one line routing contributors toCONTRIBUTING.md, leaving its app-user link intact.test-componentsrun builds theme CSS as a side effect before it fails, so any tree that has seen one is no longer a clean rig. Re-verify on a clone that has run only the documented commands, in order.GET /returned 200 with every stylesheet missing. The passing form asserts the stylesheet itself (GET /dist/development/css/src/Global.css→ 200, non-zero bytes).test-e2eresolves system Google Chrome, which this host has andenv -idoes not hide — so a green e2e run here would say nothing about a contributor's machine. Where the loop depends on something installed outside the repo, the AC is satisfied by reading the config that requires it (channel: 'chrome') and documenting it, not by a local pass.Out of Scope
hacktoberfesttopic on any repository (#18985 AC-2)..github/AI_QUICK_START.mdand the agent-facing §5 ofCONTRIBUTING.md. They address a reader who has the Agent OS; this ticket is about the reader who does not, and the two must not be merged into one path.learn/gettingstarted/Setup.md. It is correct for the audience it serves.Avoided Traps
postinstallblocks an outside install) was wrong, and the step that actually blocks one (bundle-browser-deps) was absent from the premise entirely. A draft of the fix that listedtest-componentsalongsidetest-unitwas written before either suite had been run on a cold cache; running them is what caught both.neo-agent-skillsis a published package, so an outside agent can use the skills with no Knowledge Base and no Memory Core.CONTRIBUTING.md. 2025 needed #7304 to take a temporary protocol back out; what lands here has to be true in November too.Related
#18985 (parent; this is its AC-4) · #18995 · #18996 (the two live exemplars, both unit-test tickets — the loop above is exactly what they need) · #14230 · #7296 · #7304
Live latest-open sweep: latest 20 open issues in
neomjs/neoat 2026-09-19T19:07Z — no equivalent; #18985 is the parent and the only adjacent filing. A2A in-flight sweep: @neo-opus-ada's 19:00Z message assigns this AC to me and names the three she keeps, so the lane is de-conflicted at source. Memory Core rationale sweep on the symptom: the only priorCONTRIBUTING.mdwork is the 2026-03 addition of the agent-facing §5; no prior decision on the contributor setup loop. Own open assignments in this repository (#18532, #17918, #16358, #16212, #15202): none on this surface.Decision Record impact: none
Origin Session ID: 3c85c7c8-8961-43cc-b331-a053ea1d46d5 Retrieval Hint: "engine contributor clean clone setup loop CONTRIBUTING no build or test command, every door routes to npx neo-app scaffold"