@topy-ai/maggie 0.7.3 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -9
- package/README.zh-TW.md +5 -1
- package/bin/maggie.js +18 -2
- package/bundled-contracts/maggie-deployment/release-runner-v1.json +14 -0
- package/bundled-contracts/maggiedash/README.md +22 -0
- package/bundled-contracts/maggiedash/agent-content-v1.json +15 -0
- package/bundled-contracts/maggiedash/redirect-policy-v1.json +15 -0
- package/bundled-contracts/maggiedash/section-identity-v1.json +14 -0
- package/bundled-contracts/maggiedash/telemetry-policy-v1.json +18 -0
- package/bundled-contracts/maggiedash/translation-cache-policy-v1.json +15 -0
- package/bundled-references/maggiedash-telemetry-contract.md +12 -0
- package/bundled-skills/maggie-dash/SKILL.md +76 -3
- package/bundled-skills/maggie-deployment/SKILL.md +20 -2
- package/bundled-skills/maggie-ops/SKILL.md +23 -2
- package/bundled-skills/maggie-seo-geo/SKILL.md +16 -0
- package/bundled-templates/maggiedash/dashboard-ui-contract.json +8 -7
- package/bundled-templates/maggiedash/section-registry.json +12 -0
- package/bundled-tools/clis/maggie_agent_content.py +75 -0
- package/bundled-tools/clis/maggie_dash.py +112 -1
- package/bundled-tools/clis/maggie_deployment.py +51 -0
- package/bundled-tools/clis/maggie_ops.py +23 -0
- package/bundled-tools/clis/maggie_verification.py +33 -0
- package/bundled-tools/runtime/agent_runtime.py +38 -0
- package/bundled-tools/runtime/analytics_traffic.py +6 -1
- package/bundled-tools/runtime/maggie_dash_store.py +121 -8
- package/bundled-tools/runtime/maggie_dash_ui.py +35 -1
- package/bundled-tools/runtime/maggie_sections.py +143 -0
- package/bundled-tools/runtime/restart_gate.py +22 -0
- package/bundled-tools/runtime/surface_coverage.py +48 -0
- package/package.json +1 -1
- package/references/maggiedash-telemetry-contract.md +12 -0
|
@@ -15,6 +15,23 @@ def _component_source(name: str, source: str) -> str:
|
|
|
15
15
|
return match.group(0) if match else ""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
def _render_body(component_source: str) -> str:
|
|
19
|
+
"""Return the component implementation, excluding its props signature.
|
|
20
|
+
|
|
21
|
+
A declaration and its TypeScript type are evidence that a prop exists, not
|
|
22
|
+
evidence that the rendered UI uses it. Counting only the implementation
|
|
23
|
+
prevents a dead prop from passing because it appears twice in the signature.
|
|
24
|
+
"""
|
|
25
|
+
# Typed destructured parameters contain their own `{...}` block. The
|
|
26
|
+
# function body is the last opening brace before the first return.
|
|
27
|
+
return_position = component_source.find("return")
|
|
28
|
+
opening = component_source.rfind("{", 0, return_position if return_position != -1 else len(component_source))
|
|
29
|
+
if opening == -1:
|
|
30
|
+
arrow = component_source.find("=>")
|
|
31
|
+
opening = component_source.find("{", arrow) if arrow != -1 else -1
|
|
32
|
+
return component_source[opening + 1:] if opening != -1 else ""
|
|
33
|
+
|
|
34
|
+
|
|
18
35
|
def validate(contract: object, sources: Iterable[str] = ()) -> dict:
|
|
19
36
|
errors: list[str] = []
|
|
20
37
|
if not isinstance(contract, dict):
|
|
@@ -44,12 +61,29 @@ def validate(contract: object, sources: Iterable[str] = ()) -> dict:
|
|
|
44
61
|
forbidden = [str(prop) for prop in component.get("forbiddenProps", [])]
|
|
45
62
|
if component_source:
|
|
46
63
|
signature = component_source.split("=>", 1)[0] if "=>" in component_source else component_source[:500]
|
|
64
|
+
body = _render_body(component_source)
|
|
47
65
|
for prop in props:
|
|
48
|
-
if len(re.findall(rf"\b{re.escape(prop)}\b",
|
|
66
|
+
if len(re.findall(rf"\b{re.escape(prop)}\b", body)) < 1:
|
|
49
67
|
errors.append(f"{name}.{prop} is declared but not evidenced in rendered output")
|
|
50
68
|
for prop in forbidden:
|
|
51
69
|
if re.search(rf"\b{re.escape(prop)}\b", signature):
|
|
52
70
|
errors.append(f"{name} declares forbidden dead prop: {prop}")
|
|
71
|
+
dialogs = contract.get("dialogs")
|
|
72
|
+
if isinstance(dialogs, dict) and source:
|
|
73
|
+
for pattern in dialogs.get("forbiddenSourcePatterns", []):
|
|
74
|
+
pattern = str(pattern)
|
|
75
|
+
if pattern in source:
|
|
76
|
+
errors.append(f"dialog contract forbids source pattern: {pattern}")
|
|
77
|
+
required = [str(item) for item in dialogs.get("requiredLiterals", [])]
|
|
78
|
+
for key in ("primitive", "overlayPrimitive"):
|
|
79
|
+
name = str(dialogs.get(key) or "")
|
|
80
|
+
component_source = _component_source(name, source) if name else ""
|
|
81
|
+
if not component_source:
|
|
82
|
+
errors.append(f"dialog source is missing: {name}")
|
|
83
|
+
continue
|
|
84
|
+
for literal in required:
|
|
85
|
+
if literal not in component_source:
|
|
86
|
+
errors.append(f"{name} is missing dialog requirement: {literal}")
|
|
53
87
|
return {"schemaVersion": SCHEMA, "passed": not errors, "errors": errors, "components": [item.get("name") for item in components if isinstance(item, dict)]}
|
|
54
88
|
|
|
55
89
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Provider-neutral section registry, copy limits, and stable section keys."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Any, Iterable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
SCHEMA = "maggiedash-section-registry.v1"
|
|
14
|
+
IDENTITY_SCHEMA = "maggiedash-section-identity.v1"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def validate_registry(value: object) -> dict[str, Any]:
|
|
18
|
+
errors: list[str] = []
|
|
19
|
+
if not isinstance(value, dict):
|
|
20
|
+
return {"schemaVersion": SCHEMA, "passed": False, "errors": ["registry must be an object"]}
|
|
21
|
+
if value.get("schemaVersion") != SCHEMA:
|
|
22
|
+
errors.append(f"schemaVersion must be {SCHEMA}")
|
|
23
|
+
sections = value.get("sections")
|
|
24
|
+
if not isinstance(sections, list) or not sections:
|
|
25
|
+
errors.append("sections must be a non-empty list")
|
|
26
|
+
sections = []
|
|
27
|
+
seen: set[str] = set()
|
|
28
|
+
for index, section in enumerate(sections):
|
|
29
|
+
at = f"sections[{index}]"
|
|
30
|
+
if not isinstance(section, dict):
|
|
31
|
+
errors.append(f"{at} must be an object")
|
|
32
|
+
continue
|
|
33
|
+
section_type = str(section.get("type") or "")
|
|
34
|
+
if not re.fullmatch(r"[a-z][a-z0-9-]*", section_type):
|
|
35
|
+
errors.append(f"{at}.type must be a kebab-case identifier")
|
|
36
|
+
if section_type in seen:
|
|
37
|
+
errors.append(f"duplicate section type: {section_type}")
|
|
38
|
+
seen.add(section_type)
|
|
39
|
+
for field in ("purpose", "usage", "position", "repeatable"):
|
|
40
|
+
if field not in section:
|
|
41
|
+
errors.append(f"{at}.{field} is required")
|
|
42
|
+
if section.get("position") not in {"opening", "body", "closing"}:
|
|
43
|
+
errors.append(f"{at}.position must be opening, body, or closing")
|
|
44
|
+
if not isinstance(section.get("repeatable"), bool):
|
|
45
|
+
errors.append(f"{at}.repeatable must be boolean")
|
|
46
|
+
fields = section.get("fields")
|
|
47
|
+
if not isinstance(fields, list) or not fields:
|
|
48
|
+
errors.append(f"{at}.fields must be a non-empty list")
|
|
49
|
+
continue
|
|
50
|
+
field_names: set[str] = set()
|
|
51
|
+
for field_index, field in enumerate(fields):
|
|
52
|
+
field_at = f"{at}.fields[{field_index}]"
|
|
53
|
+
if not isinstance(field, dict) or not field.get("name"):
|
|
54
|
+
errors.append(f"{field_at} needs a name")
|
|
55
|
+
continue
|
|
56
|
+
name = str(field["name"])
|
|
57
|
+
if name in field_names:
|
|
58
|
+
errors.append(f"duplicate field: {section_type}.{name}")
|
|
59
|
+
field_names.add(name)
|
|
60
|
+
if not isinstance(field.get("limit"), int) or field["limit"] < 0:
|
|
61
|
+
errors.append(f"{field_at}.limit must be a non-negative integer")
|
|
62
|
+
if not str(field.get("usage") or "").strip():
|
|
63
|
+
errors.append(f"{field_at}.usage is required")
|
|
64
|
+
repeats = field.get("repeats")
|
|
65
|
+
if repeats is not None and (not isinstance(repeats, dict) or not isinstance(repeats.get("min"), int) or not isinstance(repeats.get("max"), int) or repeats["min"] < 0 or repeats["min"] > repeats["max"]):
|
|
66
|
+
errors.append(f"{field_at}.repeats must declare valid min/max")
|
|
67
|
+
return {"schemaVersion": SCHEMA, "passed": not errors, "errors": errors, "sectionTypes": sorted(seen)}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def catalogue(registry: dict[str, Any]) -> str:
|
|
71
|
+
"""Render a compact prompt catalogue without duplicating field guidance."""
|
|
72
|
+
lines: list[str] = []
|
|
73
|
+
for section in registry.get("sections", []):
|
|
74
|
+
lines.append(f"{section['type']} — {section['purpose']} | when: {section['usage']} | place: {section['position']} | {'repeatable' if section['repeatable'] else 'once'}")
|
|
75
|
+
for field in section.get("fields", []):
|
|
76
|
+
repeat = field.get("repeats")
|
|
77
|
+
suffix = f" [{repeat['min']}-{repeat['max']} entries]" if isinstance(repeat, dict) else ""
|
|
78
|
+
lines.append(f" - {field['name']} ≤{field['limit']} chars{suffix}: {field['usage']}")
|
|
79
|
+
return "\n".join(lines)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def copy_notes(registry: dict[str, Any], sections: object) -> dict[str, Any]:
|
|
83
|
+
"""Return advisory copy/shape notes; over-limit copy is not rejected."""
|
|
84
|
+
errors: list[str] = []
|
|
85
|
+
notes: list[dict[str, Any]] = []
|
|
86
|
+
if not isinstance(sections, list):
|
|
87
|
+
return {"passed": False, "errors": ["sections must be a list"], "notes": []}
|
|
88
|
+
specs = {str(item.get("type")): item for item in registry.get("sections", []) if isinstance(item, dict)}
|
|
89
|
+
for index, section in enumerate(sections):
|
|
90
|
+
if not isinstance(section, dict):
|
|
91
|
+
errors.append(f"sections[{index}] must be an object")
|
|
92
|
+
continue
|
|
93
|
+
section_type = str(section.get("type") or "")
|
|
94
|
+
spec = specs.get(section_type)
|
|
95
|
+
if not spec:
|
|
96
|
+
errors.append(f"unknown section type: {section_type}")
|
|
97
|
+
continue
|
|
98
|
+
for field in spec.get("fields", []):
|
|
99
|
+
name = str(field.get("name"))
|
|
100
|
+
value = section.get(name)
|
|
101
|
+
limit = int(field.get("limit", 0))
|
|
102
|
+
if isinstance(value, str) and limit and len(value) > limit:
|
|
103
|
+
notes.append({"at": f"sections[{index}].{name}", "message": f"{len(value)} characters; layout limit is about {limit}"})
|
|
104
|
+
repeats = field.get("repeats")
|
|
105
|
+
if isinstance(repeats, dict) and isinstance(value, list) and not repeats["min"] <= len(value) <= repeats["max"]:
|
|
106
|
+
notes.append({"at": f"sections[{index}].{name}", "message": f"{len(value)} entries; layout expects {repeats['min']}-{repeats['max']}"})
|
|
107
|
+
return {"passed": not errors, "errors": errors, "notes": notes}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def new_section_id(existing: Iterable[str] = ()) -> str:
|
|
111
|
+
taken = set(existing)
|
|
112
|
+
while True:
|
|
113
|
+
candidate = uuid.uuid4().hex[:8]
|
|
114
|
+
if candidate not in taken:
|
|
115
|
+
return candidate
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def ensure_section_ids(sections: list[dict[str, Any]], existing: Iterable[str] = ()) -> list[dict[str, Any]]:
|
|
119
|
+
result = copy.deepcopy(sections)
|
|
120
|
+
existing_ids = set(existing)
|
|
121
|
+
taken = set(existing_ids)
|
|
122
|
+
for section in result:
|
|
123
|
+
current = str(section.get("id") or "")
|
|
124
|
+
if not current or current in existing_ids or current in taken:
|
|
125
|
+
section["id"] = new_section_id(taken)
|
|
126
|
+
taken.add(str(section["id"]))
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def section_key(section: object, index: int) -> str:
|
|
131
|
+
if isinstance(section, dict) and isinstance(section.get("id"), str) and section["id"]:
|
|
132
|
+
return section["id"]
|
|
133
|
+
return str(index)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def translation_key(page_id: str, section: object, index: int, field: str) -> str:
|
|
137
|
+
return f"page.{page_id}.sections.{section_key(section, index)}.{field}"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def section_id_migration(page_id: str, sections: list[dict[str, Any]]) -> dict[str, Any]:
|
|
141
|
+
identified = ensure_section_ids(sections)
|
|
142
|
+
mappings = [{"from": f"page.{page_id}.sections.{index}.", "to": f"page.{page_id}.sections.{section['id']}."} for index, section in enumerate(identified)]
|
|
143
|
+
return {"schemaVersion": IDENTITY_SCHEMA, "pageId": page_id, "sections": identified, "renameOrder": sorted(mappings, key=lambda item: len(item["from"]), reverse=True), "sourceChecksum": hashlib.sha256(json.dumps(sections, sort_keys=True, ensure_ascii=False, separators=(",", ":")).encode()).hexdigest()}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Evidence gate for writes performed outside the running application."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def validate(value: object) -> dict[str, Any]:
|
|
9
|
+
errors: list[str] = []
|
|
10
|
+
if not isinstance(value, dict):
|
|
11
|
+
return {"schemaVersion": "maggie-restart-gate.v1", "passed": False, "errors": ["restart gate must be an object"]}
|
|
12
|
+
if value.get("schemaVersion") != "maggie-restart-gate.v1":
|
|
13
|
+
errors.append("unsupported restart gate schema")
|
|
14
|
+
if value.get("outOfBandWrite") is not True:
|
|
15
|
+
errors.append("outOfBandWrite must be true for this gate")
|
|
16
|
+
if value.get("restartRequired") is not True:
|
|
17
|
+
errors.append("restartRequired must be true after an out-of-band write")
|
|
18
|
+
if not str(value.get("restartCommand") or "").strip():
|
|
19
|
+
errors.append("restartCommand is required")
|
|
20
|
+
if value.get("verificationAfterRestart") is not True:
|
|
21
|
+
errors.append("verificationAfterRestart must be true")
|
|
22
|
+
return {"schemaVersion": "maggie-restart-gate.v1", "passed": not errors, "errors": errors, "order": ["write", "restart", "verify"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Coverage checks that follow the surface and locale changed by a release."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
SCHEMA = "maggie-surface-coverage.v1"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def validate_contract(contract: object) -> dict[str, Any]:
|
|
12
|
+
errors: list[str] = []
|
|
13
|
+
if not isinstance(contract, dict):
|
|
14
|
+
return {"schemaVersion": SCHEMA, "passed": False, "errors": ["coverage contract must be an object"]}
|
|
15
|
+
if contract.get("schemaVersion") != SCHEMA:
|
|
16
|
+
errors.append(f"schemaVersion must be {SCHEMA}")
|
|
17
|
+
surfaces = contract.get("surfaces")
|
|
18
|
+
if not isinstance(surfaces, list) or not surfaces:
|
|
19
|
+
errors.append("surfaces must be a non-empty list")
|
|
20
|
+
surfaces = []
|
|
21
|
+
seen: set[tuple[str, str]] = set()
|
|
22
|
+
for index, item in enumerate(surfaces):
|
|
23
|
+
if not isinstance(item, dict) or not item.get("name") or not isinstance(item.get("routes"), list) or not item["routes"] or not isinstance(item.get("locales"), list) or not item["locales"]:
|
|
24
|
+
errors.append(f"surfaces[{index}] needs name, routes, and locales")
|
|
25
|
+
continue
|
|
26
|
+
for locale in item["locales"]:
|
|
27
|
+
key = (str(item["name"]), str(locale))
|
|
28
|
+
if key in seen:
|
|
29
|
+
errors.append(f"duplicate surface/locale: {key[0]}/{key[1]}")
|
|
30
|
+
seen.add(key)
|
|
31
|
+
return {"schemaVersion": SCHEMA, "passed": not errors, "errors": errors, "expectedPairs": sorted(f"{surface}/{locale}" for surface, locale in seen)}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def compare(contract: dict[str, Any], observations: object) -> dict[str, Any]:
|
|
35
|
+
checked = validate_contract(contract)
|
|
36
|
+
if not checked["passed"]:
|
|
37
|
+
return {**checked, "observedPairs": [], "missing": [], "failed": []}
|
|
38
|
+
expected = {(str(item["name"]), str(locale)) for item in contract["surfaces"] for locale in item["locales"]}
|
|
39
|
+
observed: dict[tuple[str, str], dict[str, Any]] = {}
|
|
40
|
+
for item in observations if isinstance(observations, list) else []:
|
|
41
|
+
if not isinstance(item, dict):
|
|
42
|
+
continue
|
|
43
|
+
key = (str(item.get("surface") or ""), str(item.get("locale") or ""))
|
|
44
|
+
if key in expected:
|
|
45
|
+
observed[key] = item
|
|
46
|
+
missing = sorted(f"{surface}/{locale}" for surface, locale in expected - observed.keys())
|
|
47
|
+
failed = sorted(f"{surface}/{locale}" for (surface, locale), item in observed.items() if item.get("passed") is not True or int(item.get("status", 200)) >= 400)
|
|
48
|
+
return {"schemaVersion": SCHEMA, "passed": not missing and not failed, "expectedPairs": sorted(f"{surface}/{locale}" for surface, locale in expected), "observedPairs": sorted(f"{surface}/{locale}" for surface, locale in observed), "missing": missing, "failed": failed}
|
package/package.json
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# MaggieDash telemetry contract
|
|
2
|
+
|
|
3
|
+
Every measurement path must call the shared first-party traffic filter before
|
|
4
|
+
incrementing a counter or writing a measurement event. This includes page
|
|
5
|
+
views, 404/not-found rows, redirect hits, booking clicks, analytics events and
|
|
6
|
+
operational audit counters. Filtering only page views leaves synthetic sweeps
|
|
7
|
+
in the other totals.
|
|
8
|
+
|
|
9
|
+
Use `should_record_measurement(event)` from
|
|
10
|
+
`tools/runtime/analytics_traffic.py`. The filter accepts explicit Maggie/test
|
|
11
|
+
markers and known toolchain user agents; it never guesses identity from IP
|
|
12
|
+
addresses or private user fields.
|