@topy-ai/maggie 0.7.13 → 0.7.15
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 +43 -7
- package/README.zh-TW.md +11 -3
- package/bin/maggie.js +8 -5
- package/bundled-skills/README.md +1 -0
- package/bundled-skills/catalog.json +4 -0
- package/bundled-skills/maggie-blog/SKILL.md +13 -9
- package/bundled-skills/maggie-dash/SKILL.md +17 -2
- package/bundled-skills/maggie-qa-workflow/SKILL.md +102 -0
- package/bundled-skills/maggie-seo-geo/SKILL.md +21 -0
- package/bundled-tools/clis/maggie_blog.py +5 -1
- package/bundled-tools/clis/maggie_dash.py +14 -1
- package/bundled-tools/clis/maggie_indexnow.py +60 -0
- package/bundled-tools/clis/maggie_qa_workflow.py +367 -0
- package/bundled-tools/runtime/maggie_api_contract.py +97 -0
- package/bundled-tools/runtime/maggie_blog.py +42 -1
- package/bundled-tools/runtime/maggie_indexnow.py +128 -0
- package/bundled-tools/runtime/maggie_quality.py +21 -4
- package/bundled-tools/runtime/maggie_sitemap.py +52 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,31 @@ Then invoke the installed skills from your coding agent, for example:
|
|
|
58
58
|
|
|
59
59
|
Maggie keeps the existing project foundation and asks for decisions before
|
|
60
60
|
shared routes, analytics, or publishing boundaries change. The current
|
|
61
|
-
package ships
|
|
61
|
+
package ships 19 installable skills and a local-first MaggieDash foundation.
|
|
62
|
+
|
|
63
|
+
For repeatable browser QA, create a project-owned scenario manifest and record
|
|
64
|
+
the test, fix, and retest lifecycle:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
maggie qa start --project . \
|
|
68
|
+
--scenario-file .maggie/scenario-manifest.json \
|
|
69
|
+
--environment local --base-url http://localhost:4321
|
|
70
|
+
maggie qa record --project . --run <run-id> \
|
|
71
|
+
--scenario HOME-001 --phase test --status pass \
|
|
72
|
+
--summary "Homepage smoke passed" --evidence .maggie/qa/home.png
|
|
73
|
+
maggie qa summary --project . --run <run-id>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The QA workflow stores secret-free run state under `.maggie/qa-runs/` and
|
|
77
|
+
keeps project-specific scenarios and evidence outside the npm package.
|
|
78
|
+
|
|
79
|
+
The current package also includes reusable safeguards from the latest feedback
|
|
80
|
+
review: `maggie dash api-contract` checks declared request and 2xx response
|
|
81
|
+
shapes; `maggie blog check-gate`/`approve` enforces review before publish;
|
|
82
|
+
sitemap validation flags suspiciously uniform `lastmod` dates; `maggie seo
|
|
83
|
+
indexnow` plans only changed same-origin URLs and holds back URLs accepted in
|
|
84
|
+
the last 24 hours; and dash inventory separates renderer kind from public page
|
|
85
|
+
kind while reporting source coverage for code-rendered routes.
|
|
62
86
|
|
|
63
87
|
For Google integrations, validate a redacted provider matrix before reporting
|
|
64
88
|
access. The command fails closed on unknown scopes, missing Ads prerequisites,
|
|
@@ -105,7 +129,8 @@ maggie dash install | init | status | migrate | cms ...
|
|
|
105
129
|
maggie dash transition ... # explicit content approval transition
|
|
106
130
|
maggie dash variant ... # service variant create/review/preview/publish
|
|
107
131
|
maggie dash sections ... # field fan-out, locale, binding, media, copy, identity keys
|
|
108
|
-
maggie dash inventory ... # disjoint
|
|
132
|
+
maggie dash inventory ... # disjoint renderer/page-kind inventory + coverage
|
|
133
|
+
maggie dash api-contract ... # declared API body and 2xx response contract
|
|
109
134
|
maggie agent-content write ... # host-authorized, origin-bound content bridge
|
|
110
135
|
maggie verification coverage ... # changed surface/locale evidence gate
|
|
111
136
|
maggie clone ... # authorized homepage capture
|
|
@@ -114,11 +139,13 @@ maggie clone-to-template ... # URL → validated marketplace template
|
|
|
114
139
|
maggie marketplace ... # catalog and on-demand template workflow
|
|
115
140
|
maggie memory ... # confirmed preferences and lessons
|
|
116
141
|
maggie feedback ... # redact, preview, submit, list
|
|
142
|
+
maggie qa ... # scenario browser QA, fix/retest, release gate
|
|
117
143
|
maggie localization ... # plan, validate, review, publish, stale
|
|
118
144
|
maggie service ... # import, sync, generate, validate
|
|
119
145
|
maggie seo performance ... # sampled PageSpeed/CWV report and baseline
|
|
120
146
|
maggie seo images ... # inventory, variants, confirmation, validate
|
|
121
147
|
maggie seo sitemap ... # typed/semantic plan, agent-files, apply, rollback
|
|
148
|
+
maggie seo indexnow ... # changed URLs, key check, retry-safe 24h guard
|
|
122
149
|
maggie deployment | migration | release | analytics | schedule
|
|
123
150
|
maggie migration identity --identity-file FILE [--expected-file FILE]
|
|
124
151
|
maggie deployment canary --asset URL=SHA256 --render-report report.json
|
|
@@ -220,8 +247,8 @@ artifact schemas.
|
|
|
220
247
|
Recommended upgrade sequence for the current release:
|
|
221
248
|
|
|
222
249
|
```bash
|
|
223
|
-
npx @topy-ai/maggie@0.7.
|
|
224
|
-
npx @topy-ai/maggie@0.7.
|
|
250
|
+
npx @topy-ai/maggie@0.7.15 update --project . --force
|
|
251
|
+
npx @topy-ai/maggie@0.7.15 cleanup --project .
|
|
225
252
|
```
|
|
226
253
|
|
|
227
254
|
Maintainers should pass npm credentials through the repository helper, never
|
|
@@ -231,7 +258,12 @@ as a command-line argument:
|
|
|
231
258
|
node scripts/publish-npm.mjs --maggie-env-file ../.env
|
|
232
259
|
```
|
|
233
260
|
|
|
234
|
-
The 0.7.
|
|
261
|
+
The 0.7.15 workflow adds API contracts, blog review gates, sitemap freshness,
|
|
262
|
+
change-driven IndexNow, and page-kind/source-coverage inventory. It also keeps
|
|
263
|
+
the general `maggie-qa-workflow` skill and `maggie qa`
|
|
264
|
+
CLI for scenario manifests, secret-free browser evidence metadata, test/fix/
|
|
265
|
+
retest lifecycle, adjacent regression checks, and explicit release gates. The
|
|
266
|
+
0.7.13 workflow adds field-aware section fan-out and locale coverage,
|
|
235
267
|
sibling-copy/media checks, disjoint page inventory, binding validation,
|
|
236
268
|
idempotency and database-target identity gates, full W3C sitemap lastmod
|
|
237
269
|
validation, and the accepted `X-Robots-Tag: noindex` response contract. The
|
|
@@ -484,6 +516,7 @@ python3 tools/clis/maggie_design.py rebrand \
|
|
|
484
516
|
| `maggie-memory` | Persist confirmed preferences, conventions, lessons, and errors |
|
|
485
517
|
| `maggie-content-localization` | Manage locale-aware translation, review, provenance, stale state, and publication gates |
|
|
486
518
|
| `maggie-feedback` | Collect redacted feedback drafts and explicitly submit them to the NoBlox feedback endpoint |
|
|
519
|
+
| `maggie-qa-workflow` | Run scenario-based browser QA with evidence, fix/retest lifecycle, and release gates |
|
|
487
520
|
| `maggie-auth-reference` | Generate and validate traditional email/password auth with secure server-side sessions |
|
|
488
521
|
| `maggie-blog` | Run a provider-neutral blog lifecycle with stable identity, topics, feeds, settings, and rollback |
|
|
489
522
|
|
|
@@ -528,12 +561,13 @@ repairs with `maggie dash sections validate`, `fanout-validate`,
|
|
|
528
561
|
`maggie dash inventory` to classify published pages once into disjoint kinds.
|
|
529
562
|
See the [quality contract examples](https://github.com/TOPY-AI-LTD/ai-cmo-skills/blob/main/contracts/maggiedash/quality-contracts.md).
|
|
530
563
|
|
|
531
|
-
The package includes all
|
|
564
|
+
The package includes all 19 installable skills: `maggie-blog-bootstrap`,
|
|
532
565
|
`maggie-dash`, `maggie-clone`, `maggie-clone-to-template`, `maggie-marketplace`,
|
|
533
566
|
`maggie-template`, `maggie-design`, `maggie-ops`, `maggie-deployment`,
|
|
534
567
|
`maggie-project-context`, `maggie-seo-geo`, `maggie-social-share`,
|
|
535
568
|
`maggie-service-booking`, `maggie-memory`, `maggie-content-localization`,
|
|
536
|
-
`maggie-feedback`, `maggie-
|
|
569
|
+
`maggie-feedback`, `maggie-qa-workflow`, `maggie-auth-reference`, and
|
|
570
|
+
`maggie-blog`. Use the stable
|
|
537
571
|
commands below after installation:
|
|
538
572
|
|
|
539
573
|
```bash
|
|
@@ -543,6 +577,8 @@ maggie design author --project . --route /about --purpose "Explain our approach"
|
|
|
543
577
|
maggie blog init --project . --confirm
|
|
544
578
|
maggie blog ingest --project . --source local --input content/posts.json --confirm
|
|
545
579
|
maggie blog validate --project .
|
|
580
|
+
maggie blog check-gate --project .
|
|
581
|
+
maggie blog approve --project . --slug example-post --actor reviewer --reason "reviewed" --confirm
|
|
546
582
|
maggie blog sitemap --project .
|
|
547
583
|
```
|
|
548
584
|
|
package/README.zh-TW.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
[English README](README.md) · [完整繁體中文 README](https://github.com/TOPY-AI-LTD/ai-cmo-skills/blob/main/README.zh-TW.md)
|
|
4
4
|
|
|
5
|
-
`@topy-ai/maggie` 提供
|
|
5
|
+
`@topy-ai/maggie` 提供 19 個可安裝的 AI website workflow skills,支援
|
|
6
6
|
Codex、Claude Code 與相容的 coding agents。
|
|
7
7
|
|
|
8
8
|
## 安裝
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
npx @topy-ai/maggie@0.7.
|
|
11
|
+
npx @topy-ai/maggie@0.7.15 init --agent all
|
|
12
12
|
npx @topy-ai/maggie doctor --project .
|
|
13
13
|
```
|
|
14
14
|
|
|
@@ -25,6 +25,9 @@ maggie doctor --project . --require-bootstrap --strict
|
|
|
25
25
|
deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
|
|
26
26
|
publish 與 production deployment 需要明確確認。
|
|
27
27
|
|
|
28
|
+
0.7.15 也加入 API schema contract、blog review gate、sitemap freshness
|
|
29
|
+
warning、change-driven IndexNow 與 page-kind/source-coverage inventory。
|
|
30
|
+
|
|
28
31
|
MaggieDash 也提供穩定 section identity、可重用 section arrangement、機器可讀
|
|
29
32
|
section registry、短期 agent content bridge,以及 translation out-of-band write
|
|
30
33
|
後的 restart gate。`maggie doctor` 會比較 install manifest 與磁碟上的實際 skills。
|
|
@@ -50,8 +53,13 @@ maggie dash inventory --pages-file .maggie/published-pages.json
|
|
|
50
53
|
# Migration target identity (never prints or stores a database URL)
|
|
51
54
|
maggie migration identity --identity-file .maggie/db-identity.json \
|
|
52
55
|
--expected-file .maggie/service-db-identity.json
|
|
56
|
+
|
|
57
|
+
# Scenario browser QA
|
|
58
|
+
maggie qa start --project . --scenario-file .maggie/scenario-manifest.json \
|
|
59
|
+
--environment local --base-url http://localhost:4321
|
|
60
|
+
maggie qa summary --project . --run <run-id>
|
|
53
61
|
```
|
|
54
62
|
|
|
55
|
-
完整中文說明、
|
|
63
|
+
完整中文說明、19 個 skills 清單和 roadmap:
|
|
56
64
|
[繁體中文 README](https://github.com/TOPY-AI-LTD/ai-cmo-skills/blob/main/README.zh-TW.md)
|
|
57
65
|
· [Roadmap](https://github.com/TOPY-AI-LTD/ai-cmo-skills/blob/main/ROADMAP.md)
|
package/bin/maggie.js
CHANGED
|
@@ -59,7 +59,8 @@ Usage:
|
|
|
59
59
|
maggie bootstrap interview [project]
|
|
60
60
|
maggie dash init|install|status|migrate|transition|variant|cms --project PATH [options]
|
|
61
61
|
maggie dash sections <validate|prompt|keys|remap-translations|fanout-validate|locale-validate|variant-copy-validate|media-validate|bindings-validate|idempotency-validate|reconcile> [options]
|
|
62
|
-
maggie dash inventory --pages-file FILE
|
|
62
|
+
maggie dash inventory --pages-file FILE [--require-page-kinds] [--require-source-coverage]
|
|
63
|
+
maggie dash api-contract --spec FILE
|
|
63
64
|
maggie dash components-audit --bindings-file FILE --sections-file FILE --pages-file FILE
|
|
64
65
|
maggie dash status --project PATH
|
|
65
66
|
maggie dash migrate --project PATH --confirm
|
|
@@ -92,7 +93,7 @@ Usage:
|
|
|
92
93
|
maggie design step <job-id> --step NAME --evidence FILE
|
|
93
94
|
maggie auth reference --project PATH --confirm
|
|
94
95
|
maggie auth check --project PATH [--production]
|
|
95
|
-
maggie blog init|inspect|ingest|validate|publish|sitemap|settings|rollback|integration-state
|
|
96
|
+
maggie blog init|inspect|ingest|validate|check-gate|approve|publish|sitemap|settings|rollback|integration-state
|
|
96
97
|
maggie design status <job-id>
|
|
97
98
|
maggie service import <provider-url> --project PATH
|
|
98
99
|
maggie service sync <provider-url> --project PATH
|
|
@@ -112,8 +113,9 @@ Usage:
|
|
|
112
113
|
maggie api lifecycle --project PATH [--execute --allow-quota]
|
|
113
114
|
maggie memory <init|list|search|context|add|record-error|transition|export> --project PATH
|
|
114
115
|
maggie localization <extract|plan|generate|preview|validate|review|publish|stale|glossary> [options]
|
|
115
|
-
maggie seo performance|images|sitemap [options] (sitemap supports strict validate and agent-files)
|
|
116
|
+
maggie seo performance|images|sitemap|indexnow [options] (sitemap supports strict validate and agent-files)
|
|
116
117
|
maggie feedback <collect|preview|submit|list> [options]
|
|
118
|
+
maggie qa <start|record|summary|export> [options]
|
|
117
119
|
maggie site-audit URL [--crawl] [--access-log FILE] [--require-sitemap-request] [--languages en-GB,es-MX,ja-JP] [--check-hreflang]
|
|
118
120
|
maggie site-audit URL --crawl --save-baseline FILE --reviewer NAME
|
|
119
121
|
maggie site-audit URL --crawl --baseline FILE
|
|
@@ -300,8 +302,8 @@ function service(args) {
|
|
|
300
302
|
|
|
301
303
|
function seo(args) {
|
|
302
304
|
const command = args[0];
|
|
303
|
-
const scripts = { performance: "maggie_performance.py", images: "maggie_images.py", sitemap: "maggie_sitemap.py" };
|
|
304
|
-
if (!scripts[command]) throw new Error("seo command must be performance, images, or
|
|
305
|
+
const scripts = { performance: "maggie_performance.py", images: "maggie_images.py", sitemap: "maggie_sitemap.py", indexnow: "maggie_indexnow.py" };
|
|
306
|
+
if (!scripts[command]) throw new Error("seo command must be performance, images, sitemap, or indexnow");
|
|
305
307
|
workflowCli(scripts[command], args.slice(1));
|
|
306
308
|
}
|
|
307
309
|
|
|
@@ -406,6 +408,7 @@ try {
|
|
|
406
408
|
else if (command === "memory") workflowCli("maggie_memory.py", args);
|
|
407
409
|
else if (command === "localization") workflowCli("maggie_localization.py", args);
|
|
408
410
|
else if (command === "feedback") workflowCli("maggie_feedback.py", args);
|
|
411
|
+
else if (command === "qa") workflowCli("maggie_qa_workflow.py", args);
|
|
409
412
|
else if (command === "site-audit") workflowCli("site_audit.py", args);
|
|
410
413
|
else if (command === "browser-audit") workflowCli("maggie_browser_audit.py", args);
|
|
411
414
|
else if (command === "verification") workflowCli("maggie_verification.py", args);
|
package/bundled-skills/README.md
CHANGED
|
@@ -20,6 +20,7 @@ Skills are the agent-facing workflows. They compose with the tools in
|
|
|
20
20
|
| `maggie-memory` | Persist confirmed preferences, project conventions, lessons, and error history across skill runs | local `.maggie/memory/` state |
|
|
21
21
|
| `maggie-content-localization` | Plan, validate, review, publish, and age localized content with market, locale, provenance, and translation safeguards | localization contract, locale CLI |
|
|
22
22
|
| `maggie-feedback` | Collect, redact, review, and explicitly submit feedback from skill runs | feedback CLI, hosted endpoint, GitHub Issue Forms |
|
|
23
|
+
| `maggie-qa-workflow` | Run scenario-based browser QA with evidence, fix/retest lifecycle, and release gates | browser adapter, QA workflow CLI, scenario manifest |
|
|
23
24
|
|
|
24
25
|
Read only the selected skill and its linked references for a task. Do not load
|
|
25
26
|
all skills as one undifferentiated prompt.
|
|
@@ -58,6 +58,10 @@
|
|
|
58
58
|
"name": "maggie-project-context",
|
|
59
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
60
|
},
|
|
61
|
+
{
|
|
62
|
+
"name": "maggie-qa-workflow",
|
|
63
|
+
"description": "Run scenario-based browser QA with explicit evidence, fix/retest lifecycle, and release-gate decisions for web projects."
|
|
64
|
+
},
|
|
61
65
|
{
|
|
62
66
|
"name": "maggie-seo-geo",
|
|
63
67
|
"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."
|
|
@@ -27,7 +27,7 @@ locale/revision tasks are skipped; failed tasks retry from checkpoints. Run one
|
|
|
27
27
|
worker per project. Outputs stay draft and non-indexable. Existing host HTTP
|
|
28
28
|
and scheduler integrations still need separate implementation and tests.
|
|
29
29
|
|
|
30
|
-
Use the stable CLI to initialize a local blog contract, ingest versioned local
|
|
30
|
+
Use the stable CLI to initialize a local blog contract, ingest versioned local
|
|
31
31
|
content, validate lifecycle invariants, publish with an actor and reason, and
|
|
32
32
|
generate route/feed artifacts. Read the host framework and database contract
|
|
33
33
|
before adding public routes. The host project owns rendering, persistence
|
|
@@ -35,19 +35,23 @@ credentials, and deployment; this skill owns normalized blog semantics.
|
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
maggie blog init --project . --base-path /our-blogs --confirm
|
|
38
|
-
maggie blog ingest --project . --source local --input content/posts.json --confirm
|
|
39
|
-
maggie blog validate --project .
|
|
40
|
-
maggie blog
|
|
38
|
+
maggie blog ingest --project . --source local --input content/posts.json --confirm
|
|
39
|
+
maggie blog validate --project .
|
|
40
|
+
maggie blog check-gate --project .
|
|
41
|
+
maggie blog approve --project . --slug example-post --actor reviewer --reason "reviewed" --confirm
|
|
42
|
+
maggie blog publish --project . --slug example-post --actor owner --reason "approved" --confirm
|
|
41
43
|
maggie blog sitemap --project .
|
|
42
44
|
maggie blog settings --project .
|
|
43
45
|
maggie blog rollback --project . --confirm
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
Posts are draft-first.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
Posts are draft-first. `maggie blog check-gate` reports the publication policy;
|
|
49
|
+
the default requires `requireReview=true`, disables auto-publish, and forces an
|
|
50
|
+
explicit `approve` transition before `publish`. Stable `contentId` is the
|
|
51
|
+
ingest identity and a published slug must not change during a rewrite.
|
|
52
|
+
Search/sort views are not indexable; drafts never appear in public routes, RSS,
|
|
53
|
+
or sitemap output. Provider keys remain server-side. Public publication and
|
|
54
|
+
migrations always require explicit confirmation.
|
|
51
55
|
|
|
52
56
|
To initialize native front-end pages from the approved local UI guideline,
|
|
53
57
|
run `maggie-design`:
|
|
@@ -200,10 +200,25 @@ maggie dash sections bindings-validate --sections-file .maggie/sections.json \
|
|
|
200
200
|
--references-file .maggie/reference-inventory.json
|
|
201
201
|
maggie dash sections idempotency-validate --contract-file .maggie/reconcile-contract.json
|
|
202
202
|
|
|
203
|
-
#
|
|
204
|
-
maggie dash
|
|
203
|
+
# Validate API body/response shapes before a client writes against an endpoint:
|
|
204
|
+
maggie dash api-contract --spec .maggie/openapi.json
|
|
205
|
+
|
|
206
|
+
# Classify every published page exactly once. `pageKind` describes the public
|
|
207
|
+
# page family; `kind`/`rendererKind` describes how it is rendered. Keep these
|
|
208
|
+
# axes separate and require explicit source coverage when database rows do not
|
|
209
|
+
# contain code-rendered routes:
|
|
210
|
+
maggie dash inventory --pages-file .maggie/published-pages.json \
|
|
211
|
+
--require-page-kinds --require-source-coverage
|
|
205
212
|
```
|
|
206
213
|
|
|
214
|
+
The API contract checker fails closed when a body or 2xx response schema is
|
|
215
|
+
missing. Intentional empty bodies must be explicit (`x-maggie-empty-request-body`
|
|
216
|
+
or `x-maggie-empty-response`) so a consumer does not guess field names.
|
|
217
|
+
Inventory reports `pageKindCounts` for service, variant, category hub,
|
|
218
|
+
ordinary, and blog routes, plus `sourceCoverage`. A host adapter must include
|
|
219
|
+
code-rendered routes in the input or declare coverage incomplete; a zero
|
|
220
|
+
database-row count is not evidence that no public routes exist.
|
|
221
|
+
|
|
207
222
|
The catalogue declares purpose, usage, placement, repeatability and layout
|
|
208
223
|
limits, renderer-owned examples, and the shape of repeated entries. A repeat
|
|
209
224
|
may contain an object (`title`, `body`, `href`, and so on), not just a count;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: maggie-qa-workflow
|
|
3
|
+
description: Run scenario-based browser QA with explicit evidence, fix/retest lifecycle, and release-gate decisions for web projects.
|
|
4
|
+
metadata:
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Maggie QA Workflow
|
|
9
|
+
|
|
10
|
+
Use this skill when a web project needs repeatable user-scenario testing that
|
|
11
|
+
connects browser evidence to a fix, a same-scenario retest, adjacent regression
|
|
12
|
+
checks, and an explicit Pass/Blocked/Fail release decision. It is framework
|
|
13
|
+
neutral and does not replace the project's browser-control or test tools.
|
|
14
|
+
Follow the shared [memory hook](../../references/memory-hook.md) before and
|
|
15
|
+
after the run; memory and feedback remain bounded by the privacy rules below.
|
|
16
|
+
|
|
17
|
+
## Prepare a scenario manifest
|
|
18
|
+
|
|
19
|
+
Create `.maggie/scenario-manifest.json` or pass another JSON file with a
|
|
20
|
+
non-empty `scenarios` array. Each scenario needs a unique `id`; useful fields
|
|
21
|
+
include `title`, `group`, `priority`, `routes`, `persona`, `auth`, `browser`,
|
|
22
|
+
and `viewport`. Keep project-specific scenarios in the project; do not ship
|
|
23
|
+
them in the Maggie package.
|
|
24
|
+
|
|
25
|
+
## Start and record a run
|
|
26
|
+
|
|
27
|
+
The CLI stores only secret-free run state under `.maggie/qa-runs/`. Screenshots
|
|
28
|
+
and logs stay in the project and are represented by relative paths and hashes.
|
|
29
|
+
Use a real browser adapter for visible interaction, computed layout,
|
|
30
|
+
console/network evidence, final URLs, and response status:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
maggie qa start --project . \
|
|
34
|
+
--scenario-file .maggie/scenario-manifest.json \
|
|
35
|
+
--run-id local-qa-001 --environment local \
|
|
36
|
+
--base-url http://localhost:4321 --browser chrome \
|
|
37
|
+
--commit "$(git rev-parse --short HEAD)"
|
|
38
|
+
|
|
39
|
+
maggie qa record --project . --run local-qa-001 \
|
|
40
|
+
--scenario HOME-001 --phase test --status pass \
|
|
41
|
+
--summary "Homepage navigation and consent passed" \
|
|
42
|
+
--evidence .maggie/qa-runs/local-qa-001/home.png
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For a failure, record expected behavior, actual behavior, and a stable error
|
|
46
|
+
fingerprint. A failed scenario cannot be silently changed to Pass:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
maggie qa record --project . --run local-qa-001 \
|
|
50
|
+
--scenario SEARCH-001 --phase test --status fail \
|
|
51
|
+
--summary "Search dialog did not open" \
|
|
52
|
+
--expected "Selecting a result opens its detail page" \
|
|
53
|
+
--actual "The click leaves the dialog open" \
|
|
54
|
+
--error-fingerprint search-dialog-click-stale \
|
|
55
|
+
--evidence .maggie/qa-runs/local-qa-001/search-console.txt
|
|
56
|
+
|
|
57
|
+
maggie qa record --project . --run local-qa-001 \
|
|
58
|
+
--scenario SEARCH-001 --phase fix \
|
|
59
|
+
--resolution "Guarded the dialog transition after the selected result is resolved" \
|
|
60
|
+
--validation "focused browser check and project build passed"
|
|
61
|
+
|
|
62
|
+
maggie qa record --project . --run local-qa-001 \
|
|
63
|
+
--scenario SEARCH-001 --phase retest --status pass \
|
|
64
|
+
--summary "Search result opens the detail page" \
|
|
65
|
+
--evidence .maggie/qa-runs/local-qa-001/search-retest.png
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`fix` must follow a recorded failure. `retest` must follow a test and, when
|
|
69
|
+
the test failed, a fix. After a fix, retest the original scenario and at least
|
|
70
|
+
one adjacent scenario affected by the same surface.
|
|
71
|
+
|
|
72
|
+
## Gate and release evidence
|
|
73
|
+
|
|
74
|
+
The run gate is `fail` when any scenario fails, `blocked` when there is no
|
|
75
|
+
failure but pending/blocked/inconclusive scenarios remain, and `pass` only
|
|
76
|
+
when every scenario passes. Missing credentials, unsafe fixtures, a real
|
|
77
|
+
mobile viewport, a payment sandbox, or a fault-injection environment are
|
|
78
|
+
explicit blockers; never guess around them.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
maggie qa summary --project . --run local-qa-001
|
|
82
|
+
maggie qa summary --project . --run local-qa-001 --format markdown
|
|
83
|
+
maggie qa export --project . --run local-qa-001 \
|
|
84
|
+
--output docs/qa-runs/local-qa-001.md
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Before calling a release Pass, also run the relevant build, accessibility,
|
|
88
|
+
SEO, media, API, and deployment-canary checks. A green build, HTTP 200, source
|
|
89
|
+
class, or one guest smoke is not browser QA evidence.
|
|
90
|
+
|
|
91
|
+
## Privacy and feedback
|
|
92
|
+
|
|
93
|
+
Do not put passwords, tokens, cookies, full private URLs, personal data,
|
|
94
|
+
provider response bodies, or secrets in run state, screenshots, or exported
|
|
95
|
+
reports. When the same workflow defect is reusable across projects, create a
|
|
96
|
+
privacy-safe draft through `maggie-feedback`; submitting it remains an explicit
|
|
97
|
+
user-confirmed action. Do not promote a project-specific preference directly
|
|
98
|
+
to active memory.
|
|
99
|
+
|
|
100
|
+
The implementation is `tools/clis/maggie_qa_workflow.py`; it is intentionally
|
|
101
|
+
small enough to run without a browser dependency and delegates browser
|
|
102
|
+
interaction to the host agent/browser capability.
|
|
@@ -59,6 +59,11 @@ If the change date is unknown, omit `lastmod`. An empty content-type does not
|
|
|
59
59
|
need a sitemap chunk in the sitemap index; serving an empty endpoint and
|
|
60
60
|
advertising it are separate decisions.
|
|
61
61
|
|
|
62
|
+
Validation also reports a freshness-distribution warning when a large sample
|
|
63
|
+
collapses onto one date (especially today). Treat that as a provenance review,
|
|
64
|
+
not as a reason to rewrite dates: verify the content-change event and omit
|
|
65
|
+
unknown dates. Strict semantic validation promotes the warning to a failure.
|
|
66
|
+
|
|
62
67
|
Generated sitemap XML uses the conventional readable shape by default: one
|
|
63
68
|
`<url>`/`<sitemap>` entry per block, UTF-8 XML, and date-only `lastmod` evidence
|
|
64
69
|
rendered as a full UTC W3C datetime. The plan also exposes the response
|
|
@@ -174,6 +179,15 @@ maggie seo sitemap validate --plan docs/sitemap-plan.json
|
|
|
174
179
|
# After an approved host adapter apply, rollback uses its exact backup manifest.
|
|
175
180
|
maggie seo sitemap rollback --backup-manifest .maggie-sitemap-backups/<plan>/backup-manifest.json \
|
|
176
181
|
--public-dir public --confirm
|
|
182
|
+
|
|
183
|
+
# Change-driven IndexNow: only pass URLs whose rendered content changed.
|
|
184
|
+
maggie seo indexnow key-check --public-dir public --key-file <key>.txt --key <key>
|
|
185
|
+
maggie seo indexnow plan --origin https://example.com \
|
|
186
|
+
--changed-urls-file .maggie/changed-urls.json \
|
|
187
|
+
--state-file .maggie/indexnow-state.json --key <key> \
|
|
188
|
+
--output .maggie/indexnow-plan.json
|
|
189
|
+
maggie seo indexnow submit --plan .maggie/indexnow-plan.json \
|
|
190
|
+
--state-file .maggie/indexnow-state.json --confirm
|
|
177
191
|
```
|
|
178
192
|
|
|
179
193
|
Only confirmed image variants may enter `srcset`; `apply` requires an explicit
|
|
@@ -182,6 +196,13 @@ omit empty chunks from the sitemap index, enforce absolute same-origin URLs, and
|
|
|
182
196
|
record redirects for removed sitemap files. Read the [image and sitemap PRD](https://github.com/TOPY-AI-LTD/ai-cmo-skills/blob/main/docs/image-sitemap-structure-prd.md)
|
|
183
197
|
for adapter and rollback rules.
|
|
184
198
|
|
|
199
|
+
IndexNow is opt-in and complements, rather than replaces, the sitemap. The
|
|
200
|
+
host supplies a changed-URL set and a public verification-key file. The plan
|
|
201
|
+
holds back URLs accepted within 24 hours, deduplicates same-origin URLs, and
|
|
202
|
+
records accepted state only after HTTP 200/202. A 403/429/5xx or network error
|
|
203
|
+
is retryable and must never make the editor save fail; do not retry unchanged
|
|
204
|
+
URLs in a loop.
|
|
205
|
+
|
|
185
206
|
For a deterministic technical smoke check, run:
|
|
186
207
|
|
|
187
208
|
```bash
|
|
@@ -27,6 +27,8 @@ def main() -> int:
|
|
|
27
27
|
inspect = sub.add_parser("inspect"); inspect.add_argument("--project", type=Path, default=Path.cwd())
|
|
28
28
|
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")
|
|
29
29
|
validate = sub.add_parser("validate"); validate.add_argument("--project", type=Path, default=Path.cwd())
|
|
30
|
+
gate = sub.add_parser("check-gate", help="report whether generated content requires review before publication"); gate.add_argument("--project", type=Path, default=Path.cwd())
|
|
31
|
+
approve = sub.add_parser("approve"); approve.add_argument("--project", type=Path, default=Path.cwd()); approve.add_argument("--slug", required=True); approve.add_argument("--actor", required=True); approve.add_argument("--reason", required=True); approve.add_argument("--confirm", action="store_true")
|
|
30
32
|
publish = sub.add_parser("publish"); publish.add_argument("--project", type=Path, default=Path.cwd()); publish.add_argument("--slug", required=True); publish.add_argument("--actor", required=True); publish.add_argument("--reason", required=True); publish.add_argument("--confirm", action="store_true")
|
|
31
33
|
sitemap = sub.add_parser("sitemap"); sitemap.add_argument("--project", type=Path, default=Path.cwd())
|
|
32
34
|
settings = sub.add_parser("settings"); settings.add_argument("--project", type=Path, default=Path.cwd())
|
|
@@ -38,7 +40,7 @@ def main() -> int:
|
|
|
38
40
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
39
41
|
return 0 if result["status"] != "error" else 1
|
|
40
42
|
store = BlogStore(args.project.resolve())
|
|
41
|
-
if args.command in {"init", "ingest", "publish", "rollback", "translate-pending"} and not args.confirm:
|
|
43
|
+
if args.command in {"init", "ingest", "publish", "approve", "rollback", "translate-pending"} and not args.confirm:
|
|
42
44
|
print("CONFIRMATION_REQUIRED: rerun with --confirm", file=sys.stderr); return 2
|
|
43
45
|
try:
|
|
44
46
|
if args.command == "init": result = store.init(args.base_path, args.posts_per_page)
|
|
@@ -51,6 +53,8 @@ def main() -> int:
|
|
|
51
53
|
if not isinstance(payload, list): raise ValueError("local input must be a JSON array")
|
|
52
54
|
result = store.ingest(payload, args.source)
|
|
53
55
|
elif args.command == "validate": result = store.validate()
|
|
56
|
+
elif args.command == "check-gate": result = store.review_gate(store.settings())
|
|
57
|
+
elif args.command == "approve": result = store.approve(args.slug, args.actor, args.reason)
|
|
54
58
|
elif args.command == "publish": result = store.publish(args.slug, args.actor, args.reason)
|
|
55
59
|
elif args.command == "settings": result = store.settings()
|
|
56
60
|
elif args.command == "rollback": result = store.rollback(args.backup)
|
|
@@ -25,6 +25,7 @@ from service_variants import ServiceVariantStore # noqa: E402
|
|
|
25
25
|
from maggie_dash_ui import load_and_validate # noqa: E402
|
|
26
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
27
|
from maggie_quality import validate_variant_copy, validate_media_uniqueness, classify_inventory, validate_bindings, validate_reconcile_contract # noqa: E402
|
|
28
|
+
from maggie_api_contract import validate_api_contract # noqa: E402
|
|
28
29
|
from route_imports import classify_bindings # noqa: E402
|
|
29
30
|
|
|
30
31
|
|
|
@@ -422,7 +423,14 @@ def command_components_audit(args: argparse.Namespace) -> int:
|
|
|
422
423
|
def command_inventory(args: argparse.Namespace) -> int:
|
|
423
424
|
"""Classify each published page once, with no broad predicate overlap."""
|
|
424
425
|
value = json.loads(Path(args.pages_file).resolve().read_text(encoding="utf-8"))
|
|
425
|
-
result = classify_inventory(value)
|
|
426
|
+
result = classify_inventory(value, require_page_kinds=args.require_page_kinds, require_source_coverage=args.require_source_coverage)
|
|
427
|
+
emit(result)
|
|
428
|
+
return 0 if result["passed"] else 1
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def command_api_contract(args: argparse.Namespace) -> int:
|
|
432
|
+
value = json.loads(Path(args.spec).resolve().read_text(encoding="utf-8"))
|
|
433
|
+
result = validate_api_contract(value)
|
|
426
434
|
emit(result)
|
|
427
435
|
return 0 if result["passed"] else 1
|
|
428
436
|
|
|
@@ -572,7 +580,12 @@ def parser() -> argparse.ArgumentParser:
|
|
|
572
580
|
components.set_defaults(func=command_components_audit)
|
|
573
581
|
inventory = sub.add_parser("inventory", help="classify published pages into disjoint band/code-rendered kinds")
|
|
574
582
|
inventory.add_argument("--pages-file", required=True, help="JSON page inventory")
|
|
583
|
+
inventory.add_argument("--require-page-kinds", action="store_true", help="fail when a published page has no pageKind")
|
|
584
|
+
inventory.add_argument("--require-source-coverage", action="store_true", help="fail unless sourceCoverage.complete is true")
|
|
575
585
|
inventory.set_defaults(func=command_inventory)
|
|
586
|
+
api_contract = sub.add_parser("api-contract", help="validate declared request and success-response schemas")
|
|
587
|
+
api_contract.add_argument("--spec", required=True, help="OpenAPI-like JSON document")
|
|
588
|
+
api_contract.set_defaults(func=command_api_contract)
|
|
576
589
|
variant = sub.add_parser("variant", help="manage service variant lifecycle")
|
|
577
590
|
variant_sub = variant.add_subparsers(dest="variant_command", required=True)
|
|
578
591
|
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")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Plan and submit change-driven IndexNow notifications."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "runtime"))
|
|
12
|
+
from maggie_indexnow import check_key_file, plan_indexnow, submit_plan # noqa: E402
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def read_json(path: Path, default: object) -> object:
|
|
16
|
+
if not path.exists():
|
|
17
|
+
return default
|
|
18
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def write_json(path: Path, value: object) -> None:
|
|
22
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
path.write_text(json.dumps(value, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main() -> int:
|
|
27
|
+
parser = argparse.ArgumentParser(prog="maggie seo indexnow")
|
|
28
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
29
|
+
key_check = sub.add_parser("key-check", help="verify the public ownership key file")
|
|
30
|
+
key_check.add_argument("--public-dir", required=True); key_check.add_argument("--key-file", required=True); key_check.add_argument("--key", required=True)
|
|
31
|
+
plan = sub.add_parser("plan", help="hold back recently accepted URLs and create a submission plan")
|
|
32
|
+
plan.add_argument("--origin", required=True); plan.add_argument("--changed-urls-file", required=True); plan.add_argument("--state-file", required=True); plan.add_argument("--key", required=True); plan.add_argument("--key-location"); plan.add_argument("--guard-hours", type=int, default=24); plan.add_argument("--now"); plan.add_argument("--output", required=True)
|
|
33
|
+
submit = sub.add_parser("submit", help="submit an approved plan; provider failure never updates accepted state")
|
|
34
|
+
submit.add_argument("--plan", required=True); submit.add_argument("--state-file", required=True); submit.add_argument("--endpoint", default="https://api.indexnow.org/indexnow"); submit.add_argument("--confirm", action="store_true")
|
|
35
|
+
args = parser.parse_args()
|
|
36
|
+
try:
|
|
37
|
+
if args.command == "key-check":
|
|
38
|
+
result = check_key_file(Path(args.public_dir).resolve(), args.key_file, args.key)
|
|
39
|
+
print(json.dumps(result, indent=2)); return 0 if result["status"] == "pass" else 1
|
|
40
|
+
if args.command == "plan":
|
|
41
|
+
changed = read_json(Path(args.changed_urls_file), [])
|
|
42
|
+
urls = changed.get("urls", []) if isinstance(changed, dict) else changed
|
|
43
|
+
if not isinstance(urls, list): raise ValueError("changed-urls-file must be a JSON array or {\"urls\": []}")
|
|
44
|
+
result = plan_indexnow(args.origin, urls, read_json(Path(args.state_file), {}), key=args.key, key_location=args.key_location, guard_hours=args.guard_hours, now=args.now)
|
|
45
|
+
write_json(Path(args.output), result)
|
|
46
|
+
print(json.dumps({"status": "planned", "eligible": len(result["eligible"]), "heldBack": len(result["heldBack"]), "rejected": len(result["rejected"]), "output": str(Path(args.output).resolve())}, indent=2)); return 0
|
|
47
|
+
if not args.confirm: raise ValueError("submit requires --confirm")
|
|
48
|
+
plan_value = read_json(Path(args.plan), {})
|
|
49
|
+
if not isinstance(plan_value, dict) or plan_value.get("schemaVersion") != "maggie-indexnow.v1": raise ValueError("invalid IndexNow plan")
|
|
50
|
+
state_path = Path(args.state_file); state = read_json(state_path, {})
|
|
51
|
+
if not isinstance(state, dict): state = {}
|
|
52
|
+
result = submit_plan(plan_value, state, args.endpoint)
|
|
53
|
+
if result["status"] == "accepted": write_json(state_path, state)
|
|
54
|
+
print(json.dumps(result, indent=2)); return 0 if result["status"] in {"accepted", "no-op"} else 1
|
|
55
|
+
except (OSError, ValueError, TypeError, json.JSONDecodeError) as error:
|
|
56
|
+
print(f"maggie-indexnow: {error}", file=sys.stderr); return 1
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
raise SystemExit(main())
|