Files
deepseek-harness/packages/AGENTS.md
T
Tianyi Cui 4e5c08ef82 docs: generated cordis events + services catalog
Add scripts/gen-cordis-catalog.ts: a fully-generated docs/cordis-catalog/
events-and-services.md cataloging every cordis event (exact signature + @mode)
and ctx.<key> service (exact interface), modeled on gen-module-graph's
--write/--check freshness gate. The harness tier renders in full from the
interface Events / interface Context declarations and their JSDoc; the inherited
cordis-core/loader/hmr/timer surface renders tersely from a curated table.

The generator hard-errors on a missing @mode tag and on a tag that contradicts
a conclusive signature shape (a trailing next param is structurally a
waterfall). Signature blocks use a ts cordis-catalog fence that doc-typecheck
skips. Type tokens cross-link to the core-data-structures catalog.

This supersedes the hand-maintained event-taxonomy table: verify-event-taxonomy
is deleted and verify-cordis-catalog joins doc-sync. architecture.md keeps the
Event taxonomy heading (TOC anchor) but points at the catalog; the Service-map
role table stays. RFC, AGENTS.md @mode authoring rule, and dependent doc/skill
references updated. Negative gate tests cover the missing-tag and
tag/shape-contradiction paths.
2026-06-20 19:47:09 +08:00

4.2 KiB

AGENTS.md — Harness Packages

This directory contains all @deepseek-ai/dsh-* harness packages. When editing code here, follow these conventions:

  • Effect-based registrations: every contribution (tool, section, adapter, agent, event listener) goes through ctx.effect() / ctx.on(), and register() methods return disposers. Never use bare arrays or manual cleanup.
  • Declaration merging: services declare their ctx key in declare module 'cordis' { interface Context { } } and their events in interface Events. Merge-extensible maps (ContentBlockMap, MessageSourceMap, FinishReasonMap, TurnTriggerMap, TurnEndReasonMap, SessionEventMap) are how plugins add new variants.
  • Waterfall semantics: ctx.waterfall listeners receive (...args, next); call next() to delegate, or return without it to short-circuit (veto). Never call next() after returning.
  • Plugin export shape — namespace OR default, never both. A service package exports the service class as export default (the Loader instantiates it). A function/namespace plugin exports name / inject / Config / apply as separate named exports and must NOT add export default — the cordis Loader's unwrapExports does exports.default ?? exports, so a stray default export collapses the module to the bare apply function and silently discards the inject/name/Config namespace, leaving the plugin with no injected services (it then throws cannot get property … without inject at load). See docs/postmortem/0001.
  • Read an optional (non-injected) service via ctx.get(name), not ctx.<name>. For a service a plugin reads opportunistically but deliberately leaves out of static inject (e.g. AgentLoop reading sessionPersistence), the ctx.<name> property proxy resolves by an ancestor-only fiber walk that throws when the call arrives through a foreign traceable shadow (the service lives on a sibling fiber). ctx.get(name) is the topology-independent global-store lookup, strict by default (an inactive/absent backend reads as undefined — prefer it over the ctx.get(name, false) overload, which also skips the active-state check). Services that ARE in static inject resolve fine via ctx.<name>. See docs/postmortem/0001.
  • Tests: vitest in packages/<name>/tests/*.spec.ts. Every registry needs an HMR-safety test (register a plugin, dispose its fiber, assert cleanup). Err on the side of more tests — edge cases, error paths, event ordering, races. A plugin shipped via cordis.yml also needs at least one test that drives it through the REAL Loader/export path (hand-built ctx.plugin({...}) mounts bypass unwrapExports and cannot catch a broken export shape) — see AGENTS.md § Defensive patterns "Line coverage is not behavior coverage". Real-API (with-key) e2e tests are cheap here (we are DeepSeek) and welcome — write many, especially smoke tests; see AGENTS.md § Secrets / .env.

Naming notes:

  • A service src/index.ts exports the service class as export default + all public types; a function/namespace plugin src/index.ts exports name/inject/Config/apply as named exports and NO default (see the plugin-export-shape rule above)
  • src/types.ts contain only types — no runtime code
  • Tests live at package level under tests/, not src/__tests__/
  • A package's README and module/JSDoc comments are part of the change: when you alter behavior (config keys, defaults, error codes, wire fields), update them in the same commit. CI runs pnpm run doc-sync, which typechecks fenced ts blocks in packages/*/*.md, regenerates the cordis events/services catalog from the interface Events / interface Context declarations (failing if the committed copy is stale), and checks markdown wrapping across this file too — but it does NOT catch prose drift (config keys, defaults, error codes), so those stay on the author. A new event needs an @mode tag on its JSDoc (the catalog generator hard-errors without it — see the root AGENTS.md).

Read the per-package README.md for package-specific details: service API, events, extension points, TODOs.