Thanks for your interest in ContentForest. We welcome contributions from developers of all skill levels — bug fixes, prompt tuning, validator improvements, UI tweaks, docs, all of it.
ContentForest is an internal Nano Collective tool: file an issue to request content and a GitHub Action runs Nanocoder against templated prompts to generate a release-content pack, validates the output, and opens a PR. Merged PRs deploy to a file viewer at contentforest.nanocollective.org. The repo is public for transparency; request flows and issues are intended for Nano-Collective org members.
This guide covers the dev setup, the test gate, and the divergences this project takes from the Nano Collective playbook.
- Getting Started
- Filing Requests via Issues
- Development Setup
- Testing
- Coding Standards
- Divergences from the Playbook
- Submitting Changes
- Community
Before contributing:
- Read the README for what ContentForest does and how the pieces fit.
- Read
AGENTS.mdif you're (or you're driving) an AI coding agent — it captures the don't-relitigate decisions and the agent-shaped guardrails. - Check the issue tracker. Issues from non-
Nano-Collectiveorg members are auto-closed by.github/workflows/gate-issues.yaml— see Divergences for why.
If you find an unassigned issue you'd like to work on, comment on it so we don't double up.
Most content changes don't need a code PR — they go through an issue template that dispatches an agent and opens a PR for you. The flows wired up today:
-
Request new release content (
release-requestlabel) — generate a full pack for a new product release. Pick the product (dropdown, auto-synced fromconfig/products.json), enter the version (tag without thevprefix), and optionally add an angle to emphasise. The agent reads the product's GitHub release, generates the pack, validates it, and opens a PR offrelease/<product>-<version>-issue-<n>that auto-closes the issue on merge. Edit the issue body and comment/retryto re-run if the first attempt didn't land. Wired viascripts/parse-release-request.ts+pnpm generate. -
Request a content change (
change-requestlabel) — apply a targeted edit to an existing release pack. Pick the product, version, scope (whole pack / headline channels / specific article / specific file), and describe the change. The agent applies the edit, runs the validator, and opens a PR that auto-closes the issue on merge. Comment/retryon the issue to re-run if the first attempt didn't land. Orchestrator:scripts/change-request.ts. -
Request a collective post (
collective-requestlabel) — create or edit a collective-level pack atcontent/_collective/<slug>/for posts about the Nano Collective itself rather than a specific product release. Same shape as change-request: fill the form, agent runs, PR opens. Orchestrator:scripts/collective-request.ts.
Both flows are gated to Nano-Collective org members via gate-issues.yaml — issues from non-members are auto-closed. If you're a member but your issue is auto-closed, set your org membership visibility to public at https://github.com/orgs/Nano-Collective/people.
For freeform bugs, feature ideas, or discussion, file a regular issue (no template needed) — those don't trigger automation.
- Node.js 22 (matches CI; 20+ should also work)
- pnpm 10+
- Git
git clone https://github.com/Nano-Collective/contentforest.git
cd contentforest
pnpm installCopy the .env keys you need from a maintainer. The minimum for local generation:
MINIMAX_API_KEY— Nanocoder is wired to MiniMax Coding viaagents.config.json(providerminimax-m3). Same secret CI uses.GH_TOKEN(orGITHUB_TOKEN) — needed forpnpm detect-releasesagainst the live repos and for any flow that opens PRs locally.
For UI-only work, no env vars are needed.
pnpm dev # Next.js dev server at http://localhost:3000
pnpm build # static export to dist/
pnpm fetch-refs # pull live brand + product docs into _refs/
pnpm generate --product nanocoder \ # generate a pack into content/_local/
--version 1.25.2 --test
pnpm validate --pack nanocoder/1.25.2 \ # gate locally before opening a PR
--root content/_testThe full operator runbook is in docs/runbook.md. Local prompt-tuning workflow is in docs/local-development.md.
pnpm test:allThis runs the full gate — formatting check, type-check, lint, AVA tests, knip (dead-code detection), pnpm audit, and Semgrep — and is the same gate pr-checks.yml enforces on every PR. If it's green locally, your PR will be green.
If you don't have Semgrep installed locally, the orchestrator script logs a warning and skips it. In CI it always runs. Install with brew install semgrep or pip install semgrep.
.tsfiles underscripts/,lib/, andhooks/are covered by AVA + c8 with an 80% line-coverage threshold..tsxUI components are intentionally not covered. Coverage on Next.js UI is theatre and the testable logic lives inscripts/. Test UI changes withpnpm devand exercise the affected route.- End-to-end generation is not mocked.
scripts/generate-content.tscalls Nanocoder and the result is non-deterministic. Test generation changes by runningpnpm generate --product <slug> --version <v> --testand reading the output by eye. - Validator changes must include regression tests in
scripts/validate-content.spec.ts. The validator is the load-bearing piece; if it lets bad packs through, the GitHub Action opens broken PRs.
Place specs alongside source with the .spec.ts extension (e.g. scripts/parse-change-request.spec.ts). Follow the patterns in scripts/validate-content.spec.ts — table-driven cases, no I/O against the real filesystem, no network.
- TypeScript:
strict: true, ESNext, noany(warned by Biome). - Formatting and lint: Biome handles both. Run
pnpm test:lint:fixto auto-fix; CI runsbiome checkwithout auto-fix and fails on drift. - Path alias:
@/*resolves to the repo root (Next.js convention). - Error handling: Validate at boundaries (user input, external APIs, generated content). Trust internal code.
- Comments: Default to none. Only add comments where the why is non-obvious — a hidden constraint, a workaround, behaviour that would surprise a reader. Don't explain what the code does.
- No new dependencies without a one-line justification in the PR description.
The Creating a New Project playbook describes the standard shape for an NC project. ContentForest takes the following deliberate divergences:
- No NPM publish, no
release.yml. ContentForest is a private internal Next.js app, not a publishable package.package.jsonisprivate: true, version0.0.0. Deployment is via.github/workflows/deploy-cloudflare-pages.yamlon push tomain, which uploads the static export to Cloudflare Pages. - Public repo, gated issues. The repo is public for transparency, but issues are auto-closed for non-
Nano-Collective-org members by.github/workflows/gate-issues.yaml(GitHub's native interaction limits expire after 6 months — this workflow is the permanent equivalent). If you're an NC member and your issue auto-closes, set your org membership visibility to public at https://github.com/orgs/Nano-Collective/people. - Path alias
@/*→./*instead of@/*→source/*. Next.js convention; the project doesn't use asource/folder. - No subagent spawning during generation.
agents.config.jsonblocks Nanocoder'sagenttool viananocoder.disabledTools: ["agent"]. Generation is a flat sequence of two prompts (release-channels→article-channels), not a spawning tree. - Coverage scope:
.tsonly. UI.tsxcomponents are excluded from c8 by design (see Testing). - No
docs/index.md+ frontmatter. The repo's docs aren't surfaced ondocs.nanocollective.org, so the docs-site convention doesn't apply. Operator docs live indocs/runbook.md,docs/local-development.md, anddocs/adding-a-product.md. - No bug / feature issue templates. The only templates that trigger automation are
change-request.ymlandcollective-request.yml, which dispatch agents to apply targeted edits or create collective-level packs (see Filing Requests via Issues). Org members file freeform issues for bugs and features.
The Code of Conduct and Economics Charter apply unchanged.
- Fork the repo (or branch directly if you're a maintainer).
- Make your change. Run
pnpm test:alland confirm it's clean. - Open a PR against
main. The PR template asks for the testing notes and checklist — fill it in. - CI runs
pr-checks.yml(parallel format / types / lint / tests / build / knip / audit / Semgrep). Content PRs additionally triggervalidate-content.yaml. - A maintainer reviews and merges. Merging to
maintriggers the Cloudflare Pages deploy.
Match the existing convention (light Conventional Commits):
feat: <description>— new featurefix: <description>— bug fixmod: <description>— modification to existing behaviourchore(deps): <description>— dependency updatedocs: <description>— docs-only change- Lowercase, imperative, no trailing period. Scope optional in parentheses.
- Nano Collective — the collective behind this and every other NC project.
- Discord — real-time chat with the collective.
- Code of Conduct — applies to all contributors.
- Economics Charter — how scoped bounties work when contribution is paid via the NC community fund.
Thanks for contributing.