@topy-ai/maggie 0.7.30 → 0.7.32

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.
Files changed (31) hide show
  1. package/README-zh-TW.md +2 -2
  2. package/README.md +99 -17
  3. package/bin/maggie.js +105 -17
  4. package/bundled-references/google-integrations-runbook.md +26 -0
  5. package/bundled-skills/maggie-blog/SKILL.md +18 -1
  6. package/bundled-skills/maggie-dash/SKILL.md +18 -3
  7. package/bundled-skills/maggie-deployment/SKILL.md +72 -0
  8. package/bundled-skills/maggie-deployment/references/vps.md +26 -0
  9. package/bundled-skills/maggie-design/SKILL.md +16 -0
  10. package/bundled-skills/maggie-feedback/SKILL.md +11 -2
  11. package/bundled-skills/maggie-ops/SKILL.md +11 -2
  12. package/bundled-skills/maggie-seo-geo/SKILL.md +22 -1
  13. package/bundled-tools/clis/maggie.py +12 -0
  14. package/bundled-tools/clis/maggie_analytics.py +34 -0
  15. package/bundled-tools/clis/maggie_blog.py +13 -0
  16. package/bundled-tools/clis/maggie_dash.py +17 -0
  17. package/bundled-tools/clis/maggie_deployment.py +91 -7
  18. package/bundled-tools/clis/maggie_feedback.py +18 -4
  19. package/bundled-tools/clis/maggie_icon_release_gate.py +79 -0
  20. package/bundled-tools/clis/maggie_migration.py +71 -0
  21. package/bundled-tools/clis/maggie_release_manifest.py +81 -0
  22. package/bundled-tools/clis/maggie_runtime_preflight.py +76 -0
  23. package/bundled-tools/clis/site_audit.py +78 -5
  24. package/bundled-tools/integrations/analytics.md +20 -0
  25. package/bundled-tools/runtime/maggie_blog_publish.py +38 -0
  26. package/bundled-tools/runtime/maggie_dash_panels.py +111 -0
  27. package/bundled-tools/runtime/maggie_quality.py +1 -1
  28. package/bundled-tools/runtime/maggie_sections.py +39 -1
  29. package/bundled-tools/runtime/site_baseline.py +23 -2
  30. package/package.json +1 -1
  31. package/references/google-integrations-runbook.md +26 -0
package/README-zh-TW.md CHANGED
@@ -8,7 +8,7 @@ Codex、Claude Code 與相容的 coding agents。
8
8
  ## 安裝
9
9
 
10
10
  ```bash
11
- npx @topy-ai/maggie@0.7.30 init --agent all
11
+ npx @topy-ai/maggie@0.7.32 init --agent all
12
12
  npx @topy-ai/maggie doctor --project .
13
13
  ```
14
14
 
@@ -25,7 +25,7 @@ maggie doctor --project . --require-bootstrap --strict
25
25
  deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
26
26
  publish 與 production deployment 需要明確確認。
27
27
 
28
- 目前 release 是 `0.7.30`,補上 feedback tracking issue closeout gate 與 batch 14/15 audit ledger。0.7.29 修正 npm package landing page 使用英文 README;0.7.28 加入 versioned Gemini model policy、明確 fallback 與 provenance、bundled-first CLI
28
+ 目前 release 是 `0.7.32`,加入 feedback 一基索引支援、MaggieDash panel 的 source/freshness/error evidence、Consent Mode 與實際 write/no-op reconciliation、SEO baseline recapture、sitemap origin rebasing、content-diff scope、migration ledger reconciliation、deployment credential preflight、release provenance/runtime preflight、icon release gate、opt-in blog auto-publish gate,以及 least-privilege VPS deployer。也修復 top-level `maggie feedback` dispatcher parity。0.7.31 加入 update 差異摘要、atomic installer copy、subset install manifest merge,以及 inventory drift 時 doctor non-zero。0.7.30 補上 feedback tracking issue closeout gate 與 batch 14/15 audit ledger。0.7.29 修正 npm package landing page 使用英文 README;0.7.28 加入 versioned Gemini model policy、明確 fallback 與 provenance、bundled-first CLI
29
29
  dispatch、`maggie --version`、feedback batch review 聚合與重複偵測、marketplace enrichment evidence、booking
30
30
  worker/resolver evidence,以及 read-only migration preflight。它也包含 host-owned mobile app surface contract、signed-in
31
31
  camera-state QA、直接 Astro route resolution、correlated feedback batch,以及
package/README.md CHANGED
@@ -60,19 +60,29 @@ Maggie keeps the existing project foundation and asks for decisions before
60
60
  shared routes, analytics, or publishing boundaries change. The current
61
61
  package ships 19 installable skills and a local-first MaggieDash foundation.
62
62
 
