# Repository layout ## Decision The Awesome catalog, tracked installer, and online marketplace live in one repository, with `catalog/`, `packages/dsh1024/`, and `apps/web/` as explicit ownership boundaries. The production Cloudflare D1 database is the **single source of truth** for catalog data. Everything else is either an input form into D1 or a projection out of it; there is no second canonical copy. ```text plugin source entry (PR form) ─┐ ├─> POST /api/v1/catalog/sync ─> D1 (source of truth) ─> KV cache ─> /api/v1/* GitHub topic scan (Worker cron)┘ │ └─> bot-generated README projections ``` ## Single-source-of-truth responsibilities | Store | Role | Written by | | --- | --- | --- | | Cloudflare D1 (`CATALOG_DB`) | The only catalog database and the only source of truth | The Worker only: its cron topic scan (in process) and `POST /api/v1/catalog/sync` (GitHub CI, bearer token) | | `catalog/plugins/*.json` | The pull-request **submission form** and the curated bilingual metadata history | Contributors via reviewed PRs | | `README.md`, `catalog/README.md` | Full-catalog projections generated by the catalog-sync workflow bot | `scripts/build-readme.mjs`, committed by `github-actions[bot]` | | Workers KV (`CATALOG_CACHE`) | 15-minute snapshot cache; serving stale KV is the only degradation mode | The Worker | There is no bundled registry, no `catalog/generated/` artifact, and no disaster-recovery copy. If D1 is unavailable the API serves the last KV snapshot until it expires. ## Directory responsibilities | Path | Responsibility | Manually edited | | --- | --- | --- | | `catalog/plugins/*.json` | Submission form: plugin identity, category, bilingual descriptions, added date | Yes | | `catalog/categories.json` | Category IDs, order, and bilingual labels — the only category definition anywhere | Yes | | `catalog/schema/` | Contributor-facing JSON contract | Yes | | `skills/` | Installable Agent Skills for contributor workflows | Yes | | `README.md` | Primary Chinese plugin directory (bot projection) | No | | `catalog/README.md` | English plugin directory (bot projection) | No | | `packages/dsh1024/` | The publishable `dsh1024` package: wrapper CLI (official DSH plugin command delegation, local verification, install-event delivery) plus the in-DSH marketplace plugin | Yes | | `apps/web/src/` | React interface: the catalog, and `src/community/` for the community section | Yes | | `apps/web/worker/` | Hono API, the only process that reads or writes D1; `worker/community/` holds the community's routes | Yes | | `scripts/` | Trusted pull-request review, catalog sync, README generation, and their tests | Yes | | `.github/workflows/` | PR review/merge, CI, catalog sync, and deployment automation | Yes | ## Why JSON entries remain the submission form One structured file per plugin gives this repository: - schema validation before anything reaches production; - smaller pull-request diffs and fewer merge conflicts; - one place to review bilingual metadata; - deterministic derivation of owner and install commands (path-aware: a monorepo subdirectory id derives a `github:owner/repo#path:sub/dir` install spec); - an auditable git history for every curated entry. The files are an input, not a database: after a submission merges, the catalog-sync workflow pushes the full set to `POST /api/v1/catalog/sync`, the Worker reconciles it into D1 as the `github_pr` source, and the README projections are rebuilt from the live catalog API. ## Runtime data flow The Worker is the only process with D1 access. Within it, the community routes own only the `community_*` tables and read `api_users` / `api_sessions` / `catalog_plugins`; they never write catalog data. Two catalog write paths exist: 1. **Worker cron topic scan** — discovers and validates repositories carrying the `dsh-plugin` GitHub topic and writes them in process (`github_topic` source). 2. **GitHub CI catalog sync** — after a merge to `main` (and once daily), the catalog-sync workflow POSTs every `catalog/plugins/*.json` entry to `/api/v1/catalog/sync` with a bearer token (`github_pr` source). See [Cloudflare plugin discovery](plugin-discovery.md) for scan schedules and quota controls, and [API reference](api.md) for the public `/api/v1/*` contract. Live presence has a different consistency model, so it stays in the `LiveStats` Durable Object. Catalog changes never migrate or lock the live counter. Install analytics use the same D1 database as star history but separate tables. The wrapper CLI delegates to the official DeepSeek Harness CLI, verifies the profile before recording success, and retries idempotent events from a local queue. The Worker stores a server-keyed hash of each anonymous installation instance, never the raw client ID, and merges hourly aggregates into catalog snapshots. See [install analytics](install-analytics.md) for the event contract, counting rules, privacy controls, and deployment steps. ## Growth rules A shared package should only be introduced when at least two applications need the same runtime code. There is currently one application, so there is no such package: the community is a section of `apps/web`, grouped under `src/community/` and `worker/community/` rather than split out. Add to it only when a second app genuinely needs the module; app-specific code stays in the app. D1 owns all published catalog records; source-controlled entries are the reviewed submission form and remain independently auditable through pull requests.