arkaos 4.36.0 → 4.38.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 (59) hide show
  1. package/README.md +1 -1
  2. package/THE-ARKAOS-GUIDE.md +1 -1
  3. package/VERSION +1 -1
  4. package/arka/SKILL.md +1 -1
  5. package/config/skills-curated.yaml +2 -2
  6. package/config/skills-provenance.yaml +7 -0
  7. package/core/hooks/session_start.py +17 -3
  8. package/core/memory/semantic_store.py +58 -11
  9. package/core/memory/turn_capture.py +56 -11
  10. package/core/runtime/opencode.py +16 -5
  11. package/core/runtime/opencode_hooks.py +498 -0
  12. package/departments/dev/skills/animated-website/SKILL.md +4 -2
  13. package/departments/dev/skills/scroll-world/SKILL.md +619 -0
  14. package/departments/dev/skills/scroll-world/references/index-template.html +73 -0
  15. package/departments/dev/skills/scroll-world/references/knockout.py +89 -0
  16. package/departments/dev/skills/scroll-world/references/pipeline.md +213 -0
  17. package/departments/dev/skills/scroll-world/references/prompts.md +149 -0
  18. package/departments/dev/skills/scroll-world/references/scroll-world.LICENSE +21 -0
  19. package/departments/dev/skills/scroll-world/references/scrub-engine.js +448 -0
  20. package/harness/codex/AGENTS.md +1 -1
  21. package/harness/copilot/copilot-instructions.md +1 -1
  22. package/harness/cursor/rules/arkaos.mdc +2 -2
  23. package/harness/gemini/GEMINI.md +1 -1
  24. package/harness/opencode/AGENTS.md +1 -1
  25. package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
  26. package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
  27. package/harness/opencode/agents/arka-cfo-helena.md +1 -1
  28. package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
  29. package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
  30. package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
  31. package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
  32. package/harness/opencode/agents/arka-coo-sofia.md +1 -1
  33. package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
  34. package/harness/opencode/agents/arka-cqo-marta.md +1 -1
  35. package/harness/opencode/agents/arka-cto-marco.md +1 -1
  36. package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
  37. package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
  38. package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
  39. package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
  40. package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
  41. package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
  42. package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
  43. package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
  44. package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
  45. package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
  46. package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
  47. package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
  48. package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
  49. package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
  50. package/harness/opencode/agents-meta.json +27 -0
  51. package/harness/opencode/opencode.json +23 -0
  52. package/harness/opencode/plugins/arka.ts +128 -0
  53. package/harness/zed/.rules +1 -1
  54. package/installer/adapters/opencode.js +142 -16
  55. package/installer/assets/opencode/arka.ts +128 -0
  56. package/knowledge/skills-manifest.json +14 -1
  57. package/package.json +1 -1
  58. package/pyproject.toml +1 -1
  59. package/scripts/harness_gen.py +43 -3
