Skip to content

Development checks

Setup and Git hooks

Use Node.js 22+, pnpm (the version in package.json), and Rust/rustfmt. Install prek with brew install prek on macOS or uv tool install prek==0.4.14. The Nix development shell also supplies prek.

Terminal window
pnpm install --frozen-lockfile
prek install
prek run --all-files

The hooks also need Go (actionlint and yamlfmt), Python (schema/whitespace checks), and Cargo/rustfmt on PATH. Docker Compose and hadolint are required when checking Compose files or Dockerfiles. On macOS, install the missing tools with brew install go python hadolint, Rust with rustup (including the rustfmt component), and Docker Compose using Docker Desktop or the Docker CLI’s Compose plugin. prek manages isolated hook environments; it does not replace these hook-specific tools.

Migrating an existing installation

The configuration remains .pre-commit-config.yaml. There is no need to install or run the old pre-commit runner. Replace its installed shim explicitly:

Terminal window
prek install --force
prek run --all-files

--force overwrites the existing pre-commit shim rather than chaining both runners. Back up any custom hook first; preserve custom behavior deliberately instead of blindly overwriting it. If an earlier migration left a pre-commit.legacy hook, inspect it and remove it only if it is an obsolete runner shim. Repeat installation for any additional hook types you previously installed (--hook-type pre-push, etc.); this repository configures only the default pre-commit hook. Remove the old runner from your package manager after migration. Re-running prek install is idempotent. Linked Git worktrees share hooks: install once in your main checkout, not as a worktree-local experiment.

The upstream repositories and Renovate’s pre-commit manager retain their names: they describe the compatible configuration ecosystem, not the runner we install.

Commands

CommandPurpose
pnpm lint / pnpm lint:fixBiome lint / safe lint fixes
pnpm format / pnpm format:checkBiome formatting plus Markdown/MDX Prettier formatting / checks
pnpm format:markdown / pnpm format:markdown:checkMarkdown/MDX only
pnpm checkSvelte and TypeScript diagnostics
pnpm testApplication tests, recording-boundary regressions, and preview tooling tests
pnpm buildRegenerate the settings schema with Biome, then build the app
pnpm docs:buildBuild documentation (first run pnpm --dir website install --frozen-lockfile)
prek run --all-filesAll hook checks, including YAML and Rust formatting; may fix files

Run the checks again after applying fixes. CI uses the same lint/format commands, boundary tests, type checks, and prek run --all-files. Existing CI actor restrictions and push/manual triggers are unchanged. Use the manual CI workflow on a branch for pre-merge checks. Generated/build directories are excluded from Biome/Prettier; the tracked src/lib/generated/settings.schema.json is intentionally formatted by Biome and reproduced by pnpm generate:schema.

Formatting and lint coverage

The Biome 2.5.12 migration explicitly opts into experimental full HTML/Svelte/Astro support. See Biome’s language support. The tree was formatted and validated with application tests, svelte-check, and application/documentation builds; re-run these when updating Biome.

FilesCoverage and limitations
JS/JSX/TS/TSX, MJS/CJSBiome formatting and migrated ESLint rules (exceptions below). Import sorting is disabled to avoid changing side-effect ordering.
Svelte, HTML, future AstroBiome experimental full formatting and embedded JS/TS lint rules. Svelte diagnostics remain in pnpm check. No Astro files are tracked currently; validate new Astro syntax with the docs build. This is not a claim of complete framework/template lint coverage.
JSON/JSONCBiome formatting and recommended JSON lint rules. renovate.json5 became equivalent renovate.json, because Biome does not support general JSON5 syntax (such as unquoted keys). User JSON5 settings are runtime data, not formatting inputs.
CSSBiome formatting and recommended CSS lint rules; Tailwind directives are enabled for docs styles.
Markdown/MDXPrettier only, with prose wrapping preserved and embedded code formatting disabled. No MDX files are currently tracked; future MDX is included in scripts, hooks, and CI. .prettierignore also restricts direct/editor invocations to Markdown/MDX.
YAMLExisting yamlfmt hook (both pnpm lockfiles remain excluded), plus YAML/schema/actionlint checks. Run prek, not Biome, for this coverage.
RustExisting rustfmt hook with --manifest-path src-tauri/Cargo.toml --.
Nix, TOML, Python, shell, plist, images, extensionless filesNot formatted by Biome or Prettier. These had no supported built-in Prettier formatter previously. Keep their established conventions; nix fmt remains available. Shell embedded in workflows is checked by actionlint.

ESLint migration details

