@topy-ai/maggie 0.7.9 → 0.7.11

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
@@ -218,8 +218,8 @@ artifact schemas.
218
218
  Recommended upgrade sequence for the current release:
219
219
 
220
220
  ```bash
221
- npx @topy-ai/maggie@0.7.9 update --project . --force
222
- npx @topy-ai/maggie@0.7.9 cleanup --project .
221
+ npx @topy-ai/maggie@0.7.11 update --project . --force
222
+ npx @topy-ai/maggie@0.7.11 cleanup --project .
223
223
  ```
224
224
 
225
225
  Maintainers should pass npm credentials through the repository helper, never
@@ -229,8 +229,11 @@ as a command-line argument:
229
229
  node scripts/publish-npm.mjs --maggie-env-file ../.env
230
230
  ```
231
231
 
232
- The 0.7.9 workflow adds nested section-field contracts, renderer-backed
233
- examples, stable-ID preservation during migration, and disjoint page
232
+ The 0.7.11 workflow adds served-content equivalence checks, query-route
233
+ baseline exclusions, changed-surface render evidence gates, generated skill
234
+ catalogs, component-binding audits, and icon-family noise filtering. It also
235
+ includes the 0.7.9 nested section-field contracts, renderer-backed examples,
236
+ stable-ID preservation during migration, and disjoint page
234
237
  inventory guidance. It retains the shared Google integrations runbook and
235
238
  fail-closed provider capability matrix, alongside the installable MaggieDash admin distribution and
236
239
  audited CMS operations (`cms revisions`,
@@ -248,6 +251,11 @@ observations; every route must include status, canonical, indexability, and
248
251
  required metadata checks. Agent content writes require an explicit approved
249
252
  origin, same-origin redirects, and recursive credential validation. Feedback
250
253
  submissions persist and print only an allowlisted acknowledgement. The release
254
+ also adds image-aware content equivalence with explicit legacy-baseline
255
+ limitations, locale checks for stored image alt text, a validated label/value
256
+ `pairs` section, registry fan-out checks, idempotent reconciliation, per-step
257
+ and section-scoped design evidence, catch-all source confidence, shell-safe VPS
258
+ SSH guidance, and compatible feedback CLI examples. The release
251
259
  retains the existing service matching,
252
260
  localization, seed-manifest, lockfile/analytics, sitemap, deployment and
253
261
  rollback workflows.
@@ -470,7 +478,6 @@ python3 tools/clis/maggie_design.py rebrand \
470
478
  | `maggie-feedback` | Collect redacted feedback drafts and explicitly submit them to the NoBlox feedback endpoint |
471
479
  | `maggie-auth-reference` | Generate and validate traditional email/password auth with secure server-side sessions |
472
480
  | `maggie-blog` | Run a provider-neutral blog lifecycle with stable identity, topics, feeds, settings, and rollback |
473
- | `maggie-google-capabilities` | Validate redacted Google provider evidence and separate read/report/edit/publish capability states |
474
481
 
475
482
  `maggie-design` can initialize native blog and service UI plans from a local,
476
483
  read-only structural reference:
@@ -490,13 +497,32 @@ maggie design validate-ui --project . \
490
497
  This reuses the host project's homepage shell and `DESIGN.md`; it does not
491
498
  copy reference branding, source code, private data, or provider facts.
492
499
 
493
- The remaining installable skills are `maggie-blog-bootstrap`, `maggie-dash`,
494
- `maggie-clone`, `maggie-clone-to-template`, `maggie-marketplace`,
495
- `maggie-template`, `maggie-design`, `maggie-ops`, `maggie-deployment`,
496
- `maggie-project-context`, `maggie-social-share`, and `maggie-memory`.
500
+ For existing routes, in-place design jobs are resumable and can be narrowed to
501
+ one section. Catch-all routes remain `unproven` until their content source is
502
+ declared, and each completed step records evidence:
503
+
504
+ ```bash
505
+ maggie design in-place --project . --route /pricing \
506
+ --content-source src/lib/pages/db.ts
507
+ maggie design step in-place-<id> --project . \
508
+ --step inspect-route --evidence .maggie/evidence/route.json
509
+ maggie design section --project . --route /pricing --section-id hours
510
+ maggie design section-validate --before before.json --after after.json \
511
+ --section-id hours
512
+ ```
513
+
514
+ MaggieDash section contracts include a compact `pairs` band for factual
515
+ label/value rows. Validate required values, registry fan-out, locale sidecars,
516
+ and idempotent repairs with `maggie dash sections validate`,
517
+ `fanout-validate`, `locale-validate`, and `reconcile`.
497
518
 
498
- The package also includes `maggie-auth-reference` and `maggie-blog`. Use the
499
- stable commands below after installation:
519
+ The package includes all 18 installable skills: `maggie-blog-bootstrap`,
520
+ `maggie-dash`, `maggie-clone`, `maggie-clone-to-template`, `maggie-marketplace`,
521
+ `maggie-template`, `maggie-design`, `maggie-ops`, `maggie-deployment`,
522
+ `maggie-project-context`, `maggie-seo-geo`, `maggie-social-share`,
523
+ `maggie-service-booking`, `maggie-memory`, `maggie-content-localization`,
524
+ `maggie-feedback`, `maggie-auth-reference`, and `maggie-blog`. Use the stable
525
+ commands below after installation:
500
526
 
501
527
  ```bash