@@ -0,0 +1,498 @@
1
+ """OpenCode plugin bridge — JSON stdin/stdout hook adapter.
2
+
3
+ OpenCode has no shell-hook chain like Claude Code; its plugin system
4
+ (TypeScript, ``~/.config/opencode/plugins/``) emits events instead. The
5
+ ``arka.ts`` plugin shells out to this module so the OpenCode runtime
6
+ gets the same governance stack as the Claude runtime:
7
+
8
+ prompt token-hygiene checks (UserPromptSubmit parity)
9
+ pre_tool research gate + frontend gate (PreToolUse parity)
10
+ post_tool MCP usage telemetry (PostToolUse parity)
11
+ idle kb-citation + [arka:meta] compliance (Stop-hook parity)
12
+ + detached turn capture into the shared cross-runtime
13
+ session memory (core.memory.turn_capture capture-text)
14
+ memory L9.5 parity — cross-runtime session memory retrieval +
15
+ once-per-session handoff digest on the prompt hot path
16
+ compact gate state context (PreCompact parity)
17
+
18
+ CLI contract::
19
+
20
+ echo '{"action": "pre_tool", ...}' | arka-py -m core.runtime.opencode_hooks
21
+
22
+ Output is a single JSON object on stdout. The module NEVER raises and
23
+ always exits 0 — a hook bridge must never break a turn (fail-open,
24
+ same posture as the bash hooks it mirrors).
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import os
31
+ import re
32
+ import subprocess
33
+ import sys
34
+ from datetime import datetime, timezone
35
+ from pathlib import Path
36
+
37
+ _TELEMETRY_DIR = Path.home() / ".arkaos" / "telemetry"
38
+ _PROMPT_CACHE_DIR = Path.home() / ".arkaos" / "context-cache"
39
+
40
+ _BUILTIN_MAP = {
41
+ "webfetch": "WebFetch",
42
+ "websearch": "WebSearch",
43
+ "edit": "Edit",
44
+ "write": "Write",
45
+ "read": "Read",
46
+ "bash": "Bash",
47
+ "grep": "Grep",
48
+ "glob": "Glob",
49
+ "task": "Task",
50
+ }
51
+
52
+ _ARG_KEY_MAP = {
53
+ "filePath": "file_path",
54
+ "newString": "new_string",
55
+ "oldString": "old_string",
56
+ }
57
+
58
+ _VAGUE_RE = re.compile(
59
+ r"\b(fix the bug|that file|esse ficheiro|esse bug|o erro|aquilo)\b",
60
+ re.IGNORECASE,
61
+ )
62
+
63
+ _STOPWORDS = frozenset({
64
+ "a", "o", "e", "de", "do", "da", "que", "em", "um", "uma", "para",
65
+ "com", "os", "as", "no", "na", "the", "and", "to", "of", "in", "is",
66
+ })
67
+
68
+
69
+ def _map_tool_name(name: str) -> str:
70
+ """OpenCode ``server_tool`` / builtin -> Claude-style tool name."""
71
+ if name in _BUILTIN_MAP:
72
+ return _BUILTIN_MAP[name]
73
+ if name.startswith("mcp__"):
74
+ return name
75
+ server, sep, tool = name.partition("_")
76
+ if sep and server and tool:
77
+ return f"mcp__{server}__{tool}"
78
+ return name
79
+
80
+
81
+ def _map_args(args: dict) -> dict:
82
+ return {_ARG_KEY_MAP.get(k, k): v for k, v in (args or {}).items()}
83
+
84
+
85
+ def _keywords(text: str) -> set[str]:
86
+ return {
87
+ w
88
+ for w in re.findall(r"[a-zA-ZÀ-ÿ]{4,}", text.lower())
89
+ if w not in _STOPWORDS
90
+ }
91
+
92
+
93
+ def _append_jsonl(path: Path, entry: dict) -> None:
94
+ try:
95
+ path.parent.mkdir(parents=True, exist_ok=True)
96
+ with path.open("a", encoding="utf-8") as fh:
97
+ fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
98
+ except OSError:
99
+ pass
100
+
101
+
102
+ def _recent_prompts(session_id: str) -> list[str]:
103
+ safe = re.sub(r"[^A-Za-z0-9_-]", "", session_id or "default")
104
+ path = _PROMPT_CACHE_DIR / f"opencode-prompts-{safe}.json"
105
+ try:
106
+ return json.loads(path.read_text(encoding="utf-8"))
107
+ except (OSError, json.JSONDecodeError):
108
+ return []
109
+
110
+
111
+ def _store_prompt(session_id: str, prompt: str) -> None:
112
+ safe = re.sub(r"[^A-Za-z0-9_-]", "", session_id or "default")
113
+ path = _PROMPT_CACHE_DIR / f"opencode-prompts-{safe}.json"
114
+ history = _recent_prompts(session_id)
115
+ history.append(prompt[:500])
116
+ _append = history[-3:]
117
+ try:
118
+ path.parent.mkdir(parents=True, exist_ok=True)
119
+ path.write_text(json.dumps(_append), encoding="utf-8")
120
+ except OSError:
121
+ pass
122
+
123
+
124
+ def _load_commands_registry() -> list[dict]:
125
+ """knowledge/commands-registry.json from the repo root arka-py runs
126
+ against (PYTHONPATH chain: .repo-path -> dev checkout -> lib)."""
127
+ path = Path(__file__).resolve().parents[2] / "knowledge" / "commands-registry.json"
128
+ try:
129
+ data = json.loads(path.read_text(encoding="utf-8"))
130
+ commands = data.get("commands", [])
131
+ return commands if isinstance(commands, list) else []
132
+ except (OSError, json.JSONDecodeError):
133
+ return []
134
+
135
+
136
+ def _routing_lines(prompt: str, session_id: str, cwd: str) -> list[str]:
137
+ """UserPromptSubmit routing parity (core/hooks/user_prompt_submit.py):
138
+ [ARKA:ROUTE] on every prompt + L1 [dept:X] + L5 [hint:/cmd] +
139
+ [ARKA:WORKFLOW-REQUIRED] on creation/implementation prompts. The
140
+ directive strings and the verb regex are IMPORTED from the claude
141
+ hook — one source of truth, zero drift."""
142
+ from core.hooks.user_prompt_submit import (
143
+ _ROUTE_REMINDER,
144
+ _WF_VERB_RE,
145
+ _WORKFLOW_DIRECTIVE,
146
+ )
147
+
148
+ lines = [_ROUTE_REMINDER.strip()]
149
+ if not prompt:
150
+ return lines
151
+ try:
152
+ from core.synapse.layers import (
153
+ CommandHintsLayer,
154
+ DepartmentLayer,
155
+ PromptContext,
156
+ )
157
+
158
+ ctx = PromptContext(
159
+ user_input=prompt,
160
+ cwd=cwd,
161
+ project_name=Path(cwd).name if cwd else "",
162
+ runtime_id="opencode",
163
+ extra={"session_id": session_id},
164
+ )
165
+ dept = DepartmentLayer().compute(ctx)
166
+ if dept.tag:
167
+ lines.append(dept.tag)
168
+ commands = _load_commands_registry()
169
+ if commands:
170
+ hints = CommandHintsLayer(commands=commands).compute(ctx)
171
+ if hints.tag:
172
+ lines.append(hints.tag)
173
+ except Exception: # noqa: BLE001 — routing hints are a bonus, never a blocker
174
+ pass
175
+ if prompt[0] not in ("/", "!") and _WF_VERB_RE.search(prompt):
176
+ lines.append(_WORKFLOW_DIRECTIVE.strip())
177
+ return lines
178
+
179
+
180
+ def _action_prompt(payload: dict) -> dict:
181
+ """Token-hygiene parity (config/hooks/token-hygiene.sh) + the routing
182
+ injection (UserPromptSubmit parity) that makes /arka optional."""
183
+ prompt = str(payload.get("prompt", ""))
184
+ session_id = str(payload.get("session_id", ""))
185
+ suggestions: list[str] = []
186
+ routing = _routing_lines(prompt, session_id, str(payload.get("cwd", "")))
187
+
188
+ ctx = payload.get("context_pct")
189
+ if isinstance(ctx, (int, float)):
190
+ if ctx > 80:
191
+ suggestions.append(
192
+ f"[arka:warn] Contexto a {int(ctx)}% — considera /compact."
193
+ )
194
+ elif ctx > 60:
195
+ suggestions.append(
196
+ f"[arka:suggest] Contexto a {int(ctx)}% — vigia o orçamento."
197
+ )
198
+
199
+ history = _recent_prompts(session_id)
200
+ if history and prompt:
201
+ current = _keywords(prompt)
202
+ if current:
203
+ recent = set().union(*(_keywords(p) for p in history[-3:]))
204
+ if recent and len(current & recent) / len(current) < 0.3:
205
+ suggestions.append(
206
+ "[arka:suggest] Mudança de tópico — considera /clear "
207
+ "para libertar contexto."
208
+ )
209
+ if prompt:
210
+ _store_prompt(session_id, prompt)
211
+
212
+ if len(prompt) > 2000 and "```" in prompt:
213
+ suggestions.append(
214
+ "[arka:suggest] Paste grande — usa @filepath em vez de colar "
215
+ "código no prompt."
216
+ )
217
+
218
+ if _VAGUE_RE.search(prompt) and "@" not in prompt:
219
+ suggestions.append(
220
+ "[arka:suggest] Referência vaga — usa @path para apontar o "
221
+ "ficheiro concreto."
222
+ )
223
+
224
+ return {"suggestions": suggestions, "routing": routing}
225
+
226
+
227
+ def _action_pre_tool(payload: dict) -> dict:
228
+ tool = str(payload.get("tool", ""))
229
+ mapped = _map_tool_name(tool)
230
+ session_id = str(payload.get("session_id", ""))
231
+ args = _map_args(payload.get("args") or {})
232
+
233
+ from core.workflow import research_gate
234
+
235
+ decision = research_gate.evaluate_research_gate(
236
+ mapped,
237
+ session_id=session_id,
238
+ query=str(args.get("query", "") or args.get("url", "")),
239
+ )
240
+ if not decision.allow:
241
+ research_gate.record_telemetry(session_id, mapped, decision)
242
+ return {
243
+ "allow": False,
244
+ "reason": decision.reason,
245
+ "message": decision.to_stderr_message(),
246
+ }
247
+ if decision.nudge:
248
+ research_gate.record_telemetry(session_id, mapped, decision)
249
+ return {
250
+ "allow": True,
251
+ "reason": decision.reason,
252
+ "message": decision.to_stderr_message(),
253
+ }
254
+
255
+ if tool in ("edit", "write"):
256
+ from core.workflow import frontend_gate
257
+
258
+ fe = frontend_gate.evaluate(
259
+ _map_tool_name(tool),
260
+ "",
261
+ session_id,
262
+ str(payload.get("cwd", "")),
263
+ args,
264
+ messages=payload.get("messages") or [],
265
+ )
266
+ frontend_gate.record_telemetry(session_id, mapped, fe)
267
+ if not fe.allow or fe.reason == "no-design-marker":
268
+ return {
269
+ "allow": fe.allow,
270
+ "reason": fe.reason,
271
+ "message": fe.to_stderr_message(),
272
+ }
273
+
274
+ return {"allow": True, "reason": "ok", "message": ""}
275
+
276
+
277
+ def _action_post_tool(payload: dict) -> dict:
278
+ tool = str(payload.get("tool", ""))
279
+ mapped = _map_tool_name(tool)
280
+ session_id = str(payload.get("session_id", ""))
281
+
282
+ from core.runtime import mcp_telemetry
283
+
284
+ recorded = mcp_telemetry.record(mapped, session_id=session_id)
285
+ _append_jsonl(
286
+ _TELEMETRY_DIR / "opencode-tools.jsonl",
287
+ {
288
+ "ts": datetime.now(timezone.utc).isoformat(),
289
+ "session": session_id,
290
+ "tool": mapped,
291
+ "ok": bool(payload.get("ok", True)),
292
+ },
293
+ )
294
+ return {"recorded_mcp": recorded}
295
+
296
+
297
+ def _enqueue_turn_capture(session_id: str, text: str, cwd: str) -> None:
298
+ """F1-A2 parity: fire-and-forget semantic turn capture — the same
299
+ detached-worker pattern as core/hooks/stop.py, fed with the assistant
300
+ text directly (OpenCode has no Claude-style JSONL transcript)."""
301
+ if not session_id or not text.strip():
302
+ return
303
+ repo = Path(__file__).resolve().parents[2]
304
+ if not (repo / "core" / "memory" / "turn_capture.py").is_file():
305
+ return
306
+ try:
307
+ proc = subprocess.Popen(
308
+ [sys.executable, "-m", "core.memory.turn_capture",
309
+ "capture-text", session_id, cwd or "", "opencode"],
310
+ stdin=subprocess.PIPE,
311
+ stdout=subprocess.DEVNULL,
312
+ stderr=subprocess.DEVNULL,
313
+ env={**os.environ, "PYTHONPATH": str(repo)},
314
+ start_new_session=True,
315
+ )
316
+ if proc.stdin:
317
+ try:
318
+ proc.stdin.write(text.encode("utf-8", "replace"))
319
+ proc.stdin.close()
320
+ except (BrokenPipeError, OSError):
321
+ pass
322
+ except Exception: # noqa: BLE001 — a hook bridge never breaks a turn
323
+ pass
324
+
325
+
326
+ def _action_idle(payload: dict) -> dict:
327
+ """Stop-hook parity: kb-citation + [arka:meta] compliance (soft),
328
+ plus detached turn capture into the shared cross-runtime memory."""
329
+ text = str(payload.get("response_text", ""))
330
+ session_id = str(payload.get("session_id", ""))
331
+ nudges: list[str] = []
332
+
333
+ from core.governance import kb_cite_check
334
+
335
+ result = kb_cite_check.check_citation(text)
336
+ if not result.passed and result.suggestion:
337
+ nudges.append(result.suggestion)
338
+
339
+ if len(text) > 400 and "[arka:meta]" not in text and "[arka:trivial]" not in text:
340
+ nudges.append(
341
+ "[arka:suggest] Resposta substantiva sem tag [arka:meta] — "
342
+ "fecha com kb=N research=X persona=Y gap=Z critic=W."
343
+ )
344
+
345
+ _enqueue_turn_capture(session_id, text, str(payload.get("cwd", "")))
346
+
347
+ _append_jsonl(
348
+ _TELEMETRY_DIR / "opencode-compliance.jsonl",
349
+ {
350
+ "ts": datetime.now(timezone.utc).isoformat(),
351
+ "session": session_id,
352
+ "kb_cite": result.reason,
353
+ "meta_tag": "[arka:meta]" in text,
354
+ "nudges": len(nudges),
355
+ },
356
+ )
357
+ return {"nudges": nudges}
358
+
359
+
360
+ _HANDOFF_MAX_AGE_H = 24
361
+ _HANDOFF_SUMMARY_CHARS = 160
362
+
363
+
364
+ def _handoff_marker(session_id: str) -> Path:
365
+ safe = re.sub(r"[^A-Za-z0-9_-]", "", session_id or "default")
366
+ return _PROMPT_CACHE_DIR / f"opencode-handoff-{safe}.json"
367
+
368
+
369
+ def _handoff_line(session_id: str, project: str) -> str:
370
+ """Once per session: when the newest turn in this project was captured
371
+ by ANOTHER runtime, surface it — that is the cross-runtime handoff."""
372
+ if not project or _handoff_marker(session_id).exists():
373
+ return ""
374
+ from core.memory.semantic_store import (
375
+ SessionMemoryStore,
376
+ default_db_path,
377
+ neutralize_summary,
378
+ )
379
+
380
+ if not default_db_path().is_file():
381
+ return ""
382
+ latest = SessionMemoryStore().cross_runtime_handoff(
383
+ project, "opencode", exclude_session=session_id,
384
+ max_age_h=_HANDOFF_MAX_AGE_H,
385
+ )
386
+ if not latest:
387
+ return ""
388
+ try:
389
+ _handoff_marker(session_id).parent.mkdir(parents=True, exist_ok=True)
390
+ _handoff_marker(session_id).write_text(
391
+ json.dumps({"shown_at": datetime.now(timezone.utc).isoformat()}),
392
+ encoding="utf-8",
393
+ )
394
+ except OSError:
395
+ pass
396
+ summary = neutralize_summary(latest.summary)[:_HANDOFF_SUMMARY_CHARS]
397
+ stamp = latest.ts[11:16] + "Z" if len(latest.ts) >= 16 else latest.ts
398
+ tail = f": {summary}" if summary else ""
399
+ return f"[arka:handoff] última sessão em {latest.runtime} ({stamp}){tail}"
400
+
401
+
402
+ def _action_memory(payload: dict) -> dict:
403
+ """L9.5 parity for the prompt hot path: pre-ranked semantic neighbours
404
+ + live scoped keyword hits from the shared cross-runtime store, plus
405
+ the once-per-session handoff digest. Never embeds — zero cost here."""
406
+ prompt = str(payload.get("prompt", ""))
407
+ session_id = str(payload.get("session_id", ""))
408
+ cwd = str(payload.get("cwd", ""))
409
+ lines: list[str] = []
410
+ try:
411
+ from core.synapse.session_memory_layer import (
412
+ _format_item,
413
+ _l95_feature_flag_on,
414
+ _read_session_cache,
415
+ )
416
+
417
+ if not _l95_feature_flag_on():
418
+ return {"context": []}
419
+ seen: set[str] = set()
420
+ items, ranked_at = _read_session_cache(session_id)
421
+ for item in items:
422
+ line = _format_item(item, ranked_at)
423
+ if line and item.get("summary") not in seen:
424
+ seen.add(item.get("summary"))
425
+ lines.append(line)
426
+ project = Path(cwd).name if cwd else ""
427
+ if prompt and project:
428
+ from core.memory.semantic_store import (
429
+ SessionMemoryStore,
430
+ default_db_path,
431
+ )
432
+
433
+ if default_db_path().is_file():
434
+ hits = SessionMemoryStore().keyword_search(prompt, project, top_k=2)
435
+ for hit in hits:
436
+ line = _format_item(hit)
437
+ if line and hit.get("summary") not in seen:
438
+ seen.add(hit.get("summary"))
439
+ lines.append(line)
440
+ handoff = _handoff_line(session_id, project)
441
+ if handoff:
442
+ lines.insert(0, handoff)
443
+ except Exception: # noqa: BLE001 — memory is a bonus, never a blocker
444
+ return {"context": []}
445
+ return {"context": lines[:6]}
446
+
447
+
448
+ def _action_compact(payload: dict) -> dict:
449
+ """PreCompact parity: carry gate state across compaction."""
450
+ from core.workflow import state
451
+
452
+ context: list[str] = [
453
+ "## ArkaOS gate state (injected by the opencode bridge)",
454
+ "Obrigatório: evidence flow G1 contexto -> G2 plano (aprovação) "
455
+ "-> G3 execução (teste real, exit 0) -> G4 review. Emite "
456
+ "[arka:gate:N] em cada gate; [arka:trivial] <razão> só para "
457
+ "edição de 1 ficheiro < 10 linhas.",
458
+ ]
459
+ try:
460
+ current = state.get_state()
461
+ if current:
462
+ context.append(f"Workflow activo: {json.dumps(current)[:400]}")
463
+ except Exception: # noqa: BLE001 — fail-open
464
+ pass
465
+ return {"context": context}
466
+
467
+
468
+ _ACTIONS = {
469
+ "prompt": _action_prompt,
470
+ "pre_tool": _action_pre_tool,
471
+ "post_tool": _action_post_tool,
472
+ "idle": _action_idle,
473
+ "memory": _action_memory,
474
+ "compact": _action_compact,
475
+ }
476
+
477
+
478
+ def main() -> int:
479
+ try:
480
+ payload = json.loads(sys.stdin.read() or "{}")
481
+ except json.JSONDecodeError:
482
+ payload = {}
483
+ action = str(payload.pop("action", ""))
484
+ handler = _ACTIONS.get(action)
485
+ result: dict
486
+ if handler is None:
487
+ result = {"error": f"unknown action: {action!r}"}
488
+ else:
489
+ try:
490
+ result = handler(payload)
491
+ except Exception as exc: # noqa: BLE001 — fail-open, never block
492
+ result = {"error": f"{type(exc).__name__}: {exc}"}
493
+ sys.stdout.write(json.dumps(result, ensure_ascii=False) + "\n")
494
+ return 0
495
+
496
+
497
+ if __name__ == "__main__":
498
+ raise SystemExit(main())
@@ -8,8 +8,10 @@ description: >
8
8
  TRIGGER: "animated website", "scroll animation", "video to website",
9
9
  "Apple-style page", "site com scroll do vídeo", "transforma este vídeo num
10
10
  site", "frame animation", "/dev animated-website". SKIP: embedding a plain
11
- video player -> just use <video>; regular animated site WITHOUT a video
12
- source -> frontend-design skill; video editing/production -> content/video-produce.
11
+ video player -> just use <video>; generating a scroll-scrubbed 3D world
12
+ FROM SCRATCH via Higgsfield (no existing video source) -> dev/scroll-world;
13
+ regular animated site WITHOUT a video source -> frontend-design skill;
14
+ video editing/production -> content/video-produce.
13
15
  allowed-tools: [Read, Write, Edit, Bash, Grep, Glob, Agent, WebFetch]
14
16
  metadata:
15
17
  origin: community