arkaos 4.11.0 → 4.13.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/README.md +2 -2
- package/VERSION +1 -1
- package/arka/SKILL.md +10 -1
- package/arka/skills/flow/SKILL.md +34 -5
- package/arka/skills/recipes/SKILL.md +69 -0
- package/arka/skills/refine/SKILL.md +81 -0
- 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 +9 -0
- package/config/mcp-policy.yaml +16 -0
- package/config/output-styles/arkaos.md +52 -0
- package/core/evals/__init__.py +4 -0
- package/core/evals/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/evals/__pycache__/record_cli.cpython-313.pyc +0 -0
- package/core/evals/__pycache__/verdict_labels.cpython-313.pyc +0 -0
- package/core/evals/record_cli.py +41 -8
- package/core/evals/verdict_labels.py +53 -13
- package/core/forge/__pycache__/complexity.cpython-313.pyc +0 -0
- package/core/forge/complexity.py +9 -0
- package/core/governance/__pycache__/evidence_checks.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/judge.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 +130 -4
- package/core/governance/judge.py +95 -0
- package/core/governance/phantom_action_check.py +41 -0
- package/core/hooks/__pycache__/post_tool_use.cpython-313.pyc +0 -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/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/core/hooks/post_tool_use.py +13 -0
- package/core/hooks/pre_tool_use.py +8 -2
- package/core/hooks/stop.py +23 -0
- package/core/hooks/user_prompt_submit.py +69 -0
- package/core/knowledge/__pycache__/recipes.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/recipes_cli.cpython-313.pyc +0 -0
- package/core/knowledge/recipes.py +237 -0
- package/core/knowledge/recipes_cli.py +99 -0
- package/core/runtime/__pycache__/model_routing_context.cpython-313.pyc +0 -0
- package/core/runtime/model_routing_context.py +4 -0
- package/core/shared/__pycache__/safe_session_id.cpython-313.pyc +0 -0
- package/core/shared/__pycache__/safe_session_id.cpython-314.pyc +0 -0
- package/core/shared/safe_session_id.py +11 -3
- package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/pattern_library_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/recipe_layer.cpython-313.pyc +0 -0
- package/core/synapse/engine.py +6 -0
- package/core/synapse/pattern_library_layer.py +10 -2
- package/core/synapse/recipe_layer.py +114 -0
- package/core/sync/__pycache__/mcp_optimizer.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/mcp_syncer.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/settings_syncer.cpython-313.pyc +0 -0
- package/core/sync/mcp_optimizer.py +4 -1
- package/core/sync/mcp_syncer.py +50 -6
- package/core/sync/settings_syncer.py +9 -2
- package/core/workflow/__pycache__/flow_enforcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/flow_enforcer.cpython-314.pyc +0 -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-313.pyc +0 -0
- package/core/workflow/__pycache__/plan_approval.cpython-314.pyc +0 -0
- package/core/workflow/flow_enforcer.py +63 -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/core/workflow/plan_approval.py +185 -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/SKILL.md +10 -0
- package/departments/dev/agents/frontend-dev.yaml +1 -1
- package/departments/dev/skills/onboard/SKILL.md +6 -2
- package/departments/dev/skills/refactor-plan/SKILL.md +7 -0
- package/departments/dev/skills/scaffold/SKILL.md +6 -0
- package/departments/quality/SKILL.md +16 -0
- package/installer/doctor.js +63 -1
- package/installer/index.js +20 -0
- package/installer/output-style.js +92 -0
- package/installer/update.js +19 -0
- package/installer/worktree-baseref.js +8 -2
- package/knowledge/agents-registry-v2.json +209 -14
- package/knowledge/commands-keywords.json +1390 -232
- package/knowledge/commands-registry.json +171 -4
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/config/mcp-policy.yaml
CHANGED
|
@@ -12,6 +12,7 @@ policies:
|
|
|
12
12
|
- match:
|
|
13
13
|
stack_includes: [laravel, php]
|
|
14
14
|
active:
|
|
15
|
+
- codebase-memory
|
|
15
16
|
- arka-prompts
|
|
16
17
|
- obsidian
|
|
17
18
|
- context7
|
|
@@ -36,12 +37,14 @@ policies:
|
|
|
36
37
|
- teams
|
|
37
38
|
- shopify-dev
|
|
38
39
|
- mirakl
|
|
40
|
+
- higgsfield
|
|
39
41
|
ambiguous: []
|
|
40
42
|
|
|
41
43
|
# --- Frontend: Nuxt / Vue ---
|
|
42
44
|
- match:
|
|
43
45
|
stack_includes: [nuxt, vue]
|
|
44
46
|
active:
|
|
47
|
+
- codebase-memory
|
|
45
48
|
- arka-prompts
|
|
46
49
|
- obsidian
|
|
47
50
|
- context7
|
|
@@ -66,12 +69,14 @@ policies:
|
|
|
66
69
|
- teams
|
|
67
70
|
- shopify-dev
|
|
68
71
|
- mirakl
|
|
72
|
+
- higgsfield
|
|
69
73
|
ambiguous: []
|
|
70
74
|
|
|
71
75
|
# --- Frontend: React / Next ---
|
|
72
76
|
- match:
|
|
73
77
|
stack_includes: [next, react]
|
|
74
78
|
active:
|
|
79
|
+
- codebase-memory
|
|
75
80
|
- arka-prompts
|
|
76
81
|
- obsidian
|
|
77
82
|
- context7
|
|
@@ -96,6 +101,7 @@ policies:
|
|
|
96
101
|
- teams
|
|
97
102
|
- shopify-dev
|
|
98
103
|
- mirakl
|
|
104
|
+
- higgsfield
|
|
99
105
|
ambiguous: []
|
|
100
106
|
|
|
101
107
|
# --- E-commerce (Shopify / Mirakl) ---
|
|
@@ -111,6 +117,7 @@ policies:
|
|
|
111
117
|
- mirakl
|
|
112
118
|
- sentry
|
|
113
119
|
deferred:
|
|
120
|
+
- codebase-memory
|
|
114
121
|
- memory-bank
|
|
115
122
|
- postgres
|
|
116
123
|
- supabase
|
|
@@ -126,12 +133,14 @@ policies:
|
|
|
126
133
|
- discord
|
|
127
134
|
- whatsapp
|
|
128
135
|
- teams
|
|
136
|
+
- higgsfield
|
|
129
137
|
ambiguous: []
|
|
130
138
|
|
|
131
139
|
# --- Python projects ---
|
|
132
140
|
- match:
|
|
133
141
|
stack_includes: [python]
|
|
134
142
|
active:
|
|
143
|
+
- codebase-memory
|
|
135
144
|
- arka-prompts
|
|
136
145
|
- obsidian
|
|
137
146
|
- context7
|
|
@@ -156,6 +165,7 @@ policies:
|
|
|
156
165
|
- teams
|
|
157
166
|
- shopify-dev
|
|
158
167
|
- mirakl
|
|
168
|
+
- higgsfield
|
|
159
169
|
ambiguous: []
|
|
160
170
|
|
|
161
171
|
# --- Ecosystem: marketing ---
|
|
@@ -169,6 +179,7 @@ policies:
|
|
|
169
179
|
- firecrawl
|
|
170
180
|
- clickup
|
|
171
181
|
deferred:
|
|
182
|
+
- codebase-memory
|
|
172
183
|
- memory-bank
|
|
173
184
|
- gh-grep
|
|
174
185
|
- postgres
|
|
@@ -186,6 +197,7 @@ policies:
|
|
|
186
197
|
- teams
|
|
187
198
|
- shopify-dev
|
|
188
199
|
- mirakl
|
|
200
|
+
- higgsfield
|
|
189
201
|
ambiguous: []
|
|
190
202
|
|
|
191
203
|
# --- Ecosystem: content ---
|
|
@@ -197,7 +209,9 @@ policies:
|
|
|
197
209
|
- context7
|
|
198
210
|
- canva
|
|
199
211
|
- firecrawl
|
|
212
|
+
- higgsfield
|
|
200
213
|
deferred:
|
|
214
|
+
- codebase-memory
|
|
201
215
|
- memory-bank
|
|
202
216
|
- gh-grep
|
|
203
217
|
- postgres
|
|
@@ -227,6 +241,7 @@ policies:
|
|
|
227
241
|
- context7
|
|
228
242
|
- gh-grep
|
|
229
243
|
deferred:
|
|
244
|
+
- codebase-memory
|
|
230
245
|
- memory-bank
|
|
231
246
|
- postgres
|
|
232
247
|
- supabase
|
|
@@ -246,4 +261,5 @@ policies:
|
|
|
246
261
|
- teams
|
|
247
262
|
- shopify-dev
|
|
248
263
|
- mirakl
|
|
264
|
+
- higgsfield
|
|
249
265
|
ambiguous: []
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ArkaOS
|
|
3
|
+
description: ArkaOS Operating System personality — squad-driven, framework-backed, branded responses
|
|
4
|
+
keep-coding-instructions: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ArkaOS Output Style
|
|
8
|
+
|
|
9
|
+
You are operating as ArkaOS — the Operating System for AI Agent Teams. You orchestrate the ArkaOS specialized agent departments.
|
|
10
|
+
|
|
11
|
+
## Core Behavior
|
|
12
|
+
|
|
13
|
+
- Every request routes through the appropriate department squad. Never respond as a generic assistant.
|
|
14
|
+
- Use framework-backed reasoning: cite the framework behind your recommendations (SOLID, StoryBrand, Porter's Five Forces, AARRR, etc.)
|
|
15
|
+
- Apply critical thinking before executing. Ask clarifying questions when intent is ambiguous.
|
|
16
|
+
- Quality Gate mindset: everything you produce should be production-ready, reviewed, and validated.
|
|
17
|
+
|
|
18
|
+
## Challenge the User
|
|
19
|
+
|
|
20
|
+
- When the user's request is technically wrong, mathematically impossible,
|
|
21
|
+
or would ship a worse product, challenge it BEFORE executing — pushback
|
|
22
|
+
protocol, constitution rule `arkaos-not-yes-man`.
|
|
23
|
+
- Ground the challenge in evidence or a reference company (Stripe, Linear,
|
|
24
|
+
Vercel, Notion…), never in opinion alone.
|
|
25
|
+
- Insistence is not new evidence: never grow more agreeable under pressure.
|
|
26
|
+
If the user directs execution anyway, execute under explicit objection,
|
|
27
|
+
on record.
|
|
28
|
+
- Apply the same standard to agents and subagents: judge their plans and
|
|
29
|
+
outputs adversarially — delivering fast is worthless when the solution
|
|
30
|
+
is wrong.
|
|
31
|
+
|
|
32
|
+
## Response Style
|
|
33
|
+
|
|
34
|
+
- Be direct and concise. Lead with the action or answer, not preamble.
|
|
35
|
+
- When routing to a department, announce it: "Routing to [Department] — [Lead Agent] coordinating."
|
|
36
|
+
- Use structured output: headers, tables, bullet points. No walls of text.
|
|
37
|
+
- For code: follow SOLID principles, clean code, conventional commits.
|
|
38
|
+
- For content: human-quality writing, no AI cliches, culturally appropriate for the target market.
|
|
39
|
+
|
|
40
|
+
## Squad Awareness
|
|
41
|
+
|
|
42
|
+
When handling requests, think in squads:
|
|
43
|
+
- Who is the department lead for this domain?
|
|
44
|
+
- Which specialists should execute?
|
|
45
|
+
- Does this need cross-department collaboration?
|
|
46
|
+
- Does this need Quality Gate review?
|
|
47
|
+
|
|
48
|
+
## Language
|
|
49
|
+
|
|
50
|
+
- Match the user's language (Portuguese or English).
|
|
51
|
+
- When writing in Portuguese, use European Portuguese (pt-PT), never Brazilian.
|
|
52
|
+
- Technical terms can stay in English when that's the industry standard.
|
package/core/evals/__init__.py
CHANGED
|
@@ -7,13 +7,17 @@ or local-model distillation consumes.
|
|
|
7
7
|
|
|
8
8
|
from core.evals.schema import EvalTask, load_eval_tasks
|
|
9
9
|
from core.evals.verdict_labels import (
|
|
10
|
+
load_judge_labels,
|
|
10
11
|
load_verdict_labels,
|
|
12
|
+
record_judge_label,
|
|
11
13
|
record_verdict_label,
|
|
12
14
|
)
|
|
13
15
|
|
|
14
16
|
__all__ = [
|
|
15
17
|
"EvalTask",
|
|
16
18
|
"load_eval_tasks",
|
|
19
|
+
"load_judge_labels",
|
|
17
20
|
"load_verdict_labels",
|
|
21
|
+
"record_judge_label",
|
|
18
22
|
"record_verdict_label",
|
|
19
23
|
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/core/evals/record_cli.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
"""Record a QGVerdict as an eval label
|
|
1
|
+
"""Record a QGVerdict or gate JudgeVerdict as an eval label.
|
|
2
2
|
|
|
3
|
-
Reads the reviewer's
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Reads the reviewer's verdict JSON from stdin (or --file) and appends it
|
|
4
|
+
to the matching label corpus (``--kind qg`` → qg-verdicts.jsonl,
|
|
5
|
+
``--kind judge`` → judge-verdicts.jsonl). Invoked by the orchestrator
|
|
6
|
+
right after a Quality Gate verdict or a gate-judge verdict lands (see
|
|
7
|
+
the Quality Gate and flow skill instructions), closing the "labels
|
|
8
|
+
gratuitos" loop from the evals ADR.
|
|
8
9
|
|
|
9
10
|
Unlike the underlying writer (telemetry contract: never raises), this
|
|
10
11
|
explicit CLI fails LOUDLY on invalid verdict JSON — a malformed label
|
|
@@ -20,13 +21,20 @@ from pathlib import Path
|
|
|
20
21
|
|
|
21
22
|
from pydantic import ValidationError
|
|
22
23
|
|
|
23
|
-
from core.evals.verdict_labels import record_verdict_label
|
|
24
|
+
from core.evals.verdict_labels import record_judge_label, record_verdict_label
|
|
25
|
+
from core.governance.judge import JudgeVerdict
|
|
24
26
|
from core.governance.qg_verdict import QGVerdict
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
def main(argv: list[str] | None = None) -> int:
|
|
28
30
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
29
31
|
parser.add_argument("--file", help="verdict JSON file (default: stdin)")
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--kind",
|
|
34
|
+
choices=("qg", "judge"),
|
|
35
|
+
default="qg",
|
|
36
|
+
help="qg = QGVerdict (default); judge = gate JudgeVerdict (PR2)",
|
|
37
|
+
)
|
|
30
38
|
parser.add_argument("--deliverable", default="")
|
|
31
39
|
parser.add_argument("--department", default="")
|
|
32
40
|
parser.add_argument("--eval-task-id", default="")
|
|
@@ -38,12 +46,17 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
38
46
|
if args.file
|
|
39
47
|
else sys.stdin.read()
|
|
40
48
|
)
|
|
49
|
+
if args.kind == "judge":
|
|
50
|
+
return _record_judge(raw, args)
|
|
51
|
+
return _record_qg(raw, args)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _record_qg(raw: str, args: argparse.Namespace) -> int:
|
|
41
55
|
try:
|
|
42
56
|
verdict = QGVerdict.model_validate(json.loads(raw))
|
|
43
57
|
except (json.JSONDecodeError, ValidationError) as exc:
|
|
44
58
|
print(f"error: invalid QGVerdict JSON — {exc}", file=sys.stderr)
|
|
45
59
|
return 1
|
|
46
|
-
|
|
47
60
|
record_verdict_label(
|
|
48
61
|
verdict,
|
|
49
62
|
deliverable=args.deliverable,
|
|
@@ -59,5 +72,25 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
59
72
|
return 0
|
|
60
73
|
|
|
61
74
|
|
|
75
|
+
def _record_judge(raw: str, args: argparse.Namespace) -> int:
|
|
76
|
+
try:
|
|
77
|
+
verdict = JudgeVerdict.model_validate(json.loads(raw))
|
|
78
|
+
except (json.JSONDecodeError, ValidationError) as exc:
|
|
79
|
+
print(f"error: invalid JudgeVerdict JSON — {exc}", file=sys.stderr)
|
|
80
|
+
return 1
|
|
81
|
+
record_judge_label(
|
|
82
|
+
verdict,
|
|
83
|
+
deliverable=args.deliverable,
|
|
84
|
+
department=args.department,
|
|
85
|
+
session_id=args.session_id,
|
|
86
|
+
)
|
|
87
|
+
print(json.dumps({
|
|
88
|
+
"recorded": True,
|
|
89
|
+
"verdict": verdict.verdict,
|
|
90
|
+
"gate": verdict.gate,
|
|
91
|
+
}))
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
|
|
62
95
|
if __name__ == "__main__":
|
|
63
96
|
raise SystemExit(main())
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
"""QG verdicts as free eval labels.
|
|
2
|
-
|
|
3
|
-
Every Quality Gate review
|
|
4
|
-
(
|
|
5
|
-
each review into a labeled example — the training/eval signal the
|
|
6
|
-
audit called "labels gratuitos".
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
"""QG and gate-judge verdicts as free eval labels.
|
|
2
|
+
|
|
3
|
+
Every Quality Gate review produces a structured ``QGVerdict`` and every
|
|
4
|
+
gate judge (Interaction Reform PR2) a ``JudgeVerdict``. Persisting them
|
|
5
|
+
turns each review into a labeled example — the training/eval signal the
|
|
6
|
+
E2E audit called "labels gratuitos". Two corpora, one module:
|
|
7
|
+
``qg-verdicts.jsonl`` and ``judge-verdicts.jsonl``. Append-only JSONL,
|
|
8
|
+
same conventions as the other ``~/.arkaos/telemetry`` writers: never
|
|
9
|
+
raises, fcntl advisory lock via ``llm_cost_telemetry._locked_append``
|
|
10
|
+
(reviews run as parallel subagents and verdict lines far exceed
|
|
11
|
+
atomic-write sizes, so unlocked appends could interleave and corrupt
|
|
12
|
+
the corpus), ``ARKA_QG_LABELS_PATH`` / ``ARKA_JUDGE_LABELS_PATH``
|
|
13
|
+
overrides for tests.
|
|
12
14
|
"""
|
|
13
15
|
|
|
14
16
|
from __future__ import annotations
|
|
@@ -19,12 +21,16 @@ from datetime import datetime, timezone
|
|
|
19
21
|
from pathlib import Path
|
|
20
22
|
from typing import Any
|
|
21
23
|
|
|
24
|
+
from core.governance.judge import JudgeVerdict
|
|
22
25
|
from core.governance.qg_verdict import QGVerdict
|
|
23
26
|
from core.runtime.llm_cost_telemetry import _locked_append
|
|
24
27
|
|
|
25
28
|
DEFAULT_LABELS_PATH = (
|
|
26
29
|
Path.home() / ".arkaos" / "telemetry" / "qg-verdicts.jsonl"
|
|
27
30
|
)
|
|
31
|
+
DEFAULT_JUDGE_LABELS_PATH = (
|
|
32
|
+
Path.home() / ".arkaos" / "telemetry" / "judge-verdicts.jsonl"
|
|
33
|
+
)
|
|
28
34
|
|
|
29
35
|
|
|
30
36
|
def _labels_path() -> Path:
|
|
@@ -32,6 +38,11 @@ def _labels_path() -> Path:
|
|
|
32
38
|
return Path(override) if override else DEFAULT_LABELS_PATH
|
|
33
39
|
|
|
34
40
|
|
|
41
|
+
def _judge_labels_path() -> Path:
|
|
42
|
+
override = os.environ.get("ARKA_JUDGE_LABELS_PATH", "").strip()
|
|
43
|
+
return Path(override) if override else DEFAULT_JUDGE_LABELS_PATH
|
|
44
|
+
|
|
45
|
+
|
|
35
46
|
def record_verdict_label(
|
|
36
47
|
verdict: QGVerdict,
|
|
37
48
|
deliverable: str = "",
|
|
@@ -55,9 +66,38 @@ def record_verdict_label(
|
|
|
55
66
|
return
|
|
56
67
|
|
|
57
68
|
|
|
69
|
+
def record_judge_label(
|
|
70
|
+
verdict: JudgeVerdict,
|
|
71
|
+
deliverable: str = "",
|
|
72
|
+
department: str = "",
|
|
73
|
+
session_id: str = "",
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Append one labeled gate-judge example. Never raises."""
|
|
76
|
+
try:
|
|
77
|
+
entry: dict[str, Any] = {
|
|
78
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
79
|
+
"deliverable": str(deliverable or ""),
|
|
80
|
+
"department": str(department or ""),
|
|
81
|
+
"session_id": str(session_id or ""),
|
|
82
|
+
**verdict.model_dump(),
|
|
83
|
+
}
|
|
84
|
+
with _locked_append(_judge_labels_path()) as fh:
|
|
85
|
+
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
86
|
+
except Exception: # noqa: BLE001 — telemetry must never raise
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
|
|
58
90
|
def load_verdict_labels(path: Path | None = None) -> list[dict[str, Any]]:
|
|
59
|
-
"""Read all labeled examples; malformed lines are skipped."""
|
|
60
|
-
|
|
91
|
+
"""Read all labeled QG examples; malformed lines are skipped."""
|
|
92
|
+
return _load_jsonl(path or _labels_path())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def load_judge_labels(path: Path | None = None) -> list[dict[str, Any]]:
|
|
96
|
+
"""Read all labeled gate-judge examples; malformed lines are skipped."""
|
|
97
|
+
return _load_jsonl(path or _judge_labels_path())
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _load_jsonl(target: Path) -> list[dict[str, Any]]:
|
|
61
101
|
if not target.exists():
|
|
62
102
|
return []
|
|
63
103
|
out: list[dict[str, Any]] = []
|
|
Binary file
|
package/core/forge/complexity.py
CHANGED
|
@@ -107,6 +107,15 @@ def _score_ambiguity(prompt: str, files: list[str]) -> int:
|
|
|
107
107
|
return max(0, min(100, score))
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
def score_prompt_ambiguity(prompt: str, files: list[str] | None = None) -> int:
|
|
111
|
+
"""Public 0-100 vagueness score for a user prompt.
|
|
112
|
+
|
|
113
|
+
Consumed by the UserPromptSubmit hook to suggest `/arka refine`
|
|
114
|
+
(Interaction Reform PR5) — regex-only, no I/O, hook-budget safe.
|
|
115
|
+
"""
|
|
116
|
+
return _score_ambiguity(prompt or "", files or [])
|
|
117
|
+
|
|
118
|
+
|
|
110
119
|
def _score_risk(prompt: str, files: list[str]) -> int:
|
|
111
120
|
score = 20
|
|
112
121
|
risk_matches = len(_RISK_KEYWORDS.findall(prompt))
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -28,6 +28,7 @@ import re
|
|
|
28
28
|
import shlex
|
|
29
29
|
import shutil
|
|
30
30
|
import subprocess
|
|
31
|
+
import time
|
|
31
32
|
from dataclasses import asdict, dataclass, field
|
|
32
33
|
from pathlib import Path
|
|
33
34
|
|
|
@@ -37,8 +38,16 @@ TIMEOUT_SECONDS = 300
|
|
|
37
38
|
COVERAGE_THRESHOLD = 80.0
|
|
38
39
|
ALL_CHECKS: tuple[str, ...] = (
|
|
39
40
|
"lint", "typecheck", "tests", "coverage", "security-grep", "spellcheck",
|
|
41
|
+
"ui-screenshot",
|
|
40
42
|
)
|
|
41
43
|
|
|
44
|
+
# ui-screenshot artifact contract (Excellence Reform PR-D3): captures land
|
|
45
|
+
# in <project>/.arka/evidence/ui/ per brand/design-review; the check only
|
|
46
|
+
# stats files (read-only), it never runs a browser.
|
|
47
|
+
UI_EVIDENCE_DIR = Path(".arka") / "evidence" / "ui"
|
|
48
|
+
UI_SCREENSHOT_WINDOW_HOURS = 24
|
|
49
|
+
UI_SCREENSHOT_MIN_BYTES = 10 * 1024
|
|
50
|
+
|
|
42
51
|
_MAX_SUMMARY_CHARS = 800
|
|
43
52
|
_MAX_GREP_HITS = 20
|
|
44
53
|
|
|
@@ -278,32 +287,88 @@ def _check_coverage(
|
|
|
278
287
|
return _skip("coverage", "no coverage.xml or junit.xml on disk")
|
|
279
288
|
|
|
280
289
|
|
|
290
|
+
def _grep_lines(path: Path, lines: list[str]) -> list[str]:
|
|
291
|
+
hits = []
|
|
292
|
+
for lineno_or_text in lines:
|
|
293
|
+
for name, pattern in _SECURITY_PATTERNS:
|
|
294
|
+
if pattern.search(lineno_or_text):
|
|
295
|
+
hits.append(f"{path} [{name}]: {lineno_or_text.strip()[:120]}")
|
|
296
|
+
return hits
|
|
297
|
+
|
|
298
|
+
|
|
281
299
|
def _grep_file(path: Path) -> list[str]:
|
|
282
300
|
try:
|
|
283
|
-
|
|
301
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
284
302
|
except OSError:
|
|
285
303
|
return []
|
|
286
304
|
hits = []
|
|
287
|
-
for lineno, line in enumerate(
|
|
305
|
+
for lineno, line in enumerate(text.splitlines(), start=1):
|
|
288
306
|
for name, pattern in _SECURITY_PATTERNS:
|
|
289
307
|
if pattern.search(line):
|
|
290
308
|
hits.append(f"{path}:{lineno} [{name}]")
|
|
291
309
|
return hits
|
|
292
310
|
|
|
293
311
|
|
|
312
|
+
def _diff_base(project_dir: Path) -> str | None:
|
|
313
|
+
"""Merge-base with the default branch, or None outside a usable repo."""
|
|
314
|
+
for ref in ("origin/master", "master", "origin/main", "main"):
|
|
315
|
+
proc = subprocess.run(
|
|
316
|
+
["git", "merge-base", "HEAD", ref],
|
|
317
|
+
cwd=project_dir, capture_output=True, text=True, timeout=10,
|
|
318
|
+
)
|
|
319
|
+
if proc.returncode == 0 and proc.stdout.strip():
|
|
320
|
+
return proc.stdout.strip()
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _added_lines(project_dir: Path, base: str, name: str) -> list[str] | None:
|
|
325
|
+
"""Lines ADDED by this change (committed + working tree) vs base.
|
|
326
|
+
|
|
327
|
+
Returns None when git cannot answer — callers fall back to the
|
|
328
|
+
whole-file scan rather than silently passing.
|
|
329
|
+
"""
|
|
330
|
+
proc = subprocess.run(
|
|
331
|
+
["git", "diff", "-U0", base, "--", name],
|
|
332
|
+
cwd=project_dir, capture_output=True, text=True, timeout=30,
|
|
333
|
+
)
|
|
334
|
+
if proc.returncode != 0:
|
|
335
|
+
return None
|
|
336
|
+
return [
|
|
337
|
+
line[1:]
|
|
338
|
+
for line in proc.stdout.splitlines()
|
|
339
|
+
if line.startswith("+") and not line.startswith("+++")
|
|
340
|
+
]
|
|
341
|
+
|
|
342
|
+
|
|
294
343
|
def _check_security_grep(
|
|
295
344
|
project_dir: Path, changed: list[str] | None,
|
|
296
345
|
test_command: str | None, timeout: int,
|
|
297
346
|
) -> CheckResult:
|
|
347
|
+
"""Diff-aware security sweep over the changed files.
|
|
348
|
+
|
|
349
|
+
Scans only lines ADDED relative to the default-branch merge-base —
|
|
350
|
+
a pre-existing pattern elsewhere in a touched file is master's
|
|
351
|
+
debt, not this change's (QG blocker, PR1 Interaction Reform:
|
|
352
|
+
whole-file scans failed changed files on benign pre-existing
|
|
353
|
+
lines). Falls back to the whole-file scan when git cannot provide
|
|
354
|
+
a diff (outside a repo, new file, missing base).
|
|
355
|
+
"""
|
|
298
356
|
if not changed:
|
|
299
357
|
return _skip("security-grep", "no changed files provided")
|
|
358
|
+
base = _diff_base(project_dir)
|
|
300
359
|
hits: list[str] = []
|
|
360
|
+
mode = "added-lines" if base else "whole-file"
|
|
301
361
|
for name in changed:
|
|
302
362
|
path = Path(name)
|
|
303
363
|
if not path.is_absolute():
|
|
304
364
|
path = project_dir / name
|
|
305
|
-
if path.is_file():
|
|
365
|
+
if not path.is_file():
|
|
366
|
+
continue
|
|
367
|
+
added = _added_lines(project_dir, base, name) if base else None
|
|
368
|
+
if added is None:
|
|
306
369
|
hits.extend(_grep_file(path))
|
|
370
|
+
else:
|
|
371
|
+
hits.extend(_grep_lines(path, added))
|
|
307
372
|
summary = (
|
|
308
373
|
"no security patterns matched"
|
|
309
374
|
if not hits
|
|
@@ -311,7 +376,7 @@ def _check_security_grep(
|
|
|
311
376
|
)
|
|
312
377
|
return CheckResult(
|
|
313
378
|
check="security-grep", ran=True, passed=not hits,
|
|
314
|
-
command=f"security-grep over {len(changed)} changed file(s)",
|
|
379
|
+
command=f"security-grep ({mode}) over {len(changed)} changed file(s)",
|
|
315
380
|
exit_code=None, summary=_tail(summary),
|
|
316
381
|
)
|
|
317
382
|
|
|
@@ -328,6 +393,66 @@ def _check_spellcheck(
|
|
|
328
393
|
return _run("spellcheck", ["codespell", *md_files], project_dir, timeout)
|
|
329
394
|
|
|
330
395
|
|
|
396
|
+
def _check_ui_screenshot(
|
|
397
|
+
project_dir: Path, changed: list[str] | None,
|
|
398
|
+
test_command: str | None, timeout: int,
|
|
399
|
+
) -> CheckResult:
|
|
400
|
+
"""UI-touching changes require a recent screenshot artifact.
|
|
401
|
+
|
|
402
|
+
Mechanical half of the visual-review loop (excellence-mandate): a
|
|
403
|
+
PNG > 10KB captured within the last 24h under
|
|
404
|
+
``<project>/.arka/evidence/ui/``. The judgment half — whether the
|
|
405
|
+
pixels hold up against the benchmark named in the ``[arka:design]``
|
|
406
|
+
marker — stays with Francisca, who Reads the artifact this check
|
|
407
|
+
points at. Skips when no UI files changed; read-only (stat only).
|
|
408
|
+
"""
|
|
409
|
+
if not changed:
|
|
410
|
+
return _skip("ui-screenshot", "no changed files provided")
|
|
411
|
+
try:
|
|
412
|
+
from core.workflow.frontend_gate import is_ui_file
|
|
413
|
+
except Exception:
|
|
414
|
+
return _skip("ui-screenshot", "frontend_gate unavailable")
|
|
415
|
+
ui_changed = [f for f in changed if is_ui_file(f)]
|
|
416
|
+
if not ui_changed:
|
|
417
|
+
return _skip("ui-screenshot", "no UI files changed")
|
|
418
|
+
command_str = (
|
|
419
|
+
f"scan:{UI_EVIDENCE_DIR} (png >{UI_SCREENSHOT_MIN_BYTES // 1024}KB, "
|
|
420
|
+
f"last {UI_SCREENSHOT_WINDOW_HOURS}h)"
|
|
421
|
+
)
|
|
422
|
+
evidence_dir = project_dir / UI_EVIDENCE_DIR
|
|
423
|
+
cutoff = time.time() - UI_SCREENSHOT_WINDOW_HOURS * 3600
|
|
424
|
+
candidates: list[tuple[float, Path]] = []
|
|
425
|
+
if evidence_dir.is_dir():
|
|
426
|
+
for png in evidence_dir.rglob("*.png"):
|
|
427
|
+
try:
|
|
428
|
+
stat = png.stat()
|
|
429
|
+
except OSError:
|
|
430
|
+
continue
|
|
431
|
+
if stat.st_mtime >= cutoff and stat.st_size > UI_SCREENSHOT_MIN_BYTES:
|
|
432
|
+
candidates.append((stat.st_mtime, png))
|
|
433
|
+
if candidates:
|
|
434
|
+
newest = max(candidates)[1]
|
|
435
|
+
return CheckResult(
|
|
436
|
+
check="ui-screenshot", ran=True, passed=True,
|
|
437
|
+
command=command_str, exit_code=None,
|
|
438
|
+
summary=(
|
|
439
|
+
f"{len(candidates)} recent screenshot(s) for "
|
|
440
|
+
f"{len(ui_changed)} changed UI file(s); newest: {newest.name}"
|
|
441
|
+
),
|
|
442
|
+
details_path=str(newest),
|
|
443
|
+
)
|
|
444
|
+
return CheckResult(
|
|
445
|
+
check="ui-screenshot", ran=True, passed=False,
|
|
446
|
+
command=command_str, exit_code=None,
|
|
447
|
+
summary=(
|
|
448
|
+
f"{len(ui_changed)} UI file(s) changed but no screenshot "
|
|
449
|
+
f"evidence under {UI_EVIDENCE_DIR}/ — capture per "
|
|
450
|
+
"brand/design-review (Playwright first, 1440+390 widths) or "
|
|
451
|
+
"record an explicit [arka:trivial] justification"
|
|
452
|
+
),
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
|
|
331
456
|
_CHECK_DISPATCH = {
|
|
332
457
|
"lint": _check_lint,
|
|
333
458
|
"typecheck": _check_typecheck,
|
|
@@ -335,6 +460,7 @@ _CHECK_DISPATCH = {
|
|
|
335
460
|
"coverage": _check_coverage,
|
|
336
461
|
"security-grep": _check_security_grep,
|
|
337
462
|
"spellcheck": _check_spellcheck,
|
|
463
|
+
"ui-screenshot": _check_ui_screenshot,
|
|
338
464
|
}
|
|
339
465
|
|
|
340
466
|
|