@topy-ai/maggie 0.7.33 → 0.7.35

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.
Files changed (138) hide show
  1. package/README-zh-TW.md +2 -2
  2. package/README.md +16 -3
  3. package/bundled-skills/maggie-blog-bootstrap/SKILL.md +8 -0
  4. package/bundled-skills/maggie-clone/SKILL.md +7 -1
  5. package/bundled-templates/astro-blog/README.md +217 -0
  6. package/bundled-templates/astro-blog/examples/blog.config.ts +30 -0
  7. package/bundled-templates/astro-blog/examples/content.config.ts +28 -0
  8. package/bundled-templates/astro-blog/examples/metadata.ts +37 -0
  9. package/bundled-templates/astro-blog/examples/ops-api.ts +31 -0
  10. package/bundled-templates/astro-blog/examples/route-manifest.ts +21 -0
  11. package/bundled-templates/astro-blog/starter/.env.example +36 -0
  12. package/bundled-templates/astro-blog/starter/README.md +168 -0
  13. package/bundled-templates/astro-blog/starter/astro.config.mjs +19 -0
  14. package/bundled-templates/astro-blog/starter/db/schema.sql +508 -0
  15. package/bundled-templates/astro-blog/starter/package-lock.json +8039 -0
  16. package/bundled-templates/astro-blog/starter/package.json +41 -0
  17. package/bundled-templates/astro-blog/starter/scripts/contract-check.mjs +16 -0
  18. package/bundled-templates/astro-blog/starter/scripts/fixture-seed.mjs +11 -0
  19. package/bundled-templates/astro-blog/starter/scripts/schema-check.mjs +13 -0
  20. package/bundled-templates/astro-blog/starter/scripts/seed-db.mjs +20 -0
  21. package/bundled-templates/astro-blog/starter/src/data/demo.ts +52 -0
  22. package/bundled-templates/astro-blog/starter/src/layouts/BaseLayout.astro +53 -0
  23. package/bundled-templates/astro-blog/starter/src/layouts/OpsLayout.astro +73 -0
  24. package/bundled-templates/astro-blog/starter/src/lib/ai-cmo.ts +14 -0
  25. package/bundled-templates/astro-blog/starter/src/lib/config.ts +15 -0
  26. package/bundled-templates/astro-blog/starter/src/lib/db.ts +95 -0
  27. package/bundled-templates/astro-blog/starter/src/lib/google.ts +28 -0
  28. package/bundled-templates/astro-blog/starter/src/lib/media.ts +12 -0
  29. package/bundled-templates/astro-blog/starter/src/lib/migration-validation.ts +11 -0
  30. package/bundled-templates/astro-blog/starter/src/lib/migration.ts +21 -0
  31. package/bundled-templates/astro-blog/starter/src/lib/ops-auth.ts +21 -0
  32. package/bundled-templates/astro-blog/starter/src/lib/providers/email.ts +45 -0
  33. package/bundled-templates/astro-blog/starter/src/lib/providers/media-storage.ts +37 -0
  34. package/bundled-templates/astro-blog/starter/src/lib/report-delivery.ts +26 -0
  35. package/bundled-templates/astro-blog/starter/src/lib/report-pdf.ts +49 -0
  36. package/bundled-templates/astro-blog/starter/src/lib/repository.ts +72 -0
  37. package/bundled-templates/astro-blog/starter/src/lib/seo-audit.ts +72 -0
  38. package/bundled-templates/astro-blog/starter/src/lib/seo-insights.ts +14 -0
  39. package/bundled-templates/astro-blog/starter/src/lib/seo.ts +33 -0
  40. package/bundled-templates/astro-blog/starter/src/lib/types.ts +58 -0
  41. package/bundled-templates/astro-blog/starter/src/lib/wordpress.ts +847 -0
  42. package/bundled-templates/astro-blog/starter/src/middleware.ts +7 -0
  43. package/bundled-templates/astro-blog/starter/src/pages/404.astro +11 -0
  44. package/bundled-templates/astro-blog/starter/src/pages/about.astro +15 -0
  45. package/bundled-templates/astro-blog/starter/src/pages/api/approval/[token].ts +25 -0
  46. package/bundled-templates/astro-blog/starter/src/pages/api/consent.ts +8 -0
  47. package/bundled-templates/astro-blog/starter/src/pages/api/ops/agency.ts +47 -0
  48. package/bundled-templates/astro-blog/starter/src/pages/api/ops/authors.ts +11 -0
  49. package/bundled-templates/astro-blog/starter/src/pages/api/ops/bulk.ts +58 -0
  50. package/bundled-templates/astro-blog/starter/src/pages/api/ops/calendar/publish-due.ts +12 -0
  51. package/bundled-templates/astro-blog/starter/src/pages/api/ops/config/validate.ts +4 -0
  52. package/bundled-templates/astro-blog/starter/src/pages/api/ops/content-tracking/report-state/batch.ts +19 -0
  53. package/bundled-templates/astro-blog/starter/src/pages/api/ops/content-tracking/report-state.ts +15 -0
  54. package/bundled-templates/astro-blog/starter/src/pages/api/ops/ctas.ts +5 -0
  55. package/bundled-templates/astro-blog/starter/src/pages/api/ops/entities.ts +23 -0
  56. package/bundled-templates/astro-blog/starter/src/pages/api/ops/media/optimize.ts +5 -0
  57. package/bundled-templates/astro-blog/starter/src/pages/api/ops/media/upload.ts +30 -0
  58. package/bundled-templates/astro-blog/starter/src/pages/api/ops/migrations/[id]/resume.ts +28 -0
  59. package/bundled-templates/astro-blog/starter/src/pages/api/ops/migrations/[id]/validate.ts +6 -0
  60. package/bundled-templates/astro-blog/starter/src/pages/api/ops/migrations/[id].ts +7 -0
  61. package/bundled-templates/astro-blog/starter/src/pages/api/ops/migrations/import.ts +458 -0
  62. package/bundled-templates/astro-blog/starter/src/pages/api/ops/migrations/media.ts +5 -0
  63. package/bundled-templates/astro-blog/starter/src/pages/api/ops/operations.ts +189 -0
  64. package/bundled-templates/astro-blog/starter/src/pages/api/ops/playbooks/[id]/run.ts +55 -0
  65. package/bundled-templates/astro-blog/starter/src/pages/api/ops/posts/[id]/approve.ts +13 -0
  66. package/bundled-templates/astro-blog/starter/src/pages/api/ops/posts/[id]/preview.ts +9 -0
  67. package/bundled-templates/astro-blog/starter/src/pages/api/ops/posts/[id]/publish.ts +13 -0
  68. package/bundled-templates/astro-blog/starter/src/pages/api/ops/posts/[id].ts +18 -0
  69. package/bundled-templates/astro-blog/starter/src/pages/api/ops/posts/index.ts +28 -0
  70. package/bundled-templates/astro-blog/starter/src/pages/api/ops/providers.ts +6 -0
  71. package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/history.ts +3 -0
  72. package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/latest.ts +3 -0
  73. package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/posts.ts +3 -0
  74. package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/project-context.ts +4 -0
  75. package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/sync-updates.ts +43 -0
  76. package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/sync.ts +172 -0
  77. package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/updates.ts +3 -0
  78. package/bundled-templates/astro-blog/starter/src/pages/api/ops/redirects/check.ts +5 -0
  79. package/bundled-templates/astro-blog/starter/src/pages/api/ops/redirects.ts +32 -0
  80. package/bundled-templates/astro-blog/starter/src/pages/api/ops/reports/deliver-due.ts +12 -0
  81. package/bundled-templates/astro-blog/starter/src/pages/api/ops/reports/deliver.ts +6 -0
  82. package/bundled-templates/astro-blog/starter/src/pages/api/ops/reports/export.ts +17 -0
  83. package/bundled-templates/astro-blog/starter/src/pages/api/ops/reports.ts +31 -0
  84. package/bundled-templates/astro-blog/starter/src/pages/api/ops/rewrite/history/[contentId].ts +3 -0
  85. package/bundled-templates/astro-blog/starter/src/pages/api/ops/rewrite/policy.ts +4 -0
  86. package/bundled-templates/astro-blog/starter/src/pages/api/ops/rewrite/queue.ts +4 -0
  87. package/bundled-templates/astro-blog/starter/src/pages/api/ops/seo/audit.ts +7 -0
  88. package/bundled-templates/astro-blog/starter/src/pages/api/ops/seo/insights.ts +5 -0
  89. package/bundled-templates/astro-blog/starter/src/pages/api/ops/settings/site.ts +7 -0
  90. package/bundled-templates/astro-blog/starter/src/pages/api/ops/settings.ts +19 -0
  91. package/bundled-templates/astro-blog/starter/src/pages/api/ops/sitemap/auto-detect.ts +13 -0
  92. package/bundled-templates/astro-blog/starter/src/pages/api/ops/sitemap/diff.ts +11 -0
  93. package/bundled-templates/astro-blog/starter/src/pages/api/ops/sitemap/match.ts +16 -0
  94. package/bundled-templates/astro-blog/starter/src/pages/api/ops/sitemap/matching-history.ts +4 -0
  95. package/bundled-templates/astro-blog/starter/src/pages/api/ops/sitemap/runs.ts +4 -0
  96. package/bundled-templates/astro-blog/starter/src/pages/api/ops/sitemap.ts +11 -0
  97. package/bundled-templates/astro-blog/starter/src/pages/api/ops/summary.ts +12 -0
  98. package/bundled-templates/astro-blog/starter/src/pages/api/ops/topics/[id]/faq.ts +12 -0
  99. package/bundled-templates/astro-blog/starter/src/pages/api/ops/topics.ts +11 -0
  100. package/bundled-templates/astro-blog/starter/src/pages/api/ops/wordpress/capabilities.ts +5 -0
  101. package/bundled-templates/astro-blog/starter/src/pages/api/ops/wordpress/migration-plan.ts +21 -0
  102. package/bundled-templates/astro-blog/starter/src/pages/api/ops/wordpress/migrations/[id]/resume.ts +5 -0
  103. package/bundled-templates/astro-blog/starter/src/pages/api/ops/wordpress/migrations/[id]/validate.ts +6 -0
  104. package/bundled-templates/astro-blog/starter/src/pages/api/ops/wordpress/migrations/[id].ts +13 -0
  105. package/bundled-templates/astro-blog/starter/src/pages/api/ops/wordpress/migrations.ts +147 -0
  106. package/bundled-templates/astro-blog/starter/src/pages/authors/[slug].astro +12 -0
  107. package/bundled-templates/astro-blog/starter/src/pages/blog/[slug].astro +25 -0
  108. package/bundled-templates/astro-blog/starter/src/pages/blog/index.astro +32 -0
  109. package/bundled-templates/astro-blog/starter/src/pages/blog/page/[page].astro +21 -0
  110. package/bundled-templates/astro-blog/starter/src/pages/ops/index.astro +20 -0
  111. package/bundled-templates/astro-blog/starter/src/pages/ops/operations.astro +33 -0
  112. package/bundled-templates/astro-blog/starter/src/pages/ops/posts/[id]/edit.astro +17 -0
  113. package/bundled-templates/astro-blog/starter/src/pages/ops/posts/[id]/preview.astro +11 -0
  114. package/bundled-templates/astro-blog/starter/src/pages/ops/posts/index.astro +15 -0
  115. package/bundled-templates/astro-blog/starter/src/pages/ops/posts/new.astro +12 -0
  116. package/bundled-templates/astro-blog/starter/src/pages/ops/reports.astro +12 -0
  117. package/bundled-templates/astro-blog/starter/src/pages/ops/rewrite.astro +13 -0
  118. package/bundled-templates/astro-blog/starter/src/pages/ops/settings/integrations.astro +13 -0
  119. package/bundled-templates/astro-blog/starter/src/pages/ops/settings/site.astro +11 -0
  120. package/bundled-templates/astro-blog/starter/src/pages/ops/sitemap.astro +15 -0
  121. package/bundled-templates/astro-blog/starter/src/pages/ops/topics.astro +13 -0
  122. package/bundled-templates/astro-blog/starter/src/pages/ops/wordpress.astro +34 -0
  123. package/bundled-templates/astro-blog/starter/src/pages/review/[token].astro +13 -0
  124. package/bundled-templates/astro-blog/starter/src/pages/robots.txt.ts +4 -0
  125. package/bundled-templates/astro-blog/starter/src/pages/sitemap.xml.ts +12 -0
  126. package/bundled-templates/astro-blog/starter/src/pages/topics/[slug]/page/[page].astro +10 -0
  127. package/bundled-templates/astro-blog/starter/src/pages/topics/[slug].astro +19 -0
  128. package/bundled-templates/astro-blog/starter/src/pages/topics/index.astro +10 -0
  129. package/bundled-templates/astro-blog/starter/src/styles/global.css +4 -0
  130. package/bundled-templates/astro-blog/starter/tsconfig.json +9 -0
  131. package/bundled-templates/astro-blog/starter/wrangler.jsonc +52 -0
  132. package/bundled-tools/clis/maggie_clone.py +42 -15
  133. package/bundled-tools/clis/maggie_dash.py +41 -25
  134. package/bundled-tools/clis/maggie_design.py +2 -0
  135. package/bundled-tools/clis/maggie_design_system.py +7 -1
  136. package/bundled-tools/clis/site_audit.py +25 -2
  137. package/bundled-tools/runtime/maggie_memory.py +6 -1
  138. package/package.json +1 -1
