@topy-ai/maggie 0.7.0 → 0.7.2
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 +35 -9
- package/bin/maggie.js +6 -6
- package/bundled-references/maggiedash-dashboard-ui.md +28 -0
- package/bundled-skills/maggie-blog/SKILL.md +12 -0
- package/bundled-skills/maggie-content-localization/SKILL.md +9 -0
- package/bundled-skills/maggie-dash/SKILL.md +45 -2
- package/bundled-skills/maggie-deployment/SKILL.md +17 -0
- package/bundled-skills/maggie-ops/SKILL.md +27 -0
- package/bundled-skills/maggie-seo-geo/SKILL.md +21 -1
- package/bundled-skills/maggie-service-booking/SKILL.md +6 -0
- package/bundled-templates/maggiedash/README.md +15 -0
- package/bundled-templates/maggiedash/dashboard-ui-contract.json +31 -0
- package/bundled-tools/clis/maggie.py +29 -4
- package/bundled-tools/clis/maggie_analytics.py +14 -1
- package/bundled-tools/clis/maggie_blog.py +6 -0
- package/bundled-tools/clis/maggie_dash.py +179 -0
- package/bundled-tools/clis/maggie_deployment.py +43 -0
- package/bundled-tools/clis/maggie_feedback.py +16 -1
- package/bundled-tools/clis/maggie_ops.py +21 -1
- package/bundled-tools/clis/maggie_service_booking.py +6 -3
- package/bundled-tools/clis/maggie_sitemap.py +16 -2
- package/bundled-tools/runtime/analytics_traffic.py +30 -0
- package/bundled-tools/runtime/content_localization.py +63 -1
- package/bundled-tools/runtime/dependency_lock.py +43 -0
- package/bundled-tools/runtime/integration_state.py +17 -0
- package/bundled-tools/runtime/maggie_dash_store.py +150 -6
- package/bundled-tools/runtime/maggie_dash_ui.py +60 -0
- package/bundled-tools/runtime/maggie_sitemap.py +50 -4
- package/bundled-tools/runtime/route_imports.py +51 -0
- package/bundled-tools/runtime/seed_evidence.py +25 -0
- package/package.json +1 -1
- package/references/maggiedash-dashboard-ui.md +28 -0
package/README.md
CHANGED
|
@@ -14,9 +14,9 @@ persistent project memory.
|
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
`site-audit --crawl --save-baseline FILE --reviewer NAME` records a reviewed
|
|
18
|
+
site contract; `site-audit --crawl --baseline FILE` fails on URL, metadata,
|
|
19
|
+
HTML structure or copy changes.
|
|
20
20
|
Complete sitemap coverage is required and existing baselines cannot be
|
|
21
21
|
overwritten. This does not verify browser layout or source-only changes.
|
|
22
22
|
|
|
@@ -66,8 +66,9 @@ Maggie in this order:
|
|
|
66
66
|
```bash
|
|
67
67
|
python3 tools/clis/maggie.py analyze . --json --save
|
|
68
68
|
python3 tools/clis/maggie.py bootstrap interview .
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
maggie dash install --project . --confirm
|
|
70
|
+
maggie dash init --project . --confirm
|
|
71
|
+
maggie dash migrate --project . --confirm
|
|
71
72
|
```
|
|
72
73
|
|
|
73
74
|
The normal daily loop is:
|
|
@@ -88,7 +89,7 @@ The CLI provides the installer plus durable workflow commands:
|
|
|
88
89
|
maggie init | install | update | remove | list | doctor
|
|
89
90
|
maggie cleanup --project . [--confirm]
|
|
90
91
|
maggie bootstrap interview | phase ...
|
|
91
|
-
maggie dash init | status | migrate
|
|
92
|
+
maggie dash install | init | status | migrate | cms ...
|
|
92
93
|
maggie dash transition ... # explicit content approval transition
|
|
93
94
|
maggie dash variant ... # service variant create/review/preview/publish
|
|
94
95
|
maggie clone ... # authorized homepage capture
|
|
@@ -101,7 +102,7 @@ maggie localization ... # plan, validate, review, publish, stale
|
|
|
101
102
|
maggie service ... # import, sync, generate, validate
|
|
102
103
|
maggie seo performance ... # sampled PageSpeed/CWV report and baseline
|
|
103
104
|
maggie seo images ... # inventory, variants, confirmation, validate
|
|
104
|
-
maggie seo sitemap ... # typed plan,
|
|
105
|
+
maggie seo sitemap ... # typed/semantic plan, agent-files, apply, rollback
|
|
105
106
|
maggie deployment | migration | release | analytics | schedule
|
|
106
107
|
maggie deployment canary --asset URL=SHA256 --render-report report.json
|
|
107
108
|
maggie design icon-inventory --source-dir src --runtime assets/icons.css
|
|
@@ -190,21 +191,46 @@ artifact schemas.
|
|
|
190
191
|
Recommended upgrade sequence for the current release:
|
|
191
192
|
|
|
192
193
|
```bash
|
|
193
|
-
npx @topy-ai/maggie@0.7.
|
|
194
|
-
npx @topy-ai/maggie@0.7.
|
|
194
|
+
npx @topy-ai/maggie@0.7.2 update --project . --force
|
|
195
|
+
npx @topy-ai/maggie@0.7.2 cleanup --project .
|
|
195
196
|
```
|
|
196
197
|
|
|
198
|
+
Maintainers should pass npm credentials through the repository helper, never
|
|
199
|
+
as a command-line argument:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
node scripts/publish-npm.mjs --maggie-env-file ../.env
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The 0.7.2 workflow adds the installable MaggieDash admin distribution and
|
|
206
|
+
audited CMS operations (`cms revisions`,
|
|
207
|
+
`trash`, `restore`, `schedule`, `duplicate`, `redirect`, and signed
|
|
208
|
+
`preview`), import-authoritative service matching, shared translation indexes,
|
|
209
|
+
sanitized seed manifests, lockfile/analytics traffic checks, semantic sitemap
|
|
210
|
+
validation, locale-aware `llms.txt`/`sitemap.md`/`insights.md` generation,
|
|
211
|
+
explicit integration states, and deployment Origin/infrastructure/data
|
|
212
|
+
rollback gates.
|
|
213
|
+
|
|
197
214
|
## MaggieDash lifecycle
|
|
198
215
|
|
|
199
216
|
For a new project, establish the local content and approval foundation before
|
|
200
217
|
connecting providers or publishing:
|
|
201
218
|
|
|
202
219
|
```bash
|
|
220
|
+
maggie dash install --project . --confirm
|
|
203
221
|
maggie dash init --project . --confirm
|
|
204
222
|
maggie dash status --project .
|
|
205
223
|
maggie doctor --project .
|
|
206
224
|
```
|
|
207
225
|
|
|
226
|
+
`maggie dash install` installs the first-party private MaggieDash dashboard
|
|
227
|
+
source into `./_maggie/admin`, preserving local files unless `--force` is used.
|
|
228
|
+
It records the resolved source revision in `.maggie/dash-install.json`. During
|
|
229
|
+
bootstrap this is enabled by default; use `--dash-source /path/to/MaggieDash`
|
|
230
|
+
for local development or a configured private Git URL. The host project keeps
|
|
231
|
+
ownership of routes, email/password auth, database, provider credentials, and
|
|
232
|
+
the `/api/maggie/*` adapter.
|
|
233
|
+
|
|
208
234
|
Content remains draft-first and external writes remain explicit. Use the
|
|
209
235
|
project-local memory workflow for confirmed preferences and reusable lessons;
|
|
210
236
|
feedback drafts are never promoted to active memory automatically.
|
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|status|migrate|transition|variant --project PATH [options]
|
|
68
|
+
maggie dash init|install|status|migrate|transition|variant|cms --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
|
|
@@ -92,7 +92,7 @@ Usage:
|
|
|
92
92
|
maggie design author --project PATH --route /about --purpose TEXT --audience TEXT --confirm
|
|
93
93
|
maggie auth reference --project PATH --confirm
|
|
94
94
|
maggie auth check --project PATH [--production]
|
|
95
|
-
maggie blog init|inspect|ingest|validate|publish|sitemap|settings|rollback
|
|
95
|
+
maggie blog init|inspect|ingest|validate|publish|sitemap|settings|rollback|integration-state
|
|
96
96
|
maggie design status <job-id>
|
|
97
97
|
maggie service import <provider-url> --project PATH
|
|
98
98
|
maggie service sync <provider-url> --project PATH
|
|
@@ -106,18 +106,18 @@ Usage:
|
|
|
106
106
|
maggie deployment canary --project PATH --asset URL=SHA256 --render-report report.json --output docs/deployment-canary.json
|
|
107
107
|
maggie migration --project PATH --environment staging
|
|
108
108
|
maggie schedule PATH/.maggie/schedule.json --project PATH
|
|
109
|
-
maggie analytics --project PATH --environment staging
|
|
109
|
+
maggie analytics [traffic-audit|release-gate] --project PATH --environment staging
|
|
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
113
|
maggie localization <extract|plan|generate|preview|validate|review|publish|stale|glossary> [options]
|
|
114
|
-
maggie seo performance|images|sitemap [options]
|
|
114
|
+
maggie seo performance|images|sitemap [options] (sitemap supports strict validate and agent-files)
|
|
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
117
|
maggie site-audit URL --crawl --save-baseline FILE --reviewer NAME
|
|
118
118
|
maggie site-audit URL --crawl --baseline FILE
|
|
119
119
|
maggie browser-audit URL --browse PATH --output DIR --required SELECTOR [--sticky SELECTOR]
|
|
120
|
-
maggie ops audit --project PATH
|
|
120
|
+
maggie ops audit|preflight|verify|lockfiles|seed-manifest --project PATH
|
|
121
121
|
maggie ops preflight --project PATH --write
|
|
122
122
|
|
|
123
123
|
Examples:
|
|
@@ -290,7 +290,7 @@ function service(args) {
|
|
|
290
290
|
const root = projectRoot(args);
|
|
291
291
|
const script = join(root, "tools", "clis", "maggie_service_booking.py");
|
|
292
292
|
if (!existsSync(script)) throw new Error(`service booking CLI is missing: ${script}`);
|
|
293
|
-
const result = spawnSync("python3", [script, ...args], { stdio: "inherit", cwd: root });
|
|
293
|
+
const result = spawnSync("python3", [script, ...args], { stdio: "inherit", cwd: root, env: { ...process.env, MAGGIE_VERSION: PACKAGE_VERSION } });
|
|
294
294
|
if (result.error) throw result.error;
|
|
295
295
|
process.exitCode = result.status ?? 1;
|
|
296
296
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# MaggieDash dashboard UI contract
|
|
2
|
+
|
|
3
|
+
MaggieDash workspaces use the lightweight contract in
|
|
4
|
+
`templates/maggiedash/dashboard-ui-contract.json`. It is provider-neutral and
|
|
5
|
+
describes the dashboard chrome, not a framework-specific component library.
|
|
6
|
+
|
|
7
|
+
The reference is the users workspace: one `WorkspaceBar`, one sidebar/content
|
|
8
|
+
navigation relationship, and one `ContentTabs` row. A route must not render a
|
|
9
|
+
second horizontal navigation, duplicate its sidebar, or hide duplicate markup
|
|
10
|
+
with CSS. `WorkspaceCard` owns card framing; page routes own content.
|
|
11
|
+
|
|
12
|
+
`ContentTabs` accepts only the props it renders (`tabs`, `active`, `actions`,
|
|
13
|
+
and `children`). It does not accept a page `title` or `description`. If a page
|
|
14
|
+
needs a heading or description, render a dedicated page header or use
|
|
15
|
+
`WorkspaceBar` explicitly. This keeps component APIs honest and prevents
|
|
16
|
+
dead-copy drift.
|
|
17
|
+
|
|
18
|
+
Validate a host implementation with:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
maggie dash ui validate --contract templates/maggiedash/dashboard-ui-contract.json \
|
|
22
|
+
--source src/components/WorkspaceBar.tsx \
|
|
23
|
+
--source src/components/WorkspaceCard.tsx \
|
|
24
|
+
--source src/components/ContentTabs.tsx
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The check is static evidence. It does not replace an authenticated browser
|
|
28
|
+
review of the rendered dashboard at desktop and mobile sizes.
|
|
@@ -60,3 +60,15 @@ maggie design init --project . --surface blog \
|
|
|
60
60
|
|
|
61
61
|
The blog skill supplies route/data semantics; `maggie-design` supplies the
|
|
62
62
|
host-native components and responsive visual review.
|
|
63
|
+
|
|
64
|
+
Optional Search Console/AI visibility integrations must report state rather
|
|
65
|
+
than returning an ambiguous empty success payload. Use:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
maggie blog integration-state # not-configured
|
|
69
|
+
maggie blog integration-state --configured --consent-required # awaiting-consent
|
|
70
|
+
maggie blog integration-state --configured --authorized # ready
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Provider adapters should preserve the same `not-configured`,
|
|
74
|
+
`awaiting-consent`, `awaiting-authorization`, `ready`, and `error` semantics.
|
|
@@ -116,3 +116,12 @@ styles, SVG, and dynamic expressions. Supplying source and render reports to
|
|
|
116
116
|
remains backward-compatible without those artifacts. See
|
|
117
117
|
[`docs/localization-extraction-render-prd.md`](../../docs/localization-extraction-render-prd.md)
|
|
118
118
|
for the artifact contract and limitations.
|
|
119
|
+
|
|
120
|
+
Use the shared translation index and route predicates from
|
|
121
|
+
`tools/runtime/content_localization.py`. A host must not keep a second wording
|
|
122
|
+
dictionary beside its translation registry: conflicting `(contentId, locale)`
|
|
123
|
+
records fail closed. Use `is_translated_path()` for both exact routes and
|
|
124
|
+
prefix routes, and do not let locale middleware capture root `.txt`, `.md`,
|
|
125
|
+
`.xml`, or `.json` assets. If a framework rewrites a localized request,
|
|
126
|
+
dedupe instrumentation with `rewrite_once(request_key, seen)` so one request
|
|
127
|
+
does not count twice.
|
|
@@ -5,8 +5,30 @@ description: Manage the MaggieDash project foundation, local content store, and
|
|
|
5
5
|
|
|
6
6
|
# MaggieDash
|
|
7
7
|
|
|
8
|
-
Use the provider-neutral MaggieDash contracts and CLI for project setup
|
|
9
|
-
content operations.
|
|
8
|
+
Use the provider-neutral MaggieDash contracts and CLI for project setup,
|
|
9
|
+
installation of the first-party admin workspace, and content operations.
|
|
10
|
+
|
|
11
|
+
## Install the admin workspace
|
|
12
|
+
|
|
13
|
+
During bootstrap, the recommended decision is to install MaggieDash into
|
|
14
|
+
`./_maggie/admin`. The dashboard source lives in the private first-party
|
|
15
|
+
`TOPY-AI-LTD/MaggieDash` repository during the MVP phase; Git/gh credentials
|
|
16
|
+
must already be configured for the project owner.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
maggie dash install --project . --confirm
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The command is idempotent, preserves existing files unless `--force` is
|
|
23
|
+
explicitly supplied, and records the resolved revision in
|
|
24
|
+
`.maggie/dash-install.json`. It never accepts or stores a GitHub token as a
|
|
25
|
+
CLI argument. For local development or a pinned checkout, use
|
|
26
|
+
`--source /path/to/MaggieDash` or `--source URL --ref REF`.
|
|
27
|
+
|
|
28
|
+
The installed dashboard owns the React workspace UI. The host project still
|
|
29
|
+
owns the framework route, email/password session middleware, database, media
|
|
30
|
+
storage, provider credentials, and `/api/maggie/*` adapter endpoints. Read the
|
|
31
|
+
MaggieDash host adapter contract before adding a new framework adapter.
|
|
10
32
|
|
|
11
33
|
## Workflow
|
|
12
34
|
|
|
@@ -74,3 +96,24 @@ not part of this skill.
|
|
|
74
96
|
Before and after a meaningful run, load and record confirmed project
|
|
75
97
|
preferences or repaired pitfalls with
|
|
76
98
|
[the shared memory hook](../../references/memory-hook.md).
|
|
99
|
+
|
|
100
|
+
CMS operations are explicit and auditable:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
maggie dash cms revisions --project . --project-id local-project --document-id <id> --confirm
|
|
104
|
+
maggie dash cms trash --project . --project-id local-project --document-id <id> --reason "remove from editor" --confirm
|
|
105
|
+
maggie dash cms restore --project . --project-id local-project --document-id <id> --reason "restore" --confirm
|
|
106
|
+
maggie dash cms schedule --project . --project-id local-project --document-id <id> \
|
|
107
|
+
--publish-at 2026-09-20T10:00:00Z --reason "approved release" --confirm
|
|
108
|
+
maggie dash cms duplicate --project . --project-id local-project --document-id <id> \
|
|
109
|
+
--new-id <new-id> --new-slug <new-slug> --reason "create draft" --confirm
|
|
110
|
+
maggie dash cms redirect --project . --project-id local-project --from-path /old --to-path /new \
|
|
111
|
+
--reason "canonical slug change" --confirm
|
|
112
|
+
maggie dash cms preview --project . --project-id local-project --document-id <id> \
|
|
113
|
+
--secret "$MAGGIE_PREVIEW_SECRET" --confirm
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Content writes create immutable revision snapshots. Trash is reversible and
|
|
117
|
+
does not destroy the document. Scheduling is accepted only for approved
|
|
118
|
+
content and requires a timezone. Preview tokens are short-lived HMAC-signed
|
|
119
|
+
tokens; never place the secret in source control or generated reports.
|
|
@@ -210,3 +210,20 @@ Follow the shared [Maggie Decision Loop](../../references/decision-loop.md) for
|
|
|
210
210
|
6. Require explicit final confirmation before any mutation or external write.
|
|
211
211
|
|
|
212
212
|
If a decision is not relevant, record it as `skipped` with a reason. Do not silently assume a missing choice, and do not treat an existing output as permission to skip required work.
|
|
213
|
+
|
|
214
|
+
For scheduled POST jobs, validate the request contract before installation:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
maggie deployment --validate-request --method POST \
|
|
218
|
+
--origin https://example.test --has-auth
|
|
219
|
+
maggie deployment --verify-infra --service fresha \
|
|
220
|
+
--units-dir .maggie/deployment/schedules
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The deployment scheduler must send an explicit Origin and configured auth
|
|
224
|
+
contract; a missing Origin must not be mistaken for an application auth
|
|
225
|
+
failure. Infrastructure verification accepts a declared service/timer pair or
|
|
226
|
+
both units being enabled in systemd. Data-dependent releases additionally
|
|
227
|
+
require `rollback.backupId` and `rollback.restoreCommand` in
|
|
228
|
+
`.maggie/deployment/data-release.json`, because switching code alone does not
|
|
229
|
+
restore incompatible data.
|
|
@@ -7,6 +7,19 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Maggie Ops
|
|
9
9
|
|
|
10
|
+
Before release, run the lockfile guard when a project has more than one package
|
|
11
|
+
manager:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
maggie ops lockfiles --project .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
It compares direct package and optional dependency names in `package.json`
|
|
18
|
+
with `package-lock.json`, reports the presence of `pnpm-lock.yaml`, and fails
|
|
19
|
+
when npm CI cannot install a declared package. It does not rewrite either
|
|
20
|
+
lockfile; regenerate and commit both through the project's chosen package
|
|
21
|
+
manager workflow.
|
|
22
|
+
|
|
10
23
|
## Automatic memory hook
|
|
11
24
|
|
|
12
25
|
Follow [Maggie Memory Hook](../../references/memory-hook.md) at invocation and completion.
|
|
@@ -195,3 +208,17 @@ Follow the shared [Maggie Decision Loop](../../references/decision-loop.md) for
|
|
|
195
208
|
6. Require explicit final confirmation before any mutation or external write.
|
|
196
209
|
|
|
197
210
|
If a decision is not relevant, record it as `skipped` with a reason. Do not silently assume a missing choice, and do not treat an existing output as permission to skip required work.
|
|
211
|
+
|
|
212
|
+
Before data-dependent checks, validate an explicit sanitized fixture manifest:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
maggie ops seed-manifest --project . --manifest .maggie/seed-manifest.json
|
|
216
|
+
maggie ops lockfiles --project .
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The seed manifest must be opt-in, sanitized, and contain non-empty fixtures;
|
|
220
|
+
an empty dev database is not evidence that an operational check passed.
|
|
221
|
+
Known Maggie/toolchain traffic can be measured without polluting first-party
|
|
222
|
+
analytics using `maggie analytics traffic-audit --events events.json`. The
|
|
223
|
+
classifier excludes only explicit tool/test markers and never infers identity
|
|
224
|
+
from IP or private fields.
|
|
@@ -33,6 +33,11 @@ lastmod, optional JSON object mapping locale tags to alternate URLs, such as
|
|
|
33
33
|
It emits `lastmod` and `xhtml:link rel="alternate"`. Supply truthful content
|
|
34
34
|
change dates; omit unknown dates. Alternates must currently use the approved
|
|
35
35
|
origin. Reciprocal locale coverage and date semantics require separate review.
|
|
36
|
+
`lastmod` must come from a recorded content-change event or source revision;
|
|
37
|
+
never derive it from `updated_at`, pull time, sync time, or deployment time.
|
|
38
|
+
If the change date is unknown, omit `lastmod`. An empty content-type does not
|
|
39
|
+
need a sitemap chunk in the sitemap index; serving an empty endpoint and
|
|
40
|
+
advertising it are separate decisions.
|
|
36
41
|
|
|
37
42
|
## Freeze and compare a reviewed site
|
|
38
43
|
|
|
@@ -122,7 +127,7 @@ maggie seo sitemap rollback --backup-manifest .maggie-sitemap-backups/<plan>/bac
|
|
|
122
127
|
|
|
123
128
|
Only confirmed image variants may enter `srcset`; `apply` requires an explicit
|
|
124
129
|
confirmation and host adapter. Sitemap plans keep content types separate,
|
|
125
|
-
|
|
130
|
+
omit empty chunks from the sitemap index, enforce absolute same-origin URLs, and
|
|
126
131
|
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)
|
|
127
132
|
for adapter and rollback rules.
|
|
128
133
|
|
|
@@ -207,3 +212,18 @@ Follow the shared [Maggie Decision Loop](../../references/decision-loop.md) for
|
|
|
207
212
|
6. Require explicit final confirmation before any mutation or external write.
|
|
208
213
|
|
|
209
214
|
If a decision is not relevant, record it as `skipped` with a reason. Do not silently assume a missing choice, and do not treat an existing output as permission to skip required work.
|
|
215
|
+
|
|
216
|
+
Use semantic validation when route evidence is available:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
maggie seo sitemap validate --plan .maggie/sitemap-plan.json --strict-semantic
|
|
220
|
+
maggie seo sitemap agent-files --origin https://example.test \
|
|
221
|
+
--routes-file .maggie/routes.tsv --locale fr-FR --output-dir public/fr
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Strict validation checks searchable content evidence, indexability, self
|
|
225
|
+
canonical ownership and truthful `lastmod` provenance. `lastmod` may only be
|
|
226
|
+
backed by a content-change/source-revision event; operational sync, pull,
|
|
227
|
+
deploy, or `updated_at` timestamps are rejected. The agent-file command emits
|
|
228
|
+
locale-aware `llms.txt`, `sitemap.md`, and `insights.md` from the same route
|
|
229
|
+
inventory; non-indexable routes are omitted.
|
|
@@ -7,6 +7,12 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Maggie Service Booking
|
|
9
9
|
|
|
10
|
+
Route matching evidence must come from the route source's actual import
|
|
11
|
+
statements. The shared resolver in `tools/runtime/route_imports.py` resolves
|
|
12
|
+
relative imports and records a component fingerprint; it never selects a
|
|
13
|
+
same-basename sibling as a fallback. Missing imports are evidence requiring
|
|
14
|
+
review, not permission to guess.
|
|
15
|
+
|
|
10
16
|
## Reviewed page relationships
|
|
11
17
|
|
|
12
18
|
Reviewed page selection preserves existing `supporting` relations, including
|
|
@@ -7,3 +7,18 @@ own framework conventions for rendering.
|
|
|
7
7
|
The package includes only these lightweight contracts. Large marketplace
|
|
8
8
|
previews and template media remain separately distributable through the
|
|
9
9
|
marketplace repository.
|
|
10
|
+
|
|
11
|
+
Dashboard routes should adopt `dashboard-ui-contract.json` and validate their
|
|
12
|
+
actual host components with `maggie dash ui validate`. The contract provides
|
|
13
|
+
the shared workspace chrome and rejects components that declare dead props.
|
|
14
|
+
|
|
15
|
+
The first-party dashboard distribution is installed separately into
|
|
16
|
+
`./_maggie/admin`:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
maggie dash install --project . --confirm
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
It records the source revision at `.maggie/dash-install.json`; host routes,
|
|
23
|
+
email/password authentication, and `/api/maggie/*` adapters remain owned by the
|
|
24
|
+
website project.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "maggiedash-dashboard-ui.v1",
|
|
3
|
+
"shell": {
|
|
4
|
+
"reference": "users-workspace",
|
|
5
|
+
"navigation": "sidebar-plus-content-tabs",
|
|
6
|
+
"rules": [
|
|
7
|
+
"render one workspace bar per route",
|
|
8
|
+
"render one content tab bar per route",
|
|
9
|
+
"do not duplicate sidebar navigation in page content",
|
|
10
|
+
"do not hide duplicate navigation with CSS"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"components": [
|
|
14
|
+
{
|
|
15
|
+
"name": "WorkspaceBar",
|
|
16
|
+
"props": ["workspaceName", "activeSection", "actions", "children"],
|
|
17
|
+
"forbiddenProps": []
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "WorkspaceCard",
|
|
21
|
+
"props": ["title", "children"],
|
|
22
|
+
"forbiddenProps": []
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "ContentTabs",
|
|
26
|
+
"props": ["tabs", "active", "actions", "children"],
|
|
27
|
+
"forbiddenProps": ["title", "description"]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"accessibility": ["active tab exposes aria-selected", "actions have accessible names", "workspace navigation has a landmark"]
|
|
31
|
+
}
|
|
@@ -8,6 +8,7 @@ import hashlib
|
|
|
8
8
|
import json
|
|
9
9
|
import os
|
|
10
10
|
import re
|
|
11
|
+
import subprocess
|
|
11
12
|
import sys
|
|
12
13
|
import time
|
|
13
14
|
from datetime import datetime, timezone
|
|
@@ -561,6 +562,11 @@ def command_bootstrap_phase(args: argparse.Namespace) -> int:
|
|
|
561
562
|
if analysis.get("maggiedash", {}).get("status") not in {"Detected", "Planned"}:
|
|
562
563
|
print("GATE_BLOCKED: MaggieDash project contract is unavailable.", file=sys.stderr)
|
|
563
564
|
return 2
|
|
565
|
+
bootstrap = load_state(root) or {}
|
|
566
|
+
decisions = bootstrap.get("decisions", {})
|
|
567
|
+
if decisions.get("maggiedash_admin") and not (root / STATE_DIR / "dash-install.json").exists():
|
|
568
|
+
print("GATE_BLOCKED: MaggieDash admin is enabled but not installed; run `maggie dash install --project . --confirm`.", file=sys.stderr)
|
|
569
|
+
return 2
|
|
564
570
|
phase["status"] = "passed"
|
|
565
571
|
phase["validation"] = args.validation
|
|
566
572
|
phase["validated_at"] = utc_now()
|
|
@@ -589,7 +595,7 @@ BOOTSTRAP_QUESTIONS = [
|
|
|
589
595
|
("content_source", "Where should posts be stored?", ["preserve", "local-content", "database", "api-pull"]),
|
|
590
596
|
("posts_per_page", "How many posts should appear on each blog page?", ["9", "12", "6", "custom"]),
|
|
591
597
|
("grid_columns", "How many columns should the default post grid use?", ["3", "2", "4", "custom"]),
|
|
592
|
-
("
|
|
598
|
+
("maggiedash_admin", "Should bootstrap install the private MaggieDash admin workspace into ./_maggie/admin?", ["enabled", "disabled"]),
|
|
593
599
|
]
|
|
594
600
|
|
|
595
601
|
|
|
@@ -604,7 +610,7 @@ def question_default(key: str, analysis: dict) -> str:
|
|
|
604
610
|
detected = analysis.get(key, {}).get("value") if isinstance(analysis.get(key), dict) else None
|
|
605
611
|
if key == "posts_per_page": return "9"
|
|
606
612
|
if key == "grid_columns": return "3"
|
|
607
|
-
if key == "
|
|
613
|
+
if key == "maggiedash_admin": return "enabled"
|
|
608
614
|
return detected or {"ui_system": "preserve-or-tailwind", "icon_set": "phosphor", "font": "preserve-or-system", "database": "preserve-or-sqlite"}.get(key, "detected")
|
|
609
615
|
|
|
610
616
|
|
|
@@ -641,6 +647,8 @@ def command_bootstrap_interview(args: argparse.Namespace) -> int:
|
|
|
641
647
|
raise RuntimeError(f"invalid answers file: {args.answers_file}") from error
|
|
642
648
|
if not isinstance(loaded, dict): raise RuntimeError("answers file must contain a JSON object")
|
|
643
649
|
answers.update({str(key): str(value) for key, value in loaded.items()})
|
|
650
|
+
if "maggiedash_admin" not in answers and "ops_dashboard" in answers:
|
|
651
|
+
answers["maggiedash_admin"] = answers["ops_dashboard"]
|
|
644
652
|
for key, question, choices in BOOTSTRAP_QUESTIONS:
|
|
645
653
|
value = answers.get(key, question_default(key, analysis))
|
|
646
654
|
if not args.non_interactive and key not in answers:
|
|
@@ -665,8 +673,10 @@ def command_bootstrap_interview(args: argparse.Namespace) -> int:
|
|
|
665
673
|
project=str(root), confirm=["foundation", "experience", "data", "publishing"],
|
|
666
674
|
framework=answers["framework"], language=answers["language"], content_language=answers["content_language"],
|
|
667
675
|
ui_system=answers["ui_system"], icon_set=answers["icon_set"], font=answers["font"], database=answers["database"], content_source=answers["content_source"],
|
|
668
|
-
posts_per_page=int(answers["posts_per_page"]), grid_columns=int(answers["grid_columns"]),
|
|
676
|
+
posts_per_page=int(answers["posts_per_page"]), grid_columns=int(answers["grid_columns"]),
|
|
677
|
+
ops_dashboard=answers["maggiedash_admin"] == "enabled", maggiedash_admin=answers["maggiedash_admin"] == "enabled",
|
|
669
678
|
site_domain=answers["site_domain"], api_key_present=answers["api_key_present"], foundation_source=answers["foundation_source"], maggiedash_path=None,
|
|
679
|
+
dash_source=getattr(args, "dash_source", None),
|
|
670
680
|
)
|
|
671
681
|
return command_complete(complete_args)
|
|
672
682
|
|
|
@@ -685,6 +695,17 @@ def command_complete(args: argparse.Namespace) -> int:
|
|
|
685
695
|
if foundation_analysis.get("maggiedash", {}).get("status") != "Detected":
|
|
686
696
|
print("FOUNDATION_REQUIRED: use-existing-maggiedash was selected but no MaggieDash contract was detected.", file=sys.stderr)
|
|
687
697
|
return 2
|
|
698
|
+
install_admin = bool(getattr(args, "maggiedash_admin", False))
|
|
699
|
+
if install_admin:
|
|
700
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
701
|
+
command = [sys.executable, str(Path(__file__).with_name("maggie_dash.py")), "install", "--project", str(root), "--confirm"]
|
|
702
|
+
dash_source = getattr(args, "dash_source", None)
|
|
703
|
+
if dash_source:
|
|
704
|
+
command.extend(["--source", dash_source])
|
|
705
|
+
result = subprocess.run(command, cwd=root, check=False)
|
|
706
|
+
if result.returncode:
|
|
707
|
+
print("MAGGIEDASH_INSTALL_FAILED: bootstrap was not completed; resolve the private MaggieDash source and retry.", file=sys.stderr)
|
|
708
|
+
return result.returncode
|
|
688
709
|
state = {
|
|
689
710
|
"schema_version": "1.1",
|
|
690
711
|
"status": "completed",
|
|
@@ -707,6 +728,7 @@ def command_complete(args: argparse.Namespace) -> int:
|
|
|
707
728
|
"posts_per_page": args.posts_per_page,
|
|
708
729
|
"grid_columns": args.grid_columns,
|
|
709
730
|
"ops_dashboard": args.ops_dashboard,
|
|
731
|
+
"maggiedash_admin": install_admin,
|
|
710
732
|
},
|
|
711
733
|
}
|
|
712
734
|
path = state_path(root)
|
|
@@ -714,7 +736,7 @@ def command_complete(args: argparse.Namespace) -> int:
|
|
|
714
736
|
path.write_text(json.dumps(state, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
715
737
|
contracts = {
|
|
716
738
|
"project.json": {"schema_version": "1.0", "framework": args.framework, "language": args.language, "database": args.database, "content_source": args.content_source, "site_domain": getattr(args, "site_domain", "none"), "context_source": state["decisions"]["context_source"], "foundation_source": foundation_source},
|
|
717
|
-
"maggiedash.json": {"schema_version": "1.0", "source": foundation_source, "path": getattr(args, "maggiedash_path", None), "scaffold_command": "maggie dash init --project ." if foundation_source == "create-maggiedash" else None, "capabilities": ["content", "admin", "auth", "media", "plugins", "api", "mcp"], "status": "detected" if foundation_source == "use-existing-maggiedash" else "planned" if foundation_source == "create-maggiedash" else "not-selected"},
|
|
739
|
+
"maggiedash.json": {"schema_version": "1.0", "source": foundation_source, "path": getattr(args, "maggiedash_path", None), "scaffold_command": "maggie dash init --project ." if foundation_source == "create-maggiedash" else None, "admin_install": {"enabled": install_admin, "target": "./_maggie/admin", "state": ".maggie/dash-install.json" if install_admin else None}, "capabilities": ["content", "admin", "auth", "media", "plugins", "api", "mcp"], "status": "detected" if foundation_source == "use-existing-maggiedash" else "planned" if foundation_source == "create-maggiedash" else "not-selected"},
|
|
718
740
|
"decisions.json": state["decisions"],
|
|
719
741
|
"schema.json": {"post_required": ["id", "slug", "title", "content", "status", "canonicalUrl", "publishedAt", "updatedAt", "author"], "public_status": "published", "posts_per_page": args.posts_per_page, "grid_columns": args.grid_columns, "grid_default": f"{args.grid_columns}x{args.posts_per_page // args.grid_columns}"},
|
|
720
742
|
"routes.json": {"public": ["/about", "/blog", "/blog/page/[page]", "/blog/[slug]", "/topics", "/topics/[slug]", "/authors/[slug]", "/sitemap.xml", "/robots.txt"], "ops": ["/ops", "/ops/posts", "/ops/posts/new", "/ops/posts/[id]/edit", "/ops/posts/[id]/preview", "/ops/topics", "/ops/sitemap", "/ops/reports", "/ops/settings/site", "/ops/settings/integrations", "/ops/operations", "/ops/wordpress", "/api/ops/summary", "/api/ops/posts", "/api/ops/topics", "/api/ops/bulk", "/api/ops/agency", "/api/ops/entities", "/api/ops/migrations/import", "/api/ops/migrations/[id]/resume", "/api/ops/wordpress/migration-plan", "/api/ops/pull/project-context", "/api/ops/pull/sync", "/api/ops/pull/sync-updates", "/api/ops/sitemap/matching-history", "/api/ops/sitemap/match", "/api/ops/sitemap/auto-detect", "/api/ops/rewrite/queue", "/api/ops/rewrite/history", "/api/ops/rewrite/policy", "/api/ops/content-tracking/report-state", "/api/ops/content-tracking/report-state/batch", "/api/ops/reports", "/api/ops/playbooks/[id]/run"]},
|
|
@@ -903,6 +925,7 @@ def parser() -> argparse.ArgumentParser:
|
|
|
903
925
|
interview.add_argument("--answers-file", help="JSON answers for headless/CI use")
|
|
904
926
|
interview.add_argument("--non-interactive", action="store_true", help="do not prompt; requires --answers-file and --confirm")
|
|
905
927
|
interview.add_argument("--confirm", action="store_true", help="apply the reviewed proposal")
|
|
928
|
+
interview.add_argument("--dash-source", help="local checkout or Git URL for the MaggieDash admin source")
|
|
906
929
|
interview.set_defaults(func=command_bootstrap_interview)
|
|
907
930
|
phase = bootstrap_sub.add_parser("phase", help="start/pass/fail one ordered workflow phase")
|
|
908
931
|
phase.add_argument("action", choices=["status", "start", "pass", "fail"])
|
|
@@ -915,11 +938,13 @@ def parser() -> argparse.ArgumentParser:
|
|
|
915
938
|
complete.add_argument("--api-key-present", choices=["yes", "no"], default="no")
|
|
916
939
|
complete.add_argument("--foundation-source", choices=["use-existing-maggiedash", "create-maggiedash", "preserve-existing-project"], default="preserve-existing-project")
|
|
917
940
|
complete.add_argument("--maggiedash-path", default=None, help="path to an existing MaggieDash project contract")
|
|
941
|
+
complete.add_argument("--dash-source", help="local checkout or Git URL for the MaggieDash admin source")
|
|
918
942
|
for name, default in (("framework", "detected"), ("language", "detected"), ("content-language", "confirmed"), ("ui-system", "preserve-or-tailwind"), ("icon-set", "phosphor"), ("font", "preserve-or-system"), ("database", "preserve-or-sqlite"), ("content-source", "confirmed")):
|
|
919
943
|
complete.add_argument("--" + name, dest=name.replace("-", "_"), default=default)
|
|
920
944
|
complete.add_argument("--posts-per-page", type=int, default=9)
|
|
921
945
|
complete.add_argument("--grid-columns", type=int, default=3)
|
|
922
946
|
complete.add_argument("--ops-dashboard", action=argparse.BooleanOptionalAction, default=False)
|
|
947
|
+
complete.add_argument("--maggiedash-admin", action=argparse.BooleanOptionalAction, default=False)
|
|
923
948
|
complete.set_defaults(func=command_complete)
|
|
924
949
|
generate = sub.add_parser("generate", help="generate stable contract or fixture files")
|
|
925
950
|
generate.add_argument("target", choices=["contract", "fixture", "post", "topic", "author", "migration", "seo", "ops-page"])
|
|
@@ -10,6 +10,9 @@ import re
|
|
|
10
10
|
import subprocess
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from urllib.parse import urlsplit
|
|
13
|
+
import sys
|
|
14
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "runtime"))
|
|
15
|
+
from analytics_traffic import audit_events # noqa: E402
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
GA4_ID = re.compile(r"^G-[A-Z0-9]+$", re.I)
|
|
@@ -107,7 +110,7 @@ def release_gate(args: argparse.Namespace) -> int:
|
|
|
107
110
|
|
|
108
111
|
def main() -> int:
|
|
109
112
|
parser = argparse.ArgumentParser()
|
|
110
|
-
parser.add_argument("command", nargs="?", choices=("release-gate",))
|
|
113
|
+
parser.add_argument("command", nargs="?", choices=("release-gate", "traffic-audit"))
|
|
111
114
|
parser.add_argument("--project", default=".")
|
|
112
115
|
parser.add_argument("--environment", choices=("development", "staging", "production"), default="staging")
|
|
113
116
|
parser.add_argument("--env-file", help="optional env file; values are never printed")
|
|
@@ -117,12 +120,22 @@ def main() -> int:
|
|
|
117
120
|
parser.add_argument("--network-report", help="redacted network evidence for release-gate")
|
|
118
121
|
parser.add_argument("--provider-report", help="read-only provider evidence for release-gate")
|
|
119
122
|
parser.add_argument("--smoke-report", help="production smoke evidence for release-gate")
|
|
123
|
+
parser.add_argument("--events", help="redacted JSON array of analytics events for traffic-audit")
|
|
120
124
|
args = parser.parse_args()
|
|
121
125
|
if args.command == "release-gate":
|
|
122
126
|
required = ("contract", "render_report", "network_report", "provider_report", "smoke_report")
|
|
123
127
|
if any(not getattr(args, name) for name in required):
|
|
124
128
|
parser.error("release-gate requires --contract, --render-report, --network-report, --provider-report, and --smoke-report")
|
|
125
129
|
return release_gate(args)
|
|
130
|
+
if args.command == "traffic-audit":
|
|
131
|
+
if not args.events:
|
|
132
|
+
parser.error("traffic-audit requires --events")
|
|
133
|
+
value = json.loads(Path(args.events).read_text(encoding="utf-8"))
|
|
134
|
+
if not isinstance(value, list):
|
|
135
|
+
parser.error("--events must contain a JSON array")
|
|
136
|
+
result = audit_events(value)
|
|
137
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
138
|
+
return 0
|
|
126
139
|
project = Path(args.project).resolve()
|
|
127
140
|
env = parse_env(Path(args.env_file).resolve()) if args.env_file else parse_env(project / ".env.example")
|
|
128
141
|
env.update({key: value for key, value in os.environ.items() if key in {"PUBLIC_GA4_MEASUREMENT_ID", "PUBLIC_ANALYTICS_ENABLED", "PUBLIC_ANALYTICS_CONSENT_REQUIRED", "GSC_SITE_URL", "GSC_VERIFICATION_TOKEN"}})
|
|
@@ -11,6 +11,7 @@ from pathlib import Path
|
|
|
11
11
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "runtime"))
|
|
12
12
|
from maggie_blog import BlogStore # noqa: E402
|
|
13
13
|
from localization_runner import process_adapter
|
|
14
|
+
from integration_state import integration_state # noqa: E402
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def main() -> int:
|
|
@@ -29,8 +30,13 @@ def main() -> int:
|
|
|
29
30
|
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")
|
|
30
31
|
sitemap = sub.add_parser("sitemap"); sitemap.add_argument("--project", type=Path, default=Path.cwd())
|
|
31
32
|
settings = sub.add_parser("settings"); settings.add_argument("--project", type=Path, default=Path.cwd())
|
|
33
|
+
state = sub.add_parser("integration-state"); state.add_argument("--configured", action="store_true"); state.add_argument("--consent-required", action="store_true"); state.add_argument("--consent", action="store_true"); state.add_argument("--authorized", action="store_true"); state.add_argument("--error")
|
|
32
34
|
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")
|
|
33
35
|
args = parser.parse_args()
|
|
36
|
+
if args.command == "integration-state":
|
|
37
|
+
result = integration_state(configured=args.configured, consent_required=args.consent_required, consent=args.consent, authorized=args.authorized, error=args.error)
|
|
38
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
39
|
+
return 0 if result["status"] != "error" else 1
|
|
34
40
|
store = BlogStore(args.project.resolve())
|
|
35
41
|
if args.command in {"init", "ingest", "publish", "rollback", "translate-pending"} and not args.confirm:
|
|
36
42
|
print("CONFIRMATION_REQUIRED: rerun with --confirm", file=sys.stderr); return 2
|