@topy-ai/maggie 0.7.35 → 0.7.37

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 (30) hide show
  1. package/README-zh-TW.md +45 -2
  2. package/README.md +108 -4
  3. package/bin/maggie.js +76 -19
  4. package/bundled-contracts/maggie-seo/model-policy-v2.schema.json +29 -0
  5. package/bundled-contracts/maggie-service-booking/notification-lifecycle-v1.schema.json +29 -0
  6. package/bundled-contracts/maggiedash/README.md +3 -0
  7. package/bundled-contracts/maggiedash/booking-access-v1.json +29 -0
  8. package/bundled-contracts/maggiedash/booking-customer-surface-v1.json +46 -0
  9. package/bundled-contracts/maggiedash/booking-email-templates-v1.json +38 -0
  10. package/bundled-contracts/maggiedash/booking-host-adapter-v1.json +76 -0
  11. package/bundled-contracts/maggiedash/booking-ops-evidence-v1.json +19 -0
  12. package/bundled-contracts/maggiedash/execution-board.json +1886 -0
  13. package/bundled-contracts/maggiedash/stripe-booking-capabilities-v1.json +68 -0
  14. package/bundled-skills/README.md +1 -0
  15. package/bundled-skills/catalog.json +4 -0
  16. package/bundled-skills/maggie-blog-bootstrap/SKILL.md +6 -0
  17. package/bundled-skills/maggie-booking/SKILL.md +462 -0
  18. package/bundled-skills/maggie-design/SKILL.md +15 -0
  19. package/bundled-skills/maggie-seo-geo/SKILL.md +24 -2
  20. package/bundled-skills/maggie-service-booking/SKILL.md +19 -0
  21. package/bundled-templates/astro-blog/starter/README.md +4 -0
  22. package/bundled-templates/astro-blog/starter/src/styles/global.css +6 -0
  23. package/bundled-tools/clis/maggie.py +36 -2
  24. package/bundled-tools/clis/maggie_booking.py +1080 -0
  25. package/bundled-tools/clis/maggie_dash.py +181 -5
  26. package/bundled-tools/clis/maggie_release.py +24 -0
  27. package/bundled-tools/clis/maggie_service_booking.py +38 -2
  28. package/bundled-tools/clis/site_audit.py +114 -15
  29. package/bundled-tools/runtime/site_baseline.py +11 -1
  30. package/package.json +1 -1
@@ -10,7 +10,9 @@ metadata:
10
10
  ## Versioned Gemini policy
11
11
 
12
12
  AI-assisted SEO, localization, and content generation must resolve models
13
- through one explicit policy: `maggie-gemini-model-policy.v1`. Pin the primary
13
+ through one explicit policy: `maggie-gemini-model-policy.v1` for legacy local
14
+ routes or `contracts/maggie-seo/model-policy-v2.schema.json` for provider-routed
15
+ work. Pin the primary
14
16
  model, declare a separate fallback, and record the policy version, exact
15
17
  prompt/schema versions, locale, and source revision with generated evidence.