package/README-zh-TW.md CHANGED
@@ -8,7 +8,7 @@ Codex、Claude Code 與相容的 coding agents。
8
8
  ## 安裝
9
9
 
10
10
  ```bash
11
- npx @topy-ai/maggie@0.7.33 init --agent all
11
+ npx @topy-ai/maggie@0.7.35 init --agent all
12
12
  npx @topy-ai/maggie doctor --project .
13
13
  ```
14
14
 
@@ -25,7 +25,7 @@ maggie doctor --project . --require-bootstrap --strict
25
25
  deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
26
26
  publish 與 production deployment 需要明確確認。
27
27
 
28
- 目前 release 是 `0.7.33`,記錄並審查一筆更正回饋;確認 0.7.32 已正確處理 batch index,沒有修改已驗證的 shared behavior。0.7.32 加入 feedback 一基索引支援、MaggieDash panel 的 source/freshness/error evidence、Consent Mode 與實際 write/no-op reconciliation、SEO baseline recapture、sitemap origin rebasing、content-diff scope、migration ledger reconciliation、deployment credential preflight、release provenance/runtime preflight、icon release gate、opt-in blog auto-publish gate,以及 least-privilege VPS deployer。也修復 top-level `maggie feedback` dispatcher parity。0.7.31 加入 update 差異摘要、atomic installer copy、subset install manifest merge,以及 inventory drift 時 doctor non-zero。0.7.30 補上 feedback tracking issue closeout gate 與 batch 14/15 audit ledger。0.7.29 修正 npm package landing page 使用英文 README;0.7.28 加入 versioned Gemini model policy、明確 fallback 與 provenance、bundled-first CLI
28
+ 目前 release 是 `0.7.35`,完成下一批已驗證 feedback:production fixture isolation、MaggieDash project-id inference、responsive/background asset extraction、穩定 SEO structure hash、decorative alt、明確 site URL/build validation、真正 404、Tailwind v4 starter、portable CLI scripts,以及不含 local dependencies/cache 的 Astro starter bundle。延後功能記錄在 issue #38,不會直接成為 active memory。0.7.33 記錄並審查一筆更正回饋;確認 0.7.32 已正確處理 batch index,沒有修改已驗證的 shared behavior。0.7.32 加入 feedback 一基索引支援、MaggieDash panel 的 source/freshness/error evidence、Consent Mode 與實際 write/no-op reconciliation、SEO baseline recapture、sitemap origin rebasing、content-diff scope、migration ledger reconciliation、deployment credential preflight、release provenance/runtime preflight、icon release gate、opt-in blog auto-publish gate,以及 least-privilege VPS deployer。也修復 top-level `maggie feedback` dispatcher parity。0.7.31 加入 update 差異摘要、atomic installer copy、subset install manifest merge,以及 inventory drift 時 doctor non-zero。0.7.30 補上 feedback tracking issue closeout gate 與 batch 14/15 audit ledger。0.7.29 修正 npm package landing page 使用英文 README;0.7.28 加入 versioned Gemini model policy、明確 fallback 與 provenance、bundled-first CLI
29
29
  dispatch、`maggie --version`、feedback batch review 聚合與重複偵測、marketplace enrichment evidence、booking
