motionloom 2.2.0 → 2.4.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.
Files changed (114) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/README.md +123 -17
  3. package/ROADMAP.md +8 -3
  4. package/SKILL.md +34 -3
  5. package/agent-card.json +48 -5
  6. package/agent-surfaces.json +8 -1
  7. package/artifact-adapter-registry.json +53 -0
  8. package/bin/motionloom.mjs +41 -4
  9. package/docs/AGENT-INTEGRATION.md +15 -2
  10. package/docs/CHECKLIST.md +21 -0
  11. package/docs/STATUS.md +2 -2
  12. package/docs/audits/data/deep-stress-latest.json +65 -65
  13. package/docs/releases/2.3.0.md +33 -0
  14. package/docs/releases/2.4.0.md +31 -0
  15. package/docs/releases/npm-publish-from-workstation.md +19 -8
  16. package/docs/research/agent-skill-ecosystem-notes.md +47 -0
  17. package/docs/research/ai-animation-tools-2026-notes.md +81 -0
  18. package/docs/research/ai-animation-tools-2026-report.md +168 -0
  19. package/docs/research/ai-pilot-ingest-notes.md +29 -0
  20. package/examples/agent-consumer/ai-generated-pilot/hero-male.json +10 -0
  21. package/examples/agent-consumer/ai-generated-pilot-provenance.json +55 -0
  22. package/examples/agent-consumer/ai-pilot-scout/CHATGPT-HANDOFF.md +70 -0
  23. package/examples/agent-consumer/ai-pilot-scout/CODEX-HANDOFF.md +80 -0
  24. package/examples/agent-consumer/ai-pilot-scout/README.md +39 -0
  25. package/examples/agent-consumer/ai-pilot-scout/chatgpt-geometry-correction-prompt.md +55 -0
  26. package/examples/agent-consumer/ai-pilot-scout/chatgpt-pose-generation-prompt.md +78 -0
  27. package/examples/agent-consumer/ai-pilot-scout/partial-handoff.json +73 -0
  28. package/examples/agent-consumer/artifact-intake/hero-motion-controls.json +15 -0
  29. package/examples/agent-consumer/artifact-intake/hero-motion-export.json +14 -0
  30. package/examples/agent-consumer/artifact-intake/hero-motion-provenance.json +20 -0
  31. package/examples/agent-consumer/artifact-intake/hero-motion-receipt.json +16 -0
  32. package/examples/agent-consumer/asset-consistency/assets/forest-back.png +0 -0
  33. package/examples/agent-consumer/asset-consistency/assets/forest-front.png +0 -0
  34. package/examples/agent-consumer/asset-consistency/assets/forest-mid.png +0 -0
  35. package/examples/agent-consumer/asset-consistency/assets/hero-atlas.png +0 -0
  36. package/examples/agent-consumer/asset-consistency/assets/hero-frame-00.png +0 -0
  37. package/examples/agent-consumer/asset-consistency/assets/hero-frame-01.png +0 -0
  38. package/examples/agent-consumer/asset-consistency/assets/hero-frame-02.png +0 -0
  39. package/examples/agent-consumer/asset-consistency/assets/hero-frame-03.png +0 -0
  40. package/examples/agent-consumer/asset-consistency/forest-layered-map.json +53 -0
  41. package/examples/agent-consumer/asset-consistency/hero-atlas-contract.json +24 -0
  42. package/examples/agent-consumer/asset-consistency/hero-identity.json +51 -0
  43. package/examples/agent-consumer/asset-consistency/hero-walk-action-set.json +27 -0
  44. package/examples/agent-consumer/asset-consistency/hero-walk-frame-geometry.json +63 -0
  45. package/examples/agent-consumer/rig-compatibility/hero-walk-fixture-rig.json +13 -0
  46. package/examples/agent-consumer/rive-package-gate/README.md +20 -0
  47. package/examples/agent-consumer/runtime-candidate/hero-walk-candidate.json +18 -0
  48. package/examples/agent-consumer/runtime-pilot/action-set.json +19 -0
  49. package/examples/agent-consumer/runtime-pilot/asset-identity.json +44 -0
  50. package/examples/agent-consumer/runtime-pilot/candidate.json +21 -0
  51. package/examples/agent-consumer/runtime-pilot/controls.json +35 -0
  52. package/examples/agent-consumer/runtime-pilot/export.json +17 -0
  53. package/examples/agent-consumer/runtime-pilot/provenance.json +38 -0
  54. package/examples/agent-consumer/runtime-pilot/receipt.json +30 -0
  55. package/package.json +65 -4
  56. package/references/agent-interoperability.md +11 -0
  57. package/references/browser-review-contract.md +7 -1
  58. package/references/intelligence-core.md +29 -2
  59. package/rig-adapter-registry.json +39 -0
  60. package/schemas/action-set.schema.json +42 -0
  61. package/schemas/agent-surfaces.schema.json +1 -1
  62. package/schemas/artifact-adapter-registry.schema.json +16 -0
  63. package/schemas/asset-identity.schema.json +117 -0
  64. package/schemas/asset-provenance.schema.json +183 -0
  65. package/schemas/atlas-contract.schema.json +48 -0
  66. package/schemas/control-track.schema.json +18 -0
  67. package/schemas/export-manifest.schema.json +19 -0
  68. package/schemas/frame-geometry.schema.json +79 -0
  69. package/schemas/generation-receipt.schema.json +20 -0
  70. package/schemas/layered-map.schema.json +75 -0
  71. package/schemas/rig-adapter-registry.schema.json +14 -0
  72. package/schemas/rig-compatibility.schema.json +53 -0
  73. package/schemas/rive-package-manifest.schema.json +43 -0
  74. package/schemas/runtime-candidate.schema.json +48 -0
  75. package/schemas/scene-manifest.schema.json +19 -0
  76. package/scripts/artifact-intake.py +408 -0
  77. package/scripts/asset-consistency.py +517 -0
  78. package/scripts/asset-provenance.py +395 -0
  79. package/scripts/build-ai-pilot.py +504 -0
  80. package/scripts/docs-audit.py +14 -2
  81. package/scripts/isolate-alpha-background.py +147 -0
  82. package/scripts/pr.py +2 -0
  83. package/scripts/quality-gate.py +127 -3
  84. package/scripts/report.py +135 -0
  85. package/scripts/resolve-task-bundle.py +84 -0
  86. package/scripts/review-hook.py +26 -4
  87. package/scripts/rig-compatibility.py +251 -0
  88. package/scripts/rive-package-gate.py +245 -0
  89. package/scripts/runtime-candidate.py +301 -0
  90. package/scripts/setup.mjs +500 -0
  91. package/scripts/skill-doctor.py +2 -1
  92. package/src/output/browser-review-smoke/asset-provenance.json +77 -0
  93. package/src/output/browser-review-smoke/manifest.json +1 -0
  94. package/src/output/browser-review-smoke/visual-truth.json +3 -3
  95. package/src/output/runtime-pilot-framer/asset-provenance.json +38 -0
  96. package/src/output/runtime-pilot-framer/browser-review.json +20 -0
  97. package/src/output/runtime-pilot-framer/framer-motion/runtime-telemetry.json +103 -0
  98. package/src/output/runtime-pilot-framer/manifest.json +31 -0
  99. package/src/output/runtime-pilot-framer/motion-spec.json +25 -0
  100. package/src/output/runtime-pilot-framer/runtime-evidence.json +96 -0
  101. package/src/output/runtime-pilot-framer/runtime-telemetry.json +100 -0
  102. package/src/output/runtime-pilot-framer/scene.jsx +52 -0
  103. package/src/output/runtime-pilot-framer/snapshot/.render-meta.json +9 -0
  104. package/src/output/runtime-pilot-framer/snapshot/frame-00.png +0 -0
  105. package/src/output/runtime-pilot-framer/snapshot/frame-100.png +0 -0
  106. package/src/output/runtime-pilot-framer/snapshot/frame-50.png +0 -0
  107. package/tests/scripts/run_tests.py +131 -0
  108. package/tests/scripts/test_ai_pilot_builder.py +128 -0
  109. package/tests/scripts/test_alpha_isolation.py +60 -0
  110. package/tests/scripts/test_artifact_intake.py +146 -0
  111. package/tests/scripts/test_asset_consistency.py +199 -0
  112. package/tests/scripts/test_rig_compatibility.py +105 -0
  113. package/tests/scripts/test_rive_package_gate.py +101 -0
  114. package/tests/scripts/test_runtime_candidate.py +95 -0
