arkaos 3.76.0 → 3.78.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/VERSION +1 -1
- package/arka/skills/flow/SKILL.md +14 -0
- package/config/constitution.yaml +5 -0
- package/core/governance/__pycache__/activation_tracker.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/design_system_lint.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/design_system_lint_cli.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/dna_fidelity.cpython-313.pyc +0 -0
- package/core/governance/design_system_lint.py +197 -0
- package/core/governance/design_system_lint_cli.py +92 -0
- package/departments/brand/agents/brand-director.yaml +2 -0
- package/departments/brand/agents/creative-director.md +4 -0
- package/departments/brand/agents/motion-designer.md +5 -1
- package/departments/brand/agents/ux-designer.yaml +11 -0
- package/departments/brand/agents/visual-designer.md +4 -0
- package/departments/brand/agents/visual-designer.yaml +11 -0
- package/departments/brand/references/uiux-knowledge-and-tools.md +136 -0
- package/departments/dev/agents/frontend-dev.md +41 -11
- package/departments/dev/agents/frontend-dev.yaml +6 -0
- package/installer/claude-plugins.js +32 -3
- package/installer/doctor.js +15 -0
- package/installer/frontend-tooling.js +150 -0
- package/installer/index.js +28 -0
- package/installer/keys.js +1 -0
- package/installer/update.js +35 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.78.0
|
|
@@ -81,6 +81,20 @@ spec why divergence is justified. Manual audit:
|
|
|
81
81
|
Dispatch specialists via the `Agent` tool. The squad lead from Phase 3
|
|
82
82
|
names them. Specialists run in parallel when work is independent.
|
|
83
83
|
|
|
84
|
+
**Design-system check on UI work (PR6 v3.77.0, SHOULD `design-system-locked`).**
|
|
85
|
+
Before dispatching frontend/landing specialists, run the per-project
|
|
86
|
+
linter to surface UI/UX drift:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
python -m core.governance.design_system_lint_cli <project_path>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
If the project has a `design-system.yaml`, violations come back with
|
|
93
|
+
file:line + the suggestion text declared in the YAML. The frontend
|
|
94
|
+
specialist should fix existing violations OR document why the new work
|
|
95
|
+
diverges. Projects without a `design-system.yaml` skip the check.
|
|
96
|
+
Template: `docs/examples/design-system-example.yaml`.
|
|
97
|
+
|
|
84
98
|
**Experience injection (PR3 v3.74.0).** When a specialist is dispatched,
|
|
85
99
|
Synapse layer `L2.6 AgentExperiences`
|
|
86
100
|
(`core/synapse/agent_experiences_layer.py`) detects the
|
package/config/constitution.yaml
CHANGED
|
@@ -217,6 +217,11 @@ enforcement_levels:
|
|
|
217
217
|
rule: "Bottom-line first output. Lead with answer, then why, then how. Confidence tags on assessments."
|
|
218
218
|
enforcement: "See config/standards/communication.md for full standard"
|
|
219
219
|
|
|
220
|
+
# ─── Rule added in PR6 Squad Intelligence Upgrade (2026-05-28) ───────
|
|
221
|
+
- id: design-system-locked
|
|
222
|
+
rule: "Each project SHOULD declare a `design-system.yaml` at its root listing tokens (colors, spacing, fonts), allowed_components, file_globs to scan, and forbidden_patterns with suggestions. The per-project linter (core.governance.design_system_lint) detects UI/UX drift — hex literals outside the palette, inline style attributes, raw HTML where a Nuxt UI component exists, etc. Adoption is opt-in per project (no YAML at project root → no violations). v3.77.0 is advisory-only; pre-commit hook integration lands in v3.77.x once the rule sets stabilise across the operator's projects."
|
|
223
|
+
enforcement: "Run via `python -m core.governance.design_system_lint_cli <project_path>` (text or JSON output, optional --exit-on-violations). Example template at `docs/examples/design-system-example.yaml`."
|
|
224
|
+
|
|
220
225
|
# ─── Rule added in PR5 Squad Intelligence Upgrade (2026-05-28) ───────
|
|
221
226
|
- id: dna-fidelity-warn
|
|
222
227
|
rule: "Agent outputs are compared against the `signature_markers` block in each agent's YAML at the end of every turn. Forbidden patterns (avoid_patterns) and missing opening phrases (opening_phrases) generate FidelityViolation records in ~/.arkaos/telemetry/dna-fidelity.jsonl. v3.76.0 is soft-warn — violations are recorded for telemetry and operator review, not blocked. Hard-block mode lands later once the marker set is calibrated against real production usage."
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Design System Linter — PR6 Squad Intelligence Upgrade v3.77.0.
|
|
2
|
+
|
|
3
|
+
Scans a project for forbidden patterns declared in its design-system.yaml.
|
|
4
|
+
Opt-in per project: no YAML at root means no violations. Advisory-only in
|
|
5
|
+
v3.77.0; pre-commit hook integration lands in v3.77.x.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import asdict, dataclass, field # noqa: F401 (asdict kept for callers)
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Iterator
|
|
14
|
+
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ─── Dataclasses ──────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class DesignSystem:
|
|
22
|
+
"""Loaded design-system.yaml for a project."""
|
|
23
|
+
|
|
24
|
+
version: int = 1
|
|
25
|
+
project: str = ""
|
|
26
|
+
tokens: dict = field(default_factory=dict)
|
|
27
|
+
allowed_components: list[str] = field(default_factory=list)
|
|
28
|
+
file_globs: list[str] = field(default_factory=list)
|
|
29
|
+
forbidden_patterns: list[dict] = field(default_factory=list)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class DesignViolation:
|
|
34
|
+
file: str # relative to project_path, forward slashes
|
|
35
|
+
line: int # 1-indexed
|
|
36
|
+
pattern: str # the regex string from forbidden_patterns
|
|
37
|
+
suggestion: str # suggestion text from forbidden_patterns
|
|
38
|
+
matched_text: str # the actual matched substring (cap 200 chars)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
def _default_file_globs() -> list[str]:
|
|
44
|
+
return ["**/*.vue", "**/*.tsx", "**/*.jsx"]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _escape_glob_literal(s: str) -> str:
|
|
48
|
+
"""Escape a literal glob segment (no ** inside)."""
|
|
49
|
+
return re.escape(s).replace(r"\*", "[^/]*").replace(r"\?", "[^/]")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _glob_to_regex(glob_pattern: str) -> re.Pattern[str]:
|
|
53
|
+
"""Translate a glob pattern (with ** support) to a compiled regex.
|
|
54
|
+
|
|
55
|
+
Gitignore convention: ``**/`` = zero-or-more ``dir/`` prefixes (including
|
|
56
|
+
zero), so ``**/*.vue`` matches both ``App.vue`` and ``src/App.vue``.
|
|
57
|
+
Bare ``*`` = any non-slash run; ``?`` = one non-slash char.
|
|
58
|
+
"""
|
|
59
|
+
# Split on ** tokens, keeping them as delimiters.
|
|
60
|
+
tokens = re.split(r"(\*\*)", glob_pattern)
|
|
61
|
+
result = ""
|
|
62
|
+
for i, tok in enumerate(tokens):
|
|
63
|
+
if tok != "**":
|
|
64
|
+
result += _escape_glob_literal(tok)
|
|
65
|
+
continue
|
|
66
|
+
nxt = tokens[i + 1] if i + 1 < len(tokens) else ""
|
|
67
|
+
if nxt.startswith("/"):
|
|
68
|
+
# **/ → consume the slash, emit zero-or-more dir/ groups.
|
|
69
|
+
tokens[i + 1] = nxt[1:]
|
|
70
|
+
result += "(?:[^/]+/)*"
|
|
71
|
+
else:
|
|
72
|
+
# trailing ** or ** not followed by / → match any remaining path.
|
|
73
|
+
result += ".*"
|
|
74
|
+
return re.compile(f"^{result}$")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _glob_match(pattern: str, rel_path: str) -> bool:
|
|
78
|
+
"""Return True when rel_path (forward slashes) matches the glob pattern."""
|
|
79
|
+
try:
|
|
80
|
+
return bool(_glob_to_regex(pattern).match(rel_path))
|
|
81
|
+
except re.error:
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _iter_matching_files(
|
|
86
|
+
project_path: Path, file_globs: list[str]
|
|
87
|
+
) -> Iterator[Path]:
|
|
88
|
+
"""Yield all files under project_path matching any glob in file_globs."""
|
|
89
|
+
seen: set[Path] = set()
|
|
90
|
+
for glob in file_globs:
|
|
91
|
+
for path in project_path.rglob("*"):
|
|
92
|
+
if not path.is_file():
|
|
93
|
+
continue
|
|
94
|
+
rel = path.relative_to(project_path).as_posix()
|
|
95
|
+
if _glob_match(glob, rel) and path not in seen:
|
|
96
|
+
seen.add(path)
|
|
97
|
+
yield path
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _is_excluded(rel_path: str, exclude_paths: list[str]) -> bool:
|
|
101
|
+
"""Return True if rel_path matches any exclude_paths glob or is design-system.yaml."""
|
|
102
|
+
if rel_path == "design-system.yaml":
|
|
103
|
+
return True
|
|
104
|
+
return any(_glob_match(exc, rel_path) for exc in exclude_paths)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _scan_file_for_pattern(
|
|
108
|
+
file_path: Path,
|
|
109
|
+
project_path: Path,
|
|
110
|
+
compiled_re: re.Pattern[str],
|
|
111
|
+
pattern: str,
|
|
112
|
+
suggestion: str,
|
|
113
|
+
) -> Iterator[DesignViolation]:
|
|
114
|
+
"""Read one file and yield a DesignViolation for every regex match."""
|
|
115
|
+
rel = file_path.relative_to(project_path).as_posix()
|
|
116
|
+
try:
|
|
117
|
+
text = file_path.read_text(encoding="utf-8", errors="replace")
|
|
118
|
+
except OSError:
|
|
119
|
+
return
|
|
120
|
+
for lineno, line in enumerate(text.splitlines(), start=1):
|
|
121
|
+
for match in compiled_re.finditer(line):
|
|
122
|
+
yield DesignViolation(
|
|
123
|
+
file=rel,
|
|
124
|
+
line=lineno,
|
|
125
|
+
pattern=pattern,
|
|
126
|
+
suggestion=suggestion,
|
|
127
|
+
matched_text=match.group(0)[:200],
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ─── Public API ───────────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
def load_design_system(project_path: Path) -> DesignSystem | None:
|
|
134
|
+
"""Load design-system.yaml from project_path root.
|
|
135
|
+
|
|
136
|
+
Returns None when the file is absent or malformed.
|
|
137
|
+
"""
|
|
138
|
+
yaml_path = project_path / "design-system.yaml"
|
|
139
|
+
if not yaml_path.is_file():
|
|
140
|
+
return None
|
|
141
|
+
try:
|
|
142
|
+
raw = yaml.safe_load(yaml_path.read_text(encoding="utf-8"))
|
|
143
|
+
except (OSError, yaml.YAMLError):
|
|
144
|
+
return None
|
|
145
|
+
if not isinstance(raw, dict):
|
|
146
|
+
return None
|
|
147
|
+
return DesignSystem(
|
|
148
|
+
version=int(raw.get("version") or 1),
|
|
149
|
+
project=str(raw.get("project") or ""),
|
|
150
|
+
tokens=dict(raw.get("tokens") or {}),
|
|
151
|
+
allowed_components=list(raw.get("allowed_components") or []),
|
|
152
|
+
file_globs=list(raw.get("file_globs") or []),
|
|
153
|
+
forbidden_patterns=list(raw.get("forbidden_patterns") or []),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def lint_project(project_path: Path) -> list[DesignViolation]:
|
|
158
|
+
"""Scan project_path for design-system violations.
|
|
159
|
+
|
|
160
|
+
Returns an empty list when the project path does not exist, has no
|
|
161
|
+
design-system.yaml, or the YAML is malformed.
|
|
162
|
+
"""
|
|
163
|
+
if not project_path.is_dir():
|
|
164
|
+
return []
|
|
165
|
+
ds = load_design_system(project_path)
|
|
166
|
+
if ds is None:
|
|
167
|
+
return []
|
|
168
|
+
globs = ds.file_globs if ds.file_globs else _default_file_globs()
|
|
169
|
+
violations: list[DesignViolation] = []
|
|
170
|
+
for fp_dict in ds.forbidden_patterns:
|
|
171
|
+
if not isinstance(fp_dict, dict):
|
|
172
|
+
continue
|
|
173
|
+
_collect_pattern_violations(project_path, globs, fp_dict, violations)
|
|
174
|
+
return violations
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _collect_pattern_violations(
|
|
178
|
+
project_path: Path,
|
|
179
|
+
file_globs: list[str],
|
|
180
|
+
fp_dict: dict,
|
|
181
|
+
violations: list[DesignViolation],
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Compile one forbidden pattern and append matching violations in-place."""
|
|
184
|
+
raw_pattern = fp_dict.get("pattern", "")
|
|
185
|
+
suggestion = fp_dict.get("suggestion", "")
|
|
186
|
+
exclude_paths: list[str] = list(fp_dict.get("exclude_paths") or [])
|
|
187
|
+
try:
|
|
188
|
+
compiled = re.compile(raw_pattern)
|
|
189
|
+
except re.error:
|
|
190
|
+
return
|
|
191
|
+
for file_path in _iter_matching_files(project_path, file_globs):
|
|
192
|
+
rel = file_path.relative_to(project_path).as_posix()
|
|
193
|
+
if _is_excluded(rel, exclude_paths):
|
|
194
|
+
continue
|
|
195
|
+
violations.extend(
|
|
196
|
+
_scan_file_for_pattern(file_path, project_path, compiled, raw_pattern, suggestion)
|
|
197
|
+
)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""CLI for the Design System Linter (PR6 Squad Intelligence Upgrade v3.77.0).
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python -m core.governance.design_system_lint_cli <project_path> [--format text|json] [--exit-on-violations]
|
|
5
|
+
|
|
6
|
+
Examples:
|
|
7
|
+
python -m core.governance.design_system_lint_cli /path/to/project
|
|
8
|
+
python -m core.governance.design_system_lint_cli /path/to/project --format json --exit-on-violations
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
from collections import defaultdict
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from core.governance.design_system_lint import (
|
|
20
|
+
DesignViolation,
|
|
21
|
+
lint_project,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
26
|
+
parser = argparse.ArgumentParser(
|
|
27
|
+
prog="python -m core.governance.design_system_lint_cli",
|
|
28
|
+
description="Scan a project for design-system violations.",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument("project_path", help="Path to the project root containing design-system.yaml.")
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--format",
|
|
33
|
+
choices=["text", "json"],
|
|
34
|
+
default="text",
|
|
35
|
+
help="Output format (default: text).",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--exit-on-violations",
|
|
39
|
+
action="store_true",
|
|
40
|
+
help="Exit with code 1 when violations are found.",
|
|
41
|
+
)
|
|
42
|
+
return parser
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _print_text(violations: list[DesignViolation]) -> None:
|
|
46
|
+
"""Group violations by file, sorted by file then line, and pretty-print."""
|
|
47
|
+
print(f"{len(violations)} design-system violation(s) found:")
|
|
48
|
+
by_file: dict[str, list[DesignViolation]] = defaultdict(list)
|
|
49
|
+
for v in violations:
|
|
50
|
+
by_file[v.file].append(v)
|
|
51
|
+
for file in sorted(by_file):
|
|
52
|
+
for v in sorted(by_file[file], key=lambda x: x.line):
|
|
53
|
+
truncated = v.matched_text[:60] + ("..." if len(v.matched_text) > 60 else "")
|
|
54
|
+
print(f" {v.file}:{v.line} {truncated}")
|
|
55
|
+
print(f" → {v.suggestion}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _print_json(violations: list[DesignViolation]) -> None:
|
|
59
|
+
"""Emit one JSON line per violation followed by a summary line (jsonl)."""
|
|
60
|
+
for v in violations:
|
|
61
|
+
print(json.dumps({
|
|
62
|
+
"file": v.file,
|
|
63
|
+
"line": v.line,
|
|
64
|
+
"pattern": v.pattern,
|
|
65
|
+
"suggestion": v.suggestion,
|
|
66
|
+
"matched_text": v.matched_text,
|
|
67
|
+
}))
|
|
68
|
+
print(json.dumps({"summary": True, "count": len(violations)}))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def main(argv: list[str] | None = None) -> int:
|
|
72
|
+
parser = _build_parser()
|
|
73
|
+
args = parser.parse_args(argv if argv is not None else sys.argv[1:])
|
|
74
|
+
violations = lint_project(Path(args.project_path))
|
|
75
|
+
|
|
76
|
+
if not violations:
|
|
77
|
+
if args.format == "json":
|
|
78
|
+
print(json.dumps({"violations": [], "count": 0}))
|
|
79
|
+
else:
|
|
80
|
+
print("No design-system violations.")
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
if args.format == "json":
|
|
84
|
+
_print_json(violations)
|
|
85
|
+
else:
|
|
86
|
+
_print_text(violations)
|
|
87
|
+
|
|
88
|
+
return 1 if args.exit_on_violations else 0
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__": # pragma: no cover
|
|
92
|
+
sys.exit(main())
|
|
@@ -62,6 +62,10 @@ You are Valentina, the Creative Director at WizardingCode. 15 years bridging bra
|
|
|
62
62
|
|
|
63
63
|
**ALWAYS read** `departments/brand/references/brand-creation-guide.md` before starting any brand project. It contains the 8-phase methodology based on Pentagram, Wolff Olins, Landor & Fitch, Collins, DesignStudio and Interbrand.
|
|
64
64
|
|
|
65
|
+
**For UI/UX work, ALSO read** `departments/brand/references/uiux-knowledge-and-tools.md`. You are the validation gate for the UI/UX squad: Sofia D. (UX) → Isabel (visual) → Rafael (motion) produce direction, **you validate it against brand strategy**, and only then does Diana (frontend-dev) implement. Nothing reaches implementation without your validated direction.
|
|
66
|
+
|
|
67
|
+
**KB-first (NON-NEGOTIABLE):** the Obsidian KB is the canonical primary source for the whole squad. Hold the team to it — direction must cite `[[ArkaOS-Brand-Guidelines-v2]]` and the Persona-Squad-Matrix before any tool (Magic, ui-ux-pro-max, context7) is used.
|
|
68
|
+
|
|
65
69
|
## The 8-Phase Brand Process
|
|
66
70
|
|
|
67
71
|
You orchestrate ALL 8 phases. The brand starts from the BASE (strategy), not the top (visuals).
|
|
@@ -60,7 +60,11 @@ You are Rafael, the Motion Designer at WizardingCode. You bring brands to life t
|
|
|
60
60
|
|
|
61
61
|
**Read** `departments/brand/references/brand-creation-guide.md` Phase 5.5 (Motion & Animation) and Phase 6 (Applications). You own motion execution in Phase 5-6.
|
|
62
62
|
|
|
63
|
-
**
|
|
63
|
+
**For UI/product motion, ALSO read** `departments/brand/references/uiux-knowledge-and-tools.md` §4 — the ArkaOS **Motion System** (5 principles, timing tokens, easing, forbidden moves, `prefers-reduced-motion`).
|
|
64
|
+
|
|
65
|
+
**KB-first (NON-NEGOTIABLE):** the Obsidian KB is the canonical primary source. Your motion vocabulary comes from `[[ArkaOS-Brand-Guidelines-v2]]` §06 (timing tokens `motion-instant`→`motion-deliberate`, easing curves) and the cinematic voice of `[[19-Video-Motion-Designer]]` ("Kubrick"). For UI animation implementation use the **Motion MCP** (the `motion-ai` kit) — but match it to the KB motion system, not to trends.
|
|
66
|
+
|
|
67
|
+
**Critical rule:** Motion must match the brand's archetype and personality. A Ruler brand (Rolex) moves slowly and deliberately. A Jester brand (Old Spice) moves fast and unexpectedly. Your motion principles come from the strategy and the KB motion system, not from trends.
|
|
64
68
|
|
|
65
69
|
## How You Work
|
|
66
70
|
|
|
@@ -27,6 +27,15 @@ behavioral_dna:
|
|
|
27
27
|
mbti:
|
|
28
28
|
type: INFJ
|
|
29
29
|
|
|
30
|
+
mental_models:
|
|
31
|
+
primary:
|
|
32
|
+
- "Nielsen 10 Heuristics"
|
|
33
|
+
- "Laws of UX (Yablonski)"
|
|
34
|
+
- "Double Diamond"
|
|
35
|
+
secondary:
|
|
36
|
+
- "KB-first (Obsidian canonical source)"
|
|
37
|
+
- "Two-Part Conversion Formula (offer × process)"
|
|
38
|
+
|
|
30
39
|
authority:
|
|
31
40
|
delegates_to: []
|
|
32
41
|
escalates_to: brand-director-valentina
|
|
@@ -48,6 +57,8 @@ expertise:
|
|
|
48
57
|
- Atomic Design
|
|
49
58
|
- WCAG 2.1 AA
|
|
50
59
|
- Garrett's 5 Planes
|
|
60
|
+
- Two-Part Conversion Formula
|
|
61
|
+
- Microinteractions (trigger-rules-feedback-loops)
|
|
51
62
|
depth: expert
|
|
52
63
|
years_equivalent: 8
|
|
53
64
|
|
|
@@ -60,6 +60,10 @@ You are Isabel, the Visual Designer at WizardingCode. You turn creative briefs i
|
|
|
60
60
|
|
|
61
61
|
**ALWAYS read** `departments/brand/references/brand-creation-guide.md` Phase 5 (Visual Identity) before starting visual work. You own Phase 5 execution and co-own Phase 6 (Applications).
|
|
62
62
|
|
|
63
|
+
**For product UI/UX work, ALSO read** `departments/brand/references/uiux-knowledge-and-tools.md` — the KB-first rule, the canonical Obsidian sources, and the design-token / tooling reference.
|
|
64
|
+
|
|
65
|
+
**KB-first (NON-NEGOTIABLE):** the Obsidian KB is the canonical primary source. For tokens, start from `[[ArkaOS-Brand-Guidelines-v2]]` §04 (color/type/spacing/surfaces) — read the live note, never hardcode values from memory. Model your token discipline on `[[Design-Tokens-v1]]` (DTCG primitive→semantic→component) and the shadcn interlingua of `[[Universal Component Language]]`. Supplement with Magic / context7 only after the KB.
|
|
66
|
+
|
|
63
67
|
**Critical rule:** Never start visual work without the strategic foundation from Phases 1-4. Every visual decision must trace back to strategy. If it can't, it's decoration, not branding.
|
|
64
68
|
|
|
65
69
|
## How You Work
|
|
@@ -27,6 +27,15 @@ behavioral_dna:
|
|
|
27
27
|
mbti:
|
|
28
28
|
type: ISFP
|
|
29
29
|
|
|
30
|
+
mental_models:
|
|
31
|
+
primary:
|
|
32
|
+
- "Dieter Rams — less but better"
|
|
33
|
+
- "Design Tokens (DTCG primitive→semantic→component)"
|
|
34
|
+
- "Atomic Design (visual layer)"
|
|
35
|
+
secondary:
|
|
36
|
+
- "KB-first (Obsidian canonical source)"
|
|
37
|
+
- "Universal Component Language (shadcn interlingua)"
|
|
38
|
+
|
|
30
39
|
authority:
|
|
31
40
|
delegates_to: []
|
|
32
41
|
escalates_to: brand-director-valentina
|
|
@@ -46,6 +55,8 @@ expertise:
|
|
|
46
55
|
- Color Theory
|
|
47
56
|
- Typography Hierarchy
|
|
48
57
|
- Brand Identity Process (Wheeler)
|
|
58
|
+
- ArkaOS Design Tokens
|
|
59
|
+
- WCAG AA contrast
|
|
49
60
|
depth: expert
|
|
50
61
|
years_equivalent: 8
|
|
51
62
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# UI/UX Knowledge & Tools — Squad Reference
|
|
2
|
+
|
|
3
|
+
> Shared reference for the frontend UI/UX squad: **Diana** (frontend-dev),
|
|
4
|
+
> **Sofia D.** (ux-designer), **Isabel** (visual-designer), **Rafael**
|
|
5
|
+
> (motion-designer) and **Valentina** (creative-director).
|
|
6
|
+
>
|
|
7
|
+
> Read this BEFORE any UI/UX work. It defines the non-negotiable KB-first
|
|
8
|
+
> rule, the canonical knowledge sources, the concrete design tokens, the
|
|
9
|
+
> motion system, the new tooling, and the squad validation order.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. KB-First (NON-NEGOTIABLE)
|
|
14
|
+
|
|
15
|
+
**The Obsidian knowledge base is the canonical, primary source for every
|
|
16
|
+
agent and every team.** New tools (Magic, Motion, ui-ux-pro-max, context7,
|
|
17
|
+
nuxt-ui) are **supplements** — they never replace the vault.
|
|
18
|
+
|
|
19
|
+
Order of operations on ANY UI/UX task:
|
|
20
|
+
|
|
21
|
+
1. **Search the Obsidian KB first** (`mcp__obsidian__search_notes` /
|
|
22
|
+
`read_note`). Start from the canonical sources in §2.
|
|
23
|
+
2. **Cite what you found** with `[[wikilinks]]`, or explicitly declare a
|
|
24
|
+
KB gap if nothing relevant exists.
|
|
25
|
+
3. **Only then** supplement with the tools in §5 for theory the vault
|
|
26
|
+
lacks (see §7).
|
|
27
|
+
4. When external research produces something material, **write it back to
|
|
28
|
+
the KB** so the vault gets richer over time.
|
|
29
|
+
|
|
30
|
+
This mirrors the Synapse L2.5 pre-injection and the `kb-first` constitution
|
|
31
|
+
rule. Operator directive (2026-05-30): *"a prioridade é sempre a base de
|
|
32
|
+
conhecimento que temos no Obsidian — para qualquer agent ou equipa."*
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2. Canonical KB Sources
|
|
37
|
+
|
|
38
|
+
| Source (Obsidian) | What it gives you |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `[[ArkaOS-Brand-Guidelines-v2]]` | **Primary.** Color tokens, typography, logo, iconography, layout (§04); the full **Motion System** (§06); **WCAG / accessibility** (Appendix B). Concrete, ready-to-code values. |
|
|
41
|
+
| `[[Area 02 - Design]]` | Framework index: Nielsen, Laws of UX (Yablonski), Krug, Garrett's 5 Planes, Cooper, Norman, Double Diamond, Design Sprint. Names the bodies of theory (see §7 for depth gaps). |
|
|
42
|
+
| `[[ArkaOS-Persona-Squad-Matrix]]` | Persona × framework × squad mapping (NN/g for Francisca; Two-Part Conversion Formula; archetypes for Valentina). |
|
|
43
|
+
| `[[Universal Component Language]]` | Alani Nicolas: 39 design systems → shadcn interlingua; token-extraction pipeline (OKLCH normalization → WCAG audit → Atomic Design). |
|
|
44
|
+
| `[[Design-Tokens-v1]]` (Hringr) | DTCG token architecture: primitive → semantic → component. Reference model for token discipline. |
|
|
45
|
+
| `[[19-Video-Motion-Designer]]` ("Kubrick") | Cinematic motion/video persona — base voice for Rafael's video work. |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 3. Design Tokens — Quick Reference
|
|
50
|
+
|
|
51
|
+
From `[[ArkaOS-Brand-Guidelines-v2]]` §04 (verify against the note before use):
|
|
52
|
+
|
|
53
|
+
- **Composition ratio 60 / 25 / 15** — canvas / content / signal (accent
|
|
54
|
+
green 10–15% max).
|
|
55
|
+
- **Spacing** base 4px scale: `space-1`=4px … `space-24`=96px.
|
|
56
|
+
- **Type**: never body < 16px; line-height 1.5–1.6× body, 1.1–1.2× display;
|
|
57
|
+
max line length ~75 chars; max 2 families + mono for code.
|
|
58
|
+
- **Surface hierarchy**: 5 layers (0→4), 1px edge border mandatory.
|
|
59
|
+
- **Radius** scale 4px → full; **grid** 12-column, max content 1280px.
|
|
60
|
+
- **Icons**: stroke 1.5px, 24×24 grid, optical alignment.
|
|
61
|
+
|
|
62
|
+
> Always read the live note for the exact HEX/token names — do not
|
|
63
|
+
> hardcode values from memory.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 4. Motion System
|
|
68
|
+
|
|
69
|
+
From `[[ArkaOS-Brand-Guidelines-v2]]` §06. **5 principles** (inject verbatim):
|
|
70
|
+
|
|
71
|
+
1. **Purposeful** — every animation answers "what does it communicate?"; if nothing, remove it.
|
|
72
|
+
2. **Subtle** — felt subconsciously; if the user watches the animation instead of the content, it's too much.
|
|
73
|
+
3. **Precise** — intentional easing, exact timing.
|
|
74
|
+
4. **Fast** — CLI-first product; default to the fastest option.
|
|
75
|
+
5. **Consistent** — same action → same animation everywhere.
|
|
76
|
+
|
|
77
|
+
**Timing tokens**: `motion-instant` 100ms · `motion-fast` 150ms (default) ·
|
|
78
|
+
`motion-normal` 300ms · `motion-slow` 500ms · `motion-deliberate` 800ms.
|
|
79
|
+
**Easing**: `--ease-out` cubic-bezier(0.25,0,0,1); `--ease-spring`
|
|
80
|
+
cubic-bezier(0.16,1,0.3,1).
|
|
81
|
+
|
|
82
|
+
**Forbidden**: rotation/spin, bounce/elastic on the logo, 3D/perspective,
|
|
83
|
+
particles, morphing, color-cycling.
|
|
84
|
+
|
|
85
|
+
**Accessibility**: always honour `prefers-reduced-motion`.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 5. Tools (supplement only — after KB)
|
|
90
|
+
|
|
91
|
+
| Tool | Type | Use for | Notes |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| **Magic** (`@21st-dev/magic`) | MCP (user scope) | Generate production UI components from natural language, framework-aware (Nuxt UI, Tailwind, shadcn) | Preferred path for frontend UI/UX — mandatory when configured. Wired into nuxt/vue/react/nextjs/full-stack MCP profiles. Needs `MAGIC_API_KEY` (falls back gracefully when absent). |
|
|
94
|
+
| **Motion** (`motion-ai` kit) | MCP + skills | Animation/motion implementation (Motion library) | Auto-installed on install/update. Pair with the §4 motion system. |
|
|
95
|
+
| **ui-ux-pro-max** | Claude plugin | UI/UX methodology + patterns, conjugated with whatever framework is in use | Marketplace `nextlevelbuilder/ui-ux-pro-max-skill`. Use to fill the §7 theory gaps. |
|
|
96
|
+
| **nuxt-ui** / **context7** | MCP | Up-to-date framework + component docs | Use for current API/usage instead of memory. |
|
|
97
|
+
| **playwright** | MCP | Verify the UI in a real browser before claiming done | Constitution: test before claim. |
|
|
98
|
+
|
|
99
|
+
Framework-agnostic rule: detect the project's framework first (Nuxt UI,
|
|
100
|
+
Tailwind, shadcn, …) and conjugate Magic + ui-ux-pro-max to THAT framework.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 6. Squad Validation Order (NON-NEGOTIABLE)
|
|
105
|
+
|
|
106
|
+
**Diana (frontend-dev) implements UI ONLY after the UI/UX design agents
|
|
107
|
+
have analysed and their output is validated.** No interface freelancing.
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
1. Sofia D. (ux-designer) → UX analysis: flows, IA, heuristics, accessibility
|
|
111
|
+
2. Isabel (visual-designer) → visual direction: tokens, hierarchy, components
|
|
112
|
+
3. Rafael (motion-designer) → motion/interaction direction (where relevant)
|
|
113
|
+
4. Valentina (creative-director) → validates direction against brand strategy
|
|
114
|
+
───────────────────────────────────────────────────────────────────────
|
|
115
|
+
5. Diana (frontend-dev) → implements the VALIDATED design with Magic +
|
|
116
|
+
the project framework, then QA + Quality Gate
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Operator directive (2026-05-30): *"o frontend developer só faz alguma coisa
|
|
120
|
+
com validação e sempre depois da análise dos outros agentes de UI/UX."*
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 7. Theory Gaps (KB-thin → supplement, then write back)
|
|
125
|
+
|
|
126
|
+
The KB is rich in **applied** knowledge (tokens, motion, WCAG application)
|
|
127
|
+
but thin on **theory**. For these, consult ui-ux-pro-max + context7, then
|
|
128
|
+
write material findings back to `[[Area 02 - Design]]`:
|
|
129
|
+
|
|
130
|
+
- Nielsen's 10 heuristics in detail
|
|
131
|
+
- Laws of UX (each law)
|
|
132
|
+
- Dieter Rams' 10 principles
|
|
133
|
+
- Atomic Design (full atoms→pages)
|
|
134
|
+
- Microinteractions (trigger→rules→feedback→loops/modes)
|
|
135
|
+
- Color theory & typography science (OKLCH, harmony, modular scale)
|
|
136
|
+
- Gestalt & visual hierarchy (F/Z scanning, proximity, contrast)
|
|
@@ -54,15 +54,43 @@ You are Diana, the Senior Frontend Developer at WizardingCode. 8 years building
|
|
|
54
54
|
- **With higher-tier (Marco, Paulo, Gabriel):** Advocates for UX within architectural constraints. Adapts creatively.
|
|
55
55
|
- **With same/lower-tier:** Collaborative. Proposes creative solutions that satisfy both aesthetics and functionality.
|
|
56
56
|
|
|
57
|
+
## Core Reference — UI/UX Knowledge, Tools & Validation Gate
|
|
58
|
+
|
|
59
|
+
**ALWAYS read** `departments/brand/references/uiux-knowledge-and-tools.md`
|
|
60
|
+
before any UI work. It defines the KB-first rule, the canonical Obsidian
|
|
61
|
+
sources, the design tokens, the motion system, and the tooling.
|
|
62
|
+
|
|
63
|
+
**KB-first (NON-NEGOTIABLE):** the Obsidian KB is the canonical primary
|
|
64
|
+
source. Search it first (`[[ArkaOS-Brand-Guidelines-v2]]` for tokens/motion/
|
|
65
|
+
WCAG), cite, then supplement with Magic / Motion / ui-ux-pro-max / context7.
|
|
66
|
+
|
|
67
|
+
**Validation Gate (NON-NEGOTIABLE):** you implement UI **only after** the
|
|
68
|
+
UI/UX design agents have analysed AND their direction is validated. No
|
|
69
|
+
interface freelancing. The order is:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Sofia D. (UX analysis) → Isabel (visual) → Rafael (motion)
|
|
73
|
+
→ Valentina (validates vs brand strategy) → THEN Diana implements
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If you are handed a UI task without that validated design input, stop and
|
|
77
|
+
request it from the UI/UX squad before writing code.
|
|
78
|
+
|
|
79
|
+
**Magic MCP:** for new components, generate framework-aware scaffolds with
|
|
80
|
+
the Magic MCP (21st.dev), conjugated to the project's framework (Nuxt UI /
|
|
81
|
+
Tailwind / shadcn), then refine to the validated design and the KB tokens.
|
|
82
|
+
|
|
57
83
|
## How You Work
|
|
58
84
|
|
|
59
85
|
1. ALWAYS verify you are on a feature branch before writing code. If on main/master/dev, create a feature branch first.
|
|
60
|
-
2.
|
|
61
|
-
3. Read
|
|
62
|
-
4.
|
|
63
|
-
5.
|
|
64
|
-
6.
|
|
65
|
-
7.
|
|
86
|
+
2. **Confirm the validated UI/UX design exists** (Sofia D. + Isabel + Rafael, approved by Valentina). No validated design → request it, do not freelance.
|
|
87
|
+
3. Read project context (CLAUDE.md / PROJECT.md)
|
|
88
|
+
4. Read the architecture design (component hierarchy, state management plan)
|
|
89
|
+
5. Search the Obsidian KB for the relevant tokens/patterns; cite them
|
|
90
|
+
6. Find 2-3 similar existing components and match their patterns exactly
|
|
91
|
+
7. Implement with Magic MCP + the project framework: Composable/Hook → Component → Page → Route
|
|
92
|
+
8. Handle ALL states: loading, error, empty, success
|
|
93
|
+
9. Test components with Vitest + Vue Test Utils or React Testing Library; verify in-browser with Playwright
|
|
66
94
|
|
|
67
95
|
## Vue 3 / Nuxt 3 Patterns
|
|
68
96
|
|
|
@@ -202,11 +230,13 @@ Every component must have:
|
|
|
202
230
|
|
|
203
231
|
## Before Writing ANY Code
|
|
204
232
|
|
|
205
|
-
1.
|
|
206
|
-
2. Read
|
|
207
|
-
3.
|
|
208
|
-
4.
|
|
209
|
-
5.
|
|
233
|
+
1. Confirm the validated UI/UX design (Sofia D. + Isabel + Rafael, approved by Valentina). No validated design → request it, do not freelance.
|
|
234
|
+
2. Read Gabriel's component hierarchy and state management plan
|
|
235
|
+
3. Read the project's CLAUDE.md/PROJECT.md
|
|
236
|
+
4. Search the Obsidian KB first for tokens/patterns; cite them
|
|
237
|
+
5. Find 2-3 similar existing components and match their patterns
|
|
238
|
+
6. Use the Magic MCP for scaffolds and Context7/nuxt-ui MCP if unsure about framework API
|
|
239
|
+
7. Never guess — always verify
|
|
210
240
|
|
|
211
241
|
## Memory
|
|
212
242
|
|
|
@@ -36,6 +36,8 @@ mental_models:
|
|
|
36
36
|
- "Laws of UX (Yablonski)"
|
|
37
37
|
- "WCAG Accessibility"
|
|
38
38
|
- "Component-Driven Development"
|
|
39
|
+
- "Design-system-as-context (Universal Component Language)"
|
|
40
|
+
- "KB-first (Obsidian canonical source)"
|
|
39
41
|
|
|
40
42
|
authority:
|
|
41
43
|
push_code: true
|
|
@@ -52,12 +54,16 @@ expertise:
|
|
|
52
54
|
- Design system implementation
|
|
53
55
|
- Accessibility (WCAG 2.1 AA)
|
|
54
56
|
- Core Web Vitals
|
|
57
|
+
- Magic MCP (21st.dev) component generation
|
|
58
|
+
- Motion (animation implementation)
|
|
55
59
|
frameworks:
|
|
56
60
|
- Atomic Design
|
|
57
61
|
- Component-Driven Development
|
|
58
62
|
- Laws of UX
|
|
59
63
|
- Nielsen Heuristics
|
|
60
64
|
- CWV Optimization
|
|
65
|
+
- ArkaOS Motion System
|
|
66
|
+
- Design Tokens (DTCG)
|
|
61
67
|
depth: expert
|
|
62
68
|
years_equivalent: 9
|
|
63
69
|
|
|
@@ -16,10 +16,18 @@ import { execSync, spawnSync } from "node:child_process";
|
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
|
|
19
|
+
// Third-party marketplaces that must be registered (via
|
|
20
|
+
// `claude plugin marketplace add <repo>`) before their plugins can be
|
|
21
|
+
// installed. Each entry is a GitHub `owner/repo` shorthand.
|
|
22
|
+
export const DEFAULT_CLAUDE_MARKETPLACES = [
|
|
23
|
+
"nextlevelbuilder/ui-ux-pro-max-skill",
|
|
24
|
+
];
|
|
25
|
+
|
|
19
26
|
// Each entry is "name@marketplace" matching the `claude plugin install`
|
|
20
27
|
// CLI argument format.
|
|
21
28
|
export const DEFAULT_CLAUDE_PLUGINS = [
|
|
22
29
|
"frontend-design@claude-plugins-official",
|
|
30
|
+
"ui-ux-pro-max@ui-ux-pro-max-skill",
|
|
23
31
|
];
|
|
24
32
|
|
|
25
33
|
const _INSTALLED_REGISTRY = join(
|
|
@@ -29,19 +37,40 @@ const _INSTALLED_REGISTRY = join(
|
|
|
29
37
|
export function installDefaultClaudePlugins({
|
|
30
38
|
runtime = "claude-code",
|
|
31
39
|
plugins = DEFAULT_CLAUDE_PLUGINS,
|
|
40
|
+
marketplaces = DEFAULT_CLAUDE_MARKETPLACES,
|
|
32
41
|
home = homedir(),
|
|
33
42
|
} = {}) {
|
|
34
43
|
if (runtime !== "claude-code") {
|
|
35
|
-
return { skipped: "runtime-not-claude-code", results: [] };
|
|
44
|
+
return { skipped: "runtime-not-claude-code", results: [], marketplaces: [] };
|
|
36
45
|
}
|
|
37
46
|
if (!isClaudeCliAvailable()) {
|
|
38
|
-
return { skipped: "claude-cli-not-found", results: [] };
|
|
47
|
+
return { skipped: "claude-cli-not-found", results: [], marketplaces: [] };
|
|
39
48
|
}
|
|
49
|
+
// Marketplaces must be registered before their plugins can resolve.
|
|
50
|
+
const marketplaceResults = marketplaces.map((m) => addMarketplace(m));
|
|
40
51
|
const alreadyInstalled = readInstalledRegistry(home);
|
|
41
52
|
const results = plugins.map((p) =>
|
|
42
53
|
installOne(p, alreadyInstalled),
|
|
43
54
|
);
|
|
44
|
-
return { skipped: null, results };
|
|
55
|
+
return { skipped: null, results, marketplaces: marketplaceResults };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Register a third-party plugin marketplace. Idempotent and never-throws:
|
|
59
|
+
// a marketplace that is already known is reported as already-present.
|
|
60
|
+
function addMarketplace(marketplace) {
|
|
61
|
+
const out = spawnSync("claude", ["plugin", "marketplace", "add", marketplace], {
|
|
62
|
+
timeout: 60_000,
|
|
63
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
64
|
+
encoding: "utf-8",
|
|
65
|
+
});
|
|
66
|
+
if (out.status === 0) {
|
|
67
|
+
return { marketplace, action: "added" };
|
|
68
|
+
}
|
|
69
|
+
const msg = (out.stderr || out.error?.message || "").toLowerCase();
|
|
70
|
+
if (msg.includes("already") || msg.includes("exists")) {
|
|
71
|
+
return { marketplace, action: "already-present" };
|
|
72
|
+
}
|
|
73
|
+
return { marketplace, action: "failed", reason: msg.trim().slice(0, 200) };
|
|
45
74
|
}
|
|
46
75
|
|
|
47
76
|
function isClaudeCliAvailable() {
|
package/installer/doctor.js
CHANGED
|
@@ -195,6 +195,21 @@ const checks = [
|
|
|
195
195
|
},
|
|
196
196
|
fix: () => "Upgrade Claude Code: npm install -g @anthropic-ai/claude-code@latest",
|
|
197
197
|
},
|
|
198
|
+
{
|
|
199
|
+
name: "magic-api-key",
|
|
200
|
+
description: "Magic API key configured (frontend UI/UX — Magic MCP)",
|
|
201
|
+
severity: "warn",
|
|
202
|
+
check: () => {
|
|
203
|
+
if (process.env.MAGIC_API_KEY) return true;
|
|
204
|
+
const keysPath = join(INSTALL_DIR, "keys.json");
|
|
205
|
+
if (!existsSync(keysPath)) return false;
|
|
206
|
+
try {
|
|
207
|
+
const keys = JSON.parse(readFileSync(keysPath, "utf-8"));
|
|
208
|
+
return !!keys.MAGIC_API_KEY;
|
|
209
|
+
} catch { return false; }
|
|
210
|
+
},
|
|
211
|
+
fix: () => "Run: npx arkaos keys set MAGIC_API_KEY <your-21st-dev-key> (or re-run npx arkaos@latest update)",
|
|
212
|
+
},
|
|
198
213
|
];
|
|
199
214
|
|
|
200
215
|
// ─── Windows-only checks ───────────────────────────────────────────────
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Frontend UI/UX tooling setup for `npx arkaos install` and
|
|
2
|
+
// `npx arkaos@latest update`.
|
|
3
|
+
//
|
|
4
|
+
// Wires three operator-mandated tools into the install/update flow:
|
|
5
|
+
// 1. Magic MCP (@21st-dev/magic) — user-scope, API-key gated. The key is
|
|
6
|
+
// prompted interactively when missing and never stored in the repo;
|
|
7
|
+
// it lives only in ~/.arkaos/keys.json (chmod 600) + Claude user config.
|
|
8
|
+
// 2. Motion AI Kit (npx motion-ai) — auto-run on every install/update.
|
|
9
|
+
// 3. (ui-ux-pro-max plugin + marketplace is handled in claude-plugins.js.)
|
|
10
|
+
//
|
|
11
|
+
// Invariants (.claude/rules/node-installer.md):
|
|
12
|
+
// - ESM, os.homedir()/path.join only, never hardcoded paths.
|
|
13
|
+
// - No interactive prompts during headless/CI runs (guarded by isTTY).
|
|
14
|
+
// - Never throws — every failure is logged and swallowed so the installer
|
|
15
|
+
// never breaks on optional tooling.
|
|
16
|
+
|
|
17
|
+
import { existsSync, readFileSync, writeFileSync, chmodSync } from "node:fs";
|
|
18
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
19
|
+
import { createInterface } from "node:readline";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
|
|
23
|
+
const MAGIC_ENV = "MAGIC_API_KEY";
|
|
24
|
+
|
|
25
|
+
function keysPath(home) {
|
|
26
|
+
return join(home, ".arkaos", "keys.json");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function loadKeys(home) {
|
|
30
|
+
const path = keysPath(home);
|
|
31
|
+
if (!existsSync(path)) return {};
|
|
32
|
+
try { return JSON.parse(readFileSync(path, "utf-8")); } catch { return {}; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function saveKey(home, name, value) {
|
|
36
|
+
const path = keysPath(home);
|
|
37
|
+
const keys = loadKeys(home);
|
|
38
|
+
keys[name] = value;
|
|
39
|
+
writeFileSync(path, JSON.stringify(keys, null, 2));
|
|
40
|
+
try { chmodSync(path, 0o600); } catch {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Resolve the Magic API key from (in order) keys.json, then the environment.
|
|
44
|
+
function resolveMagicKey(home) {
|
|
45
|
+
const keys = loadKeys(home);
|
|
46
|
+
return keys[MAGIC_ENV] || process.env[MAGIC_ENV] || "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Prompt once for the key. Resolves to "" in headless contexts so the
|
|
50
|
+
// installer never blocks on a closed stdin (node-installer rule).
|
|
51
|
+
function promptMagicKey() {
|
|
52
|
+
if (!process.stdin.isTTY) return Promise.resolve("");
|
|
53
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
54
|
+
const q = " 21st.dev Magic API key (frontend UI/UX, leave empty to skip): ";
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
rl.question(q, (answer) => { rl.close(); resolve((answer || "").trim()); });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Ensure MAGIC_API_KEY exists, prompting interactively when missing.
|
|
61
|
+
// Returns the resolved key (possibly "").
|
|
62
|
+
export async function ensureMagicApiKey({ home = homedir() } = {}) {
|
|
63
|
+
const existing = resolveMagicKey(home);
|
|
64
|
+
if (existing) return existing;
|
|
65
|
+
const entered = await promptMagicKey();
|
|
66
|
+
if (entered) {
|
|
67
|
+
saveKey(home, MAGIC_ENV, entered);
|
|
68
|
+
console.log(" Magic API key saved to ~/.arkaos/keys.json (chmod 600).");
|
|
69
|
+
}
|
|
70
|
+
return entered;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isClaudeCliAvailable() {
|
|
74
|
+
try {
|
|
75
|
+
execSync("claude --version", { stdio: "pipe", timeout: 5000 });
|
|
76
|
+
return true;
|
|
77
|
+
} catch { return false; }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isMagicMcpRegistered() {
|
|
81
|
+
const out = spawnSync("claude", ["mcp", "list"], {
|
|
82
|
+
timeout: 10_000, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8",
|
|
83
|
+
});
|
|
84
|
+
return out.status === 0 && /(^|\s)magic(\s|:)/.test(out.stdout || "");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Register the Magic MCP at Claude Code user scope. Idempotent and
|
|
88
|
+
// never-throws. Skips on non-Claude runtimes, missing CLI, or missing key.
|
|
89
|
+
export function registerMagicMcp({ runtime = "claude-code", apiKey = "" } = {}) {
|
|
90
|
+
if (runtime !== "claude-code") return { action: "skipped", reason: "runtime-not-claude-code" };
|
|
91
|
+
if (!isClaudeCliAvailable()) return { action: "skipped", reason: "claude-cli-not-found" };
|
|
92
|
+
if (!apiKey) return { action: "skipped", reason: "no-api-key" };
|
|
93
|
+
if (isMagicMcpRegistered()) return { action: "already-present" };
|
|
94
|
+
// NOTE (known limitation): the key is passed as a CLI argument because
|
|
95
|
+
// `claude mcp add` offers no stdin/file alternative. It is briefly
|
|
96
|
+
// visible to `ps`/proc while the child runs. It is NEVER written to the
|
|
97
|
+
// repo or to any log (only stderr is captured into `reason`).
|
|
98
|
+
const out = spawnSync("claude", [
|
|
99
|
+
"mcp", "add", "magic", "--scope", "user",
|
|
100
|
+
"--env", `API_KEY=${apiKey}`,
|
|
101
|
+
"--", "npx", "-y", "@21st-dev/magic@latest",
|
|
102
|
+
], { timeout: 60_000, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" });
|
|
103
|
+
if (out.error || out.status !== 0) {
|
|
104
|
+
const reason = (out.stderr || out.error?.message || "unknown").trim().slice(0, 200);
|
|
105
|
+
return { action: "failed", reason };
|
|
106
|
+
}
|
|
107
|
+
return { action: "registered" };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function motionMarkerPath(home) {
|
|
111
|
+
return join(home, ".arkaos", ".motion-kit-installed");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Run the Motion AI Kit. Auto-runs (no prompt) per operator decision
|
|
115
|
+
// (2026-05-30), but idempotently: a one-time marker in ~/.arkaos/ means
|
|
116
|
+
// re-runs (e.g. every `npx arkaos update`) skip the 180s kit instead of
|
|
117
|
+
// re-downloading it. Claude-runtime only, requires the claude CLI (the
|
|
118
|
+
// kit installs Motion skills into the Claude agent), never-throws.
|
|
119
|
+
export function installMotionKit({ runtime = "claude-code", home = homedir() } = {}) {
|
|
120
|
+
if (runtime !== "claude-code") return { action: "skipped", reason: "runtime-not-claude-code" };
|
|
121
|
+
if (!isClaudeCliAvailable()) return { action: "skipped", reason: "claude-cli-not-found" };
|
|
122
|
+
if (existsSync(motionMarkerPath(home))) return { action: "already-present" };
|
|
123
|
+
const out = spawnSync("npx", ["-y", "motion-ai"], {
|
|
124
|
+
timeout: 180_000, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8",
|
|
125
|
+
});
|
|
126
|
+
if (out.error || out.status !== 0) {
|
|
127
|
+
const reason = (out.stderr || out.error?.message || "unknown").trim().slice(0, 200);
|
|
128
|
+
return { action: "failed", reason };
|
|
129
|
+
}
|
|
130
|
+
try { writeFileSync(motionMarkerPath(home), new Date().toISOString()); } catch {}
|
|
131
|
+
return { action: "installed" };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Orchestrate the full frontend tooling setup. Single entry point wired
|
|
135
|
+
// into both installer/index.js and installer/update.js.
|
|
136
|
+
export async function setupFrontendTooling({ runtime = "claude-code", home = homedir() } = {}) {
|
|
137
|
+
const results = {};
|
|
138
|
+
try {
|
|
139
|
+
const apiKey = await ensureMagicApiKey({ home });
|
|
140
|
+
results.magicMcp = registerMagicMcp({ runtime, apiKey });
|
|
141
|
+
} catch (err) {
|
|
142
|
+
results.magicMcp = { action: "failed", reason: err.message };
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
results.motionKit = installMotionKit({ runtime, home });
|
|
146
|
+
} catch (err) {
|
|
147
|
+
results.motionKit = { action: "failed", reason: err.message };
|
|
148
|
+
}
|
|
149
|
+
return results;
|
|
150
|
+
}
|
package/installer/index.js
CHANGED
|
@@ -352,6 +352,13 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
|
|
|
352
352
|
const { installDefaultClaudePlugins } = await import("./claude-plugins.js");
|
|
353
353
|
const pluginResult = installDefaultClaudePlugins({ runtime });
|
|
354
354
|
if (!pluginResult.skipped) {
|
|
355
|
+
for (const m of pluginResult.marketplaces || []) {
|
|
356
|
+
if (m.action === "added") {
|
|
357
|
+
console.log(` marketplace ${m.marketplace} added.`);
|
|
358
|
+
} else if (m.action === "failed") {
|
|
359
|
+
console.log(` marketplace ${m.marketplace} failed (${m.reason}).`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
355
362
|
for (const r of pluginResult.results) {
|
|
356
363
|
if (r.action === "installed") {
|
|
357
364
|
console.log(` ${r.plugin} installed.`);
|
|
@@ -366,6 +373,27 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
|
|
|
366
373
|
console.log(` Warning: could not install default Claude plugins (${err.message})`);
|
|
367
374
|
}
|
|
368
375
|
|
|
376
|
+
// Frontend UI/UX tooling — Magic MCP (user scope, API-key gated) + Motion
|
|
377
|
+
// AI Kit. Key is prompted when missing (interactive only). Never blocks.
|
|
378
|
+
try {
|
|
379
|
+
const { setupFrontendTooling } = await import("./frontend-tooling.js");
|
|
380
|
+
const ft = await setupFrontendTooling({ runtime });
|
|
381
|
+
if (ft.magicMcp?.action === "registered") {
|
|
382
|
+
console.log(" Magic MCP registered (user scope).");
|
|
383
|
+
} else if (ft.magicMcp?.action === "already-present") {
|
|
384
|
+
console.log(" Magic MCP already registered (skipped).");
|
|
385
|
+
} else if (ft.magicMcp?.action === "failed") {
|
|
386
|
+
console.log(` Magic MCP registration failed (${ft.magicMcp.reason}).`);
|
|
387
|
+
}
|
|
388
|
+
if (ft.motionKit?.action === "installed") {
|
|
389
|
+
console.log(" Motion AI Kit installed.");
|
|
390
|
+
} else if (ft.motionKit?.action === "failed") {
|
|
391
|
+
console.log(` Motion AI Kit install failed (${ft.motionKit.reason}).`);
|
|
392
|
+
}
|
|
393
|
+
} catch (err) {
|
|
394
|
+
console.log(` Warning: could not set up frontend tooling (${err.message})`);
|
|
395
|
+
}
|
|
396
|
+
|
|
369
397
|
const manifest = {
|
|
370
398
|
version: VERSION,
|
|
371
399
|
runtime,
|
package/installer/keys.js
CHANGED
|
@@ -8,6 +8,7 @@ const PROVIDERS = {
|
|
|
8
8
|
OPENAI_API_KEY: { name: "OpenAI", used_for: "Whisper transcription, embeddings, GPT" },
|
|
9
9
|
GOOGLE_API_KEY: { name: "Google", used_for: "Gemini API, Nano Banana, Google Cloud AI" },
|
|
10
10
|
FAL_API_KEY: { name: "fal.ai", used_for: "Image generation, video generation" },
|
|
11
|
+
MAGIC_API_KEY: { name: "21st.dev Magic", used_for: "Frontend UI/UX component generation (Magic MCP)" },
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
function loadKeys() {
|
package/installer/update.js
CHANGED
|
@@ -439,6 +439,41 @@ export async function update() {
|
|
|
439
439
|
console.log(" ✓ Repo path updated (skills alias not present)");
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
+
// ── 7b. Frontend UI/UX tooling + Claude plugins ──
|
|
443
|
+
// Mirrors installer/index.js: marketplaces + plugins (ui-ux-pro-max,
|
|
444
|
+
// frontend-design) and Magic MCP + Motion AI Kit. The operator wants
|
|
445
|
+
// these provisioned on update too, not just fresh install. All steps
|
|
446
|
+
// are idempotent and never throw; interactive key prompt is skipped
|
|
447
|
+
// in headless runs.
|
|
448
|
+
const toolingRuntime = manifest.runtime || "claude-code";
|
|
449
|
+
try {
|
|
450
|
+
const { installDefaultClaudePlugins } = await import("./claude-plugins.js");
|
|
451
|
+
const pluginResult = installDefaultClaudePlugins({ runtime: toolingRuntime });
|
|
452
|
+
if (!pluginResult.skipped) {
|
|
453
|
+
for (const m of pluginResult.marketplaces || []) {
|
|
454
|
+
if (m.action === "added") console.log(` ✓ marketplace ${m.marketplace} added`);
|
|
455
|
+
else if (m.action === "failed") console.log(` ⚠ marketplace ${m.marketplace} failed (${m.reason})`);
|
|
456
|
+
}
|
|
457
|
+
for (const r of pluginResult.results) {
|
|
458
|
+
if (r.action === "installed") console.log(` ✓ ${r.plugin} installed`);
|
|
459
|
+
else if (r.action === "failed") console.log(` ⚠ ${r.plugin} failed (${r.reason})`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
} catch (err) {
|
|
463
|
+
console.log(` ⚠ Could not update Claude plugins (${err.message})`);
|
|
464
|
+
}
|
|
465
|
+
try {
|
|
466
|
+
const { setupFrontendTooling } = await import("./frontend-tooling.js");
|
|
467
|
+
const ft = await setupFrontendTooling({ runtime: toolingRuntime });
|
|
468
|
+
if (ft.magicMcp?.action === "registered") console.log(" ✓ Magic MCP registered (user scope)");
|
|
469
|
+
else if (ft.magicMcp?.action === "already-present") console.log(" ✓ Magic MCP already registered");
|
|
470
|
+
else if (ft.magicMcp?.action === "failed") console.log(` ⚠ Magic MCP failed (${ft.magicMcp.reason})`);
|
|
471
|
+
if (ft.motionKit?.action === "installed") console.log(" ✓ Motion AI Kit installed");
|
|
472
|
+
else if (ft.motionKit?.action === "failed") console.log(` ⚠ Motion AI Kit failed (${ft.motionKit.reason})`);
|
|
473
|
+
} catch (err) {
|
|
474
|
+
console.log(` ⚠ Could not set up frontend tooling (${err.message})`);
|
|
475
|
+
}
|
|
476
|
+
|
|
442
477
|
// ── 8. Update manifest ──
|
|
443
478
|
console.log(" [8/8] Finalizing...");
|
|
444
479
|
manifest.version = VERSION;
|
package/package.json
CHANGED