arkaos 3.71.1 → 3.73.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 +20 -0
- package/config/agent-ownership.yaml +169 -0
- package/config/constitution.yaml +5 -0
- package/config/hooks/pre-tool-use.ps1 +77 -0
- package/config/hooks/pre-tool-use.sh +80 -0
- package/core/governance/__pycache__/specialist_telemetry.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/specialist_telemetry_cli.cpython-313.pyc +0 -0
- package/core/governance/specialist_telemetry.py +117 -0
- package/core/governance/specialist_telemetry_cli.py +51 -0
- package/core/terminal/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/terminal/__pycache__/connections.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/specialist_enforcer.cpython-313.pyc +0 -0
- package/core/workflow/specialist_enforcer.py +462 -0
- package/dashboard/app/layouts/default.vue +7 -0
- package/dashboard/app/pages/cognition.vue +311 -0
- package/dashboard/app/pages/settings.vue +215 -51
- package/installer/autostart.js +178 -0
- package/installer/cli.js +7 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
- package/scripts/dashboard-api.py +155 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.73.0
|
|
@@ -73,6 +73,26 @@ state the gap explicitly and propose filling it.
|
|
|
73
73
|
Dispatch specialists via the `Agent` tool. The squad lead from Phase 3
|
|
74
74
|
names them. Specialists run in parallel when work is independent.
|
|
75
75
|
|
|
76
|
+
**Dispatch must be announced (NON-NEGOTIABLE `dispatch-must-be-announced`).**
|
|
77
|
+
Immediately before each `Agent` tool call, emit on its own line:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
[arka:dispatch] <calling-persona> -> <specialist-id>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Example before dispatching a frontend specialist from Paulo's seat:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
[arka:dispatch] paulo -> frontend-dev
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The PreToolUse specialist-enforcer (`core/workflow/specialist_enforcer.py`)
|
|
90
|
+
reads this marker to identify which specialist holds the floor. Without
|
|
91
|
+
it, the specialist's subsequent `Write`/`Edit` will block when
|
|
92
|
+
`hooks.specialistEnforcement=true`. The marker format mirrors
|
|
93
|
+
`[arka:routing]` exactly but uses the verb `dispatch` and points from
|
|
94
|
+
the caller to the receiver.
|
|
95
|
+
|
|
76
96
|
### Phase 7 — Plan and make the spec
|
|
77
97
|
Run six parallel reviewers on the plan:
|
|
78
98
|
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# ArkaOS — Agent File Ownership
|
|
3
|
+
#
|
|
4
|
+
# Maps file path patterns to the specialist agents (Tier 2) who own writes
|
|
5
|
+
# to those files. Squad leads (Tier 1) MUST dispatch the specialist via
|
|
6
|
+
# the Agent tool before writing to owned files — otherwise the PreToolUse
|
|
7
|
+
# hook blocks the write.
|
|
8
|
+
#
|
|
9
|
+
# Read by: core/workflow/specialist_enforcer.py
|
|
10
|
+
# Hook: config/hooks/pre-tool-use.sh (between KB-gate and flow-gate)
|
|
11
|
+
#
|
|
12
|
+
# Bypass: emit `[arka:specialist-bypass <reason>]` in the same assistant
|
|
13
|
+
# message immediately before the Write/Edit. Bypass is logged to
|
|
14
|
+
# ~/.arkaos/telemetry/specialist-dispatch.jsonl for accountability.
|
|
15
|
+
#
|
|
16
|
+
# Feature flag: hooks.specialistEnforcement in ~/.arkaos/config.json
|
|
17
|
+
# (defaults to false during rollout — promote to true after telemetry
|
|
18
|
+
# shows the rule set is stable).
|
|
19
|
+
# ============================================================================
|
|
20
|
+
|
|
21
|
+
version: 1
|
|
22
|
+
|
|
23
|
+
# Squad leads (Tier 1) — orchestrate, dispatch specialists, do not implement.
|
|
24
|
+
# The routing tag `[arka:routing] <dept> -> <name>` identifies which lead
|
|
25
|
+
# currently holds the floor. Lead names are normalized to lowercase by
|
|
26
|
+
# the enforcer before comparison.
|
|
27
|
+
leads:
|
|
28
|
+
- paulo # dev — Tech Lead
|
|
29
|
+
- luna # mkt — Marketing Lead
|
|
30
|
+
- valentina # brand — Brand Lead
|
|
31
|
+
- helena # fin — CFO (also c_suite)
|
|
32
|
+
- tomas # strat — Strategy Lead
|
|
33
|
+
- ricardo # ecom — E-commerce Lead
|
|
34
|
+
- clara # kb — Knowledge Lead
|
|
35
|
+
- daniel # ops — Operations Lead
|
|
36
|
+
- carolina # pm — Project Management Lead
|
|
37
|
+
- tiago # saas — SaaS Lead
|
|
38
|
+
- ines # landing — Landing Lead
|
|
39
|
+
- rafael # content — Content Lead
|
|
40
|
+
- beatriz # community — Community Lead
|
|
41
|
+
- miguel # sales — Sales Lead
|
|
42
|
+
- rodrigo # leadership — People Lead
|
|
43
|
+
- sofia # org — COO (also c_suite)
|
|
44
|
+
|
|
45
|
+
# C-Suite (Tier 0) — veto power, may write anywhere without dispatch.
|
|
46
|
+
# Listed here so the enforcer treats them as always-authorized.
|
|
47
|
+
c_suite:
|
|
48
|
+
- marco # CTO
|
|
49
|
+
- marta # CQO
|
|
50
|
+
- eduardo # Copy Director
|
|
51
|
+
- francisca # Tech Director
|
|
52
|
+
- sofia # COO
|
|
53
|
+
- helena # CFO
|
|
54
|
+
|
|
55
|
+
# Ownership rules — evaluated top-to-bottom, FIRST match wins.
|
|
56
|
+
# Patterns use glob syntax (fnmatch + ** for recursive directory).
|
|
57
|
+
# `owners` lists the specialist agent IDs (kebab-case) authorized to write.
|
|
58
|
+
# A persona currently holding the routing floor must match one of `owners`
|
|
59
|
+
# (case-insensitive). Leads always fail unless they appear in `owners`.
|
|
60
|
+
ownership:
|
|
61
|
+
# ─── Frontend ────────────────────────────────────────────────────────
|
|
62
|
+
- pattern: "**/*.vue"
|
|
63
|
+
owners: [frontend-dev]
|
|
64
|
+
reason: "Vue templates require frontend specialist"
|
|
65
|
+
|
|
66
|
+
- pattern: "**/*.tsx"
|
|
67
|
+
owners: [frontend-dev]
|
|
68
|
+
reason: "React TSX requires frontend specialist"
|
|
69
|
+
|
|
70
|
+
- pattern: "**/*.jsx"
|
|
71
|
+
owners: [frontend-dev]
|
|
72
|
+
reason: "React JSX requires frontend specialist"
|
|
73
|
+
|
|
74
|
+
- pattern: "**/components/**"
|
|
75
|
+
owners: [frontend-dev]
|
|
76
|
+
reason: "Component layer is frontend specialist territory"
|
|
77
|
+
|
|
78
|
+
# ─── Backend (PHP/Laravel) ───────────────────────────────────────────
|
|
79
|
+
- pattern: "**/app/Services/**"
|
|
80
|
+
owners: [senior-dev, backend-dev]
|
|
81
|
+
reason: "Service layer requires backend specialist"
|
|
82
|
+
|
|
83
|
+
- pattern: "**/app/Repositories/**"
|
|
84
|
+
owners: [senior-dev, backend-dev]
|
|
85
|
+
reason: "Repository layer requires backend specialist"
|
|
86
|
+
|
|
87
|
+
- pattern: "**/app/Http/Controllers/**"
|
|
88
|
+
owners: [senior-dev, backend-dev]
|
|
89
|
+
reason: "Controllers require backend specialist"
|
|
90
|
+
|
|
91
|
+
- pattern: "**/database/migrations/**"
|
|
92
|
+
owners: [dba, backend-dev]
|
|
93
|
+
reason: "Migrations require DBA review"
|
|
94
|
+
|
|
95
|
+
# ─── Security-sensitive ──────────────────────────────────────────────
|
|
96
|
+
- pattern: "**/.env*"
|
|
97
|
+
owners: [security-eng]
|
|
98
|
+
reason: "Environment files require security specialist"
|
|
99
|
+
|
|
100
|
+
- pattern: "**/auth/**"
|
|
101
|
+
owners: [security-eng, backend-dev]
|
|
102
|
+
reason: "Auth code requires security review"
|
|
103
|
+
|
|
104
|
+
- pattern: "core/security/**"
|
|
105
|
+
owners: [security-eng]
|
|
106
|
+
reason: "Core security module is security specialist territory"
|
|
107
|
+
|
|
108
|
+
- pattern: "config/hooks/**"
|
|
109
|
+
owners: [security-eng, devops-eng]
|
|
110
|
+
reason: "Hooks affect runtime behavior — security + devops review required"
|
|
111
|
+
|
|
112
|
+
# ─── DevOps / Infrastructure ─────────────────────────────────────────
|
|
113
|
+
- pattern: ".github/workflows/**"
|
|
114
|
+
owners: [devops-eng]
|
|
115
|
+
reason: "CI/CD workflows are devops specialist territory"
|
|
116
|
+
|
|
117
|
+
- pattern: "**/Dockerfile*"
|
|
118
|
+
owners: [devops-eng]
|
|
119
|
+
reason: "Container builds require devops specialist"
|
|
120
|
+
|
|
121
|
+
- pattern: "infrastructure/**"
|
|
122
|
+
owners: [devops-eng]
|
|
123
|
+
reason: "Infrastructure-as-code requires devops specialist"
|
|
124
|
+
|
|
125
|
+
# ─── Core architecture ──────────────────────────────────────────────
|
|
126
|
+
- pattern: "core/workflow/**/*.py"
|
|
127
|
+
owners: [architect, senior-dev]
|
|
128
|
+
reason: "Workflow engine requires architecture review"
|
|
129
|
+
|
|
130
|
+
- pattern: "core/agents/**/*.py"
|
|
131
|
+
owners: [architect, senior-dev]
|
|
132
|
+
reason: "Agent schema/loader requires architecture review"
|
|
133
|
+
|
|
134
|
+
- pattern: "core/governance/**/*.py"
|
|
135
|
+
owners: [architect, security-eng]
|
|
136
|
+
reason: "Governance code requires architecture + security review"
|
|
137
|
+
|
|
138
|
+
- pattern: "docs/adr/**"
|
|
139
|
+
owners: [architect]
|
|
140
|
+
reason: "ADRs are architect's responsibility"
|
|
141
|
+
|
|
142
|
+
# ─── Open-access (any persona may write) ─────────────────────────────
|
|
143
|
+
# Tests are written by the specialist who owns the code under test.
|
|
144
|
+
# qa-eng owns test STRATEGY, not individual test files.
|
|
145
|
+
- pattern: "**/tests/**"
|
|
146
|
+
owners: ["*"]
|
|
147
|
+
reason: "Tests written by specialist who owns code under test"
|
|
148
|
+
|
|
149
|
+
- pattern: "**/*.md"
|
|
150
|
+
owners: ["*"]
|
|
151
|
+
reason: "Markdown docs are open to any persona"
|
|
152
|
+
|
|
153
|
+
# Lead-allowed files — leads + c-suite ALWAYS allowed without dispatch.
|
|
154
|
+
# Used for cross-cutting files that touch many specialists.
|
|
155
|
+
lead_allowed:
|
|
156
|
+
- "CHANGELOG.md"
|
|
157
|
+
- "VERSION"
|
|
158
|
+
- "package.json"
|
|
159
|
+
- "package-lock.json"
|
|
160
|
+
- "pyproject.toml"
|
|
161
|
+
- "uv.lock"
|
|
162
|
+
- "README.md"
|
|
163
|
+
- "CLAUDE.md"
|
|
164
|
+
- "CONSTITUTION.md"
|
|
165
|
+
- "config/agent-ownership.yaml" # bootstrap — this file
|
|
166
|
+
- "config/constitution.yaml"
|
|
167
|
+
- "**/*.yaml" # squad/workflow configs
|
|
168
|
+
- "**/*.yml"
|
|
169
|
+
- "knowledge/**" # KB curation
|
package/config/constitution.yaml
CHANGED
|
@@ -121,6 +121,11 @@ enforcement_levels:
|
|
|
121
121
|
rule: "ArkaOS learns from user corrections via hybrid mechanism: implicit auto-detection with confidence scoring for typical corrections (default), explicit Marta-led confirmation for high-leverage rules (NON-NEGOTIABLE candidates) or rules that contradict existing memory. Marta is the owner of the learning loop. Memory rules carry a confidence field that climbs as the rule is applied without correction."
|
|
122
122
|
enforcement: "Correction signals detected via absolute-language keywords ('sempre', 'nunca', 'no exceptions') and correction magnitude; [arka:learned-rule confidence=X] tag emitted when rule auto-saved; explicit Marta confirmation question fired when rule is high-leverage or conflicts with existing memory."
|
|
123
123
|
|
|
124
|
+
# ─── Rule added in PR1 Squad Intelligence Upgrade (2026-05-28) ───────
|
|
125
|
+
- id: dispatch-must-be-announced
|
|
126
|
+
rule: "When a squad lead dispatches a specialist via the Agent tool, the lead MUST emit `[arka:dispatch] <from> -> <to>` on a line of its own immediately before the dispatch call. The marker identifies the specialist to the PreToolUse specialist-enforcer so writes from the specialist pass `owner-match` instead of falling through as `no-routing-tag` or blocking the lead. The format is identical to `[arka:routing]` but uses the verb `dispatch` and points from the calling persona to the receiving specialist (e.g., `[arka:dispatch] paulo -> frontend-dev`)."
|
|
127
|
+
enforcement: "PreToolUse hook (config/hooks/pre-tool-use.sh) reads the dispatch marker via core.workflow.specialist_enforcer._resolve_persona. Dispatch tag overrides routing tag because it is more specific. Without it, lead writes to specialist-owned files are blocked when hooks.specialistEnforcement=true. See ADR docs/adr/2026-05-28-specialist-dispatch-subagent-blindspot.md for the architectural constraint this rule mitigates."
|
|
128
|
+
|
|
124
129
|
quality_gate:
|
|
125
130
|
description: "Mandatory pre-delivery review. Nothing ships without APPROVED verdict."
|
|
126
131
|
trigger: "After the last execution phase, before delivery to user"
|
|
@@ -116,6 +116,83 @@ print(json.dumps({
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
# --- Specialist-dispatch gate (between KB-gate and flow-gate) ---
|
|
120
|
+
# Blocks Tier-1 leads from writing to specialist-owned files without
|
|
121
|
+
# dispatching first. Only fires for file-mutation tools.
|
|
122
|
+
$specialistPy = Join-Path $env:ARKAOS_ROOT "core/workflow/specialist_enforcer.py"
|
|
123
|
+
if ((Test-Path $specialistPy) -and ($toolName -in @("Write","Edit","MultiEdit","NotebookEdit"))) {
|
|
124
|
+
$toolInputJson = "{}"
|
|
125
|
+
if ($inp.tool_input) {
|
|
126
|
+
$toolInputJson = ($inp.tool_input | ConvertTo-Json -Compress -Depth 10)
|
|
127
|
+
}
|
|
128
|
+
$env:TOOL_NAME = $toolName
|
|
129
|
+
$env:TRANSCRIPT_PATH = $transcriptPath
|
|
130
|
+
$env:SESSION_ID = $sessionId
|
|
131
|
+
$env:CWD = $cwd
|
|
132
|
+
$env:TOOL_INPUT_JSON = $toolInputJson
|
|
133
|
+
|
|
134
|
+
$spScript = @'
|
|
135
|
+
import json
|
|
136
|
+
import os
|
|
137
|
+
import sys
|
|
138
|
+
|
|
139
|
+
sys.path.insert(0, os.environ["ARKAOS_ROOT"])
|
|
140
|
+
try:
|
|
141
|
+
from core.workflow.specialist_enforcer import evaluate, record_telemetry
|
|
142
|
+
except Exception:
|
|
143
|
+
print(json.dumps({"allow": True, "reason": "specialist-import-failed"}))
|
|
144
|
+
sys.exit(0)
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
tool_input = json.loads(os.environ.get("TOOL_INPUT_JSON", "{}"))
|
|
148
|
+
except json.JSONDecodeError:
|
|
149
|
+
tool_input = {}
|
|
150
|
+
|
|
151
|
+
decision = evaluate(
|
|
152
|
+
tool_name=os.environ.get("TOOL_NAME", ""),
|
|
153
|
+
transcript_path=os.environ.get("TRANSCRIPT_PATH", ""),
|
|
154
|
+
session_id=os.environ.get("SESSION_ID", ""),
|
|
155
|
+
cwd=os.environ.get("CWD", ""),
|
|
156
|
+
tool_input=tool_input,
|
|
157
|
+
)
|
|
158
|
+
try:
|
|
159
|
+
record_telemetry(
|
|
160
|
+
session_id=os.environ.get("SESSION_ID", ""),
|
|
161
|
+
tool=os.environ.get("TOOL_NAME", ""),
|
|
162
|
+
decision=decision,
|
|
163
|
+
cwd=os.environ.get("CWD", ""),
|
|
164
|
+
target_file=str(tool_input.get("file_path", "")),
|
|
165
|
+
)
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
|
168
|
+
print(json.dumps({
|
|
169
|
+
"allow": decision.allow,
|
|
170
|
+
"reason": decision.reason,
|
|
171
|
+
"stderr_msg": decision.to_stderr_message(),
|
|
172
|
+
}))
|
|
173
|
+
'@
|
|
174
|
+
|
|
175
|
+
$spDecisionJson = $spScript | & $python.Source -
|
|
176
|
+
if (-not [string]::IsNullOrWhiteSpace($spDecisionJson)) {
|
|
177
|
+
try {
|
|
178
|
+
$spDecision = $spDecisionJson | ConvertFrom-Json
|
|
179
|
+
} catch { $spDecision = $null }
|
|
180
|
+
|
|
181
|
+
if ($spDecision -and -not $spDecision.allow) {
|
|
182
|
+
[Console]::Error.WriteLine($spDecision.stderr_msg)
|
|
183
|
+
$denyOut = @{
|
|
184
|
+
hookSpecificOutput = @{
|
|
185
|
+
hookEventName = "PreToolUse"
|
|
186
|
+
permissionDecision = "deny"
|
|
187
|
+
permissionDecisionReason = $spDecision.stderr_msg
|
|
188
|
+
}
|
|
189
|
+
} | ConvertTo-Json -Compress -Depth 5
|
|
190
|
+
Write-Output $denyOut
|
|
191
|
+
exit 2
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
119
196
|
# --- Fast allow: not a flow-gated tool ---
|
|
120
197
|
if ($toolName -ne "Write" -and $toolName -ne "Edit" -and $toolName -ne "MultiEdit") {
|
|
121
198
|
exit 0
|
|
@@ -120,6 +120,86 @@ PY
|
|
|
120
120
|
fi
|
|
121
121
|
fi
|
|
122
122
|
|
|
123
|
+
# ─── Specialist-dispatch gate (between KB-gate and flow-gate) ──────────
|
|
124
|
+
# Blocks Tier-1 leads from writing to specialist-owned files without
|
|
125
|
+
# dispatching first. Only fires for file-mutation tools. Independent
|
|
126
|
+
# feature flag (`hooks.specialistEnforcement`) — fails open if the
|
|
127
|
+
# Python module is absent or raises.
|
|
128
|
+
if [ -f "$ARKAOS_ROOT/core/workflow/specialist_enforcer.py" ]; then
|
|
129
|
+
case "$TOOL_NAME" in
|
|
130
|
+
Write|Edit|MultiEdit|NotebookEdit)
|
|
131
|
+
TOOL_INPUT_JSON_SP="{}"
|
|
132
|
+
if command -v jq &>/dev/null; then
|
|
133
|
+
TOOL_INPUT_JSON_SP=$(echo "$input" | jq -c '.tool_input // {}' 2>/dev/null)
|
|
134
|
+
fi
|
|
135
|
+
SP_DECISION_JSON=$(TOOL_NAME="$TOOL_NAME" \
|
|
136
|
+
TRANSCRIPT_PATH="$TRANSCRIPT_PATH" \
|
|
137
|
+
SESSION_ID="$SESSION_ID" \
|
|
138
|
+
CWD="$CWD" \
|
|
139
|
+
TOOL_INPUT_JSON="$TOOL_INPUT_JSON_SP" \
|
|
140
|
+
ARKAOS_ROOT="$ARKAOS_ROOT" \
|
|
141
|
+
python3 - <<'PY' 2>/dev/null
|
|
142
|
+
import json
|
|
143
|
+
import os
|
|
144
|
+
import sys
|
|
145
|
+
|
|
146
|
+
sys.path.insert(0, os.environ["ARKAOS_ROOT"])
|
|
147
|
+
try:
|
|
148
|
+
from core.workflow.specialist_enforcer import evaluate, record_telemetry
|
|
149
|
+
except Exception:
|
|
150
|
+
print(json.dumps({"allow": True, "reason": "specialist-import-failed"}))
|
|
151
|
+
sys.exit(0)
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
tool_input = json.loads(os.environ.get("TOOL_INPUT_JSON", "{}"))
|
|
155
|
+
except json.JSONDecodeError:
|
|
156
|
+
tool_input = {}
|
|
157
|
+
|
|
158
|
+
decision = evaluate(
|
|
159
|
+
tool_name=os.environ.get("TOOL_NAME", ""),
|
|
160
|
+
transcript_path=os.environ.get("TRANSCRIPT_PATH", ""),
|
|
161
|
+
session_id=os.environ.get("SESSION_ID", ""),
|
|
162
|
+
cwd=os.environ.get("CWD", ""),
|
|
163
|
+
tool_input=tool_input,
|
|
164
|
+
)
|
|
165
|
+
try:
|
|
166
|
+
record_telemetry(
|
|
167
|
+
session_id=os.environ.get("SESSION_ID", ""),
|
|
168
|
+
tool=os.environ.get("TOOL_NAME", ""),
|
|
169
|
+
decision=decision,
|
|
170
|
+
cwd=os.environ.get("CWD", ""),
|
|
171
|
+
target_file=str(tool_input.get("file_path", "")),
|
|
172
|
+
)
|
|
173
|
+
except Exception:
|
|
174
|
+
pass
|
|
175
|
+
print(json.dumps({
|
|
176
|
+
"allow": decision.allow,
|
|
177
|
+
"reason": decision.reason,
|
|
178
|
+
"stderr_msg": decision.to_stderr_message(),
|
|
179
|
+
}))
|
|
180
|
+
PY
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if [ -n "$SP_DECISION_JSON" ]; then
|
|
184
|
+
SP_ALLOW=$(echo "$SP_DECISION_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('allow', True))" 2>/dev/null)
|
|
185
|
+
if [ "$SP_ALLOW" != "True" ] && [ "$SP_ALLOW" != "true" ]; then
|
|
186
|
+
SP_STDERR=$(echo "$SP_DECISION_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('stderr_msg',''))" 2>/dev/null)
|
|
187
|
+
echo "$SP_STDERR" >&2
|
|
188
|
+
STDERR_MSG="$SP_STDERR" python3 - <<'PY'
|
|
189
|
+
import json, os
|
|
190
|
+
print(json.dumps({"hookSpecificOutput": {
|
|
191
|
+
"hookEventName": "PreToolUse",
|
|
192
|
+
"permissionDecision": "deny",
|
|
193
|
+
"permissionDecisionReason": os.environ.get("STDERR_MSG", ""),
|
|
194
|
+
}}))
|
|
195
|
+
PY
|
|
196
|
+
exit 2
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
;;
|
|
200
|
+
esac
|
|
201
|
+
fi
|
|
202
|
+
|
|
123
203
|
# ─── Fast allow: not a flow-gated tool ──────────────────────────────────
|
|
124
204
|
# PR11 v2.33.0 expanded the gated set to include all EFFECT tools.
|
|
125
205
|
# Bash is special — handled per-command by the Python enforcer via
|
|
Binary file
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Specialist-dispatch telemetry summarizer (PR1 — Squad Intelligence Upgrade).
|
|
2
|
+
|
|
3
|
+
Reads ``~/.arkaos/telemetry/specialist-dispatch.jsonl`` (the JSONL stream
|
|
4
|
+
the PreToolUse specialist-enforcer appends to on every gated decision)
|
|
5
|
+
and produces compact summaries for ``/arka status`` and tuning.
|
|
6
|
+
|
|
7
|
+
Mirrors the pattern of ``core.governance.enforcement_telemetry`` so
|
|
8
|
+
periods, malformed-line tolerance, and zero-division safety behave the
|
|
9
|
+
same way across telemetry surfaces. Read-only.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from collections import Counter
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from datetime import datetime, timedelta, timezone
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
DEFAULT_PATH: Path = (
|
|
22
|
+
Path.home() / ".arkaos" / "telemetry" / "specialist-dispatch.jsonl"
|
|
23
|
+
)
|
|
24
|
+
_VALID_PERIODS: frozenset[str] = frozenset({"today", "week", "month", "all"})
|
|
25
|
+
_TOP_N: int = 5
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class SpecialistSummary:
|
|
30
|
+
"""Aggregated specialist-dispatch telemetry over a time slice."""
|
|
31
|
+
period: str
|
|
32
|
+
total_calls: int
|
|
33
|
+
blocked_calls: int
|
|
34
|
+
block_rate: float
|
|
35
|
+
bypass_used: int
|
|
36
|
+
top_blocked_personas: list[tuple[str, int]] = field(default_factory=list)
|
|
37
|
+
top_owners_required: list[tuple[str, int]] = field(default_factory=list)
|
|
38
|
+
corrupt_line_count: int = 0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def summarise(period: str, *, path: Path | None = None) -> SpecialistSummary:
|
|
42
|
+
if period not in _VALID_PERIODS:
|
|
43
|
+
raise ValueError(f"invalid period: {period!r}")
|
|
44
|
+
src = path or DEFAULT_PATH
|
|
45
|
+
cutoff = _period_cutoff(period)
|
|
46
|
+
entries, corrupt = _read_jsonl(src, cutoff)
|
|
47
|
+
return _build_summary(period, entries, corrupt)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _period_cutoff(period: str, now: datetime | None = None) -> datetime | None:
|
|
51
|
+
ref = now or datetime.now(timezone.utc)
|
|
52
|
+
if period == "today":
|
|
53
|
+
return ref.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
54
|
+
if period == "week":
|
|
55
|
+
return ref - timedelta(days=7)
|
|
56
|
+
if period == "month":
|
|
57
|
+
return ref - timedelta(days=30)
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _read_jsonl(
|
|
62
|
+
src: Path, cutoff: datetime | None
|
|
63
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
64
|
+
if not src.exists():
|
|
65
|
+
return [], 0
|
|
66
|
+
entries: list[dict[str, Any]] = []
|
|
67
|
+
corrupt = 0
|
|
68
|
+
try:
|
|
69
|
+
with src.open("r", encoding="utf-8", errors="replace") as fh:
|
|
70
|
+
for line in fh:
|
|
71
|
+
if not line.strip():
|
|
72
|
+
continue
|
|
73
|
+
try:
|
|
74
|
+
entry = json.loads(line)
|
|
75
|
+
except json.JSONDecodeError:
|
|
76
|
+
corrupt += 1
|
|
77
|
+
continue
|
|
78
|
+
if cutoff is not None:
|
|
79
|
+
ts_raw = entry.get("ts", "")
|
|
80
|
+
try:
|
|
81
|
+
ts = datetime.fromisoformat(ts_raw)
|
|
82
|
+
except (TypeError, ValueError):
|
|
83
|
+
corrupt += 1
|
|
84
|
+
continue
|
|
85
|
+
if ts < cutoff:
|
|
86
|
+
continue
|
|
87
|
+
entries.append(entry)
|
|
88
|
+
except OSError:
|
|
89
|
+
return entries, corrupt
|
|
90
|
+
return entries, corrupt
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _build_summary(
|
|
94
|
+
period: str, entries: list[dict[str, Any]], corrupt: int
|
|
95
|
+
) -> SpecialistSummary:
|
|
96
|
+
total = len(entries)
|
|
97
|
+
blocked = sum(1 for e in entries if e.get("allow") is False)
|
|
98
|
+
bypass = sum(1 for e in entries if e.get("bypass_used") is True)
|
|
99
|
+
rate = (blocked / total) if total else 0.0
|
|
100
|
+
persona_counter: Counter[str] = Counter()
|
|
101
|
+
owner_counter: Counter[str] = Counter()
|
|
102
|
+
for e in entries:
|
|
103
|
+
if e.get("allow") is False:
|
|
104
|
+
persona = e.get("current_persona") or "unknown"
|
|
105
|
+
persona_counter[persona] += 1
|
|
106
|
+
for owner in e.get("required_owners", []) or []:
|
|
107
|
+
owner_counter[owner] += 1
|
|
108
|
+
return SpecialistSummary(
|
|
109
|
+
period=period,
|
|
110
|
+
total_calls=total,
|
|
111
|
+
blocked_calls=blocked,
|
|
112
|
+
block_rate=rate,
|
|
113
|
+
bypass_used=bypass,
|
|
114
|
+
top_blocked_personas=persona_counter.most_common(_TOP_N),
|
|
115
|
+
top_owners_required=owner_counter.most_common(_TOP_N),
|
|
116
|
+
corrupt_line_count=corrupt,
|
|
117
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""CLI front-end for specialist-dispatch telemetry.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python -m core.governance.specialist_telemetry_cli today
|
|
5
|
+
python -m core.governance.specialist_telemetry_cli week
|
|
6
|
+
python -m core.governance.specialist_telemetry_cli month
|
|
7
|
+
python -m core.governance.specialist_telemetry_cli all
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from core.governance.specialist_telemetry import summarise
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _format_human(summary) -> str:
|
|
18
|
+
pct = f"{summary.block_rate * 100:.1f}%"
|
|
19
|
+
lines = [
|
|
20
|
+
f"Specialist Dispatch Telemetry — {summary.period}",
|
|
21
|
+
f" Total calls: {summary.total_calls}",
|
|
22
|
+
f" Blocked: {summary.blocked_calls} ({pct})",
|
|
23
|
+
f" Bypasses used: {summary.bypass_used}",
|
|
24
|
+
]
|
|
25
|
+
if summary.top_blocked_personas:
|
|
26
|
+
lines.append(" Top blocked personas:")
|
|
27
|
+
for persona, count in summary.top_blocked_personas:
|
|
28
|
+
lines.append(f" - {persona}: {count}")
|
|
29
|
+
if summary.top_owners_required:
|
|
30
|
+
lines.append(" Top owners required:")
|
|
31
|
+
for owner, count in summary.top_owners_required:
|
|
32
|
+
lines.append(f" - {owner}: {count}")
|
|
33
|
+
if summary.corrupt_line_count:
|
|
34
|
+
lines.append(f" Corrupt lines: {summary.corrupt_line_count}")
|
|
35
|
+
return "\n".join(lines)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main(argv: list[str] | None = None) -> int:
|
|
39
|
+
args = argv if argv is not None else sys.argv[1:]
|
|
40
|
+
period = args[0] if args else "today"
|
|
41
|
+
try:
|
|
42
|
+
summary = summarise(period)
|
|
43
|
+
except ValueError as exc:
|
|
44
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
45
|
+
return 2
|
|
46
|
+
print(_format_human(summary))
|
|
47
|
+
return 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__": # pragma: no cover
|
|
51
|
+
sys.exit(main())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|