Files
deepseek-harness/docs/development.md
T

12 KiB

Development guide

English | 中文

This onboarding guide helps project contributors get started with the local environment, daily workflow, and CI flow; see the Agent Notes for design rationale and technical trade-offs.

Prerequisites

  • Node.js supports 22.19+ and 24+. CI covers 22.19, 24, and 26; see the Node engine floor Agent Note.
  • Corepack-enabled pnpm. The repo pins pnpm@11.7.0 in package.json; run corepack enable if pnpm --version does not resolve through Corepack.
  • Git.
  • Optional: a DeepSeek API key for the TUI/Headless/ACP agent demos 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 script 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 runs the whole-repo tsc -b graph: it emits every package/vendor lib/types and checks examples, tests, and scripts through the two no-emit aggregates described below.

TypeScript project layout

The repository's TypeScript configuration has exactly three roles; every tsconfig file plays one of them.

File Role Forms a program?
tsconfig.json Solution root: extends base, files: [], references to the two aggregates. The whole-repo tsc -b tsconfig.json graph, the tsserver discovery entry, and — through the inherited paths — the resolution config for tsx running examples/ and scripts/ (their nearest tsconfig is this file). No
tsconfig.host.json Host aggregate: host-side packages (via references), examples, tests, scripts, website. Excludes packages/client. Yes
tsconfig.client.json Client aggregate: packages/client/* packages and their tests, apps/web. Yes
tsconfig.base.json Shared compilerOptions and the source paths map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no include, so its paths apply to every importer. No
tsconfig.base.client.json Browser compiler shape (jsx, DOM libs, types: []) extended by the client aggregate and every packages/client/* package. No

Host and client stay two aggregate programs because both sides declaration-merge the cordis Context interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a ts.Program — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Two disciplines follow:

  • tsconfig.base.json never gains include or files: they would leak into every extending package project and narrow the facade's match-all scope.
  • A script that builds a repo-wide ts.Program seeds tsconfig.host.json or tsconfig.client.json explicitly — never the root solution, because flattening both aggregates into one program collides the Context merges. Program-backed generators and gates (scripts/ts-project.ts consumers, doc-typecheck standalone mode) are host-only by decision; the client side gains program-backed tooling only with a concrete need.

Static analysis and tests resolve workspace imports through the base paths map to src and must pass on a clean tree; gates that consume built lib/ output declare that dependency explicitly. Decision record: solution-root note; the tsc-first emit pipeline is the ts-build-config note.

If a relevant local check consumes built package output, build once first:

pnpm run build

pnpm run hygiene includes publint, which validates package entrypoints against the built lib/*.js files, and verify-node-next-types, which validates built declarations against a temporary NodeNext consumer. A fresh worktree has no bundled JS or declarations until pnpm run build runs; ordinary commits and pushes do not require that build unless their selected checks consume it.

Environment variables

The real DeepSeek adapter and key-backed agent demos 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 a fast local checkpoint:

  • pre-commit runs staged-file ESLint fixes, checks the staged diff for whitespace errors, and runs the vendor manifest guard.
  • pre-push runs only the incremental repository typecheck (tsc -b over the root solution, covering both the host and client aggregates).

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.

The hooks intentionally do not run tests, snapshots, documentation checks, builds, or hygiene. Contributors run the checks relevant to the changed behavior once; CI owns exhaustive coverage, built-artifact smokes, and the Node 22.19, 24, and 26 compatibility matrix.

Contributors can opt into the comprehensive local gate set with pnpm run check:all. The command is independent of both Git hooks and is not an agent instruction.

CI gates

The keyless CI workflow groups independent gates into broad lanes and runs a smaller compatibility signal across supported Node versions. Artifact consumers wait for one build within their lane. The separate real-API workflow runs pnpm run test:e2e with its configured worker bound. See scripts/run-gates.ts and the workflow files for the current gate and job inventory.

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 check:all      # comprehensive opt-in gate set; not wired to Git hooks
pnpm run typecheck      # tsc -b over the root solution: emits package/vendor lib/types, checks both aggregates
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.md + services.md from source
pnpm run verify-cordis-catalog  # fail if either cordis catalog is stale
pnpm run verify-export-jsdoc    # fail if a module-level package export lacks complete JSDoc
pnpm run gen-doc-graphs     # regenerate generated relationship docs from source and curated graph definitions
pnpm run verify-doc-graphs  # fail if generated relationship docs are stale
pnpm run verify-md-wrap  # fail on hard-wrapped prose paragraphs in docs/README markdown
pnpm run verify-mermaid  # fail if a ```mermaid diagram has invalid Mermaid syntax
pnpm run verify-type-equiv  # fail if a ```ts type-equiv doc block drifts from its source type
pnpm run verify-doc-budgets  # fail if a budgeted standing doc exceeds its word ceiling
pnpm run doc-sync       # all Markdown/doc gates, scheduled concurrently; the doc-sync leaf list in scripts/run-gates.ts is the full list
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          # emit lib/types intermediates, then bundle lib/index.* runtime files
pnpm run verify-node-next-types  # fail if built declarations are not NodeNext-consumable
pnpm run hygiene        # knip, publint, workspace constraints, and NodeNext declaration check

When changing package public behavior, update the relevant README or JSDoc in the same change. pnpm run doc-sync catches checked TypeScript snippets, generated doc freshness, markdown wrap/link drift, type equivalence, translation pairing, Mermaid syntax, and doc budgets, but broader prose/API sync still needs review.

Demos

The one-shot Headless coding agent needs DEEPSEEK_API_KEY in the environment or repo-root .env:

pnpm run demo:headless "summarize this workspace"

The full-screen interactive coding agent needs DEEPSEEK_API_KEY in the environment or repo-root .env:

pnpm run demo:tui

The self-referential cordis-agent demo can inspect and modify its live plugin runtime and needs the same credentials:

pnpm run demo:cordis

The ACP server agent demo exposes the agent over JSON-RPC stdio and also needs DEEPSEEK_API_KEY:

pnpm run demo:acp

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 open FIXME unless 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 source-equivalent declarations together with their original JSDoc so a reader sees the exact shape and source contract. 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 and attached JSDoc from source via the TypeScript parser and asserts the block matches both. For a class whose implementation bodies do not belong in the catalog, use ```ts public-api and set "projection": "public-api"; the checked projection retains the public fields, constructor, accessors, methods, and original class/member JSDoc while omitting bodies and private or protected members. Comparison ignores whitespace and non-JSDoc comments but requires every original JSDoc comment, including member documentation, so readers see the source contract beside the exact shape. The gate also enforces a 1:1 correspondence by document, symbol, and projection, so a block can't go silently unchecked and a stale entry can't linger. doc-typecheck skips both fence kinds (they aren't standalone-compilable) and excludes them from its opt-out ratio. When you change a documented declaration or its JSDoc, 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.