502
528
  maggie auth reference --project . --confirm
package/bin/maggie.js CHANGED
@@ -17,26 +17,18 @@ const MARKETPLACE_ROOT = join(PACKAGE_ROOT, "bundled-marketplace");
17
17
  const CONTRACTS_ROOT = join(PACKAGE_ROOT, "bundled-contracts");
18
18
  const TEMPLATES_ROOT = join(PACKAGE_ROOT, "bundled-templates");
19
19
  const STATE_DIR = ".maggie";
20
- const SKILL_NAMES = [
21
- "maggie-blog-bootstrap",
22
- "maggie-dash",
23
- "maggie-clone",
24
- "maggie-clone-to-template",
25
- "maggie-marketplace",
26
- "maggie-template",
27
- "maggie-design",
28
- "maggie-ops",
29
- "maggie-deployment",
30
- "maggie-project-context",
31
- "maggie-seo-geo",
32
- "maggie-social-share",
33
- "maggie-service-booking",
34
- "maggie-memory",
35
- "maggie-content-localization",
36
- "maggie-feedback",
37
- "maggie-auth-reference",
38
- "maggie-blog",
39
- ];
20
+ function loadSkillNames() {
21
+ try {
22
+ return JSON.parse(readFileSync(join(SKILLS_ROOT, "catalog.json"), "utf8")).skills.map((skill) => skill.name);
23
+ } catch {
24
+ // Local regression tests may invoke the wrapper before package assembly.
25
+ // Discover real frontmatter directories and never resurrect retired content.
26
+ return readdirSync(SKILLS_ROOT, { withFileTypes: true })
27
+ .filter((entry) => entry.isDirectory() && entry.name !== "maggie-emdash" && existsSync(join(SKILLS_ROOT, entry.name, "SKILL.md")))
28
+ .map((entry) => entry.name).sort();
29
+ }
30
+ }
31
+ const SKILL_NAMES = loadSkillNames();
40
32
  const RETIRED_PATHS = [
41
33
  ".agents/skills/maggie-emdash",
42
34
  ".claude/skills/maggie-emdash",
@@ -66,7 +58,8 @@ Usage:
66
58
  maggie doctor [--project PATH]
67
59
  maggie bootstrap interview [project]
68
60
  maggie dash init|install|status|migrate|transition|variant|cms --project PATH [options]
69
- maggie dash sections <validate|prompt|keys> [options]
61
+ maggie dash sections <validate|prompt|keys|remap-translations|fanout-validate|locale-validate|reconcile> [options]
62
+ maggie dash components-audit --bindings-file FILE --sections-file FILE --pages-file FILE
70
63
  maggie dash status --project PATH
71
64
  maggie dash migrate --project PATH --confirm
72
65
  maggie content FILE --source PROVIDER --project PATH --confirm
@@ -92,6 +85,10 @@ Usage:
92
85
  maggie design validate-ui --project PATH --plan PATH --rendered-dir PATH --confirm
93
86
  maggie design icon-inventory --project PATH --source-dir src --runtime assets/icons.css --output docs/icon-inventory.json
94
87
  maggie design author --project PATH --route /about --purpose TEXT --audience TEXT --confirm
88
+ maggie design in-place --project PATH --route /pricing [--content-source PATH]
89
+ maggie design section --project PATH --route /pricing --section-id ID
90
+ maggie design section-validate --before FILE --after FILE --section-id ID
91
+ maggie design step <job-id> --step NAME --evidence FILE
95
92
  maggie auth reference --project PATH --confirm
96
93
  maggie auth check --project PATH [--production]
97
94
  maggie blog init|inspect|ingest|validate|publish|sitemap|settings|rollback|integration-state
@@ -13,7 +13,6 @@ Skills are the agent-facing workflows. They compose with the tools in
13
13
  | `maggie-template` | Discover, recommend, inspect, and apply a selected Astro homepage template | marketplace catalog, bootstrap state, template `DESIGN.md` |
14
14
  | `maggie-design` | Fully clone authorized interior pages, then reconcile them to the homepage header/footer shell | browser MCP, clone planner CLI, completed Maggie homepage |
15
15
  | `maggie-ops` | Build, connect, operate, upgrade, and verify the private blog operations dashboard | Ops dashboard contract, authenticated API bridge, host tests |
16
- | `maggie-google-capabilities` | Validate a redacted Google provider capability matrix without inferring write access | Google integrations runbook, provider evidence |
17
16
  | `maggie-deployment` | Deploy and verify a dynamic Maggie blog, Cloudflare-first | Cloudflare Workers, D1, R2, KV, Wrangler |
18
17
  | `maggie-project-context` | Sync Project, voice, site and CTA context | project-context CLI/API |
19
18
  | `maggie-seo-geo` | Plan, audit, create/rewrite and measure SEO/GEO | visibility, SEO audit, GSC/GA4, content quality |
@@ -0,0 +1,78 @@
1
+ {
2
+ "schemaVersion": "maggie-skill-catalog.v1",
3
+ "generatedFrom": "skills/*/SKILL.md",
4
+ "skills": [
5
+ {
6
+ "name": "maggie-auth-reference",
7
+ "description": "Generate and validate a provider-neutral traditional email/password auth reference with secure server sessions and production security gates."
8
+ },
9
+ {
10
+ "name": "maggie-blog",
11
+ "description": "Create and operate a provider-neutral blog with stable post identity, topics, draft approval, archive/post routes, RSS, sitemap, settings, and idempotent local ingest."
12
+ },
13
+ {
14
+ "name": "maggie-blog-bootstrap",
15
+ "description": "Establish a MaggieDash-backed Astro blog or complete a small SEO/GEO-ready blog in an existing project."
16
+ },
17
+ {
18
+ "name": "maggie-clone",
19
+ "description": "Reverse-engineer an authorized website homepage and create the Maggie homepage foundation inside an existing blog project. Use with /maggie-clone plus a homepage URL when the user wants to replicate the homepage structure, header, footer, assets, responsive behavior, and interactions."
20
+ },
21
+ {
22
+ "name": "maggie-clone-to-template",
23
+ "description": "Turn an authorized website URL and a design or copy prompt into a validated marketplace template through clone, asset, screenshot, comparison, revision, and packaging gates."
24
+ },
25
+ {
26
+ "name": "maggie-content-localization",
27
+ "description": "Manage translation, polish, rewrite, market localization, review, stale detection, and publishing for pages, guides, posts, services, products, and categories."
28
+ },
29
+ {
30
+ "name": "maggie-dash",
31
+ "description": "Manage the MaggieDash project foundation, local content store, and approval lifecycle."
32
+ },
33
+ {
34
+ "name": "maggie-deployment",
35
+ "description": "Deploy and operate Maggie blog projects with Cloudflare Workers as the default target, while preserving an adapter boundary for VPS, GCP, and AWS."
36
+ },
37
+ {
38
+ "name": "maggie-design",
39
+ "description": "Design authorized interior pages, review rendered responsive layouts, or explicitly rebrand a packaged homepage/template. Use rebrand only with a named source brand and target brand."
40
+ },
41
+ {
42
+ "name": "maggie-feedback",
43
+ "description": "Collect, review, and explicitly submit privacy-safe feedback about a Maggie skill run, workflow result, bug, or feature request."
44
+ },
45
+ {
46
+ "name": "maggie-marketplace",
47
+ "description": "Build, rebrand, localise, validate, version, and apply reusable Astro marketplace templates from an authorised preview URL or local HTML export. Use when adding a design template, creating a neutral demo, or preparing a stable homepage starter for an Astro project."
48
+ },
49
+ {
50
+ "name": "maggie-memory",
51
+ "description": "Persist and retrieve confirmed user preferences, project conventions, lessons from repaired mistakes, and structured error history across Maggie skill runs."
52
+ },
53
+ {
54
+ "name": "maggie-ops",
55
+ "description": "Build, connect, operate, upgrade, and verify a private Maggie Ops dashboard for blog content, API Pull, sitemap matching, rewrite approvals, reports, and integrations. Use when the user asks for Ops/admin functionality or lifecycle operations rather than public blog pages."
56
+ },
57
+ {
58
+ "name": "maggie-project-context",
59
+ "description": "Sync a site's safe AI CMO Project, selected brand voice, site settings, and CTA context into a local generated context file. Use when connecting a vibe-coded blog or existing website to AI CMO, refreshing brand context, or diagnosing missing CTA/project data."
60
+ },
61
+ {
62
+ "name": "maggie-seo-geo",
63
+ "description": "Plan, audit, create, rewrite, and measure content for AI CMO's paid SEO and GEO workflow. Use for topic opportunities, AI visibility, technical SEO, extractable article structure, sitemap-based rewrites, GSC readback, or SEO/GEO client reports."
64
+ },
65
+ {
66
+ "name": "maggie-service-booking",
67
+ "description": "Import, synchronise, validate, and design SPA service pages from a booking provider such as Fresha. Use for service catalogues, treatment variants, prices, durations, booking links, payment links, and booking-aware page generation."
68
+ },
69
+ {
70
+ "name": "maggie-social-share",
71
+ "description": "Turn approved AI CMO articles and content assets into platform-native social drafts, scheduled posts, and measured distribution. Use for Social Share planning, LinkedIn/Facebook copy, CTA mapping, calendars, approvals, publish retries, or social performance readback."
72
+ },
73
+ {
74
+ "name": "maggie-template",
75
+ "description": "Discover, recommend, inspect, and apply verified Astro homepage templates from the Maggie marketplace. Use when a user wants to start a site from a selected template, compare available designs, or choose a visual direction before maggie-clone or maggie-design."
76
+ }
77
+ ]
78
+ }
@@ -177,6 +177,20 @@ maggie dash sections validate --registry templates/maggiedash/section-registry.j
177
177
  maggie dash sections prompt --registry templates/maggiedash/section-registry.json
178
178
  maggie dash sections keys --registry templates/maggiedash/section-registry.json \
179
179
  --page-id <page-id> --sections-file .maggie/sections.json
180
+ maggie dash sections remap-translations \
181
+ --translations-file .maggie/translations-legacy.json \
182
+ --mapping-file .maggie/section-translation-map.json \
183
+ --output .maggie/translations-v2.json --confirm
184
+
185
+ # Validate locale sidecars for stored image alt text before publishing:
186
+ maggie dash sections locale-validate \
187
+ --page-id <page-id> --sections-file .maggie/sections.json \
188
+ --translations-file .maggie/translations-by-locale.json --locale zh-Hant
189
+
190
+ # Check that a host wired every registry type through all implementation surfaces:
191
+ maggie dash sections fanout-validate \
192
+ --registry templates/maggiedash/section-registry.json \
193
+ --fanout-file templates/maggiedash/section-fanout.json
180
194
  ```
181
195
 
182
196
  The catalogue declares purpose, usage, placement, repeatability and layout
@@ -184,10 +198,24 @@ limits, renderer-owned examples, and the shape of repeated entries. A repeat
184
198
  may contain an object (`title`, `body`, `href`, and so on), not just a count;
185
199
  the nested limits are displayed to the planner and checked by `sections
186
200
  validate`. Over-limit copy is an editor note; unknown types fail validation.
187
- The starter registry is provider-neutral and extensible: a host may add its
188
- own renderer-backed bands, but every added band must have a real-renderer
189
- preview or an explicitly labelled example fallback at the supported
190
- breakpoints. Do not describe the vocabulary as a fixed number of bands.
201
+ The registry is intentionally marked `catalogStatus: starter`, not presented
202
+ as a production-complete vocabulary. Hosts commonly add renderer-backed bands
203
+ such as `form`, `map`, `steps`, `checklist`, `compare`, `quote`, `timeline`,
204
+ `gallery`, `stats`, `split`, `glossary`, `credentials`, `team`, `before-after`,
205
+ `statement`, `features`, `scope`, `animated`, `opening`, `breadcrumb`, and
206
+ `bound-collection`; the machine-readable registry lists these examples. Every
207
+ added band must have a real-renderer preview or an explicitly labelled example
208
+ fallback at the supported breakpoints. Do not describe the starter vocabulary
209
+ as a fixed number of bands.
210
+
211
+ Required fields and repeated `pairs` values cannot be blank. The `pairs`
212
+ starter band is intended for compact label/value facts such as opening hours;
213
+ it is not a prose fallback. The registry fan-out manifest is a host integration
214
+ contract: when a host adds a type, update its type union/schema, blank state,
215
+ validation, readable-content extraction, renderer, and editor entry together,
216
+ then run `fanout-validate`. `locale-validate` checks stored image-alt sidecars;
217
+ the host adapter must write the section and all non-default locale rows in one
218
+ transaction. The JSON report is evidence, not a database write.
191
219
 
192
220
  Translation keys use stable section IDs, with an array-index fallback only
193
221
  until the ordered migration is complete. Migration preserves unique IDs that
@@ -195,6 +223,13 @@ already belong to the source section list; it generates a new ID only for a
195
223
  missing, duplicate, or target-conflicting ID. Never rebuild IDs from array
196
224
  position or overwrite existing translation keys.
197
225
 
226
+ When copy already lives under another keyspace, generate an explicit prefix
227
+ mapping from the converter and run `remap-translations`. The helper applies
228
+ longest prefixes first, preserves unmapped keys for review, and fails on
229
+ conflicting destinations. For a database adapter, apply the section rows and
230
+ translation remap in one transaction with the same migration ID; the JSON CLI
231
+ is the reviewable output form, not a substitute for the adapter's transaction.
232
+
198
233
  Keep data-owned values out of page copy. For example, a pricing band should
199
234
  store a service identifier or slug and let the live service/booking adapter
200
235
  render current options and prices. Inventory reports must classify each
@@ -30,6 +30,12 @@ errors, missing assets, or visual placeholders. The result follows
30
30
  `maggie-deployment-canary.v1`; never include cookies, authorization headers, or
31
31
  response bodies in it.
32
32
 
33
+ When a release changes a visitor-facing HTML, CSS, component, or asset surface,
34
+ the release preflight automatically requires both a passing icon inventory and
35
+ rendered canary evidence. The canary must include screenshots, zero console or
36
+ network errors, and zero placeholder matches. Query-driven routes belong in
37
+ behavior/API checks, not static byte baselines.
38
+
33
39
  ## Automatic memory hook
34
40
 
35
41
  Follow [Maggie Memory Hook](../../references/memory-hook.md) at invocation and completion.
@@ -37,6 +37,39 @@ systemd service, environment file, Nginx/TLS configuration, release root,
37
37
  database/media migration and backup strategy, health checks, and rollback
38
38
  owner before any remote mutation.
39
39
 
40
+ ### SSH and secret-source contract
41
+
42
+ Use these canonical project variables when a VPS adapter is configured:
43
+
44
+ | Variable | Meaning | Precedence |
45
+ | --- | --- | --- |
46
+ | `NOBLOX_DEPLOY_HOST` | VPS hostname or IP | explicit CLI flag, then project `.env`, then provider secret manager |
47
+ | `NOBLOX_DEPLOY_SSH_USER` | SSH login user | explicit CLI flag, then project `.env`, then provider secret manager |
48
+ | `NOBLOX_DEPLOY_SSH_PORT` | SSH port | explicit CLI flag, then project `.env`, then provider secret manager |
49
+ | `NOBLOX_DEPLOY_SSH_KEY` | path or secret reference for the private key | explicit CLI flag, then project `.env`, then provider secret manager |
50
+ | `NOBLOX_DEPLOY_ENV_FILE` | remote runtime env-file reference | provider secret manager only for the secret values |
51
+
52
+ Legacy provider-specific names may be supported by an adapter, but must be
53
+ mapped to these names in the local plan. The SSH key is preferred. Do not put
54
+ an SSH password in a plan or command line; if password authentication is
55
+ unavoidable, resolve `NOBLOX_DEPLOY_SSH_PASSWORD` at execution time from the
56
+ approved secret manager and never echo it. Explicit CLI values override `.env`;
57
+ `.env` overrides the provider adapter's default names. Values are validated by
58
+ name and presence only, never printed.
59
+
60
+ When composing SSH options in Bash or zsh, use an array so whitespace is not
61
+ reinterpreted as extra arguments:
62
+
63
+ ```zsh
64
+ ssh_opts=(-o StrictHostKeyChecking=accept-new -o BatchMode=yes)
65
+ ssh "${ssh_opts[@]}" -p "$NOBLOX_DEPLOY_SSH_PORT" \
66
+ "$NOBLOX_DEPLOY_SSH_USER@$NOBLOX_DEPLOY_HOST" "systemctl is-active example"
67
+ ```
68
+
69
+ Do not use `ssh_opts='-o StrictHostKeyChecking=accept-new -o BatchMode=yes'`
70
+ followed by `ssh $ssh_opts ...`; zsh does not perform the word splitting that
71
+ this pattern assumes.
72
+
40
73
  Create a local, secret-free plan before remote execution:
41
74
 
42
75
  ```bash
@@ -10,13 +10,33 @@ metadata:
10
10
  ## In-place route evidence
11
11
 
12
12
  `maggie design in-place --project . --route /example` resolves local source
13
- files and falls back to a discovered `[...slug]`/`[[...slug]]` file. That
14
- fallback is currently a source candidate, not proof that the concrete URL is
15
- served. Verify the route's HTTP response, content identity and framework route
13
+ files and falls back to a discovered `[...slug]`/`[[...slug]]` file. A catch-all
14
+ is recorded as `resolution: unproven` unless `--content-source` names the
15
+ actual DB/content resolver. A source candidate is not proof that the concrete
16
+ URL is served: verify the HTTP response, content identity, and framework route
16
17
  mapping before editing a shared catch-all. Next app-router catch-alls whose
17
- file is named `page.tsx` are not covered by this filename fallback. Capture
18
- responsive and interaction evidence separately; a plan in phase `ready` is
19
- not a successful browser test.
18
+ file is named `page.tsx` are not covered by this filename fallback.
19
+
20
+ In-place work is resumable. Record each completed step and its evidence:
21
+
22
+ ```bash
23
+ maggie design step in-place-<id> --project . \
24
+ --step inspect-route --evidence .maggie/evidence/route.json
25
+ maggie design status in-place-<id> --project .
26
+ ```
27
+
28
+ The job becomes `done` only after every required step has evidence; `ready`
29
+ means the contract exists, not that browser QA passed. For a one-band change,
30
+ use section-scoped mode and validate before/after evidence:
31
+
32
+ ```bash
33
+ maggie design section --project . --route /pricing --section-id hours
34
+ maggie design section-validate --before before.json --after after.json \
35
+ --section-id hours
36
+ ```
37
+
38
+ The validator requires the target section to change and proves that every
39
+ other section remains unchanged.
20
40
 
21
41
  ## Source-to-runtime icon inventory
22
42
 
@@ -34,8 +54,10 @@ Use `missing`, `unknown`, and `coverage` from the versioned
34
54
  `maggie-icon-inventory.v1` report as the release gate. A binary font without a
35
55
  name map is reported as evidence only; it must not be treated as proof that a
36
56
  glyph exists. Fix the source token or add the runtime definition before
37
- shipping. Do not paste private source, URLs with credentials, or user data
38
- into the report.
57
+ shipping. The inventory compares a scoped runtime icon family (`ph-*`, `fa-*`,
58
+ or `lucide-*`), normalizes family prefixes, and ignores inline SVG IDs and font
59
+ filename noise. Do not paste private source, URLs with credentials, or user
60
+ data into the report.
39
61
 
40
62
  ## Automatic memory hook
41
63
 
@@ -16,7 +16,7 @@ general lesson or regression test.
16
16
  ## Collect locally
17
17
 
18
18
  ```bash
19
- maggie feedback collect --project . \
19
+ maggie feedback --project . collect \
20
20
  --skill maggie-clone-to-template --run-id clone-001 \
21
21
  --type bug --phase visual-qa \
22
22
  --summary "Mobile hero overflows the viewport" \
@@ -26,6 +26,7 @@ maggie feedback collect --project . \
26
26
  --reproduce "Run the clone workflow" \
27
27
  --reproduce "Open the mobile screenshot" \
28
28
  --screenshot ./screenshots/mobile.png
29
+ # The equivalent `maggie feedback collect --project . ...` spelling is also supported.
29
30
  maggie feedback preview .maggie/feedback/<feedback-id>.json --format markdown
30
31
  ```
31
32
 
@@ -83,8 +83,13 @@ automatically replace the baseline following failure. Dynamic dates, class
83
83
  names and copy can produce legitimate differences requiring review.
84
84
 
85
85
  This covers server-rendered sitemap pages, not CSS rendering, JavaScript-only
86
- content, database translation keys or browser interactions. A source key change
87
- is detected here only when it changes served content. Baselines contain site
86
+ content, database translation keys or browser interactions. Query-string URLs
87
+ are recorded as excluded from byte baselines because they often represent
88
+ search/filter state; use browser or API-specific tests for those states. The
89
+ baseline now compares a served-content contract (headings, paragraphs and
90
+ links) alongside metadata and structure, so a section migration cannot pass
91
+ merely because the HTML byte shape stayed similar. A source key change is
92
+ detected here only when it changes served content. Baselines contain site
88
93
  metadata; do not publish private project contracts without permission.
89
94
 
90
95
  ## Browser behavior evidence
@@ -0,0 +1,11 @@
1
+ {
2
+ "schemaVersion": "maggiedash-section-fanout.v1",
3
+ "description": "Starter host contract: every registry type must be wired through each implementation surface. Hosts extending the registry must regenerate this file.",
4
+ "typeUnion": ["cards", "cta", "faq", "hero", "links", "pairs", "prose"],
5
+ "sectionSchema": ["cards", "cta", "faq", "hero", "links", "pairs", "prose"],
6
+ "blank": ["cards", "cta", "faq", "hero", "links", "pairs", "prose"],
7
+ "validation": ["cards", "cta", "faq", "hero", "links", "pairs", "prose"],
8
+ "textExtraction": ["cards", "cta", "faq", "hero", "links", "pairs", "prose"],
9
+ "renderer": ["cards", "cta", "faq", "hero", "links", "pairs", "prose"],
10
+ "editor": ["cards", "cta", "faq", "hero", "links", "pairs", "prose"]
11
+ }
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "maggiedash-section-registry.v1",
3
3
  "description": "The provider-neutral starter vocabulary used by page planners and editors. Hosts may extend it with renderer-backed section types.",
4
+ "catalogStatus": "starter",
5
+ "hostExtensionExamples": ["form", "map", "steps", "checklist", "compare", "quote", "timeline", "gallery", "stats", "split", "glossary", "credentials", "team", "before-after", "statement", "features", "scope", "animated", "opening", "breadcrumb", "bound-collection"],
4
6
  "sections": [
5
7
  {
6
8
  "type": "hero",
@@ -31,6 +33,19 @@
31
33
  ],
32
34
  "example": {"type": "prose", "heading": "Make the important part easier to understand", "paragraphs": ["Start with the decision your reader is trying to make.", "Then give them the context, evidence and next step in that order."]}
33
35
  },
36
+ {
37
+ "type": "pairs",
38
+ "purpose": "Shows named facts as a compact label-and-value band.",
39
+ "usage": "Use for opening hours, specifications, eligibility, or other factual pairs; never for long prose.",
40
+ "position": "body",
41
+ "repeatable": true,
42
+ "fields": [
43
+ {"name": "heading", "usage": "What the facts describe.", "limit": 90, "required": true},
44
+ {"name": "rows", "usage": "Non-empty factual label/value pairs.", "limit": 0, "repeats": {"min": 1, "max": 12, "of": [{"name": "label", "usage": "The fact name.", "limit": 70, "required": true}, {"name": "value", "usage": "The concise fact value; never blank.", "limit": 180, "required": true}]}},
45
+ {"name": "note", "usage": "Optional clarification below the facts.", "limit": 220}
46
+ ],
47
+ "example": {"type": "pairs", "heading": "At a glance", "rows": [{"label": "Typical response", "value": "Within one working day"}, {"label": "Available", "value": "Monday to Friday"}], "note": "Times may change on public holidays."}
48
+ },
34
49
  {
35
50
  "type": "cards",
36
51
  "purpose": "Presents parallel points side by side.",
@@ -23,7 +23,8 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "runtime"))
23
23
  from maggie_dash_store import MaggieDashStore # noqa: E402
