create-kywi-app 0.3.2 → 0.5.0

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.
package/README.md CHANGED
@@ -14,14 +14,17 @@ independently of the CLI's prompting/arg-parsing.
14
14
  ## Usage
15
15
 
16
16
  ```bash
17
- npx create-kywi-app [project-name] [options]
17
+ npx create-kywi-app [project-name] [options] # scaffold a new project (default)
18
+ npx create-kywi-app agents [--force] # add/refresh agent guidance in an
19
+ # existing project (run from its root)
18
20
 
19
21
  Options:
20
22
  --yes, -y Use defaults, skip prompts
21
23
  --mode <mode> coupled | headless | decoupled (default: coupled)
22
24
  --db <provider> postgresql | mysql (default: postgresql)
23
25
  --auth <list> comma-separated: credentials,google,github (default: credentials)
24
- --help, -h Show this help
26
+ --force, -f agents: overwrite existing guidance files (default: skip them)
27
+ --help, -h Show this help
25
28
  ```
26
29
 
27
30
  With no flags and a TTY, it prompts interactively (project name, DB provider,
@@ -64,6 +67,22 @@ next.config.mjs the FULL config to consume core (transpilePac
64
67
  .env.example DATABASE_URL + AUTH_SECRET
65
68
  .gitignore
66
69
  README.md project-specific quick start (createdb, migrate, seed, dev, /admin, admin.features)
70
+ AGENTS.md guidance for AI agents working on this site: an app-specific
71
+ header (where things live, the /admin URL, the CLI, the three
72
+ scaffolded Claude Code skills) + Kywi's official building
73
+ patterns (docs/agents/AGENT-PATTERNS.md, verbatim)
74
+ CLAUDE.md thin pointer that sends a Claude Code session to AGENTS.md first,
75
+ plus a nudge toward the kywi-content-model skill (and the other
76
+ two scaffolded skills)
77
+ .claude/skills/kywi-content-model/SKILL.md Claude Code project skill: design a CONTENT-MODEL.md and get
78
+ owner sign-off before creating types or entering content — loaded
79
+ automatically by Claude Code, a pre-build checklist for other agents
80
+ .claude/skills/kywi-collections/SKILL.md Claude Code project skill: build any collection (testimonials,
81
+ logos, posts, products, …) as folder + feed + Feed Display — loaded
82
+ automatically when adding a collection
83
+ .claude/skills/kywi-personalization/SKILL.md Claude Code project skill: confirm the use case, then
84
+ build audiences, page variants, or A/B experiments — loaded
85
+ automatically when the owner wants personalization or A/B testing
67
86
  lib/kywi.ts server runtime: DB, API handler, content scope (memoised)
68
87
  lib/config.ts re-export of kywi.config
69
88
  middleware.ts auth gate + session refresh + cookie→bearer bridge (over core/host)
@@ -100,6 +119,35 @@ The generated project's own `README.md` walks through `createdb`, copying
100
119
  `decoupled` deployment it also shows the `@kywi-software/sdk` usage snippet (see
101
120
  `packages/sdk/README.md` in this monorepo for the current, verified SDK API).
102
121
 
122
+ ## Adding (or refreshing) agent guidance in an existing project
123
+
124
+ New scaffolds get the agent-guidance files above automatically. To add them to a
125
+ project that already exists — or to pull the **latest** guidance into one that has
126
+ older copies — run the `agents` subcommand **from the project's root**:
127
+
128
+ ```bash
129
+ npx create-kywi-app@latest agents # add any missing guidance files
130
+ npx create-kywi-app@latest agents --force # overwrite them with the latest
131
+ ```
132
+
133
+ It writes the same five files (`AGENTS.md`, `CLAUDE.md`, and the three
134
+ `.claude/skills/<slug>/SKILL.md`). What it does:
135
+
136
+ - **Detects the project** — requires a `kywi.config.ts` in the current directory
137
+ (otherwise it exits with an error), reads the project name from `package.json`
138
+ (falling back to the directory name) and the deployment mode from the config.
139
+ - **Writes the real `AGENTS.md` header** — the "where things live" section is
140
+ generated from the landmarks it actually finds on disk (e.g. `lib/site.ts`,
141
+ `lib/modules.tsx`, the public `app/(site)/[[...slug]]/page.tsx`), so a
142
+ hand-built app that predates part of the scaffold gets an honest header rather
143
+ than one that asserts files it doesn't have.
144
+ - **Never clobbers by default** — existing files are skipped (a customized
145
+ `AGENTS.md` is left untouched); pass `--force` to overwrite. It prints a
146
+ created/updated/skipped summary either way.
147
+
148
+ Use `@latest` so an existing project picks up the newest guidance regardless of
149
+ which `create-kywi-app` version originally scaffolded it.
150
+
103
151
  ## Installing the generated packages
104
152
 
105
153
  All `@kywi-software/*` packages (`core`, `cli`, `sdk`, `mcp`, `js`) are
@@ -0,0 +1,294 @@
1
+ # Kywi Patterns — building sites end users can maintain
2
+
3
+ Guidance for agents building Kywi sites. These are opinions, not rules — a
4
+ developer can overrule any of them — but they encode what Kywi is *for*: sites
5
+ that get handed to non-developers who keep them alive without you. Follow them
6
+ unless the project has a stated reason not to.
7
+
8
+ ## The core principle
9
+
10
+ **If a non-developer might ever want to change it, model it in the CMS — don't
11
+ hardcode it.** Every hardcoded testimonial array, pasted-in logo grid, or
12
+ copy-edited JSX headline is a future support request. The test before writing
13
+ any markup: *"When the site owner wants this different next month, do they
14
+ edit content in the admin, or do they need a developer?"* If the answer is
15
+ "developer," reach for one of the patterns below instead.
16
+
17
+ Static code is for structure and brand chrome. Content — anything with words,
18
+ images, prices, names, dates — belongs in Kywi.
19
+
20
+ ## Before you build: design the content model and get sign-off
21
+
22
+ The most expensive failure in a CMS build is a poorly planned content model —
23
+ types bolted on mid-build, collections discovered after their items were
24
+ entered as free-form pages, taxonomy retrofitted onto a hundred nodes. Content
25
+ entry is the costly part; planning is cheap. So **start every new site (or any
26
+ significant new content area) by proposing the content model and getting the
27
+ owner's sign-off before creating types or entering content.**
28
+
29
+ Produce a short `CONTENT-MODEL.md` at the project root covering:
30
+
31
+ - **Content inventory → types.** Every repeated shape, with its proposed type
32
+ (built-in page vs. custom), fields (name, field type, required), and
33
+ relationships. One table per type.
34
+ - **Site tree sketch.** Top-level pages and a folder per collection (§1).
35
+ - **Feeds.** Name, source type, taxonomy filter, sort/limit, and which pages
36
+ display each one.
37
+ - **Taxonomy.** The category sets and expected tags (§3).
38
+ - **Reusable components.** The sections that will appear on multiple pages (§4).
39
+ - **Maintenance notes.** Who edits what; whether review workflow (§9), i18n
40
+ (§11), or scheduling (§10) apply.
41
+ - **Open questions** for the owner.
42
+
43
+ Present it, iterate, and get explicit approval — then build in playbook order.
44
+ Keep the file updated as the model evolves; it's the site's living map.
45
+
46
+ Proportionality applies (this is guidance, not law): a single-page brochure
47
+ site needs a few sentences of confirmation, not a document. But anything
48
+ involving custom types, collections, or more than a handful of pages deserves
49
+ the full proposal — ten minutes of sign-off beats re-entering fifty nodes.
50
+
51
+ ## Quick decisions
52
+
53
+ | You're about to… | Do this instead |
54
+ |---|---|
55
+ | Hardcode a list (testimonials, logos, posts, products, team) | Folder of nodes + a Feed + the Feed Display module (§1) |
56
+ | Create one-off pages with identical field shapes | A custom content type + one node per item (§2) |
57
+ | Filter/group content by topic, industry, audience | Categories & tags, queried by feeds (§3) |
58
+ | Copy-paste a CTA/banner/snippet across pages | Save it as a reusable component (§4) |
59
+ | Write page sections as JSX | Build them as layout sections/modules the owner can edit (§5) |
60
+ | Hand-code a `<form>` | Build it in the Forms admin, place the Form module (§6) |
61
+ | Drop images into `/public` | Upload to the Media library (§7) |
62
+ | Build segment-specific or experimental UI unprompted | Ask the owner first — offer personalization/A-B with examples (§8) |
63
+ | Let everyone publish straight to the live site | Configure groups + review workflow (§9) |
64
+ | "Launch this next Tuesday" by deploying on Tuesday | Scheduled publishing / a changeset (§10) |
65
+
66
+ ---
67
+
68
+ ## 1. Collections: folders + feeds + Feed Display
69
+
70
+ **The workhorse pattern.** Any repeating set of items that appears somewhere on
71
+ the site — customer logos, testimonials, recent blog posts, products, portfolio
72
+ pieces, team members, FAQs, press mentions — is a collection, and collections
73
+ are never hardcoded.
74
+
75
+ - **Structure:** a folder in the Site Tree per collection (`/testimonials`,
76
+ `/customers`, `/work`), one content node per item. Items that need their own
77
+ public page (blog posts, portfolio pieces) live at real paths; items that are
78
+ only ever displayed in aggregate (logos, quotes) still get nodes — their pages
79
+ just aren't linked.
80
+ - **Query:** create a Feed (admin → Feeds): pick the content type, filter by
81
+ taxonomy, sort, and limit ("6 most recent posts", "testimonials tagged
82
+ `homepage`").
83
+ - **Display:** place the **Feed Display** module wherever the collection should
84
+ render — home page hero strip, interior sidebars, a `/blog` index. One feed
85
+ can feed many pages.
86
+ - **Why it wins:** the owner adds a testimonial by creating one node. Every
87
+ page showing that feed updates. No layout edits, no deploys.
88
+
89
+ Advanced: product catalogs and portfolios are the same pattern with a richer
90
+ custom type (§2) and taxonomy-driven feeds per category (§3). Multiple feeds
91
+ over one collection give different slices (featured vs. all, per-category).
92
+
93
+ ## 2. Structured data: custom content types
94
+
95
+ When several nodes share the same shape — recipes (ingredients, steps, prep
96
+ time), team profiles (role, bio, headshot, links), products (price, SKU,
97
+ gallery), case studies (client, industry, outcome) — define a **custom content
98
+ type** so the shape is enforced and the data is queryable, instead of burying
99
+ it in free-form body text.
100
+
101
+ **Default: create types in the admin (Type Designer), not in kywi.config.ts.**
102
+ Admin-created types are runtime-managed: the site owner can add a field next
103
+ year ("dietary tags on recipes") without a developer or a migration. Reserve
104
+ config-defined types (`contentTypes` in `kywi.config.ts`) for schema the
105
+ *developer* must own — shapes that code depends on, reviewed in git, migrated
106
+ by `kywi migrate`. When in doubt, admin-created.
107
+
108
+ - Fields available include text, textarea, rich text, date, image, URL, email,
109
+ JSON, and relationship (with a target type — e.g. a recipe's `author` →
110
+ profile). Title can be made optional per type.
111
+ - **Combine with §1:** the type gives you structure; a feed over the type gives
112
+ you the listing (`/recipes` index, an intranet's people directory); each
113
+ node's own page is the detail view. Listing + detail with zero custom code.
114
+ - Don't shadow built-in fields (slug, body) with custom ones — Kywi will block
115
+ it; use the built-ins.
116
+
117
+ ## 3. Taxonomy: categories & tags
118
+
119
+ Categories and tags are the cross-cutting organization layer over §1 and §2.
120
+ Use **categories** for a site's stable sections (Recipes: Breakfast / Dinner /
121
+ Dessert) and **tags** for freeform, evolving labels (gluten-free, quick,
122
+ featured).
123
+
124
+ - Feeds filter by taxonomy, so "show featured testimonials on the home page"
125
+ is: tag the nodes `featured`, point the feed at the tag. The owner curates
126
+ the home page by tagging — never by editing the page.
127
+ - Prefer taxonomy-driven feeds over separate folders when items belong to
128
+ multiple groupings (a recipe is both `quick` and `vegetarian`; a folder can
129
+ only hold it once).
130
+
131
+ ## 4. Reusable components: edit once, propagate everywhere
132
+
133
+ Anything designed once and used on multiple pages — CTA bands, promo banners,
134
+ newsletter signup blocks, campaign snippets, "as seen in" strips — should be a
135
+ **reusable component**, not copy-pasted sections.
136
+
137
+ - In the layout editor, select the module and **Save as Component**; insert it
138
+ elsewhere from the component picker. Placed instances are **live
139
+ references**: editing the source component updates every page that uses it.
140
+ - **Detach** an instance when a page genuinely needs a one-off variant — it
141
+ becomes an independent copy from that point on.
142
+ - Rule of thumb: the second time you paste the same section, stop and make it
143
+ a component. The owner should be able to update the sitewide CTA in one
144
+ place, ten minutes before their webinar.
145
+
146
+ ## 5. Page layouts & modules — not hardcoded JSX
147
+
148
+ Pages the owner should be able to restructure — landing pages, the home page,
149
+ campaign pages — should be built **in the layout editor** (sections, columns,
150
+ modules), rendered through Kywi's layout renderer. A scaffolded app already
151
+ renders saved layouts on every content page; keep it that way.
152
+
153
+ - The built-in module palette (hero, cards, CTA, testimonial, feed display,
154
+ pricing, FAQ, forms, comments, media…) covers most marketing-site needs.
155
+ - Brand-specific blocks the palette lacks: build a **custom module** once
156
+ (`defineModule` in `kywi.config.ts` + a React component registered in the
157
+ host's module map — the scaffold's `lib/modules.tsx` shows the shape). The
158
+ developer owns the component; the owner places and configures instances.
159
+ - Create **saved layouts** (Layouts admin) as page templates — "Landing page",
160
+ "Case study" — so new pages start from a consistent skeleton instead of a
161
+ blank canvas.
162
+ - Hand-written JSX pages are fine for genuinely fixed chrome (a bespoke 404,
163
+ legal boilerplate shells) — but if marketing will ever want to swap a
164
+ headline, it's a layout page.
165
+
166
+ ## 6. Forms: always the Forms builder
167
+
168
+ Never hand-code a `<form>`. Build forms in the Forms admin (fields, multi-step,
169
+ success message, notification emails, optional reCAPTCHA via Settings →
170
+ Security) and place them with the **Form / Form Embed** modules, which submit
171
+ through Kywi's pipeline into admin → Submissions.
172
+
173
+ - The owner edits fields, recipients, and the thank-you message without code.
174
+ - Submissions are stored, browsable, and exportable — a hand-rolled form that
175
+ emails someone is data loss with extra steps.
176
+
177
+ ## 7. Media: the library, not /public
178
+
179
+ All owner-managed imagery — logos, hero images, headshots, product shots —
180
+ goes through the Media library (upload, alt text, automatic variants,
181
+ on-demand resize). Reserve `/public` for build-time brand assets (favicon,
182
+ font files) that only change when the code does.
183
+
184
+ The difference matters at handover: the owner can swap a hero image in the
185
+ library; they cannot ship a new `/public` file.
186
+
187
+ ## 8. Personalization & A/B testing — offer it, don't default to it
188
+
189
+ Kywi ships a full personalization stack: **audiences** (rule-based, plus an
190
+ optional self-identification widget), **page variants** targeted per audience,
191
+ and **A/B experiments** with stable per-visitor assignment and recorded
192
+ exposures. Most sites don't need it on day one — and unrequested
193
+ personalization is complexity the owner didn't ask to maintain.
194
+
195
+ **The agent's job is to surface the capability, not to assume it.** When
196
+ scoping a build, ask the owner whether any of these fit, with examples:
197
+
198
+ - *"Should returning visitors see a different home-page hero than first-timers
199
+ (e.g. 'Welcome back — pick up where you left off')?"*
200
+ - *"Do you serve distinct segments (agencies vs. freelancers, industries,
201
+ regions) that should get tailored messaging on key pages?"*
202
+ - *"Would you like visitors to self-identify (e.g. 'I'm a developer / I'm a
203
+ marketer') and see content ordered for them?"*
204
+ - *"Is there a headline, CTA, or pricing presentation you'd like to A/B test
205
+ before committing?"*
206
+
207
+ If yes, principles: personalize **sections and modules**, not whole sites; the
208
+ default variant must stand alone (personalization is progressive enhancement);
209
+ one experiment per conversion goal, and let it conclude before layering more.
210
+ If no, skip it — the machinery is there when they grow into it.
211
+
212
+ ## 9. Editorial workflow: drafts, review, versions
213
+
214
+ For any site with more than one author — or an owner who wants a safety net —
215
+ configure the workflow rather than letting everything publish directly:
216
+
217
+ - **Groups & permissions:** editors write (`draft → Submit for Review`),
218
+ a smaller group approves and publishes. Content-level permissions can gate
219
+ specific sections (e.g. only Legal edits `/legal/*`).
220
+ - Published pages accept **pending revisions** — edits go through review while
221
+ the live page keeps serving — so review doesn't mean taking pages down.
222
+ - Every save records a **version** with restore; approvers see field-level
223
+ diffs. Mention this at handover: "you can always roll back."
224
+
225
+ Solo-owner sites can publish directly — but still enable it before the team
226
+ grows past one.
227
+
228
+ ## 10. Scheduling & changesets
229
+
230
+ - Content has schedule fields: publish at a future time, unpublish/expire
231
+ automatically. The background scheduler handles both — "post this Monday 9am"
232
+ is a field, not a calendar reminder.
233
+ - **Changesets** batch related edits (a product launch touching six pages) and
234
+ publish them together, optionally on a schedule. Use one whenever a launch
235
+ spans multiple nodes — partial launches are worse than late ones.
236
+
237
+ ## 11. Multilingual sites
238
+
239
+ If the owner needs more than one language, declare `locales` in
240
+ `kywi.config.ts` up front — the scaffold routes locale prefixes (`/es/...`),
241
+ falls back to the default locale, and emits hreflang alternates. Translations
242
+ are per-node in the admin. Retrofitting i18n is far costlier than declaring it
243
+ early, so ask at scoping time.
244
+
245
+ ## 12. SEO & the Agent Experience layer
246
+
247
+ - Fill the **SEO tab** on every page that matters (meta title/description,
248
+ og:image); the scaffold maps it into the public head and emits JSON-LD.
249
+ - The AX layer serves `llms.txt`, `llms-full.txt`, `sitemap.xml`, and
250
+ `robots.txt` at the site root out of the box — a Kywi site is legible to
251
+ agents and crawlers by default. Don't remove these routes; they're part of
252
+ the product's value.
253
+
254
+ ## Other capabilities worth knowing
255
+
256
+ - **Comments:** a moderated Comments module (submissions land pending) for
257
+ blogs/community pages — ask the owner if discussion fits.
258
+ - **Webhooks** (Web Services → Webhooks): notify external systems on content
259
+ events (rebuild a static mirror, ping Slack, sync a CRM) — signed, retried.
260
+ - **API keys** with enforced scopes: hand a read-only key to a partner or a
261
+ frontend without exposing write access.
262
+
263
+ ## Build-order playbook (new site)
264
+
265
+ 0. **Propose the content model and get sign-off** ("Before you build", above)
266
+ — nothing else starts until the owner approves it.
267
+ 1. **Model first:** custom content types for every repeated shape (§2).
268
+ 2. **Structure:** site tree — pages, folders per collection (§1).
269
+ 3. **Taxonomy:** categories/tags the feeds will need (§3).
270
+ 4. **Media:** upload the brand's assets to the library (§7).
271
+ 5. **Templates:** saved layouts + reusable components for the recurring
272
+ sections (§4, §5); custom modules only where the palette falls short.
273
+ 6. **Wire collections:** feeds + Feed Display placements (§1).
274
+ 7. **Forms:** contact/newsletter/etc. in the builder (§6).
275
+ 8. **Workflow:** groups, permissions, review path (§9).
276
+ 9. **Ask about** personalization/A-B (§8), comments, webhooks, i18n (§11).
277
+ 10. **SEO pass** (§12), then hand over: show the owner where *their* edits
278
+ live — content nodes, feeds, components — and confirm nothing they'll want
279
+ to change requires you.
280
+
281
+ ## Anti-patterns (smells)
282
+
283
+ - Creating types and entering content before the owner signed off on a
284
+ content model.
285
+ - A hardcoded array of testimonials/logos/posts in a page component.
286
+ - The same CTA JSX pasted on four pages.
287
+ - A `<form>` that POSTs to a hand-rolled route (or nowhere).
288
+ - Marketing imagery in `/public`.
289
+ - A "blog" that is a folder of `.mdx` files the owner can't edit.
290
+ - One-off content types created in config for shapes the owner will evolve.
291
+ - Personalization built speculatively, with no owner request behind it.
292
+ - Direct-publish-only workflow on a multi-author site.
293
+
294
+ Every one of these has a section above. If you catch yourself mid-smell, refactor to the pattern before handover — it's minutes now, migrations later.
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: kywi-collections
3
+ description: Use when adding ANY collection to this Kywi site — testimonials, customer logos, blog posts, products, portfolio pieces, team members, FAQs, press mentions — or whenever you catch yourself about to hardcode a repeating list of items in a page. Walks the folder + feed + Feed Display pattern end to end.
4
+ ---
5
+
6
+ # Build a collection: folder + feed + Feed Display
7
+
8
+ Collections are never hardcoded. A collection is any set of similar items that
9
+ grows over time and is displayed somewhere — the owner must be able to add an
10
+ item in the admin and see every relevant page update, with no code changes.
11
+
12
+ ## Workflow
13
+
14
+ ### 1. Recognize the collection
15
+
16
+ About to write an array of testimonials, a grid of logos, a list of cards in
17
+ JSX? Stop — that's a collection. Confirm the shape: what fields does one item
18
+ have, and where on the site do items appear (one page? several? aggregate-only
19
+ or does each item need its own page)?
20
+
21
+ ### 2. Model the item type
22
+
23
+ - Items with structure beyond title/body/image (products, recipes, profiles)
24
+ → create a custom content type first (admin → Content Types; admin-created
25
+ by default so the owner can evolve it). Match the approved `CONTENT-MODEL.md`
26
+ — if this collection isn't in it, update the model and confirm the addition
27
+ with the owner before building.
28
+ - Simple items (a quote + attribution, a logo + link) can use an existing or
29
+ minimal type — don't over-model.
30
+
31
+ ### 3. Structure: a folder in the Site Tree
32
+
33
+ Create a folder for the collection (`/testimonials`, `/work`, `/customers`)
34
+ and add the initial nodes inside it. Two gotchas:
35
+
36
+ - **Nodes must be `published`** to appear in public feeds — drafts won't show.
37
+ - Aggregate-only items (logos, quotes) still get nodes; their individual pages
38
+ simply go unlinked. Items needing detail pages (posts, portfolio pieces)
39
+ live at real, linkable paths.
40
+
41
+ ### 4. Query: create the Feed
42
+
43
+ Admin → Feeds → new feed: pick the content type, add taxonomy filters if the
44
+ display is a slice ("testimonials tagged `homepage`"), set sort and limit
45
+ ("6 most recent"). **Check the feed preview shows the expected items before
46
+ moving on** — an empty preview means a wrong type, unpublished nodes, or a
47
+ taxonomy term that isn't applied to anything yet.
48
+
49
+ Multiple slices of one collection = multiple feeds over the same type
50
+ (featured vs. all; per-category).
51
+
52
+ ### 5. Display: place the Feed Display module
53
+
54
+ In the layout editor on each target page, add the **Feed Display** module and
55
+ select the feed. If the same feed placement (with surrounding design) recurs
56
+ on several pages, save the section as a reusable component instead of
57
+ rebuilding it per page.
58
+
59
+ Scaffolded apps hydrate feeds server-side automatically. A hand-built host
60
+ must call `hydrateLayoutFeeds` before rendering `KywiLayout` — if the module
61
+ renders an empty list publicly but previews fine in the admin, missing host
62
+ hydration is the usual cause.
63
+
64
+ ### 6. Verify end to end
65
+
66
+ Publish the page, load it publicly, confirm items render. Then the real test:
67
+ **add one more node to the folder and reload — it must appear without touching
68
+ the page.** If it doesn't, the loop isn't closed; fix before moving on.
69
+
70
+ ### 7. Handover note
71
+
72
+ Record in `CONTENT-MODEL.md` (feeds table) and tell the owner: "to add a
73
+ <item>, create it in <folder>; it appears on <pages> automatically."
@@ -0,0 +1,94 @@
1
+ ---
2
+ name: kywi-content-model
3
+ description: Use BEFORE building out this Kywi site or adding any significant new content area (custom types, collections, structured sections) — designs the content model and gets the owner's sign-off first. The most expensive CMS failure is a poorly planned content model; content entry is costly, planning is cheap.
4
+ ---
5
+
6
+ # Design the content model, then get sign-off
7
+
8
+ Do not create content types, enter content, or build page layouts for a new
9
+ site or content area until the owner has approved a content model. This is
10
+ guidance, not law — a single-page brochure site needs a sentence of
11
+ confirmation, not a document — but anything with custom types, collections, or
12
+ more than a handful of pages gets the full treatment.
13
+
14
+ ## Workflow
15
+
16
+ ### 1. Discovery — ask before proposing
17
+
18
+ Understand the site before modeling it. Ask the owner (batch the questions):
19
+
20
+ - What does the site need to communicate, and to whom?
21
+ - What kinds of content exist or are planned? (posts, products, recipes,
22
+ people, case studies, events, FAQs, testimonials, locations…)
23
+ - Which of those are *collections* — sets of similar items that grow over time?
24
+ - Who maintains the site after handover, and how technical are they?
25
+ - Multiple authors (→ review workflow)? Multiple languages? Timed launches?
26
+ - Any personalization/A-B interest? (Offer examples; don't assume — see
27
+ AGENTS.md §8.)
28
+
29
+ ### 2. Draft `CONTENT-MODEL.md` at the project root
30
+
31
+ Use this template:
32
+
33
+ ```markdown
34
+ # Content model — <site>
35
+
36
+ ## Types
37
+ | Type | Built-in/custom | Purpose | Maintained by |
38
+ |---|---|---|---|
39
+
40
+ ### <each custom type>
41
+ | Field | Type | Required | Notes |
42
+ |---|---|---|---|
43
+
44
+ ## Site tree
45
+ <top-level pages and a folder per collection, as an indented list>
46
+
47
+ ## Feeds
48
+ | Feed | Source type | Filter | Sort/limit | Displayed on |
49
+ |---|---|---|---|---|
50
+
51
+ ## Taxonomy
52
+ Categories: <stable sets> · Tags: <expected freeform labels>
53
+
54
+ ## Reusable components
55
+ | Component | Used on |
56
+ |---|---|
57
+
58
+ ## Maintenance
59
+ Editors/approvers, workflow needs, i18n locales, scheduling needs.
60
+
61
+ ## Open questions
62
+ ```
63
+
64
+ Modeling rules of thumb (full rationale in AGENTS.md):
65
+
66
+ - Repeated shape → custom type, **admin-created by default** (owners can add
67
+ fields later without a developer); config-defined only for developer-owned
68
+ schema.
69
+ - Anything displayed as a list somewhere → collection: folder + feed + Feed
70
+ Display module. Never a hardcoded array.
71
+ - Cross-cutting groupings → categories (stable) and tags (evolving), queried
72
+ by feeds.
73
+ - Sections used on 2+ pages → reusable components.
74
+ - Don't shadow built-in fields (title, slug, body); don't invent a type per
75
+ page — types are for repeated shapes.
76
+
77
+ ### 3. Get explicit sign-off
78
+
79
+ Present the proposal and iterate. Ask directly: *"Does this content model
80
+ match how you think about your content, and who will maintain each part?"*
81
+ Get an explicit yes before proceeding. If scope is trivial, a one-paragraph
82
+ summary and a "confirm?" suffices.
83
+
84
+ ### 4. Build — only after approval
85
+
86
+ Follow the build-order playbook in AGENTS.md: types → tree/folders → taxonomy
87
+ → media → layouts/components → feeds → forms → workflow → (ask about
88
+ personalization, comments, i18n) → SEO pass → handover.
89
+
90
+ ### 5. Keep the model current
91
+
92
+ `CONTENT-MODEL.md` is a living document. When the model changes later (new
93
+ type, new collection), update the file and re-confirm significant changes with
94
+ the owner. It doubles as the handover map of "where your content lives."
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: kywi-personalization
3
+ description: Use when the site owner wants personalization or A/B testing on this Kywi site — audience-targeted content, segment-specific pages, self-identification, or experiments — or before YOU propose any of those. Multi-step - audiences, variants/experiments, preview-token verification, conclusion.
4
+ ---
5
+
6
+ # Personalization & A/B testing — confirmed use case first, then build
7
+
8
+ **Never build personalization speculatively.** If the owner hasn't asked,
9
+ offer it with concrete examples and let them decide (returning-visitor hero,
10
+ segment-specific messaging, self-identified content ordering, a CTA test).
11
+ Only proceed with a confirmed use case and a stated success measure.
12
+
13
+ ## Workflow
14
+
15
+ ### 1. Confirm the use case and what "working" means
16
+
17
+ With the owner, pin down: which segment or hypothesis, which page(s) and
18
+ section(s), and what outcome defines success (a conversion, a click-through, a
19
+ qualitative "the right people see the right message"). Write it down — it
20
+ decides the mechanism and when you're done.
21
+
22
+ ### 2. Choose the mechanism
23
+
24
+ - **Known segment, deterministic content** → audience + **page variant**
25
+ ("agencies see the agency hero"). No measurement involved.
26
+ - **Hypothesis to measure** → **A/B experiment** ("does the short headline
27
+ convert better?"). Variants + recorded exposures.
28
+ - **Visitor-declared identity** → **self-ID widget** ("I'm a developer / I'm
29
+ a marketer") feeding an audience.
30
+
31
+ ### 3. Build the audience (variant and self-ID paths)
32
+
33
+ Admin → Audiences: define the rules, or configure the self-ID widget and its
34
+ fields. Use the audience test tool to confirm the rules match the intended
35
+ visitors before wiring anything to it.
36
+
37
+ ### 4a. Page-variant path
38
+
39
+ In the layout editor on the target page: **+ Page Variant** → select the
40
+ audience → edit the variant. Change **only the sections that segment needs** —
41
+ the default variant must remain complete and self-sufficient (personalization
42
+ is progressive enhancement; anonymous visitors get the default).
43
+
44
+ ### 4b. Experiment path
45
+
46
+ Admin → Experiments: create the experiment. In the layout, add an A/B
47
+ container with an arm per treatment. Assignment is deterministic per visitor
48
+ (the scaffold's middleware issues a persistent `kywi_visitor` cookie), so a
49
+ visitor sees the same arm on every visit — no client runtime needed.
50
+
51
+ ### 5. Verify before calling it done
52
+
53
+ - **Preview tokens** (Audiences → Preview Tokens): view the page *as each
54
+ audience* and confirm the right variant serves.
55
+ - Experiments: two fresh browser profiles should get (possibly) different
56
+ arms, and each profile must get the **same** arm on reload.
57
+ - Confirm exposures are being recorded in the Experiments admin.
58
+ - Load the page as a plain anonymous visitor: the default must be complete.
59
+
60
+ ### 6. Run, conclude, promote
61
+
62
+ One experiment per conversion goal; let it reach a conclusion before layering
63
+ another on the same page. When it concludes, **promote the winner into the
64
+ base layout and remove the experiment container** — a site accreting stale
65
+ experiments is a maintenance smell.
66
+
67
+ ### 7. Document for handover
68
+
69
+ Record what is personalized where (and why) in `CONTENT-MODEL.md`'s
70
+ maintenance notes. The owner must know a page has variants before they edit
71
+ it — editing only the default of a heavily-personalized page is a classic
72
+ post-handover surprise.
@@ -12,12 +12,12 @@
12
12
  */
13
13
  import { mkdir, writeFile, readdir } from 'node:fs/promises'
14
14
  import { existsSync } from 'node:fs'
15
- import { dirname, join, resolve } from 'node:path'
15
+ import { dirname, join, resolve, basename } from 'node:path'
16
16
  import { createInterface } from 'node:readline/promises'
17
17
  import { stdin, stdout, argv, exit } from 'node:process'
18
18
  import { fileURLToPath } from 'node:url'
19
19
  import { readFileSync } from 'node:fs'
20
- import { buildFileSet } from '../lib/templates.mjs'
20
+ import { buildFileSet, guidanceFileSet, detectAgentsLandmarks } from '../lib/templates.mjs'
21
21
 
22
22
  const MODES = ['coupled', 'headless', 'decoupled']
23
23
  const DB_PROVIDERS = ['postgresql', 'mysql']
@@ -38,12 +38,13 @@ function readKywiVersion() {
38
38
  // ── Arg parsing ───────────────────────────────────────────────────────────────
39
39
 
40
40
  function parseArgs(args) {
41
- const opts = { name: undefined, yes: false, mode: undefined, db: undefined, auth: undefined, help: false }
42
- const positional = []
41
+ const opts = { name: undefined, yes: false, mode: undefined, db: undefined, auth: undefined, help: false, force: false, positional: [] }
42
+ const positional = opts.positional
43
43
  for (let i = 0; i < args.length; i++) {
44
44
  const arg = args[i]
45
45
  if (arg === '--yes' || arg === '-y') opts.yes = true
46
46
  else if (arg === '--help' || arg === '-h') opts.help = true
47
+ else if (arg === '--force' || arg === '-f') opts.force = true
47
48
  else if (arg === '--mode') opts.mode = args[++i]
48
49
  else if (arg.startsWith('--mode=')) opts.mode = arg.slice(7)
49
50
  else if (arg === '--db') opts.db = args[++i]
@@ -61,19 +62,27 @@ function printHelp() {
61
62
  create-kywi-app — scaffold a new Kywi CMS project
62
63
 
63
64
  Usage:
64
- create-kywi-app [project-name] [options]
65
+ create-kywi-app [project-name] [options] Scaffold a new project (default)
66
+ create-kywi-app agents [--force] Install/refresh the agent-guidance
67
+ files (AGENTS.md, CLAUDE.md,
68
+ .claude/skills/) in the CURRENT
69
+ Kywi project — run it from the
70
+ project root
65
71
 
66
72
  Options:
67
73
  --yes, -y Use defaults, skip prompts
68
74
  --mode <mode> coupled | headless | decoupled (default: coupled)
69
75
  --db <provider> postgresql | mysql (default: postgresql)
70
76
  --auth <list> comma-separated: credentials,google,github (default: credentials)
77
+ --force, -f agents: overwrite existing guidance files (default: skip them)
71
78
  --help, -h Show this help
72
79
 
73
80
  Examples:
74
81
  npx create-kywi-app my-site
75
82
  npx create-kywi-app my-site --yes
76
83
  npx create-kywi-app blog --mode headless --auth credentials,google
84
+ npx create-kywi-app@latest agents # add guidance to an existing project
85
+ npx create-kywi-app@latest agents --force # refresh it to the latest version
77
86
  `)
78
87
  }
79
88
 
@@ -146,6 +155,107 @@ async function isNonEmptyDir(dir) {
146
155
  return entries.length > 0
147
156
  }
148
157
 
158
+ // ── `agents` subcommand ─────────────────────────────────────────────────────────
159
+
160
+ const DETECTABLE_MODES = ['coupled', 'headless', 'decoupled']
161
+
162
+ /** Read the project name from ./package.json, falling back to the directory name. */
163
+ function readProjectName(dir) {
164
+ try {
165
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'))
166
+ if (typeof pkg.name === 'string' && pkg.name.trim()) return pkg.name.trim()
167
+ } catch {
168
+ /* no/invalid package.json — fall through to the directory basename */
169
+ }
170
+ return basename(dir) || 'kywi-app'
171
+ }
172
+
173
+ /**
174
+ * Detect the deployment mode from kywi.config.ts (`mode: 'coupled'|'headless'|
175
+ * 'decoupled'`). Returns coupled with detected:false when absent/unmatched, so the
176
+ * caller can print a note.
177
+ */
178
+ function detectMode(dir) {
179
+ try {
180
+ const cfg = readFileSync(join(dir, 'kywi.config.ts'), 'utf8')
181
+ const m = cfg.match(/mode:\s*['"](coupled|headless|decoupled)['"]/)
182
+ if (m && DETECTABLE_MODES.includes(m[1])) return { mode: m[1], detected: true }
183
+ } catch {
184
+ /* unreadable config — handled by the caller's kywi.config.ts existence gate */
185
+ }
186
+ return { mode: 'coupled', detected: false }
187
+ }
188
+
189
+ /**
190
+ * `create-kywi-app agents [--force]` — install/refresh the agent-guidance files
191
+ * that a fresh scaffold ships (AGENTS.md, CLAUDE.md, the three .claude/skills/*)
192
+ * into an EXISTING Kywi project. The AGENTS.md header is generated from landmarks
193
+ * detected on disk, so it describes the real project rather than asserting scaffold
194
+ * structure. Without --force, existing target files are skipped (never clobbered);
195
+ * with --force, they are overwritten.
196
+ */
197
+ async function runAgentsCommand(opts) {
198
+ const cwd = process.cwd()
199
+
200
+ // 1. Detect a Kywi project.
201
+ if (!existsSync(join(cwd, 'kywi.config.ts'))) {
202
+ stdout.write(
203
+ `\n✖ No kywi.config.ts here — run \`create-kywi-app agents\` inside a Kywi project (its root).\n`,
204
+ )
205
+ return 1
206
+ }
207
+
208
+ const projectName = readProjectName(cwd)
209
+ const { mode, detected } = detectMode(cwd)
210
+
211
+ stdout.write(`\nRefreshing agent guidance for ${projectName} (${mode} mode)…\n`)
212
+ if (!detected) {
213
+ stdout.write(` note: no deployment mode found in kywi.config.ts — assuming \`coupled\`.\n`)
214
+ }
215
+
216
+ // The guidance files depend only on projectName + mode (+ detected landmarks);
217
+ // the other answers fields are irrelevant here but kept shape-complete.
218
+ const answers = {
219
+ projectName,
220
+ mode,
221
+ dbProvider: 'postgresql',
222
+ authProviders: ['credentials'],
223
+ kywiVersion: KYWI_VERSION,
224
+ }
225
+ const landmarks = detectAgentsLandmarks(cwd)
226
+ const files = guidanceFileSet(answers, landmarks)
227
+
228
+ // 4. Write with safe semantics: without --force, skip existing files.
229
+ let created = 0
230
+ let updated = 0
231
+ let skipped = 0
232
+ for (const [rel, content] of Object.entries(files)) {
233
+ const abs = join(cwd, rel)
234
+ const exists = existsSync(abs)
235
+ if (exists && !opts.force) {
236
+ stdout.write(` skipped (exists): ${rel}\n`)
237
+ skipped++
238
+ continue
239
+ }
240
+ await mkdir(dirname(abs), { recursive: true })
241
+ await writeFile(abs, content, 'utf8')
242
+ if (exists) {
243
+ stdout.write(` updated: ${rel}\n`)
244
+ updated++
245
+ } else {
246
+ stdout.write(` created: ${rel}\n`)
247
+ created++
248
+ }
249
+ }
250
+
251
+ stdout.write(`\n✔ ${created} created, ${updated} updated, ${skipped} skipped.\n`)
252
+ if (skipped > 0) {
253
+ stdout.write(` Re-run with --force to overwrite the skipped file(s).\n`)
254
+ }
255
+ stdout.write(`\nAgents will pick these up automatically; see AGENTS.md.\n`)
256
+ return 0
257
+ }
258
+
149
259
  // ── Main ──────────────────────────────────────────────────────────────────────
150
260
 
151
261
  async function main() {
@@ -155,6 +265,12 @@ async function main() {
155
265
  return 0
156
266
  }
157
267
 
268
+ // Subcommand: `create-kywi-app agents [--force]` refreshes the agent-guidance
269
+ // files in an existing project instead of scaffolding a new one.
270
+ if (opts.positional[0] === 'agents') {
271
+ return runAgentsCommand(opts)
272
+ }
273
+
158
274
  let answers
159
275
  try {
160
276
  answers = await resolveAnswers(opts)
package/lib/templates.mjs CHANGED
@@ -32,6 +32,10 @@
32
32
  * ships session fixes without the app hand-maintaining crypto.
33
33
  */
34
34
 
35
+ import { readFileSync, existsSync } from 'node:fs'
36
+ import { dirname, join } from 'node:path'
37
+ import { fileURLToPath } from 'node:url'
38
+
35
39
  /** @typedef {{ projectName: string, dbProvider: 'postgresql'|'mysql', authProviders: string[], mode: 'coupled'|'headless'|'decoupled', kywiVersion: string }} Answers */
36
40
 
37
41
  const CORE_RANGE = (v) => `^${v}`
@@ -1903,6 +1907,11 @@ ${themingBlock}
1903
1907
 
1904
1908
  \`\`\`
1905
1909
  kywi.config.ts your config: sites, themes, content types, auth, mode, admin.features
1910
+ AGENTS.md guidance for AI agents working on this site (Kywi's building patterns)
1911
+ CLAUDE.md points AI agents to AGENTS.md
1912
+ .claude/skills/kywi-content-model/SKILL.md content-model skill (loaded automatically)
1913
+ .claude/skills/kywi-collections/SKILL.md collections skill (loaded automatically)
1914
+ .claude/skills/kywi-personalization/SKILL.md personalization skill (loaded automatically)
1906
1915
  middleware.ts auth gate + session refresh + cookie→bearer bridge
1907
1916
  next.config.mjs required Next config to consume @kywi-software/core
1908
1917
  lib/kywi.ts server runtime (DB, API handler, content scope)
@@ -1929,6 +1938,282 @@ function escapeJsxText(value) {
1929
1938
  return String(value).replace(/[{}<>]/g, (ch) => `{'${ch}'}`)
1930
1939
  }
1931
1940
 
1941
+ // ── Agent guidance (AGENTS.md / CLAUDE.md) ──────────────────────────────────────
1942
+
1943
+ /**
1944
+ * Kywi's canonical building-patterns doc, shipped as a package asset and embedded
1945
+ * verbatim into every generated app's AGENTS.md. Resolved relative to THIS module
1946
+ * (import.meta.url), NOT process.cwd(), so it loads from an installed
1947
+ * create-kywi-app the same as from the monorepo. Kept byte-identical to
1948
+ * docs/agents/AGENT-PATTERNS.md by scripts/sync-agent-patterns.mjs (a drift test
1949
+ * guards it). Cached so repeated buildFileSet() calls don't re-read the file.
1950
+ * @returns {string}
1951
+ */
1952
+ let _agentPatternsDoc
1953
+ function agentPatternsDoc() {
1954
+ if (_agentPatternsDoc === undefined) {
1955
+ const assetPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'assets', 'agent-patterns.md')
1956
+ _agentPatternsDoc = readFileSync(assetPath, 'utf8')
1957
+ }
1958
+ return _agentPatternsDoc
1959
+ }
1960
+
1961
+ /**
1962
+ * Kywi's Claude Code project skills — each ships as a package asset and is
1963
+ * embedded verbatim into every generated app's .claude/skills/<slug>/SKILL.md
1964
+ * (all modes, in this order). Exported so tests can iterate the same table
1965
+ * instead of duplicating it.
1966
+ * @type {Array<{ slug: string, asset: string }>}
1967
+ */
1968
+ export const SKILLS = [
1969
+ // Before building anything: design the content model, get owner sign-off.
1970
+ { slug: 'kywi-content-model', asset: 'kywi-content-model-skill.md' },
1971
+ // When adding any collection: folder + feed + Feed Display, end to end.
1972
+ { slug: 'kywi-collections', asset: 'kywi-collections-skill.md' },
1973
+ // When the owner wants personalization/A-B: confirm the use case, then build.
1974
+ { slug: 'kywi-personalization', asset: 'kywi-personalization-skill.md' },
1975
+ ]
1976
+
1977
+ /**
1978
+ * Cache of asset filename → file content for scaffolded Claude Code skills,
1979
+ * populated lazily by {@link skillDoc}.
1980
+ * @type {Map<string, string>}
1981
+ */
1982
+ const _skillDocCache = new Map()
1983
+
1984
+ /**
1985
+ * Read (and cache) one Claude Code skill's SKILL.md asset by filename.
1986
+ * Resolved relative to THIS module (import.meta.url), same mechanism as
1987
+ * {@link agentPatternsDoc}, so it loads from an installed create-kywi-app the
1988
+ * same as from the monorepo. Cached so repeated buildFileSet() calls don't
1989
+ * re-read the file.
1990
+ * @param {string} asset
1991
+ * @returns {string}
1992
+ */
1993
+ export function skillDoc(asset) {
1994
+ if (!_skillDocCache.has(asset)) {
1995
+ const assetPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'assets', asset)
1996
+ _skillDocCache.set(asset, readFileSync(assetPath, 'utf8'))
1997
+ }
1998
+ return _skillDocCache.get(asset)
1999
+ }
2000
+
2001
+ /**
2002
+ * Landmark → its relative path in a Kywi project. A "landmark" is a file whose
2003
+ * presence changes how the AGENTS.md header should describe the app. The scaffold
2004
+ * derives its set statically from the mode ({@link scaffoldLandmarks}); the
2005
+ * `agents` refresh command detects them on disk ({@link detectAgentsLandmarks}),
2006
+ * so the header reflects the REAL project instead of asserting scaffold structure.
2007
+ * One source of truth for the path strings, shared by both.
2008
+ * @type {Record<'adminHost'|'moduleMap'|'middleware'|'libSite'|'sitePage'|'headlessPage', string>}
2009
+ */
2010
+ export const AGENTS_LANDMARK_PATHS = {
2011
+ adminHost: 'app/admin/[[...admin]]/page.tsx',
2012
+ moduleMap: 'lib/modules.tsx',
2013
+ middleware: 'middleware.ts',
2014
+ libSite: 'lib/site.ts',
2015
+ sitePage: 'app/(site)/[[...slug]]/page.tsx',
2016
+ headlessPage: 'app/page.tsx',
2017
+ }
2018
+
2019
+ /**
2020
+ * @typedef {Object} AgentsLandmarks
2021
+ * @property {boolean} scaffolded Generating for a fresh scaffold (true) vs
2022
+ * refreshing into an existing project (false). Controls only the intro line's
2023
+ * "scaffolded by create-kywi-app" claim.
2024
+ * @property {boolean} adminHost app/admin/[[...admin]]/page.tsx present
2025
+ * @property {boolean} moduleMap lib/modules.tsx present
2026
+ * @property {boolean} middleware middleware.ts present
2027
+ * @property {boolean} libSite lib/site.ts present (the scaffold's public-render helpers)
2028
+ * @property {boolean} sitePage app/(site)/[[...slug]]/page.tsx present (renders public pages)
2029
+ * @property {boolean} headlessPage app/page.tsx present (the headless/decoupled 404 root)
2030
+ */
2031
+
2032
+ /**
2033
+ * The landmark set a FRESH scaffold of the given mode has — derived statically
2034
+ * from the mode, never from the filesystem, so buildFileSet emits AGENTS.md that
2035
+ * is byte-identical to the previous mode-branching implementation.
2036
+ * @param {'coupled'|'headless'|'decoupled'} mode
2037
+ * @returns {AgentsLandmarks}
2038
+ */
2039
+ export function scaffoldLandmarks(mode) {
2040
+ const coupled = mode === 'coupled'
2041
+ return {
2042
+ scaffolded: true,
2043
+ adminHost: true,
2044
+ moduleMap: true,
2045
+ middleware: true,
2046
+ libSite: coupled,
2047
+ sitePage: coupled,
2048
+ headlessPage: !coupled,
2049
+ }
2050
+ }
2051
+
2052
+ /**
2053
+ * Detect the AGENTS.md landmarks that actually exist in an EXISTING project, so
2054
+ * `create-kywi-app agents` writes a header that matches the app on disk (a
2055
+ * hand-built app may render the public site from the page directly and predate
2056
+ * the scaffold's lib/site.ts helper or lib/modules.tsx map).
2057
+ * @param {string} projectDir absolute path to the project root
2058
+ * @returns {AgentsLandmarks}
2059
+ */
2060
+ export function detectAgentsLandmarks(projectDir) {
2061
+ const has = (rel) => existsSync(join(projectDir, rel))
2062
+ return {
2063
+ scaffolded: false,
2064
+ adminHost: has(AGENTS_LANDMARK_PATHS.adminHost),
2065
+ moduleMap: has(AGENTS_LANDMARK_PATHS.moduleMap),
2066
+ middleware: has(AGENTS_LANDMARK_PATHS.middleware),
2067
+ libSite: has(AGENTS_LANDMARK_PATHS.libSite),
2068
+ sitePage: has(AGENTS_LANDMARK_PATHS.sitePage),
2069
+ headlessPage: has(AGENTS_LANDMARK_PATHS.headlessPage),
2070
+ }
2071
+ }
2072
+
2073
+ /**
2074
+ * AGENTS.md — a short, app-specific header that orients an agent in THIS app,
2075
+ * followed by Kywi's canonical patterns doc verbatim. Landmark-aware: each "Where
2076
+ * things live" bullet is emitted only for a landmark that is actually present, and
2077
+ * absent public-render (lib/site.ts) or module-map (lib/modules.tsx) wiring becomes
2078
+ * an honest "this app predates the scaffold's …" line instead of a bullet that
2079
+ * asserts a file the project does not have. With no landmarks passed it defaults to
2080
+ * the fresh-scaffold set for the mode, so the scaffold output is unchanged.
2081
+ * @param {Answers} a
2082
+ * @param {AgentsLandmarks} [landmarks]
2083
+ */
2084
+ export function agentsMd(a, landmarks = scaffoldLandmarks(a.mode)) {
2085
+ const L = landmarks
2086
+ const modeSentence =
2087
+ a.mode === 'coupled'
2088
+ ? 'This app renders the public site AND serves the admin + API.'
2089
+ : a.mode === 'headless'
2090
+ ? 'This app serves the admin + API only (`GET /` returns 404); there is no public rendering here.'
2091
+ : 'This app serves the admin + API only; a separate frontend consumes the API via `@kywi-software/sdk`. There is no public rendering here.'
2092
+
2093
+ // Existing projects aren't necessarily scaffolded — don't assert they were.
2094
+ const intro = L.scaffolded
2095
+ ? `This is a **Kywi CMS** project scaffolded by \`create-kywi-app\` (\`${a.mode}\` mode).`
2096
+ : `This is a **Kywi CMS** project (\`${a.mode}\` mode).`
2097
+
2098
+ // "Where things live" — one bullet per PRESENT landmark, in a fixed order.
2099
+ const bullets = []
2100
+ bullets.push(`- \`kywi.config.ts\` — project config: sites, themes, content types, auth
2101
+ providers, deployment mode, and \`admin.features\`. Edit it, then re-run
2102
+ \`pnpm migrate\`.`)
2103
+ if (L.adminHost) {
2104
+ bullets.push(`- \`app/admin/[[...admin]]/page.tsx\` — mounts Kywi's **full admin**
2105
+ (\`KywiAdminApp\`) at **\`/admin\`**. Every surface — content, media, feeds,
2106
+ forms, audiences, settings, … — is already there; never hand-build admin pages.`)
2107
+ }
2108
+ if (L.moduleMap) {
2109
+ bullets.push(`- \`lib/modules.tsx\` — the custom-module map (\`defineModule\` renderers), shared
2110
+ by the admin editor and the public site.`)
2111
+ } else {
2112
+ bullets.push(`- No \`lib/modules.tsx\` custom-module map — this app predates the scaffold's
2113
+ \`defineModule\` module map (shared by the admin editor and the public site); see
2114
+ the patterns doc below for the intended shape.`)
2115
+ }
2116
+ // Public-render surface: the scaffold's lib/site.ts helper, else a page that
2117
+ // renders publicly without it (honest note), else no public rendering at all.
2118
+ if (L.libSite) {
2119
+ bullets.push(`- \`lib/site.ts\` — public-render helpers (path/locale resolution, feeds,
2120
+ components, personalization) used by \`app/(site)/[[...slug]]/page.tsx\`, which
2121
+ renders every published page at its slug.`)
2122
+ } else if (L.sitePage) {
2123
+ bullets.push(`- \`app/(site)/[[...slug]]/page.tsx\` — renders every published page at its
2124
+ slug. This app predates the scaffold's \`lib/site.ts\` public-render helpers
2125
+ (path/locale resolution, feeds, components, personalization); see the patterns
2126
+ doc below for the intended shape.`)
2127
+ } else {
2128
+ bullets.push(
2129
+ a.mode === 'decoupled'
2130
+ ? `- No public rendering in this mode — \`app/page.tsx\` returns 404. Build a
2131
+ separate frontend against the REST API at \`/api/v1\` with \`@kywi-software/sdk\`.`
2132
+ : `- No public rendering in this mode — \`app/page.tsx\` returns 404. Content is
2133
+ served over the REST API at \`/api/v1\`.`,
2134
+ )
2135
+ }
2136
+ if (L.middleware) {
2137
+ bullets.push(`- \`middleware.ts\` — auth gate + session refresh, thin wiring over
2138
+ \`@kywi-software/core/host\`.`)
2139
+ }
2140
+ bullets.push(`- \`.claude/skills/kywi-content-model/SKILL.md\` — content-model planning
2141
+ skill, loaded automatically before building anything.`)
2142
+ bullets.push(`- \`.claude/skills/kywi-collections/SKILL.md\` — collections skill, loaded
2143
+ automatically when adding any collection.`)
2144
+ bullets.push(`- \`.claude/skills/kywi-personalization/SKILL.md\` — personalization skill,
2145
+ loaded automatically when the owner wants personalization or A/B testing.`)
2146
+
2147
+ const header = `# Agent guide — ${a.projectName}
2148
+
2149
+ ${intro}
2150
+ ${modeSentence}
2151
+
2152
+ ## Where things live
2153
+
2154
+ ${bullets.join('\n')}
2155
+
2156
+ ## Running it
2157
+
2158
+ \`\`\`bash
2159
+ pnpm migrate # apply the schema
2160
+ pnpm seed # create the default site + a superadmin (prints credentials)
2161
+ pnpm dev # http://localhost:3000 (admin at /admin)
2162
+ \`\`\`
2163
+
2164
+ ## Kywi's building patterns
2165
+
2166
+ Everything below is **Kywi's official guidance** for building sites end users can
2167
+ maintain — read it before adding content, pages, forms, or modules. The rule that
2168
+ matters most: **if a non-developer might ever want to change it, model it in the
2169
+ CMS instead of hardcoding it.**
2170
+
2171
+ ---
2172
+
2173
+ `
2174
+ return header + agentPatternsDoc()
2175
+ }
2176
+
2177
+ /**
2178
+ * CLAUDE.md — a thin pointer so a Claude Code session reads AGENTS.md first.
2179
+ * @param {Answers} a
2180
+ */
2181
+ export function claudeMd(a) {
2182
+ return `# ${a.projectName}
2183
+
2184
+ This is a **Kywi CMS** project. **Read \`AGENTS.md\` before building anything** — it
2185
+ holds Kywi's official building patterns and a map of where things live in this app.
2186
+
2187
+ Core principle: **model content in the CMS instead of hardcoding it** — see \`AGENTS.md\`.
2188
+
2189
+ Before building out a new content area, use the \`kywi-content-model\` skill (design the model, get sign-off) — skills in \`.claude/skills/\` also cover collections and personalization.
2190
+ `
2191
+ }
2192
+
2193
+ /**
2194
+ * The agent-guidance files every Kywi app should carry: AGENTS.md, an app-specific
2195
+ * header + Kywi's canonical patterns doc; CLAUDE.md, a thin pointer into it; and
2196
+ * the three Claude Code project skills, verbatim assets. ONE source of truth for
2197
+ * "which files are the agent guidance", shared by {@link buildFileSet} (fresh
2198
+ * scaffold, {@link scaffoldLandmarks}) and the `agents` refresh command
2199
+ * ({@link detectAgentsLandmarks}) — so both emit identical content for identical
2200
+ * inputs, and adding/renaming a skill in {@link SKILLS} updates both at once.
2201
+ * @param {Answers} a
2202
+ * @param {AgentsLandmarks} [landmarks]
2203
+ * @returns {Record<string, string>}
2204
+ */
2205
+ export function guidanceFileSet(a, landmarks = scaffoldLandmarks(a.mode)) {
2206
+ /** @type {Record<string, string>} */
2207
+ const files = {
2208
+ 'AGENTS.md': agentsMd(a, landmarks),
2209
+ 'CLAUDE.md': claudeMd(a),
2210
+ }
2211
+ for (const { slug, asset } of SKILLS) {
2212
+ files[`.claude/skills/${slug}/SKILL.md`] = skillDoc(asset)
2213
+ }
2214
+ return files
2215
+ }
2216
+
1932
2217
  /**
1933
2218
  * Build the complete map of relative-path → file-content for a project.
1934
2219
  * @param {Answers} answers
@@ -1945,6 +2230,12 @@ export function buildFileSet(answers) {
1945
2230
  '.env.example': envExample(),
1946
2231
  '.gitignore': gitignore(),
1947
2232
  'README.md': readme(answers),
2233
+ // agent guidance (every mode): AGENTS.md (app-specific header + Kywi's
2234
+ // canonical patterns doc verbatim), CLAUDE.md (thin pointer into it), and the
2235
+ // three Claude Code project skills. Landmarks are derived statically from the
2236
+ // mode so this stays byte-identical to the pre-landmark implementation; the
2237
+ // same guidanceFileSet powers `create-kywi-app agents` for existing projects.
2238
+ ...guidanceFileSet(answers, scaffoldLandmarks(answers.mode)),
1948
2239
  // server runtime + config
1949
2240
  'lib/kywi.ts': libKywi(),
1950
2241
  'lib/config.ts': libConfig(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kywi-app",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "Scaffold a new Kywi CMS project — npx create-kywi-app my-site",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/Kywi-Software/kywi-cms#readme",
@@ -15,6 +15,7 @@
15
15
  "files": [
16
16
  "bin",
17
17
  "lib",
18
+ "assets",
18
19
  "README.md",
19
20
  "LICENSE"
20
21
  ],