# Contributing [Documentation index](README.md) · [简体中文](contributing.md) Thanks for considering contributing to dsh-TUI! This guide is the shared development contract for humans and coding agents working on `@deepseek-harness-tui/dsh-tui`. ## How To Contribute - **Report bugs** through the bug issue form: version, terminal environment, and a minimal reproduction. A report does not reserve the implementation or authorize a pull request. - **Request features** in [Discussions Ideas](https://github.com/ccch1mneyyy/dsh-TUI/discussions/new?category=ideas). - Issues do not accept feature requests. - Accepted proposals get a tracking issue, and its assignee owns the implementation. - **Do not start writing code before the proposal is accepted** — OAuth, `/cost`, notifications, a plugin API and a remote runtime were each written in full and then closed. - A discussion, issue, comment, or a claim that a maintainer agreed does not authorize a pull request. - **Open a pull request** only if you have write/admin/maintain on this repository, or your GitHub username is listed in [`.github/APPROVED_CONTRIBUTORS`](../.github/APPROVED_CONTRIBUTORS). - Unsolicited implementation pull requests from everyone else are closed by `pr-gate`, regardless of size, title, test results, or whether a human or an agent wrote the code. - Maintainers add names based on trusted prior work. It is not an application program — do not open an issue or discussion asking to be added. - Membership permits a pull request; it grants no write access and does not pre-approve feature scope. - A write collaborator may reopen a closed pull request as a one-off exception. Reopening by anyone else is closed again. - Open the pull request against `main`. Keep changes focused: one logical change per PR, with a Chinese or bilingual title and a description that follows the [PR template](../.github/PULL_REQUEST_TEMPLATE.md): motivation, the shape of the change, and how it was verified. Agents open PRs with `.agents/skills/pr`. - **A pull request that changes code must link an issue**: add a `Closes #` line to the description, or link it through the Development sidebar. The `issue-link` CI group checks this and fails without a link. - Changes classified as docs-only by CI are exempt (see path routing under Verification). For a maintainer release, revert, or CI hotfix that genuinely has no issue to link, apply the `no-issue-needed` label. - **Run the verification matrix** below before requesting a review; CI runs the same commands. - New features should include or extend a focused regression script. Before opening an implementation pull request, confirm the authenticated GitHub account has write access or appears in `.github/APPROVED_CONTRIBUTORS`. - If neither is true, refuse to open the pull request and point at the bug form or Discussions. - A human cannot bypass this with a private approval, an issue link, or a pasted maintainer comment. ### When the gates take effect The feature proposal flow applies only to pull requests opened on or after 2026-08-24. The pull-request allowlist applies only to pull requests opened (or reopened) after the gate lands. - Pull requests already open before that follow the previous rules. - They are not closed retroactively and need no Discussion or tracking issue. ### Merge queue Merges into `main` go through [Mergify](https://mergify.com)'s merge queue, configured in [`.mergify.yml`](../.mergify.yml): a pull request enters the queue once it has one approving review, and the queue updates it onto the latest `main`, re-runs CI on a temporary pull request, and merges it when green. The merge conditions are injected from the ruleset on the base branch (approval, `ci-gate`, resolved threads, approval of the last push), the same bar as a manual merge. The queue gives nothing away and offers no path around the approval requirement; an urgent merge is still `gh pr merge --admin`, which only an admin can run. The queue only takes pull requests whose base is `main`: a stacked pull request is not queued until it is retargeted. An approved pull request you want to hold back takes the `on hold` label. The temporary pull requests the queue creates (`mergify/merge-queue/*`) are drafts that run CI once and close; `pr-gate` and `issue-link` both let them through as bots. The matching ruleset decision: *Require branches to be up to date before merging* is off on `main`. It conflicts with the queue — the queue tests a temporary pull request, so GitHub sees the original one as out of date and refuses the merge — while testing the merged state on the latest `main` is exactly what the queue does for you. Approval, `ci-gate` and resolved threads are still enforced by GitHub, and Mergify is **not** on any bypass list. ## Scope This file applies to the entire repository. It is the shared development contract for humans and coding agents working on `@deepseek-harness-tui/dsh-tui`. `@deepseek-harness-tui/dsh-tui` is a single-package, ESM-only TypeScript project. It provides a React terminal UI front door for DeepSeek Harness through Cordis. - The package owns the TUI, its local command surface, and an Ink/Yoga renderer. - DeepSeek Harness owns the agent, session, model, tool, skill, persistence, and policy domains that the TUI consumes. Before making a broad change, read `package.json`, the relevant README section, and every source file being edited. Prefer the repository's existing service boundaries and helpers over introducing parallel abstractions. ## Repository Map - `src/index.ts`: public Cordis plugin entry point, configuration schema, and lazy handoff to the runtime plugin. - `src/dsh-adapter/plugin.ts`: TTY validation, service registration, agent creation/resume, React tree mounting, and terminal/process teardown. - `src/dsh-adapter/oauth/`: pi-ai subscription OAuth provider routes, the `/auth` command, credential store, and user-questions bridge; DeepSeek account authorization delegates to the Host service. Mounted through the `src/oauth.ts` subpath entry. - `src/dsh-adapter/questions-answerer.ts` and `preset-resolution.ts`: isolate upstream prerelease dispatch for user questions and agent presets so version branches do not spread into bootstrap or channel actions. - The questionnaire "provider seat" guard (DUPLICATE_PROVIDER probe + private symbol check, #586) only applies to the legacy rc `registerProvider` path. - On the 0.1.2 line's `user-questions/request` waterfall, Cordis first scope-filters requests carrying an agent; agentless `/auth` requests are dispatched without a scope carrier. - Under the answerer convention, the first eligible listener that returns instead of delegating with `next()` claims the request. - Cordis waterfall is around middleware, however: an outer listener can call `next()` and then observe, replace, or reject the downstream result, while `{ prepend: true }` inserts a listener at the front. - Upstream offers no supported way to discover or reserve a verifiably exclusive claimant, so the legacy seat guard and its warning cannot be reproduced locally. - `src/dsh-adapter/channel.ts`: event-to-view projection and the non-React action surface. - It translates DSH session events into transcript rows. - It implements submit, steering, rewind, resume, model/preset switching, local reports, and related state transitions. - `src/screens/Chat.tsx`: top-level interaction coordinator. It owns modal precedence, global keyboard handling, scroll/search/selection state, slash command dispatch, and composition of the chat screen. - `src/screens/StatusLine.tsx` and `src/screens/StatusMetrics.ts`: terminal status presentation and metric derivation. - `src/components/`: feature components. `components/design-system/` contains theme-aware primitives; `components/messages/` contains transcript rows; `components/questions/` contains the `ask_user_question` UI. - `src/ui.ts`: preferred facade for the local renderer, themed `Box`/`Text`, hooks, and public TUI primitives. - `src/ink/`: low-level Ink-based renderer and terminal implementation. Treat it as sensitive infrastructure: keep changes focused and accompany them with renderer-specific regression coverage. - `src/native-ts/yoga-layout/`: ported layout engine used by the renderer. - `src/terminal-utils/`: terminal formatting and presentation helpers. - `src/*Prefs.ts`, `src/customTheme.ts`, and `src/sessionHistory.ts`: persisted user preferences and local session metadata under `~/.dsh-tui`. - `.agents/skills/*/SKILL.md`: project skills for repository maintainers, discovered by the DSH filesystem provider and excluded from the npm package. - `cordis.patch.yml`: package bundle overlay used by profile installation. Ordering, row IDs, disabled host rows, and insert/override semantics matter. - `cordis.yml`: full bare-composition example for direct Cordis/DSH startup. - `scripts/`: headless regressions, reproduction harnesses, probes, and diagnostics. Read each script's header before running it. - `.github/scripts/pr-intake/`: PR intake gate (locale, close copy, allowlist, issue-link). Workflows only orchestrate; `pr-gate.yml` must check out the default branch and must not run the PR head. - `lib/`: ignored JavaScript, declarations, and declaration maps generated from `src/` and shipped to npm. `./invariant` uses the compiled `lib/types/dsh-adapter/invariant.js` entry as well. - `README.md` (English, the default front page) and `README_ZH.md` (Chinese): the bilingual user documentation. Keep behavior, configuration, shortcuts, and limitations synchronized between them. ## Runtime Shape The central runtime path is: ```text Cordis config -> src/index.ts -> src/dsh-adapter/plugin.ts -> DSH agent/session services -> src/dsh-adapter/channel.ts (session events -> Channel snapshot) -> src/screens/Chat.tsx -> src/components/* -> src/ui.ts -> src/ink/* + Yoga layout -> terminal ANSI output ``` Keep ownership in the layer where it belongs: - Agent/session/tool facts come from DSH services and durable session events. - Projection and TUI actions belong in `dsh-adapter/channel.ts`, not in presentation components. - Interaction modes and key precedence belong in `Chat.tsx` or the focused modal/input component. - Reusable visual behavior belongs in `components/` and theme-aware primitives. - Terminal protocol, layout, hit-testing, selection, and frame-diff behavior belong in `ink/`. Do not reimplement a DSH domain service in the TUI merely to make a screen easier to build. Adapt the service through the channel or an existing registry seam. ## Toolchain - Supported Node versions are `^22.19 || >=24`; CI uses Node 24. - CI and publishing use pnpm 11. Use pnpm as the development package manager. The `packageManager` field in the root `package.json` is the single source of truth for the pnpm version; both CI and corepack read it from there. - Install a clean checkout with: ```sh git clone --recurse-submodules https://github.com/ccch1mneyyy/dsh-TUI.git cd dsh-TUI pnpm install --frozen-lockfile ``` In an existing checkout, run `git submodule update --init --recursive` first. `vendor/dsh-std` is a workspace dependency, so installation fails while that submodule is empty. - `pnpm-lock.yaml` is the single lockfile. npm consumers do not read a dependency's lockfile, so `package-lock.json` has been removed (follow-up of #173). - When intentionally changing dependencies, update `pnpm-lock.yaml` with `pnpm add`, inspect the full lockfile diff, and avoid unrelated upgrades. - Every `@deepseek-ai/*` framework package this package references at runtime or from its published types (following `UPSTREAM_BLESSED_PACKAGES`, including `@deepseek-ai/schemastery`) is both a peer and a dev dependency. - Framework packages are host-provided and resolve at runtime to the host's own instance through the `$DSH_HOME/profiles/node_modules` fallback tree (see #198 — declaring them as runtime dependencies lands real copies inside the profile and splits module identity from the host). - The dev declarations exist only so the package can type-check locally. Add new references of this kind to both sections at matching ranges (the verify:manifest-deps gate enforces it). - Framework packages used only by tests/scripts (e.g. dsh-settings, dsh-tools, dsh-session-persistence-*) stay dev-only — do NOT declare peers for them. - Non-host packages such as `dsh-working-activity` stay runtime dependencies. - Historical exception, now resolved: `dsh-working-activity@0.2.4` and earlier pulled a real copy of `@deepseek-ai/schemastery` (plus cosmokit) into the profile via its runtime dependency, shadowing the fallback tree. - 0.2.5 peer-ified it (working-activity#2), so profiles no longer carry any framework copies. Keep the dependency range at `^0.2.6` or above (0.2.6 also fixes the web-side WorkingLine absent-field guard on unpatched hosts, working-activity#5). - Do not expose, persist, or print credentials. Interactive startup reads `DEEPSEEK_API_KEY`; diagnostics may report whether it is set but must not reveal the complete value. ## Build And Generated Files The normal build and type-check gate is: ```sh pnpm build ``` - This removes the complete `lib/` directory, runs `tsc -p tsconfig.json` to emit `src/` into `lib/types/`, and then checks the adapter boundary, upstream contract, and patch surface. - The `prepare` lifecycle serves **source-checkout bootstrapping only** (it fails fast when the vendored submodules are absent — see scripts/prepare-guard.mjs). - Git URL dependency installs have been triply blocked since vendoring (#308: workspace deps / submodules / pnpm ≥11's prepare allowlist) and are unsupported — install the registry package. - Local and CI workflows use explicit commands instead of depending on whether pnpm implicitly runs the root lifecycle. Rules for generated output: - Edit `src/`, never `lib/`, to implement behavior. - After any source change, run `pnpm build`, but do not commit generated files from `lib/`. - Clean compilation removes the complete `lib/` first, so renamed or deleted source modules cannot leave stale output behind. - Run `pnpm verify:package` to ensure every `main`, `types`, `bin`, and `exports` target is present in the npm tarball and to smoke-import the main and invariant entries. - Documentation-only, workflow-only, and YAML-only changes do not require a rebuild unless they also alter TypeScript inputs. - Changes limited to ordinary comments and blank lines may skip the local rebuild; behavior, type, configuration, or build-input changes are not exempt. This does not waive the regressions required below for the changed area; see Verification for the applicable checks. - Git URL installation with `--ignore-scripts` skips `prepare` and is therefore unsupported. Registry packages already contain compiled output and do not depend on lifecycle scripts running on the consumer's machine. `scripts/build.sh` is an alternate builder for a local DeepSeek Harness source checkout. It locates a DSH checkout and rewires dependencies to that checkout. It is not the default build command for this standalone repository. ## Verification There is no root `test` or `lint` script. Do not claim that either ran. The TypeScript build is the universal static gate, followed by focused executable regressions. Select local verification by actual impact. - For documentation and skills, check facts, links, triggers, and conflicting instructions. - For ordinary comments, check the explanation against the implementation and confirm that code and types are unchanged, for example with an AST comparison that ignores comments. Compiler directives, JSDoc type annotations, and build-tool annotations are not ordinary comments. - For workflow and YAML changes, check syntax and affected configuration contracts. - Do not add behavior tests for prose edits. Once required checks pass, broaden or repeat them only for new changes, failures, or unresolved risks. CI separately routes changes using the path allowlist in `.github/workflows/ci.yml`. - `AGENTS.md`, `.agents/skills/`, and comments in source files are outside the docs-only exemption and still trigger code gates. - A local rebuild exemption does not skip CI; preserve required gates and report the actual local verification scope. `verify:build` also checks source hygiene, renderer primitives, theme and activity preference migrations, status animations, table layout, mermaid diagrams, and side-question behavior. - Source hygiene rejects the listed naming and compiled-input regressions. - It is not a source-provenance or license audit. CI runs these commands after installation: ```sh pnpm compile # generate a clean runtime test -f lib/types/index.js pnpm verify:build # build gates without recompiling pnpm verify:package # npm tarball and entry smoke test node --import tsx/esm scripts/repro-askpanel.tsx node --import tsx/esm scripts/verify-askpanel-layout.tsx node --import tsx/esm scripts/repro-toolcards.tsx ``` CI test jobs set `DSH_TUI_LANG=zh` as a fallback, but standalone regressions must not depend on it. When running a script that does not pin yet, prefix `DSH_TUI_LANG=zh` locally; otherwise a machine with an `en` lang.json or an `en_US` locale reports false failures. At import time, UI language resolves from `DSH_TUI_LANG` → `~/.dsh-tui/lang.json` → the OS locale. Scripts asserting or locating UI copy (including assertions that text is absent) must pin the matching language: set `process.env.DSH_TUI_LANG = 'zh'` / `'en'` before dynamic imports; Chinese-copy scripts with static imports should put `import './lib/default-lang-zh.mjs'` before all other imports. Do not use `??=` to preserve the host value or choose assertions based on the host language. Bilingual regressions already calling `setLang` per scenario and tests using Chinese only as input data (width, clipboard, etc.) need no redundant pin. `node scripts/verify-regression-language.mjs` (build first; included in the `channel-ui` CI group) tests locale, saved preference, and environment overrides opposing each script's expected language, each with a temporary HOME. This protects both Chinese positive assertions and English negative assertions. The language fixes in `verify-ime-cursor`, `repro-suggestion-click`, and `verify-queue` remain, but those scripts run only standalone until their legacy fixed waits are migrated; they are not included in the CI matrix. Diagnostic probes are not run wholesale by this regression group; pass `DSH_TUI_LANG=zh` explicitly when their output needs to be Chinese. Run all three CI regressions for changes to shared rendering, `Chat`, prompt or question layout, tool cards, theme primitives, or the Ink core. For a narrow change, also run the closest focused script: | Change area | Focused verification | | --- | --- | | General headless screen composition | `pnpm smoke` | | Channel submit/steer/pending behavior | `node scripts/verify-submit.mjs` | | Rewind/edit/resend and historical inbox cancellation | `pnpm verify:rewind-edit` | | Prompt queue behavior | `node scripts/verify-queue.mjs` | | Goal/todo projection and rendering | `node scripts/verify-channel-goal-todo.mjs` and `node scripts/verify-goal-todo.mjs` | | Compaction and folded transcript rows | `node scripts/verify-compact.mjs` | | Compaction × session-switch lifecycle (cancel before the fork snapshot, persistence-classified toast) | `node --import tsx/esm scripts/verify-compact-switch.tsx` | | Theme loading, persistence, and runtime plugin seam | `node --import tsx/esm scripts/verify-themes.mjs`, `node --import tsx/esm scripts/verify-runtime-themes.ts` | | Default-reasoning-effort and similar preference chains (effortPrefs / settings defaults) | `node --import tsx/esm scripts/verify-effort-default.ts` | | Scrolling/sticky-bottom behavior | `node scripts/verify-scroll.mjs`, `node scripts/verify-resticky.mjs`, and the matching `repro-*` harness | | Long plan-review body (`exit_plan_mode` windowing + wheel) | `node --import tsx/esm scripts/verify-plan-review-scroll.tsx` | | Fullscreen copy-on-select | `node scripts/verify-copy-on-select.mjs` | | Component-level mouse drag protocol (target capture, bubbling, click/selection compatibility, interrupted-session cleanup) | `node --import tsx/esm scripts/verify-drag-protocol.tsx` | | Mouse pointer event pipeline (wheel coords/modifier bits, click/hover dispatch, out-of-bounds clamping, pointer-state reset) | `node --import tsx/esm scripts/verify-pointer-events.ts` | | Hover event performance (complete interest boundaries, no-interest rect fast path, frame/multi-root invalidation) | `node --import tsx/esm scripts/verify-hover-coalesce.tsx` | | Prompt-input mouse selection editing (drag/Shift+click/double-click word select, delete/replace, layered Esc, Ctrl+C copy, CJK wide cells, fold-side clamping) | `node --import tsx/esm scripts/verify-input-selection.tsx` | | Sixel encoding, worker cache, thumbnail/preview lifecycle | `node --import tsx/esm scripts/verify-terminal-images-sixel.tsx`, `node --import tsx/esm scripts/verify-sixel-transcript.tsx`; timing comparison `node --import tsx/esm scripts/bench-sixel-encode.tsx` | | Standalone Markdown nodes (tables, mermaid diagrams) and streaming block spacing | `pnpm verify:table-layout`, `pnpm verify:mermaid-diagram`, `node --import tsx/esm scripts/verify-streaming-markdown-spacing.tsx` | | Cross-process session mount ledger (failure behavior, strict reads, lock recovery, reservations) | `pnpm verify:session-mounts` | | Unsent-draft handoff across screens (snapshot, cursor, image bindings, ownership) | `pnpm verify:composer-draft-handoff`; end-to-end screen switching also `node scripts/verify-session-browser.mjs` | Most focused scripts invoked with plain `node` import `lib/types/`; run `pnpm build` first. Scripts that import TypeScript sources declare the `node --import tsx/esm