30
30
  worker/resolver evidence,以及 read-only migration preflight。它也包含 host-owned mobile app surface contract、signed-in
31
31
  camera-state QA、直接 Astro route resolution、correlated feedback batch,以及
package/README.md CHANGED
@@ -60,7 +60,16 @@ Maggie keeps the existing project foundation and asks for decisions before
60
60
  shared routes, analytics, or publishing boundaries change. The current
61
61
  package ships 19 installable skills and a local-first MaggieDash foundation.
62
62
 
63
- The current release is `0.7.33`. It records the reviewed correction for the
63
+ The current release is `0.7.35`. It closes the next validated feedback batch:
64
+ production demo fixtures are development-only, Dash infers the persisted
65
+ project id, clone asset extraction includes responsive/background assets, SEO
66
+ structure hashes ignore content-hashed asset names, decorative empty alt text
67
+ is accepted, and missing starter routes return a real 404. It also adds a
68
+ Tailwind v4 starter entry point, explicit `PUBLIC_SITE_URL` validation, a
69
+ portable installed-CLI script path, and a bundled Astro starter without local
70
+ dependencies or build caches. Deferred feature requests are tracked in
71
+ [issue #38](https://github.com/TOPY-AI-LTD/ai-cmo-skills/issues/38), not active
72
+ memory. The 0.7.33 release records the reviewed correction for the
64
73
  batch-index feedback workflow while keeping the verified 0.7.32 behavior
65
74
  unchanged. The 0.7.32 release added explicit one-based feedback batch index
66
75
  support, source/freshness/error evidence for MaggieDash measurement
@@ -500,8 +509,8 @@ artifact schemas.
500
509
  Recommended upgrade sequence for the current release:
501
510
 
502
511
  ```bash
503
- npx @topy-ai/maggie@0.7.33 update --project . --force
504
- npx @topy-ai/maggie@0.7.33 cleanup --project .
512
+ npx @topy-ai/maggie@0.7.35 update --project . --force
513
+ npx @topy-ai/maggie@0.7.35 cleanup --project .
505
514
  ```
506
515
 
507
516
  Maintainers should pass npm credentials through the repository helper, never
@@ -774,6 +783,10 @@ report. It fails closed when source evidence is missing unless
774
783
  The marketplace catalog is lightweight and templates are fetched on demand.
775
784
  These desktop previews are hosted by the public NoBlox template registry so
776
785
  the npm package does not download every template asset during installation.
786
+ The package also includes the small, dependency-free Astro blog starter under
787
+ `bundled-templates/astro-blog/starter`; run `npm install` in the copied starter
788
+ and configure `PUBLIC_SITE_URL` before building. Its Tailwind v4 entry point is
789
+ opt-in at the host project level, and production never falls back to demo posts.
777
790
 
778
791
  <table>
779
792
  <tr>
@@ -68,6 +68,14 @@ response fields. It does not accept a static source scan as runtime evidence.
68
68
 
69
69
  - Preserve detected framework, language, router, UI system, icon set, fonts,
70
70
  database, and deployment target unless the user confirms a change.
71
+ - The bundled Astro starter is SQLite-first. A PostgreSQL or MySQL selection is
72
+ only a host adapter decision until the project provides and verifies its
73
+ repository/migration adapter; never report the starter's SQLite store as a
74
+ working PostgreSQL adapter.
75
+ - An empty directory still needs an approved host framework scaffold. Do not
76
+ pretend that installing Maggie skills alone creates an Astro/Next.js app;
77
+ use the bundled starter or the host framework's official scaffold, then run
78
+ the interview against that project.
71
79
  - Store no credentials in source or `.maggie` reports. API keys stay server
72
80
  side and are never inferred from a domain.
73
81
  - Traditional email/password auth uses the MaggieDash auth contract; passkeys,
@@ -152,7 +152,8 @@ interaction states, and component specs. Capture desktop (1440px), tablet
152
152
  - exact visible text, links, labels, alt text, forms, and public content;
153
153
  - fonts, weights, colors, spacing, breakpoints, radii, shadows, gradients,
154
154
  borders, and responsive layout changes from computed styles;
155
- - every image, video, SVG, favicon, font, background image, and layered asset;
155
+ - every image, responsive `srcset` candidate, CSS/inline background, video,
156
+ SVG, favicon, font, and layered asset;
156
157
  - interaction models: static, click-driven, hover-driven, scroll-driven,
157
158
  intersection-observer-driven, keyboard, or time-driven;
158
159
  - loading, empty, error, open, active, hover, and scrolled states where
@@ -164,6 +165,11 @@ Do not click first when a section may be scroll-driven. Scroll slowly, then
164
165
  test clicks and hovers. For tabs or pills, capture every state. For headers or
165
166
  sticky elements, record before/after computed values and the trigger.
166
167
 
168
+ Headless browser tabs can throttle timers in the background. Wait for
169
+ Lenis/ScrollTrigger-style smooth scrolling and animations to settle before
170
+ capturing; record the settle method, such as a stable `animation.currentTime`,
171
+ in the evidence notes.
172
+
167
173
  Persist the evidence before building:
168
174
 
169
175
  ```text
@@ -0,0 +1,217 @@
1
+ # Astro Blog Template Contract
2
+
3
+ This is a Maggie implementation contract for an existing Astro project. It is
4
+ not a second starter app: inspect the host project first, preserve its layout,
5
+ styling, deployment adapter, and content source, then add only the missing blog
6
+ surfaces.
7
+
8
+ Astro is the preferred template for content-heavy sites. Use Content
9
+ Collections for local or build-time content, and use a live loader or server
10
+ endpoint only when the product requires request-time freshness. Astro defaults
11
+ to static output and hydrates interactive components as islands, so keep the
12
+ blog reading path mostly server-rendered HTML.
13
+
14
+ See the shared [blog implementation contract](../../bundled-references/blog-implementation.md)
15
+ and [canonical data contract](../../bundled-references/blog-data-contract.md) for the
16
+ stable backend model. Use the [technical SEO contract](../../bundled-references/seo-technical-contract.md)
17
+ for behaviour and acceptance criteria. For AI CMO-specific work, read the
18
+ [Maggie API Pull guide](../../bundled-tools/integrations/maggie-api-pull.md) and
19
+ [Maggie Project Context guide](../../bundled-tools/integrations/maggie-project-context.md).
20
+ The complete public-page and private-operations surface is defined in the
21
+ [AI-native blog application contract](../../bundled-references/ai-native-blog-contract.md).
22
+ The daily operations data model and API are defined in the
23
+ [blog operations contract](../../bundled-references/blog-operations-contract.md).
24
+ For deployment, use the [Maggie Deployment skill](../../bundled-skills/maggie-deployment/SKILL.md);
25
+ the starter includes a Cloudflare Workers target with D1, R2, KV, and Wrangler
26
+ configuration. Keep Node/SQLite for local development only until the D1
27
+ repository boundary is selected and verified.
28
+
29
+ ## Expected mapping
30
+
31
+ Adapt names to the host project, but preserve these responsibilities:
32
+
33
+ ```text
34
+ src/content.config.ts typed post collection and schema
35
+ src/content/posts/ local Markdown/MDX posts, if used
36
+ src/pages/posts/index.astro crawlable post list
37
+ src/pages/posts/[...slug].astro post detail and 404 handling
38
+ src/pages/sitemap-index.xml.ts sitemap index when multiple sitemaps exist
39
+ src/pages/sitemap-0.xml.ts published post URLs and lastmod
40
+ src/pages/robots.txt.ts crawler rules and sitemap reference
41
+ src/layouts/PostLayout.astro metadata, JSON-LD, and article shell
42
+ src/lib/posts.ts source adapter and read model
43
+ src/components/analytics/GA4.astro consent-aware analytics hook
44
+ ```
45
+
46
+ The [`examples/`](examples/) directory contains a Content Collections schema,
47
+ typed config, route manifest, metadata builder, and Ops transition guard. Treat
48
+ them as typed starting points, not permission to change the canonical field
49
+ names.
50
+
51
+ The actual sitemap route may be provided by an Astro integration. Do not
52
+ create duplicate sitemap routes; verify the generated response instead.
53
+
54
+ The default list configuration is `postsPerPage: 9` and `gridColumns: 3`,
55
+ rendering a 3×3 grid. The starter exposes these as
56
+ `PUBLIC_POSTS_PER_PAGE` and `PUBLIC_GRID_COLUMNS`; operators can change them
57
+ later through the typed site-settings boundary.
58
+
59
+ Keep these stable: the post model, publication state, source identity, URL
60
+ rules, metadata fields, JSON-LD semantics, sitemap eligibility, and sync/error
61
+ state. Let the agent choose the visual layout, component composition, colors,
62
+ spacing, and interaction details after it has detected the existing design
63
+ system.
64
+
65
+ The Maggie Ops starter uses a CMS-style operator journey: persistent workspace
66
+ navigation, a compact account bar, dashboard-first summary metrics, quick next
67
+ actions, visible health status, and responsive navigation on small screens. It
68
+ keeps Maggie's content, API Pull, sitemap, rewrite, SEO/GEO, and agency flows
69
+ inside this shell without depending on another CMS.
70
+
71
+ ## Required frontend surface
72
+
73
+ Implement the public information architecture from the shared application
74
+ contract:
75
+
76
+ ```text
77
+ /blog title, description, hot topics, grid, FAQs, CTA, pagination
78
+ /blog/[slug] EEAT authorship, dates, content, related posts, FAQs, CTA
79
+ /topics topic index
80
+ /topics/[topic-slug] topic title, description, post grid, FAQs, CTA, pagination
81
+ /authors/[author-slug] authorship and published posts, when enabled
82
+ /sitemap.xml published canonical URLs only
83
+ /robots.txt crawler policy and sitemap reference
84
+ ```
85
+
86
+ The agent may design the header, cards, navigation, typography, color system,
87
+ responsive layout, and progressive enhancement. It must use the shared
88
+ repository functions for publication filtering and the shared metadata builder
89
+ for SEO output.
90
+
91
+ ## Maggie Ops surface
92
+
93
+ Bootstrap must ship the complete private dashboard described in the shared
94
+ [Ops dashboard contract](../../bundled-references/ops-dashboard-contract.md). The
95
+ starter's `src/pages/ops/` and `src/pages/api/ops/` are the reference
96
+ implementation; preserve their authentication and repository boundaries when
97
+ customizing the UI.
98
+
99
+ Implement `/ops` as a private authenticated application, never as a public
100
+ content route. The minimum screens are:
101
+
102
+ ```text
103
+ /ops health, sync state, queue, and recent reports
104
+ /ops/posts inventory, filters, status, and bulk-safe actions
105
+ /ops/posts/[id]/edit validated post edit and metadata preview
106
+ /ops/posts/[id]/preview noindex preview with draft/review state
107
+ /ops/topics topic, FAQ, and published-post management
108
+ /ops/sitemap sitemap sources, matching history, eligibility
109
+ /ops/reports technical SEO, content quality, visibility, analytics
110
+ /ops/settings/site origin, locale, timezone, defaults, CTA
111
+ /ops/settings/integrations AI CMO, GSC, and GA4 configured/verified status
112
+ /ops/wordpress WordPress preview, apply, resume, and import history
113
+ ```
114
+
115
+ All writes require authentication, authorization, an audit event, and a
116
+ visible confirmation. Publishing, rewrite queueing, sitemap matching, and
117
+ integration changes must show a dry-run or impact preview first.
118
+
119
+ WordPress migration is an explicit server-side workflow. It reads the
120
+ WordPress REST API posts, categories, tags, and media collections; preserves
121
+ source IDs for idempotent reruns; maps categories to draft topics; stores tags
122
+ as migration terms; registers featured/all media; and records old-path
123
+ redirects. Imported posts are drafts unless the operator explicitly selects
124
+ the preserve-published option. See the [Astro starter migration screen](starter/README.md)
125
+ for environment and operation details.
126
+
127
+ ## Content model
128
+
129
+ Define a single normalized post shape at the adapter boundary. The full
130
+ database, sync-state, delivery-state, redirect, and metadata contracts live in
131
+ the shared references:
132
+
133
+ - [blog data contract](../../bundled-references/blog-data-contract.md)
134
+ - [technical SEO contract](../../bundled-references/seo-technical-contract.md)
135
+
136
+ The Astro-specific schema starts here:
137
+
138
+ ```ts
139
+ type Post = {
140
+ id: string
141
+ slug: string
142
+ title: string
143
+ excerpt?: string
144
+ content: string
145
+ publishedAt: string
146
+ updatedAt?: string
147
+ canonicalUrl?: string
148
+ coverImage?: string
149
+ author?: { name: string; url?: string }
150
+ status: "draft" | "review" | "approved" | "scheduled" | "published" | "archived"
151
+ }
152
+ ```
153
+
154
+ Use the [example Content Collections schema](examples/content.config.ts) for
155
+ local or build-time entries. If the
156
+ remote API has optional or inconsistent fields, normalize and validate them in
157
+ `src/lib/posts.ts`; do not spread provider-specific fields through templates.
158
+ The `slug` is the public identity and must not silently change after
159
+ publication. Create a redirect when a published slug must change.
160
+
161
+ ## Rendering and data freshness
162
+
163
+ - Prefer prerendered post pages for stable content and CDN delivery.
164
+ - Use `getStaticPaths()` for build-time collections and ensure unpublished
165
+ entries never produce a public route.
166
+ - For API Pull content that changes without a deploy, choose an Astro server
167
+ adapter and fetch through a server-only module, or trigger a controlled
168
+ rebuild after a successful sync.
169
+ - Keep API keys in server-only environment variables. Never expose them through
170
+ `PUBLIC_*`, client islands, serialized page props, or browser requests.
171
+ - Cache remote reads and make sync jobs idempotent. A build failure must not
172
+ replace the last known-good public content with an empty collection.
173
+
174
+ ## API Pull and rewrite boundary
175
+
176
+ The Astro site is a host adapter, not the rewrite source of truth:
177
+
178
+ 1. Fetch published or explicitly requested posts from Maggie API Pull.
179
+ 2. Map remote identifiers, title, slug, canonical URL, timestamps, and body
180
+ into the local `Post` model.
181
+ 3. Store a stable remote ID and sync cursor so retries do not duplicate posts.
182
+ 4. Treat sitemap matching as an explicit queue input; match by canonical URL or
183
+ normalized slug, then title-derived slug only when the existing slug is
184
+ empty and the result is unique.
185
+ 5. Pull rewrite results into a draft or review state. Do not publish from a
186
+ build hook, cron job, or content loader without an explicit approval gate.
187
+
188
+ The sitemap must contain only posts that are public on the host site, with
189
+ absolute canonical URLs and `lastmod` when known.
190
+
191
+ ## SEO, GEO, and analytics requirements
192
+
193
+ Each post page should server-render:
194
+
195
+ - one `h1`, title, author, published date, and primary content;
196
+ - canonical URL, description, Open Graph, and Twitter metadata;
197
+ - Article JSON-LD that agrees with visible content;
198
+ - useful internal links and image dimensions/alt text;
199
+ - no indexable duplicate query URLs.
200
+
201
+ Add GA4 only when configured and permitted by the site's consent policy. Add
202
+ GSC verification through an environment variable, HTML file, or DNS workflow;
203
+ do not hardcode ownership tokens in the template.
204
+
205
+ ## Acceptance checklist
206
+
207
+ ```bash
208
+ npm run build
209
+ npm run preview
210
+ ```
211
+
212
+ Verify the rendered list, a post, an unpublished/missing post, `robots.txt`,
213
+ the sitemap, canonical metadata, JSON-LD, analytics-disabled mode, and an API
214
+ Pull failure with a preserved last-known-good build.
215
+
216
+ The [local site audit](../../bundled-tools/clis/site_audit.py) can check the deployed
217
+ host, but it does not replace route-level tests.
@@ -0,0 +1,30 @@
1
+ export const blogConfig = {
2
+ site: {
3
+ name: "Example Site",
4
+ baseUrl: "https://example.com",
5
+ locale: "en-GB",
6
+ timezone: "Europe/London",
7
+ blogPath: "/blog",
8
+ postsPerPage: 12,
9
+ topicMinimumPosts: 3,
10
+ },
11
+ defaults: {
12
+ language: "en",
13
+ robotsPolicy: "index,follow",
14
+ },
15
+ integrations: {
16
+ aiCmo: {
17
+ enabled: false,
18
+ baseUrl: "https://api.cmo.so",
19
+ apiKeyEnv: "AI_CMO_API_KEY",
20
+ syncMode: "manual",
21
+ },
22
+ gsc: { enabled: false, verificationMode: "dns" },
23
+ ga4: { enabled: false, consentRequired: true },
24
+ },
25
+ publishing: {
26
+ requireApproval: true,
27
+ allowScheduledPublish: false,
28
+ allowAutoRewritePublish: false,
29
+ },
30
+ } as const;
@@ -0,0 +1,28 @@
1
+ import { defineCollection, z } from "astro:content";
2
+ import { glob } from "astro/loaders";
3
+
4
+ const posts = defineCollection({
5
+ loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/posts" }),
6
+ schema: z.object({
7
+ id: z.string(),
8
+ source: z.enum(["local", "api-pull", "cms", "manual"]),
9
+ sourceId: z.string().optional(),
10
+ title: z.string().min(1),
11
+ excerpt: z.string().default(""),
12
+ canonicalUrl: z.string().url(),
13
+ coverImageUrl: z.string().url().optional(),
14
+ coverImageAlt: z.string().optional(),
15
+ authorName: z.string().optional(),
16
+ authorUrl: z.string().url().optional(),
17
+ language: z.string().default("en"),
18
+ tags: z.array(z.string()).default([]),
19
+ status: z.enum(["draft", "review", "approved", "scheduled", "published", "archived"]),
20
+ publishedAt: z.coerce.date().optional(),
21
+ updatedAt: z.coerce.date().optional(),
22
+ }).refine(
23
+ (post) => post.status !== "published" || post.publishedAt !== undefined,
24
+ "published posts require publishedAt",
25
+ ),
26
+ });
27
+
28
+ export const collections = { posts };
@@ -0,0 +1,37 @@
1
+ type PostMeta = {
2
+ title: string;
3
+ excerpt: string;
4
+ canonicalUrl: string;
5
+ imageUrl?: string;
6
+ authorName?: string;
7
+ publishedAt: string;
8
+ updatedAt?: string;
9
+ };
10
+
11
+ export function buildPostMeta(post: PostMeta, siteName: string) {
12
+ const image = post.imageUrl ? [post.imageUrl] : undefined;
13
+ return {
14
+ title: `${post.title} | ${siteName}`,
15
+ description: post.excerpt,
16
+ canonical: post.canonicalUrl,
17
+ openGraph: {
18
+ type: "article",
19
+ title: post.title,
20
+ description: post.excerpt,
21
+ url: post.canonicalUrl,
22
+ images: image,
23
+ },
24
+ twitter: { card: "summary_large_image", title: post.title, description: post.excerpt, images: image },
25
+ jsonLd: {
26
+ "@context": "https://schema.org",
27
+ "@type": "Article",
28
+ headline: post.title,
29
+ description: post.excerpt,
30
+ url: post.canonicalUrl,
31
+ datePublished: post.publishedAt,
32
+ ...(post.updatedAt ? { dateModified: post.updatedAt } : {}),
33
+ ...(post.authorName ? { author: { "@type": "Person", name: post.authorName } } : {}),
34
+ ...(image ? { image } : {}),
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,31 @@
1
+ type PostState = "draft" | "review" | "approved" | "published" | "archived";
2
+
3
+ const transitions: Record<PostState, PostState[]> = {
4
+ draft: ["review", "archived"],
5
+ review: ["draft", "approved", "archived"],
6
+ approved: ["review", "published", "archived"],
7
+ published: ["archived"],
8
+ archived: ["draft"],
9
+ };
10
+
11
+ export function assertPostTransition(from: PostState, to: PostState) {
12
+ if (!transitions[from].includes(to)) {
13
+ throw new Error(`invalid post transition: ${from} -> ${to}`);
14
+ }
15
+ }
16
+
17
+ export function assertPublishable(post: {
18
+ status: PostState;
19
+ title: string;
20
+ slug: string;
21
+ canonicalUrl: string;
22
+ publishedAt?: string;
23
+ authorId?: string;
24
+ topicIds: string[];
25
+ }) {
26
+ if (post.status !== "approved") throw new Error("post must be approved before publishing");
27
+ if (!post.title || !post.slug || !post.canonicalUrl) throw new Error("post SEO identity is incomplete");
28
+ if (!post.authorId) throw new Error("published post requires an author");
29
+ if (post.topicIds.length === 0) throw new Error("published post requires a topic");
30
+ if (post.publishedAt && Number.isNaN(Date.parse(post.publishedAt))) throw new Error("publishedAt must be ISO-8601");
31
+ }
@@ -0,0 +1,21 @@
1
+ export const publicRoutes = {
2
+ blogIndex: "/blog",
3
+ post: "/blog/[...slug]",
4
+ topics: "/topics",
5
+ topic: "/topics/[topic-slug]",
6
+ author: "/authors/[author-slug]",
7
+ sitemap: "/sitemap.xml",
8
+ robots: "/robots.txt",
9
+ } as const;
10
+
11
+ export const opsRoutes = {
12
+ dashboard: "/ops",
13
+ posts: "/ops/posts",
14
+ postEditor: "/ops/posts/[id]/edit",
15
+ preview: "/ops/posts/[id]/preview",
16
+ topics: "/ops/topics",
17
+ sitemap: "/ops/sitemap",
18
+ reports: "/ops/reports",
19
+ siteSettings: "/ops/settings/site",
20
+ integrations: "/ops/settings/integrations",
21
+ } as const;
@@ -0,0 +1,36 @@
1
+ PUBLIC_SITE_URL=https://example.com
2
+ PUBLIC_SITE_NAME=Example Site
3
+ PUBLIC_SITE_LOCALE=en-GB
4
+ PUBLIC_SITE_TIMEZONE=Europe/London
5
+ PUBLIC_POSTS_PER_PAGE=9
6
+ PUBLIC_GRID_COLUMNS=3
7
+ AI_CMO_API_KEY=
8
+ AI_CMO_BASE_URL=https://api.cmo.so
9
+ GA4_MEASUREMENT_ID=
10
+ GSC_VERIFICATION_TOKEN=
11
+ GSC_SITE_URL=https://example.com
12
+ GSC_ACCESS_TOKEN=
13
+ GA4_PROPERTY_ID=
14
+ GA4_ACCESS_TOKEN=
15
+ WORDPRESS_API_TOKEN=
16
+ WORDPRESS_USERNAME=
17
+ WORDPRESS_APP_PASSWORD=
18
+ OPS_AUTH_PROVIDER=
19
+ OPS_LOCAL_TOKEN=
20
+ OPS_LOCAL_ROLE=agency-owner
21
+ MAGGIE_DEPLOY_PROVIDER=node
22
+ CLOUDFLARE_ENV=staging
23
+ BLOG_DATA_SOURCE=sqlite
24
+ BLOG_DB_PATH=.data/maggie.sqlite
25
+ REPORT_EMAIL_WEBHOOK_URL=
26
+ EMAIL_PROVIDER=resend
27
+ RESEND_API_KEY=
28
+ EMAIL_FROM=
29
+ EMAIL_REPLY_TO=
30
+ EMAIL_WEBHOOK_URL=
31
+ MEDIA_STORAGE_PROVIDER=cloudinary
32
+ CLOUDINARY_CLOUD_NAME=
33
+ CLOUDINARY_API_KEY=
34
+ CLOUDINARY_API_SECRET=
35
+ CLOUDINARY_UPLOAD_FOLDER=maggie
36
+ MEDIA_OUTPUT_DIR=public/media/optimized