Contributing to Tacit
September 1, 2026 · View on GitHub
Thanks for looking. Tacit is a small, zero-build plugin for
DeepSeek Harness (dsh):
plain ES modules, no bundler, no TypeScript step. You do not need push access,
a signing key or an API key to contribute — the whole test suite runs against
a stubbed harness.
Prerequisites
- Node ≥ 22 and pnpm 11.23.0 (
corepack enablewill honor the pinnedpackageManagerversion) - a GitHub account (to fork and open a pull request)
- a working
dshinstall (npx @deepseek-ai/dsh web) with a DeepSeek API key configured in Settings → Models — only needed to try your change live or run the smoke test, not for the unit tests
Find something to work on
good first issue— small, bounded, with acceptance criteria and a pointer to the code.help wanted— bigger pieces where the design is open; comment on the issue first and the maintainer will pair on the approach.- Anything else you noticed: open an issue or ask in Discussions.
If a task will take you more than an hour, leave a comment on the issue before you start so nobody duplicates the work. Where the code lives is mapped in docs/architecture.md.
Fork, clone, link
gh repo fork hackernotfound/dsh-tacit --clone # or fork on github.com and git clone your fork
cd dsh-tacit
git remote add upstream https://github.com/hackernotfound/dsh-tacit # skip if gh added it already
pnpm install
npx @deepseek-ai/dsh plugin --profile web add "$PWD" # links this folder into ~/.dsh/profiles/web
Then start (or restart) npx @deepseek-ai/dsh web and refresh the harness page.
Restart vs refresh: anything under lib/ runs in the host process — restart
npx @deepseek-ai/dsh web after changing it. The browser side is edited under
client/src/ (one file per section, no imports — the harness loads a single
classic script per plugin) and rebuilt into client/client.js with
pnpm build:client; then a page refresh is enough. Never edit
client/client.js by hand — pnpm check:client (also run in CI) rejects a
bundle that differs from its sources. Reinstall only when package.json
dependencies change.
Tests
pnpm test # node --test: fold, calls, analysis, trust, schema, store, host integration, client SSR
pnpm build:client # regenerate client/client.js from client/src/
pnpm check:client # the committed client/client.js matches client/src/
pnpm check:docs # local Markdown links and anchors
pnpm check:package # npm tarball contents and English root README
pnpm check # all four checks above
pnpm smoke # HTTP end-to-end against a running dsh web (no model calls, free)
TACIT_SMOKE_SESSION=<id> pnpm smoke # also exercises ✨ Improve: one real model call, ≈ \$0.001
TACIT_BASE=http://127.0.0.1:4000 pnpm smoke # if your dsh web is not on :3080
pnpm rehearse # real headless turns in a throwaway DSH_HOME: proves the zero-click loop end to end
# needs a DeepSeek API key configured in the harness; ≈ \$0.001 in Tacit calls per run, plus the agent's own turn
pnpm check:ci-logs # scans this repo's own recent GitHub Actions logs for credentials, personal paths and addresses; needs gh logged in
CI runs pnpm test on Node 22 and 24 plus the docs and package checks for every
push and PR. Please keep CI green.
Open a pull request
git fetch upstream
git switch -c fix/short-description upstream/main # feat/, fix/, docs/, chore/, refactor/
# … make your change …
pnpm check
git commit -am "fix: short description"
git push -u origin fix/short-description
gh pr create # or open the PR from your fork on github.com
- One concern per PR. A fix and an unrelated refactor are two PRs.
- The PR template asks for the essentials: what and why,
pnpm checkgreen, tests for behaviour changes,zh/enparity, and a line under## UnreleasedinCHANGELOG.mdwhen the change is release-worthy. - You do not need to sign commits or keep history tidy; the maintainer squashes or rebases on merge.
- Reviews are usually within a few days. A request for changes is about the diff, never about you.
Ground rules
- Tests for behavior. Anything that changes what Tacit learns, injects, or
spends money on needs a test in
test/. The integration tests stub the whole harness (test/integration.test.mjs) so no key is needed. zhandendictionaries stay in sync. Both live inclient/client.jsand a test fails if their key sets diverge. If you only speak one of the two, add the other with a best-effort translation and say so in the PR — a native review is welcome.- Never add a way for Tacit to read an API key or call a custom endpoint.
All model calls go through
ctx.llm.stream; the model is allowlisted inlib/schema.js. - Cost is a feature. Every call must use
reasoningEffort: 'low'with a tool schema and pass thesessionIdso cost meters can attribute it, and wrap the call withmetered()so it lands in the usage ledger; new automatic calls should be capped (seeautoDailyBudget). Add any new call to the cost table in docs/privacy-and-cost.md. - Don't delete user data. There are exactly two deletion paths, both
restricted to Tacit's own files: analysis reports
(
reports/<session>/<turn>.json), via "Clear all analysis reports" in Settings; and usage day files (usage/<YYYY-MM-DD>.json) older thancostHistoryDays, or all of them viaclearUsage(). Nothing else on disk is ever touched, and neither directory is ever removed.
Where things live
Behaviour: docs/how-it-works.md. Code map, hooks,
routes and storage: docs/architecture.md. Short version:
lib/fold.js turns session events into turn digests, lib/analyze.js holds
the prompts, heuristics and model calls, lib/service.js is the host service
(auto triggers, trials, bootstrap, steering), lib/routes.js the JSON API,
client/src/*.js the whole UI (i18n, API client, stores, components, panel,
CSS, plugin body — concatenated into the shipped client/client.js).
Reporting bugs
Use the bug report form.
It asks for the dsh version (npx @deepseek-ai/dsh --version), what you typed,
what the Tacit tab / console showed, and — if it is about a directive — the
directive text from Settings → Tacit. Please strip anything private from
prompts before pasting them. Suspected vulnerabilities go through
SECURITY.md, not a public issue.
Release policy (maintainer only)
Contributors never need to run any of this; it is here so the versioning is predictable. Not every merged change needs an npm release:
- Use a patch release for fixes, runtime dependency or configuration
changes, packaged host/client changes, and root
README.mdupdates that must appear on npmjs.com. - While Tacit is pre-1.0, use a minor release for new features and breaking changes.
- Do not release for GitHub-only
docs/changes, tests, CI, contributor files, security files or repository settings.
Release-worthy notes go under ## [Unreleased] in CHANGELOG.md, grouped
under ### Added, ### Changed, ### Fixed, ### Security or ### Removed
(Keep a Changelog); each bullet is one
line (two wrapped at most) saying what changes for the person using Tacit,
and ends with its PR number; a test enforces the length. Anything a
user must do or will notice on update (a restart, a recomputed store, a migrated
file, a new setting) goes first, under ### Upgrading. To release, rename that
section to ## [x.y.z] - YYYY-MM-DD, add its compare link at the foot of the
file, leave a fresh ## [Unreleased] above it, bump package.json, and open
the release PR (main is protected, so the bump lands by PR). Once it is
merged, push an annotated tag: git tag -a vx.y.z -m vx.y.z && git push origin vx.y.z. The tag publishes to npm with provenance through OIDC trusted
publishing (no npm token) and creates the GitHub release with that changelog
section as its body (node scripts/release-notes.mjs x.y.z prints it; a test
fails when the packaged version has no section). Never move an existing
release tag; corrections after publication get a new patch version.