DeepSeek Harness ships two CI surfaces (GitHub Actions for the TypeScript monorepo and its Python SDK; GitLab CI for the native Python wheels) plus a rich set of local developer toold — lefthook git hooks, oxlint, jscpd, and the invariant/vendored-link hygiene gates. Releases follow family-based version bumps (release/dsh-* / release/vendor-*) and commit the version into the repository before a human tags — the upstream repo keeps no CHANGELOG, so every release is a mechanical version-bump commit. This page maps the whole engineering pipeline.
GitLab CI: Python release shape
.gitlab-ci.yml runs only for Python release tags (python-v<major>.<minor>.<patch>…). Two stages, build and publish:
sdk-wheelbuilds the keyless SDK wheel.runtime-linux-x64,runtime-linux-arm64,runtime-macos-arm64,runtime-windows-x64each build the bundled single-exe runtime for its target, then verify it: a Python smoke (python scripts/smoke-python-runtime.py), glibc <= 2.28 via readelf for Linux, manylinux smoke through a Docker image, and a macOS deployment-target check; the Windows lane runsscripts/build-exe-for-python-sdk.ts --targets=node24-win-x64and smokes the installed-wheel path under a wheel venv.publish-pythonuploads the five.whlfiles (SDK wheel + four runtime wheels) to the project's Package Registry withtwine, guarded by a tag-vs-package.jsonversion assertion and afind release -name '*.whl'count check. GitLab does not overwrite an existing version, so each release needs a freshpython-v<version>tag.
The tag-vs-version guard is central: test "$CI_COMMIT_TAG" = "python-v$DSH_VERSION" || exit 1.
GitHub Actions: the PR gate
.github/workflows/ci.yml is the required pull-request pipeline. It is sophisticated about runner pools and concurrency; the key lanes (all Node 24 unless noted):
| Job | Runs | What it does |
|---|---|---|
node-24 / static | Linux enterprise pool | pnpm run check:ci:static |
node-24-coverage | Linux | pnpm run check:ci:coverage (exhaustive coverage) |
node-24-consumers | Linux | pnpm run check:ci:consumers (snapshots, artifacts, Playwright-based web) |
node-compat (matrix) | ubuntu-latest | Node 22.19, 24.9, and 26: check:node-compat |
python-sdk | ubuntu-latest | Python 3.10 keyless suite via uv |
python-runtime | reusable build | release-shaped Linux x64 exe build |
windows | ubuntu-latest | Wine: runs scripts/wine-windows-gates.sh (Windows Node under Wine) |
windows-build / windows-coverage | real Windows | split halves of the former monolithic windows-native job (check:ci:windows-blocking; build first, then check:ci:coverage) — one slow job no longer blocks the rest |
windows-native-tests | real Windows | Windows-specific native test files (pwsh loader, workflow worker, subprocess exit, sqlite differential) |
windows-observational | real Windows | check:ci:windows-observational, continue-on-error (non-blocking) |
all-checks-passed | ubuntu-latest | if: always() aggregate verdict; fails on any non-success needed job |
The former push-only serial standby drills (serial-linux-selfhosted / serial-windows) moved out of the PR verdict into the new ci-master.yml (master-push + manual workflow_dispatch for larger-runner/consolidated-runner benchmarks); ci-master.yml deliberately sits outside the PR verdict because needs cannot reach across workflow files.
The gate scripts come from scripts/run-gates.ts. check:ci, check:ci:linux-primary, check:ci:static, check:ci:coverage, check:ci:snapshot, check:ci:artifacts, check:ci:consumers, and check:ci:windows-* are all thin npm-script sugar over separate run-gates.ts orchestrations. Env knobs bound concurrency: DSH_GATE_CONCURRENCY, DSH_COVERAGE_MAX_WORKERS, DSH_SNAPSHOT_MAX_CONCURRENCY, DSH_E2E_MAX_WORKERS, DSH_OXLINT_THREADS, DSH_PUBLINT_CONCURRENCY.
Notable CI policies baked into the workflow env: DSH_TELEMETRY_DISABLED=1 (CI runs never report to the production telemetry endpoint), fetch-depth for the archive gate, Playwright Chromium for the web gates, prepare-ci-bubblewrap.sh to unrestrict the namespace for the sandbox suites, and a failover mechanism via repository variables DSH_CI_FAILOVER_LINUX / DSH_CI_FAILOVER_WINDOWS that retarget jobs onto in-house self-hosted pools.
The release process (visible from git)
The release flow is versioned and tag-only:
$ git log --oneline --grep=release -15
99f6f02fec Merge pull request #2620 from deepseek-harness/release/dsh-0.1.0-rc.7
bb4ca698d6 release(dsh): 0.1.0-rc.7
887c4977db Merge pull request #2546 from deepseek-harness/fix/publish-deporder
d5be1d62c9 feat(release): count publish progress against the whole release set
0e50fa290c fix(release): state what the echo helper does, and drop two dead claims
7b973e27c8 feat(release): reject a module-scope load of an optional dependency
9fa0575ccc fix(release): print the publish order and the peer edges it drops
70eb76eaec fix(release): keep npm's own output in the publish log
47399764c5 fix(release): order publication by every installed dependency section
fb82698709 Merge pull request #2531 from deepseek-harness/release/dsh-0.1.0-rc.6
15148dbd9a release(dsh): 0.1.0-rc.6
47f943859b Merge pull request #2519 from deepseek-harness/feat/npm-public
abe560f81e release(dsh): 0.1.0-rc.5
8c1e8d9890 build(release): publish the dsh family publicly
124aa5f01a Merge pull request #2521 from deepseek-harness/release/dsh-0.1.0-rc.3scripts/release/ implements it:
| Script | Role |
|---|---|
bump.ts | Bump one family's version and commit it (--family dsh shares one version across members + root; --family vendor has one line per package but ships the whole family) |
verify.ts | Verify release version against the workspace state |
pack.ts | Build and pack tarballs into a dist/npm dir |
verify-packed-install.ts | Install the packed tarballs (plus vendor and Landlock tarballs) and prove they resolve |
publish.ts | Upload the exact packed bytes to npm |
The .github/workflows/release.yml workflow (plus the vendored/native variants) runs release:verify → build → release:pack → release:verify-packed-install on every PR/push (a pack-only proof that the whole publish set still packs). Publication is a manual act: the dsh family publishes through release-publish.yml and the vendored framework through release-vendor-publish.yml — both are workflow_dispatch-only, repack the current tree at dispatch time, and are meant to be run explicitly from a dsh-v* (resp. vendor-*) tag. Neither workflow listens to pull_request/push, and both declare contents: read — CI never writes the repo. The Landlock native package releases through its own native/landlock-run workflows. THIRD_PARTY_NOTICES.md is regenerated by lefthook whenever a dependency edit changes the payload. There is no CHANGELOG: each release is a mechanical version-bump commit (release(dsh): … / release(vendor): …).
Developer tooling
lefthook git hooks (lefthook.yml)
postinstall runs node scripts/install-lefthook.mjs. Local hooks are deliberately fast checkpoints; CI owns the full matrix:
- pre-commit: staged-lint (
tsx scripts/run-oxlint.ts --config .oxlintrc.staged.json), translation pairing for*.i18n.yaml, archived-agent-notes check, whitespacegit diff --cached --check, vendor manifest guard, and third-party-notices regeneration (re-generates +git add THIRD_PARTY_NOTICES.md). - pre-merge-commit: translation pairing + archived notes.
- pre-push:
pnpm run typecheck.
oxlint (run-oxlint.ts, .oxlintrc.json)
npm run lint = build:lib:host then tsx scripts/run-oxlint.ts . (the contracts-ready variant runs after the client contract build). .oxlintrc.json turns correctness rules off at the top and re-enables strict, mostly type-aware rules scoped per override; it ignores vendor/**, native/**, and *.config.ts. There is a stricter --fix staged variant (.oxlintrc.staged.json) for the hook.
jscpd duplication (jscpd.json)
npm run duplication runs jscpd --config .jscpd.json packages scripts with minTokens: 60, minLines: 6, mode: "mild", and an ignorePattern for explicit /* jscpd:ignore-start */ … /* jscpd:ignore-end */ blocks (used heavily in invariant companions and exports).
hygiene bundle
pnpm run hygiene chains the deep gates: rescope-vendor:check, publint, constraints, verify-dsh-package-licenses, verify-package-invariants, verify-built-package-invariants, verify-cordis-config, verify-node-next-types, verify-runtime-closure, verify-vendored-links. (knip was removed repo-wide — no knip.json, no knip script, no CI lane; unused-code detection is no longer part of the gates.) These guarantee the workspace is internally consistent before anything ships.
Contribution process
CONTRIBUTING.md states the project is early-stage and does not accept external pull requests yet; contributions happen through GitHub Discussions, ecosystem plugins (via the dsh-plugin topic), and community content. It is a deliberate gate that keeps PR review effort focused on the internal team while growing the ecosystem around the harness.
Tooling
| Tool | Version (from root package.json devDependencies) |
|---|---|
| vitest | ^4.1.8 |
| oxlint | |
| oxlint-tsgolint | |
| jscpd | ^5.0.12 |
| lefthook | ^2.1.9 |
| tsx | ^4.22.4 |
| typescript | ^6.0.3 |
| publint | ^0.3.21 |
| tsdown | ^0.22.2 |
knip is intentionally absent: it was removed repo-wide.
Further reading
- Testing strategy — what each
check:ci:*gate actually runs. - Runtime invariants — the
verify-package-invariantsgate and companions. - Vendored libraries —
verify-vendored-links,rescope-vendor, and the family releases. .gitlab-ci.yml— the Python wheel pipeline and its tag guard.scripts/run-gates.tsandscripts/release/bump.ts— gate orchestration and family versioning.lefthook.yml— the exact pre-commit/pre-push jobs and their globs.