@topy-ai/maggie 0.1.0 → 0.1.1
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 +63 -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 +1 -0
- package/bundled-skills/maggie-blog-bootstrap/SKILL.md +31 -3
- package/bundled-skills/maggie-clone/SKILL.md +22 -15
- package/bundled-skills/maggie-deployment/SKILL.md +2 -0
- package/bundled-skills/maggie-design/SKILL.md +146 -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/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,11 @@ 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",
|
|
15
18
|
"maggie-deployment",
|
|
16
19
|
"maggie-project-context",
|
|
17
20
|
"maggie-seo-geo",
|
|
@@ -24,6 +27,7 @@ function usage() {
|
|
|
24
27
|
Usage:
|
|
25
28
|
maggie init [--project PATH] [--agent auto|codex|claude|all] [--skills LIST]
|
|
26
29
|
maggie install [SKILL ...] [--project PATH] [--agent auto|codex|claude|all]
|
|
30
|
+
maggie update [SKILL ...] [--project PATH] [--agent auto|codex|claude|all] [--force]
|
|
27
31
|
maggie list
|
|
28
32
|
maggie doctor [--project PATH]
|
|
29
33
|
maggie remove [SKILL ...] [--project PATH] [--agent codex|claude|all]
|
|
@@ -77,6 +81,35 @@ function copyIfMissing(source, target, force = false) {
|
|
|
77
81
|
return "installed";
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
function digest(path) {
|
|
85
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function syncTree(source, target, force) {
|
|
89
|
+
let changed = 0;
|
|
90
|
+
for (const entry of readdirSync(source, { withFileTypes: true })) {
|
|
91
|
+
const sourcePath = join(source, entry.name);
|
|
92
|
+
const targetPath = join(target, entry.name);
|
|
93
|
+
if (entry.isDirectory()) {
|
|
94
|
+
changed += syncTree(sourcePath, targetPath, force);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!existsSync(targetPath)) {
|
|
98
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
99
|
+
writeFileSync(targetPath, readFileSync(sourcePath));
|
|
100
|
+
console.log(`installed ${targetPath}`);
|
|
101
|
+
changed++;
|
|
102
|
+
} else if (force || digest(sourcePath) === digest(targetPath)) {
|
|
103
|
+
writeFileSync(targetPath, readFileSync(sourcePath));
|
|
104
|
+
console.log(`updated ${targetPath}`);
|
|
105
|
+
changed++;
|
|
106
|
+
} else {
|
|
107
|
+
console.log(`preserved ${targetPath} (local changes; use --force to replace)`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return changed;
|
|
111
|
+
}
|
|
112
|
+
|
|
80
113
|
function install(args) {
|
|
81
114
|
const root = projectRoot(args);
|
|
82
115
|
const skills = selectedSkills(args);
|
|
@@ -103,6 +136,34 @@ function install(args) {
|
|
|
103
136
|
console.log("Run `maggie doctor --project .` before using mutating workflows.");
|
|
104
137
|
}
|
|
105
138
|
|
|
139
|
+
function update(args) {
|
|
140
|
+
const root = projectRoot(args);
|
|
141
|
+
const skills = selectedSkills(args);
|
|
142
|
+
const force = args.includes("--force");
|
|
143
|
+
const roots = agentRoots(args, root);
|
|
144
|
+
if (!existsSync(SKILLS_ROOT)) throw new Error("bundled skills are missing; run npm pack from the package source");
|
|
145
|
+
let updated = 0;
|
|
146
|
+
for (const agentRoot of roots) {
|
|
147
|
+
for (const skill of skills) {
|
|
148
|
+
const source = join(SKILLS_ROOT, skill);
|
|
149
|
+
const target = join(agentRoot, "skills", skill);
|
|
150
|
+
if (!existsSync(target)) {
|
|
151
|
+
console.log(`absent ${target} (run install to add it)`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
updated += syncTree(source, target, force);
|
|
155
|
+
}
|
|
156
|
+
if (existsSync(REFERENCES_ROOT) && existsSync(join(agentRoot, "references"))) updated += syncTree(REFERENCES_ROOT, join(agentRoot, "references"), force);
|
|
157
|
+
}
|
|
158
|
+
const tools = join(root, "tools");
|
|
159
|
+
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);
|
|
160
|
+
const stateDir = join(root, STATE_DIR);
|
|
161
|
+
mkdirSync(stateDir, { recursive: true });
|
|
162
|
+
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");
|
|
163
|
+
console.log(`Maggie update complete: ${updated} files changed`);
|
|
164
|
+
if (!force) console.log("Local files with changes were preserved. Review the output and rerun with --force only when replacement is intended.");
|
|
165
|
+
}
|
|
166
|
+
|
|
106
167
|
function list() {
|
|
107
168
|
for (const skill of SKILL_NAMES) console.log(skill);
|
|
108
169
|
}
|
|
@@ -143,6 +204,7 @@ try {
|
|
|
143
204
|
if (["help", "--help", "-h"].includes(command)) usage();
|
|
144
205
|
else if (command === "list") list();
|
|
145
206
|
else if (command === "init" || command === "install") install(args);
|
|
207
|
+
else if (command === "update") update(args);
|
|
146
208
|
else if (command === "doctor") doctor(args);
|
|
147
209
|
else if (command === "remove") remove(args);
|
|
148
210
|
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,7 @@ 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` | Build authorized interior pages using the homepage header/footer as the shared shell | browser MCP, clone planner CLI, completed Maggie homepage |
|
|
10
11
|
| `maggie-deployment` | Deploy and verify a dynamic Maggie blog, Cloudflare-first | Cloudflare Workers, D1, R2, KV, Wrangler |
|
|
11
12
|
| `maggie-project-context` | Sync Project, voice, site and CTA context | project-context CLI/API |
|
|
12
13
|
| `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,14 @@ 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:** enable the complete private Maggie Ops dashboard, including
|
|
165
|
+
content inventory/editor/preview, topics and FAQs, sitemap matching and
|
|
166
|
+
history, rewrite/API Pull controls, reports, settings/integrations,
|
|
167
|
+
migrations, calendar, media, redirects, tasks, agency scope, audit events,
|
|
168
|
+
authentication, role checks, and noindex/robots protection. This is enabled
|
|
169
|
+
by default and is required for a completed bootstrap unless the user
|
|
170
|
+
explicitly chooses a different operating model.
|
|
171
|
+
|
|
153
172
|
Show the proposed choices, detected evidence, files likely to change, and
|
|
154
173
|
unknowns for each gate. If the user confirms only part of a gate, implement
|
|
155
174
|
only that part and leave the rest pending. Do not ask a single broad “is this
|
|
@@ -169,6 +188,11 @@ English only when existing project language is absent
|
|
|
169
188
|
These are proposals, not automatic permission. SQLite, English, a new font,
|
|
170
189
|
or a new design system must never silently replace detected project choices.
|
|
171
190
|
|
|
191
|
+
The default public blog index is 9 posts per page in a 3-column (3×3) grid.
|
|
192
|
+
Store `postsPerPage: 9` and `gridColumns: 3` in typed configuration so the
|
|
193
|
+
operator can change them later from site settings without changing the post
|
|
194
|
+
model or pagination logic.
|
|
195
|
+
|
|
172
196
|
### Phase 4: Contract
|
|
173
197
|
|
|
174
198
|
Implement or map these post fields:
|
|
@@ -180,7 +204,8 @@ canonicalUrl, coverImage, author, tags, status
|
|
|
180
204
|
|
|
181
205
|
The public contract must support:
|
|
182
206
|
|
|
183
|
-
- `/posts` with stable pagination
|
|
207
|
+
- `/posts` with stable pagination, defaulting to 9 posts per page and a 3-column
|
|
208
|
+
grid;
|
|
184
209
|
- `/posts/:slug` with a real 404 for missing/unpublished posts;
|
|
185
210
|
- canonical, Open Graph, Twitter, and Article metadata;
|
|
186
211
|
- post links that are crawlable plain anchors;
|
|
@@ -199,6 +224,9 @@ Implement the host project's equivalent of:
|
|
|
199
224
|
- JSON-LD `Article` or `BlogPosting` with valid dates and image URLs;
|
|
200
225
|
- GA4 page view/event hooks that do nothing when analytics is disabled;
|
|
201
226
|
- GSC verification via a public token or DNS instruction, never a private key.
|
|
227
|
+
- a complete private Ops dashboard following
|
|
228
|
+
[`ops-dashboard-contract.md`](../../references/ops-dashboard-contract.md),
|
|
229
|
+
backed by the same repository as public reads; do not ship a mock-only Ops UI.
|
|
202
230
|
|
|
203
231
|
### Phase 6: Optional AI CMO integration
|
|
204
232
|
|
|
@@ -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,12 @@ 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
|
-
- The clone is an additive
|
|
42
|
+
- The clone is an additive homepage operation. Do not overwrite an existing route,
|
|
40
43
|
component namespace, asset namespace, or research artifact without explicit
|
|
41
44
|
approval.
|
|
45
|
+
- The homepage header and footer become the shared shell source of truth. Record
|
|
46
|
+
their component paths, tokens, breakpoints, states, and asset dependencies so
|
|
47
|
+
later design pages never fork them.
|
|
42
48
|
- Keep AI CMO writes, publishing, and external deployment separate from local
|
|
43
49
|
page construction. A clone does not automatically create, publish, or queue
|
|
44
50
|
content in Maggie.
|
|
@@ -61,7 +67,7 @@ editing and report the missing capability.
|
|
|
61
67
|
```bash
|
|
62
68
|
python3 tools/clis/maggie.py status <project-root>
|
|
63
69
|
python3 tools/clis/maggie.py doctor <project-root> --require-bootstrap --strict
|
|
64
|
-
python3 tools/clis/maggie_clone.py plan <
|
|
70
|
+
python3 tools/clis/maggie_clone.py plan <homepage-url> --project <project-root>
|
|
65
71
|
```
|
|
66
72
|
|
|
67
73
|
The clone planner emits collision-resistant site/page keys and destination
|
|
@@ -76,7 +82,7 @@ editing and report the missing capability.
|
|
|
76
82
|
approval for a combined route-scoped app. Do not mix global fonts or CSS
|
|
77
83
|
foundations silently.
|
|
78
84
|
|
|
79
|
-
Write an output plan before implementation containing
|
|
85
|
+
Write an output plan before implementation containing:
|
|
80
86
|
|
|
81
87
|
```text
|
|
82
88
|
source URL -> destination route
|
|
@@ -185,7 +191,7 @@ The cloned page remains a Maggie blog project page:
|
|
|
185
191
|
|
|
186
192
|
## Phase 4: Verification
|
|
187
193
|
|
|
188
|
-
For
|
|
194
|
+
For the homepage target:
|
|
189
195
|
|
|
190
196
|
1. Run the host typecheck/lint/build commands and `doctor --strict`.
|
|
191
197
|
2. Verify the exact destination URL, neighboring blog routes, 404 behavior,
|
|
@@ -207,7 +213,8 @@ differences and whether they are known, measured, or unverified.
|
|
|
207
213
|
|
|
208
214
|
## Completion report
|
|
209
215
|
|
|
210
|
-
Report source-to-route
|
|
216
|
+
Report source-to-route mapping, the shared header/footer source of truth,
|
|
217
|
+
preserved routes, sections/components/specs,
|
|
211
218
|
assets downloaded and failed, files changed, commands run, build/audit status,
|
|
212
219
|
visual QA result, and known limitations. Deployment, API Pull, rewrite, and
|
|
213
220
|
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,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: maggie-design
|
|
3
|
+
description: Design or recreate authorized interior pages inside a Maggie blog while reusing the cloned homepage header and footer exactly. 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.0.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Maggie Design
|
|
9
|
+
|
|
10
|
+
Create authorized interior pages inside an existing Maggie project. This is
|
|
11
|
+
the page-design companion to `maggie-clone`: the homepage establishes the
|
|
12
|
+
global shell, and this skill designs only the content area between that shell.
|
|
13
|
+
|
|
14
|
+
Invoke it as:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
/maggie-design <target-url1> [<target-url2> ...]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Hard boundaries
|
|
21
|
+
|
|
22
|
+
- Require a completed `.maggie/bootstrap-state.json` and a completed homepage
|
|
23
|
+
foundation from `maggie-clone`. If either is missing, stop and request it.
|
|
24
|
+
- Reject the origin homepage as a target. Use `maggie-clone` for that job.
|
|
25
|
+
- Treat the homepage header and footer as immutable shared layout boundaries.
|
|
26
|
+
Reuse their component, tokens, navigation, logo, fonts, breakpoints,
|
|
27
|
+
responsive behavior, accessibility behavior, analytics hooks, and footer
|
|
28
|
+
links. Do not restyle, duplicate, or fork them for an interior page.
|
|
29
|
+
- Clone only the target's content region: hero, sections, cards, forms,
|
|
30
|
+
pricing/features, testimonials, FAQs, conversion blocks, and page-specific
|
|
31
|
+
interactions. Do not copy credentials, private data, tracking IDs,
|
|
32
|
+
authentication, checkout logic, or proprietary backend behavior.
|
|
33
|
+
- Keep each page in its own route and component namespace. Existing blog,
|
|
34
|
+
homepage, metadata, sitemap, robots, and Ops routes remain unchanged unless
|
|
35
|
+
the user explicitly approves a shared-shell change.
|
|
36
|
+
|
|
37
|
+
## Phase 0: Preflight and decision loop
|
|
38
|
+
|
|
39
|
+
Follow the shared [Maggie decision loop](../../references/decision-loop.md).
|
|
40
|
+
Inspect before editing:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python3 tools/clis/maggie.py status <project-root>
|
|
44
|
+
python3 tools/clis/maggie.py doctor <project-root> --require-bootstrap --strict
|
|
45
|
+
python3 tools/clis/maggie_clone.py plan <target-url1> [<target-url2> ...] --project <project-root>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Verify the homepage foundation exists and locate its actual shared shell. The
|
|
49
|
+
plan must state, for every target:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
source URL -> destination route
|
|
53
|
+
homepage shell component/layout -> reused unchanged
|
|
54
|
+
content component namespace -> new isolated namespace
|
|
55
|
+
research and screenshots -> page-scoped directories
|
|
56
|
+
assets -> page-scoped directory; shared assets remain shared
|
|
57
|
+
files preserved -> all existing public/blog/Ops routes
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Ask for confirmation of destination routes, page scope, asset reuse, and any
|
|
61
|
+
requested CTA/form behavior before mutation. Interior pages do not silently
|
|
62
|
+
become published posts or sitemap entries.
|
|
63
|
+
|
|
64
|
+
## Phase 1: Content-area reconnaissance
|
|
65
|
+
|
|
66
|
+
Use the available browser/Chrome/Playwright capability. Capture desktop
|
|
67
|
+
1440px, tablet 768px, and mobile 390px. Inspect the target with the global
|
|
68
|
+
header and footer excluded from the page scope after verifying their local
|
|
69
|
+
equivalent. Record:
|
|
70
|
+
|
|
71
|
+
- content topology and section order;
|
|
72
|
+
- exact visible copy, links, labels, images, forms, and public states;
|
|
73
|
+
- content-area typography, spacing, colors, borders, radii, shadows, and
|
|
74
|
+
responsive changes, comparing every value with the homepage tokens;
|
|
75
|
+
- hover, focus, open, loading, empty, error, scroll, tab, and reduced-motion
|
|
76
|
+
behavior;
|
|
77
|
+
- required assets and their reuse/licensing notes;
|
|
78
|
+
- page metadata and whether the page is a marketing route, conversion route,
|
|
79
|
+
or actual blog content.
|
|
80
|
+
|
|
81
|
+
Persist page-scoped evidence before building:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
docs/research/<site-key>/<page-key>/
|
|
85
|
+
OUTPUT_PLAN.md
|
|
86
|
+
PAGE_TOPOLOGY.md
|
|
87
|
+
BEHAVIORS.md
|
|
88
|
+
DESIGN_TOKENS.md
|
|
89
|
+
COMPONENT_INVENTORY.md
|
|
90
|
+
ASSET_MANIFEST.md
|
|
91
|
+
docs/design-references/<site-key>/<page-key>/
|
|
92
|
+
desktop.png tablet.png mobile.png
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Every new component spec must explicitly say: `header: reuse homepage`,
|
|
96
|
+
`footer: reuse homepage`, and list the content-only target file and states.
|
|
97
|
+
|
|
98
|
+
## Phase 2: Build the content area
|
|
99
|
+
|
|
100
|
+
Build the page using the host framework and the homepage's shared layout
|
|
101
|
+
component. Put page-specific output under an isolated namespace, for example:
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
src/components/sites/<site-key>/<page-key>/
|
|
105
|
+
src/pages/<approved-route>.*
|
|
106
|
+
public/sites/<site-key>/<page-key>/
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The page must:
|
|
110
|
+
|
|
111
|
+
- render inside the exact homepage shell and use its skip link, landmarks,
|
|
112
|
+
navigation, footer, fonts, and responsive breakpoints;
|
|
113
|
+
- use the existing metadata helper and canonical route;
|
|
114
|
+
- use normal anchors and real form validation;
|
|
115
|
+
- keep marketing pages out of the post sitemap and Article JSON-LD unless they
|
|
116
|
+
are explicitly mapped to the canonical post contract;
|
|
117
|
+
- preserve analytics consent and server-only API keys;
|
|
118
|
+
- implement observed interactions rather than guessed click-only substitutes.
|
|
119
|
+
|
|
120
|
+
If a page needs a new global navigation item, footer link, font, token, or
|
|
121
|
+
shared component change, stop, show the impact, and obtain explicit approval.
|
|
122
|
+
|
|
123
|
+
## Phase 3: Verify
|
|
124
|
+
|
|
125
|
+
For every target:
|
|
126
|
+
|
|
127
|
+
1. Run the host build, typecheck/lint, and `maggie doctor --strict` gates.
|
|
128
|
+
2. Verify the exact destination route, homepage route, blog routes, 404,
|
|
129
|
+
robots, sitemap exclusion/inclusion, canonical metadata, and JSON-LD.
|
|
130
|
+
3. Compare local and target screenshots at 1440px, 768px, and 390px. Compare
|
|
131
|
+
the header/footer separately to prove they are the same local components,
|
|
132
|
+
then compare only the content region for page fidelity.
|
|
133
|
+
4. Sweep keyboard focus, links, forms, hover, tabs/dialogs, scroll behavior,
|
|
134
|
+
mobile menu, and reduced-motion behavior.
|
|
135
|
+
5. Run `python3 tools/clis/site_audit.py <local-or-production-url> --json`.
|
|
136
|
+
|
|
137
|
+
Do not claim exact fidelity when a dynamic state, asset, or authenticated
|
|
138
|
+
target could not be inspected. Report measured differences and limitations.
|
|
139
|
+
|
|
140
|
+
## Completion report
|
|
141
|
+
|
|
142
|
+
Report target-to-route mappings, the reused homepage shell files, new
|
|
143
|
+
content-only components, research/screenshots, assets, preserved routes,
|
|
144
|
+
commands and test results, visual QA status, and any requested global changes
|
|
145
|
+
that remain pending approval. Deployment, publishing, API Pull, and rewrite
|
|
146
|
+
operations are separate actions.
|
|
@@ -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 bootstrap implement the complete private Maggie Ops dashboard?", ["enabled", "disabled"]),
|
|
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 "enabled"
|
|
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=True)
|
|
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"])
|
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.
|