Contributing
Thanks for your interest in contributing to Graphorin - a TypeScript framework for building long-living personal AI assistants. This document explains how to set up the repository, the development workflow, and the project conventions.
By participating, you agree to abide by the Code of Conduct.
Prerequisites
- Node.js 22.x LTS or newer (see
.nvmrc). - pnpm (the project's package manager). The exact version is pinned in the root
package.jsonpackageManagerfield -corepackwill activate it automatically:
corepack enable- Git with SSH or HTTPS access to GitHub.
- A POSIX-compatible shell (bash / zsh) on macOS / Linux, or PowerShell / Git Bash on Windows. CI runs on macOS, Linux, and Windows for every PR.
Repository layout
packages/ Each @graphorin/* package lives here as its own workspace.
examples/ Stand-alone example apps that consume @graphorin/* packages.
scripts/ Repo-wide maintenance scripts (CI helpers, license checks, …).
.github/ GitHub Actions workflows, issue templates, and PR template.
.changeset/ Changesets configuration; one file per pending release entry.First-time setup
git clone https://github.com/o-stepper/graphorin.git
cd graphorin
corepack enable
pnpm install --frozen-lockfile
pnpm -r build
pnpm -r testIf everything is green, you are ready to make changes.
Development workflow
- Create a branch.
feat/<slug>for feature work,fix/<slug>for bug fixes,chore/<slug>for maintenance, orhotfix/<short-description>for unplanned fixes. Never push directly tomain. Branches are deleted on merge (pass--delete-branchor enable auto-delete); do not resurrect a merged branch for follow-up work - cut a fresh one frommainso you never rebase onto pre-fix code. - Make your change inside the relevant
packages/<scope>orexamples/<app>workspace. - Add or update tests. Vitest runs across the whole workspace.
- Run the local checks before opening a PR:
pnpm -r build
pnpm -r typecheck
pnpm -r test
pnpm lint
pnpm run check-no-network- Add a changeset for any change that affects a published
@graphorin/*package:
pnpm changesetPick the affected packages, the bump type (patch / minor / major), and write a single short paragraph that explains the why of the change. The CI release pipeline consumes the changeset on merge.
- Open a Pull Request against
mainusing the PR template. The template asks for:- The short summary of behavioural changes.
- The reasoning / motivation.
- The test plan (what you tested and how).
- Risks and out-of-scope deferrals reviewers should know about.
Conventional Commits
Graphorin follows the Conventional Commits specification:
feat(scope): ...- new featurefix(scope): ...- bug fixdocs(scope): ...- documentation onlychore(scope): ...- tooling, dependencies, internal housekeepingrefactor(scope): ...- non-behavioural refactortest(scope): ...- tests onlyperf(scope): ...- performance improvement- A trailing
!(e.g.feat(core)!: ...) plus aBREAKING CHANGE:footer marks a breaking change.
The scope is the package name without the @graphorin/ prefix, e.g. core, agent, memory, server, cli.
The subject is in imperative mood, present tense, and at most 72 characters.
The body explains the why, not the what. One logical change per commit; squash on merge if your branch had churn.
Code style
- Biome is the single tool for both lint and format. Run
pnpm lintandpnpm format. - TypeScript strict mode,
noUncheckedIndexedAccess,composite: true. Zeroanyin public APIs. - ESM-only. Every
@graphorin/*package ships ESM only and runs on Node 22+. - Naming: files in
kebab-case.ts, types inPascalCase, functions and variables incamelCase, constants inSCREAMING_SNAKE_CASE, discriminated-union variants as'kebab-case'string literals. - Imports: always use
import typefor type-only imports. - No default exports in
@graphorin/coreor any other foundation package; named exports only. - The committed API reference must stay fresh.
documentation/apiis generated by TypeDoc and committed; the docs workflow regenerates it and fails on any diff (W-128). After changing public API or TSDoc, regenerate locally and commit the result:pnpm --filter @graphorin/docs run clean && pnpm --filter @graphorin/docs run build:typedoc && pnpm --filter @graphorin/docs run build:sanitise. - Doc snippets are code. Every
```tsblock on every hand-written documentation page is type-checked bypnpm run check-doc-snippets(deny-by-default: new pages are discovered automatically). Write snippets as complete, copy-pasteable programs. The only opt-out is a```ts no-checkinfo token on a deliberately partial block; it is visible in the diff and must be justified in the PR description.
Testing
- Unit tests are required for every change. The default coverage threshold is 70 %; security-critical packages target 85 %.
- Type-level tests (via
vitest'sexpectTypeOfortsd) are mandatory for every public interface. - Integration / end-to-end tests are required for changes that span multiple packages.
- Property tests (via
fast-check) are required for redaction, secret leakage, channel merge, and conflict-resolution thresholds once the corresponding packages exist. - Network-gated tests are opt-in via the
RUN_NETWORK_TESTS=1environment variable (e.g.RUN_NETWORK_TESTS=1 pnpm testin the affected package); they are skipped by default, so the standard CI run never makes outbound calls.
Versioning
Graphorin follows SemVer. Pre-1.0, minor bumps cover breaking changes and patch bumps cover everything else (the industry pre-1.0 norm). Once Graphorin reaches 1.0, strict SemVer applies. All @graphorin/* packages are released lockstep at the same version while on the 0.x line.
Versions are tracked with Changesets: open a PR with a changeset describing your change. All @graphorin/* packages release lockstep at the same version while on the 0.x line.
Release mechanics: the
0.2.0through0.5.0bumps were applied by the maintainer as manual passes becausechangeset versionkept computing a bogus major bump: with@graphorin/serverdeclaringworkspace:*peer dependencies on four sibling packages, Changesets escalated any minor/patch bump of those peers into a major forserver, and thefixedlockstep group then lifted every package to1.0.0. That root cause is fixed (audit E2): the internal peers are now ranged (workspace:>=<current minor>.0 <1.0.0) andonlyUpdatePeerDependentsWhenOutOfRangeis enabled, sochangeset versioncomputes the correct lockstep bump and regenerates per-package CHANGELOGs. The floor of those sibling peers is NOT static (W-135):bump-version --syncrewrites it to the just-computed minor AFTERchangeset versionruns (a statically narrow range would re-trigger the fixed-group escalation), so the publishedserverrequires its siblings at the same minor and npm cannot assemble a mixed install;check-version-consistencyfails a release pass that skipped the rewrite, andpnpm installmust be re-run before committing the release PR so the lockfile matches the rewritten manifests (the@changesets/changelog-githubgenerator needs aGITHUB_TOKEN; CI has one, locally export one). Two paths restore the fully-automatic "Version Packages" PR (W-015): either add a fine-grainedRELEASE_PATsecret (permissions: contents + pull-requests, scoped to THIS repository only, with an expiry) -release.ymlusessecrets.RELEASE_PAT || secrets.GITHUB_TOKEN- or enable the repo setting "Allow GitHub Actions to create and approve pull requests". Without either, the defaultGITHUB_TOKENcannot open PRs here,release.ymlruns on pushes tomainwith pending changesets show as failed for exactly that reason, and the release PR is opened by the maintainer: runpnpm run versionon a branch (the rootversionscript now chainschangeset version && bump-version --sync, so the private workspaces and every text site - badges, footers, image tags, benchmark baselines; inventory inscripts/version-surface.mjs- are synchronized andcheck-version-consistencyself-verifies in one step), then open the PR. Whichever path produced the version commit, four follow-ups stay manual (bump-version prints them as reminders): the rootCHANGELOG.mdsection, the README version teaser, the migration-guide retitle, and thedocumentation/apiregeneration. Publishing then happens automatically on the merge-to-mainrun once zero changesets remain -0.6.0shipped through exactly this flow. Everything else derives the version at build time (VERSION = pkg.versionin every package), and thecheck-version-consistencyCI gate fails any PR that hardcodes a framework version in code or lets a text site drift. Themvp-readinessworkspace audit also rejects a release whose per-package CHANGELOG top entry does not match the version being released. Do not hand-bump versions in a feature PR: author a changeset and let the release pass apply it.
Publish auth (W-139): releases authenticate via npm trusted publishing (OIDC), not a long-lived token. One-time registry-side setup, per package (all 27): npmjs.com -> package -> Settings -> Trusted publisher -> GitHub Actions, repository
o-stepper/graphorin, workflowrelease.yml. Thereadinessjob ofrelease.ymlruns the full build+test withcontents: readand no persisted git credentials; only thepublishjob holdscontents: write+id-token: write, upgrades npm to >= 11.5.1 (the OIDC exchange floor;changeset publishshells tonpm publish), and carries noNPM_TOKEN. A dispatch rehearsal withRELEASE_ENABLED != 'true'validates the environment but not the token exchange - only a real publish does, so until the first OIDC release passes the C4 provenance smoke, theNPM_TOKENsecret stays in repo settings as a one-line rollback (re-add the env on the changesets/action step); after that verified release, delete the secret.Git tags & provenance: the tag history starts at
0.5.0, the first version published to the npm registry (each published package carries a@graphorin/<pkg>@<version>tag created by the release pipeline). The earlier0.2.0,0.3.0, and0.4.0bumps were internal-only and were never published, so no retro tags were backfilled for them: audit item CI-7 keeps the changelog backfill and these CONTRIBUTING notes, but retro-tagging never-published versions is a deliberate skip. Every real publish is provenance-checked in CI by the post-publishnpm audit signaturessmoke in.github/workflows/release.yml.
Privacy & no-phone-home
Graphorin makes no implicit network calls of any kind. Any change that introduces a fetch, http(s).request, or socket call must be in an allow-listed code path (LLM provider adapters, MCP transports, OAuth flows, opt-in pricing refresh, embedder model downloads, or storage-adapter network drivers). The CI script pnpm run check-no-network enforces this. PRs that fail this check will not be merged.
See SECURITY.md for the full privacy commitment.
Reporting bugs and requesting features
- Bug reports: open an issue.
- Feature requests: open an issue.
- Security disclosures: see
SECURITY.md. - Design discussion / Q&A: GitHub Discussions (enabled post-launch).
License
By contributing to Graphorin, you agree that your contributions will be licensed under the MIT License.
Graphorin · v0.7.0 · MIT License · © 2026 Oleksiy Stepurenko · https://graphorin.com · https://github.com/o-stepper/graphorin
Pack gate
CI's package-shape job validates the PUBLISHED artifacts (the workspace's workspace:* symlinks and shared devDependencies mask packaging defects): publint + @arethetypeswrong/cli over every packed tarball, then a scratch consumer that installs all 27 tarballs in ONE npm install call (load-bearing on Version-Packages branches, where the tarballs name not-yet-published versions and only a simultaneous install lets the file: instances satisfy each other) and compiles scripts/pack-consumer/consumer.ts under a moduleResolution x zod matrix with skipLibCheck: false. Run it locally with node scripts/check-package-shape.mjs (add --skip-build after a fresh pnpm build; needs registry access). The --allow-fail zod4 flag tracks the known zod-4 d.ts break until its fix lands - remove it from ci.yml together with that fix.