63
- The current release is `0.7.30`. It keeps the 0.7.29 feature set and adds the
64
- internal feedback issue closeout gate. The 0.7.29 release fixes npm
65
- metadata to render this English README as the package landing page. The 0.7.28
66
- release adds a versioned Gemini model policy with
67
- an explicit fallback and provenance, bundled-CLI version discovery, and a
68
- bundled-first dispatcher that avoids stale project wrappers. It also adds
69
- privacy-safe feedback batch review/duplicate detection, marketplace
70
- enrichment evidence, bounded booking worker/resolver reports, and read-only
71
- migration preflight contracts. The release retains the provider-neutral MaggieDash host
72
- adapter contract, idempotent PostgreSQL starter schema, type-only backend
73
- boundary, runtime endpoint conformance evidence, safe quota/error summaries,
74
- stale marketplace readiness revalidation, explicit VPS port/origin planning,
75
- first-release rollback evidence, and clean non-interactive bootstrap errors.
63
+ The current release is `0.7.32`. It adds explicit one-based feedback batch
64
+ index support, source/freshness/error evidence for MaggieDash measurement
65
+ panels, Consent Mode and actual-write reconciliation contracts, reviewed SEO
66
+ baseline recapture, requested-origin sitemap rebasing, explicit content-diff
67
+ scope, migration-ledger reconciliation, canonical deployment credential
68
+ preflight, unified release provenance, runtime preflight classification,
69
+ design icon-release gating, opt-in blog auto-publish gating, and a
70
+ least-privilege VPS deployer plan. It also restores top-level `maggie
71
+ feedback` dispatch parity. The 0.7.31 release added bounded update diff
72
+ evidence, atomic installer copies, subset-install manifest merge, and
73
+ non-zero doctor failures for inventory drift. The 0.7.30 release added the
74
+ internal feedback issue closeout gate. The 0.7.29 release fixed npm metadata
75
+ to render this English README as the package landing page. The 0.7.28
76
+ release added a versioned Gemini model policy with an explicit fallback and
77
+ provenance, bundled-CLI version discovery, a bundled-first dispatcher that
78
+ avoids stale project wrappers, privacy-safe feedback batch review/duplicate
79
+ detection, marketplace enrichment evidence, bounded booking worker/resolver
80
+ reports, and read-only migration preflight contracts. The release retains the
81
+ provider-neutral MaggieDash host adapter contract, idempotent PostgreSQL
82
+ starter schema, type-only backend boundary, runtime endpoint conformance
83
+ evidence, safe quota/error summaries, stale marketplace readiness
84
+ revalidation, explicit VPS port/origin planning, first-release rollback
85
+ evidence, and clean non-interactive bootstrap errors.
76
86
  It retains the host-owned mobile app surface
77
87
  contract (`maggie design app-init`/`app-validate`), direct Astro route
78
88
  resolution, correlated feedback batches, and runtime package-version capture.
@@ -129,6 +139,16 @@ evidence proves that a dashboard screen mounted and executed its checks, so a
129
139
  static component scan cannot pass by itself. MaggieDash hosts may also expose
130
140
  the provider-neutral activity-log and managed-navigation contracts.
131
141
 
142
+ Measurement panels must report their source, capture time/freshness, and
143
+ independent warning/error state. Validate the host-produced report before
144
+ using it in a release decision:
145
+
146
+ ```bash
147
+ maggie dash panels-validate \
148
+ --report .maggie/measurement-panels.json \
149
+ --output docs/measurement-panels-validation.json
150
+ ```
151
+
132
152
  The current package also includes reusable safeguards from the latest feedback
133
153
  review: `maggie dash api-contract` checks declared request and 2xx response
134
154
  shapes; `maggie blog check-gate`/`approve` enforces review before publish;
@@ -249,10 +269,17 @@ maggie ops favicon-check ... # served favicon behaviour check
249
269
  maggie deployment | migration | release | analytics | schedule
250
270
  maggie deployment readiness --project PATH
251
271
  maggie deployment parity --project PATH --evidence FILE --output FILE
272
+ maggie deployment runtime-preflight --evidence FILE --output FILE
273
+ maggie deployment release-manifest --evidence FILE --output FILE
274
+ maggie deployment credential-preflight [--expected-host HOST]
252
275
  maggie migration identity --identity-file FILE [--expected-file FILE]
253
276
  maggie migration preflight --evidence FILE [--output FILE]
277
+ maggie migration reconcile --evidence FILE --migration-dir DIR [--output FILE]
254
278
  maggie deployment canary --asset URL=SHA256 --render-report report.json
255
279
  maggie design icon-inventory --source-dir src --runtime assets/icons.css
280
+ maggie design icon-release-gate --icon-report FILE --browser-report FILE --output FILE
281
+ maggie dash panels-validate --report FILE [--output FILE]
282
+ maggie blog auto-publish-gate --evidence FILE --output FILE
256
283
  maggie api lifecycle | site-audit | ops audit
257
284
  maggie marketplace enrichment-report --project PATH --input FILE [--output FILE]
258
285
  maggie service worker-health --project PATH --evidence FILE [--output FILE]
@@ -318,6 +345,16 @@ The adapter is an argv array, receives a versioned request on stdin, and must
318
345
  print only the three review-policy fields. Provider stderr is suppressed and a
319
346
  missing local store without an adapter fails closed.
320
347
 