@@ -0,0 +1,500 @@
1
+ /**
2
+ * MotionLoom onboarding wizard.
3
+ * Style: Timeline Desk — one clear path, explicit states, no hidden side effects.
4
+ * The wizard is intentionally Node-only at the entrypoint so npx can explain
5
+ * missing Python before delegating to the canonical Python contracts.
6
+ */
7
+ import { createRequire } from "node:module";
8
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { dirname, join, relative, resolve } from "node:path";
10
+ import { spawnSync } from "node:child_process";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
14
+ const PACKAGE_JSON_PATH = join(PACKAGE_ROOT, "package.json");
15
+ const PACKAGE = JSON.parse(readFileSync(PACKAGE_JSON_PATH, "utf8"));
16
+ const VERSION = PACKAGE.version;
17
+ const PACKAGE_NAME = PACKAGE.name;
18
+ const MARKER_START = "<!-- MOTIONLOOM:START -->";
19
+ const MARKER_END = "<!-- MOTIONLOOM:END -->";
20
+
21
+ function usage() {
22
+ console.log(`MotionLoom ${VERSION} — quick start for project-aware animation
23
+
24
+ Start here:
25
+ motionloom init [options] One safe setup for the current project
26
+ motionloom status [options] Short, read-only project readiness check
27
+ motionloom doctor Validate the installed package
28
+
29
+ Maintenance:
30
+ motionloom setup [options] Alias for init (kept for compatibility)
31
+ motionloom repair [options] Re-apply only missing managed pieces
32
+
33
+ Options:
34
+ --project-root <path> Host project (default: current directory)
35
+ --package-manager <name> auto, npm, pnpm or yarn (default: auto)
36
+ --motionloom-root <path> Use a local checkout instead of node_modules
37
+ --dry-run Preview commands and file changes only
38
+ --skip-install Do not install the npm package
39
+ --skip-memory Do not analyze or initialize Project Memory
40
+ --no-router Do not create or update AGENTS.md
41
+ --yes Accept safe defaults without prompting
42
+ --json Emit machine-readable JSON
43
+ -h, --help Show this help
44
+
45
+ Safe defaults:
46
+ - local devDependency, never a global install
47
+ - idempotent AGENTS.md merge, never overwrite existing project guidance
48
+ - Project Memory stays bound to the current project root
49
+ - no commit, push, PR, approval or production promotion
50
+
51
+ No asset or runtime gate runs during init. Choose an animation task first;
52
+ the installed Agent router will load the relevant MotionLoom workflow then.
53
+ `);
54
+ }
55
+
56
+ function parseArgs(argv) {
57
+ const args = [...argv];
58
+ let action = "setup";
59
+ if (["setup", "init", "status", "repair"].includes(args[0])) action = args.shift();
60
+ const options = {
61
+ action,
62
+ projectRoot: process.cwd(),
63
+ packageManager: "auto",
64
+ motionloomRoot: null,
65
+ dryRun: false,
66
+ skipInstall: false,
67
+ skipMemory: false,
68
+ noRouter: false,
69
+ yes: false,
70
+ json: false,
71
+ };
72
+
73
+ for (let index = 0; index < args.length; index += 1) {
74
+ const arg = args[index];
75
+ if (arg === "-h" || arg === "--help") {
76
+ options.help = true;
77
+ continue;
78
+ }
79
+ if (arg === "--dry-run") {
80
+ options.dryRun = true;
81
+ continue;
82
+ }
83
+ if (arg === "--skip-install") {
84
+ options.skipInstall = true;
85
+ continue;
86
+ }
87
+ if (arg === "--skip-memory") {
88
+ options.skipMemory = true;
89
+ continue;
90
+ }
91
+ if (arg === "--no-router") {
92
+ options.noRouter = true;
93
+ continue;
94
+ }
95
+ if (arg === "--yes") {
96
+ options.yes = true;
97
+ continue;
98
+ }
99
+ if (arg === "--json") {
100
+ options.json = true;
101
+ continue;
102
+ }
103
+ const next = args[index + 1];
104
+ if (["--project-root", "--package-manager", "--motionloom-root"].includes(arg)) {
105
+ if (!next || next.startsWith("--")) throw new Error(`${arg} requires a value`);
106
+ index += 1;
107
+ if (arg === "--project-root") options.projectRoot = next;
108
+ if (arg === "--package-manager") options.packageManager = next;
109
+ if (arg === "--motionloom-root") options.motionloomRoot = next;
110
+ continue;
111
+ }
112
+ throw new Error(`unknown option: ${arg}`);
113
+ }
114
+ return options;
115
+ }
116
+
117
+ function readJson(path) {
118
+ try {
119
+ return JSON.parse(readFileSync(path, "utf8"));
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
124
+
125
+ function projectPackage(root) {
126
+ return readJson(join(root, "package.json"));
127
+ }
128
+
129
+ function detectProjectKind(packageJson) {
130
+ const dependencies = {
131
+ ...(packageJson?.dependencies || {}),
132
+ ...(packageJson?.devDependencies || {}),
133
+ };
134
+ if (dependencies.next) return "Next.js";
135
+ if (dependencies["@angular/core"]) return "Angular";
136
+ if (dependencies.vue || dependencies.nuxt) return "Vue";
137
+ if (dependencies.svelte || dependencies["@sveltejs/kit"]) return "Svelte";
138
+ if (dependencies.phaser) return "Phaser";
139
+ if (dependencies["pixi.js"] || dependencies["@pixi/react"]) return "PixiJS";
140
+ if (dependencies["@babylonjs/core"]) return "Babylon.js";
141
+ if (dependencies.three || dependencies["@react-three/fiber"]) return "Three.js";
142
+ if (dependencies.react || dependencies["react-dom"]) return "React";
143
+ return "Node / unclassified";
144
+ }
145
+
146
+ function hasDependency(packageJson) {
147
+ if (!packageJson) return false;
148
+ return Boolean(
149
+ packageJson.dependencies?.[PACKAGE_NAME] ||
150
+ packageJson.devDependencies?.[PACKAGE_NAME] ||
151
+ packageJson.optionalDependencies?.[PACKAGE_NAME],
152
+ );
153
+ }
154
+
155
+ function detectPackageManager(root, requested) {
156
+ if (requested !== "auto") return requested;
157
+ if (existsSync(join(root, "pnpm-lock.yaml"))) return "pnpm";
158
+ if (existsSync(join(root, "yarn.lock"))) return "yarn";
159
+ if (existsSync(join(root, "package-lock.json"))) return "npm";
160
+ const packageJson = projectPackage(root);
161
+ const declared = String(packageJson?.packageManager || "").split("@")[0];
162
+ return ["npm", "pnpm", "yarn"].includes(declared) ? declared : "npm";
163
+ }
164
+
165
+ function installCommand(manager) {
166
+ if (manager === "pnpm") return ["pnpm", ["add", "--save-dev", `${PACKAGE_NAME}@${VERSION}`]];
167
+ if (manager === "yarn") return ["yarn", ["add", "--dev", `${PACKAGE_NAME}@${VERSION}`]];
168
+ return ["npm", ["install", "--save-dev", `${PACKAGE_NAME}@${VERSION}`]];
169
+ }
170
+
171
+ function run(program, args, cwd, capture = false) {
172
+ const result = spawnSync(program, args, {
173
+ cwd,
174
+ encoding: "utf8",
175
+ stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit",
176
+ env: process.env,
177
+ });
178
+ return {
179
+ ok: !result.error && result.status === 0,
180
+ status: result.status ?? 1,
181
+ error: result.error?.message || null,
182
+ stdout: result.stdout || "",
183
+ stderr: result.stderr || "",
184
+ };
185
+ }
186
+
187
+ function pythonRuntime() {
188
+ const candidates = process.platform === "win32" ? ["python", "python3"] : ["python3", "python"];
189
+ for (const executable of candidates) {
190
+ const result = run(executable, ["--version"], process.cwd(), true);
191
+ if (!result.error && result.status === 0) {
192
+ const version = `${result.stdout}\n${result.stderr}`.match(/Python\s+(\d+)\.(\d+)\.(\d+)/i);
193
+ if (version) {
194
+ const major = Number(version[1]);
195
+ const minor = Number(version[2]);
196
+ return { executable, version: `${major}.${minor}.${version[3]}`, supported: major > 3 || (major === 3 && minor >= 11) };
197
+ }
198
+ }
199
+ }
200
+ return { executable: null, version: null, supported: false };
201
+ }
202
+
203
+ function runtimeChecks() {
204
+ const nodeMajor = Number(process.versions.node.split(".")[0]);
205
+ const python = pythonRuntime();
206
+ return {
207
+ node: { version: process.versions.node, supported: nodeMajor >= 18 },
208
+ python,
209
+ };
210
+ }
211
+
212
+ function resolveMotionLoomRoot(projectRoot, explicitRoot) {
213
+ if (explicitRoot) {
214
+ const root = resolve(explicitRoot);
215
+ return existsSync(join(root, "bin", "motionloom.mjs")) ? root : null;
216
+ }
217
+ const hostPackage = projectPackage(projectRoot);
218
+ if (hostPackage?.name === PACKAGE_NAME && existsSync(join(projectRoot, "bin", "motionloom.mjs"))) {
219
+ return projectRoot;
220
+ }
221
+ const local = join(projectRoot, "node_modules", PACKAGE_NAME);
222
+ if (existsSync(join(local, "bin", "motionloom.mjs"))) return resolve(local);
223
+ try {
224
+ const require = createRequire(import.meta.url);
225
+ const packageJson = require.resolve(`${PACKAGE_NAME}/package.json`, { paths: [projectRoot] });
226
+ const root = dirname(packageJson);
227
+ return existsSync(join(root, "bin", "motionloom.mjs")) ? root : null;
228
+ } catch {
229
+ return null;
230
+ }
231
+ }
232
+
233
+ function localCli(motionloomRoot, args, projectRoot, capture = false) {
234
+ return run(process.execPath, [join(motionloomRoot, "bin", "motionloom.mjs"), ...args], projectRoot, capture);
235
+ }
236
+
237
+ function routerBlock() {
238
+ return `${MARKER_START}
239
+ ## MotionLoom
240
+
241
+ When a task involves animation, motion, assets, runtime rendering or Dev Lab review:
242
+
243
+ - Read the installed MotionLoom package's canonical \`SKILL.md\` and \`agent-card.json\` first; do not copy them into the host project.
244
+ - Run \`npx --no-install motionloom status --json\` before planning animation work.
245
+ - Keep project context and \`.motionloom/project-memory.json\` bound to this project; never copy them from another project.
246
+ - Continue normal product work without running MotionLoom gates until the task actually includes animation or an animation asset.
247
+ - For an animation task, follow the applicable canonical workflow; use ingest/runtime checks only when importing assets or testing a target runtime.
248
+ - After rendering, open the MotionLoom Dev Lab candidate for user review before any PR handoff. Evidence never implies user approval.
249
+ - Keep Git side effects local-only until the user explicitly confirms commit, push or PR.
250
+
251
+ ${MARKER_END}`;
252
+ }
253
+
254
+ function routerState(projectRoot) {
255
+ const path = join(projectRoot, "AGENTS.md");
256
+ if (!existsSync(path)) return { status: "missing", path };
257
+ const text = readFileSync(path, "utf8");
258
+ if (text.includes(MARKER_START) && text.includes(MARKER_END)) return { status: "managed", path };
259
+ if (/^##\s+MotionLoom\s*$/im.test(text)) return { status: "unmarked", path };
260
+ return { status: "absent", path };
261
+ }
262
+
263
+ function ensureRouter(projectRoot, dryRun) {
264
+ const state = routerState(projectRoot);
265
+ const path = state.path;
266
+ if (state.status === "managed") {
267
+ const text = readFileSync(path, "utf8");
268
+ const pattern = new RegExp(`${MARKER_START}[\\s\\S]*?${MARKER_END}`, "m");
269
+ const updated = text.replace(pattern, routerBlock());
270
+ if (updated !== text && !dryRun) writeFileSync(path, updated, "utf8");
271
+ return { ...state, action: updated !== text ? (dryRun ? "would_update" : "updated") : "unchanged" };
272
+ }
273
+ if (state.status === "unmarked") return { ...state, action: "manual_review_required" };
274
+ const prefix = existsSync(path) ? `\n\n${routerBlock()}\n` : `${routerBlock()}\n`;
275
+ if (!dryRun) writeFileSync(path, prefix, { encoding: "utf8", flag: existsSync(path) ? "a" : "w" });
276
+ return { ...state, action: dryRun ? "would_create" : "created" };
277
+ }
278
+
279
+ function checkMemory(motionloomRoot, projectRoot) {
280
+ const result = localCli(motionloomRoot, ["memory", "validate", "--project-root", projectRoot, "--json"], projectRoot, true);
281
+ let payload = null;
282
+ try {
283
+ payload = JSON.parse(result.stdout);
284
+ } catch {
285
+ payload = { raw: result.stdout.trim() };
286
+ }
287
+ return { ok: result.ok, payload, stderr: result.stderr.trim() };
288
+ }
289
+
290
+ function checkDiscovery(motionloomRoot) {
291
+ const result = localCli(motionloomRoot, ["discovery", "check", "--root", motionloomRoot, "--json"], motionloomRoot, true);
292
+ let payload = null;
293
+ try {
294
+ payload = JSON.parse(result.stdout);
295
+ } catch {
296
+ payload = { raw: result.stdout.trim() };
297
+ }
298
+ return { ok: result.ok, payload, stderr: result.stderr.trim() };
299
+ }
300
+
301
+ function statusReport(options) {
302
+ const projectRoot = resolve(options.projectRoot);
303
+ const packageJson = projectPackage(projectRoot);
304
+ const motionloomRoot = resolveMotionLoomRoot(projectRoot, options.motionloomRoot);
305
+ const runtime = runtimeChecks();
306
+ const router = routerState(projectRoot);
307
+ const context = existsSync(join(projectRoot, "project-context.json"));
308
+ const memory = existsSync(join(projectRoot, ".motionloom", "project-memory.json"));
309
+ const memoryCheck = motionloomRoot && memory ? checkMemory(motionloomRoot, projectRoot) : { ok: false, payload: null };
310
+ const discovery = motionloomRoot ? checkDiscovery(motionloomRoot) : { ok: false, payload: null };
311
+ const packageInstalled = Boolean(motionloomRoot && existsSync(join(motionloomRoot, "package.json")));
312
+ const needsReview = Boolean(memory && !memoryCheck.ok);
313
+ const ready = Boolean(
314
+ packageInstalled &&
315
+ runtime.node.supported &&
316
+ runtime.python.supported &&
317
+ discovery.ok &&
318
+ ["managed"].includes(router.status) &&
319
+ context &&
320
+ memory &&
321
+ memoryCheck.ok,
322
+ );
323
+ const status = ready ? "ready" : needsReview ? "needs_review" : "needs_setup";
324
+ return {
325
+ status,
326
+ project_root: projectRoot,
327
+ package: {
328
+ declared: hasDependency(packageJson),
329
+ installed: packageInstalled,
330
+ version: motionloomRoot ? readJson(join(motionloomRoot, "package.json"))?.version || null : null,
331
+ root: motionloomRoot,
332
+ },
333
+ runtime,
334
+ router,
335
+ context: { path: join(projectRoot, "project-context.json"), exists: context },
336
+ memory: { path: join(projectRoot, ".motionloom", "project-memory.json"), exists: memory, validation: memoryCheck },
337
+ discovery,
338
+ quick_start: {
339
+ project_kind: detectProjectKind(packageJson),
340
+ gates_active: false,
341
+ next_action: "Continue normal work. Start an animation task when you are ready; MotionLoom will then load only the relevant workflow.",
342
+ },
343
+ };
344
+ }
345
+
346
+ function printStatus(report, json) {
347
+ if (json) {
348
+ console.log(JSON.stringify(report, null, 2));
349
+ return;
350
+ }
351
+ const label = report.status === "ready" ? "READY" : report.status === "needs_review" ? "NEEDS REVIEW" : "NEEDS SETUP";
352
+ console.log(`MotionLoom: ${label} · ${report.quick_start.project_kind}`);
353
+ if (report.status === "ready") {
354
+ console.log("Project memory and Agent routing are ready. No animation gate is active.");
355
+ console.log("Next: continue normal work. When you start animation, ask the Agent to use MotionLoom.");
356
+ return;
357
+ }
358
+ console.log(`Missing setup pieces: package ${report.package.installed ? "ready" : "missing"}, memory ${report.memory.exists ? "present" : "missing"}, router ${report.router.status}.`);
359
+ console.log("Next: npx --yes motionloom init");
360
+ }
361
+
362
+ function setup(options) {
363
+ const projectRoot = resolve(options.projectRoot);
364
+ const packageJson = projectPackage(projectRoot);
365
+ const result = {
366
+ command: options.action,
367
+ onboarding: "quick_start",
368
+ status: "blocked",
369
+ project_root: projectRoot,
370
+ project_kind: detectProjectKind(packageJson),
371
+ dry_run: options.dryRun,
372
+ changed: [],
373
+ planned: [],
374
+ warnings: [],
375
+ errors: [],
376
+ };
377
+ if (!packageJson) {
378
+ result.errors.push("package.json is missing; run this command from the root of a Node project or pass --project-root");
379
+ return result;
380
+ }
381
+ const runtime = runtimeChecks();
382
+ if (!runtime.node.supported) result.errors.push(`Node.js ${runtime.node.version} is unsupported; MotionLoom requires Node.js 18+`);
383
+ if (!runtime.python.supported) result.errors.push("Python 3.11+ was not found; install Python and ensure it is on PATH");
384
+ if (result.errors.length) {
385
+ result.runtime = runtime;
386
+ return result;
387
+ }
388
+
389
+ const manager = detectPackageManager(projectRoot, options.packageManager);
390
+ if (!["npm", "pnpm", "yarn"].includes(manager)) {
391
+ result.errors.push(`unsupported package manager: ${manager}`);
392
+ return result;
393
+ }
394
+ result.package_manager = manager;
395
+ let motionloomRoot = resolveMotionLoomRoot(projectRoot, options.motionloomRoot);
396
+ const needsInstall = !motionloomRoot || !hasDependency(packageJson);
397
+ if (needsInstall && options.skipInstall) {
398
+ result.errors.push("MotionLoom is not installed in this project; remove --skip-install or install the local devDependency first");
399
+ return result;
400
+ }
401
+ if (needsInstall) {
402
+ const [program, args] = installCommand(manager);
403
+ result.planned.push({ step: "install", command: [program, ...args] });
404
+ if (!options.dryRun) {
405
+ const install = run(program, args, projectRoot, options.json);
406
+ if (!install.ok) {
407
+ const detail = install.stderr.trim() || install.stdout.trim();
408
+ result.errors.push(detail || install.error || `package installation failed with exit code ${install.status}`);
409
+ return result;
410
+ }
411
+ result.changed.push("installed local MotionLoom devDependency");
412
+ motionloomRoot = resolveMotionLoomRoot(projectRoot, options.motionloomRoot);
413
+ }
414
+ } else {
415
+ result.planned.push({ step: "install", action: "already_installed" });
416
+ }
417
+ if (!motionloomRoot && !options.dryRun) {
418
+ result.errors.push("MotionLoom package root could not be resolved after installation");
419
+ return result;
420
+ }
421
+
422
+ if (!options.noRouter) {
423
+ const router = ensureRouter(projectRoot, options.dryRun);
424
+ if (router.action === "manual_review_required") {
425
+ result.warnings.push("AGENTS.md already contains an unmarked MotionLoom section; no automatic merge was attempted");
426
+ } else if (router.action.startsWith("would_") || ["created", "updated"].includes(router.action)) {
427
+ result[options.dryRun ? "planned" : "changed"].push({ step: "agent_router", action: router.action, path: relative(projectRoot, router.path) });
428
+ }
429
+ }
430
+
431
+ if (!options.skipMemory && !options.dryRun && motionloomRoot) {
432
+ const contextPath = join(projectRoot, "project-context.json");
433
+ const memoryPath = join(projectRoot, ".motionloom", "project-memory.json");
434
+ let bootstrap;
435
+ if (!existsSync(contextPath)) {
436
+ bootstrap = localCli(motionloomRoot, ["analyze", projectRoot, "--init-memory"], projectRoot, options.json);
437
+ if (bootstrap.ok) result.changed.push("analyzed project and initialized Project Memory");
438
+ } else if (!existsSync(memoryPath)) {
439
+ bootstrap = localCli(motionloomRoot, ["memory", "init", "--project-root", projectRoot, "--context-path", contextPath, "--json"], projectRoot, options.json);
440
+ if (bootstrap.ok) result.changed.push("initialized Project Memory from existing project context");
441
+ }
442
+ if (bootstrap && !bootstrap.ok) result.errors.push("Project Memory bootstrap failed; run `npx motionloom analyze . --init-memory` and inspect the reported project path");
443
+ } else if (!options.skipMemory) {
444
+ result.planned.push({ step: "project_memory", action: "analyze project and initialize .motionloom/project-memory.json" });
445
+ }
446
+
447
+ if (!options.dryRun && motionloomRoot) {
448
+ const report = statusReport({ ...options, projectRoot, motionloomRoot });
449
+ result.status = report.status;
450
+ result.readiness = report;
451
+ if (report.status === "needs_review") result.warnings.push("Project Memory exists but needs review; no destructive repair was attempted");
452
+ if (report.status === "needs_setup") result.warnings.push("Setup completed partially; run `npx motionloom status --json` for the exact missing item");
453
+ } else {
454
+ result.status = "planned";
455
+ result.planned.push({ step: "verification", action: "run discovery check, validate memory and print readiness" });
456
+ }
457
+ return result;
458
+ }
459
+
460
+ function printSetup(result, json) {
461
+ if (json) {
462
+ console.log(JSON.stringify(result, null, 2));
463
+ return;
464
+ }
465
+ const label = result.status === "ready" ? "READY" : result.status === "planned" ? "DRY RUN" : result.status === "needs_review" ? "NEEDS REVIEW" : "BLOCKED";
466
+ console.log(`MotionLoom quick start: ${label} · ${result.project_kind || "project"}`);
467
+ for (const error of result.errors) console.log(` BLOCK ${error}`);
468
+ for (const warning of result.warnings) console.log(` NOTE ${warning}`);
469
+ if (result.status === "ready") {
470
+ console.log("Ready. Project memory is set up and your normal work stays uninterrupted.");
471
+ console.log("When an animation task begins, ask your Agent to use MotionLoom. Asset and runtime checks appear only when relevant.");
472
+ } else if (result.status === "planned") {
473
+ console.log("Dry run only: rerun without --dry-run to apply the same safe quick start.");
474
+ } else {
475
+ console.log("Next: run `npx --no-install motionloom status --json` for details, then resolve the reported setup item.");
476
+ }
477
+ }
478
+
479
+ function main() {
480
+ const options = parseArgs(process.argv.slice(2));
481
+ if (options.help) {
482
+ usage();
483
+ return 0;
484
+ }
485
+ if (options.action === "status") {
486
+ const report = statusReport(options);
487
+ printStatus(report, options.json);
488
+ return report.status === "ready" ? 0 : report.status === "needs_review" ? 10 : 11;
489
+ }
490
+ const result = setup(options);
491
+ printSetup(result, options.json);
492
+ return ["ready", "planned"].includes(result.status) ? 0 : result.status === "needs_review" ? 10 : 11;
493
+ }
494
+
495
+ try {
496
+ process.exitCode = main();
497
+ } catch (error) {
498
+ console.error(`MotionLoom onboarding failed: ${error instanceof Error ? error.message : String(error)}`);
499
+ process.exitCode = 2;
500
+ }
@@ -22,6 +22,7 @@ REQUIRED_SCRIPT_FILES = [
22
22
  "scripts/project_memory_loader.py",
23
23
  "scripts/analyze.py",
24
24
  "scripts/devlab.py",
25
+ "scripts/setup.mjs",
25
26
  ]
26
27
  REQUIRED_SCHEMAS = [
27
28
  "task.schema.json",
@@ -131,7 +132,7 @@ def run() -> int:
131
132
  package_path = ROOT / "package.json"
132
133
  try:
133
134
  package = json.loads(package_path.read_text(encoding="utf-8"))
134
- for script in ("test", "validate", "doctor", "report", "report:check", "review", "memory:bootstrap", "memory:recover", "memory:validate", "devlab", "pack:dotlottie"):
135
+ for script in ("test", "validate", "doctor", "setup", "setup:dry", "status", "repair", "report", "report:check", "review", "memory:bootstrap", "memory:recover", "memory:validate", "devlab", "pack:dotlottie"):
135
136
  if script not in package.get("scripts", {}):
136
137
  warnings.append({"code": "missing_package_script", "message": f"package.json has no {script} script."})
137
138
  except (FileNotFoundError, json.JSONDecodeError) as exc:
@@ -0,0 +1,77 @@
1
+ {
2
+ "schema_version": "1.0",
3
+ "provenance_id": "browser-review-smoke-asset-provenance",
4
+ "task_id": "browser-review-smoke-task",
5
+ "scene": "browser-review-smoke",
6
+ "created_at": "2026-08-14T00:00:00Z",
7
+ "asset": {
8
+ "id": "browser-review-smoke-animation",
9
+ "path": "animation.json",
10
+ "type": "deterministic-fixture",
11
+ "framework": "lottie",
12
+ "version": "fixture-2.2.0"
13
+ },
14
+ "authority": "ai_assisted_human_reviewed",
15
+ "readiness": "production_eligible",
16
+ "generator": {
17
+ "model": "motionloom-deterministic-fixture-builder",
18
+ "task_id": "browser-review-smoke-task",
19
+ "source": "repository-fixture",
20
+ "generated_at": "2026-08-14T00:00:00Z",
21
+ "agent": "motionloom-ci"
22
+ },
23
+ "human_review": {
24
+ "reviewer": "MotionLoom fixture maintainer",
25
+ "decision": "approved",
26
+ "scope": "deterministic runtime and contract fixture, not production art approval",
27
+ "reviewed_at": "2026-08-14T00:00:00Z",
28
+ "user_confirmed": true,
29
+ "notes": "The fixture is eligible for repository CI and PR evidence; final product approval remains outside this artifact."
30
+ },
31
+ "license": {
32
+ "spdx": "MIT",
33
+ "source": "MotionLoom repository fixture",
34
+ "attribution": "MotionLoom deterministic browser-review smoke fixture."
35
+ },
36
+ "files": [
37
+ {
38
+ "path": "animation.json",
39
+ "role": "runtime-scene",
40
+ "sha256": "c5ab312427678b17ae903d280d89db9d202670f031f10ad6fe774fc9aaecee8a"
41
+ }
42
+ ],
43
+ "provenance_chain": [
44
+ {
45
+ "step": "generate",
46
+ "actor": "agent:motionloom-ci",
47
+ "source": "repository-fixture",
48
+ "timestamp": "2026-08-14T00:00:00Z"
49
+ },
50
+ {
51
+ "step": "runtime-test",
52
+ "actor": "runtime:lottie",
53
+ "source": "browser-review-smoke runtime evidence",
54
+ "timestamp": "2026-08-14T00:00:00Z"
55
+ },
56
+ {
57
+ "step": "human-review",
58
+ "actor": "user:fixture-maintainer",
59
+ "source": "deterministic fixture review boundary",
60
+ "timestamp": "2026-08-14T00:00:00Z"
61
+ }
62
+ ],
63
+ "runtime_evidence": {
64
+ "status": "pass",
65
+ "runtime": "lottie-runtime",
66
+ "tested_at": "2026-08-14T00:00:00Z",
67
+ "evidence_path": "snapshot/.render-meta.json"
68
+ },
69
+ "full_gate": {
70
+ "status": "pass",
71
+ "quality_gate": "pass",
72
+ "visual_truth": "pass",
73
+ "license": "pass",
74
+ "checked_at": "2026-08-14T00:00:00Z",
75
+ "report_path": "artifacts/browser-review-smoke-task/quality-report.json"
76
+ }
77
+ }
@@ -5,6 +5,7 @@
5
5
  "category": "loading",
6
6
  "file": "animation.json",
7
7
  "visual_truth": "visual-truth.json",
8
+ "asset_provenance": "asset-provenance.json",
8
9
  "source_binding": {
9
10
  "kind": "fixture",
10
11
  "source_path": "animation.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": "1.0",
3
3
  "contract": "motionloom-visual-truth",
4
- "generated_at": "2026-08-13T13:53:38.679496Z",
4
+ "generated_at": "2026-08-13T17:17:31.327320Z",
5
5
  "status": "review_required",
6
6
  "scene": "browser-review-smoke",
7
7
  "task_id": "browser-review-smoke-task",
@@ -51,9 +51,9 @@
51
51
  "source_path": "src/output/browser-review-smoke/animation.json",
52
52
  "source_sha256": "c5ab312427678b17ae903d280d89db9d202670f031f10ad6fe774fc9aaecee8a",
53
53
  "manifest_path": "src/output/browser-review-smoke/manifest.json",
54
- "manifest_sha256": "467fea255a18efb8687e78a1a60662ad30d4fa6f77d9c3f3878196f3c5e670ad",
54
+ "manifest_sha256": "8d1e07c81dbbcbcc060415ac71dfcae7ce88f2e7aa91347b226dbf4f554e5623",
55
55
  "runtime_evidence_path": "artifacts/browser-review-smoke-task/runtime-adapters/runtime-evidence.json",
56
- "runtime_evidence_sha256": "2d51a37dbd1962c3f8539fed53fd5709448da55f2b00f2ceb37dfd8a55d0d716",
56
+ "runtime_evidence_sha256": "88194b8ed8e8eb9780a80bc55114b9cf52886c911b70a7f4331c18fa39a9b090",
57
57
  "motion_ir_path": "artifacts/browser-review-smoke-task/motion-ir.json",
58
58
  "motion_ir_sha256": "bdb2305737e5dee9b9ee078dd5657a4a5717bf4184f112a1d01ac82361c5a23f",
59
59
  "runtime_status": "pass",
@@ -0,0 +1,38 @@
1
+ {
2
+ "schema_version": "1.0",
3
+ "provenance_id": "runtime-pilot-framer-scene-provenance",
4
+ "task_id": "runtime-pilot-001",
5
+ "scene": "runtime-pilot-framer",
6
+ "created_at": "2026-08-15T00:00:00Z",
7
+ "asset": {
8
+ "id": "runtime/framer-motion-pilot",
9
+ "path": "scene.jsx",
10
+ "type": "runtime_scene",
11
+ "framework": "framer-motion",
12
+ "version": "13.1.0"
13
+ },
14
+ "authority": "code_authored",
15
+ "readiness": "review_required",
16
+ "license": {
17
+ "spdx": "MIT",
18
+ "source": "MotionLoom repository source",
19
+ "attribution": "MotionLoom contributors"
20
+ },
21
+ "files": [
22
+ {
23
+ "path": "scene.jsx",
24
+ "role": "runtime-source",
25
+ "sha256": "f4c0e56de842cae6b234566b556e4f07154f8109dde382a474218a9de8b45a8b",
26
+ "bytes": 1769
27
+ }
28
+ ],
29
+ "provenance_chain": [
30
+ {
31
+ "step": "code-authoring",
32
+ "actor": "MotionLoom runtime-pilot author",
33
+ "source": "scene.jsx",
34
+ "timestamp": "2026-08-15T00:00:00Z",
35
+ "sha256": "f4c0e56de842cae6b234566b556e4f07154f8109dde382a474218a9de8b45a8b"
36
+ }
37
+ ]
38
+ }