δΈζ
# dsh-zotero Development Guide
## Repository structure
```
src/
index.ts # Plugin entry (pure re-export)
service.ts # ZoteroService (Cordis service)
local/provider.ts # LocalApiProvider (Zotero Local API)
local/*-domain.ts # Domain pipelines (search/detail/retrieve/attachment/export/changes/browse) + scope-directory/pagination/limits
http-client.ts # HTTP transport (loopback fetch)
config.ts # Config schema and validation
types.ts # Domain types (DTOs)
contract.ts # Remote wire contract (descriptors + strict codecs)
errors.ts # Error class and error codes
json.ts # Lossless JSON read helper
concurrency.ts # Bounded concurrency
evidence.ts # BM25 ranking
attachments.ts # Attachment selection
local/children-wire.ts # Local API child-object contracts: bare /children (notes/attachments) and ?itemType=annotation (annotations)
normalize.ts # Zotero item β domain DTO normalization
presentation-meta.ts # Display projection of tool results
refs.ts # Zotero object reference syntax
ref-grammar.ts # Reference text patterns
export-items.ts # Per-document export parsing
export-mapping.ts # Ref β batch item mapping
ask.ts # User-question fallback when the connection fails (one card per failure kind, shared by parallel calls)
prompt.ts # Model-facing policy section
command.ts # /zotero status command
remote.ts # Remote service for web tab
typert.ts # Typert manifest
settings-namespace.ts # Settings namespace constants
tools/ # 8 model tools (search/get/children/attachment/retrieve/export/browse/changes) + present/validate shared pieces
client/ # Browser side (settings page, Sources tab, sources reducers, workspace views)
tests/ # Unit tests (mock Zotero server + browser page tests)
```
## Install and build
```sh
npm install # sibling of deepseek-harness; add --no-workspaces only for a nested copy
npm test # unit tests (mock Zotero server + browser card tests)
npm run typecheck # upstream dependency state check + tsc --noEmit (node/test/client projects)
npm run build # tsc + esbuild (node lib/ + browser lib/client.js)
npm run build:client # rebuild browser side only
npm run test:coverage # coverage gate (97 statements / 95 branches / 98 functions / 97 lines)
npm run harness:check # upstream pin and declaration freshness (typecheck already runs it)
npm run harness:pin -- # move the whole pin to (devDeps/overrides/peers/engines/README/AGENTS)
npm run verify:pack # assert the packed tarball carries the declared entries
npm run format # prettier --write
npm run format:check # format check
```
> This repo sits beside deepseek-harness as a sibling (see AGENTS.md): plain `npm install`. Add `--no-workspaces` only when nested inside the harness workspace.
>
> Upstream **types** come from the built `lib/types` artifacts of the sibling checkout that `node_modules/@deepseek-ai/*` symlinks at (the same read a published consumer makes). A sibling `git pull` does not regenerate them, so `npm run typecheck` first runs `node scripts/harness-state.mjs`: when an imported package's `src` is newer than its declaration file, it prints the build command to run. `npm run harness:check -- --strict` (used by the release check) turns that report into a failure.
## Integration tests
```sh
npm run test:integration
# or: ZOTERO_INTEGRATION=1 npx vitest run tests/integration/zotero.integration.spec.ts
```
Requires a local Zotero running on `127.0.0.1:23119`.
## Two-part build
- **Node side** (`lib/`): tsc generates from TypeScript, contains service, tools, provider, transport.
- **Browser side** (`lib/client.js`): esbuild generates, contains settings page and Sources tab views.
## Local development
### From dsh source
```sh
pnpm install && pnpm run build # build dsh first
pnpm dsh web --patch ./dsh-zotero/dev.cordis.yml
```
### With npm-installed dsh
Three approaches:
1. **Tarball install verification**:
```sh
npm pack
dsh plugin --profile add ./dsh-zotero-*.tgz
cd ~/.dsh/profiles/
node --input-type=module < /path/to/dsh-zotero/scripts/smoke.mjs
```
2. **Node-side hot reload**:
```sh
npm run dev & # tsc --watch
dsh web --patch ./dev-lib.cordis.yml --port 3307
```
3. **Browser-side development**:
```sh
npm run dev:client # esbuild watch
# the checkout must be installed into the profile for the browser side to load
```
## Testing
- Unit tests use MockZotero (mock HTTP server)
- Browser card tests use jsdom + @testing-library/react
- Coverage gate lives in `vitest.config.ts` (97 statements / 95 branches / 98 functions / 97 lines; `src/index.ts`, `src/types.ts`, `css-modules.d.ts`, `sources/model.ts` are types-only/re-export exclusions)
- Integration tests run against real Zotero, skipped by default
## Release checklist
- `npm run harness:check -- --strict` passes (pin consistent, upstream declarations not behind the sibling source)
- `npm run verify:pack` passes (tarball carries `lib/index.js`, `lib/index.d.ts`, `lib/client.js`, `cordis.patch.yml`)
- `npm test` passes
- `npm run typecheck` passes
- `npm run test:coverage` passes (gate above)
- `npm run format:check` passes
- `npm run build` succeeds
- smoke.mjs passes after tarball install
- Integration tests pass when Zotero is available