Files
apes-sidebar/AGENTS.md
T
figmar 0e91e99f49 feat(apes-sidebar): Phase 0 工程骨架 — host/client 分裂 + i18n + 不变量面
从 0 重写的 v2.0.0 DSH 侧边栏插件第一阶段:
- 工程骨架:tsdown 双构建(host ESM + client CJS closure)、vitest、strict TS
- host 半:namespace 插件形状(name/inject/apply,无 default),minimal apply
- client 半:closure factory 注册 5 个 apes:* tab 占位 + zh/en i18n
- 不变量面:assert/semver/peer guard(dsh-better-sidebar >= 0.18.0)
- 依赖策略:dsh-better-sidebar=peerDependency(不打包),cordis=devDependency(仅类型)
- 门禁:pnpm typecheck + build + test 全绿(14 单测通过)
- AGENTS.md 工程约定 + .gitignore(排除 lib/node_modules/maxkb-integration)

模板:dsh-better-sidebar。作用域锁定信息层(tasks/agents/apps/messages/profile),
不含 file/terminal/browser(归 better-sidebar)、不含 team/room(归 background-agents)。
2026-09-04 17:14:22 +08:00

6.3 KiB

apes-sidebar — engineering conventions

A DSH (DeepSeek Harness) sidebar plugin that registers five information-layer tabs into the dsh-better-sidebar host: apes:tasks, apes:agents, apes:apps, apes:messages, apes:profile.

This repo is a from-scratch v2.0.0 rewrite of the legacy JS-bundle sidebar. The engineering template is dsh-better-sidebar (TS + tsdown + host/client split). If a convention here conflicts with dsh-better-sidebar, follow better-sidebar and file a note in docs/.

Stack

  • Language: TypeScript (strict, noUncheckedIndexedAccess, ESM, .ts extension imports)
  • Build: tsdown (rolldown). Dual output — lib/index.js + lib/invariant.js (host, ESM) and lib/client.js (client, CJS closure factory)
  • Type check: tsc --noEmit -p tsconfig.json
  • Unit tests: vitest (node environment, no DOM)
  • Package manager: pnpm (Node ≥ 22.13 required — node:sqlite + pnpm 11)
  • Peer: dsh-better-sidebar ≥ 0.18.0 (host-provided, never bundled)
  • Dev-only types: @deepseek-ai/cordis (types; host provides at runtime)

Commands

pnpm install          # install deps (preinstall re-checks on build)
pnpm typecheck        # tsc --noEmit (strict)
pnpm build            # rm -rf lib && tsc (d.ts) && tsdown (bundles)
pnpm test             # vitest run (unit only; no e2e in Phase 0)
pnpm build && pnpm test && pnpm typecheck   # the full gate

Run the full gate before pushing. All three must be green.

Layout

src/
  index.ts            # HOST entry: export name/inject/apply (namespace plugin shape)
  invariant.ts        # invariant surface (assert, semver, peer guard) — dep-free
  context-types.ts    # service face contracts (ApesContext = CordisContext & {...})
  client/
    index.tsx         # CLIENT entry: closure factory registering apes:* tabs
    locales.ts        # i18n (zh default, en fallback) + useApesLocale hook
tests/
  smoke.spec.ts       # host plugin shape + apply + invariant surface
  locales.spec.ts     # i18n contract against a mock ApesLocaleService
tsdown.config.ts      # dual build config (host ESM + client CJS)
vitest.config.ts
cordis.patch.yml      # plugin registration into the DSH profile

Hard rules

  1. Namespace plugin export shape. src/index.ts MUST export { name, inject, apply } as named exports — never a default export. A stray default makes the cordis Loader.unwrapExports discard name/inject/apply and the plugin silently fails to mount. tests/smoke.spec.ts pins this.

  2. Host / client split is load-bearing.

    • The host half (lib/index.js) runs in Node. It may use Node builtins.
    • The client half (lib/client.js) runs in the DSH browser shell. It is a CJS closure factory — module.exports = (hostApi) => {...}. React is external (host-provided). Never import a Node builtin into src/client/**.
    • src/invariant.ts is reachable from BOTH halves — it must stay dependency-free (no Node builtins, no @deepseek-ai imports).
  3. dsh-better-sidebar is a peerDependency, not a bundled dep. The tsdown config lists it in deps.neverBundle. If a build inlines it, the client bundle ships a second copy of the sidebar registry — tabs register into a dead instance and never appear.

  4. @deepseek-ai/cordis is devDependency (types only). The host provides the real cordis context at runtime. Do NOT add cordis to dependencies or to deps.neverBundle — that would force the client to resolve it.

  5. Tab IDs are apes:*. Never wb:* (legacy prefix) or any better-sidebar internal id. The five Phase 1 tabs: apes:tasks, apes:agents, apes:apps, apes:messages, apes:profile.

  6. Scope lock. This plugin is the information layer only: tasks / agents / apps / messages / profile. Do NOT add file/terminal/browser panels — those belong to dsh-better-sidebar itself. Do NOT add team/room features — those belong to dsh-background-agents.

  7. Bilingual i18n. Every user-facing string goes through t(key) in src/client/locales.ts with BOTH a zh and an en entry. No raw strings in JSX.

  8. pnpm build-script approval. node-pty (a transitive runtime dep of better-sidebar's terminal panel) needs its native build approved once: pnpm approve-builds (pick node-pty, y). Without this, pnpm build's preinstall dep-check hard-fails. It is approved in the committed pnpm-lock.yaml state — don't reset it.

Testing conventions

  • Unit tests run in a node vitest environment. No DOM, no jsdom (the client React components are not rendered in Phase 0 — a full cordis scratch mount smoke lands in Phase 1).
  • The host apply() is tested against a minimal fake context (see tests/smoke.spec.ts) — never against a live DSH shell in CI.
  • The locale contract is tested against a mock ApesLocaleService (see tests/locales.spec.ts).
  • A test that needs the real DSH host is a Phase 1+ concern (cordis scratch profile); do not add such tests to Phase 0.

Gotchas (learned, don't rediscover)

  • tsdown 0.22 deprecated external/noExternal in favour of deps.neverBundle/deps.alwaysBundle. external and deps.neverBundle cannot coexist — using both throws at build. Use only the deps API.
  • allowImportingTsExtensions (for .ts-extension imports) is set in tsconfig.json. tsconfig.build.json inherits it but emits d.ts, which is compatible (tsdown handles the strip). If tsc build complains, the declaration emit needs emitDeclarationOnly + noEmit: false — that is the current tsconfig.build.json setup.
  • node-pty build approval is per-machine state in pnpm-lock.yaml + the pnpm store. A fresh pnpm install on a clean checkout will re-prompt.
  • The workspace path contains spaces and Chinese characters (文稿 - figmarLite的MacBook Pro/...) — always quote it in shell commands.

Phase status

  • Phase 0 (done): engineering skeleton, host/client split, i18n, invariant surface, build gate green, unit smoke green.
  • Phase 1 (next): real tab registration for the five apes:* panels against a mock host registry, plus a cordis scratch mount smoke.
  • Phase 2 (later): badge/reminder counts, auto-collapse, per-tab state.