This is the public OpenKnowledge repository. Keep changes compatible with the published package and standalone clone experience.
- Read README.md for the project overview.
- Read CONTRIBUTING.md before changing public PR flow, dependencies, or exported docs.
- Use Node.js 24 or newer and pnpm 10 or newer.
- This repo does not use code comments. Read Comment policy before writing any.
pnpm install
pnpm run check
pnpm run buildUse these during development:
pnpm run format
pnpm run lint
pnpm run typecheck
pnpm run testRun local apps:
pnpm --filter @inkeep/open-knowledge-app run dev
cd docs
pnpm run devpackages/app- web app and editor UIpackages/cli- CLI and package entrypointpackages/core- shared domain logicpackages/desktop- Electron desktop apppackages/plugin- agent integration packagepackages/server- local collaboration serverdocs- documentation site
Write no comments. Explanation belongs in names, types, tests, the commit message, the PR body, or a doc — a comment is the one place it rots unseen. This inverts the usual open-source norm, so it surprises people; it is deliberate, and pnpm run lint enforces it.
Keep only these, and nothing else:
- Directives a tool parses, anchored at the comment's first line:
biome-ignore lint/style/noVar: reason,@ts-expect-error the fixture is deliberately malformed(a reason is required),oxlint-disable-next-line,@vitest-environment jsdom,@ts-nocheck,@vite-ignore,/// <reference types="vite/client" />,SPDX-License-Identifier: GPL-3.0-or-later,/* @lintignore knip cannot see the dynamic import site */(block or JSDoc form only; the line-comment form is rejected because knip does not read it). If a tool reads it, it is code. - JSDoc carrying
@deprecated. The allowlist also defines an audit-tag class, but its tag list is empty in this repo, so that class admits nothing here: a tag-bearing JSDoc block is rejected as prose. - Contract markers, which the comment must BEGIN with:
STOP: a cross-file contract a reader must not break,WARN: a sibling that silently drifts if this changes,UPSTREAM(electron/electron#19920): a code shape forced on us from outside this repo. - A validated citation, as in
precedent #42; the numbered titles are listed inPRECEDENTS.mdat the repo root. - Registered guard markers: write
error-log-shape-ok: <why>andpresence-exempt: <why>only. The predicate also admitsdocumented exemption from Precedent #30,Defect class: <slug>andTOLERATED: <why>, but their checkers live in test trees the mirror does not carry, so lint accepts them here while nothing reads them — that is prose wearing a marker, and it is a review finding even though the gate is green. The set is fixed; you cannot invent a new one. - Type-only JSDoc, in a .mjs or .js file only: a JSDoc block whose every line is a bare type annotation, which is the only typing mechanism the JavaScript strata have. Two hard edges: a description after the identifier makes the block prose, and the same block in a .ts file is prose, because TypeScript has real syntax there.
An UPSTREAM referent must be one of four shapes, so it always resolves to something a reader can look up: a GitHub issue (electron/electron#19920), an RFC (RFC 9457), a CommonMark section (CommonMark §6.5), or a package version (vite@7.1.0). Anything else fails lint.
Banned: plain prose, JSDoc description text, //////// section dividers, commented-out code such as const disabled = true;, TODO, FIXME, @ts-ignore, and process metadata such as PRD-1234 or at line 370. Process metadata stays banned inside an allowlisted comment too: a legitimate STOP marker still fails lint if it carries a ticket number, a spec path, or a line reference.
A marker is not a license, and passing lint is not the same as being legitimate. Lint rejects a referent it cannot resolve (UPSTREAM(some blog post): not a resolvable referent) and a citation of a precedent that does not exist (precedent #999). It cannot tell whether a well-formed marker is load-bearing, so a STOP: essay or a WARN: that warns of nothing passes lint and gets caught in review instead — as a worse finding than the comment it disguises. The same applies to reasoning parked in a suppression's reason slot: a suppression reason is free text, so a paragraph of design rationale smuggled into one passes lint, and is a review finding for the same reason.
If pnpm run lint fails on a comment, the diagnostic names the class, the fix, and a link into lint-plugins/no-comments/README.md, which is the full policy. Delete the comment, or convert it to one of the forms above.
Shell scripts are declared in scope by no-comments.config.jsonc, but the sweep that reads the hash grammars is not part of this mirror, so pnpm run lint here checks the JavaScript and TypeScript families only. A .sh file is gated upstream; the same policy applies to it, and a reviewer will hold it to that.
- This repo is generated from an allowlist. Do not rely on hidden source-only folders being present.
- Public PRs are reviewed by maintainers and accepted changes sync back here automatically. A PR may close rather than show as merged; that is expected for this mirror.
- Top-level public docs such as
README.md,CONTRIBUTING.md, andAGENTS.mdare overlay files. Keep them public-safe and standalone. - Do not add secrets, private customer context, internal-only specs, local paths, or generated debug artifacts.
- Keep dependency updates paired with
pnpm-lock.yaml. Runpnpm run noticeswhen third-party notices may change.
Every behavior-changing PR ships a .changeset/<kebab-name>.md file. The body becomes the user-facing entry on the next beta's GitHub Release and on the aggregated stable Release notes — that's how npm consumers and desktop auto-update users learn what changed. Write release-note copy, not a commit-message reprise.
- Create one with
pnpm run changeset, or hand-write a file named.changeset/<descriptive-kebab-slug>.md. - Front-matter: at minimum
'@inkeep/open-knowledge': patch. OpenKnowledge follows semver with a pre-1.0 shift-down: while we're below1.0.0, what semver would call a major (breaking API change) is encoded asminor, and what semver would call a minor (new feature) is encoded aspatch. Most changesets arepatch.minoris rare — reserve it for large API contract changes or large feature additions. Never declaremajorpre-1.0 (see the"//"line in.changeset/config.json).
Changelog authoring and review. Use one short sentence for the user-visible outcome; a second only for a necessary qualification or upgrade action. Maximum 50 words per changeset body, across all paragraphs and bullets. Omit implementation details, internal identifiers and exhaustive edge cases; link migration instructions. Reviewers check accuracy, required actions and brevity; request technical detail in docs. Never split one explanation across changesets to evade the budget.
- Don't write inline references to sibling-package versions (e.g.
@inkeep/open-knowledge-core@0.5.0-beta.6) — the fixed-group lock-step bumps are computed at release time and any number you'd write would be wrong. - Skip changesets for docs-only edits, test-only edits, or CI-only edits that don't change runtime behavior.
Cadence: merging a PR with a changeset triggers a beta publish within minutes via the event-driven release.yml on the public mirror.
Run the smallest relevant check while iterating, then run:
pnpm run checkFor UI or editor changes, also run the affected package tests from packages/app.