16
18
  Only model availability failures (retired model, quota, timeout, or provider
@@ -18,6 +20,13 @@ Only model availability failures (retired model, quota, timeout, or provider
18
20
  invalid output remain errors. Do not scatter retired model defaults across
19
21
  routes.
20
22
 
23
+ Provider-routed work must use policy v2 and declare the provider, endpoint
24
+ environment variable, API-key environment variable, billing mode (`byok` or
25
+ `managed`), and a finite `maxTokens` limit. A route is not considered BYOK
26
+ merely because a key exists in the process; the selected endpoint and billing
27
+ path must be recorded in the policy evidence. Never print key values or send a
28
+ provider secret in a report.
29
+
21
30
  ## Static audit and sitemap evidence
22
31
 
23
32
  Run `maggie site-audit https://example.com --crawl --json` for sitemap-listed
@@ -129,6 +138,13 @@ maggie site-audit https://example.com --crawl --json \
129
138
  --reason 'https://example.com/pricing=approved pricing refresh'
130
139
  ```
131
140
 
141
+ When the same approved reason applies to every drift, use `--reason-all` or
142
+ `--reason '*=approved site-wide stylesheet rebuild'`. A URL-specific reason can
143
+ still override the site-wide reason. If a page changes between its first and
144
+ second fetch, the comparison reports it as `unstable` rather than silently
145
+ calling it a content or markup change; review that page and supply a reason
146
+ before recapture.
147
+
132
148
  The command fails closed when the current crawl is incomplete, a drift has no
133
149
  reason, a reason names a URL without a reported drift, or the destination file
134
150
  already exists. The new contract records its `baselineId`, `supersedes` value,
@@ -249,7 +265,9 @@ accepts JPEG, PNG, GIF, and ICO responses. It reports missing, unreachable,
249
265
  undersized, unreadable, or unsupported images and warns when one source is
250
266
  used for at least 75% of a sample of three or more pages. Page-specific
251
267
  first-band images remain an editorial choice and should be declared by the
252
- host manifest.
268
+ host manifest. When `docs/social-cards-pages.json` or
269
+ `docs/public-urls.json` exists, `maggie release` includes the social-card audit
270
+ in its read-only release path.
253
271
 
254
272
  Head-tag auditing consumes a host-produced JSON manifest with `url`, `shell`,
255
273
  and normalized `tags`. It compares structural declarations for icons,
@@ -281,6 +299,10 @@ is intended; the report then says `"declared-origin"`.
281
299
  Use `--output <project>/docs/site-audit.json` to persist the evidence used by
282
300
  the release review.
283
301
 
302
+ For a full crawl, add `--check-internal-links` to follow same-origin HTML links
303
+ and check HTTP 200 targets plus `#fragment` IDs. External, mailto, and other
304
+ non-site links are intentionally out of scope.
305
+
284
306
  - **Plan:** combine Project Context, GSC/search signals, competitor gaps, and
285
307
  AI visibility prompts into topic clusters and briefs.
286
308
  - **Audit:** inspect robots, sitemap, canonical URLs, metadata, JSON-LD,
@@ -186,6 +186,25 @@ evidence artifact and validates the selected ending (`pending`, `redirect`,
186
186
  signals, and sitemap exclusion. It never stores response bodies or decides a
187
187
  redirect destination for the host.
188
188
 
189
+ ## Appointment time contract
190
+
191
+ Any booking or seasonal variant that receives a timezone-naive wall-clock
192
+ timestamp must carry an IANA `venueTimezone` such as `Europe/London`. The
193
+ shared parser in `tools/clis/maggie_service_booking.py` applies venue DST rules
194
+ instead of the server timezone. An ambiguous DST time must also declare
195
+ `venueTimeFold: 0` or `1`; nonexistent local times are rejected. Prefer an
196
+ offset-bearing timestamp when the provider already supplies one. This is a
197
+ validation contract only: it does not claim that a provider supports booking
198
+ creation or confirmation.
199
+
200
+ When a host implements its own notification adapter, use
201
+ [`notification-lifecycle-v1.schema.json`](../../bundled-contracts/maggie-service-booking/notification-lifecycle-v1.schema.json)
202
+ to represent acknowledgement, studio notification, confirmation, alternative,
203
+ and cancellation events. Every event needs an idempotency key and explicit
204
+ status. A request must remain `pending-confirmation` until the host/provider
205
+ actually confirms it; an email or UI receipt must never imply an appointment
206
+ was booked. The contract is provider-neutral and does not send mail by itself.
207
+
189
208
  ## Asynchronous onboarding contract
190
209
 
191
210
  Provider onboarding is three independent state machines: `import` owns the
@@ -88,6 +88,10 @@ with `WORDPRESS_API_TOKEN` or a server-only application-password pair.
88
88
  The starter includes a Tailwind v4 Vite entry point for projects that choose
89
89
  Tailwind during bootstrap. It remains compatible with the existing contract
90
90
  surface and does not require replacing the host's design system.
91
+ The entry point explicitly scopes Tailwind v4 scanning to renderable Astro
92
+ pages, layouts, and components. API/lib fixtures and prose in server-only files
93
+ are not treated as utility-class sources; keep that scope when adding a host
94
+ component.
91
95
 
92
96
  `npm run maggie:*` uses the installed `maggie` binary. It is portable after the
93
97
  starter is copied out of this monorepo; it no longer depends on repository-
@@ -1,4 +1,10 @@
1
1
  @import "tailwindcss";
2
2
 
3
+ /* Scan renderable Astro surfaces only. Server fixtures and prose in API/lib
4
+ files must not become accidental utility classes in the shipped bundle. */
5
+ @source "../pages/**/*.astro";
6
+ @source "../layouts/**/*.astro";
7
+ @source "../components/**/*.{astro,jsx,tsx}";
8
+
3
9
  /* The starter keeps its contract UI framework-neutral while providing a
4
10
  working Tailwind v4 entry point for projects that select Tailwind. */
@@ -23,6 +23,7 @@ STATE_FILE = "bootstrap-state.json"
23
23
  IGNORED_DIRS = {".git", ".next", "node_modules", "dist", "build", "coverage", "generated"}
24
24
  TEXT_SUFFIXES = {".css", ".html", ".js", ".jsx", ".md", ".mdx", ".svelte", ".ts", ".tsx", ".vue", ".astro", ".njk", ".liquid", ".json", ".yaml", ".yml"}
25
25
  DOCTOR_NON_SITE_DIRS = {".agents", ".claude", "__tests__", "contracts", "docs", "examples", "fixtures", "marketplace", "references", "scripts", "test", "tests", "tools"}
26
+ DOCTOR_SERVER_DIRS = {"api", "data", "lib", "server", "services"}
26
27
  WORKFLOW_PHASES = (
27
28
  "maggiedash-foundation",
28
29
  "project-research",
@@ -81,6 +82,26 @@ def doctor_source_files(root: Path, files: list[Path]) -> list[Path]:
81
82
  return result
82
83
 
83
84
 
85
+ def doctor_placeholder_files(root: Path, source_files: list[Path], rendered_dir: str | None = None) -> tuple[list[Path], str]:
86
+ """Choose rendered output first, with a narrow public-source fallback.
87
+
88
+ Server fixtures and CLI examples are not shipped page metadata and must not
89
+ fail a public-site placeholder check. A build output directory is the
90
+ strongest evidence; ``--rendered-dir`` makes that choice explicit for CI.
91
+ """
92
+ candidates = [Path(rendered_dir).expanduser().resolve()] if rendered_dir else [root / name for name in ("dist", "build", "out", "public")]
93
+ for candidate in candidates:
94
+ if not candidate.is_dir():
95
+ continue
96
+ files = sorted(path for path in candidate.rglob("*") if path.is_file() and path.suffix.lower() in TEXT_SUFFIXES)
97
+ return files, "rendered-output"
98
+ fallback = [
99
+ path for path in source_files
100
+ if not any(part.lower() in DOCTOR_SERVER_DIRS for part in path.relative_to(root).parts[:-1])
101
+ ]
102
+ return fallback, "public-site-source"
103
+
104
+
84
105
  def package_info(root: Path) -> tuple[dict, Path | None]:
85
106
  candidates = [root / "package.json"] + sorted(path for path in root.glob("*/package.json") if path.parent.name not in IGNORED_DIRS)
86
107
  parsed: list[tuple[int, dict, Path]] = []
@@ -819,6 +840,8 @@ def command_doctor(args: argparse.Namespace) -> int:
819
840
  files = list(files_for(root))
820
841
  source_files = doctor_source_files(root, files)
821
842
  text = "\n".join(read_text(path) for path in source_files)
843
+ placeholder_files, placeholder_scope = doctor_placeholder_files(root, source_files, getattr(args, "rendered_dir", None))
844
+ placeholder_text = "\n".join(read_text(path) for path in placeholder_files)
822
845
  paths = [evidence(path, root).lower() for path in source_files]
823
846
 
824
847
  checks = {
@@ -838,7 +861,7 @@ def command_doctor(args: argparse.Namespace) -> int:
838
861
  "jsonld_dates": "datePublished" in text and ("dateModified" in text or "updatedAt" in text),
839
862
  "published_only_guard": "published" in text.lower() and any(token in text.lower() for token in {"sitemap", "post"}),
840
863
  "no_public_api_key": not bool(re.search(r"(?:PUBLIC|NEXT_PUBLIC)[A-Z0-9_]*(?:API|KEY|TOKEN)", text, re.IGNORECASE)),
841
- "no_placeholder_metadata": not any(token in text.lower() for token in {"your-domain.com", "example.com", "lorem ipsum", "replace-me"}),
864
+ "no_placeholder_metadata": not any(token in placeholder_text.lower() for token in {"your-domain.com", "example.com", "lorem ipsum", "replace-me"}),
842
865
  }
843
866
  if args.strict:
844
867
  checks.update({
@@ -886,7 +909,7 @@ def command_doctor(args: argparse.Namespace) -> int:
886
909
  safe_skips.append("robots route requires framework confirmation")
887
910
  for check, reason in (("canonical_metadata", "canonical metadata needs a site-specific URL"), ("meta_description", "metadata content needs editorial input"), ("article_jsonld", "JSON-LD shape needs content-specific fields")):
888
911
  if not checks[check]: safe_skips.append(reason)
889
- result = {"project_root": str(root), "placeholder_scan": {"scope": "public-site-source", "files": len(source_files)}, "checks": {key: {"ok": value} for key, value in checks.items()}, "passed": all(checks.values())}
912
+ result = {"project_root": str(root), "placeholder_scan": {"scope": placeholder_scope, "files": len(placeholder_files)}, "checks": {key: {"ok": value} for key, value in checks.items()}, "passed": all(checks.values())}
890
913
  if getattr(args, "fix_safe", False): result["safe_fixes"] = {"applied": safe_fixes, "skipped_requires_confirmation": safe_skips}
891
914
  print(json.dumps(result, indent=2))
892
915
  return 0 if result["passed"] else 1
@@ -940,6 +963,13 @@ def command_feedback(args: argparse.Namespace) -> int:
940
963
  return completed.returncode
941
964
 
942
965
 
966
+ def command_booking(args: argparse.Namespace) -> int:
967
+ """Dispatch Booking contract checks through the canonical Booking CLI."""
968
+ cli = Path(__file__).with_name("maggie_booking.py")
969
+ completed = subprocess.run([sys.executable, str(cli), *args.booking_args], cwd=Path.cwd(), check=False)
970
+ return completed.returncode
971
+
972
+
943
973
  def parser() -> argparse.ArgumentParser:
944
974
  p = argparse.ArgumentParser(prog="maggie", description=__doc__)
945
975
  sub = p.add_subparsers(dest="command", required=True)
@@ -994,12 +1024,16 @@ def parser() -> argparse.ArgumentParser:
994
1024
  feedback = sub.add_parser("feedback", help="collect, review, and submit privacy-safe feedback")
995
1025
  feedback.add_argument("feedback_args", nargs=argparse.REMAINDER, help="arguments for maggie_feedback.py")
996
1026
  feedback.set_defaults(func=command_feedback)
1027
+ booking = sub.add_parser("booking", help="inspect and validate the MaggieDash Booking workspace")
1028
+ booking.add_argument("booking_args", nargs=argparse.REMAINDER, help="arguments for maggie_booking.py")
1029
+ booking.set_defaults(func=command_booking)
997
1030
  doctor = sub.add_parser("doctor", help="check blog routes and SEO output invariants")
998
1031
  doctor.add_argument("project", nargs="?", default=".")
999
1032
  doctor.add_argument("--require-bootstrap", action="store_true")
1000
1033
  doctor.add_argument("--strict", action="store_true", help="also require the complete AI-native blog and Ops surface")
1001
1034
  doctor.add_argument("--ci", action="store_true", help="CI alias for strict checks")
1002
1035
  doctor.add_argument("--fix-safe", action="store_true", help="apply only low-risk route fixes; never changes schema, slugs, canonical URLs or publishing state")
1036
+ doctor.add_argument("--rendered-dir", help="scan this build output for placeholder metadata instead of source fixtures")
1003
1037
  doctor.set_defaults(func=command_doctor)
1004
1038
  for name in ("routes", "schema", "seo", "migration", "env"):
1005
1039
  check = sub.add_parser(name, help=f"run Maggie {name} contract checks")