biome migrate eslint --include-inspired supplied the rule mappings, which were consolidated in biome.json. The old TypeScript recommended configuration also applied to .js files; it disabled checks handled by TypeScript. Those explicit settings are retained, with the JS-recommended checks restored for MJS/CJS. Declaration-file namespaces remain allowed for SvelteKit’s App namespace. noUnusedImports and noUnusedFunctionParameters are enabled separately from noUnusedVariables (the migration tool only mapped the latter). A regression fixture checks unused parameters in independently linted MJS scripts, outside TypeScript’s application checks. The retired recording routes no longer require template-only Svelte $store import exceptions. The old ESLint configuration did not lint Svelte files.

The migration is not an assertion that every ESLint rule has an identical Biome implementation. The known differences are:

  • no-delete-var and no-octal: Biome’s parser rejects delete identifier and legacy 077 literals in the repository’s module files. There is no separate lint rule; this does not promise coverage of hypothetical sloppy-mode scripts.
  • no-new-symbol: the old TypeScript configuration disabled this rule. Biome’s noInvalidBuiltinInstantiation rejects new Symbol() in MJS/CJS; TypeScript diagnoses it in checked application code.
  • no-unexpected-multiline: no dedicated Biome rule. Formatting checks reveal noncanonical line breaks, but formatting preserves parsed behavior and cannot recover intent after an accidental automatic-semicolon-insertion continuation. Review expression statements across line breaks carefully; type checking is not an equivalent replacement in every case.
  • no-invalid-regexp: literal regex syntax is parsed, but constant-string constructors such as new RegExp("[") are not validated by Biome. Tests must exercise constructed regular expressions. This is a retained lint gap, not coverage provided by type checking.
  • @typescript-eslint/triple-slash-reference: Biome has no equivalent restriction on valid triple-slash references. Prefer imports; review any new reference directive.
  • @typescript-eslint/no-unused-expressions: Biome’s noUnusedExpressions is enabled, including rejection of short-circuit expression statements; TypeScript-specific syntax may differ between implementations.
  • Inspired mappings (noBannedTypes, noUselessThisAlias, noAssignInExpressions, noTsIgnore) are not option-for-option equivalents. In particular, noTsIgnore bans @ts-ignore but does not enforce the old description requirement for @ts-expect-error or the ban on @ts-nocheck; retain those conventions in review. Biome also rejects empty function bodies, so deliberate no-op arrows explicitly return undefined. Two local suppressions document existing initialization and subscribe-once patterns that Biome otherwise flags.

Session model package boundary

packages/session-model is the private @persyk/session-model workspace package. Root pnpm check runs its standalone source/test TypeScript checks before the app check; pnpm test runs its tests before application integration tests. Both remain part of the existing IPC CI workflow. Run either package command independently with pnpm --filter @persyk/session-model check or pnpm --filter @persyk/session-model test.

pnpm test:boundaries also checks the package’s runtime source with a TypeScript AST allowlist: only Zod and relative imports that stay inside package source are allowed, including type imports, re-exports and dynamic imports. Nonliteral dynamic imports, reference directives, CommonJS and platform-global identifiers are rejected. The check deliberately reserves those identifiers even as local names. Tests may use Node filesystem APIs to read the shared packages/session-model/fixtures corpus; Rust compatibility tests include the same files. The exports map permits only the explicit root API, not internal modules or fixture subpaths. These boundary checks also run in the relevant Git hook.

Recording core boundary

src/lib/recording/core/**/*.ts must not import Tauri, Svelte, OpenAI, stores, realtime clients, browser adapters, settings, or persistence through the previously restricted paths. The same seven browser globals remain forbidden. Biome’s noRestrictedImports and noRestrictedGlobals enforce this boundary; ** patterns are necessary to retain the old gitignore-style matching of nested import paths. The restrictions are path-based as before, not a new universal dependency resolver.

pnpm test:boundaries runs the actual configured linter against temporary TypeScript fixtures. It checks every restricted import family (including type imports, side-effect imports and re-exports), nested paths, all seven globals, and permitted capability/injected dependencies. It also checks that browser adapters remain outside this restriction. The suite runs in pnpm test, CI, and the relevant hook.

Hook compatibility

All original whitespace/EOF, YAML, large-file (500 KB), Taskfile schema, actionlint, Compose, hadolint, yamlfmt, and Rust hooks retain their versions, file filters and arguments. The local ESLint hook is replaced with Biome and expanded to supported code formats; Markdown/MDX and boundary regression hooks are added.

The tracked tree currently has no Taskfile, Compose file, or Dockerfile, so prek correctly reports those three hooks as “no files to check.” Migration validation used valid and invalid fixtures in a temporary Git repository to verify each hook actually passes valid input and rejects invalid input, without changing the file filters or installing hooks into a shared worktree.