Skip to content

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.json packageManager field - corepack will activate it automatically:
bash
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

bash
git clone https://github.com/o-stepper/graphorin.git
cd graphorin
corepack enable
pnpm install --frozen-lockfile
pnpm -r build
pnpm -r test

If everything is green, you are ready to make changes.

Development workflow

  1. Create a branch. feat/<slug> for feature work, fix/<slug> for bug fixes, chore/<slug> for maintenance, or hotfix/<short-description> for unplanned fixes. Never push directly to main. Branches are deleted on merge (pass --delete-branch or enable auto-delete); do not resurrect a merged branch for follow-up work - cut a fresh one from main so you never rebase onto pre-fix code.
  2. Make your change inside the relevant packages/<scope> or examples/<app> workspace.
  3. Add or update tests. Vitest runs across the whole workspace.
  4. Run the local checks before opening a PR:
bash
pnpm -r build
pnpm -r typecheck
pnpm -r test
pnpm lint
pnpm run check-no-network
  1. Add a changeset for any change that affects a published @graphorin/* package:
bash
pnpm changeset

Pick 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.

  1. Open a Pull Request against main using 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 feature
  • fix(scope): ... - bug fix
  • docs(scope): ... - documentation only
  • chore(scope): ... - tooling, dependencies, internal housekeeping
  • refactor(scope): ... - non-behavioural refactor
  • test(scope): ... - tests only
  • perf(scope): ... - performance improvement
  • A trailing ! (e.g. feat(core)!: ...) plus a BREAKING 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 lint and pnpm format.
  • TypeScript strict mode, noUncheckedIndexedAccess, composite: true. Zero any in public APIs.
  • ESM-only. Every @graphorin/* package ships ESM only and runs on Node 22+.
  • Naming: files in kebab-case.ts, types in PascalCase, functions and variables in camelCase, constants in SCREAMING_SNAKE_CASE, discriminated-union variants as 'kebab-case' string literals.
  • Imports: always use import type for type-only imports.
  • No default exports in @graphorin/core or any other foundation package; named exports only.
  • The committed API reference must stay fresh. documentation/api is 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 ```ts block on every hand-written documentation page is type-checked by pnpm 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-check info 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's expectTypeOf or tsd) 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=1 environment variable (e.g. RUN_NETWORK_TESTS=1 pnpm test in 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.0 through 0.5.0 bumps were applied by the maintainer as manual passes because changeset version kept computing a bogus major bump: with @graphorin/server declaring workspace:* peer dependencies on four sibling packages, Changesets escalated any minor/patch bump of those peers into a major for server, and the fixed lockstep group then lifted every package to 1.0.0. That root cause is fixed (audit E2): the internal peers are now ranged (workspace:>=<current minor>.0 <1.0.0) and onlyUpdatePeerDependentsWhenOutOfRange is enabled, so changeset version computes the correct lockstep bump and regenerates per-package CHANGELOGs. The floor of those sibling peers is NOT static (W-135): bump-version --sync rewrites it to the just-computed minor AFTER changeset version runs (a statically narrow range would re-trigger the fixed-group escalation), so the published server requires its siblings at the same minor and npm cannot assemble a mixed install; check-version-consistency fails a release pass that skipped the rewrite, and pnpm install must be re-run before committing the release PR so the lockfile matches the rewritten manifests (the @changesets/changelog-github generator needs a GITHUB_TOKEN; CI has one, locally export one). Two paths restore the fully-automatic "Version Packages" PR (W-015): either add a fine-grained RELEASE_PAT secret (permissions: contents + pull-requests, scoped to THIS repository only, with an expiry) - release.yml uses secrets.RELEASE_PAT || secrets.GITHUB_TOKEN - or enable the repo setting "Allow GitHub Actions to create and approve pull requests". Without either, the default GITHUB_TOKEN cannot open PRs here, release.yml runs on pushes to main with pending changesets show as failed for exactly that reason, and the release PR is opened by the maintainer: run pnpm run version on a branch (the root version script now chains changeset version && bump-version --sync, so the private workspaces and every text site - badges, footers, image tags, benchmark baselines; inventory in scripts/version-surface.mjs - are synchronized and check-version-consistency self-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 root CHANGELOG.md section, the README version teaser, the migration-guide retitle, and the documentation/api regeneration. Publishing then happens automatically on the merge-to-main run once zero changesets remain - 0.6.0 shipped through exactly this flow. Everything else derives the version at build time (VERSION = pkg.version in every package), and the check-version-consistency CI gate fails any PR that hardcodes a framework version in code or lets a text site drift. The mvp-readiness workspace 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, workflow release.yml. The readiness job of release.yml runs the full build+test with contents: read and no persisted git credentials; only the publish job holds contents: write + id-token: write, upgrades npm to >= 11.5.1 (the OIDC exchange floor; changeset publish shells to npm publish), and carries no NPM_TOKEN. A dispatch rehearsal with RELEASE_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, the NPM_TOKEN secret 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 earlier 0.2.0, 0.3.0, and 0.4.0 bumps 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-publish npm audit signatures smoke 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

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.