348
+ Automated blog publishing stays human-reviewed by default. If a host opts in,
349
+ the gate requires explicit review bypass evidence plus locale, image-alt, SEO,
350
+ sitemap, indexing-notification, and audit checks:
351
+
352
+ ```bash
353
+ maggie blog auto-publish-gate \
354
+ --evidence .maggie/blog/auto-publish-evidence.json \
355
+ --output docs/blog-auto-publish-gate.json
356
+ ```
357
+
321
358
  ### Design and deployment release gates
322
359
 
323
360
  Check source-to-runtime icon coverage before shipping a design:
@@ -332,6 +369,17 @@ and is only static name coverage. Pair changed icon controls with a
332
369
  browser-runtime `icon-rendered` assertion so the host adapter proves that the
333
370
  glyph is visible, has positive dimensions, and has an accessible label.
334
371
 
372
+ For a design release, compose the static inventory with browser-painted glyph
373
+ evidence. Every tested icon must be visible, have positive dimensions, expose
374
+ an accessible name, and load successfully:
375
+
376
+ ```bash
377
+ maggie design icon-release-gate \
378
+ --icon-report docs/icon-inventory.json \
379
+ --browser-report .maggie/icon-browser-evidence.json \
380
+ --output docs/icon-release-gate.json
381
+ ```
382
+
335
383
  After deployment, compare an immutable asset fingerprint and validate the
336
384
  browser adapter's rendered report:
337
385
 
@@ -360,6 +408,37 @@ privileges, route API status, stale resident processes, source-job IDs, and
360
408
  browser/driver compatibility. Add the resulting report to readiness with
361
409
  `--runtime-parity`; a failed report blocks the gate.
362
410
 
411
+ Use the runtime preflight to classify dependency, browser, worker, database,
412
+ and route failures independently. A non-pass check must include remediation:
413
+
414
+ ```bash
415
+ maggie deployment runtime-preflight \
416
+ --evidence .maggie/verification/runtime-preflight-input.json \
417
+ --output .maggie/verification/runtime-preflight.json
418
+ ```
419
+
420
+ Before activation, reconcile the host's `maggie_schema_migrations` ledger with
421
+ local SQL files. This is read-only and produces an ordered missing-migration
422
+ plan. Deployment credential checks similarly verify only the presence of the
423
+ canonical names, never their values:
424
+
425
+ ```bash
426
+ maggie migration reconcile \
427
+ --evidence .maggie/migration/ledger.json \
428
+ --migration-dir migrations \
429
+ --output docs/migration-reconcile.json
430
+ maggie deployment credential-preflight --expected-host example.com
431
+ ```
432
+
433
+ Finally, combine route, migration, runtime, asset/icon, edge-cache,
434
+ browser/render, and rollback evidence into one release provenance manifest:
435
+
436
+ ```bash
437
+ maggie deployment release-manifest \
438
+ --evidence .maggie/verification/release-evidence.json \
439
+ --output docs/release-manifest.json
440
+ ```
441
+
363
442
  Unit regression does not establish runtime release readiness. Produce unit
364
443
  evidence and then validate all required release evidence slots; add
365
444
  `--runtime-parity` when the deployment uses the worker/browser surfaces above:
@@ -419,8 +498,8 @@ artifact schemas.
419
498
  Recommended upgrade sequence for the current release:
420
499
 
421
500
  ```bash
422
- npx @topy-ai/maggie@0.7.30 update --project . --force
423
- npx @topy-ai/maggie@0.7.30 cleanup --project .
501
+ npx @topy-ai/maggie@0.7.32 update --project . --force
502
+ npx @topy-ai/maggie@0.7.32 cleanup --project .
424
503
  ```
425
504
 
426
505
  Maintainers should pass npm credentials through the repository helper, never
@@ -586,10 +665,13 @@ For related observations, create 1–50 local drafts with shared correlation
586
665
  metadata and review them before submitting:
587
666
 
588
667
  ```bash
589
- maggie feedback batch --project . --batch-file ./feedback-batch.json
668
+ maggie feedback batch --project . --batch-file ./feedback-batch.json \
669
+ --batch-base 0
590
670
  ```
591
671
 
592
- The batch command never submits automatically or writes active memory.
672
+ The batch command never submits automatically or writes active memory. External
673
+ one-based runners can pass `--batch-base 1`; Maggie stores canonical indexes
674
+ zero-based and validates the final item boundary explicitly.
593
675
 
594
676
  Aggregate a correlated batch before reviewing individual drafts:
595
677
 
package/bin/maggie.js CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { createHash } from "node:crypto";
5
5
  import { spawnSync } from "node:child_process";
6
- import { cp } from "node:fs/promises";
7
- import { dirname, join, resolve } from "node:path";
6
+ import { basename, dirname, join, resolve } from "node:path";
8
7
  import { fileURLToPath } from "node:url";
9
8
 
10
9
  const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -92,6 +91,7 @@ Usage:
92
91
  maggie design app-init --project PATH --route /app/valuation --auth-mode email-password --confirm
93
92
  maggie design app-validate --project PATH --plan PATH --runtime-evidence FILE --rendered-dir PATH --confirm
94
93
  maggie design icon-inventory --project PATH --source-dir src --runtime assets/icons.css --output docs/icon-inventory.json
