dogsbay 0.2.0-beta.2 → 0.2.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,280 @@
1
+ ---
2
+ name: dogsbay:plugin-api
3
+ description: Authoring contract for Dogsbay plugins — DogsbayPlugin hooks, factory pattern, name resolution, and lifecycle. Use when writing a plugin, debugging an installed plugin, or configuring plugins in dogsbay.config.yml.
4
+ ---
5
+
6
+ # Dogsbay plugin API
7
+
8
+ Plugins extend the build pipeline at well-defined hooks. Each
9
+ plugin is an **npm package** that exports a default factory
10
+ function — Dogsbay calls the factory once per build with the
11
+ user's options, and the returned `DogsbayPlugin` object
12
+ contributes to the lifecycle.
13
+
14
+ ## Plugin shape
15
+
16
+ ```ts
17
+ import type { DogsbayPlugin, PluginContext } from "@dogsbay/types";
18
+
19
+ export default function imageZoom(
20
+ options: { medium?: { background?: string } } = {},
21
+ ): DogsbayPlugin {
22
+ return {
23
+ id: "image-zoom",
24
+ description: "Click-to-enlarge images, view-transition aware.",
25
+
26
+ extendConfig(ctx) {
27
+ // Mutate the resolved DogsbayConfig before content imports
28
+ },
29
+
30
+ onContentImported(pages, nav, ctx) {
31
+ // Walk the page tree; tag images with data-zoomable, etc.
32
+ },
33
+
34
+ transformNav(nav, ctx) {
35
+ // Mutate the nav tree (add groups, reorder, etc.)
36
+ },
37
+
38
+ emitFiles(helpers, ctx) {
39
+ // Write extra files into the output directory
40
+ },
41
+
42
+ clientModules() {
43
+ // Import paths that get bundled into every page's client JS
44
+ return ["./client.js"];
45
+ },
46
+
47
+ styles() {
48
+ // CSS files to include in the build
49
+ return ["./styles.css"];
50
+ },
51
+
52
+ defineClientConfig(ctx) {
53
+ // Build-time config that becomes a client-side constant
54
+ return { background: options.medium?.background ?? "#000" };
55
+ },
56
+
57
+ componentWrappers() {
58
+ // Astro components that wrap MarkdownContent slots
59
+ return [{ slot: "MarkdownContent", component: "./MyWrapper.astro" }];
60
+ },
61
+
62
+ auditRules(ctx) {
63
+ // Register dogsbay site-check rules
64
+ return [
65
+ {
66
+ name: "image-zoom/missing-alt",
67
+ severity: "warning",
68
+ run(page) { /* ... */ },
69
+ },
70
+ ];
71
+ },
72
+
73
+ addAstroIntegrations() {
74
+ // Astro integrations to register in astro.config.mjs
75
+ return [{ packageName: "@astrojs/some-integration", options: {} }];
76
+ },
77
+ };
78
+ }
79
+ ```
80
+
81
+ All hooks are optional. A plugin that only contributes a CSS
82
+ file declares `styles()` and nothing else.
83
+
84
+ ## Hooks — when each runs
85
+
86
+ | Hook | When | Mutates | Common use |
87
+ |---|---|---|---|
88
+ | `extendConfig` | Right after config load, before resolver | DogsbayConfig | Inject sources / plugins / taxonomies |
89
+ | `onContentImported` | After all sources imported, before draft filter | `pages[]`, `nav[]` | Walk trees, tag nodes, inject pages |
90
+ | `transformNav` | After `onContentImported`, before nav emit | `nav[]` | Reorder, group, decorate |
91
+ | `emitFiles` | After standard emitters | output dir | Write extra files (sitemaps, search indexes, etc.) |
92
+ | `clientModules` | At codegen time | adds to plugin-runtime entry | Inject browser-side JS |
93
+ | `styles` | At codegen time | adds to global CSS | Inject CSS files |
94
+ | `defineClientConfig` | At codegen time | writes per-plugin config module | Bridge build-time → client constants |
95
+ | `componentWrappers` | At per-page emit | wraps named slots | Wrap MarkdownContent with custom component (e.g. provider, decorator) |
96
+ | `auditRules` | When `dogsbay site check` runs | registers rules | Enforce conventions specific to the plugin |
97
+ | `addAstroIntegrations` | At astro.config.mjs scaffold | registers integrations | Pull in existing Astro integrations |
98
+
99
+ ## Factory pattern + ordering
100
+
101
+ Plugins are listed in `dogsbay.config.yml`'s `plugins:` field.
102
+ Each entry is either a string (package name) or an object with
103
+ options:
104
+
105
+ ```yaml
106
+ plugins:
107
+ - image-zoom # short — auto-prefix to @dogsbay/plugin-image-zoom
108
+ - "@your-org/plugin-foo" # explicit
109
+ - name: typedoc
110
+ id: api-typedoc # optional: override the plugin's default id
111
+ options:
112
+ sourceRoot: ./src
113
+ include: ["*.ts"]
114
+ ```
115
+
116
+ Name resolution:
117
+
118
+ 1. If the value contains `/` (scope), use as-is
119
+ 2. If `dogsbay-plugin-X` exists, use that
120
+ 3. If `@dogsbay/plugin-X` exists, use that
121
+ 4. Treat as absolute path / relative path / directory
122
+
123
+ The auto-prefix lets users write `image-zoom` and have it
124
+ resolve to `@dogsbay/plugin-image-zoom`.
125
+
126
+ ## Order — `before` / `after`
127
+
128
+ Each plugin's id is unique within a build. Plugins can declare
129
+ ordering constraints:
130
+
131
+ ```ts
132
+ return {
133
+ id: "my-plugin",
134
+ before: ["image-zoom"], // runs before image-zoom
135
+ after: ["typedoc"], // runs after typedoc
136
+ // ...
137
+ };
138
+ ```
139
+
140
+ The loader topologically sorts the plugin list. Cycles fail at
141
+ load time with a clear error.
142
+
143
+ ## Plugin context
144
+
145
+ Every hook receives a `PluginContext` with:
146
+
147
+ ```ts
148
+ interface PluginContext {
149
+ config: PluginVisibleConfig; // the resolved DogsbayConfig (filtered to "user-visible" fields)
150
+ outputDir: string; // absolute path to output dir (default ./astro)
151
+ logger: PluginLogger; // pre-fixed with [plugin: <id>]
152
+ }
153
+ ```
154
+
155
+ `logger.info / warn / error` route through the CLI's logger and
156
+ prefix with the plugin id, so users can trace warnings to a
157
+ specific plugin.
158
+
159
+ ## EmitFiles helpers
160
+
161
+ The `emitFiles` hook receives a helper bag:
162
+
163
+ ```ts
164
+ interface EmitFilesHelpers {
165
+ writeFile(relativePath: string, content: string | Uint8Array): void;
166
+ copyDir(srcAbs: string, destRelative: string): void;
167
+ }
168
+ ```
169
+
170
+ Both helpers are scoped to `outputDir` — relative paths are
171
+ resolved against it. A plugin can't escape its sandbox.
172
+
173
+ ## Plugin-contributed skills (Phase 3+)
174
+
175
+ Plugins can ship their own skills in their package, under
176
+ `skills/*.md`. The plugin loader merges them into the project's
177
+ discovery surface when the plugin is enabled.
178
+
179
+ ```
180
+ @dogsbay/plugin-image-zoom/
181
+ ├── package.json
182
+ ├── dist/
183
+ ├── skills/
184
+ │ └── image-zoom-config.md ← merged into .dogsbay/skills/plugins/
185
+ └── styles.css
186
+ ```
187
+
188
+ This means installing a plugin AUTOMATICALLY teaches the user's
189
+ LLM how to use it — no separate skill install step.
190
+
191
+ ## Reference plugins
192
+
193
+ Two plugins ship from the Dogsbay monorepo as references:
194
+
195
+ - `@dogsbay/plugin-image-zoom` — image-zoom on click. Exercises
196
+ `onContentImported` (tags images), `clientModules` (medium-zoom
197
+ binding), `styles`, `defineClientConfig`. Source:
198
+ `packages/plugin-image-zoom/`.
199
+ - `@dogsbay/plugin-typedoc` — TypeScript API docs from source
200
+ files. Exercises `onContentImported` (injects pages from
201
+ reflection), `transformNav`, `auditRules`. Source:
202
+ `packages/plugin-typedoc/`.
203
+
204
+ Both are good "read me first" references for new plugin
205
+ authors.
206
+
207
+ ## Common patterns
208
+
209
+ ### Pure styling plugin
210
+
211
+ ```ts
212
+ export default function darkOptional(): DogsbayPlugin {
213
+ return {
214
+ id: "dark-optional",
215
+ styles: () => ["./dark.css"],
216
+ };
217
+ }
218
+ ```
219
+
220
+ ### Tag-based content transformer
221
+
222
+ ```ts
223
+ export default function imageZoom(opts: Options = {}): DogsbayPlugin {
224
+ return {
225
+ id: "image-zoom",
226
+ onContentImported(pages) {
227
+ for (const page of pages) {
228
+ walkTree(page.tree, (node) => {
229
+ if (node.type === "image") {
230
+ node.props = { ...node.props, "data-zoomable": "true" };
231
+ }
232
+ });
233
+ }
234
+ return { pages, nav: [] };
235
+ },
236
+ clientModules: () => ["./client.js"],
237
+ styles: () => ["./styles.css"],
238
+ };
239
+ }
240
+ ```
241
+
242
+ ### Page-injecting plugin
243
+
244
+ ```ts
245
+ export default function typedoc(opts: Options): DogsbayPlugin {
246
+ let reflections: Reflection[] = [];
247
+ return {
248
+ id: "typedoc",
249
+ extendConfig(ctx) {
250
+ reflections = reflectFromSourceRoot(opts.sourceRoot);
251
+ },
252
+ onContentImported(pages, nav) {
253
+ const newPages = reflections.map(reflectionToExportPage);
254
+ const apiNav = buildNavFromReflections(reflections);
255
+ return { pages: [...pages, ...newPages], nav: [...nav, ...apiNav] };
256
+ },
257
+ auditRules() {
258
+ return [{ name: "typedoc/missing-doc", run: checkUndocumented }];
259
+ },
260
+ };
261
+ }
262
+ ```
263
+
264
+ ## Common mistakes
265
+
266
+ - ❌ Forgetting to make the factory the **default export** — the
267
+ loader specifically looks for `module.default`.
268
+ - ❌ Returning a Promise from a synchronous hook (e.g.
269
+ `extendConfig`) — most hooks accept sync OR async, but check
270
+ the type signature.
271
+ - ❌ Mutating the input arrays in `onContentImported` (e.g.
272
+ `pages.push(...)`) instead of returning the new shape — works
273
+ by accident today but is fragile across versions. Always
274
+ return `{ pages, nav }`.
275
+ - ❌ Writing to absolute paths from `emitFiles` — the helper is
276
+ sandboxed to `outputDir`. Use relative paths.
277
+ - ❌ Hardcoding the plugin id — accept it from options if the
278
+ user might want to install the same plugin twice with
279
+ different configs (the loader auto-suffixes duplicates with
280
+ `#1`, `#2`).
@@ -0,0 +1,156 @@
1
+ ---
2
+ name: dogsbay:project-anatomy
3
+ description: Understand the file layout of a Dogsbay project — where the config lives, where content goes, what astro/ contains, what's regenerated each build vs scaffolded once. Use when navigating an existing site or wiring user-authored files into the build.
4
+ ---
5
+
6
+ # Dogsbay project anatomy
7
+
8
+ Every Dogsbay-owned project (the `dogsbay site …` flow) has the
9
+ same shape. Knowing which directory holds what — and which files
10
+ are user-edited vs auto-regenerated — prevents the most common
11
+ "why didn't my change show up" confusions.
12
+
13
+ ## The layout
14
+
15
+ ```
16
+ my-docs/
17
+ ├── dogsbay.config.yml ← user-edited; site config
18
+ ├── content/ ← user-edited; markdown source
19
+ │ ├── index.md
20
+ │ ├── getting-started.md
21
+ │ └── nav.yml ← user-edited; sidebar
22
+ ├── public/ ← user-edited; static assets pass-through
23
+ │ └── …
24
+ ├── .dogsbay/ ← scaffolded; user can shadow specific files
25
+ │ ├── skills/
26
+ │ │ ├── platform/ ← symlinked → node_modules/dogsbay/skills/platform/
27
+ │ │ ├── plugins/ ← merged from enabled plugins (when present)
28
+ │ │ └── site/ ← user-authored; empty until populated
29
+ │ ├── patterns/ ← installed pattern packages (Diátaxis, etc.)
30
+ │ └── workflows/ ← installed workflow packages (PR review, etc.)
31
+ ├── astro/ ← REGENERATED EACH BUILD; do not hand-edit
32
+ │ ├── package.json ← scaffold-once; user can add deps
33
+ │ ├── astro.config.mjs ← scaffold-once
34
+ │ ├── tsconfig.json ← scaffold-once
35
+ │ ├── src/
36
+ │ │ ├── components/ui/ ← scaffold-once; user can edit individual components
37
+ │ │ ├── content/ ← REGENERATED — astro content collections
38
+ │ │ ├── data/ ← REGENERATED — nav.json, site.json, switcherMap.json
39
+ │ │ ├── lib/ ← REGENERATED — plugin-runtime, etc.
40
+ │ │ ├── middleware.ts ← REGENERATED
41
+ │ │ ├── pages/ ← REGENERATED EVERY BUILD; never hand-edit
42
+ │ │ ├── styles/ ← scaffold-once; theme.css + global.css
43
+ │ │ └── wrappers/ ← REGENERATED — plugin wrapper stacks
44
+ │ ├── public/ ← copied from project root /public/
45
+ │ ├── dist/ ← astro build output (gitignored)
46
+ │ └── node_modules/ ← gitignored
47
+ ├── .claude/skills/ ← created by `dogsbay agent install --agent claude`
48
+ └── .cursor/rules/ ← created by `dogsbay agent install --agent cursor`
49
+ ```
50
+
51
+ ## Two-directory model — why
52
+
53
+ Dogsbay separates the **content + config** that a writer thinks
54
+ about (project root) from the **buildable Astro project** that
55
+ the build pipeline owns (`astro/`). This is the single most
56
+ important thing to understand when working with a Dogsbay site.
57
+
58
+ | Project root | astro/ subdirectory |
59
+ |---|---|
60
+ | `dogsbay.config.yml`, `content/`, `public/`, `.dogsbay/` | `package.json`, `astro.config.mjs`, `src/pages/`, `node_modules/` |
61
+ | Hand-edited; in source control | Scaffolded once + regenerated on each `site build`; node_modules + dist gitignored |
62
+ | Where writers spend 99% of their time | Where the build pipeline lives |
63
+ | Run `dogsbay site …` from here | `pnpm install` runs here for first-time deps |
64
+
65
+ The config at the root anchors path resolution. Source paths in
66
+ `dogsbay.config.yml` (`./content`, `./openapi/spec.yaml`,
67
+ output: `./astro`) all resolve against the config file's
68
+ directory.
69
+
70
+ ## Scaffold tiers — what's written when
71
+
72
+ `format-astro` emits files in four tiers. Knowing which tier a
73
+ file belongs to tells you when it changes.
74
+
75
+ | Tier | Function | Files | When written |
76
+ |---|---|---|---|
77
+ | Static (scaffold-once) | `emitSiteScaffold` | `package.json`, `theme.css`, `astro.config.mjs`, `tsconfig.json`, `src/components/ui/*` | Only on first init or with `--force` |
78
+ | Content | `emitAstroPages` | `nav.json`, per-page `.astro` + `.md.ts` mirrors, root `index.astro` redirect | Every `site build` |
79
+ | Config-derived | `emitConfigDerivedFiles` | `public/robots.txt` (with `Content-Signal`) | Every `site build` |
80
+ | Agent | `emitAgentReadinessFiles` | `public/llms.txt`, `llms-full.txt`, per-section, `public/_headers`, `src/middleware.ts` | Every `site build` (gated by `agent.llmsTxt` / `agent.mdMirror`) |
81
+
82
+ Hand-edits to scaffold-once files (theme.css, individual
83
+ components) survive subsequent builds. Hand-edits to
84
+ regenerated-every-build files (pages, nav.json, llms.txt) get
85
+ overwritten.
86
+
87
+ ## What runs where
88
+
89
+ - `dogsbay site …` runs at the **project root** (anywhere with a
90
+ `dogsbay.config.yml` at or above cwd).
91
+ - `pnpm install` / `pnpm run build` / `pnpm run dev` / `astro
92
+ …` run in **`astro/`** (the buildable Astro project's
93
+ package.json lives here).
94
+ - `dogsbay site dev` and `site preview` are wrappers — they run
95
+ from the project root, do `dogsbay site build`, then `cd astro
96
+ && astro …` for you. Use these.
97
+
98
+ ## Common confusions
99
+
100
+ ### "I ran `npm run build` and got 'no package.json'"
101
+
102
+ You ran it at the project root. There's no `package.json` at the
103
+ root — the build script lives in `astro/package.json`. Use
104
+ `dogsbay site preview` instead, which handles the `cd astro` for
105
+ you.
106
+
107
+ ### "I edited `astro/src/pages/index.astro` and my change disappeared"
108
+
109
+ That directory is regenerated every `site build`. Edit
110
+ `content/index.md` instead — the build emits the .astro from
111
+ markdown.
112
+
113
+ ### "I edited `astro/src/components/ui/card/Card.astro` and my change DID survive"
114
+
115
+ Components are scaffold-once. The build doesn't touch them once
116
+ written. You own them like any shadcn-style component — edit
117
+ freely.
118
+
119
+ ### "Where do I put images?"
120
+
121
+ In `public/` at the project root. The build copies the directory
122
+ into `astro/public/` so Astro serves it as static assets.
123
+
124
+ ### "Why is there an `index.astro` in `astro/src/pages/` AND
125
+ `docs/index.astro`?"
126
+
127
+ The root `index.astro` is a 0-byte redirect to your `basePath`
128
+ (default `/docs/`). The real home page is at
129
+ `docs/index.astro`, generated from `content/index.md`.
130
+
131
+ ## Plugin runtime emission
132
+
133
+ Plugins contribute additional files at build time:
134
+
135
+ - `astro/src/lib/plugin-runtime.ts` — entry that imports each
136
+ plugin's clientModules
137
+ - `astro/src/lib/plugin-config/<id>.ts` — per-plugin config
138
+ - `astro/src/styles/plugins/*.css` — per-plugin stylesheets
139
+ - `astro/src/components/wrappers/*.astro` — wrapper stacks for
140
+ the MarkdownContent slot
141
+ - `astro/astro.config.plugins.mjs` — plugin alias map +
142
+ `vite.server.fs.allow` entries
143
+
144
+ These all live under `astro/src/lib/` or `astro/src/components/wrappers/`.
145
+ Hand-editing any of them gets overwritten on rebuild.
146
+
147
+ ## Output dir customisation
148
+
149
+ The default is `./astro`. Users can override via `dogsbay.config.yml`:
150
+
151
+ ```yaml
152
+ output: ./build/site
153
+ ```
154
+
155
+ But almost no one does this. Stick with `./astro` unless there's
156
+ a specific reason.