Merge brought in the RFC-classification reorg and two new doc gates; rewrite every drifted packages/<name> cross-link (Markdown link targets, moved-README relative depths, and .ts comment paths) to the grouped paths. Add two doc-sync/hygiene gates so the manual checks this restructure needed become automated: - verify-package-paths.ts: flags a packages/<path> reference (in Markdown or a .ts comment/string) that does not resolve AND names a real package in a segment — i.e. a stale path to a MOVED package. A path naming a non-existent package (a forward-looking proposal) is left alone, so it applies uniformly across proposed/implemented/rejected. - check-workspace-constraints: assert the packages/<group>/<pkg> depth-2 shape (group dirs carry no package.json; no flat or over-nested packages). Group names stay open; only the shape is fixed.
7.0 KiB
Development guide
This guide covers the local setup needed to work on DeepSeek Harness and understand the local hooks, daily checks, and CI gates.
Prerequisites
- Node.js 24 or newer. The repo declares
node >=24; CI runs the matrix on Node 24 and 26. - Corepack-enabled pnpm. The repo pins
pnpm@11.7.0inpackage.json; runcorepack enableifpnpm --versiondoes not resolve through Corepack. - Git.
- Optional: a DeepSeek API key for the coding-agent demo and real-API e2e tests.
First-time setup
Install dependencies from the repo root:
pnpm install
The install also runs the root postinstall script, which installs lefthook from the repo dev dependency through scripts/install-lefthook.mjs; the wrapper uses lefthook's reviewed --force mode so linked worktrees with an existing core.hooksPath do not fail normal pnpm run … commands.
If hooks are missing because dependencies were restored from cache or postinstall was skipped, install them manually:
pnpm exec lefthook install --force
Run typecheck once after a fresh clone:
pnpm run typecheck
That first typecheck builds declaration output used by type-aware linting for vendored packages. Without it, pnpm run lint can report unresolved-type no-unsafe-* errors even when source code is fine.
If you are preparing to push from a fresh clone or worktree, also build once:
pnpm run build
pnpm run hygiene includes publint, which validates package entrypoints against the built lib/*.js files. A fresh worktree has no bundled JS until pnpm run build runs.
Environment variables
The real DeepSeek adapter and coding-agent demo read credentials from the environment or from a gitignored .env at the repo root:
DEEPSEEK_API_KEY=sk-...
DEEPSEEK_BASE_URL=https://... # optional
DEEPSEEK_BASE_URL is optional and defaults to the public API. Never commit real credentials. The real-API e2e suites self-skip when DEEPSEEK_API_KEY is not set.
Git hooks
lefthook is configured in lefthook.yml as an early local checkpoint before review:
pre-commitruns staged-file ESLint fixes,pnpm run typecheck, and the vendor manifest guard.pre-pushrunspnpm run test,pnpm run test:snapshot,pnpm run hygiene,pnpm run doc-sync, andpnpm run verify-module-graph.
The vendor manifest guard checks that changes under vendor/*/src are staged with the matching vendor/README.md manifest update. See vendor/README.md before editing vendored code.
These hooks do not exactly mirror CI. Notably, pre-push runs unit tests without coverage, while CI runs pnpm run test:coverage; CI also runs an echo-agent smoke test and exercises the matrix on Node 24 and 26.
CI gates
The GitHub workflow runs these gates on each pull request:
pnpm install --frozen-lockfilepnpm run constraintspnpm run typecheckpnpm run lintpnpm run doc-syncpnpm run verify-module-graphpnpm run test:coveragepnpm run test:snapshotpnpm run buildpnpm run knip && pnpm run publint- an echo-agent smoke test that checks the demo's tool call, tool result, and JSONL output
pnpm run hygiene is the local shorthand for pnpm run knip && pnpm run publint && pnpm run constraints; CI splits pnpm run constraints into its own earlier step, then runs pnpm run knip && pnpm run publint after pnpm run build.
Daily commands
Use these from the repo root:
pnpm run test # unit tests
pnpm run test:coverage # unit tests with per-file coverage gates
pnpm run test:e2e # real-API tests; self-skips without DEEPSEEK_API_KEY
pnpm run typecheck # build declarations, then typecheck source, tests, and examples
pnpm run lint # eslint .
pnpm run lint:fix # eslint . --fix
pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs
pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events-and-services.md from source
pnpm run verify-cordis-catalog # fail if the cordis events/services catalog is stale
pnpm run verify-md-wrap # fail on hard-wrapped prose paragraphs in docs/README markdown
pnpm run verify-type-equiv # fail if a ```ts type-equiv doc block drifts from its source type
pnpm run doc-sync # doc-typecheck, cordis-catalog freshness, markdown wrap/link, and type-equiv verification
pnpm run gen-module-graph # regenerate docs/module-graph.md from package peerDeps
pnpm run verify-module-graph # fail if docs/module-graph.md is stale
pnpm run build # build declarations and JS bundles
pnpm run hygiene # knip, publint, and workspace constraints
When changing package public behavior, update the relevant README or JSDoc in the same change. pnpm run doc-sync catches checked TypeScript snippets, cordis events/services catalog drift, and hard-wrapped markdown prose, but broader prose/API sync still needs review.
Demos
The echo demo does not need API credentials:
pnpm run demo:echo
The coding-agent demo uses the real DeepSeek adapter and needs DEEPSEEK_API_KEY in the environment or repo-root .env:
pnpm run demo:coding
TODO markers
Use one of three comment tags to flag known issues in the code, ordered by urgency:
FIXME— an issue that should block a new release. A release should not ship with an openFIXMEunless reviewers explicitly agree the change can be merged anyway.TODO— an issue that should be fixed soon, once we have the resources.XXX— an issue that we may fix someday; lowest priority, no commitment.
Pick the tag that matches the urgency so anyone scanning the code can tell a release blocker from a someday-maybe.
Documenting types verbatim (ts type-equiv)
The core data structures docs paste real type definitions so a reader sees the exact shape. To keep a paste from drifting when source changes, fence it as ```ts type-equiv (instead of ```ts) and register it in scripts/type-equiv.manifest.json with the source file and symbol it mirrors:
{ "doc": "docs/core-data-structures/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" }
pnpm run verify-type-equiv (part of doc-sync) then extracts that symbol's declaration from source via the TypeScript parser and asserts the block matches it (whitespace- and comment-insensitive, so a doc block may show a clean definition and the prose can carry the semantics). It also enforces a 1:1 correspondence: every ts type-equiv block has exactly one manifest entry and vice-versa, so a block can't go silently unchecked and a stale entry can't linger. doc-typecheck skips ts type-equiv blocks (they aren't standalone-compilable) and excludes them from its opt-out ratio. When you change a documented type, the gate fails until you update the paste; when you add or remove a block, update the manifest in the same change.
Architecture context
Read docs/architecture.md before changing anything under packages/. The codebase is built around Cordis plugins, event-sourced sessions, typed service seams, and explicit extension points.