94
+ maggie design icon-release-gate --icon-report FILE --browser-report FILE --output FILE
95
95
  maggie design author --project PATH --route /about --purpose TEXT --audience TEXT --confirm
96
96
  maggie design in-place --project PATH --route /pricing [--content-source PATH]
97
97
  maggie design section --project PATH --route /pricing --section-id ID
@@ -118,6 +118,8 @@ Usage:
118
118
  maggie deployment readiness --project PATH
119
119
  maggie deployment parity --project PATH --evidence FILE --output FILE
120
120
  maggie deployment canary --project PATH --asset URL=SHA256 --render-report report.json --output docs/deployment-canary.json
121
+ maggie deployment release-manifest --evidence FILE --output FILE
122
+ maggie deployment runtime-preflight --evidence FILE --output FILE
121
123
  maggie migration preflight --evidence FILE --output FILE
122
124
  maggie migration --project PATH --environment staging
123
125
  maggie migration identity --identity-file FILE [--expected-file FILE]
@@ -169,6 +171,33 @@ function selectedSkills(args) {
169
171
  return unique;
170
172
  }
171
173
 
174
+ function diskSkillNames(root) {
175
+ const installed = new Set();
176
+ for (const agentRoot of [join(root, ".agents"), join(root, ".claude")]) {
177
+ const skillsRoot = join(agentRoot, "skills");
178
+ if (!existsSync(skillsRoot)) continue;
179
+ for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
180
+ if (entry.isDirectory() && existsSync(join(skillsRoot, entry.name, "SKILL.md"))) installed.add(entry.name);
181
+ }
182
+ }
183
+ return installed;
184
+ }
185
+
186
+ function mergedSkillNames(root, selected) {
187
+ const manifestPath = join(root, STATE_DIR, "install.json");
188
+ let manifestSkills = [];
189
+ try {
190
+ if (existsSync(manifestPath)) {
191
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
192
+ manifestSkills = Array.isArray(manifest.skills) ? manifest.skills : [];
193
+ }
194
+ } catch {
195
+ manifestSkills = [];
196
+ }
197
+ return [...new Set([...manifestSkills, ...diskSkillNames(root), ...selected])]
198
+ .filter((skill) => SKILL_NAMES.includes(skill)).sort();
199
+ }
200
+
172
201
  function agentRoots(args, root) {
173
202
  const requested = option(args, "--agent", "auto");
174
203
  if (!["auto", "codex", "claude", "all"].includes(requested)) throw new Error(`unknown agent: ${requested}`);
@@ -184,7 +213,18 @@ function agentRoots(args, root) {
184
213
  function copyIfMissing(source, target, force = false) {
185
214
  if (existsSync(target) && !force) return "skipped";
186
215
  mkdirSync(dirname(target), { recursive: true });
187
- cp(source, target, { recursive: true, force });
216
+ if (existsSync(target)) {
217
+ cpSync(source, target, { recursive: true, force });
218
+ return "installed";
219
+ }
220
+ const staging = join(dirname(target), `.${basename(target)}.maggie-copy-${process.pid}-${Date.now()}`);
221
+ try {
222
+ cpSync(source, staging, { recursive: true, force: true });
223
+ renameSync(staging, target);
224
+ } catch (error) {
225
+ rmSync(staging, { recursive: true, force: true });
226
+ throw error;
227
+ }
188
228
  return "installed";
189
229
  }
190
230
 
@@ -192,6 +232,56 @@ function digest(path) {
192
232
  return createHash("sha256").update(readFileSync(path)).digest("hex");
193
233
  }
194
234
 
235
+ function diffSummary(source, target) {
236
+ const packageBuffer = readFileSync(source);
237
+ const localBuffer = readFileSync(target);
238
+ if (packageBuffer.includes(0) || localBuffer.includes(0)) return "binary content differs";
239
+ const packageLines = packageBuffer.toString("utf8").split(/\r?\n/);
240
+ const localLines = localBuffer.toString("utf8").split(/\r?\n/);
241
+ const limit = 2000;
242
+ if (packageLines.length > limit || localLines.length > limit) {
243
+ return `text differs (${localLines.length} local lines vs ${packageLines.length} package lines; diff bounded at ${limit} lines)`;
244
+ }
245
+ let packageIndex = 0;
246
+ let localIndex = 0;
247
+ let added = 0;
248
+ let removed = 0;
249
+ let blocks = 0;
250
+ while (packageIndex < packageLines.length || localIndex < localLines.length) {
251
+ if (packageIndex >= packageLines.length) {
252
+ added += localLines.length - localIndex;
253
+ blocks++;
254
+ break;
255
+ }
256
+ if (localIndex >= localLines.length) {
257
+ removed += packageLines.length - packageIndex;
258
+ blocks++;
259
+ break;
260
+ }
261
+ if (packageLines[packageIndex] === localLines[localIndex]) {
262
+ packageIndex++;
263
+ localIndex++;
264
+ continue;
265
+ }
266
+ const packageNext = packageLines.slice(packageIndex, packageIndex + 40).findIndex((line, offset) => line === localLines[localIndex]);
267
+ const localNext = localLines.slice(localIndex, localIndex + 40).findIndex((line, offset) => line === packageLines[packageIndex]);
268
+ if (packageNext >= 0 && (localNext < 0 || packageNext <= localNext)) {
269
+ removed += packageNext || 1;
270
+ packageIndex += packageNext || 1;
271
+ } else if (localNext >= 0) {
272
+ added += localNext || 1;
273
+ localIndex += localNext || 1;
274
+ } else {
275
+ removed++;
276
+ added++;
277
+ packageIndex++;
278
+ localIndex++;
279
+ }
280
+ blocks++;
281
+ }
282
+ return `${added} local additions, ${removed} package lines removed across ${blocks} changed blocks (${localLines.length} local lines vs ${packageLines.length} package lines)`;
283
+ }
284
+
195
285
  function syncTree(source, target, force) {
196
286
  let changed = 0;
197
287
  for (const entry of readdirSync(source, { withFileTypes: true })) {
@@ -211,7 +301,7 @@ function syncTree(source, target, force) {
211
301
  console.log(`updated ${targetPath}`);
212
302
  changed++;
213
303
  } else {
214
- console.log(`preserved ${targetPath} (local changes; use --force to replace)`);
304
+ console.log(`preserved ${targetPath} (local changes; ${diffSummary(sourcePath, targetPath)}; use --force to replace)`);
215
305
  }
216
306
  }
217
307
  return changed;
@@ -248,7 +338,7 @@ function install(args) {
248
338
  if (existsSync(join(TEMPLATES_ROOT, "maggiedash"))) copyIfMissing(join(TEMPLATES_ROOT, "maggiedash"), join(root, "templates", "maggiedash"));
249
339
  const stateDir = join(root, ".maggie");
250
340
  mkdirSync(stateDir, { recursive: true });
251
- writeFileSync(join(stateDir, "install.json"), JSON.stringify({ version: PACKAGE_VERSION, agents: roots.map((item) => item.slice(root.length + 1)), skills, installed_at: new Date().toISOString() }, null, 2) + "\n");
341
+ writeFileSync(join(stateDir, "install.json"), JSON.stringify({ version: PACKAGE_VERSION, agents: roots.map((item) => item.slice(root.length + 1)), skills: mergedSkillNames(root, skills), installed_at: new Date().toISOString() }, null, 2) + "\n");
252
342
  console.log(`Maggie installed in ${root}`);
253
343
  console.log("Run `maggie doctor --project .` before using mutating workflows.");
254
344
  }
@@ -285,7 +375,7 @@ function update(args) {
285
375
  if (existsSync(join(TEMPLATES_ROOT, "maggiedash")) && existsSync(join(root, "templates", "maggiedash"))) updated += syncTree(join(TEMPLATES_ROOT, "maggiedash"), join(root, "templates", "maggiedash"), force);
286
376
  const stateDir = join(root, STATE_DIR);
287
377
  mkdirSync(stateDir, { recursive: true });
288
- writeFileSync(join(stateDir, "install.json"), JSON.stringify({ version: PACKAGE_VERSION, agents: roots.map((item) => item.slice(root.length + 1)), skills, updated_at: new Date().toISOString() }, null, 2) + "\n");
378
+ writeFileSync(join(stateDir, "install.json"), JSON.stringify({ version: PACKAGE_VERSION, agents: roots.map((item) => item.slice(root.length + 1)), skills: mergedSkillNames(root, skills), updated_at: new Date().toISOString() }, null, 2) + "\n");
289
379
  console.log(`Maggie update complete: ${updated} files changed`);
290
380
  if (!force) console.log("Local files with changes were preserved. Review the output and rerun with --force only when replacement is intended.");
291
381
  }
@@ -359,19 +449,14 @@ function doctor(args) {
359
449
  let manifest = null;
360
450
  try { if (existsSync(manifestPath)) manifest = JSON.parse(readFileSync(manifestPath, "utf8")); } catch { manifest = null; }
361
451
  const expected = Array.isArray(manifest?.skills) ? [...new Set(manifest.skills)] : null;
362
- const installed = [];
363
- const installedNames = new Set();
364
- for (const agentRoot of [join(root, ".agents"), join(root, ".claude")]) {
365
- for (const skill of SKILL_NAMES) if (existsSync(join(agentRoot, "skills", skill, "SKILL.md"))) {
366
- installed.push(`${agentRoot}/${skill}`);
367
- installedNames.add(skill);
368
- }
369
- }
452
+ const installedNames = diskSkillNames(root);
370
453
  const missing = expected ? expected.filter((skill) => !installedNames.has(skill)) : [];
371
- const inventoryState = expected && missing.length ? "INCOMPLETE" : "PASS";
372
- console.log(`${inventoryState} skill-inventory manifest=${expected?.length ?? "unknown"} on-disk=${installedNames.size}${missing.length ? ` missing=${missing.join(",")}` : ""}`);
454
+ const extra = expected ? [...installedNames].filter((skill) => !expected.includes(skill)).sort() : [];
455
+ const inventoryState = expected && (missing.length || extra.length) ? "INCOMPLETE" : "PASS";
456
+ console.log(`${inventoryState} skill-inventory manifest=${expected?.length ?? "unknown"} on-disk=${installedNames.size}${missing.length ? ` missing=${missing.join(",")}` : ""}${extra.length ? ` extra=${extra.join(",")}` : ""}`);
373
457
  console.log(`INFO installed-skills=${installedNames.size}`);
374
458
  console.log("INFO doctor is diagnostic; bootstrap completion remains a project decision gate.");
459
+ if (inventoryState === "INCOMPLETE") process.exitCode = 1;
375
460
  }
376
461
 
377
462
  function remove(args) {
@@ -422,6 +507,7 @@ try {
422
507
  else if (command === "clone") workflowCli("maggie_clone.py", args);
423
508
  else if (command === "clone-to-template") workflowCli("maggie_clone_to_template.py", args);
424
509
  else if (command === "design" && args[0] === "icon-inventory") workflowCli("maggie_icon_inventory.py", args.slice(1));
510
+ else if (command === "design" && args[0] === "icon-release-gate") workflowCli("maggie_icon_release_gate.py", args.slice(1));
425
511
  else if (command === "design") workflowCli("maggie_design.py", args);
426
512
  else if (command === "auth") workflowCli("maggie_auth.py", args);
427
513
  else if (command === "blog") workflowCli("maggie_blog.py", args);
@@ -432,6 +518,8 @@ try {
432
518
  else if (command === "deployment" && args[0] === "canary") workflowCli("maggie_deployment_canary.py", args.slice(1));
433
519
  else if (command === "deployment" && args[0] === "readiness") workflowCli("maggie_deployment_readiness.py", args.slice(1));
434
520
  else if (command === "deployment" && args[0] === "parity") workflowCli("maggie_deployment_parity.py", args.slice(1));
521
+ else if (command === "deployment" && args[0] === "release-manifest") workflowCli("maggie_release_manifest.py", args.slice(1));
522
+ else if (command === "deployment" && args[0] === "runtime-preflight") workflowCli("maggie_runtime_preflight.py", args.slice(1));
435
523
  else if (command === "deployment") workflowCli("maggie_deployment.py", args);
436
524
  else if (command === "migration") workflowCli("maggie_migration.py", args);
437
525
  else if (command === "schedule") workflowCli("maggie_schedule.py", args);
@@ -52,6 +52,32 @@ The validator accepts the complete provider allowlist, but a capability is
52
52
  verified only when its corresponding required scope is present. Scope presence
53
53
  alone never proves product access.
54
54
 
55
+ ### GTM operation scope matrix
56
+
57
+ Request the union of the rows needed by one operation; do not use a broad
58
+ scope merely because a later operation might need it:
59
+
60
+ | GTM operation | Minimum OAuth scope |
61
+ |---|---|
62
+ | List/read account, container, tag, trigger, or variable | `https://www.googleapis.com/auth/tagmanager.readonly` |
63
+ | Edit container configuration | `https://www.googleapis.com/auth/tagmanager.edit.containers` |
64
+ | Create or update a container version | `https://www.googleapis.com/auth/tagmanager.edit.containerversions` |
65
+ | Publish a container version | `https://www.googleapis.com/auth/tagmanager.publish` |
66
+
67
+ An operation that edits a container and creates a version must request both
68
+ edit scopes; publishing additionally requires the publish scope. Record the
69
+ operation name and exact scope union in the capability evidence so a successful
70
+ read cannot be mistaken for edit or publish access.
71
+
72
+ ### Missing fields are unknown
73
+
74
+ Google APIs can omit fields, especially when a proto3 field is absent from the
75
+ response or was not selected by the request. An omitted boolean is not
76
+ `false`, and an absent schema field is not evidence that the capability is
77
+ disabled. Reports must use `unknown`/`not_observed` in the provider adapter's
78
+ evidence and obtain a corroborating read or time-series observation before
79
+ making a negative claim. Never silently coerce a missing field to `false`.
80
+
55
81
  ## Provider setup and read-only preflight
56
82
 
57
83
  1. Select one provider and one resource. Enable only that provider API in the
@@ -54,7 +54,24 @@ explicit `approve` transition before `publish`. Stable `contentId` is the
54
54
  ingest identity and a published slug must not change during a rewrite.
55
55
  Search/sort views are not indexable; drafts never appear in public routes, RSS,
56
56
  or sitemap output. Provider keys remain server-side. Public publication and
57
- migrations always require explicit confirmation.
57
+ migrations always require explicit confirmation.
58
+
59
+ Automatic publication is an opt-in host policy, not the default. Keep human
60
+ review enabled unless the owner explicitly chooses otherwise. If a host has
61
+ that approval, validate its evidence before its scheduler calls the host's
62
+ publish path:
63
+
64
+ ```bash
65
+ maggie blog auto-publish-gate \
66
+ --evidence .maggie/blog-auto-publish-evidence.json \
67
+ --output .maggie/blog-auto-publish-gate.json
68
+ ```
69
+
70
+ The `maggie-blog-auto-publish.v1` evidence must explicitly set `optIn: true`
71
+ and `reviewDisabledExplicitly: true`, then pass locale variants, feature image
72
+ and alt text, SEO fields, sitemap generation, indexing notification, and audit
73
+ checks. The gate never publishes; missing evidence keeps content in human
74
+ review and remains non-indexable.
58
75
 
59
76
  The adapter runs as an argv array without a shell, receives a versioned request
60
77
  on stdin, and must print only an object containing
@@ -80,6 +80,14 @@ This is a runtime contract gate, not a source-only check; it fails when a
80
80
  declared endpoint is missing, uses the wrong route/method, returns a non-2xx
81
81
  status or omits required fields.
82
82
 
83
+ For analytics panels and Consent Mode, keep the same evidence boundary: the
84
+ default consent state must be written before the container loads, first-party
85
+ storage behavior must be explicitly `stop` or `degrade`, and intentional form
86
+ events must be declared separately as `record` or `omit`. A browser report must
87
+ prove each decision; an absent field is unknown and must not be interpreted as
88
+ false. Run the analytics release gate when the dashboard or host measurement
89
+ contract changes.
90
+
83
91
  MaggieDash 0.2.4 adds two reusable operator workspaces. Activity is a read-only
84
92
  view backed by `/api/maggie/activity.json`; it supports actor/action filters,
85
93
  refresh, and a safe unavailable state. Navigation is an ordered,
@@ -255,6 +263,10 @@ maggie dash schema-audit --inventory .maggie/schema-inventory.json \
255
263
  # Prove a browser check mounted the screen and executed its data calls:
256
264
  maggie dash ui runtime-validate --evidence .maggie/dashboard-runtime.json
257
265
 
266
+ # Validate independent dashboard measurement panels and freshness evidence:
267
+ maggie dash panels-validate --report .maggie/measurement-panels.json \
268
+ --output .maggie/measurement-panels-result.json
269
+
258
270
  # Classify docs, show inbound references and completed progress trackers:
259
271
  maggie docs audit --project . --docs-dir docs \
260
272
  --output docs/documentation-audit.json
@@ -339,9 +351,12 @@ category, collection, or service reference; an unresolved binding is a publish
339
351
  failure, not an empty state.
340
352
 
341
353
  Repair scripts must select all candidates, compare every target field, write
342
- only changed rows, and report both changed and unchanged rows. Validate that
343
- contract with `idempotency-validate`; a query that selects only rows that look
344
- unconverted cannot repair its own bad output.
354
+ only changed rows, and report both changed and unchanged rows. Each step must
355
+ also return a boolean `wrote`; the summary's `writes` count is the number of
356
+ true outcomes, not the number of executed steps. The shared section reconcile
357
+ command exposes `writeCount`, `unchangedCount`, and `writeOutcome` for this
358
+ purpose. Validate the repair contract with `idempotency-validate`; a query
359
+ that selects only rows that look unconverted cannot repair its own bad output.
345
360
 
346
361
  After any script or direct adapter write to translation data, invalidate the
347
362
  running process before verification. Record the restart and run the rendering
@@ -88,6 +88,36 @@ not fabricate browser, rendered, or deployment evidence and does not deploy
88
88
  from this command. See
89
89
  [`readiness-v1.schema.json`](../../bundled-contracts/maggie-deployment/readiness-v1.schema.json).
90
90
 
91
+ Classify host/runtime failures before a release can move:
92
+
93
+ ```bash
94
+ maggie deployment runtime-preflight \
95
+ --evidence .maggie/verification/runtime-preflight-input.json \
96
+ --output .maggie/verification/runtime-preflight.json
97
+ ```
98
+
99
+ The `maggie-runtime-preflight.v1` input has independent checks for
100
+ dependencies, browser/driver compatibility, worker configuration, database
101
+ privileges, and routes/API. Each non-pass status must include an actionable
102
+ remediation. Missing checks are `unknown` and fail closed; the command never
103
+ connects to a host or mutates runtime state.
104
+
105
+ Before production approval, compose one cross-workflow manifest from the
106
+ sanitized outputs of the route, migration, runtime, asset/icon, edge-cache,
107
+ browser/render, and rollback checks:
108
+
109
+ ```bash
110
+ maggie deployment release-manifest \
111
+ --evidence .maggie/release-evidence.json \
112
+ --output .maggie/release-manifest.json
113
+ ```
114
+
115
+ The `maggie-release-evidence.v1` input links the release commit and Maggie
116
+ version to every required evidence slot and release fingerprint. The validator
117
+ fails if any slot is missing or not passed, keeps only normalized metadata, and
118
+ records `mutation: "not executed"`; it does not deploy or claim that a check
119
+ was run.
120
+
91
121
  ## Automatic memory hook
92
122
 
93
123
  Follow [Maggie Memory Hook](../../references/memory-hook.md) at invocation and completion.
@@ -115,6 +145,22 @@ This command never deploys. Production execution still requires the shared
115
145
  decision loop, explicit confirmation, migration/rollback details, and the
116
146
  provider-specific deploy command.
117
147
 
148
+ For a VPS adapter, check the project `.env` by variable name before reporting
149
+ credentials as unavailable:
150
+
151
+ ```bash
152
+ maggie deployment credential-preflight \
153
+ --env-file .env --expected-host example-host \
154
+ --output .maggie/deployment/credentials.json
155
+ ```
156
+
157
+ The preflight checks `DEPLOY_SERVER_IP`, `DEPLOY_SERVER_SSH_USER`,
158
+ `DEPLOY_SERVER_SSH_PASSWORD`, and `DEPLOY_SERVER_SSH_SUDOER` by presence only.
159
+ It never prints or stores values. `--expected-host` is optional, but should be
160
+ used for production to prevent deploying to an unintended server. SSH-key
161
+ adapters may use their own named secret contract; map it explicitly in their
162
+ local plan.
163
+
118
164
  When deployment verification depends on Google properties or accounts, use
119
165
  the shared [Google integrations runbook](../../references/google-integrations-runbook.md).
120
166
  Keep provider auth and capability evidence separate from deployment health;
@@ -146,6 +192,15 @@ carries `.agents`/`.claude` state, restarts before verification, and prunes to
146
192
  the current release plus one rollback candidate. It never
147
193
  contains credentials and never SSHs, changes DNS, restarts systemd or deploys.
148
194
 
195
+ VPS plans use a dedicated deployer account by default:
196
+ `--deployer-user maggie-deploy`. The generated sudoers contract permits only
197
+ the exact `/bin/systemctl restart <service>` and
198
+ `/bin/systemctl is-active <service>` commands for the named service. Release
199
+ files and the application remain separated (`maggie-deploy` versus
200
+ `www-data`); no wildcard, shell, `sudo su`, or broad root command is generated.
201
+ Install and review that narrow sudoers policy on the host before executing a
202
+ runner.
203
+
149
204
  VPS plans include a bounded retention policy: keep two immutable releases,
150
205
  preserve the `current` target and rollback target, and review prune candidates
151
206
  before any operator executes cleanup. Generate a read-only candidate report:
@@ -186,6 +241,23 @@ The gate fails on a missing or mismatched target identity and never executes a
186
241
  migration. A successful connection alone is not proof that the intended
187
242
  database was selected.
188
243
 
244
+ Before activation, reconcile the host's migration ledger rather than relying
245
+ on a remembered local migration list:
246
+
247
+ ```bash
248
+ maggie migration reconcile \
249
+ --evidence .maggie/migration/server-ledger.json \
250
+ --migration-dir migrations \
251
+ --output .maggie/migration/ledger-reconcile.json
252
+ ```
253
+
254
+ The host adapter must query `maggie_schema_migrations` and emit only the
255
+ version identifiers and environment. The read-only command orders local
256
+ numbered SQL files, reports `missing`, and remains `mutation: "not executed"`.
257
+ Apply every missing file through the host migration runner, re-read the ledger,
258
+ and only then continue to the release switch; a missing version is a failed
259
+ deployment preflight, not evidence that activation can proceed.
260
+
189
261
  If a release depends on existing rows or seeded data, set
190
262
  `dataDependencies: true` in `.maggie/migration-manifest.json` and provide
191
263
  `.maggie/deployment/data-release.json` before deployment. The checkpoint must
@@ -39,6 +39,19 @@ owner before any remote mutation.
39
39
 
40
40
  ### SSH and secret-source contract
41
41
 
42
+ First run the shared credential presence check against the project `.env`:
43
+
44
+ ```bash
45
+ maggie deployment credential-preflight --env-file .env \
46
+ --expected-host example-host
47
+ ```
48
+
49
+ The canonical password-based names are `DEPLOY_SERVER_IP`,
50
+ `DEPLOY_SERVER_SSH_USER`, `DEPLOY_SERVER_SSH_PASSWORD`, and
51
+ `DEPLOY_SERVER_SSH_SUDOER`. The command reports only presence and an optional
52
+ host match; it never prints a value. A provider may instead use an SSH key, but
53
+ must state that alternate name and source in its plan.
54
+
42
55
  Use these canonical project variables when a VPS adapter is configured:
43
56
 
44
57
  | Variable | Meaning | Precedence |
@@ -86,6 +99,19 @@ The generated Nginx/Astro boundary keeps `checkOrigin` enabled and requires
86
99
  canonical HTTPS `allowedDomains` plus trusted `X-Forwarded-Host`/
87
100
  `X-Forwarded-Proto` handling when TLS terminates at the proxy.
88
101
 
102
+ Use a dedicated least-privilege deployer when generating the plan:
103
+
104
+ ```bash
105
+ python3 tools/clis/maggie_deployment.py . --vps-plan \
106
+ --domain example.co.uk --service example --node-port 4321 \
107
+ --deployer-user maggie-deploy
108
+ ```
109
+
110
+ The plan separates the SSH/deploy account from the `www-data` application
111
+ account and allowlists only the exact service restart and status commands.
112
+ The runner uses non-interactive `sudo -n`; it never requires broad sudo or a
113
+ shell escalation. Review/install the host sudoers entry separately.
114
+
89
115
  Review the generated systemd and Nginx files, then obtain explicit approval
90
116
  before installing them on a host.
91
117