@topy-ai/maggie 0.1.0 → 0.1.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/bin/maggie.js +64 -1
- package/bundled-references/blog-data-contract.md +8 -0
- package/bundled-references/decision-loop.md +25 -0
- package/bundled-references/ops-dashboard-contract.md +41 -0
- package/bundled-skills/README.md +2 -0
- package/bundled-skills/maggie-blog-bootstrap/SKILL.md +28 -3
- package/bundled-skills/maggie-clone/SKILL.md +27 -17
- package/bundled-skills/maggie-deployment/SKILL.md +2 -0
- package/bundled-skills/maggie-design/SKILL.md +214 -0
- package/bundled-skills/maggie-ops/SKILL.md +163 -0
- package/bundled-skills/maggie-ops/agents/openai.yaml +4 -0
- package/bundled-skills/maggie-project-context/SKILL.md +2 -0
- package/bundled-skills/maggie-seo-geo/SKILL.md +2 -0
- package/bundled-skills/maggie-social-share/SKILL.md +2 -0
- package/bundled-tools/clis/maggie.py +94 -3
- package/bundled-tools/clis/maggie_design.py +153 -0
- package/package.json +1 -1
- package/references/blog-data-contract.md +8 -0
- package/references/decision-loop.md +25 -0
- package/references/ops-dashboard-contract.md +41 -0
package/bin/maggie.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
4
5
|
import { cp } from "node:fs/promises";
|
|
5
6
|
import { dirname, join, resolve } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -9,9 +10,12 @@ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
9
10
|
const SKILLS_ROOT = join(PACKAGE_ROOT, "bundled-skills");
|
|
10
11
|
const REFERENCES_ROOT = join(PACKAGE_ROOT, "bundled-references");
|
|
11
12
|
const TOOLS_ROOT = join(PACKAGE_ROOT, "bundled-tools");
|
|
13
|
+
const STATE_DIR = ".maggie";
|
|
12
14
|
const SKILL_NAMES = [
|
|
13
15
|
"maggie-blog-bootstrap",
|
|
14
16
|
"maggie-clone",
|
|
17
|
+
"maggie-design",
|
|
18
|
+
"maggie-ops",
|
|
15
19
|
"maggie-deployment",
|
|
16
20
|
"maggie-project-context",
|
|
17
21
|
"maggie-seo-geo",
|
|
@@ -24,6 +28,7 @@ function usage() {
|
|
|
24
28
|
Usage:
|
|
25
29
|
maggie init [--project PATH] [--agent auto|codex|claude|all] [--skills LIST]
|
|
26
30
|
maggie install [SKILL ...] [--project PATH] [--agent auto|codex|claude|all]
|
|
31
|
+
maggie update [SKILL ...] [--project PATH] [--agent auto|codex|claude|all] [--force]
|
|
27
32
|
maggie list
|
|
28
33
|
maggie doctor [--project PATH]
|
|
29
34
|
maggie remove [SKILL ...] [--project PATH] [--agent codex|claude|all]
|
|
@@ -77,6 +82,35 @@ function copyIfMissing(source, target, force = false) {
|
|
|
77
82
|
return "installed";
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
function digest(path) {
|
|
86
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function syncTree(source, target, force) {
|
|
90
|
+
let changed = 0;
|
|
91
|
+
for (const entry of readdirSync(source, { withFileTypes: true })) {
|
|
92
|
+
const sourcePath = join(source, entry.name);
|
|
93
|
+
const targetPath = join(target, entry.name);
|
|
94
|
+
if (entry.isDirectory()) {
|
|
95
|
+
changed += syncTree(sourcePath, targetPath, force);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!existsSync(targetPath)) {
|
|
99
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
100
|
+
writeFileSync(targetPath, readFileSync(sourcePath));
|
|
101
|
+
console.log(`installed ${targetPath}`);
|
|
102
|
+
changed++;
|
|
103
|
+
} else if (force || digest(sourcePath) === digest(targetPath)) {
|
|
104
|
+
writeFileSync(targetPath, readFileSync(sourcePath));
|
|
105
|
+
console.log(`updated ${targetPath}`);
|
|
106
|
+
changed++;
|
|
107
|
+
} else {
|
|
108
|
+
console.log(`preserved ${targetPath} (local changes; use --force to replace)`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return changed;
|
|
112
|
+
}
|
|
113
|
+
|
|
80
114
|
function install(args) {
|
|
81
115
|
const root = projectRoot(args);
|
|
82
116
|
const skills = selectedSkills(args);
|
|
@@ -103,6 +137,34 @@ function install(args) {
|
|
|
103
137
|
console.log("Run `maggie doctor --project .` before using mutating workflows.");
|
|
104
138
|
}
|
|
105
139
|
|
|
140
|
+
function update(args) {
|
|
141
|
+
const root = projectRoot(args);
|
|
142
|
+
const skills = selectedSkills(args);
|
|
143
|
+
const force = args.includes("--force");
|
|
144
|
+
const roots = agentRoots(args, root);
|
|
145
|
+
if (!existsSync(SKILLS_ROOT)) throw new Error("bundled skills are missing; run npm pack from the package source");
|
|
146
|
+
let updated = 0;
|
|
147
|
+
for (const agentRoot of roots) {
|
|
148
|
+
for (const skill of skills) {
|
|
149
|
+
const source = join(SKILLS_ROOT, skill);
|
|
150
|
+
const target = join(agentRoot, "skills", skill);
|
|
151
|
+
if (!existsSync(target)) {
|
|
152
|
+
console.log(`absent ${target} (run install to add it)`);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
updated += syncTree(source, target, force);
|
|
156
|
+
}
|
|
157
|
+
if (existsSync(REFERENCES_ROOT) && existsSync(join(agentRoot, "references"))) updated += syncTree(REFERENCES_ROOT, join(agentRoot, "references"), force);
|
|
158
|
+
}
|
|
159
|
+
const tools = join(root, "tools");
|
|
160
|
+
if (existsSync(TOOLS_ROOT)) for (const group of ["clis", "integrations"]) if (existsSync(join(tools, group))) updated += syncTree(join(TOOLS_ROOT, group), join(tools, group), force);
|
|
161
|
+
const stateDir = join(root, STATE_DIR);
|
|
162
|
+
mkdirSync(stateDir, { recursive: true });
|
|
163
|
+
writeFileSync(join(stateDir, "install.json"), JSON.stringify({ version: "0.1.0", agents: roots.map((item) => item.slice(root.length + 1)), skills, updated_at: new Date().toISOString() }, null, 2) + "\n");
|
|
164
|
+
console.log(`Maggie update complete: ${updated} files changed`);
|
|
165
|
+
if (!force) console.log("Local files with changes were preserved. Review the output and rerun with --force only when replacement is intended.");
|
|
166
|
+
}
|
|
167
|
+
|
|
106
168
|
function list() {
|
|
107
169
|
for (const skill of SKILL_NAMES) console.log(skill);
|
|
108
170
|
}
|
|
@@ -143,6 +205,7 @@ try {
|
|
|
143
205
|
if (["help", "--help", "-h"].includes(command)) usage();
|
|
144
206
|
else if (command === "list") list();
|
|
145
207
|
else if (command === "init" || command === "install") install(args);
|
|
208
|
+
else if (command === "update") update(args);
|
|
146
209
|
else if (command === "doctor") doctor(args);
|
|
147
210
|
else if (command === "remove") remove(args);
|
|
148
211
|
else throw new Error(`unknown command: ${command}`);
|
|
@@ -56,6 +56,14 @@ with the canonical post source enum while remaining unique and rerunnable.
|
|
|
56
56
|
This is the default for a new, small single-instance project. Use the same
|
|
57
57
|
logical fields with Postgres or another engine when the deployment requires it.
|
|
58
58
|
|
|
59
|
+
## Default listing configuration
|
|
60
|
+
|
|
61
|
+
The default blog index is a 3-column grid with 9 posts per page (3×3). Store
|
|
62
|
+
this as typed configuration (`postsPerPage: 9`, `gridColumns: 3`) rather than
|
|
63
|
+
scattering literals through templates. Users may change it later through site
|
|
64
|
+
settings; pagination, canonical URLs, sitemap eligibility, and audits must use
|
|
65
|
+
the same configured value.
|
|
66
|
+
|
|
59
67
|
```sql
|
|
60
68
|
CREATE TABLE posts (
|
|
61
69
|
id TEXT PRIMARY KEY,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Maggie Decision Loop
|
|
2
|
+
|
|
3
|
+
Every state-changing Maggie skill follows this protocol:
|
|
4
|
+
|
|
5
|
+
1. Inspect first and label evidence `Detected`, `Likely`, `Missing`, or
|
|
6
|
+
`Unknown`.
|
|
7
|
+
2. Propose one recommended choice and up to two alternatives.
|
|
8
|
+
3. Ask one focused question at a time, showing the selection, evidence,
|
|
9
|
+
affected files, and risks.
|
|
10
|
+
4. Record each answer in `.maggie/decisions.json` or a skill checkpoint.
|
|
11
|
+
5. Summarize all selections and require explicit final confirmation before
|
|
12
|
+
mutation or external writes.
|
|
13
|
+
6. If an earlier answer changes, recalculate dependent choices and confirm
|
|
14
|
+
again; never silently overwrite a confirmed decision.
|
|
15
|
+
|
|
16
|
+
Headless runs use an answers JSON file plus a separate `--confirm` flag. They
|
|
17
|
+
must follow the same sequence and may not bypass final confirmation.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
python3 tools/clis/maggie.py bootstrap interview .
|
|
21
|
+
python3 tools/clis/maggie.py bootstrap interview . --answers-file .maggie/answers.json --non-interactive --confirm
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Use this loop for framework changes, clone routes, rewrite policy, publishing,
|
|
25
|
+
integrations, migrations, and deployment.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Maggie Ops Dashboard Contract
|
|
2
|
+
|
|
3
|
+
Bootstrap must implement a private, authenticated Ops application before the
|
|
4
|
+
blog is complete. Visual design is flexible; the operational surface and
|
|
5
|
+
server-side boundaries are fixed.
|
|
6
|
+
|
|
7
|
+
## Required screens
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
/ops health summary and pending actions
|
|
11
|
+
/ops/posts inventory, filters, bulk preview/apply
|
|
12
|
+
/ops/posts/new create draft
|
|
13
|
+
/ops/posts/[id]/edit validated editor and metadata preview
|
|
14
|
+
/ops/posts/[id]/preview noindex preview
|
|
15
|
+
/ops/topics topics and FAQs
|
|
16
|
+
/ops/sitemap sources, matching runs, unmatched and eligible counts
|
|
17
|
+
/ops/reports SEO, content quality, visibility, analytics
|
|
18
|
+
/ops/settings/site origin, locale, timezone and defaults
|
|
19
|
+
/ops/settings/integrations Maggie API, GSC, GA4 and provider health
|
|
20
|
+
/ops/operations calendar, tasks, media, redirects and agency work
|
|
21
|
+
/ops/wordpress migration preview, apply, validate and resume
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Required server routes include `/api/ops/summary`, `/api/ops/posts`,
|
|
25
|
+
`/api/ops/topics`, `/api/ops/sitemap/{runs,match,auto-detect}`,
|
|
26
|
+
`/api/ops/reports`, `/api/ops/pull/{project-context,sync,sync-updates}`,
|
|
27
|
+
`/api/ops/rewrite/{queue,history,policy}`, and content-tracking report-state.
|
|
28
|
+
|
|
29
|
+
Every request enforces session authentication, role authorization, input
|
|
30
|
+
validation, and state-transition validation. Keys never reach browser code.
|
|
31
|
+
Mutations show an impact preview where applicable and create an audit event
|
|
32
|
+
with actor, timestamp, previous state, next state, reason, and an
|
|
33
|
+
idempotency/correlation key. Ops pages are `noindex`, excluded from public
|
|
34
|
+
sitemaps, and blocked in robots rules.
|
|
35
|
+
|
|
36
|
+
## Bootstrap acceptance
|
|
37
|
+
|
|
38
|
+
The bootstrap manifest contains the complete Ops route set, an enabled
|
|
39
|
+
dashboard decision, and a verification command. `maggie doctor --strict
|
|
40
|
+
--require-bootstrap` fails when the host project has no Ops UI, Ops API, or
|
|
41
|
+
authentication boundary.
|
package/bundled-skills/README.md
CHANGED
|
@@ -7,6 +7,8 @@ Skills are the agent-facing workflows. They compose with the tools in
|
|
|
7
7
|
|---|---|---|
|
|
8
8
|
| `maggie-blog-bootstrap` | Build a complete blog in an existing project | site audit, analytics, API Pull |
|
|
9
9
|
| `maggie-clone` | Reverse-engineer authorized URLs into namespaced blog-project pages | browser MCP, clone planner CLI, bootstrap state |
|
|
10
|
+
| `maggie-design` | Fully clone authorized interior pages, then reconcile them to the homepage header/footer shell | browser MCP, clone planner CLI, completed Maggie homepage |
|
|
11
|
+
| `maggie-ops` | Build, connect, operate, upgrade, and verify the private blog operations dashboard | Ops dashboard contract, authenticated API bridge, host tests |
|
|
10
12
|
| `maggie-deployment` | Deploy and verify a dynamic Maggie blog, Cloudflare-first | Cloudflare Workers, D1, R2, KV, Wrangler |
|
|
11
13
|
| `maggie-project-context` | Sync Project, voice, site and CTA context | project-context CLI/API |
|
|
12
14
|
| `maggie-seo-geo` | Plan, audit, create/rewrite and measure SEO/GEO | visibility, SEO audit, GSC/GA4, content quality |
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: maggie-blog-bootstrap
|
|
3
3
|
description: Build or complete a small SEO/GEO-ready blog in an existing vibe-coded project, including posts, metadata, sitemap, analytics hooks, and optional AI CMO API Pull integration. Use when adding a blog to a custom site, WordPress, Wix, Shopify, or headless stack.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 1.
|
|
5
|
+
version: 1.4.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maggie Blog Bootstrap
|
|
@@ -31,6 +31,9 @@ GA4/GSC, AI CMO API Pull, rewrite delivery, or a basic content system.
|
|
|
31
31
|
blog” is not approval to select an unconfirmed database, visual identity,
|
|
32
32
|
language, or publishing policy.
|
|
33
33
|
6. Prefer deterministic adapters and idempotent upserts over one-off scripts.
|
|
34
|
+
7. Use the shared [Maggie decision loop](../../references/decision-loop.md):
|
|
35
|
+
inspect, propose, ask one question, record the answer, summarize, and then
|
|
36
|
+
require final confirmation. The CLI equivalent is `bootstrap interview`.
|
|
34
37
|
|
|
35
38
|
## Required phases
|
|
36
39
|
|
|
@@ -140,7 +143,15 @@ database, language, or visual system is unknown, say so instead of guessing.
|
|
|
140
143
|
|
|
141
144
|
### Phase 3: Decision gates
|
|
142
145
|
|
|
143
|
-
Ask the user to confirm the recommendation in small groups before writing code
|
|
146
|
+
Ask the user to confirm the recommendation in small groups before writing code.
|
|
147
|
+
Run the interactive loop when possible:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
python3 tools/clis/maggie.py bootstrap interview .
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
It checkpoints every answer, shows the complete proposal, and requires a
|
|
154
|
+
separate final confirmation. Use the following groups for the conversation:
|
|
144
155
|
|
|
145
156
|
1. **Foundation:** framework, programming language, content language, runtime,
|
|
146
157
|
and deployment target.
|
|
@@ -150,6 +161,11 @@ Ask the user to confirm the recommendation in small groups before writing code:
|
|
|
150
161
|
4. **Content and publishing:** initial routes, API Pull, sitemap matching,
|
|
151
162
|
rewrite approval, analytics, GSC, and whether the first run is dry-run.
|
|
152
163
|
|
|
164
|
+
5. **Operations handoff:** decide whether the project needs the private Maggie
|
|
165
|
+
Ops dashboard now. If yes, hand off to `maggie-ops`; do not silently build a
|
|
166
|
+
partial admin surface inside bootstrap. If no, record Ops as pending while
|
|
167
|
+
completing the public blog foundation.
|
|
168
|
+
|
|
153
169
|
Show the proposed choices, detected evidence, files likely to change, and
|
|
154
170
|
unknowns for each gate. If the user confirms only part of a gate, implement
|
|
155
171
|
only that part and leave the rest pending. Do not ask a single broad “is this
|
|
@@ -169,6 +185,11 @@ English only when existing project language is absent
|
|
|
169
185
|
These are proposals, not automatic permission. SQLite, English, a new font,
|
|
170
186
|
or a new design system must never silently replace detected project choices.
|
|
171
187
|
|
|
188
|
+
The default public blog index is 9 posts per page in a 3-column (3×3) grid.
|
|
189
|
+
Store `postsPerPage: 9` and `gridColumns: 3` in typed configuration so the
|
|
190
|
+
operator can change them later from site settings without changing the post
|
|
191
|
+
model or pagination logic.
|
|
192
|
+
|
|
172
193
|
### Phase 4: Contract
|
|
173
194
|
|
|
174
195
|
Implement or map these post fields:
|
|
@@ -180,7 +201,8 @@ canonicalUrl, coverImage, author, tags, status
|
|
|
180
201
|
|
|
181
202
|
The public contract must support:
|
|
182
203
|
|
|
183
|
-
- `/posts` with stable pagination
|
|
204
|
+
- `/posts` with stable pagination, defaulting to 9 posts per page and a 3-column
|
|
205
|
+
grid;
|
|
184
206
|
- `/posts/:slug` with a real 404 for missing/unpublished posts;
|
|
185
207
|
- canonical, Open Graph, Twitter, and Article metadata;
|
|
186
208
|
- post links that are crawlable plain anchors;
|
|
@@ -199,6 +221,9 @@ Implement the host project's equivalent of:
|
|
|
199
221
|
- JSON-LD `Article` or `BlogPosting` with valid dates and image URLs;
|
|
200
222
|
- GA4 page view/event hooks that do nothing when analytics is disabled;
|
|
201
223
|
- GSC verification via a public token or DNS instruction, never a private key.
|
|
224
|
+
- an explicit Ops decision and handoff to `maggie-ops` when private content
|
|
225
|
+
operations are requested; the Ops dashboard is not satisfied by a mock or
|
|
226
|
+
partial bootstrap screen.
|
|
202
227
|
|
|
203
228
|
### Phase 6: Optional AI CMO integration
|
|
204
229
|
|
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: maggie-clone
|
|
3
|
-
description: Reverse-engineer
|
|
4
|
-
argument-hint: "<target-url1> [<target-url2> ...]"
|
|
3
|
+
description: Reverse-engineer an authorized website homepage and create the Maggie homepage foundation inside an existing blog project. Use with /maggie-clone plus a homepage URL when the user wants to replicate the homepage structure, header, footer, assets, responsive behavior, and interactions.
|
|
5
4
|
metadata:
|
|
6
5
|
version: 1.0.0
|
|
7
6
|
---
|
|
8
7
|
|
|
9
8
|
# Maggie Clone
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
Before any route or asset mutation, follow the shared [Maggie decision loop](../../references/decision-loop.md): inspect, propose, ask focused questions, checkpoint answers, summarize, and confirm.
|
|
11
|
+
|
|
12
|
+
Create the authorized website homepage inside an existing Maggie blog project.
|
|
13
|
+
This is the foundation workflow: it establishes the shared header, footer,
|
|
14
|
+
navigation, brand assets, design tokens, and responsive shell that
|
|
15
|
+
`maggie-design` will reuse for interior pages. It preserves the host project's
|
|
16
|
+
bootstrap foundation, public blog routes, content contracts, SEO safeguards,
|
|
17
|
+
and existing user work while adding namespaced page output.
|
|
15
18
|
|
|
16
19
|
Invoke it as:
|
|
17
20
|
|
|
18
21
|
```text
|
|
19
|
-
/maggie-clone <
|
|
22
|
+
/maggie-clone <homepage-url>
|
|
20
23
|
```
|
|
21
24
|
|
|
22
|
-
The
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
The target must be the origin homepage or an explicitly approved homepage
|
|
26
|
+
variant. If the user supplies an interior page, stop and route the request to
|
|
27
|
+
`maggie-design`; do not use homepage clone to create an interior page.
|
|
25
28
|
|
|
26
29
|
## Boundaries
|
|
27
30
|
|
|
@@ -36,9 +39,15 @@ state but do not silently create duplicate routes.
|
|
|
36
39
|
- Use real public assets only when the user is authorized to reproduce them.
|
|
37
40
|
Preserve attribution or licensing requirements and report assets that could
|
|
38
41
|
not be safely reused.
|
|
39
|
-
-
|
|
40
|
-
component namespace, asset namespace, or research artifact
|
|
41
|
-
|
|
42
|
+
- A supplied homepage URL is always a create or update request. If the
|
|
43
|
+
homepage route, component namespace, asset namespace, or research artifact
|
|
44
|
+
already exists, inspect it and reconcile/regenerate the requested target;
|
|
45
|
+
never silently skip it because output exists. Preserve unrelated local
|
|
46
|
+
changes and report changed, preserved, and conflicted files. Explicit
|
|
47
|
+
approval is still required for unrelated shared-foundation changes.
|
|
48
|
+
- The homepage header and footer become the shared shell source of truth. Record
|
|
49
|
+
their component paths, tokens, breakpoints, states, and asset dependencies so
|
|
50
|
+
later design pages never fork them.
|
|
42
51
|
- Keep AI CMO writes, publishing, and external deployment separate from local
|
|
43
52
|
page construction. A clone does not automatically create, publish, or queue
|
|
44
53
|
content in Maggie.
|
|
@@ -61,7 +70,7 @@ editing and report the missing capability.
|
|
|
61
70
|
```bash
|
|
62
71
|
python3 tools/clis/maggie.py status <project-root>
|
|
63
72
|
python3 tools/clis/maggie.py doctor <project-root> --require-bootstrap --strict
|
|
64
|
-
python3 tools/clis/maggie_clone.py plan <
|
|
73
|
+
python3 tools/clis/maggie_clone.py plan <homepage-url> --project <project-root>
|
|
65
74
|
```
|
|
66
75
|
|
|
67
76
|
The clone planner emits collision-resistant site/page keys and destination
|
|
@@ -76,7 +85,7 @@ editing and report the missing capability.
|
|
|
76
85
|
approval for a combined route-scoped app. Do not mix global fonts or CSS
|
|
77
86
|
foundations silently.
|
|
78
87
|
|
|
79
|
-
Write an output plan before implementation containing
|
|
88
|
+
Write an output plan before implementation containing:
|
|
80
89
|
|
|
81
90
|
```text
|
|
82
91
|
source URL -> destination route
|
|
@@ -185,7 +194,7 @@ The cloned page remains a Maggie blog project page:
|
|
|
185
194
|
|
|
186
195
|
## Phase 4: Verification
|
|
187
196
|
|
|
188
|
-
For
|
|
197
|
+
For the homepage target:
|
|
189
198
|
|
|
190
199
|
1. Run the host typecheck/lint/build commands and `doctor --strict`.
|
|
191
200
|
2. Verify the exact destination URL, neighboring blog routes, 404 behavior,
|
|
@@ -207,7 +216,8 @@ differences and whether they are known, measured, or unverified.
|
|
|
207
216
|
|
|
208
217
|
## Completion report
|
|
209
218
|
|
|
210
|
-
Report source-to-route
|
|
219
|
+
Report source-to-route mapping, the shared header/footer source of truth,
|
|
220
|
+
preserved routes, sections/components/specs,
|
|
211
221
|
assets downloaded and failed, files changed, commands run, build/audit status,
|
|
212
222
|
visual QA result, and known limitations. Deployment, API Pull, rewrite, and
|
|
213
223
|
publication require a separate explicit request.
|
|
@@ -7,6 +7,8 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Maggie Deployment
|
|
9
9
|
|
|
10
|
+
Before changing deployment or production state, follow the shared [Maggie decision loop](../../references/decision-loop.md) and obtain explicit confirmation of target, environment, migrations, and rollback plan.
|
|
11
|
+
|
|
10
12
|
Deploy a confirmed Maggie blog to a real hosting target. Cloudflare Workers is
|
|
11
13
|
the default target for dynamic Astro sites; use the Cloudflare reference for
|
|
12
14
|
Worker, D1, R2, KV, secrets, environments, and verification details.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: maggie-design
|
|
3
|
+
description: Fully clone authorized interior pages, then reconcile their header and footer to the Maggie homepage shell. Use with /maggie-design plus one or more target URLs for pricing, about, feature, landing, or other non-homepage pages.
|
|
4
|
+
metadata:
|
|
5
|
+
version: 1.1.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Maggie Design
|
|
9
|
+
|
|
10
|
+
Create authorized interior pages inside an existing Maggie project. This is
|
|
11
|
+
the interior-page companion to `maggie-clone`, but it is not a shortened
|
|
12
|
+
content-only clone. For every supplied URL, first execute the complete clone
|
|
13
|
+
workflow and create a full first-pass page, including the target's header and
|
|
14
|
+
footer. Then reconcile the page to the already-cloned homepage shell so that
|
|
15
|
+
only the page-specific content remains different.
|
|
16
|
+
|
|
17
|
+
Invoke it as:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
/maggie-design <target-url1> [<target-url2> ...]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Create the execution contract before browser research:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python3 tools/clis/maggie_design.py <target-url1> [<target-url2> ...] \\
|
|
27
|
+
--project <project-root> --save
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The contract is non-skippable. For every supplied URL, execute every phase in
|
|
31
|
+
`required_phases` in order. `operation` is `create` or `update-regenerate`;
|
|
32
|
+
existing routes, artifacts, or screenshots never change a URL into a skip.
|
|
33
|
+
|
|
34
|
+
Every URL is a required work item. A URL that already has a route, component,
|
|
35
|
+
or research directory means update/regenerate that target; it must never be
|
|
36
|
+
silently skipped because output already exists.
|
|
37
|
+
|
|
38
|
+
## Hard boundaries
|
|
39
|
+
|
|
40
|
+
- Require a completed `.maggie/bootstrap-state.json` and a completed homepage
|
|
41
|
+
foundation from `maggie-clone`. If either is missing, stop and request it.
|
|
42
|
+
- Reject the origin homepage as a target. Use `maggie-clone` for that job.
|
|
43
|
+
- The target is initially cloned as a complete page. After that first pass,
|
|
44
|
+
replace its header and footer with the homepage's canonical shared shell.
|
|
45
|
+
Reuse the homepage component, tokens, navigation, logo, fonts, breakpoints,
|
|
46
|
+
responsive behavior, accessibility behavior, analytics hooks, and footer
|
|
47
|
+
links. Do not leave a target-specific shell in the final route.
|
|
48
|
+
- The final page-specific scope is the content between the shared shell:
|
|
49
|
+
hero, sections, cards, forms, pricing/features, testimonials, FAQs,
|
|
50
|
+
conversion blocks, and page-specific interactions. Do not copy credentials,
|
|
51
|
+
private data, tracking IDs, authentication, checkout logic, or proprietary
|
|
52
|
+
backend behavior.
|
|
53
|
+
- Keep each page in its own route and component namespace. Existing blog,
|
|
54
|
+
homepage, metadata, sitemap, robots, and Ops routes remain unchanged except
|
|
55
|
+
for the explicitly requested target route update. Preserve unrelated local
|
|
56
|
+
changes, but do not convert an existing target route into a no-op.
|
|
57
|
+
|
|
58
|
+
## Phase 0: Preflight and decision loop
|
|
59
|
+
|
|
60
|
+
Follow the shared [Maggie decision loop](../../references/decision-loop.md).
|
|
61
|
+
Inspect before editing:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
python3 tools/clis/maggie.py status <project-root>
|
|
65
|
+
python3 tools/clis/maggie.py doctor <project-root> --require-bootstrap --strict
|
|
66
|
+
python3 tools/clis/maggie_clone.py plan <target-url1> [<target-url2> ...] --project <project-root>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Read and follow the complete `maggie-clone` workflow before proceeding. Verify
|
|
70
|
+
the homepage foundation exists and locate its actual shared shell. The plan
|
|
71
|
+
must state, for every target:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
source URL -> destination route
|
|
75
|
+
homepage shell component/layout -> reused unchanged
|
|
76
|
+
content component namespace -> new isolated namespace
|
|
77
|
+
research and screenshots -> page-scoped directories
|
|
78
|
+
assets -> page-scoped directory; shared assets remain shared
|
|
79
|
+
operation -> create or update/regenerate (never skip)
|
|
80
|
+
files preserved -> all unrelated public/blog/Ops routes
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Ask for confirmation of destination routes, page scope, asset reuse, and any
|
|
84
|
+
requested CTA/form behavior before mutation. Interior pages do not silently
|
|
85
|
+
become published posts or sitemap entries. Confirmation authorizes processing
|
|
86
|
+
each listed URL, including an existing route; it does not authorize unrelated
|
|
87
|
+
route changes.
|
|
88
|
+
|
|
89
|
+
## Phase 1: Complete clone reconnaissance
|
|
90
|
+
|
|
91
|
+
This phase is the full `maggie-clone` reconnaissance contract applied to the
|
|
92
|
+
interior URL. Do not replace it with a content-only scrape. The target header
|
|
93
|
+
and footer are deliberately captured and implemented in the first pass even
|
|
94
|
+
though they will be replaced later.
|
|
95
|
+
|
|
96
|
+
Use the available browser/Chrome/Playwright capability. Capture desktop
|
|
97
|
+
1440px, tablet 768px, and mobile 390px. Run the same full reconnaissance
|
|
98
|
+
contract as `maggie-clone`, including the target header and footer. They must
|
|
99
|
+
be captured because the first-pass clone needs complete evidence before the
|
|
100
|
+
shell is reconciled. Record:
|
|
101
|
+
|
|
102
|
+
- full page topology, including header, footer, sticky layers, and shell
|
|
103
|
+
transitions;
|
|
104
|
+
- content topology and section order;
|
|
105
|
+
- exact visible copy, links, labels, images, forms, and public states;
|
|
106
|
+
- content-area typography, spacing, colors, borders, radii, shadows, and
|
|
107
|
+
responsive changes, comparing every value with the homepage tokens;
|
|
108
|
+
- hover, focus, open, loading, empty, error, scroll, tab, and reduced-motion
|
|
109
|
+
behavior;
|
|
110
|
+
- required assets and their reuse/licensing notes;
|
|
111
|
+
- page metadata and whether the page is a marketing route, conversion route,
|
|
112
|
+
or actual blog content.
|
|
113
|
+
|
|
114
|
+
Persist page-scoped evidence before building:
|
|
115
|
+
|
|
116
|
+
```text
|
|
117
|
+
docs/research/<site-key>/<page-key>/
|
|
118
|
+
OUTPUT_PLAN.md
|
|
119
|
+
PAGE_TOPOLOGY.md
|
|
120
|
+
BEHAVIORS.md
|
|
121
|
+
DESIGN_TOKENS.md
|
|
122
|
+
COMPONENT_INVENTORY.md
|
|
123
|
+
ASSET_MANIFEST.md
|
|
124
|
+
docs/design-references/<site-key>/<page-key>/
|
|
125
|
+
desktop.png tablet.png mobile.png
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Every new component spec must initially document the full target component,
|
|
129
|
+
then include a reconciliation note naming the homepage shell source file and
|
|
130
|
+
the final content-only target file and states.
|
|
131
|
+
|
|
132
|
+
## Phase 2: Build the complete first-pass page
|
|
133
|
+
|
|
134
|
+
The first pass is a real full clone, not a mock, analysis-only artifact, or
|
|
135
|
+
shell-free approximation. It must contain the target header, target footer,
|
|
136
|
+
page content, responsive behavior, assets, and observed interactions.
|
|
137
|
+
|
|
138
|
+
Build the target page from the full clone evidence, including its observed
|
|
139
|
+
header, footer, responsive shell, assets, and interactions. This first pass is
|
|
140
|
+
required even when the final result will replace the shell. For an existing
|
|
141
|
+
target route, inspect the current implementation, calculate the update plan,
|
|
142
|
+
and regenerate/reconcile that route; report changed and preserved files.
|
|
143
|
+
|
|
144
|
+
Do not report `already exists` as a completion state and do not skip browser
|
|
145
|
+
research, asset checks, or verification for an existing URL.
|
|
146
|
+
|
|
147
|
+
## Phase 3: Reconcile to the homepage shell
|
|
148
|
+
|
|
149
|
+
After the complete first pass, make the homepage shell the sole source of
|
|
150
|
+
truth. Put page-specific output under an isolated namespace, for example:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
src/components/sites/<site-key>/<page-key>/
|
|
154
|
+
src/pages/<approved-route>.*
|
|
155
|
+
public/sites/<site-key>/<page-key>/
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Perform this reconciliation explicitly:
|
|
159
|
+
|
|
160
|
+
1. Identify the generated target header/footer and the homepage header/footer
|
|
161
|
+
components, layout wrappers, tokens, assets, and behavior contracts.
|
|
162
|
+
2. Replace the generated target shell with imports/composition from the
|
|
163
|
+
homepage source of truth. Do not copy its markup into the page namespace.
|
|
164
|
+
3. Remove target-only shell styles, duplicate logo/font/icon assets, and
|
|
165
|
+
duplicate navigation/footer links from the final page bundle.
|
|
166
|
+
4. Keep the target's route-specific content and interactions inside the
|
|
167
|
+
content region. A global token or shared-shell change requires explicit
|
|
168
|
+
approval.
|
|
169
|
+
5. Verify the resulting DOM has one header and one footer, with the target
|
|
170
|
+
content between them and no hidden duplicate shell.
|
|
171
|
+
|
|
172
|
+
This is a replacement step after the first pass, never a substitute for it.
|
|
173
|
+
The final implementation must not retain the target header/footer as an
|
|
174
|
+
alternative path or silently fall back to them when the homepage shell exists.
|
|
175
|
+
|
|
176
|
+
The final page must:
|
|
177
|
+
|
|
178
|
+
- render inside the exact homepage shell and use its skip link, landmarks,
|
|
179
|
+
navigation, footer, fonts, and responsive breakpoints;
|
|
180
|
+
- use the existing metadata helper and canonical route;
|
|
181
|
+
- use normal anchors and real form validation;
|
|
182
|
+
- keep marketing pages out of the post sitemap and Article JSON-LD unless they
|
|
183
|
+
are explicitly mapped to the canonical post contract;
|
|
184
|
+
- preserve analytics consent and server-only API keys;
|
|
185
|
+
- implement observed interactions rather than guessed click-only substitutes.
|
|
186
|
+
|
|
187
|
+
If a page needs a new global navigation item, footer link, font, token, or
|
|
188
|
+
shared component change, stop, show the impact, and obtain explicit approval.
|
|
189
|
+
|
|
190
|
+
## Phase 4: Verify
|
|
191
|
+
|
|
192
|
+
For every target:
|
|
193
|
+
|
|
194
|
+
1. Run the host build, typecheck/lint, and `maggie doctor --strict` gates.
|
|
195
|
+
2. Verify the exact destination route, homepage route, blog routes, 404,
|
|
196
|
+
robots, sitemap exclusion/inclusion, canonical metadata, and JSON-LD.
|
|
197
|
+
3. Compare the complete first-pass clone to the target at 1440px, 768px, and
|
|
198
|
+
390px. Then compare the final page shell to the homepage and compare only
|
|
199
|
+
the final content region to the target for page fidelity.
|
|
200
|
+
4. Sweep keyboard focus, links, forms, hover, tabs/dialogs, scroll behavior,
|
|
201
|
+
mobile menu, and reduced-motion behavior.
|
|
202
|
+
5. Run `python3 tools/clis/site_audit.py <local-or-production-url> --json`.
|
|
203
|
+
|
|
204
|
+
Do not claim exact fidelity when a dynamic state, asset, or authenticated
|
|
205
|
+
target could not be inspected. Report measured differences and limitations.
|
|
206
|
+
|
|
207
|
+
## Completion report
|
|
208
|
+
|
|
209
|
+
Report every input URL and its create/update result, target-to-route mappings,
|
|
210
|
+
the full first-pass artifacts, reused homepage shell files, removed duplicate
|
|
211
|
+
shell artifacts, final content-only components, research/screenshots, assets,
|
|
212
|
+
preserved unrelated routes, commands and test results, visual QA status, and
|
|
213
|
+
any requested global changes pending approval. Deployment, publishing, API
|
|
214
|
+
Pull, and rewrite operations are separate actions.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: maggie-ops
|
|
3
|
+
description: Build, connect, operate, upgrade, and verify a private Maggie Ops dashboard for blog content, API Pull, sitemap matching, rewrite approvals, reports, and integrations. Use when the user asks for Ops/admin functionality or lifecycle operations rather than public blog pages.
|
|
4
|
+
metadata:
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Maggie Ops
|
|
9
|
+
|
|
10
|
+
Manage the private operational surface for a Maggie blog. This skill is
|
|
11
|
+
separate from `maggie-blog-bootstrap`: bootstrap establishes the public blog
|
|
12
|
+
contract, while Maggie Ops establishes the authenticated editor and operations
|
|
13
|
+
contract around it.
|
|
14
|
+
|
|
15
|
+
Invoke it as:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
/maggie-ops <audit|install|connect|operate|upgrade|verify>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
If the user does not name a mode, inspect the project and propose the smallest
|
|
22
|
+
mode that satisfies the request. Do not rebuild the public blog or change its
|
|
23
|
+
framework just to add Ops.
|
|
24
|
+
|
|
25
|
+
## Required preflight
|
|
26
|
+
|
|
27
|
+
1. Inspect `.maggie/analysis.json` and `.maggie/bootstrap-state.json`.
|
|
28
|
+
2. Read [`ops-dashboard-contract.md`](../../references/ops-dashboard-contract.md).
|
|
29
|
+
3. Inventory existing private routes, session/auth middleware, role checks,
|
|
30
|
+
API proxy routes, content tables, audit events, robots, and deployment
|
|
31
|
+
configuration.
|
|
32
|
+
4. Run the host's existing typecheck, test, and build commands before editing.
|
|
33
|
+
5. State which mode is being run, which routes/API resources it will touch,
|
|
34
|
+
and whether any operation can consume quota or change production state.
|
|
35
|
+
|
|
36
|
+
Do not proceed with a production mutation when authentication, ownership,
|
|
37
|
+
role authorization, secret configuration, or rollback information is unknown.
|
|
38
|
+
|
|
39
|
+
## Modes
|
|
40
|
+
|
|
41
|
+
### Audit
|
|
42
|
+
|
|
43
|
+
Produce a gap report against the Ops contract. Check the actual rendered
|
|
44
|
+
screens and server routes, not just documentation. Classify every item as
|
|
45
|
+
implemented, partial, missing, or unverifiable. Include:
|
|
46
|
+
|
|
47
|
+
- dashboard/list/editor/detail screens and responsive states;
|
|
48
|
+
- session authentication, role authorization, CSRF/input validation, and
|
|
49
|
+
server-only secret handling;
|
|
50
|
+
- content inventory and identity editing;
|
|
51
|
+
- API Pull status, project context, delivery state, and idempotency;
|
|
52
|
+
- sitemap sources, matching runs, unmatched URLs, matched assets, and history;
|
|
53
|
+
- rewrite queue, preview quota reservation, approval/rejection transitions;
|
|
54
|
+
- reports, GSC/GA4/provider health, audit events, noindex, and robots rules.
|
|
55
|
+
|
|
56
|
+
### Install
|
|
57
|
+
|
|
58
|
+
Add or complete the private Ops surface in the existing project. Follow the
|
|
59
|
+
host's UI system and routing conventions, but use the EmDash-style information
|
|
60
|
+
architecture as the baseline:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
/ops dashboard and pending actions
|
|
64
|
+
/ops/posts paginated content list
|
|
65
|
+
/ops/posts/[id]/edit validated editor
|
|
66
|
+
/ops/posts/[id]/preview noindex preview
|
|
67
|
+
/ops/sitemap sources, match runs, history, eligibility
|
|
68
|
+
/ops/reports SEO, GEO, visibility, analytics
|
|
69
|
+
/ops/settings/integrations Maggie API and provider health
|
|
70
|
+
/ops/operations calendar, tasks, media, redirects, agency work
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Use a clean list → detail/editor journey, outlined cards, clear status
|
|
74
|
+
chips, predictable spacing, explicit loading/empty/error states, and a
|
|
75
|
+
desktop/mobile layout consistent with the reference admin product. Do not
|
|
76
|
+
ship a mock table whose buttons do not reach a server route.
|
|
77
|
+
|
|
78
|
+
### Connect
|
|
79
|
+
|
|
80
|
+
Wire the dashboard to real backend contracts. Prefer the first-party Maggie
|
|
81
|
+
API or a narrowly scoped server-side Ops bridge. The browser may call only
|
|
82
|
+
the project's authenticated proxy; Maggie/API secrets must remain server-side.
|
|
83
|
+
|
|
84
|
+
At minimum, verify real readbacks for:
|
|
85
|
+
|
|
86
|
+
- content assets and versions/identity;
|
|
87
|
+
- pull runs and delivery status;
|
|
88
|
+
- sitemap sources and matching runs;
|
|
89
|
+
- rewrite jobs and action state;
|
|
90
|
+
- project context and CTA/settings data.
|
|
91
|
+
|
|
92
|
+
Every mutation must validate the session, role, resource ownership/project
|
|
93
|
+
scope, input schema, legal state transition, and idempotency/correlation key.
|
|
94
|
+
Record actor, timestamp, previous state, next state, reason, and result.
|
|
95
|
+
|
|
96
|
+
### Operate
|
|
97
|
+
|
|
98
|
+
Use the dashboard or API to perform an explicitly requested operation. Show an
|
|
99
|
+
impact preview before bulk or quota-consuming work. Keep these actions
|
|
100
|
+
separate:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
read status → inspect impact → run/mutate → read back result → record audit
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Sitemap matching must report total sitemap documents, total URLs, matched
|
|
107
|
+
posts, already matched/queued posts, not-tracked URLs, newly eligible posts,
|
|
108
|
+
and matching time. Rewrite preview reserves quota; approval is a separate
|
|
109
|
+
human action. Never silently publish, overwrite content, or enqueue a whole
|
|
110
|
+
site because a list endpoint returned data.
|
|
111
|
+
|
|
112
|
+
### Upgrade
|
|
113
|
+
|
|
114
|
+
Upgrade an existing Ops installation without losing local work:
|
|
115
|
+
|
|
116
|
+
1. read the installed version and local modifications;
|
|
117
|
+
2. compare the target contract, frontend routes, backend routes, and schema;
|
|
118
|
+
3. apply additive changes first and write migrations only when required;
|
|
119
|
+
4. preserve local UI customizations unless the user explicitly requests a
|
|
120
|
+
replacement;
|
|
121
|
+
5. run read-only verification before enabling new mutations;
|
|
122
|
+
6. report changed, preserved, conflicting, and deprecated files.
|
|
123
|
+
|
|
124
|
+
For an installed Maggie package, use:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx @topy-ai/maggie update --project .
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Use `--force` only after the user explicitly approves replacing locally
|
|
131
|
+
modified managed files. An npm skill update does not automatically deploy the
|
|
132
|
+
dashboard or migrate a production database.
|
|
133
|
+
|
|
134
|
+
### Verify
|
|
135
|
+
|
|
136
|
+
Run the host build/test/typecheck gates and verify the actual private routes
|
|
137
|
+
with an authenticated session. Test at least:
|
|
138
|
+
|
|
139
|
+
- unauthenticated and non-admin denial;
|
|
140
|
+
- list, detail, editor save, and validation errors;
|
|
141
|
+
- API Pull readback and stale/error states;
|
|
142
|
+
- sitemap matching with zero, partial, and complete matches;
|
|
143
|
+
- rewrite preview, approval, rejection, retry, and quota failure;
|
|
144
|
+
- refresh/idempotency behavior and audit records;
|
|
145
|
+
- `noindex`, robots blocking, and exclusion from public sitemap;
|
|
146
|
+
- desktop and mobile layout against the EmDash-style design baseline.
|
|
147
|
+
|
|
148
|
+
Do not claim an API is connected because a frontend button rendered. Show the
|
|
149
|
+
endpoint, response status, persisted state, and verification command.
|
|
150
|
+
|
|
151
|
+
## Stop conditions
|
|
152
|
+
|
|
153
|
+
Stop before a write when the user has not authorized it, the project scope is
|
|
154
|
+
ambiguous, the endpoint is only proposed/documented rather than reachable,
|
|
155
|
+
the admin identity cannot be verified, the operation would expose a secret,
|
|
156
|
+
or a migration/rollback plan is missing.
|
|
157
|
+
|
|
158
|
+
## Completion report
|
|
159
|
+
|
|
160
|
+
Report the mode, screens and endpoints implemented, authentication/ownership
|
|
161
|
+
boundary, quota impact, database/migration status, audit behavior, preserved
|
|
162
|
+
local changes, commands and live/readback checks, remaining gaps, and whether
|
|
163
|
+
deployment was actually performed.
|
|
@@ -7,6 +7,8 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Maggie Project Context
|
|
9
9
|
|
|
10
|
+
For changes, follow the shared [Maggie decision loop](../../references/decision-loop.md): inspect the project, propose mappings, checkpoint answers, and confirm before writing context or calling external APIs.
|
|
11
|
+
|
|
10
12
|
Use the authenticated AI CMO Project as the source of truth for brand
|
|
11
13
|
positioning, audience, voice, site URL, and conversion CTAs.
|
|
12
14
|
|
|
@@ -7,6 +7,8 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Maggie SEO and GEO
|
|
9
9
|
|
|
10
|
+
For state-changing audits, rewrites, or publishing, follow the shared [Maggie decision loop](../../references/decision-loop.md) and confirm policy and scope before external writes.
|
|
11
|
+
|
|
10
12
|
Treat SEO and GEO as one measurable content system: people-first content,
|
|
11
13
|
technical accessibility, extractable structure, evidence, authority, brand
|
|
12
14
|
voice, CTA alignment, and observed search/AI outcomes.
|
|
@@ -7,6 +7,8 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Maggie Social Share
|
|
9
9
|
|
|
10
|
+
For scheduling or publishing actions, follow the shared [Maggie decision loop](../../references/decision-loop.md): inspect, propose channels and copy, checkpoint answers, then confirm.
|
|
11
|
+
|
|
10
12
|
Social Share is a distribution layer downstream of the same AI CMO Project,
|
|
11
13
|
brand voice, CTA, canonical URL, content asset, and approval state used by SEO
|
|
12
14
|
and GEO. It is not a second content source of truth.
|
|
@@ -397,6 +397,83 @@ def command_status(args: argparse.Namespace) -> int:
|
|
|
397
397
|
return 0 if state.get("status") == "completed" else 1
|
|
398
398
|
|
|
399
399
|
|
|
400
|
+
BOOTSTRAP_QUESTIONS = [
|
|
401
|
+
("framework", "Which framework should Maggie preserve?", ["detected", "astro", "nextjs", "eleventy", "custom"]),
|
|
402
|
+
("language", "Which implementation language should the blog use?", ["detected", "typescript", "javascript"]),
|
|
403
|
+
("content_language", "What is the default content language?", ["detected", "en", "zh-TW", "zh-CN", "multilingual"]),
|
|
404
|
+
("ui_system", "Which UI system should the blog use?", ["preserve", "tailwind", "css-modules", "plain-css"]),
|
|
405
|
+
("icon_set", "Which icon set should the blog use?", ["preserve", "heroicons", "lucide", "none"]),
|
|
406
|
+
("font", "Which typography choice should the blog use?", ["preserve", "system", "custom"]),
|
|
407
|
+
("database", "Which database boundary should the blog use?", ["preserve", "sqlite", "postgres", "mysql"]),
|
|
408
|
+
("content_source", "Where should posts be stored?", ["preserve", "local-content", "database", "api-pull"]),
|
|
409
|
+
("posts_per_page", "How many posts should appear on each blog page?", ["9", "12", "6", "custom"]),
|
|
410
|
+
("grid_columns", "How many columns should the default post grid use?", ["3", "2", "4", "custom"]),
|
|
411
|
+
("ops_dashboard", "Should the project add the private Maggie Ops dashboard after bootstrap?", ["disabled", "enabled"]),
|
|
412
|
+
]
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def question_default(key: str, analysis: dict) -> str:
|
|
416
|
+
detected = analysis.get(key, {}).get("value") if isinstance(analysis.get(key), dict) else None
|
|
417
|
+
if key == "posts_per_page": return "9"
|
|
418
|
+
if key == "grid_columns": return "3"
|
|
419
|
+
if key == "ops_dashboard": return "disabled"
|
|
420
|
+
return detected or {"ui_system": "preserve-or-tailwind", "icon_set": "preserve-or-heroicons", "font": "preserve-or-system", "database": "preserve-or-sqlite"}.get(key, "detected")
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def interview_choices(key: str, choices: list[str], current: str) -> str:
|
|
424
|
+
print(f"\n{key}: {current}")
|
|
425
|
+
for index, choice in enumerate(choices, 1):
|
|
426
|
+
marker = " (recommended)" if choice == current else ""
|
|
427
|
+
print(f" {index}. {choice}{marker}")
|
|
428
|
+
while True:
|
|
429
|
+
answer = input("Select a number, or type a value: ").strip()
|
|
430
|
+
if answer.isdigit() and 1 <= int(answer) <= len(choices):
|
|
431
|
+
return choices[int(answer) - 1]
|
|
432
|
+
if answer in choices or (key in {"posts_per_page", "grid_columns"} and answer.isdigit()):
|
|
433
|
+
return answer
|
|
434
|
+
print("Please choose one of the listed options.")
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def command_bootstrap_interview(args: argparse.Namespace) -> int:
|
|
438
|
+
root = Path(args.project).resolve()
|
|
439
|
+
analysis = detect(root)
|
|
440
|
+
answers: dict[str, str] = {}
|
|
441
|
+
if args.answers_file:
|
|
442
|
+
try:
|
|
443
|
+
loaded = json.loads(Path(args.answers_file).read_text(encoding="utf-8"))
|
|
444
|
+
except (OSError, json.JSONDecodeError) as error:
|
|
445
|
+
raise RuntimeError(f"invalid answers file: {args.answers_file}") from error
|
|
446
|
+
if not isinstance(loaded, dict): raise RuntimeError("answers file must contain a JSON object")
|
|
447
|
+
answers.update({str(key): str(value) for key, value in loaded.items()})
|
|
448
|
+
for key, question, choices in BOOTSTRAP_QUESTIONS:
|
|
449
|
+
value = answers.get(key, question_default(key, analysis))
|
|
450
|
+
if not args.non_interactive and key not in answers:
|
|
451
|
+
print(f"\n{question}")
|
|
452
|
+
value = interview_choices(key, choices, value)
|
|
453
|
+
elif value not in choices and key not in {"posts_per_page", "grid_columns"}:
|
|
454
|
+
raise RuntimeError(f"invalid value for {key}: {value}")
|
|
455
|
+
answers[key] = value
|
|
456
|
+
checkpoint = root / STATE_DIR / "bootstrap-interview.json"
|
|
457
|
+
checkpoint.parent.mkdir(parents=True, exist_ok=True)
|
|
458
|
+
checkpoint.write_text(json.dumps({"status": "in_progress", "answers": answers, "updated_at": utc_now()}, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
459
|
+
print("\nBootstrap proposal")
|
|
460
|
+
print(json.dumps(answers, indent=2, ensure_ascii=False))
|
|
461
|
+
if not args.confirm:
|
|
462
|
+
if args.non_interactive:
|
|
463
|
+
print("CONFIRMATION_REQUIRED: rerun with --confirm after reviewing the proposal.", file=sys.stderr)
|
|
464
|
+
return 2
|
|
465
|
+
if input("Apply these selections and generate the bootstrap contract? [y/N] ").strip().lower() not in {"y", "yes"}:
|
|
466
|
+
print("Bootstrap remains pending; the checkpoint was saved.")
|
|
467
|
+
return 0
|
|
468
|
+
complete_args = argparse.Namespace(
|
|
469
|
+
project=str(root), confirm=["foundation", "experience", "data", "publishing"],
|
|
470
|
+
framework=answers["framework"], language=answers["language"], content_language=answers["content_language"],
|
|
471
|
+
ui_system=answers["ui_system"], icon_set=answers["icon_set"], font=answers["font"], database=answers["database"], content_source=answers["content_source"],
|
|
472
|
+
posts_per_page=int(answers["posts_per_page"]), grid_columns=int(answers["grid_columns"]), ops_dashboard=answers["ops_dashboard"] == "enabled",
|
|
473
|
+
)
|
|
474
|
+
return command_complete(complete_args)
|
|
475
|
+
|
|
476
|
+
|
|
400
477
|
def command_complete(args: argparse.Namespace) -> int:
|
|
401
478
|
root = Path(args.project).resolve()
|
|
402
479
|
required = {"foundation", "experience", "data", "publishing"}
|
|
@@ -406,7 +483,7 @@ def command_complete(args: argparse.Namespace) -> int:
|
|
|
406
483
|
print("Confirmation required for: " + ", ".join(sorted(missing)), file=sys.stderr)
|
|
407
484
|
return 2
|
|
408
485
|
state = {
|
|
409
|
-
"schema_version": "1.
|
|
486
|
+
"schema_version": "1.1",
|
|
410
487
|
"status": "completed",
|
|
411
488
|
"completed_at": utc_now(),
|
|
412
489
|
"confirmed": {name: True for name in sorted(required)},
|
|
@@ -419,6 +496,9 @@ def command_complete(args: argparse.Namespace) -> int:
|
|
|
419
496
|
"font": args.font,
|
|
420
497
|
"database": args.database,
|
|
421
498
|
"content_source": args.content_source,
|
|
499
|
+
"posts_per_page": args.posts_per_page,
|
|
500
|
+
"grid_columns": args.grid_columns,
|
|
501
|
+
"ops_dashboard": args.ops_dashboard,
|
|
422
502
|
},
|
|
423
503
|
}
|
|
424
504
|
path = state_path(root)
|
|
@@ -427,8 +507,8 @@ def command_complete(args: argparse.Namespace) -> int:
|
|
|
427
507
|
contracts = {
|
|
428
508
|
"project.json": {"schema_version": "1.0", "framework": args.framework, "language": args.language, "database": args.database, "content_source": args.content_source},
|
|
429
509
|
"decisions.json": state["decisions"],
|
|
430
|
-
"schema.json": {"post_required": ["id", "slug", "title", "content", "status", "canonicalUrl", "publishedAt", "updatedAt", "author"], "public_status": "published"},
|
|
431
|
-
"routes.json": {"public": ["/about", "/blog", "/blog/[slug]", "/topics", "/authors/[slug]", "/sitemap.xml", "/robots.txt"], "ops": ["/ops", "/ops/posts", "/ops/operations", "/ops/wordpress", "/api/ops/bulk", "/api/ops/agency", "/api/ops/entities", "/api/ops/migrations/[id]/resume", "/api/ops/wordpress/migration-plan", "/api/ops/pull/project-context", "/api/ops/pull/sync-updates", "/api/ops/sitemap/matching-history", "/api/ops/content-tracking/report-state", "/api/ops/content-tracking/report-state/batch", "/api/ops/playbooks/[id]/run"]},
|
|
510
|
+
"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}"},
|
|
511
|
+
"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"]},
|
|
432
512
|
"integrations.json": {"ai_cmo": "server-only", "gsc": "server-only", "ga4": "consent-aware"},
|
|
433
513
|
"migration.json": {"supported": ["wordpress-rest", "wxr", "csv", "json", "sitemap", "media-archive"], "idempotency": "source-id-and-checksum", "default_conflict": "manual", "sync_modes": ["all", "new", "modified"], "deleted_policy": "archive"},
|
|
434
514
|
}
|
|
@@ -511,10 +591,12 @@ def command_doctor(args: argparse.Namespace) -> int:
|
|
|
511
591
|
"faq_surface": "faq" in text.lower(),
|
|
512
592
|
"cta_surface": "cta" in text.lower(),
|
|
513
593
|
"pagination_surface": "pagination" in text.lower() or "page_size" in text.lower(),
|
|
594
|
+
"nine_posts_three_columns": bool(re.search(r"(?:postsPerPage|posts_per_page|PUBLIC_POSTS_PER_PAGE)[^\n]{0,80}(?:9|default.?9)", text, re.IGNORECASE)) and ("grid" in text.lower() or "grid_columns" in text.lower()),
|
|
514
595
|
"eeat_authorship": "author" in text.lower() and ("credential" in text.lower() or "organisation" in text.lower() or "organization" in text.lower()),
|
|
515
596
|
"modified_date": "dateModified" in text or "updatedAt" in text,
|
|
516
597
|
"ops_surface": any("ops" in path for path in paths),
|
|
517
598
|
"ops_authentication": "auth" in text.lower() or "middleware" in text.lower(),
|
|
599
|
+
"ops_api_surface": all(token in text for token in ("/api/ops/summary", "/api/ops/posts", "/api/ops/sitemap", "/api/ops/reports")),
|
|
518
600
|
"integration_config": "AI_CMO_API_KEY" in text or "measurement_id" in text.lower() or "verification" in text.lower(),
|
|
519
601
|
"bootstrap_contract": all((root / STATE_DIR / filename).exists() for filename in ("project.json", "decisions.json", "schema.json", "routes.json", "integrations.json", "migration.json")),
|
|
520
602
|
"scheduled_publication_state": "scheduled" in text.lower() and "published" in text.lower(),
|
|
@@ -610,9 +692,18 @@ def parser() -> argparse.ArgumentParser:
|
|
|
610
692
|
bootstrap_sub = bootstrap.add_subparsers(dest="bootstrap_command", required=True)
|
|
611
693
|
complete = bootstrap_sub.add_parser("complete", help="write state after explicit user confirmations")
|
|
612
694
|
complete.add_argument("project", nargs="?", default=".")
|
|
695
|
+
interview = bootstrap_sub.add_parser("interview", help="walk through one decision at a time and confirm the proposal")
|
|
696
|
+
interview.add_argument("project", nargs="?", default=".")
|
|
697
|
+
interview.add_argument("--answers-file", help="JSON answers for headless/CI use")
|
|
698
|
+
interview.add_argument("--non-interactive", action="store_true", help="do not prompt; requires --answers-file and --confirm")
|
|
699
|
+
interview.add_argument("--confirm", action="store_true", help="apply the reviewed proposal")
|
|
700
|
+
interview.set_defaults(func=command_bootstrap_interview)
|
|
613
701
|
complete.add_argument("--confirm", action="append", choices=["foundation", "experience", "data", "publishing"], required=True)
|
|
614
702
|
for name, default in (("framework", "detected"), ("language", "detected"), ("content-language", "confirmed"), ("ui-system", "preserve-or-tailwind"), ("icon-set", "preserve-or-heroicons"), ("font", "preserve-or-system"), ("database", "preserve-or-sqlite"), ("content-source", "confirmed")):
|
|
615
703
|
complete.add_argument("--" + name, dest=name.replace("-", "_"), default=default)
|
|
704
|
+
complete.add_argument("--posts-per-page", type=int, default=9)
|
|
705
|
+
complete.add_argument("--grid-columns", type=int, default=3)
|
|
706
|
+
complete.add_argument("--ops-dashboard", action=argparse.BooleanOptionalAction, default=False)
|
|
616
707
|
complete.set_defaults(func=command_complete)
|
|
617
708
|
generate = sub.add_parser("generate", help="generate stable contract or fixture files")
|
|
618
709
|
generate.add_argument("target", choices=["contract", "fixture", "post", "topic", "author", "migration", "seo", "ops-page"])
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Create a non-skippable execution contract for Maggie interior-page design.
|
|
3
|
+
|
|
4
|
+
This command does not fetch a site or edit the host project. It gives an
|
|
5
|
+
agent a machine-readable checklist so an interior URL cannot accidentally be
|
|
6
|
+
treated as a content-only clone or skipped because output already exists.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from urllib.parse import urlsplit
|
|
15
|
+
|
|
16
|
+
from maggie_clone import normalized, plan
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
PHASES = [
|
|
20
|
+
{
|
|
21
|
+
"id": "preflight",
|
|
22
|
+
"required": [
|
|
23
|
+
"bootstrap-state",
|
|
24
|
+
"homepage-shell",
|
|
25
|
+
"host-inventory",
|
|
26
|
+
"baseline-validation",
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "full-research",
|
|
31
|
+
"required": [
|
|
32
|
+
"desktop-1440",
|
|
33
|
+
"tablet-768",
|
|
34
|
+
"mobile-390",
|
|
35
|
+
"target-header",
|
|
36
|
+
"target-footer",
|
|
37
|
+
"assets",
|
|
38
|
+
"interactions",
|
|
39
|
+
"component-specs",
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "full-first-pass",
|
|
44
|
+
"required": [
|
|
45
|
+
"target-header",
|
|
46
|
+
"target-footer",
|
|
47
|
+
"page-content",
|
|
48
|
+
"responsive-behavior",
|
|
49
|
+
"target-interactions",
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "shell-reconciliation",
|
|
54
|
+
"required": [
|
|
55
|
+
"replace-target-header-with-homepage-shell",
|
|
56
|
+
"replace-target-footer-with-homepage-shell",
|
|
57
|
+
"remove-duplicate-shell-assets-and-styles",
|
|
58
|
+
"one-header-and-one-footer",
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "verification",
|
|
63
|
+
"required": [
|
|
64
|
+
"build-typecheck-lint",
|
|
65
|
+
"route-and-404",
|
|
66
|
+
"metadata-robots-sitemap-jsonld",
|
|
67
|
+
"shell-regression",
|
|
68
|
+
"desktop-tablet-mobile-comparison",
|
|
69
|
+
"site-audit",
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def existing_evidence(project: Path, target: dict[str, str]) -> list[str]:
|
|
76
|
+
"""Return evidence paths without deciding that any work can be skipped."""
|
|
77
|
+
|
|
78
|
+
candidates = [
|
|
79
|
+
Path(target["artifact_root"]),
|
|
80
|
+
Path(target["screenshot_root"]),
|
|
81
|
+
Path(target["component_root"]),
|
|
82
|
+
Path(target["asset_root"]),
|
|
83
|
+
]
|
|
84
|
+
route = target["destination_route"].strip("/") or "root"
|
|
85
|
+
for base in (project / "src" / "app", project / "src" / "pages"):
|
|
86
|
+
if base.exists():
|
|
87
|
+
candidates.extend(
|
|
88
|
+
path
|
|
89
|
+
for path in base.rglob("*")
|
|
90
|
+
if path.is_file() and route.lower() in str(path).lower()
|
|
91
|
+
)
|
|
92
|
+
return sorted({str(path) for path in candidates if path.exists()})
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def design_plan(urls: list[str], project: Path) -> dict[str, object]:
|
|
96
|
+
targets = []
|
|
97
|
+
for target in plan(urls, project):
|
|
98
|
+
parsed = urlsplit(target["source_url"])
|
|
99
|
+
if parsed.path in {"", "/"}:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
f"homepage URL is not valid for maggie-design: {target['source_url']} "
|
|
102
|
+
"(use maggie-clone)"
|
|
103
|
+
)
|
|
104
|
+
evidence = existing_evidence(project, target)
|
|
105
|
+
targets.append(
|
|
106
|
+
{
|
|
107
|
+
**target,
|
|
108
|
+
"operation": "update-regenerate" if evidence else "create",
|
|
109
|
+
"input_url_is_required_work_item": True,
|
|
110
|
+
"skip_allowed": False,
|
|
111
|
+
"existing_evidence": evidence,
|
|
112
|
+
"required_phases": PHASES,
|
|
113
|
+
"shell_source_of_truth": {
|
|
114
|
+
"must_resolve_before_build": True,
|
|
115
|
+
"must_be_reused_in_final_page": True,
|
|
116
|
+
"target_shell_allowed_in_final_page": False,
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
return {
|
|
121
|
+
"workflow": "maggie-design",
|
|
122
|
+
"contract_version": "1.0",
|
|
123
|
+
"project_root": str(project),
|
|
124
|
+
"non_skippable": True,
|
|
125
|
+
"rule": "Every supplied URL is processed through all required phases; existing output changes the operation to update-regenerate, never skip.",
|
|
126
|
+
"targets": targets,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def main() -> int:
|
|
131
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
132
|
+
parser.add_argument("urls", nargs="+", help="authorized interior-page URLs")
|
|
133
|
+
parser.add_argument("--project", type=Path, default=Path.cwd())
|
|
134
|
+
parser.add_argument("--save", action="store_true", help="save .maggie/design-plan.json")
|
|
135
|
+
args = parser.parse_args()
|
|
136
|
+
try:
|
|
137
|
+
project = args.project.resolve()
|
|
138
|
+
result = design_plan(args.urls, project)
|
|
139
|
+
except ValueError as error:
|
|
140
|
+
parser.error(str(error))
|
|
141
|
+
encoded = json.dumps(result, indent=2, ensure_ascii=False) + "\n"
|
|
142
|
+
if args.save:
|
|
143
|
+
destination = project / ".maggie" / "design-plan.json"
|
|
144
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
145
|
+
destination.write_text(encoded, encoding="utf-8")
|
|
146
|
+
result["saved_to"] = str(destination)
|
|
147
|
+
encoded = json.dumps(result, indent=2, ensure_ascii=False) + "\n"
|
|
148
|
+
print(encoded, end="")
|
|
149
|
+
return 0
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
raise SystemExit(main())
|
package/package.json
CHANGED
|
@@ -56,6 +56,14 @@ with the canonical post source enum while remaining unique and rerunnable.
|
|
|
56
56
|
This is the default for a new, small single-instance project. Use the same
|
|
57
57
|
logical fields with Postgres or another engine when the deployment requires it.
|
|
58
58
|
|
|
59
|
+
## Default listing configuration
|
|
60
|
+
|
|
61
|
+
The default blog index is a 3-column grid with 9 posts per page (3×3). Store
|
|
62
|
+
this as typed configuration (`postsPerPage: 9`, `gridColumns: 3`) rather than
|
|
63
|
+
scattering literals through templates. Users may change it later through site
|
|
64
|
+
settings; pagination, canonical URLs, sitemap eligibility, and audits must use
|
|
65
|
+
the same configured value.
|
|
66
|
+
|
|
59
67
|
```sql
|
|
60
68
|
CREATE TABLE posts (
|
|
61
69
|
id TEXT PRIMARY KEY,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Maggie Decision Loop
|
|
2
|
+
|
|
3
|
+
Every state-changing Maggie skill follows this protocol:
|
|
4
|
+
|
|
5
|
+
1. Inspect first and label evidence `Detected`, `Likely`, `Missing`, or
|
|
6
|
+
`Unknown`.
|
|
7
|
+
2. Propose one recommended choice and up to two alternatives.
|
|
8
|
+
3. Ask one focused question at a time, showing the selection, evidence,
|
|
9
|
+
affected files, and risks.
|
|
10
|
+
4. Record each answer in `.maggie/decisions.json` or a skill checkpoint.
|
|
11
|
+
5. Summarize all selections and require explicit final confirmation before
|
|
12
|
+
mutation or external writes.
|
|
13
|
+
6. If an earlier answer changes, recalculate dependent choices and confirm
|
|
14
|
+
again; never silently overwrite a confirmed decision.
|
|
15
|
+
|
|
16
|
+
Headless runs use an answers JSON file plus a separate `--confirm` flag. They
|
|
17
|
+
must follow the same sequence and may not bypass final confirmation.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
python3 tools/clis/maggie.py bootstrap interview .
|
|
21
|
+
python3 tools/clis/maggie.py bootstrap interview . --answers-file .maggie/answers.json --non-interactive --confirm
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Use this loop for framework changes, clone routes, rewrite policy, publishing,
|
|
25
|
+
integrations, migrations, and deployment.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Maggie Ops Dashboard Contract
|
|
2
|
+
|
|
3
|
+
Bootstrap must implement a private, authenticated Ops application before the
|
|
4
|
+
blog is complete. Visual design is flexible; the operational surface and
|
|
5
|
+
server-side boundaries are fixed.
|
|
6
|
+
|
|
7
|
+
## Required screens
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
/ops health summary and pending actions
|
|
11
|
+
/ops/posts inventory, filters, bulk preview/apply
|
|
12
|
+
/ops/posts/new create draft
|
|
13
|
+
/ops/posts/[id]/edit validated editor and metadata preview
|
|
14
|
+
/ops/posts/[id]/preview noindex preview
|
|
15
|
+
/ops/topics topics and FAQs
|
|
16
|
+
/ops/sitemap sources, matching runs, unmatched and eligible counts
|
|
17
|
+
/ops/reports SEO, content quality, visibility, analytics
|
|
18
|
+
/ops/settings/site origin, locale, timezone and defaults
|
|
19
|
+
/ops/settings/integrations Maggie API, GSC, GA4 and provider health
|
|
20
|
+
/ops/operations calendar, tasks, media, redirects and agency work
|
|
21
|
+
/ops/wordpress migration preview, apply, validate and resume
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Required server routes include `/api/ops/summary`, `/api/ops/posts`,
|
|
25
|
+
`/api/ops/topics`, `/api/ops/sitemap/{runs,match,auto-detect}`,
|
|
26
|
+
`/api/ops/reports`, `/api/ops/pull/{project-context,sync,sync-updates}`,
|
|
27
|
+
`/api/ops/rewrite/{queue,history,policy}`, and content-tracking report-state.
|
|
28
|
+
|
|
29
|
+
Every request enforces session authentication, role authorization, input
|
|
30
|
+
validation, and state-transition validation. Keys never reach browser code.
|
|
31
|
+
Mutations show an impact preview where applicable and create an audit event
|
|
32
|
+
with actor, timestamp, previous state, next state, reason, and an
|
|
33
|
+
idempotency/correlation key. Ops pages are `noindex`, excluded from public
|
|
34
|
+
sitemaps, and blocked in robots rules.
|
|
35
|
+
|
|
36
|
+
## Bootstrap acceptance
|
|
37
|
+
|
|
38
|
+
The bootstrap manifest contains the complete Ops route set, an enabled
|
|
39
|
+
dashboard decision, and a verification command. `maggie doctor --strict
|
|
40
|
+
--require-bootstrap` fails when the host project has no Ops UI, Ops API, or
|
|
41
|
+
authentication boundary.
|