24
24
  from service_variants import ServiceVariantStore # noqa: E402
25
25
  from maggie_dash_ui import load_and_validate # noqa: E402
26
- from maggie_sections import catalogue, section_id_migration, validate_registry, copy_notes # noqa: E402
26
+ from maggie_sections import catalogue, remap_translations, section_id_migration, validate_registry, copy_notes, validate_values, validate_fanout, reconcile_fields, validate_locale_coverage # noqa: E402
27
+ from route_imports import classify_bindings # noqa: E402
27
28
 
28
29
 
29
30
  def project_root(args: argparse.Namespace) -> Path:
@@ -319,6 +320,47 @@ def command_ui(args: argparse.Namespace) -> int:
319
320
 
320
321
 
321
322
  def command_sections(args: argparse.Namespace) -> int:
323
+ if args.sections_command == "remap-translations":
324
+ require_confirm(args)
325
+ translations = json.loads(Path(args.translations_file).resolve().read_text(encoding="utf-8"))
326
+ mappings = json.loads(Path(args.mapping_file).resolve().read_text(encoding="utf-8"))
327
+ result = remap_translations(translations, mappings)
328
+ if result["passed"]:
329
+ output = Path(args.output).resolve()
330
+ if output.exists() and not args.force:
331
+ raise ValueError(f"output exists; use --force to replace: {output}")
332
+ output.parent.mkdir(parents=True, exist_ok=True)
333
+ output.write_text(json.dumps(result["translations"], indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
334
+ result["output"] = str(output)
335
+ emit(result)
336
+ return 0 if result["passed"] else 1
337
+ if args.sections_command == "fanout-validate":
338
+ registry = json.loads(Path(args.registry).resolve().read_text(encoding="utf-8"))
339
+ fanout = json.loads(Path(args.fanout_file).resolve().read_text(encoding="utf-8"))
340
+ checked = validate_registry(registry)
341
+ result = {**validate_fanout(registry, fanout), "registry": checked}
342
+ emit(result)
343
+ return 0 if result["passed"] and checked["passed"] else 1
344
+ if args.sections_command == "reconcile":
345
+ current = json.loads(Path(args.current_file).resolve().read_text(encoding="utf-8"))
346
+ desired = json.loads(Path(args.desired_file).resolve().read_text(encoding="utf-8"))
347
+ result = reconcile_fields(current, desired)
348
+ if result["passed"]:
349
+ require_confirm(args)
350
+ output = Path(args.output).resolve()
351
+ if output.exists() and not args.force:
352
+ raise ValueError(f"output exists; use --force to replace: {output}")
353
+ output.parent.mkdir(parents=True, exist_ok=True)
354
+ output.write_text(json.dumps(result["result"], indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
355
+ result["output"] = str(output)
356
+ emit(result)
357
+ return 0 if result["passed"] else 1
358
+ if args.sections_command == "locale-validate":
359
+ sections = json.loads(Path(args.sections_file).resolve().read_text(encoding="utf-8"))
360
+ translations = json.loads(Path(args.translations_file).resolve().read_text(encoding="utf-8"))
361
+ result = validate_locale_coverage(args.page_id, sections, translations, args.locale)
362
+ emit(result)
363
+ return 0 if result["passed"] else 1
322
364
  registry = json.loads(Path(args.registry).resolve().read_text(encoding="utf-8"))
323
365
  checked = validate_registry(registry)
324
366
  if not checked["passed"]:
@@ -329,14 +371,31 @@ def command_sections(args: argparse.Namespace) -> int:
329
371
  return 0
330
372
  if args.sections_command == "validate":
331
373
  sections = json.loads(Path(args.sections_file).resolve().read_text(encoding="utf-8"))
332
- result = {**checked, "copy": copy_notes(registry, sections)}
374
+ result = {**checked, "copy": copy_notes(registry, sections), "values": validate_values(registry, sections)}
333
375
  emit(result)
334
- return 0 if result["copy"]["passed"] else 1
376
+ return 0 if result["copy"]["passed"] and result["values"]["passed"] else 1
335
377
  sections = json.loads(Path(args.sections_file).resolve().read_text(encoding="utf-8"))
336
378
  emit(section_id_migration(args.page_id, sections))
337
379
  return 0
338
380
 
339
381
 
382
+ def command_components_audit(args: argparse.Namespace) -> int:
383
+ bindings = json.loads(Path(args.bindings_file).resolve().read_text(encoding="utf-8"))
384
+ sections = json.loads(Path(args.sections_file).resolve().read_text(encoding="utf-8"))
385
+ pages = json.loads(Path(args.pages_file).resolve().read_text(encoding="utf-8"))
386
+ result = classify_bindings(
387
+ bindings if isinstance(bindings, list) else bindings.get("bindings", []),
388
+ set(sections if isinstance(sections, list) else sections.get("paths", [])),
389
+ set(pages if isinstance(pages, list) else pages.get("paths", [])),
390
+ )
391
+ if args.output:
392
+ output = Path(args.output).resolve(); output.parent.mkdir(parents=True, exist_ok=True)
393
+ output.write_text(json.dumps(result, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
394
+ result["output"] = str(output)
395
+ emit(result)
396
+ return 0 if result["passed"] else 1
397
+
398
+
340
399
  def variant_store(args: argparse.Namespace) -> ServiceVariantStore:
341
400
  return ServiceVariantStore(project_root(args) / ".maggie" / "service-variants.json")
342
401
 
@@ -449,7 +508,25 @@ def parser() -> argparse.ArgumentParser:
449
508
  sections_prompt.set_defaults(func=command_sections)
450
509
  sections_keys = sections_sub.add_parser("keys")
451
510
  sections_keys.add_argument("--registry", required=True); sections_keys.add_argument("--sections-file", required=True); sections_keys.add_argument("--page-id", required=True)
511
+ sections_remap = sections_sub.add_parser("remap-translations", help="move an existing translation keyspace into stable section keys")
512
+ sections_remap.add_argument("--translations-file", required=True); sections_remap.add_argument("--mapping-file", required=True); sections_remap.add_argument("--output", required=True); sections_remap.add_argument("--force", action="store_true"); sections_remap.add_argument("--confirm", action="store_true")
513
+ sections_remap.set_defaults(func=command_sections)
514
+ sections_fanout = sections_sub.add_parser("fanout-validate", help="check registry type fan-out across host implementation surfaces")
515
+ sections_fanout.add_argument("--registry", required=True); sections_fanout.add_argument("--fanout-file", required=True)
516
+ sections_fanout.set_defaults(func=command_sections)
517
+ sections_reconcile = sections_sub.add_parser("reconcile", help="apply an idempotent compare-and-set JSON repair")
518
+ sections_reconcile.add_argument("--current-file", required=True); sections_reconcile.add_argument("--desired-file", required=True); sections_reconcile.add_argument("--output", required=True); sections_reconcile.add_argument("--force", action="store_true"); sections_reconcile.add_argument("--confirm", action="store_true")
519
+ sections_reconcile.set_defaults(func=command_sections)
520
+ sections_locale = sections_sub.add_parser("locale-validate", help="check locale sidecar coverage for stored image alt fields")
521
+ sections_locale.add_argument("--page-id", required=True); sections_locale.add_argument("--sections-file", required=True); sections_locale.add_argument("--translations-file", required=True); sections_locale.add_argument("--locale", action="append", required=True)
522
+ sections_locale.set_defaults(func=command_sections)
452
523
  sections_keys.set_defaults(func=command_sections)
524
+ components = sub.add_parser("components-audit", help="classify route component bindings against live section/page inventories")
525
+ components.add_argument("--bindings-file", required=True)
526
+ components.add_argument("--sections-file", required=True, help="JSON array/object of section-managed paths")
527
+ components.add_argument("--pages-file", required=True, help="JSON array/object of live page paths")
528
+ components.add_argument("--output")
529
+ components.set_defaults(func=command_components_audit)
453
530
  variant = sub.add_parser("variant", help="manage service variant lifecycle")
454
531
  variant_sub = variant.add_subparsers(dest="variant_command", required=True)
455
532
  create = variant_sub.add_parser("create"); create.add_argument("--project", default="."); create.add_argument("--service-id", required=True); create.add_argument("--variant-id", required=True); create.add_argument("--variant-type", required=True); create.add_argument("--locale", required=True); create.add_argument("--market", required=True); create.add_argument("--slug", required=True); create.add_argument("--title", required=True); create.add_argument("--facts", required=True); create.add_argument("--source-revision", required=True); create.add_argument("--canonical-variant-id"); create.add_argument("--cluster-link", action="append", default=[]); create.add_argument("--layout-family", default="service-default"); create.add_argument("--confirm", action="store_true")