motionloom 2.0.0
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/LICENSE +21 -0
- package/README.md +179 -0
- package/SKILL.md +122 -0
- package/agent-card.json +161 -0
- package/assets/library/ATTRIBUTION.md +6 -0
- package/assets/library/README.md +20 -0
- package/assets/library/avatar-base.svg +19 -0
- package/assets/library/error-alert.json +1 -0
- package/assets/library/rive/ATTRIBUTION.md +12 -0
- package/assets/library/rive/state-machine-test.riv +0 -0
- package/assets/library/success-check.json +1 -0
- package/bin/motionloom.mjs +81 -0
- package/docs/BROWSER-REVIEW-E2E.md +78 -0
- package/docs/CATEGORIES.md +16 -0
- package/docs/CHECKLIST.md +31 -0
- package/docs/DEEP-AUDIT-WORKING-NOTES.md +22 -0
- package/docs/FRAMEWORK-SELECTION.md +26 -0
- package/docs/PROJECT-MANIFEST.md +37 -0
- package/docs/ROADMAP-INTELLIGENCE.md +224 -0
- package/docs/audits/1.10.0-attestation-research-notes.md +19 -0
- package/docs/audits/1.8.0-trust-boundary-hardening.md +56 -0
- package/docs/audits/1.9.0-evidence-interoperability-threat-model.md +37 -0
- package/docs/audits/2.0.0-attestation-acceptance.md +30 -0
- package/docs/releases/1.5.0.md +25 -0
- package/docs/releases/1.6.0.md +27 -0
- package/docs/releases/1.7.0.md +23 -0
- package/docs/releases/1.8.0.md +23 -0
- package/docs/releases/1.9.0.md +21 -0
- package/docs/releases/2.0.0.md +21 -0
- package/docs/releases/npm-publish-from-workstation.md +88 -0
- package/docs/research/AGENT-PROTOCOL-FINDINGS.md +43 -0
- package/examples/report-demo/REPORT.md +50 -0
- package/examples/report-demo/artifact-manifest.json +25 -0
- package/examples/report-demo/decision-log.jsonl +0 -0
- package/examples/report-demo/execution-report.json +70 -0
- package/examples/report-demo/handoff.json +22 -0
- package/examples/report-demo/issue-register.json +5 -0
- package/examples/report-demo/task.json +13 -0
- package/package.json +95 -0
- package/project-context.example.json +26 -0
- package/references/browser-review-contract.md +32 -0
- package/references/dotlottie-source-notes.md +21 -0
- package/references/intelligence-core.md +98 -0
- package/references/reporting-contract.md +38 -0
- package/references/runtime-capability.md +12 -0
- package/references/signed-attestation.md +31 -0
- package/schemas/artifact-manifest.schema.json +22 -0
- package/schemas/browser-review-candidate.schema.json +24 -0
- package/schemas/capability-registry.schema.json +61 -0
- package/schemas/continuity-report.schema.json +52 -0
- package/schemas/evidence-verifier-report.schema.json +35 -0
- package/schemas/execution-report.schema.json +35 -0
- package/schemas/fix-plan.schema.json +44 -0
- package/schemas/handoff.schema.json +19 -0
- package/schemas/motion-ir.schema.json +74 -0
- package/schemas/project-graph.schema.json +70 -0
- package/schemas/provenance.schema.json +76 -0
- package/schemas/runtime-evidence.schema.json +48 -0
- package/schemas/runtime-telemetry.schema.json +50 -0
- package/schemas/scene-manifest.schema.json +45 -0
- package/schemas/semantic-benchmark.schema.json +26 -0
- package/schemas/semantic-lint-report.schema.json +48 -0
- package/schemas/signed-attestation.schema.json +95 -0
- package/schemas/task.schema.json +39 -0
- package/schemas/trust-policy.schema.json +53 -0
- package/scripts/analyze.sh +13 -0
- package/scripts/attestation-keygen.py +63 -0
- package/scripts/attestation-verifier.py +178 -0
- package/scripts/attestation.py +288 -0
- package/scripts/capture-runtime-telemetry.sh +37 -0
- package/scripts/devlab.sh +77 -0
- package/scripts/eval-intelligence.py +377 -0
- package/scripts/evidence-verifier.py +222 -0
- package/scripts/fetch-library.sh +57 -0
- package/scripts/intelligence.py +1543 -0
- package/scripts/manifest.py +61 -0
- package/scripts/pr.sh +103 -0
- package/scripts/quality-gate.py +378 -0
- package/scripts/render-node.mjs +53 -0
- package/scripts/render.sh +37 -0
- package/scripts/report-contract.py +181 -0
- package/scripts/report.py +588 -0
- package/scripts/review-hook.py +199 -0
- package/scripts/runtime-adapters.mjs +187 -0
- package/scripts/skill-doctor.py +150 -0
- package/scripts/to-dotlottie.mjs +99 -0
- package/scripts/to-dotlottie.sh +25 -0
- package/scripts/validate-lottie.py +102 -0
- package/src/core/analyzer.py +226 -0
- package/src/core/snapshot.py +124 -0
- package/src/core/spec.py +240 -0
- package/src/output/browser-review-smoke/animation.json +57 -0
- package/src/output/browser-review-smoke/browser-review.json +21 -0
- package/src/output/browser-review-smoke/manifest.json +22 -0
- package/src/output/browser-review-smoke/motion-spec.json +28 -0
- package/src/output/browser-review-smoke/snapshot/.render-meta.json +10 -0
- package/src/output/browser-review-smoke/snapshot/frame-00.png +0 -0
- package/src/output/browser-review-smoke/snapshot/frame-100.png +0 -0
- package/src/output/browser-review-smoke/snapshot/frame-50.png +0 -0
- package/src/rig/README.md +35 -0
- package/src/rig/cutout_rig.py +211 -0
- package/templates/framer-motion/ui-micro.tsx +50 -0
- package/templates/gsap/scroll-scene.js +54 -0
- package/templates/lottie/README.md +21 -0
- package/templates/lottie/react-component.tsx +82 -0
- package/templates/lottie/scaffold/animation.json +57 -0
- package/templates/lottie/scaffold/character-rig.svg +19 -0
- package/templates/lottie/vanilla.js +68 -0
- package/templates/rive/README.md +36 -0
- package/tests/evals/intelligence-cases.json +131 -0
- package/tests/scripts/run_tests.py +843 -0
- package/tests/scripts/test_attestation.py +172 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Validate task bundles bound to the scenes changed by a commit."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def read_json(path: Path) -> dict:
|
|
13
|
+
try:
|
|
14
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
15
|
+
except (OSError, json.JSONDecodeError):
|
|
16
|
+
return {}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def has_symlink_component(path: Path, root: Path) -> bool:
|
|
20
|
+
current = path
|
|
21
|
+
while True:
|
|
22
|
+
if current.is_symlink():
|
|
23
|
+
return True
|
|
24
|
+
if current == root:
|
|
25
|
+
return False
|
|
26
|
+
if current == current.parent:
|
|
27
|
+
return True
|
|
28
|
+
current = current.parent
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def task_dirs(root: Path, scene: str) -> list[Path]:
|
|
32
|
+
artifacts = root / "artifacts"
|
|
33
|
+
if not artifacts.is_dir() or artifacts.is_symlink():
|
|
34
|
+
return []
|
|
35
|
+
resolved_artifacts = artifacts.resolve()
|
|
36
|
+
matched = []
|
|
37
|
+
for task_path in sorted(artifacts.glob("*/task.json")):
|
|
38
|
+
if has_symlink_component(task_path, root):
|
|
39
|
+
continue
|
|
40
|
+
try:
|
|
41
|
+
if not task_path.resolve().is_relative_to(resolved_artifacts):
|
|
42
|
+
continue
|
|
43
|
+
except (OSError, RuntimeError):
|
|
44
|
+
continue
|
|
45
|
+
task = read_json(task_path)
|
|
46
|
+
if task.get("scene") == scene:
|
|
47
|
+
matched.append(task_path.parent)
|
|
48
|
+
return matched
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def main() -> int:
|
|
52
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
53
|
+
parser.add_argument("--scenes-file", required=True)
|
|
54
|
+
parser.add_argument("--root", default=".")
|
|
55
|
+
parser.add_argument("--require-attestation", action="store_true")
|
|
56
|
+
args = parser.parse_args()
|
|
57
|
+
root = Path(args.root).resolve()
|
|
58
|
+
report_script = Path(__file__).resolve().with_name("report.py")
|
|
59
|
+
scenes_file = Path(args.scenes_file)
|
|
60
|
+
scenes = [line.strip() for line in scenes_file.read_text(encoding="utf-8").splitlines() if line.strip()]
|
|
61
|
+
if not scenes:
|
|
62
|
+
print("No changed scenes; report completeness check skipped.")
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
missing: list[str] = []
|
|
66
|
+
selected: list[str] = []
|
|
67
|
+
required = (
|
|
68
|
+
"task.json",
|
|
69
|
+
"execution-report.json",
|
|
70
|
+
"artifact-manifest.json",
|
|
71
|
+
"handoff.json",
|
|
72
|
+
"issue-register.json",
|
|
73
|
+
"semantic-lint-report.json",
|
|
74
|
+
"semantic-lint-benchmark.json",
|
|
75
|
+
"continuity-report.json",
|
|
76
|
+
"fix-plan.json",
|
|
77
|
+
"evidence-verifier-report.json",
|
|
78
|
+
)
|
|
79
|
+
if args.require_attestation:
|
|
80
|
+
required = required + ("attestation.json", "trust-policy.json")
|
|
81
|
+
attestation_verifier = Path(__file__).resolve().with_name("attestation-verifier.py")
|
|
82
|
+
for scene in scenes:
|
|
83
|
+
matched = task_dirs(root, scene)
|
|
84
|
+
if not matched:
|
|
85
|
+
missing.append(f"{scene}: no artifacts/<task-id>/task.json bound to scene")
|
|
86
|
+
continue
|
|
87
|
+
complete = [task_dir for task_dir in matched if all((task_dir / name).is_file() for name in required)]
|
|
88
|
+
if not complete:
|
|
89
|
+
missing.append(f"{scene}: incomplete task/report/P1 feedback bundle")
|
|
90
|
+
continue
|
|
91
|
+
passing: list[Path] = []
|
|
92
|
+
for task_dir in complete:
|
|
93
|
+
result = subprocess.run(
|
|
94
|
+
[sys.executable, str(report_script), "check", "--task-dir", str(task_dir)],
|
|
95
|
+
cwd=root,
|
|
96
|
+
capture_output=True,
|
|
97
|
+
text=True,
|
|
98
|
+
)
|
|
99
|
+
verification = read_json(task_dir / "evidence-verifier-report.json")
|
|
100
|
+
task = read_json(task_dir / "task.json")
|
|
101
|
+
verifier_ok = (
|
|
102
|
+
verification.get("verified") is True
|
|
103
|
+
and verification.get("approval") is False
|
|
104
|
+
and verification.get("bindings", {}).get("scene") == scene
|
|
105
|
+
and verification.get("bindings", {}).get("task_id") == task.get("task_id")
|
|
106
|
+
)
|
|
107
|
+
attestation_ok = True
|
|
108
|
+
if args.require_attestation:
|
|
109
|
+
attestation_result = subprocess.run(
|
|
110
|
+
[
|
|
111
|
+
sys.executable,
|
|
112
|
+
str(attestation_verifier),
|
|
113
|
+
"--attestation",
|
|
114
|
+
str(task_dir / "attestation.json"),
|
|
115
|
+
"--trust-policy",
|
|
116
|
+
str(task_dir / "trust-policy.json"),
|
|
117
|
+
"--expected-task-id",
|
|
118
|
+
str(task.get("task_id") or ""),
|
|
119
|
+
"--expected-scene",
|
|
120
|
+
scene,
|
|
121
|
+
],
|
|
122
|
+
cwd=root,
|
|
123
|
+
capture_output=True,
|
|
124
|
+
text=True,
|
|
125
|
+
)
|
|
126
|
+
attestation_doc = read_json(task_dir / "attestation.json")
|
|
127
|
+
verifier_doc = read_json(task_dir / "attestation-verifier-report.json")
|
|
128
|
+
if not verifier_doc:
|
|
129
|
+
try:
|
|
130
|
+
verifier_doc = json.loads(attestation_result.stdout)
|
|
131
|
+
except json.JSONDecodeError:
|
|
132
|
+
verifier_doc = {}
|
|
133
|
+
attestation_ok = (
|
|
134
|
+
attestation_result.returncode == 0
|
|
135
|
+
and verifier_doc.get("verified") is True
|
|
136
|
+
and verifier_doc.get("approval") is False
|
|
137
|
+
and attestation_doc.get("approval") is False
|
|
138
|
+
and verifier_doc.get("bindings", {}).get("scene") == scene
|
|
139
|
+
and verifier_doc.get("bindings", {}).get("task_id") == task.get("task_id")
|
|
140
|
+
)
|
|
141
|
+
if result.returncode == 0 and verifier_ok and attestation_ok:
|
|
142
|
+
passing.append(task_dir)
|
|
143
|
+
if not passing:
|
|
144
|
+
missing.append(f"{scene}: semantic report check failed")
|
|
145
|
+
continue
|
|
146
|
+
state_rank = {"confirmed": 4, "ready_for_pr": 3, "validated": 2, "review_required": 1}
|
|
147
|
+
ranked = sorted(
|
|
148
|
+
passing,
|
|
149
|
+
key=lambda path: (
|
|
150
|
+
state_rank.get(str(read_json(path / "task.json").get("state")), 0),
|
|
151
|
+
str(read_json(path / "task.json").get("updated_at", "")),
|
|
152
|
+
str(read_json(path / "task.json").get("task_id", "")),
|
|
153
|
+
),
|
|
154
|
+
reverse=True,
|
|
155
|
+
)
|
|
156
|
+
top_task = read_json(ranked[0] / "task.json")
|
|
157
|
+
top_key = (state_rank.get(str(top_task.get("state")), 0), str(top_task.get("updated_at", "")))
|
|
158
|
+
if sum(
|
|
159
|
+
1
|
|
160
|
+
for path in passing
|
|
161
|
+
if (
|
|
162
|
+
state_rank.get(str(read_json(path / "task.json").get("state")), 0),
|
|
163
|
+
str(read_json(path / "task.json").get("updated_at", "")),
|
|
164
|
+
)
|
|
165
|
+
== top_key
|
|
166
|
+
) > 1:
|
|
167
|
+
missing.append(f"{scene}: ambiguous passing task bundles share the same state and updated_at")
|
|
168
|
+
else:
|
|
169
|
+
selected.append(f"{scene}={top_task.get('task_id', ranked[0].name)}")
|
|
170
|
+
|
|
171
|
+
if missing:
|
|
172
|
+
print("FAIL: report/handoff contract is incomplete:")
|
|
173
|
+
print("\n".join(f" - {item}" for item in missing))
|
|
174
|
+
return 1
|
|
175
|
+
suffix = f" Selected bundles: {', '.join(selected)}." if selected else ""
|
|
176
|
+
print(f"Report contract passed for {len(scenes)} scene(s).{suffix}")
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
if __name__ == "__main__":
|
|
181
|
+
raise SystemExit(main())
|