arkaos 4.12.0 → 4.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/VERSION +1 -1
- package/arka/SKILL.md +1 -1
- package/arka/skills/flow/SKILL.md +11 -4
- package/config/claude-agents/content-researcher.md +51 -0
- package/config/claude-agents/frontend-dev.md +1 -1
- package/config/claude-agents/info-compiler.md +51 -0
- package/config/claude-agents/scriptwriter.md +9 -7
- package/config/claude-agents/trends-analyst.md +51 -0
- package/config/claude-agents/ux-designer.md +1 -1
- package/config/claude-agents/video-producer.md +51 -0
- package/config/claude-agents/visual-designer.md +1 -1
- package/config/constitution.yaml +1 -0
- package/config/mcp-policy.yaml +8 -0
- package/core/governance/__pycache__/evidence_checks.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/phantom_action_check.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/phantom_action_check.cpython-314.pyc +0 -0
- package/core/governance/evidence_checks.py +70 -0
- package/core/hooks/__pycache__/pre_tool_use.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/pre_tool_use.cpython-314.pyc +0 -0
- package/core/hooks/__pycache__/stop.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/stop.cpython-314.pyc +0 -0
- package/core/hooks/pre_tool_use.py +8 -2
- package/core/runtime/__pycache__/model_routing_context.cpython-313.pyc +0 -0
- package/core/runtime/model_routing_context.py +4 -0
- package/core/workflow/__pycache__/frontend_gate.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/frontend_gate_telemetry.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/frontend_gate_telemetry_cli.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/plan_approval.cpython-314.pyc +0 -0
- package/core/workflow/frontend_gate.py +118 -25
- package/core/workflow/frontend_gate_telemetry.py +129 -0
- package/core/workflow/frontend_gate_telemetry_cli.py +53 -0
- package/departments/brand/agents/ux-designer.yaml +1 -1
- package/departments/brand/agents/visual-designer.yaml +1 -1
- package/departments/brand/references/uiux-knowledge-and-tools.md +73 -0
- package/departments/brand/skills/colors/SKILL.md +76 -3
- package/departments/brand/skills/design-review/SKILL.md +81 -31
- package/departments/brand/skills/design-system/SKILL.md +32 -0
- package/departments/brand/skills/identity-system/SKILL.md +65 -4
- package/departments/brand/skills/logo-brief/SKILL.md +81 -3
- package/departments/brand/skills/mockup-generate/SKILL.md +82 -3
- package/departments/brand/skills/ux-audit/SKILL.md +74 -5
- package/departments/brand/skills/wireframe/SKILL.md +75 -3
- package/departments/content/SKILL.md +16 -2
- package/departments/content/agents/production/content-researcher.yaml +68 -0
- package/departments/content/agents/production/info-compiler.yaml +68 -0
- package/departments/content/agents/production/trends-analyst.yaml +69 -0
- package/departments/content/agents/production/video-producer.yaml +77 -0
- package/departments/content/agents/scriptwriter.yaml +29 -10
- package/departments/content/skills/research-compile/SKILL.md +75 -0
- package/departments/content/skills/short-form/SKILL.md +3 -1
- package/departments/content/skills/trend-hunt/SKILL.md +74 -0
- package/departments/content/skills/video-produce/SKILL.md +98 -0
- package/departments/content/skills/video-setup/SKILL.md +91 -0
- package/departments/content/squad.yaml +6 -0
- package/departments/content/workflows/shorts-social.yaml +83 -0
- package/departments/content/workflows/video-production.yaml +142 -0
- package/departments/content/workflows/youtube.yaml +3 -0
- package/departments/dev/agents/frontend-dev.yaml +1 -1
- package/installer/core-snapshot.js +44 -20
- package/installer/doctor.js +56 -1
- package/knowledge/agents-registry-v2.json +209 -14
- package/knowledge/commands-keywords.json +1388 -234
- package/knowledge/commands-registry.json +132 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
|
@@ -3,17 +3,28 @@
|
|
|
3
3
|
Constitution: ``excellence-mandate`` (NON-NEGOTIABLE, Excellence Reform
|
|
4
4
|
2026-07-05). UI work must load the frontend design skills and judge
|
|
5
5
|
against a named benchmark BEFORE code is written. This gate makes that
|
|
6
|
-
duty mechanical: a Write/Edit/MultiEdit touching a UI file requires
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
duty mechanical: a Write/Edit/MultiEdit touching a UI file requires the
|
|
7
|
+
STRUCTURED design marker in the recent assistant messages:
|
|
8
|
+
|
|
9
|
+
[arka:design] benchmark=<Company> skills=<comma,list> tokens=<path|none>
|
|
10
|
+
|
|
11
|
+
(the same ceremony contract as ``[arka:routing]`` / ``[arka:dispatch]``,
|
|
12
|
+
upgraded in PR-D2 from the bare ``[arka:design] <anything>`` form — the
|
|
13
|
+
bare form is now a LEGACY marker: tolerated in warn mode with a nudge,
|
|
14
|
+
counted separately in telemetry, treated as missing in hard mode).
|
|
10
15
|
|
|
11
16
|
Modes via ``hooks.frontendGate`` in ``~/.arkaos/config.json``:
|
|
12
17
|
|
|
13
18
|
absent / "warn" → nudge on stderr, allow (rollout default)
|
|
14
|
-
true / "hard" → deny UI edits without the marker
|
|
19
|
+
true / "hard" → deny UI edits without the structured marker
|
|
15
20
|
false / "off" → gate disabled
|
|
16
21
|
|
|
22
|
+
Scope: component/stylesheet suffixes (now including ``.html``), plus a
|
|
23
|
+
WARN-only content heuristic for ``.ts/.js`` files that visibly carry UI
|
|
24
|
+
(className=/styled/@apply/tailwind/cva) — heuristic hits never deny,
|
|
25
|
+
even in hard mode, and carry their own ``ui_scope`` so the telemetry can
|
|
26
|
+
exclude them from the hard-flip decision if they prove FP-heavy.
|
|
27
|
+
|
|
17
28
|
Bypasses: ``[arka:trivial] <reason>`` marker (same contract as the
|
|
18
29
|
evidence flow — single-file edits under 10 lines) and
|
|
19
30
|
``ARKA_BYPASS_DESIGN=1`` env (logged for accountability).
|
|
@@ -40,18 +51,35 @@ TELEMETRY_PATH = Path.home() / ".arkaos" / "telemetry" / "frontend-gate.jsonl"
|
|
|
40
51
|
|
|
41
52
|
ASSISTANT_WINDOW = 20
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
# Any [arka:design] marker line; classification into structured vs legacy
|
|
55
|
+
# happens on the tail (order-insensitive key scan, see _classify_marker).
|
|
56
|
+
DESIGN_LINE_RE = re.compile(r"\[arka:design\]([^\n]*)", re.IGNORECASE)
|
|
57
|
+
_BENCHMARK_KEY_RE = re.compile(r"\bbenchmark=\S+", re.IGNORECASE)
|
|
58
|
+
_SKILLS_KEY_RE = re.compile(r"\bskills=\S+", re.IGNORECASE)
|
|
59
|
+
|
|
60
|
+
MARKER_TEMPLATE = (
|
|
61
|
+
"[arka:design] benchmark=<Company> skills=<comma,list> tokens=<path|none>"
|
|
62
|
+
)
|
|
44
63
|
|
|
45
64
|
_GATED_TOOLS = frozenset({"Write", "Edit", "MultiEdit"})
|
|
46
65
|
|
|
47
|
-
# Component + stylesheet extensions. Plain .ts/.js
|
|
48
|
-
#
|
|
49
|
-
#
|
|
66
|
+
# Component + stylesheet extensions. Plain .ts/.js stay out of the suffix
|
|
67
|
+
# set (stores, utils, configs would flood the gate) — they are covered by
|
|
68
|
+
# the WARN-only content heuristic below when they visibly carry UI.
|
|
50
69
|
UI_SUFFIXES = frozenset({
|
|
51
70
|
".vue", ".tsx", ".jsx", ".svelte", ".astro",
|
|
52
71
|
".css", ".scss", ".sass", ".less",
|
|
72
|
+
".html", ".htm",
|
|
53
73
|
})
|
|
54
74
|
|
|
75
|
+
_HEURISTIC_SUFFIXES = frozenset({".ts", ".js", ".mjs", ".cjs"})
|
|
76
|
+
_UI_CONTENT_RE = re.compile(
|
|
77
|
+
r"className=|styled\.|styled\(|@apply\b|\btailwind\b|cva\("
|
|
78
|
+
)
|
|
79
|
+
_UI_FILENAME_RE = re.compile(
|
|
80
|
+
r"(?:^|/)tailwind\.config\.[cm]?[tj]s$|\.styles?\.[tj]s$|(?:^|/)theme\.[tj]s$"
|
|
81
|
+
)
|
|
82
|
+
|
|
55
83
|
|
|
56
84
|
@dataclass
|
|
57
85
|
class Decision:
|
|
@@ -62,9 +90,19 @@ class Decision:
|
|
|
62
90
|
mode: str = "warn"
|
|
63
91
|
target_file: str = ""
|
|
64
92
|
marker_found: str | None = None
|
|
93
|
+
marker_kind: str = "none" # structured | legacy | trivial | none
|
|
94
|
+
ui_scope: str = "suffix" # suffix | heuristic
|
|
65
95
|
|
|
66
96
|
def to_stderr_message(self) -> str:
|
|
67
|
-
if self.reason
|
|
97
|
+
if self.reason == "legacy-marker":
|
|
98
|
+
head = "[arka:suggest]" if self.allow else "[ARKA:DESIGN]"
|
|
99
|
+
return (
|
|
100
|
+
f"{head} Legacy design marker found ({self.marker_found}). "
|
|
101
|
+
f"The structured form is now required for hard-mode passage: "
|
|
102
|
+
f"`{MARKER_TEMPLATE}` — name the benchmark company and the "
|
|
103
|
+
f"skills actually loaded (or skills=degraded:<missing>)."
|
|
104
|
+
)
|
|
105
|
+
if self.reason != "no-design-marker":
|
|
68
106
|
return ""
|
|
69
107
|
head = "[arka:suggest]" if self.allow else "[ARKA:DESIGN]"
|
|
70
108
|
verb = "should" if self.allow else "MUST"
|
|
@@ -74,7 +112,7 @@ class Decision:
|
|
|
74
112
|
f"(frontend-design, ui-ux-pro-max, project design system) at "
|
|
75
113
|
f"maximum effort and judge against a named benchmark FIRST "
|
|
76
114
|
f"(constitution `excellence-mandate`). Emit "
|
|
77
|
-
f"`
|
|
115
|
+
f"`{MARKER_TEMPLATE}` before UI edits, "
|
|
78
116
|
f"or `[arka:trivial] <reason>` for sub-10-line fixes."
|
|
79
117
|
)
|
|
80
118
|
|
|
@@ -100,13 +138,52 @@ def is_ui_file(file_path: str) -> bool:
|
|
|
100
138
|
return Path(file_path).suffix.lower() in UI_SUFFIXES
|
|
101
139
|
|
|
102
140
|
|
|
103
|
-
def
|
|
104
|
-
"""
|
|
141
|
+
def _edit_payloads(tool_name: str, tool_input: dict) -> list[str]:
|
|
142
|
+
"""The text this tool call is about to write, per tool shape."""
|
|
143
|
+
if tool_name == "Write":
|
|
144
|
+
return [str(tool_input.get("content", ""))]
|
|
145
|
+
if tool_name == "Edit":
|
|
146
|
+
return [str(tool_input.get("new_string", ""))]
|
|
147
|
+
if tool_name == "MultiEdit":
|
|
148
|
+
edits = tool_input.get("edits") or []
|
|
149
|
+
return [str(e.get("new_string", "")) for e in edits if isinstance(e, dict)]
|
|
150
|
+
return []
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def is_heuristic_ui_file(
|
|
154
|
+
file_path: str, tool_name: str, tool_input: dict
|
|
155
|
+
) -> bool:
|
|
156
|
+
"""WARN-only UI detection for .ts/.js: filename or visible UI content."""
|
|
157
|
+
path = Path(file_path)
|
|
158
|
+
if path.suffix.lower() not in _HEURISTIC_SUFFIXES:
|
|
159
|
+
return False
|
|
160
|
+
if _UI_FILENAME_RE.search(file_path.replace("\\", "/")):
|
|
161
|
+
return True
|
|
162
|
+
return any(
|
|
163
|
+
_UI_CONTENT_RE.search(payload)
|
|
164
|
+
for payload in _edit_payloads(tool_name, tool_input)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _classify_marker(messages: list[str]) -> tuple[str | None, str]:
|
|
169
|
+
"""Return (matched_marker, kind) — kind: structured|legacy|trivial|none.
|
|
170
|
+
|
|
171
|
+
A structured marker anywhere in the window wins over legacy ones.
|
|
172
|
+
"""
|
|
173
|
+
legacy: str | None = None
|
|
105
174
|
for message in messages:
|
|
106
|
-
match
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
175
|
+
for match in DESIGN_LINE_RE.finditer(message):
|
|
176
|
+
tail = match.group(1)
|
|
177
|
+
if _BENCHMARK_KEY_RE.search(tail) and _SKILLS_KEY_RE.search(tail):
|
|
178
|
+
return match.group(0).strip(), "structured"
|
|
179
|
+
if tail.strip() and legacy is None:
|
|
180
|
+
legacy = match.group(0).strip()
|
|
181
|
+
trivial = TRIVIAL_RE.search(message)
|
|
182
|
+
if trivial:
|
|
183
|
+
return trivial.group(0), "trivial"
|
|
184
|
+
if legacy is not None:
|
|
185
|
+
return legacy, "legacy"
|
|
186
|
+
return None, "none"
|
|
110
187
|
|
|
111
188
|
|
|
112
189
|
def evaluate(
|
|
@@ -119,25 +196,41 @@ def evaluate(
|
|
|
119
196
|
) -> Decision:
|
|
120
197
|
"""Evaluate one tool call against the frontend excellence gate."""
|
|
121
198
|
file_path = str(tool_input.get("file_path", ""))
|
|
122
|
-
if tool_name not in _GATED_TOOLS
|
|
199
|
+
if tool_name not in _GATED_TOOLS:
|
|
200
|
+
return Decision(allow=True, reason="not-ui-scope", target_file=file_path)
|
|
201
|
+
if is_ui_file(file_path):
|
|
202
|
+
ui_scope = "suffix"
|
|
203
|
+
elif is_heuristic_ui_file(file_path, tool_name, tool_input):
|
|
204
|
+
ui_scope = "heuristic"
|
|
205
|
+
else:
|
|
123
206
|
return Decision(allow=True, reason="not-ui-scope", target_file=file_path)
|
|
124
207
|
mode = _mode()
|
|
125
208
|
if mode == "off":
|
|
126
209
|
return Decision(allow=True, reason="flag-off", mode=mode,
|
|
127
|
-
target_file=file_path)
|
|
210
|
+
target_file=file_path, ui_scope=ui_scope)
|
|
128
211
|
if os.environ.get("ARKA_BYPASS_DESIGN") == "1":
|
|
129
212
|
return Decision(allow=True, reason="env-bypass", mode=mode,
|
|
130
|
-
target_file=file_path)
|
|
213
|
+
target_file=file_path, ui_scope=ui_scope)
|
|
131
214
|
if messages is None:
|
|
132
215
|
messages = _load_last_assistant_messages(
|
|
133
216
|
transcript_path, ASSISTANT_WINDOW
|
|
134
217
|
)
|
|
135
|
-
marker =
|
|
136
|
-
if
|
|
218
|
+
marker, kind = _classify_marker(messages)
|
|
219
|
+
if kind in ("structured", "trivial"):
|
|
137
220
|
return Decision(allow=True, reason="design-evidence", mode=mode,
|
|
138
|
-
target_file=file_path, marker_found=marker
|
|
139
|
-
|
|
140
|
-
|
|
221
|
+
target_file=file_path, marker_found=marker,
|
|
222
|
+
marker_kind=kind, ui_scope=ui_scope)
|
|
223
|
+
# Heuristic scope is WARN-only by design: it never denies, even in
|
|
224
|
+
# hard mode — its telemetry (ui_scope=heuristic) informs whether it
|
|
225
|
+
# ever graduates to denying scope.
|
|
226
|
+
deniable = mode == "hard" and ui_scope == "suffix"
|
|
227
|
+
if kind == "legacy":
|
|
228
|
+
return Decision(allow=not deniable, reason="legacy-marker", mode=mode,
|
|
229
|
+
target_file=file_path, marker_found=marker,
|
|
230
|
+
marker_kind=kind, ui_scope=ui_scope)
|
|
231
|
+
return Decision(allow=not deniable, reason="no-design-marker",
|
|
232
|
+
mode=mode, target_file=file_path, marker_kind=kind,
|
|
233
|
+
ui_scope=ui_scope)
|
|
141
234
|
|
|
142
235
|
|
|
143
236
|
def record_telemetry(session_id: str, tool: str, decision: Decision) -> None:
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Frontend-gate telemetry summarizer (Excellence Reform PR-D2).
|
|
2
|
+
|
|
3
|
+
Reads ``~/.arkaos/telemetry/frontend-gate.jsonl`` (the JSONL stream the
|
|
4
|
+
PreToolUse frontend gate appends on every gated decision) and produces
|
|
5
|
+
the evidence artifact for the WARN→hard flip decision: counts by
|
|
6
|
+
reason / marker_kind / mode / ui_scope, plus the would-have-denied rate
|
|
7
|
+
(suffix-scope events that hard mode would deny today).
|
|
8
|
+
|
|
9
|
+
Mirrors ``core.governance.specialist_telemetry`` so periods,
|
|
10
|
+
malformed-line tolerance, and zero-division safety behave the same way
|
|
11
|
+
across telemetry surfaces. Read-only.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
from collections import Counter
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from datetime import datetime, timedelta, timezone
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
DEFAULT_PATH: Path = (
|
|
24
|
+
Path.home() / ".arkaos" / "telemetry" / "frontend-gate.jsonl"
|
|
25
|
+
)
|
|
26
|
+
_VALID_PERIODS: frozenset[str] = frozenset({"today", "week", "month", "all"})
|
|
27
|
+
|
|
28
|
+
# Reasons hard mode denies on suffix scope — the flip's FP surface.
|
|
29
|
+
_WOULD_DENY_REASONS = frozenset({"no-design-marker", "legacy-marker"})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class FrontendGateSummary:
|
|
34
|
+
"""Aggregated frontend-gate telemetry over a time slice."""
|
|
35
|
+
|
|
36
|
+
period: str
|
|
37
|
+
total_events: int
|
|
38
|
+
by_reason: list[tuple[str, int]] = field(default_factory=list)
|
|
39
|
+
by_marker_kind: list[tuple[str, int]] = field(default_factory=list)
|
|
40
|
+
by_mode: list[tuple[str, int]] = field(default_factory=list)
|
|
41
|
+
by_ui_scope: list[tuple[str, int]] = field(default_factory=list)
|
|
42
|
+
would_deny_events: int = 0
|
|
43
|
+
would_deny_rate: float = 0.0
|
|
44
|
+
corrupt_line_count: int = 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def summarise(period: str, *, path: Path | None = None) -> FrontendGateSummary:
|
|
48
|
+
if period not in _VALID_PERIODS:
|
|
49
|
+
raise ValueError(f"invalid period: {period!r}")
|
|
50
|
+
src = path or DEFAULT_PATH
|
|
51
|
+
cutoff = _period_cutoff(period)
|
|
52
|
+
entries, corrupt = _read_jsonl(src, cutoff)
|
|
53
|
+
return _build_summary(period, entries, corrupt)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _period_cutoff(period: str, now: datetime | None = None) -> datetime | None:
|
|
57
|
+
ref = now or datetime.now(timezone.utc)
|
|
58
|
+
if period == "today":
|
|
59
|
+
return ref.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
60
|
+
if period == "week":
|
|
61
|
+
return ref - timedelta(days=7)
|
|
62
|
+
if period == "month":
|
|
63
|
+
return ref - timedelta(days=30)
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _read_jsonl(
|
|
68
|
+
src: Path, cutoff: datetime | None
|
|
69
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
70
|
+
if not src.exists():
|
|
71
|
+
return [], 0
|
|
72
|
+
entries: list[dict[str, Any]] = []
|
|
73
|
+
corrupt = 0
|
|
74
|
+
try:
|
|
75
|
+
with src.open("r", encoding="utf-8", errors="replace") as fh:
|
|
76
|
+
for line in fh:
|
|
77
|
+
if not line.strip():
|
|
78
|
+
continue
|
|
79
|
+
try:
|
|
80
|
+
entry = json.loads(line)
|
|
81
|
+
except json.JSONDecodeError:
|
|
82
|
+
corrupt += 1
|
|
83
|
+
continue
|
|
84
|
+
if cutoff is not None:
|
|
85
|
+
ts_raw = entry.get("ts", "")
|
|
86
|
+
try:
|
|
87
|
+
ts = datetime.fromisoformat(ts_raw)
|
|
88
|
+
except (TypeError, ValueError):
|
|
89
|
+
corrupt += 1
|
|
90
|
+
continue
|
|
91
|
+
if ts < cutoff:
|
|
92
|
+
continue
|
|
93
|
+
entries.append(entry)
|
|
94
|
+
except OSError:
|
|
95
|
+
return entries, corrupt
|
|
96
|
+
return entries, corrupt
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _build_summary(
|
|
100
|
+
period: str, entries: list[dict[str, Any]], corrupt: int
|
|
101
|
+
) -> FrontendGateSummary:
|
|
102
|
+
# Only gated UI events matter for the flip; not-ui-scope is noise.
|
|
103
|
+
gated = [e for e in entries if e.get("reason") != "not-ui-scope"]
|
|
104
|
+
total = len(gated)
|
|
105
|
+
reason_counter: Counter[str] = Counter()
|
|
106
|
+
kind_counter: Counter[str] = Counter()
|
|
107
|
+
mode_counter: Counter[str] = Counter()
|
|
108
|
+
scope_counter: Counter[str] = Counter()
|
|
109
|
+
would_deny = 0
|
|
110
|
+
for e in gated:
|
|
111
|
+
reason = str(e.get("reason", "unknown"))
|
|
112
|
+
reason_counter[reason] += 1
|
|
113
|
+
kind_counter[str(e.get("marker_kind", "unknown"))] += 1
|
|
114
|
+
mode_counter[str(e.get("mode", "unknown"))] += 1
|
|
115
|
+
scope = str(e.get("ui_scope", "suffix"))
|
|
116
|
+
scope_counter[scope] += 1
|
|
117
|
+
if reason in _WOULD_DENY_REASONS and scope == "suffix":
|
|
118
|
+
would_deny += 1
|
|
119
|
+
return FrontendGateSummary(
|
|
120
|
+
period=period,
|
|
121
|
+
total_events=total,
|
|
122
|
+
by_reason=reason_counter.most_common(),
|
|
123
|
+
by_marker_kind=kind_counter.most_common(),
|
|
124
|
+
by_mode=mode_counter.most_common(),
|
|
125
|
+
by_ui_scope=scope_counter.most_common(),
|
|
126
|
+
would_deny_events=would_deny,
|
|
127
|
+
would_deny_rate=(would_deny / total) if total else 0.0,
|
|
128
|
+
corrupt_line_count=corrupt,
|
|
129
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""CLI front-end for frontend-gate telemetry (WARN→hard flip evidence).
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python -m core.workflow.frontend_gate_telemetry_cli today
|
|
5
|
+
python -m core.workflow.frontend_gate_telemetry_cli week
|
|
6
|
+
python -m core.workflow.frontend_gate_telemetry_cli month
|
|
7
|
+
python -m core.workflow.frontend_gate_telemetry_cli all
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from core.workflow.frontend_gate_telemetry import summarise
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _format_human(summary) -> str:
|
|
18
|
+
pct = f"{summary.would_deny_rate * 100:.1f}%"
|
|
19
|
+
lines = [
|
|
20
|
+
f"Frontend Gate Telemetry — {summary.period}",
|
|
21
|
+
f" Gated UI events: {summary.total_events}",
|
|
22
|
+
f" Would-have-denied: {summary.would_deny_events} ({pct}) "
|
|
23
|
+
f"[suffix scope, hard-flip evidence]",
|
|
24
|
+
]
|
|
25
|
+
for label, pairs in (
|
|
26
|
+
("By reason", summary.by_reason),
|
|
27
|
+
("By marker kind", summary.by_marker_kind),
|
|
28
|
+
("By mode", summary.by_mode),
|
|
29
|
+
("By UI scope", summary.by_ui_scope),
|
|
30
|
+
):
|
|
31
|
+
if pairs:
|
|
32
|
+
lines.append(f" {label}:")
|
|
33
|
+
for key, count in pairs:
|
|
34
|
+
lines.append(f" - {key}: {count}")
|
|
35
|
+
if summary.corrupt_line_count:
|
|
36
|
+
lines.append(f" Corrupt lines: {summary.corrupt_line_count}")
|
|
37
|
+
return "\n".join(lines)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def main(argv: list[str] | None = None) -> int:
|
|
41
|
+
args = argv if argv is not None else sys.argv[1:]
|
|
42
|
+
period = args[0] if args else "today"
|
|
43
|
+
try:
|
|
44
|
+
summary = summarise(period)
|
|
45
|
+
except ValueError as exc:
|
|
46
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
47
|
+
return 2
|
|
48
|
+
print(_format_human(summary))
|
|
49
|
+
return 0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__": # pragma: no cover
|
|
53
|
+
sys.exit(main())
|
|
@@ -134,3 +134,76 @@ write material findings back to `[[Area 02 - Design]]`:
|
|
|
134
134
|
- Microinteractions (trigger→rules→feedback→loops/modes)
|
|
135
135
|
- Color theory & typography science (OKLCH, harmony, modular scale)
|
|
136
136
|
- Gestalt & visual hierarchy (F/Z scanning, proximity, contrast)
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 8. Anti-Default Doctrine (NON-NEGOTIABLE — excellence-mandate)
|
|
141
|
+
|
|
142
|
+
AI-generated design clusters around three default looks. Producing any of
|
|
143
|
+
them WITHOUT a brief that explicitly asks for it is a constitution
|
|
144
|
+
violation (`excellence-mandate`: "no default-looking output"):
|
|
145
|
+
|
|
146
|
+
1. **Cream + serif + terracotta** — warm cream background (≈ `#F4F1EA`),
|
|
147
|
+
high-contrast serif display, terracotta accent.
|
|
148
|
+
2. **Dark + acid accent** — near-black background with a single bright
|
|
149
|
+
acid-green or vermilion accent.
|
|
150
|
+
3. **Broadsheet** — hairline rules, zero border-radius, dense
|
|
151
|
+
newspaper-like columns.
|
|
152
|
+
|
|
153
|
+
These are defaults, not choices. Where the brief pins a direction, the
|
|
154
|
+
brief wins — including when it asks for one of these. Where an axis is
|
|
155
|
+
free, never spend it on a default.
|
|
156
|
+
|
|
157
|
+
**Token-plan-then-critique loop** (before writing any code or asset):
|
|
158
|
+
|
|
159
|
+
- **Color**: palette as 4–6 NAMED hex values (name = role, not "blue").
|
|
160
|
+
- **Type**: faces for 2+ roles — characterful display used with restraint,
|
|
161
|
+
complementary body, utility for captions/data when needed.
|
|
162
|
+
- **Layout**: one-sentence concept + ASCII wireframe to compare options.
|
|
163
|
+
- **Signature**: the ONE element this design will be remembered by.
|
|
164
|
+
Spend your boldness there; keep everything else quiet and disciplined.
|
|
165
|
+
|
|
166
|
+
Then critique the plan BEFORE building: *"would this exact design appear
|
|
167
|
+
for any other brief?"* If yes, revise and state what changed and why.
|
|
168
|
+
Quality floor, never announced: responsive to mobile, visible keyboard
|
|
169
|
+
focus, `prefers-reduced-motion` respected, WCAG AA contrast.
|
|
170
|
+
|
|
171
|
+
## 9. Design Marker Contract (`[arka:design]`)
|
|
172
|
+
|
|
173
|
+
Every UI/design-producing turn MUST emit the structured marker on its own
|
|
174
|
+
line BEFORE the first file edit:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
[arka:design] benchmark=<Company> skills=<comma,list> tokens=<path|none>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- `benchmark=` — named reference company (Linear, Stripe, Vercel, Notion,
|
|
181
|
+
Airbnb…). Pick it FIRST and state what its design lead would reject in
|
|
182
|
+
your plan.
|
|
183
|
+
- `skills=` — the design skills ACTUALLY loaded this session via the
|
|
184
|
+
Skill tool (e.g. `frontend-design,ui-ux-pro-max,gsap-core`). If a
|
|
185
|
+
plugin is not installed, be honest: `skills=degraded:<missing-name>`
|
|
186
|
+
and fall back to §3 + §8 of this reference. Never claim a load that
|
|
187
|
+
did not happen; never silently proceed as if it had.
|
|
188
|
+
- `tokens=` — path of the project design-system source
|
|
189
|
+
(`design-system.yaml`, tokens file), or `none` + one-line justification.
|
|
190
|
+
|
|
191
|
+
A bare `[arka:design] <token>` is a LEGACY marker: tolerated in WARN mode,
|
|
192
|
+
counted separately in telemetry, and treated as missing once the frontend
|
|
193
|
+
gate goes hard.
|
|
194
|
+
|
|
195
|
+
## 10. Aesthetic Directions Catalog (optional, curated)
|
|
196
|
+
|
|
197
|
+
When a project has NO design system yet and the brief leaves the visual
|
|
198
|
+
axis free, seed a direction from the curated TypeUI catalog
|
|
199
|
+
(`npx typeui.sh pull <slug>` — MIT, bergside/awesome-design-skills):
|
|
200
|
+
|
|
201
|
+
`editorial` · `brutalism` · `neobrutalism` · `bento` · `expressive` ·
|
|
202
|
+
`dramatic` — anti-default directions; `shadcn` · `mono` — clean baselines.
|
|
203
|
+
|
|
204
|
+
Catalog tokens are a SEED for the project's own token system, never the
|
|
205
|
+
final design system. Motion is absent from the catalog — that is GSAP's
|
|
206
|
+
job: load `gsap-core` + `gsap-timeline` for any animation work,
|
|
207
|
+
`gsap-scrolltrigger`/`gsap-plugins` as the work demands, `gsap-react` on
|
|
208
|
+
React, and treat `gsap-performance` as the review bar
|
|
209
|
+
(`npx skills add https://github.com/greensock/gsap-skills` when absent).
|
|
@@ -20,11 +20,84 @@ allowed-tools: [Read, Write, Edit, Bash, Grep, Glob, Agent, WebFetch, WebSearch]
|
|
|
20
20
|
# Colors — `/brand colors <mood>`
|
|
21
21
|
|
|
22
22
|
> **Agent:** Isabel (Visual Designer) | **Framework:** Color Theory + Accessibility
|
|
23
|
+
> **Squad reference:** `departments/brand/references/uiux-knowledge-and-tools.md` (§3 tokens, §8 anti-default, §9 marker)
|
|
23
24
|
|
|
24
|
-
##
|
|
25
|
+
## Load design intelligence (MANDATORY — excellence-mandate)
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
Do this BEFORE proposing a single hex value, in this order, and record
|
|
28
|
+
what actually loaded:
|
|
29
|
+
|
|
30
|
+
1. **`Skill(frontend-design)`** — Anthropic's anti-default doctrine: the
|
|
31
|
+
three AI-default looks, token-plan-then-critique, signature discipline.
|
|
32
|
+
2. **`Skill(ui-ux-pro-max)`** — curated design data: 161 palettes,
|
|
33
|
+
57 font pairings, 99 UX guidelines; use its palette data as
|
|
34
|
+
comparative evidence, never as a menu to copy from.
|
|
35
|
+
3. **Read the project design system** — `design-system.yaml`, token
|
|
36
|
+
files, or the brandbook; §3 of the squad reference is the in-repo
|
|
37
|
+
fallback when the Obsidian note is unreachable.
|
|
38
|
+
|
|
39
|
+
### Graceful degradation (honest, never silent)
|
|
40
|
+
|
|
41
|
+
If a plugin skill is NOT installed: say so explicitly, fall back to §3 +
|
|
42
|
+
§8 of the squad reference, and emit the marker with
|
|
43
|
+
`skills=degraded:<missing-name>`. Never claim a load that did not
|
|
44
|
+
happen; never proceed as if it had.
|
|
45
|
+
|
|
46
|
+
## Benchmark first
|
|
47
|
+
|
|
48
|
+
NAME the reference company (Linear, Stripe, Vercel, Notion, Airbnb…)
|
|
49
|
+
whose color craft this brand should be judged against, and write one
|
|
50
|
+
line on what that company's design lead would reject in your current
|
|
51
|
+
direction. No named benchmark, no palette.
|
|
52
|
+
|
|
53
|
+
## Anti-default check
|
|
54
|
+
|
|
55
|
+
Before finalizing: which of the three AI-default looks (§8 — cream +
|
|
56
|
+
serif + terracotta; dark + acid accent; broadsheet) is this palette
|
|
57
|
+
drifting toward? Self-test: *"would this exact palette appear for any
|
|
58
|
+
other brief?"* If yes, revise and state what changed and why.
|
|
59
|
+
|
|
60
|
+
## Design marker (before any file edit)
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
[arka:design] benchmark=<Company> skills=<comma,list> tokens=<path|none>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Emit on its own line BEFORE the first Write/Edit. Full contract: §9 of
|
|
67
|
+
the squad reference.
|
|
68
|
+
|
|
69
|
+
## Workflow
|
|
70
|
+
|
|
71
|
+
1. **KB + brief** — search the vault for existing brand/palette notes;
|
|
72
|
+
extract the mood words from the brief and the subject's own world
|
|
73
|
+
(its materials, environments, vernacular — that is where
|
|
74
|
+
non-default color comes from).
|
|
75
|
+
2. **Anchor** — pick the anchor hue from the subject, not from fashion.
|
|
76
|
+
State the reasoning in one sentence.
|
|
77
|
+
3. **Build the palette as 4–6 NAMED roles** (name = function, never
|
|
78
|
+
"blue"): `surface` (canvas), `ink` (text), `primary` (identity),
|
|
79
|
+
`accent` (signal, ≤ 10–15% usage), plus optional `positive`/`danger`
|
|
80
|
+
semantics. Derive tints/shades in OKLCH so lightness steps are
|
|
81
|
+
perceptually even; give each role a hex.
|
|
82
|
+
4. **WCAG pairs table** — for every text-bearing combination, compute
|
|
83
|
+
the contrast ratio and mark AA/AAA for body and large text. Any pair
|
|
84
|
+
below AA is redesigned, not footnoted.
|
|
85
|
+
5. **Dark-mode variants** — restate every role for dark surfaces (do
|
|
86
|
+
not naive-invert; re-pick lightness in OKLCH).
|
|
87
|
+
6. **Usage guidelines** — the 60/25/15 composition ratio (§3), do/don't
|
|
88
|
+
examples, and which role carries the signature element.
|
|
89
|
+
7. **Critique pass** — rerun the anti-default self-test against the
|
|
90
|
+
named benchmark before delivering.
|
|
27
91
|
|
|
28
92
|
## Output
|
|
29
93
|
|
|
30
|
-
|
|
94
|
+
A palette block ready for token systems (DTCG-shaped: role → hex +
|
|
95
|
+
OKLCH), the WCAG pairs table, dark-mode variants, and usage guidelines
|
|
96
|
+
— saved to Obsidian under the project's Brand path and referenced by
|
|
97
|
+
`tokens=` in the design marker of any implementation that follows.
|
|
98
|
+
|
|
99
|
+
| Deliverable | Format |
|
|
100
|
+
|---|---|
|
|
101
|
+
| Palette (4–6 named roles) | hex + OKLCH per role, light + dark |
|
|
102
|
+
| Contrast evidence | WCAG pairs table (AA/AAA per combination) |
|
|
103
|
+
| Usage guide | 60/25/15 ratio, do/don't, signature carrier |
|