@topy-ai/maggie 0.6.8 → 0.7.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 +39 -2
- package/README.zh-TW.md +1 -1
- package/bin/maggie.js +6 -2
- package/bundled-references/blog-translation-ingestion.md +52 -0
- package/bundled-skills/maggie-blog/SKILL.md +16 -0
- package/bundled-skills/maggie-content-localization/SKILL.md +41 -1
- package/bundled-skills/maggie-dash/SKILL.md +31 -0
- package/bundled-skills/maggie-deployment/SKILL.md +1 -1
- package/bundled-skills/maggie-design/SKILL.md +12 -1
- package/bundled-skills/maggie-memory/SKILL.md +7 -0
- package/bundled-skills/maggie-seo-geo/SKILL.md +75 -1
- package/bundled-skills/maggie-service-booking/SKILL.md +11 -1
- package/bundled-tools/clis/maggie_blog.py +13 -1
- package/bundled-tools/clis/maggie_browser_audit.py +78 -0
- package/bundled-tools/clis/maggie_dash.py +47 -0
- package/bundled-tools/clis/maggie_design.py +12 -0
- package/bundled-tools/clis/maggie_localization.py +42 -0
- package/bundled-tools/clis/maggie_memory.py +4 -1
- package/bundled-tools/clis/maggie_service_booking.py +3 -3
- package/bundled-tools/clis/site_audit.py +103 -8
- package/bundled-tools/runtime/browser_behavior.py +35 -0
- package/bundled-tools/runtime/browser_geometry.js +31 -0
- package/bundled-tools/runtime/localization_runner.py +113 -0
- package/bundled-tools/runtime/maggie_blog.py +66 -1
- package/bundled-tools/runtime/maggie_memory.py +7 -2
- package/bundled-tools/runtime/maggie_sitemap.py +16 -3
- package/bundled-tools/runtime/service_variants.py +152 -0
- package/bundled-tools/runtime/site_baseline.py +60 -0
- package/package.json +1 -1
- package/references/blog-translation-ingestion.md +52 -0
package/README.md
CHANGED
|
@@ -14,6 +14,16 @@ persistent project memory.
|
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
|
+
Working-tree additions (not yet released): `site-audit --crawl --save-baseline
|
|
18
|
+
FILE --reviewer NAME` records a reviewed site contract; `site-audit --crawl
|
|
19
|
+
--baseline FILE` fails on URL, metadata, HTML structure or copy changes.
|
|
20
|
+
Complete sitemap coverage is required and existing baselines cannot be
|
|
21
|
+
overwritten. This does not verify browser layout or source-only changes.
|
|
22
|
+
|
|
23
|
+
`localization generate` invokes a trusted project-supplied provider process
|
|
24
|
+
with explicit `--confirm`, resumable draft batches and no automatic publishing.
|
|
25
|
+
See the installed localization skill for the adapter protocol and examples.
|
|
26
|
+
|
|
17
27
|
```bash
|
|
18
28
|
npx @topy-ai/maggie init --agent codex
|
|
19
29
|
```
|
|
@@ -80,6 +90,7 @@ maggie cleanup --project . [--confirm]
|
|
|
80
90
|
maggie bootstrap interview | phase ...
|
|
81
91
|
maggie dash init | status | migrate
|
|
82
92
|
maggie dash transition ... # explicit content approval transition
|
|
93
|
+
maggie dash variant ... # service variant create/review/preview/publish
|
|
83
94
|
maggie clone ... # authorized homepage capture
|
|
84
95
|
maggie design ... # authorized interior-page design
|
|
85
96
|
maggie clone-to-template ... # URL → validated marketplace template
|
|
@@ -179,8 +190,8 @@ artifact schemas.
|
|
|
179
190
|
Recommended upgrade sequence for the current release:
|
|
180
191
|
|
|
181
192
|
```bash
|
|
182
|
-
npx @topy-ai/maggie@0.
|
|
183
|
-
npx @topy-ai/maggie@0.
|
|
193
|
+
npx @topy-ai/maggie@0.7.0 update --project . --force
|
|
194
|
+
npx @topy-ai/maggie@0.7.0 cleanup --project .
|
|
184
195
|
```
|
|
185
196
|
|
|
186
197
|
## MaggieDash lifecycle
|
|
@@ -419,6 +430,32 @@ Blog imports are idempotent and draft-first. Published slugs remain stable;
|
|
|
419
430
|
public feeds exclude drafts, and `maggie blog rollback --confirm` restores the
|
|
420
431
|
latest local content backup.
|
|
421
432
|
|
|
433
|
+
In the working tree, opt-in blog translation uses `autoTranslateEnabled` and
|
|
434
|
+
`translationLocales` in `.maggie/blog/settings.json`. Process or retry persisted
|
|
435
|
+
posts without a new pull:
|
|
436
|
+
|
|
437
|
+
```bash
|
|
438
|
+
maggie blog translate-pending --project . \
|
|
439
|
+
--adapter-command '["python3", "scripts/translation-provider.py"]' --confirm
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
The project supplies the trusted provider script. Completed locale/revision
|
|
443
|
+
tasks are skipped; failed work returns nonzero. Title, excerpt, body, topic
|
|
444
|
+
labels and supplied image alt text remain draft/non-indexable. Run one worker
|
|
445
|
+
per project; existing host HTTP schedulers require separate integration.
|
|
446
|
+
|
|
447
|
+
Browser behavior validation uses the shared gstack browser:
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
maggie browser-audit https://example.com \
|
|
451
|
+
--browse "$HOME/.codex/skills/gstack/browse/dist/browse" \
|
|
452
|
+
--output .maggie/browser-audit --required main --sticky header \
|
|
453
|
+
--viewport 390x844 --viewport 768x1024
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
It captures scroll/geometry samples and screenshots, then fails on hidden
|
|
457
|
+
required elements, horizontal overflow or invalid sticky positioning.
|
|
458
|
+
|
|
422
459
|
List every installed skill and command:
|
|
423
460
|
|
|
424
461
|
```bash
|
package/README.zh-TW.md
CHANGED
package/bin/maggie.js
CHANGED
|
@@ -65,7 +65,7 @@ Usage:
|
|
|
65
65
|
maggie list
|
|
66
66
|
maggie doctor [--project PATH]
|
|
67
67
|
maggie bootstrap interview [project]
|
|
68
|
-
maggie dash init --project PATH
|
|
68
|
+
maggie dash init|status|migrate|transition|variant --project PATH [options]
|
|
69
69
|
maggie dash status --project PATH
|
|
70
70
|
maggie dash migrate --project PATH --confirm
|
|
71
71
|
maggie content FILE --source PROVIDER --project PATH --confirm
|
|
@@ -110,10 +110,13 @@ Usage:
|
|
|
110
110
|
maggie release PATH --environment staging --target vps-with-cloudflare-dns
|
|
111
111
|
maggie api lifecycle --project PATH [--execute --allow-quota]
|
|
112
112
|
maggie memory <init|list|search|context|add|record-error|transition|export> --project PATH
|
|
113
|
-
maggie localization <plan|preview|validate|review|publish|stale|glossary> [options]
|
|
113
|
+
maggie localization <extract|plan|generate|preview|validate|review|publish|stale|glossary> [options]
|
|
114
114
|
maggie seo performance|images|sitemap [options]
|
|
115
115
|
maggie feedback <collect|preview|submit|list> [options]
|
|
116
116
|
maggie site-audit URL [--crawl] [--languages en-GB,es-MX,ja-JP] [--check-hreflang]
|
|
117
|
+
maggie site-audit URL --crawl --save-baseline FILE --reviewer NAME
|
|
118
|
+
maggie site-audit URL --crawl --baseline FILE
|
|
119
|
+
maggie browser-audit URL --browse PATH --output DIR --required SELECTOR [--sticky SELECTOR]
|
|
117
120
|
maggie ops audit --project PATH
|
|
118
121
|
maggie ops preflight --project PATH --write
|
|
119
122
|
|
|
@@ -390,6 +393,7 @@ try {
|
|
|
390
393
|
else if (command === "localization") workflowCli("maggie_localization.py", args);
|
|
391
394
|
else if (command === "feedback") workflowCli("maggie_feedback.py", args);
|
|
392
395
|
else if (command === "site-audit") workflowCli("site_audit.py", args);
|
|
396
|
+
else if (command === "browser-audit") workflowCli("maggie_browser_audit.py", args);
|
|
393
397
|
else if (command === "dash") workflowCli("maggie_dash.py", args);
|
|
394
398
|
else if (command === "content") workflowCli("maggie_content.py", args);
|
|
395
399
|
else if (command === "init" || command === "install") install(args);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Blog translation ingestion contract
|
|
2
|
+
|
|
3
|
+
## Entry points and ownership
|
|
4
|
+
|
|
5
|
+
List every ingest entry point before enabling automatic translation: manual
|
|
6
|
+
CLI, admin HTTP action, scheduled tick, webhook and backfill. Each must use
|
|
7
|
+
the same post-persistence scheduling function. Record the caller and source
|
|
8
|
+
revision in the run evidence. A hook in a standalone script is insufficient
|
|
9
|
+
evidence for a scheduled HTTP path.
|
|
10
|
+
|
|
11
|
+
The packaged Python BlogStore reconciles durable translation tasks after source
|
|
12
|
+
persistence when `autoTranslateEnabled` is true and `translationLocales` contains
|
|
13
|
+
supported locale tags. The same reconciliation runs before translation retries,
|
|
14
|
+
so an interruption between source persistence and scheduling is recoverable.
|
|
15
|
+
Tasks are under `.maggie/blog/translations/`; content changes create new task
|
|
16
|
+
identities, and only current identities are processed. Old drafts are retained
|
|
17
|
+
for inspection, not eligible for automatic publication.
|
|
18
|
+
|
|
19
|
+
Run `maggie blog translate-pending --project . --adapter-command
|
|
20
|
+
'["python3", "scripts/translation-provider.py"]' --confirm` to generate drafts
|
|
21
|
+
from saved sources without re-pulling. The project supplies a trusted adapter
|
|
22
|
+
using the localization provider protocol. It receives title, excerpt, body,
|
|
23
|
+
topic labels and supplied image alt text. Completed tasks are skipped; failures
|
|
24
|
+
return partial/nonzero and store only an error category. Use one worker per
|
|
25
|
+
project. Host database/API schedulers must explicitly integrate this boundary;
|
|
26
|
+
installing the skill does not patch an existing host ingestion implementation.
|
|
27
|
+
|
|
28
|
+
## Durable work
|
|
29
|
+
|
|
30
|
+
After source persistence, enqueue translation work using an idempotency key
|
|
31
|
+
of project, content ID, source revision, target locale and operation. The
|
|
32
|
+
source write and scheduling intent must commit together or use a reconciliation
|
|
33
|
+
pass that detects missing intents. A repeat pull must retry pending/failed
|
|
34
|
+
translation without purchasing another upstream pull or duplicating completed
|
|
35
|
+
work. A changed source revision invalidates earlier translation work.
|
|
36
|
+
|
|
37
|
+
Track pending, running, succeeded and failed states with attempt count and
|
|
38
|
+
safe error category. A run with failed required translations is partial, not
|
|
39
|
+
completed. Missing credentials or provider availability must be visible;
|
|
40
|
+
never print credentials or provider payloads in the report. Keep translated
|
|
41
|
+
content draft and non-indexable until its validation and review pass.
|
|
42
|
+
|
|
43
|
+
## Acceptance evidence
|
|
44
|
+
|
|
45
|
+
Exercise the actual CLI and scheduled HTTP entry points against fixture
|
|
46
|
+
providers. With translation enabled, both must schedule identical work for
|
|
47
|
+
the same source revision. With translation disabled, neither schedules work.
|
|
48
|
+
Test provider failure, process interruption, retry without re-pull, duplicate
|
|
49
|
+
delivery, changed source revision, multiple target locales, media alt text and
|
|
50
|
+
topic labels. Assert persisted target output and queue state, not merely the
|
|
51
|
+
presence of a translate function name. Browser preview must show the target
|
|
52
|
+
locale before publication is claimed complete.
|
|
@@ -11,6 +11,22 @@ project lessons, never raw provider credentials or temporary content facts.
|
|
|
11
11
|
|
|
12
12
|
# Maggie Blog
|
|
13
13
|
|
|
14
|
+
## Automatic translation across ingest paths
|
|
15
|
+
|
|
16
|
+
Follow the [translation ingestion contract](../../references/blog-translation-ingestion.md)
|
|
17
|
+
when adding host auto-translation. Inventory manual CLI, admin API and scheduler
|
|
18
|
+
entry points and connect each to the same durable translation scheduling step.
|
|
19
|
+
Verify the actual scheduled path with a fixture provider and failure/retry
|
|
20
|
+
tests. An enabled setting or successful manual run does not prove scheduled
|
|
21
|
+
translation works. Packaged BlogStore reconciles translation tasks when settings
|
|
22
|
+
enable `autoTranslateEnabled` with supported `translationLocales`. Run
|
|
23
|
+
`maggie blog translate-pending --project . --adapter-command
|
|
24
|
+
'["python3", "scripts/translation-provider.py"]' --confirm` with a trusted
|
|
25
|
+
project-supplied adapter to process saved posts without re-pulling. Completed
|
|
26
|
+
locale/revision tasks are skipped; failed tasks retry from checkpoints. Run one
|
|
27
|
+
worker per project. Outputs stay draft and non-indexable. Existing host HTTP
|
|
28
|
+
and scheduler integrations still need separate implementation and tests.
|
|
29
|
+
|
|
14
30
|
Use the stable CLI to initialize a local blog contract, ingest versioned local
|
|
15
31
|
content, validate lifecycle invariants, publish with an actor and reason, and
|
|
16
32
|
generate route/feed artifacts. Read the host framework and database contract
|
|
@@ -2,11 +2,51 @@
|
|
|
2
2
|
name: maggie-content-localization
|
|
3
3
|
description: Manage translation, polish, rewrite, market localization, review, stale detection, and publishing for pages, guides, posts, services, products, and categories.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 1.
|
|
5
|
+
version: 1.3.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maggie Content Localization
|
|
9
9
|
|
|
10
|
+
## Generation responsibility and operation contract
|
|
11
|
+
|
|
12
|
+
`plan` records `generationContract.mode`, `preserveMeaning`,
|
|
13
|
+
`allowStructuralRewrite`, and `marketAdaptation`. Rewrite permits structural
|
|
14
|
+
changes; translate and polish preserve meaning; localise enables market
|
|
15
|
+
adaptation. A host generation adapter or the authoring agent must apply these
|
|
16
|
+
instructions when producing copy. Planning does not invoke a model or generate
|
|
17
|
+
translations. Validation currently checks mode consistency and rewrite
|
|
18
|
+
permission, not whether prose was actually restructured. Review the produced
|
|
19
|
+
copy against the requested operation before approval.
|
|
20
|
+
|
|
21
|
+
### Resumable draft generation
|
|
22
|
+
|
|
23
|
+
After extracting source strings and planning a job with `--source`, run:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
maggie localization generate .maggie/localization/<job>.json \
|
|
27
|
+
--project . --source .maggie/localization/source.json \
|
|
28
|
+
--output .maggie/localization/generation/<job>.json \
|
|
29
|
+
--adapter-command '["python3", "scripts/translation-provider.py"]' --confirm
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The adapter script is project-supplied, not bundled. Only execute a trusted
|
|
33
|
+
adapter: it receives source text and may call a paid external provider.
|
|
34
|
+
The JSON argv array executes without a shell, relative to `--project`.
|
|
35
|
+
Its stdin is a `maggie-provider-request.v1` object with `strings` and `contract`
|
|
36
|
+
(job identity, source revision, languages, locale, market and operation).
|
|
37
|
+
Its stdout must contain only a JSON object mapping every supplied string ID
|
|
38
|
+
to nonempty translated text. Exit nonzero on provider failure; provider logs
|
|
39
|
+
are suppressed from CLI errors to avoid exposing content or credentials.
|
|
40
|
+
|
|
41
|
+
`--character-budget` defaults to 8000 source characters per batch (a single
|
|
42
|
+
larger string remains intact); `--timeout` defaults to 120 seconds per call.
|
|
43
|
+
Malformed output splits batches until individual strings; provider failures
|
|
44
|
+
save completed batches for retry. Rerun with the same output to resume; changed
|
|
45
|
+
source or operation requires a new output. Run only one process per output.
|
|
46
|
+
The output is a draft checkpoint, not an approved translation: this command
|
|
47
|
+
does not edit project files, update the job, publish, or prove semantic quality.
|
|
48
|
+
Host ingestion integration and operation-quality verification remain under #28.
|
|
49
|
+
|
|
10
50
|
Localization job filenames are derived from content identity, but content IDs
|
|
11
51
|
are data, not paths. The CLI sanitizes separators and adds a short identity
|
|
12
52
|
hash when needed, so IDs such as `site.example/static-pages` always produce a
|
|
@@ -33,6 +33,37 @@ Valid transitions and statuses are owned by the MaggieDash storage contract;
|
|
|
33
33
|
do not edit the database directly. A transition is an auditable state change,
|
|
34
34
|
not a publish shortcut.
|
|
35
35
|
|
|
36
|
+
## Service variants
|
|
37
|
+
|
|
38
|
+
Service variants use `.maggie/service-variants.json` so location, event and
|
|
39
|
+
holiday pages retain service identity and canonical ownership:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
maggie dash variant create --project . --service-id massage \
|
|
43
|
+
--variant-id massage-london --variant-type location --locale en-GB --market uk \
|
|
44
|
+
--slug location/london --title "Massage in London" \
|
|
45
|
+
--facts '[{"key":"availability","value":"Weekdays"}]' \
|
|
46
|
+
--source-revision source-1 --confirm
|
|
47
|
+
maggie dash variant review --project . --variant-id massage-london \
|
|
48
|
+
--actor editor --reason "facts checked" --confirm
|
|
49
|
+
maggie dash variant preview --project . --variant-id massage-london \
|
|
50
|
+
--actor reviewer --reason "rendered QA" --preview-url http://localhost/preview --confirm
|
|
51
|
+
maggie dash variant publish --project . --variant-id massage-london \
|
|
52
|
+
--actor owner --reason "approved" --confirm
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The lifecycle is draft → review → approved → preview/published. Records carry
|
|
56
|
+
locale, market, source revision, provenance, canonical relationship, cluster
|
|
57
|
+
links, hreflang, facts and layout family. Translated variants cannot publish
|
|
58
|
+
before their canonical source. Slugs use `location/<slug>`, `event/<slug>`, or
|
|
59
|
+
`holiday/<slug>`; collisions fail. `variant slug-plan` creates a reviewed
|
|
60
|
+
redirect with owner approval before an indexed URL changes. `variant validate`
|
|
61
|
+
checks facts, canonical relation, source similarity and layout metadata.
|
|
62
|
+
With `--render-report report.json`, it additionally requires schema
|
|
63
|
+
`maggie-service-variant-render.v1`, a passing report, and matching layout
|
|
64
|
+
family, locale and canonical URL. Metadata validation alone is not a rendering
|
|
65
|
+
claim.
|
|
66
|
+
|
|
36
67
|
All mutating commands require `--confirm`. The default development adapter is
|
|
37
68
|
SQLite; production adapters must satisfy the same versioned contracts.
|
|
38
69
|
|
|
@@ -2,11 +2,22 @@
|
|
|
2
2
|
name: maggie-design
|
|
3
3
|
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.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 1.
|
|
5
|
+
version: 1.4.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maggie Design
|
|
9
9
|
|
|
10
|
+
## In-place route evidence
|
|
11
|
+
|
|
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
|
|
16
|
+
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.
|
|
20
|
+
|
|
10
21
|
## Source-to-runtime icon inventory
|
|
11
22
|
|
|
12
23
|
Before release, inventory icon names in source and compare them with the
|
|
@@ -38,6 +38,13 @@ truth, content state, or audit log.
|
|
|
38
38
|
|
|
39
39
|
## Commands
|
|
40
40
|
|
|
41
|
+
`add` defaults to `candidate` and reports that the item is excluded from
|
|
42
|
+
`search` and `context`. Inspect it with `maggie memory list --status candidate
|
|
43
|
+
--project .`; after review, run `maggie memory transition --project . <kind>
|
|
44
|
+
<id> active`. Explicit status listing supports query and skill filters while
|
|
45
|
+
preserving project scope. Expired active items remain inspectable through
|
|
46
|
+
explicit status listing but are excluded from normal context.
|
|
47
|
+
|
|
41
48
|
```bash
|
|
42
49
|
maggie memory init --project .
|
|
43
50
|
maggie memory context --project . --skill maggie-clone
|
|
@@ -2,11 +2,85 @@
|
|
|
2
2
|
name: maggie-seo-geo
|
|
3
3
|
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.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 1.
|
|
5
|
+
version: 1.1.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maggie SEO and GEO
|
|
9
9
|
|
|
10
|
+
## Static audit and sitemap evidence
|
|
11
|
+
|
|
12
|
+
Run `maggie site-audit https://example.com --crawl --json` for sitemap-listed
|
|
13
|
+
pages. Homepage checks expose `robots_directive` and `robots_conflict`.
|
|
14
|
+
Crawl entries expose `robots` (no restrictive meta found) and
|
|
15
|
+
`robots_conflict` (a contradiction exists). Missing robots meta is allowed;
|
|
16
|
+
noindex/none/nofollow or contradictory index/follow directives fail the gate.
|
|
17
|
+
Crawled pages also inspect X-Robots-Tag headers, including bot-prefixed noindex.
|
|
18
|
+
Static audit success
|
|
19
|
+
does not establish Google indexing, scroll behavior, responsive visibility,
|
|
20
|
+
or translation quality. Record those as unverified without separate evidence.
|
|
21
|
+
|
|
22
|
+
Crawl reports include `summary.byLocaleTemplate` with total, passed, failed
|
|
23
|
+
and failed URLs per declared locale and template. Regions/scripts remain
|
|
24
|
+
distinct (for example en-GB versus en-US). A template is identified only when
|
|
25
|
+
the page has exactly one distinct `data-template` value; absent or ambiguous
|
|
26
|
+
markup is `unknown`, not an inferred route family. Static report evidence marks
|
|
27
|
+
`rendered` and `behavioral` as `not_run`; these groups do not prove responsive
|
|
28
|
+
or scroll behavior. Fetch failures remain visible in the unknown group.
|
|
29
|
+
|
|
30
|
+
Sitemap planning accepts TSV columns: content type, absolute URL, optional
|
|
31
|
+
lastmod, optional JSON object mapping locale tags to alternate URLs, such as
|
|
32
|
+
`{"en-GB":"https://example.com/post","zh-Hant":"https://example.com/zh/post"}`.
|
|
33
|
+
It emits `lastmod` and `xhtml:link rel="alternate"`. Supply truthful content
|
|
34
|
+
change dates; omit unknown dates. Alternates must currently use the approved
|
|
35
|
+
origin. Reciprocal locale coverage and date semantics require separate review.
|
|
36
|
+
|
|
37
|
+
## Freeze and compare a reviewed site
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
maggie site-audit https://example.com --crawl --json \
|
|
41
|
+
--save-baseline docs/seo-baseline-v1.json --reviewer maintainer
|
|
42
|
+
maggie site-audit https://example.com --crawl --json \
|
|
43
|
+
--baseline docs/seo-baseline-v1.json --output .maggie/seo-comparison.json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Only save after reviewing the launched site. Creation requires a passing,
|
|
47
|
+
complete sitemap crawl and never overwrites an existing file. Comparison exits
|
|
48
|
+
nonzero for added/removed URLs, changed metadata, response directives/redirect
|
|
49
|
+
targets, structured data, images, DOM structure or text. Text and structure
|
|
50
|
+
hashes catch served translation reversions and template swaps even when section
|
|
51
|
+
counts match. Reports list changed fields, not raw copy.
|
|
52
|
+
|
|
53
|
+
A crawl exceeding `--max-pages` fails instead of silently sampling; raise the
|
|
54
|
+
limit to cover the sitemap. Oversized responses fail instead of truncating.
|
|
55
|
+
For intentional changes, review the diff and create a new versioned baseline
|
|
56
|
+
with a reviewer. Commit the approved change and new contract together; never
|
|
57
|
+
automatically replace the baseline following failure. Dynamic dates, class
|
|
58
|
+
names and copy can produce legitimate differences requiring review.
|
|
59
|
+
|
|
60
|
+
This covers server-rendered sitemap pages, not CSS rendering, JavaScript-only
|
|
61
|
+
content, database translation keys or browser interactions. A source key change
|
|
62
|
+
is detected here only when it changes served content. Baselines contain site
|
|
63
|
+
metadata; do not publish private project contracts without permission.
|
|
64
|
+
|
|
65
|
+
## Browser behavior evidence
|
|
66
|
+
|
|
67
|
+
For a local or deployed page, run the browser audit with the shared gstack
|
|
68
|
+
`browse` binary:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
maggie browser-audit https://example.com --browse "$HOME/.codex/skills/gstack/browse/dist/browse" \
|
|
72
|
+
--output .maggie/browser-audit --required "main" \
|
|
73
|
+
--required "nav" --sticky "header" --viewport 390x844 --viewport 768x1024
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The command captures three real scroll positions per viewport, DOM geometry,
|
|
77
|
+
horizontal overflow, required-element visibility, sticky/fixed top-inset
|
|
78
|
+
behavior and a screenshot. It exits nonzero for missing/hidden elements, mixed
|
|
79
|
+
viewport samples, no scroll advance, overflow or sticky geometry failure. It
|
|
80
|
+
does not claim keyboard accessibility, network asset correctness or semantic
|
|
81
|
+
content quality unless those checks run separately. Use the installed browser
|
|
82
|
+
path explicitly when Bun is not in PATH.
|
|
83
|
+
|
|
10
84
|
## Automatic memory hook
|
|
11
85
|
|
|
12
86
|
Follow [Maggie Memory Hook](../../references/memory-hook.md) at invocation and completion.
|
|
@@ -2,11 +2,21 @@
|
|
|
2
2
|
name: maggie-service-booking
|
|
3
3
|
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.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 1.
|
|
5
|
+
version: 1.1.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maggie Service Booking
|
|
9
9
|
|
|
10
|
+
## Reviewed page relationships
|
|
11
|
+
|
|
12
|
+
Reviewed page selection preserves existing `supporting` relations, including
|
|
13
|
+
routes absent from the current source scan. It does not automatically create
|
|
14
|
+
supporting relations for all suggested candidates. Inspect each service's
|
|
15
|
+
`pages` after selection and validate the rendered links. The current selection
|
|
16
|
+
path writes `canonical` for selected pages; multiple selections can violate
|
|
17
|
+
the one-canonical invariant. Resolve roles and run validation before publishing.
|
|
18
|
+
Variant role assignment and layout/URL conventions remain tracked in issue #27.
|
|
19
|
+
|
|
10
20
|
## Automatic memory hook
|
|
11
21
|
|
|
12
22
|
Follow [Maggie Memory Hook](../../references/memory-hook.md) at invocation and completion.
|
|
@@ -10,11 +10,18 @@ from pathlib import Path
|
|
|
10
10
|
|
|
11
11
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "runtime"))
|
|
12
12
|
from maggie_blog import BlogStore # noqa: E402
|
|
13
|
+
from localization_runner import process_adapter
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def main() -> int:
|
|
16
17
|
parser = argparse.ArgumentParser(prog="maggie blog")
|
|
17
18
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
19
|
+
translate = sub.add_parser("translate-pending")
|
|
20
|
+
translate.add_argument("--project", type=Path, default=Path.cwd())
|
|
21
|
+
translate.add_argument("--adapter-command", required=True, help="trusted provider JSON argv array")
|
|
22
|
+
translate.add_argument("--timeout", type=int, default=120)
|
|
23
|
+
translate.add_argument("--character-budget", type=int, default=8000)
|
|
24
|
+
translate.add_argument("--confirm", action="store_true")
|
|
18
25
|
init = sub.add_parser("init"); init.add_argument("--project", type=Path, default=Path.cwd()); init.add_argument("--base-path", default="/our-blogs"); init.add_argument("--posts-per-page", type=int, default=9); init.add_argument("--confirm", action="store_true")
|
|
19
26
|
inspect = sub.add_parser("inspect"); inspect.add_argument("--project", type=Path, default=Path.cwd())
|
|
20
27
|
ingest = sub.add_parser("ingest"); ingest.add_argument("--project", type=Path, default=Path.cwd()); ingest.add_argument("--input", type=Path, required=True); ingest.add_argument("--source", default="local"); ingest.add_argument("--confirm", action="store_true")
|
|
@@ -25,10 +32,13 @@ def main() -> int:
|
|
|
25
32
|
rollback = sub.add_parser("rollback"); rollback.add_argument("--project", type=Path, default=Path.cwd()); rollback.add_argument("--backup"); rollback.add_argument("--confirm", action="store_true")
|
|
26
33
|
args = parser.parse_args()
|
|
27
34
|
store = BlogStore(args.project.resolve())
|
|
28
|
-
if args.command in {"init", "ingest", "publish", "rollback"} and not args.confirm:
|
|
35
|
+
if args.command in {"init", "ingest", "publish", "rollback", "translate-pending"} and not args.confirm:
|
|
29
36
|
print("CONFIRMATION_REQUIRED: rerun with --confirm", file=sys.stderr); return 2
|
|
30
37
|
try:
|
|
31
38
|
if args.command == "init": result = store.init(args.base_path, args.posts_per_page)
|
|
39
|
+
elif args.command == "translate-pending":
|
|
40
|
+
adapter = process_adapter(json.loads(args.adapter_command), args.project, args.timeout)
|
|
41
|
+
result = store.translate_pending(adapter, args.character_budget)
|
|
32
42
|
elif args.command == "inspect": result = {"settings": store.settings(), "posts": store.posts(), "runs": json.loads(store.runs_path.read_text(encoding="utf-8")) if store.runs_path.exists() else []}
|
|
33
43
|
elif args.command == "ingest":
|
|
34
44
|
payload = json.loads(args.input.read_text(encoding="utf-8"));
|
|
@@ -42,6 +52,8 @@ def main() -> int:
|
|
|
42
52
|
except (OSError, ValueError, json.JSONDecodeError) as error:
|
|
43
53
|
print(f"maggie-blog: {error}", file=sys.stderr); return 1
|
|
44
54
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
55
|
+
if args.command in {"ingest", "translate-pending"} and result.get("status") == "partial":
|
|
56
|
+
return 1
|
|
45
57
|
return 0 if args.command != "validate" or result.get("valid") else 1
|
|
46
58
|
|
|
47
59
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Capture real viewport/scroll evidence using an installed gstack browse CLI."""
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
RUNTIME = Path(__file__).resolve().parents[1] / "runtime"
|
|
11
|
+
sys.path.insert(0, str(RUNTIME))
|
|
12
|
+
from browser_behavior import validate_samples
|
|
13
|
+
from localization_runner import checkpoint
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def audit(args):
|
|
17
|
+
if urlparse(args.url).scheme not in {"http", "https", "file"}:
|
|
18
|
+
raise ValueError("URL must use http, https or file")
|
|
19
|
+
if not args.required:
|
|
20
|
+
raise ValueError("at least one --required selector is necessary")
|
|
21
|
+
output = args.output.resolve()
|
|
22
|
+
output.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
def call(*command):
|
|
24
|
+
result = subprocess.run([str(args.browse), *command], capture_output=True, text=True, timeout=45)
|
|
25
|
+
if result.returncode:
|
|
26
|
+
raise ValueError("browser command failed: " + command[0])
|
|
27
|
+
return result.stdout
|
|
28
|
+
report = {"schemaVersion": "maggie-browser-audit.v1", "url": args.url,
|
|
29
|
+
"passed": False, "viewports": [], "evidence": "browser-captured"}
|
|
30
|
+
try:
|
|
31
|
+
for index, viewport in enumerate(args.viewport or ["390x844", "768x1024", "1440x900"]):
|
|
32
|
+
width, height = [int(value) for value in viewport.split("x")]
|
|
33
|
+
if min(width, height) < 100:
|
|
34
|
+
raise ValueError("viewport dimensions must be at least 100")
|
|
35
|
+
call("viewport", viewport)
|
|
36
|
+
call("goto", args.url)
|
|
37
|
+
selectors = list(dict.fromkeys(args.required + args.sticky))
|
|
38
|
+
call("js", "window.__maggieAuditSelectors=" + json.dumps(selectors))
|
|
39
|
+
samples = []
|
|
40
|
+
for step, fraction in enumerate([0, 0.5, 0.9]):
|
|
41
|
+
call("js", f"window.scrollTo(0, (document.documentElement.scrollHeight-innerHeight)*{fraction})")
|
|
42
|
+
# Two animation frames allow layout/scroll handlers to settle.
|
|
43
|
+
call("js", "new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve(true))))")
|
|
44
|
+
sample = output / f"viewport-{index}-sample-{step}.json"
|
|
45
|
+
call("eval", str(RUNTIME / "browser_geometry.js"), "--out", str(sample), "--raw")
|
|
46
|
+
samples.append(json.loads(sample.read_text()))
|
|
47
|
+
screenshot = output / f"viewport-{index}.png"
|
|
48
|
+
call("screenshot", "--viewport", str(screenshot))
|
|
49
|
+
if not screenshot.is_file():
|
|
50
|
+
raise ValueError("browser did not create screenshot")
|
|
51
|
+
finding = validate_samples(samples, args.required, args.sticky)
|
|
52
|
+
report["viewports"].append({"viewport": viewport, "samples": samples,
|
|
53
|
+
"screenshot": str(screenshot), **finding})
|
|
54
|
+
report["passed"] = all(item["passed"] for item in report["viewports"])
|
|
55
|
+
finally:
|
|
56
|
+
checkpoint(output / "report.json", report)
|
|
57
|
+
print(json.dumps(report, indent=2))
|
|
58
|
+
return 0 if report["passed"] else 1
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def main():
|
|
62
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
63
|
+
parser.add_argument("url")
|
|
64
|
+
parser.add_argument("--browse", type=Path, required=True)
|
|
65
|
+
parser.add_argument("--output", type=Path, required=True, help="new directory for evidence")
|
|
66
|
+
parser.add_argument("--viewport", action="append")
|
|
67
|
+
parser.add_argument("--required", action="append", default=[])
|
|
68
|
+
parser.add_argument("--sticky", action="append", default=[])
|
|
69
|
+
args = parser.parse_args()
|
|
70
|
+
try:
|
|
71
|
+
return audit(args)
|
|
72
|
+
except (ValueError, OSError, subprocess.TimeoutExpired) as error:
|
|
73
|
+
print(f"browser-audit: {error}", file=sys.stderr)
|
|
74
|
+
return 1
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
raise SystemExit(main())
|
|
@@ -16,6 +16,7 @@ from pathlib import Path
|
|
|
16
16
|
|
|
17
17
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "runtime"))
|
|
18
18
|
from maggie_dash_store import MaggieDashStore # noqa: E402
|
|
19
|
+
from service_variants import ServiceVariantStore # noqa: E402
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def project_root(args: argparse.Namespace) -> Path:
|
|
@@ -92,6 +93,40 @@ def command_transition(args: argparse.Namespace) -> int:
|
|
|
92
93
|
store.close()
|
|
93
94
|
|
|
94
95
|
|
|
96
|
+
def variant_store(args: argparse.Namespace) -> ServiceVariantStore:
|
|
97
|
+
return ServiceVariantStore(project_root(args) / ".maggie" / "service-variants.json")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def command_variant(args: argparse.Namespace) -> int:
|
|
101
|
+
require_confirm(args)
|
|
102
|
+
store = variant_store(args)
|
|
103
|
+
if args.variant_command == "create":
|
|
104
|
+
result = store.create(service_id=args.service_id, variant_id=args.variant_id,
|
|
105
|
+
variant_type=args.variant_type, locale=args.locale, market=args.market,
|
|
106
|
+
slug=args.slug, title=args.title, facts=json.loads(args.facts),
|
|
107
|
+
source_revision=args.source_revision, canonical_variant_id=args.canonical_variant_id,
|
|
108
|
+
cluster_links=args.cluster_link, layout_family=args.layout_family)
|
|
109
|
+
elif args.variant_command == "edit":
|
|
110
|
+
result = store.edit(args.variant_id, title=args.title, facts=json.loads(args.facts),
|
|
111
|
+
source_revision=args.source_revision, actor=args.actor, reason=args.reason)
|
|
112
|
+
elif args.variant_command == "translate":
|
|
113
|
+
result = store.translate(args.source_variant_id, variant_id=args.variant_id,
|
|
114
|
+
locale=args.locale, market=args.market, title=args.title, facts=json.loads(args.facts),
|
|
115
|
+
source_revision=args.source_revision, actor=args.actor)
|
|
116
|
+
elif args.variant_command in {"review", "preview", "publish", "archive"}:
|
|
117
|
+
target = {"review": "review", "preview": "preview", "publish": "published", "archive": "archived"}[args.variant_command]
|
|
118
|
+
result = store.transition(args.variant_id, target, args.actor, args.reason, preview_url=getattr(args, "preview_url", None))
|
|
119
|
+
elif args.variant_command == "validate":
|
|
120
|
+
render_report = json.loads(Path(args.render_report).read_text(encoding="utf-8")) if args.render_report else None
|
|
121
|
+
result = store.validate(args.variant_id, args.source_text, render_report)
|
|
122
|
+
elif args.variant_command == "slug-plan":
|
|
123
|
+
result = store.plan_slug_change(args.variant_id, args.new_slug, args.actor)
|
|
124
|
+
else:
|
|
125
|
+
result = [item for item in store.data["variants"] if not args.variant_id or item["id"] == args.variant_id]
|
|
126
|
+
emit(result)
|
|
127
|
+
return 0 if not isinstance(result, dict) or result.get("passed", True) else 1
|
|
128
|
+
|
|
129
|
+
|
|
95
130
|
def parser() -> argparse.ArgumentParser:
|
|
96
131
|
parser = argparse.ArgumentParser(prog="maggie dash", description=__doc__)
|
|
97
132
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
@@ -120,6 +155,18 @@ def parser() -> argparse.ArgumentParser:
|
|
|
120
155
|
transition.add_argument("--reason", required=True)
|
|
121
156
|
transition.add_argument("--confirm", action="store_true")
|
|
122
157
|
transition.set_defaults(func=command_transition)
|
|
158
|
+
variant = sub.add_parser("variant", help="manage service variant lifecycle")
|
|
159
|
+
variant_sub = variant.add_subparsers(dest="variant_command", required=True)
|
|
160
|
+
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")
|
|
161
|
+
edit = variant_sub.add_parser("edit"); edit.add_argument("--project", default="."); edit.add_argument("--variant-id", required=True); edit.add_argument("--title", required=True); edit.add_argument("--facts", required=True); edit.add_argument("--source-revision", required=True); edit.add_argument("--actor", required=True); edit.add_argument("--reason", required=True); edit.add_argument("--confirm", action="store_true")
|
|
162
|
+
translate = variant_sub.add_parser("translate"); translate.add_argument("--project", default="."); translate.add_argument("--source-variant-id", required=True); translate.add_argument("--variant-id", required=True); translate.add_argument("--locale", required=True); translate.add_argument("--market", required=True); translate.add_argument("--title", required=True); translate.add_argument("--facts", required=True); translate.add_argument("--source-revision", required=True); translate.add_argument("--actor", required=True); translate.add_argument("--confirm", action="store_true")
|
|
163
|
+
for name in ("review", "preview", "publish", "archive"):
|
|
164
|
+
command = variant_sub.add_parser(name); command.add_argument("--project", default="."); command.add_argument("--variant-id", required=True); command.add_argument("--actor", required=True); command.add_argument("--reason", required=True); command.add_argument("--confirm", action="store_true")
|
|
165
|
+
if name == "preview": command.add_argument("--preview-url", required=True)
|
|
166
|
+
validate = variant_sub.add_parser("validate"); validate.add_argument("--project", default="."); validate.add_argument("--variant-id", required=True); validate.add_argument("--source-text"); validate.add_argument("--render-report"); validate.add_argument("--confirm", action="store_true")
|
|
167
|
+
plan_slug = variant_sub.add_parser("slug-plan"); plan_slug.add_argument("--project", default="."); plan_slug.add_argument("--variant-id", required=True); plan_slug.add_argument("--new-slug", required=True); plan_slug.add_argument("--actor", required=True); plan_slug.add_argument("--confirm", action="store_true")
|
|
168
|
+
inspect = variant_sub.add_parser("inspect"); inspect.add_argument("--project", default="."); inspect.add_argument("--variant-id"); inspect.add_argument("--confirm", action="store_true")
|
|
169
|
+
variant.set_defaults(func=command_variant)
|
|
123
170
|
return parser
|
|
124
171
|
|
|
125
172
|
|