arkaos 4.0.2 → 4.1.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 +16 -22
- package/arka/skills/flow/SKILL.md +108 -234
- package/config/claude-agents/eduardo-copy.md +53 -0
- package/config/claude-agents/francisca-tech.md +52 -0
- package/config/claude-agents/marta-cqo.md +52 -0
- package/config/claude-agents/paulo-tech-lead.md +50 -0
- package/config/cognition/schedules.yaml +15 -0
- package/config/constitution.yaml +17 -21
- package/config/hooks/_lib/workflow-classifier.sh +1 -1
- package/config/hooks/post-tool-use.sh +52 -536
- package/config/hooks/pre-tool-use.sh +46 -280
- package/config/hooks/session-start.sh +8 -10
- package/config/hooks/stop.sh +44 -424
- package/config/hooks/user-prompt-submit.sh +55 -457
- package/core/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/cognition/__pycache__/dreaming.cpython-313.pyc +0 -0
- package/core/cognition/dreaming.py +5 -0
- package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
- package/core/cognition/scheduler/daemon.py +10 -0
- package/core/forge/__init__.py +2 -0
- package/core/forge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/persistence.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/renderer.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/forge/orchestrator.py +50 -27
- package/core/forge/persistence.py +16 -0
- package/core/forge/renderer.py +16 -0
- package/core/forge/runtime_dispatcher.py +34 -19
- package/core/forge/schema.py +12 -0
- package/core/governance/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/governance/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/closing_marker_check.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/closing_marker_check.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/compliance_telemetry.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/constitution.cpython-312.pyc +0 -0
- package/core/governance/__pycache__/constitution.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/evidence_checks.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/kb_cite_check.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/meta_tag_check.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/meta_tag_check.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/qg_verdict.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/skill_proposer.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/skill_proposer.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/sycophancy_detector.cpython-314.pyc +0 -0
- package/core/governance/closing_marker_check.py +9 -5
- package/core/governance/compliance_telemetry.py +1 -1
- package/core/governance/compliance_telemetry_cli.py +1 -1
- package/core/governance/evidence_checks.py +421 -0
- package/core/governance/meta_tag_check.py +36 -0
- package/core/governance/qg_verdict.py +78 -0
- package/core/governance/review_workflow.py +34 -1
- package/core/governance/skill_proposer.py +2 -1
- package/core/hooks/__init__.py +12 -0
- package/core/hooks/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/hooks/__pycache__/_shared.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/_shared.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/_shared.cpython-314.pyc +0 -0
- package/core/hooks/__pycache__/post_tool_use.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/post_tool_use.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/pre_tool_use.cpython-312.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-312.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-312.pyc +0 -0
- package/core/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/core/hooks/_shared.py +110 -0
- package/core/hooks/post_tool_use.py +689 -0
- package/core/hooks/pre_tool_use.py +267 -0
- package/core/hooks/stop.py +395 -0
- package/core/hooks/user_prompt_submit.py +600 -0
- package/core/jobs/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/jobs/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/jobs/__pycache__/auto_doc_worker.cpython-312.pyc +0 -0
- package/core/jobs/__pycache__/auto_doc_worker.cpython-314.pyc +0 -0
- package/core/jobs/__pycache__/manager.cpython-312.pyc +0 -0
- package/core/jobs/__pycache__/manager.cpython-314.pyc +0 -0
- package/core/knowledge/__pycache__/embedder.cpython-312.pyc +0 -0
- package/core/knowledge/__pycache__/embedder.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/vector_store.cpython-312.pyc +0 -0
- package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/core/knowledge/embedder.py +118 -11
- package/core/knowledge/vector_store.py +111 -14
- package/core/memory/__pycache__/session_store.cpython-314.pyc +0 -0
- package/core/obsidian/__pycache__/relator.cpython-313.pyc +0 -0
- package/core/obsidian/relator.py +18 -8
- package/core/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/base.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/base.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/base.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/capabilities_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/capabilities_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/claude_code.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/claude_code.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/claude_code.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/codex_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/codex_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/codex_cli.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/cost_governor.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/cost_governor.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/cursor.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/cursor.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/cursor.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/gemini_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/gemini_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/gemini_cli.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/llm_cost_telemetry.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/llm_provider.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/llm_provider.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/llm_provider.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/llm_retry.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/llm_retry.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/llm_retry.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/native_usage.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/native_usage.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/native_usage.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/ollama_provider.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/path_resolver.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/pricing.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/pricing.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/pricing.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/registry.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/registry.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/registry.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/squad_orchestrator.cpython-312.pyc +0 -0
- package/core/runtime/base.py +29 -0
- package/core/runtime/capabilities_cli.py +74 -0
- package/core/runtime/claude_code.py +8 -0
- package/core/runtime/codex_cli.py +205 -37
- package/core/runtime/cost_governor.py +184 -0
- package/core/runtime/cursor.py +8 -0
- package/core/runtime/gemini_cli.py +8 -0
- package/core/runtime/llm_provider.py +32 -13
- package/core/runtime/llm_retry.py +233 -0
- package/core/runtime/native_usage.py +186 -0
- package/core/runtime/pricing.py +9 -0
- package/core/runtime/registry.py +3 -0
- package/core/shared/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/shared/__pycache__/safe_session_id.cpython-314.pyc +0 -0
- package/core/shared/__pycache__/test_evidence.cpython-313-pytest-9.1.1.pyc +0 -0
- package/core/shared/__pycache__/test_evidence.cpython-313.pyc +0 -0
- package/core/shared/test_evidence.py +34 -0
- package/core/synapse/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/agent_experiences_layer.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/cache.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/graph_context_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/kb_cache.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/kb_cache.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-314.pyc +0 -0
- package/core/synapse/engine.py +7 -0
- package/core/synapse/graph_context_layer.py +212 -0
- package/core/synapse/kb_cache.py +3 -0
- package/core/synapse/layers.py +104 -20
- package/core/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/engine.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/flow_enforcer.cpython-312.pyc +0 -0
- 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__/gate_checkpoint.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/gate_checkpoint.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/gate_checkpoint.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/loader.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/marker_cache.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/research_gate.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/research_gate.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/schema.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/specialist_enforcer.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/specialist_enforcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/state.cpython-314.pyc +0 -0
- package/core/workflow/enforcer.py +5 -16
- package/core/workflow/flow_enforcer.py +36 -11
- package/core/workflow/gate_checkpoint.py +149 -0
- package/core/workflow/research_gate.py +15 -3
- package/core/workflow/rules_registry.py +137 -389
- package/core/workflow/specialist_enforcer.py +21 -4
- package/dashboard/app/pages/knowledge/index.vue +4 -1
- package/departments/ops/skills/n8n-flow/SKILL.md +13 -0
- package/departments/ops/skills/workflow-automate/SKILL.md +16 -0
- package/departments/quality/SKILL.md +45 -5
- package/departments/quality/agents/copy-director.yaml +3 -1
- package/departments/quality/agents/tech-director.yaml +3 -1
- package/installer/adapters/claude-code.js +46 -2
- package/installer/config-seed.js +39 -14
- package/installer/doctor.js +8 -0
- package/installer/graphify.js +153 -0
- package/installer/index.js +20 -0
- package/installer/init.js +11 -0
- package/installer/update.js +19 -0
- package/package.json +1 -1
- package/pyproject.toml +3 -1
- package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
- package/scripts/__pycache__/knowledge-index.cpython-313.pyc +0 -0
- package/scripts/__pycache__/synapse-bridge.cpython-312.pyc +0 -0
- package/scripts/__pycache__/synapse-bridge.cpython-313.pyc +0 -0
- package/scripts/knowledge-index.py +8 -2
- package/scripts/synapse-bridge.py +102 -26
|
@@ -1,552 +1,68 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# ============================================================================
|
|
3
|
-
# ARKA OS — PostToolUse Hook (
|
|
4
|
-
#
|
|
5
|
-
#
|
|
3
|
+
# ARKA OS — PostToolUse Hook (thin wrapper, PR-6 v4.1.0 hook hygiene)
|
|
4
|
+
#
|
|
5
|
+
# Delegates the ENTIRE event to ONE python process:
|
|
6
|
+
# python3 -m core.hooks.post_tool_use
|
|
7
|
+
#
|
|
8
|
+
# The entrypoint preserves the behavior contract of the previous
|
|
9
|
+
# ~38-spawn-site version:
|
|
10
|
+
# - Flow marker cache: detects [arka:routing] / [arka:trivial] in the
|
|
11
|
+
# assistant message and persists via core.workflow.marker_cache
|
|
12
|
+
# write_marker so PreToolUse can short-circuit the transcript scan.
|
|
13
|
+
# - CQO REJECTED experience auto-record + APPROVED pattern stubs.
|
|
14
|
+
# - Activation tracking for every Task/Agent dispatch.
|
|
15
|
+
# - Gotchas memory (~/.arkaos/gotchas.json) with fix suggestions.
|
|
16
|
+
# - Workflow violation rules + enforcement engine + forge scope-creep
|
|
17
|
+
# (delegated once to the ArkaOS venv python when the ambient python3
|
|
18
|
+
# lacks PyYAML — mirrors the old ARKAOS_PY resolution).
|
|
19
|
+
# - Cognition capture enqueue (detached background process).
|
|
20
|
+
# - Hook metrics. Output: JSON to stdout. Timeout: 5s.
|
|
6
21
|
# ============================================================================
|
|
7
22
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
_hook_ms() {
|
|
14
|
-
local end_ns=$(date +%s%N 2>/dev/null || echo "0")
|
|
15
|
-
if [ "$_HOOK_START_NS" != "0" ] && [ "$end_ns" != "0" ] && [ ${#end_ns} -gt 10 ]; then
|
|
16
|
-
echo $(( (end_ns - _HOOK_START_NS) / 1000000 ))
|
|
23
|
+
if [ -z "${ARKAOS_ROOT:-}" ]; then
|
|
24
|
+
if [ -f "$HOME/.arkaos/.repo-path" ]; then
|
|
25
|
+
ARKAOS_ROOT=$(cat "$HOME/.arkaos/.repo-path")
|
|
26
|
+
elif [ -d "$HOME/.arkaos" ]; then
|
|
27
|
+
ARKAOS_ROOT="$HOME/.arkaos"
|
|
17
28
|
else
|
|
18
|
-
|
|
19
|
-
fi
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
# Extract fields
|
|
23
|
-
TOOL_NAME=$(echo "$input" | jq -r '.tool_name // ""' 2>/dev/null)
|
|
24
|
-
TOOL_OUTPUT=$(echo "$input" | jq -r '.tool_output // ""' 2>/dev/null)
|
|
25
|
-
EXIT_CODE=$(echo "$input" | jq -r '.exit_code // "0"' 2>/dev/null)
|
|
26
|
-
CWD=$(echo "$input" | jq -r '.cwd // ""' 2>/dev/null)
|
|
27
|
-
SESSION_ID_PTU=$(echo "$input" | jq -r '.session_id // ""' 2>/dev/null)
|
|
28
|
-
ASSISTANT_MSG=$(echo "$input" | jq -r '.assistant_message // ""' 2>/dev/null)
|
|
29
|
-
|
|
30
|
-
# ─── Flow marker cache write (v2 — turn-scoped ALLOW accelerator) ───────
|
|
31
|
-
# Detect [arka:routing] or [arka:trivial] in the assistant message that
|
|
32
|
-
# accompanied this tool call, and persist it so the PreToolUse enforcer
|
|
33
|
-
# can short-circuit the transcript scan for the rest of the turn.
|
|
34
|
-
# Never blocks the hook — all failures are swallowed.
|
|
35
|
-
if [ -n "$SESSION_ID_PTU" ] && [ -n "$ASSISTANT_MSG" ] && command -v python3 &>/dev/null; then
|
|
36
|
-
_MARKER_KIND=""
|
|
37
|
-
_MARKER_DEPT=""
|
|
38
|
-
_MARKER_LEAD=""
|
|
39
|
-
if printf '%s' "$ASSISTANT_MSG" | grep -qiE '\[arka:routing\][[:space:]]*[A-Za-z_-]+[[:space:]]*->[[:space:]]*[A-Za-z_-]+'; then
|
|
40
|
-
_MARKER_KIND="routing"
|
|
41
|
-
_ROUTE_LINE=$(printf '%s' "$ASSISTANT_MSG" | grep -iEo '\[arka:routing\][[:space:]]*[A-Za-z_-]+[[:space:]]*->[[:space:]]*[A-Za-z_-]+' | head -1)
|
|
42
|
-
_MARKER_DEPT=$(printf '%s' "$_ROUTE_LINE" | sed -E 's/.*\[arka:routing\][[:space:]]*([A-Za-z_-]+).*/\1/')
|
|
43
|
-
_MARKER_LEAD=$(printf '%s' "$_ROUTE_LINE" | sed -E 's/.*->[[:space:]]*([A-Za-z_-]+).*/\1/')
|
|
44
|
-
elif printf '%s' "$ASSISTANT_MSG" | grep -qiE '\[arka:trivial\][[:space:]]*\S+'; then
|
|
45
|
-
_MARKER_KIND="trivial"
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
if [ -n "$_MARKER_KIND" ]; then
|
|
49
|
-
_MARKER_ROOT="${ARKAOS_ROOT:-}"
|
|
50
|
-
if [ -z "$_MARKER_ROOT" ] && [ -f "$HOME/.arkaos/.repo-path" ]; then
|
|
51
|
-
_MARKER_ROOT=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)
|
|
52
|
-
fi
|
|
53
|
-
[ -z "$_MARKER_ROOT" ] && _MARKER_ROOT="$HOME/.arkaos"
|
|
54
|
-
SESSION_ID_PTU="$SESSION_ID_PTU" \
|
|
55
|
-
MARKER_KIND="$_MARKER_KIND" \
|
|
56
|
-
MARKER_DEPT="$_MARKER_DEPT" \
|
|
57
|
-
MARKER_LEAD="$_MARKER_LEAD" \
|
|
58
|
-
PYTHONPATH="$_MARKER_ROOT" \
|
|
59
|
-
python3 -c "
|
|
60
|
-
import os, sys
|
|
61
|
-
try:
|
|
62
|
-
from core.workflow.marker_cache import write_marker
|
|
63
|
-
write_marker(
|
|
64
|
-
os.environ.get('SESSION_ID_PTU', ''),
|
|
65
|
-
os.environ.get('MARKER_KIND', ''),
|
|
66
|
-
os.environ.get('MARKER_DEPT', ''),
|
|
67
|
-
os.environ.get('MARKER_LEAD', ''),
|
|
68
|
-
)
|
|
69
|
-
except Exception:
|
|
70
|
-
pass
|
|
71
|
-
" 2>/dev/null || true
|
|
29
|
+
ARKAOS_ROOT="${ARKA_OS:-$HOME/.claude/skills/arkaos}"
|
|
72
30
|
fi
|
|
73
31
|
fi
|
|
32
|
+
export ARKAOS_ROOT
|
|
74
33
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
if [
|
|
83
|
-
|
|
84
|
-
if [ "$
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
_AE_ROOT=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)
|
|
89
|
-
fi
|
|
90
|
-
[ -z "$_AE_ROOT" ] && _AE_ROOT="$HOME/.arkaos"
|
|
91
|
-
|
|
92
|
-
# ─── REJECTED → experience auto-record ────────────────────────────
|
|
93
|
-
if echo "$TOOL_OUTPUT" | grep -qE 'Quality Gate Verdict:[[:space:]]*REJECTED'; then
|
|
94
|
-
REVIEWING_TARGET=$(printf '%s' "$TOOL_INPUT_PROMPT" \
|
|
95
|
-
| grep -oE '\[arka:reviewing[[:space:]]+[A-Za-z0-9_.-]+\]' \
|
|
96
|
-
| head -1 \
|
|
97
|
-
| sed -E 's/.*\[arka:reviewing[[:space:]]+([A-Za-z0-9_.-]+)\].*/\1/')
|
|
98
|
-
if [ -n "$REVIEWING_TARGET" ]; then
|
|
99
|
-
VERDICT_TEXT="$TOOL_OUTPUT" \
|
|
100
|
-
AGENT_ID="$REVIEWING_TARGET" \
|
|
101
|
-
SESSION_ID="$SESSION_ID_PTU" \
|
|
102
|
-
ARKAOS_ROOT="$_AE_ROOT" \
|
|
103
|
-
python3 - <<'PY' 2>/dev/null || true
|
|
104
|
-
import os, sys
|
|
105
|
-
sys.path.insert(0, os.environ["ARKAOS_ROOT"])
|
|
106
|
-
try:
|
|
107
|
-
from core.governance.cqo_experience_recorder import record_from_verdict
|
|
108
|
-
record_from_verdict(
|
|
109
|
-
verdict_text=os.environ.get("VERDICT_TEXT", ""),
|
|
110
|
-
agent_id=os.environ.get("AGENT_ID", ""),
|
|
111
|
-
session_id=os.environ.get("SESSION_ID", ""),
|
|
112
|
-
context="auto-recorded via PostToolUse hook (cqo dispatch REJECTED)",
|
|
113
|
-
)
|
|
114
|
-
except Exception:
|
|
115
|
-
pass
|
|
116
|
-
PY
|
|
117
|
-
fi
|
|
118
|
-
fi
|
|
119
|
-
|
|
120
|
-
# ─── APPROVED → pattern stub auto-create (PR4.5 v3.75.1) ──────────
|
|
121
|
-
# When the dispatch prompt contains `[arka:pattern-suggest <id> <name>]`
|
|
122
|
-
# AND the verdict is APPROVED, create a stub PatternCard. The
|
|
123
|
-
# operator enriches by editing ~/.arkaos/patterns/cards.jsonl or
|
|
124
|
-
# calling record_pattern() with the full metadata. Skips when the
|
|
125
|
-
# id already exists (never overwrites enriched cards).
|
|
126
|
-
if echo "$TOOL_OUTPUT" | grep -qE 'Quality Gate Verdict:[[:space:]]*APPROVED'; then
|
|
127
|
-
PATTERN_LINE=$(printf '%s' "$TOOL_INPUT_PROMPT" \
|
|
128
|
-
| grep -oE '\[arka:pattern-suggest[[:space:]]+[A-Za-z0-9_.-]+[[:space:]]+[^][]+\]' \
|
|
129
|
-
| head -1)
|
|
130
|
-
if [ -n "$PATTERN_LINE" ]; then
|
|
131
|
-
PID=$(printf '%s' "$PATTERN_LINE" | sed -E 's/\[arka:pattern-suggest[[:space:]]+([A-Za-z0-9_.-]+).*/\1/')
|
|
132
|
-
PNAME=$(printf '%s' "$PATTERN_LINE" | sed -E 's/\[arka:pattern-suggest[[:space:]]+[A-Za-z0-9_.-]+[[:space:]]+([^][]+)\]/\1/')
|
|
133
|
-
if [ -n "$PID" ] && [ -n "$PNAME" ]; then
|
|
134
|
-
PATTERN_ID="$PID" \
|
|
135
|
-
PATTERN_NAME="$PNAME" \
|
|
136
|
-
ARKAOS_ROOT="$_AE_ROOT" \
|
|
137
|
-
python3 - <<'PY' 2>/dev/null || true
|
|
138
|
-
import os, sys
|
|
139
|
-
sys.path.insert(0, os.environ["ARKAOS_ROOT"])
|
|
140
|
-
try:
|
|
141
|
-
from datetime import datetime, timezone
|
|
142
|
-
from core.knowledge.pattern_cards import PatternCard, record_pattern, query_patterns
|
|
143
|
-
pid = os.environ["PATTERN_ID"]
|
|
144
|
-
existing = [c for c in query_patterns(limit=1000) if c.id == pid]
|
|
145
|
-
if not existing:
|
|
146
|
-
ts = datetime.now(timezone.utc).isoformat()
|
|
147
|
-
record_pattern(PatternCard(
|
|
148
|
-
id=pid,
|
|
149
|
-
name=os.environ["PATTERN_NAME"],
|
|
150
|
-
feature_keywords=[pid.replace("-", " "), os.environ["PATTERN_NAME"].lower()],
|
|
151
|
-
description="Stub auto-created from APPROVED CQO verdict — enrich via record_pattern() or by editing the JSONL.",
|
|
152
|
-
stack=[],
|
|
153
|
-
files=[],
|
|
154
|
-
acceptance_criteria=[],
|
|
155
|
-
edge_cases=[],
|
|
156
|
-
references=[],
|
|
157
|
-
projects_using=["arkaos"],
|
|
158
|
-
created_at=ts,
|
|
159
|
-
last_updated=ts,
|
|
160
|
-
))
|
|
161
|
-
except Exception:
|
|
162
|
-
pass
|
|
163
|
-
PY
|
|
164
|
-
fi
|
|
165
|
-
fi
|
|
166
|
-
fi
|
|
167
|
-
fi
|
|
168
|
-
|
|
169
|
-
# ─── Activation tracking (PR5 v3.76.0) ─────────────────────────────
|
|
170
|
-
# Record every Task/Agent dispatch regardless of subagent_type (CQO or
|
|
171
|
-
# any other). Runs AFTER the cqo branch so verdicts don't block it.
|
|
172
|
-
# Never blocks — _ACT_ROOT reuses the same resolution as _AE_ROOT.
|
|
173
|
-
if [ -n "$SUBAGENT_TYPE" ]; then
|
|
174
|
-
_ACT_ROOT="${ARKAOS_ROOT:-}"
|
|
175
|
-
if [ -z "$_ACT_ROOT" ] && [ -f "$HOME/.arkaos/.repo-path" ]; then
|
|
176
|
-
_ACT_ROOT=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)
|
|
177
|
-
fi
|
|
178
|
-
[ -z "$_ACT_ROOT" ] && _ACT_ROOT="$HOME/.arkaos"
|
|
179
|
-
SUBAGENT_TYPE="$SUBAGENT_TYPE" \
|
|
180
|
-
SESSION_ID="$SESSION_ID_PTU" \
|
|
181
|
-
ARKAOS_ROOT="$_ACT_ROOT" \
|
|
182
|
-
python3 - <<'PY' 2>/dev/null || true
|
|
183
|
-
import os, sys
|
|
184
|
-
sys.path.insert(0, os.environ["ARKAOS_ROOT"])
|
|
185
|
-
try:
|
|
186
|
-
from core.governance.activation_tracker import record_activation
|
|
187
|
-
record_activation(
|
|
188
|
-
subagent_type=os.environ.get("SUBAGENT_TYPE", ""),
|
|
189
|
-
session_id=os.environ.get("SESSION_ID", ""),
|
|
190
|
-
)
|
|
191
|
-
except Exception:
|
|
192
|
-
pass
|
|
193
|
-
PY
|
|
194
|
-
fi
|
|
195
|
-
fi
|
|
196
|
-
|
|
197
|
-
# Only process if there was an error
|
|
198
|
-
if [ "$EXIT_CODE" = "0" ] || [ -z "$EXIT_CODE" ]; then
|
|
199
|
-
# Also check for error patterns in output even with exit code 0
|
|
200
|
-
if ! echo "$TOOL_OUTPUT" | grep -qiE '(error:|fatal:|exception:|failed|ENOENT|EACCES|EPERM|panic:)'; then
|
|
34
|
+
if ! command -v python3 &>/dev/null; then
|
|
35
|
+
echo '{}'
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
# Self-root fallback: the wrapper ships next to its python entrypoint, so
|
|
39
|
+
# when ARKAOS_ROOT points at a pre-PR-6 install without core/hooks/, use
|
|
40
|
+
# the root this script lives in.
|
|
41
|
+
if [ ! -f "$ARKAOS_ROOT/core/hooks/post_tool_use.py" ]; then
|
|
42
|
+
_SELF_ROOT="$(cd "$(dirname "$0")/../.." 2>/dev/null && pwd)"
|
|
43
|
+
if [ -n "$_SELF_ROOT" ] && [ -f "$_SELF_ROOT/core/hooks/post_tool_use.py" ]; then
|
|
44
|
+
ARKAOS_ROOT="$_SELF_ROOT"
|
|
45
|
+
export ARKAOS_ROOT
|
|
46
|
+
else
|
|
201
47
|
echo '{}'
|
|
202
48
|
exit 0
|
|
203
49
|
fi
|
|
204
50
|
fi
|
|
205
51
|
|
|
206
|
-
#
|
|
207
|
-
#
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
PATTERN=$(echo "$ERROR_LINE" | \
|
|
219
|
-
sed -E 's/[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}[^ ]*/TIMESTAMP/g' | \
|
|
220
|
-
sed -E 's/[0-9a-f]{7,40}/HASH/g' | \
|
|
221
|
-
sed -E 's/line [0-9]+/line N/g' | \
|
|
222
|
-
sed -E 's/:[0-9]+:/:N:/g' | \
|
|
223
|
-
head -c 200)
|
|
224
|
-
|
|
225
|
-
[ -z "$PATTERN" ] && { echo '{}'; exit 0; }
|
|
226
|
-
|
|
227
|
-
# ─── Categorize ──────────────────────────────────────────────────────────
|
|
228
|
-
CATEGORY="general"
|
|
229
|
-
if echo "$ERROR_LINE" | grep -qiE '(artisan|eloquent|laravel|blade|migration|composer|php )'; then
|
|
230
|
-
CATEGORY="laravel"
|
|
231
|
-
elif echo "$ERROR_LINE" | grep -qiE '(npm|node|vue|react|nuxt|next|vite|webpack|typescript|tsx|jsx)'; then
|
|
232
|
-
CATEGORY="frontend"
|
|
233
|
-
elif echo "$ERROR_LINE" | grep -qiE '(git |merge|rebase|checkout|branch|commit|push|pull)'; then
|
|
234
|
-
CATEGORY="git"
|
|
235
|
-
elif echo "$ERROR_LINE" | grep -qiE '(sql|postgres|mysql|database|migration|table|column|constraint)'; then
|
|
236
|
-
CATEGORY="database"
|
|
237
|
-
elif echo "$ERROR_LINE" | grep -qiE '(permission|denied|EACCES|EPERM|chmod|chown|sudo)'; then
|
|
238
|
-
CATEGORY="permissions"
|
|
239
|
-
elif echo "$ERROR_LINE" | grep -qiE '(test|assert|expect|jest|phpunit|bats|coverage)'; then
|
|
240
|
-
CATEGORY="testing"
|
|
241
|
-
fi
|
|
242
|
-
|
|
243
|
-
# ─── Match Fix Suggestion ────────────────────────────────────────────────
|
|
244
|
-
SUGGESTION=""
|
|
245
|
-
FIXES_FILE="${ARKA_OS:-$HOME/.claude/skills/arka}/config/gotchas-fixes.json"
|
|
246
|
-
# Also check repo path
|
|
247
|
-
if [ ! -f "$FIXES_FILE" ]; then
|
|
248
|
-
REPO_PATH=$(cat "${ARKA_OS:-$HOME/.claude/skills/arka}/.repo-path" 2>/dev/null || echo "")
|
|
249
|
-
[ -n "$REPO_PATH" ] && FIXES_FILE="$REPO_PATH/config/gotchas-fixes.json"
|
|
250
|
-
fi
|
|
251
|
-
if [ -f "$FIXES_FILE" ] && command -v jq &>/dev/null; then
|
|
252
|
-
SUGGESTION=$(jq -r --arg err "$ERROR_LINE" '
|
|
253
|
-
.fixes[] | select(.pattern_match as $p | $err | test($p; "i")) | .suggestion
|
|
254
|
-
' "$FIXES_FILE" 2>/dev/null | head -1)
|
|
255
|
-
fi
|
|
256
|
-
|
|
257
|
-
# ─── Detect Active Project ───────────────────────────────────────────────
|
|
258
|
-
PROJECT=""
|
|
259
|
-
if [ -n "$CWD" ]; then
|
|
260
|
-
# Try to extract project name from CWD
|
|
261
|
-
REPO_PATH=$(cat "${ARKA_OS:-$HOME/.claude/skills/arka}/.repo-path" 2>/dev/null || echo "")
|
|
262
|
-
if [ -n "$REPO_PATH" ] && [ -d "$REPO_PATH/projects" ]; then
|
|
263
|
-
for proj_dir in "$REPO_PATH/projects"/*/; do
|
|
264
|
-
[ -f "$proj_dir/.project-path" ] || continue
|
|
265
|
-
PROJ_PATH=$(cat "$proj_dir/.project-path" 2>/dev/null)
|
|
266
|
-
if [ -n "$PROJ_PATH" ] && [[ "$CWD" == "$PROJ_PATH"* ]]; then
|
|
267
|
-
PROJECT=$(basename "$proj_dir")
|
|
268
|
-
break
|
|
269
|
-
fi
|
|
270
|
-
done
|
|
271
|
-
fi
|
|
272
|
-
# Fallback: use directory name
|
|
273
|
-
[ -z "$PROJECT" ] && PROJECT=$(basename "$CWD")
|
|
274
|
-
fi
|
|
275
|
-
|
|
276
|
-
# ─── Store in gotchas.json ────────────────────────────────────────────────
|
|
277
|
-
GOTCHAS_FILE="$HOME/.arkaos/gotchas.json"
|
|
278
|
-
mkdir -p "$HOME/.arkaos"
|
|
279
|
-
|
|
280
|
-
# Use flock for concurrent safety (fallback if flock not available on macOS)
|
|
281
|
-
LOCK_FILE="$HOME/.arkaos/gotchas.lock"
|
|
282
|
-
if command -v flock &>/dev/null; then
|
|
283
|
-
LOCK_CMD="flock -w 3 200"
|
|
284
|
-
else
|
|
285
|
-
LOCK_CMD="true"
|
|
286
|
-
fi
|
|
287
|
-
(
|
|
288
|
-
eval "$LOCK_CMD" || { echo '{}'; exit 0; }
|
|
289
|
-
|
|
290
|
-
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
291
|
-
|
|
292
|
-
# Initialize if missing or invalid
|
|
293
|
-
if [ ! -f "$GOTCHAS_FILE" ] || ! jq empty "$GOTCHAS_FILE" 2>/dev/null; then
|
|
294
|
-
echo '[]' > "$GOTCHAS_FILE"
|
|
295
|
-
fi
|
|
296
|
-
|
|
297
|
-
# Check if pattern already exists
|
|
298
|
-
EXISTING_IDX=$(jq -r --arg pat "$PATTERN" \
|
|
299
|
-
'to_entries[] | select(.value.pattern == $pat) | .key' \
|
|
300
|
-
"$GOTCHAS_FILE" 2>/dev/null | head -1)
|
|
301
|
-
|
|
302
|
-
if [ -n "$EXISTING_IDX" ] && [ "$EXISTING_IDX" -ge 0 ] 2>/dev/null; then
|
|
303
|
-
# Increment count, update last_seen, add project if new, add suggestion if missing
|
|
304
|
-
jq --argjson idx "$EXISTING_IDX" \
|
|
305
|
-
--arg now "$NOW" \
|
|
306
|
-
--arg proj "$PROJECT" \
|
|
307
|
-
--arg sug "$SUGGESTION" \
|
|
308
|
-
'.[$idx].count += 1 |
|
|
309
|
-
.[$idx].last_seen = $now |
|
|
310
|
-
(if $proj != "" and ($proj | IN(.[$idx].projects[]?) | not) then .[$idx].projects += [$proj] else . end) |
|
|
311
|
-
(if $sug != "" and ((.[$idx].suggestion // "") == "") then .[$idx].suggestion = $sug else . end)' \
|
|
312
|
-
"$GOTCHAS_FILE" > "$GOTCHAS_FILE.tmp" 2>/dev/null && mv "$GOTCHAS_FILE.tmp" "$GOTCHAS_FILE"
|
|
313
|
-
else
|
|
314
|
-
# Add new entry
|
|
315
|
-
jq --arg pat "$PATTERN" \
|
|
316
|
-
--arg full "$ERROR_LINE" \
|
|
317
|
-
--arg cat "$CATEGORY" \
|
|
318
|
-
--arg tool "$TOOL_NAME" \
|
|
319
|
-
--arg now "$NOW" \
|
|
320
|
-
--arg proj "$PROJECT" \
|
|
321
|
-
--arg sug "$SUGGESTION" \
|
|
322
|
-
'. += [{
|
|
323
|
-
"pattern": $pat,
|
|
324
|
-
"full_pattern": ($full | .[0:500]),
|
|
325
|
-
"category": $cat,
|
|
326
|
-
"tool": $tool,
|
|
327
|
-
"count": 1,
|
|
328
|
-
"first_seen": $now,
|
|
329
|
-
"last_seen": $now,
|
|
330
|
-
"projects": (if $proj != "" then [$proj] else [] end),
|
|
331
|
-
"suggestion": (if $sug != "" then $sug else null end)
|
|
332
|
-
}]' \
|
|
333
|
-
"$GOTCHAS_FILE" > "$GOTCHAS_FILE.tmp" 2>/dev/null && mv "$GOTCHAS_FILE.tmp" "$GOTCHAS_FILE"
|
|
334
|
-
fi
|
|
335
|
-
|
|
336
|
-
# Keep top 100 sorted by count
|
|
337
|
-
jq 'sort_by(-.count) | .[0:100]' "$GOTCHAS_FILE" > "$GOTCHAS_FILE.tmp" 2>/dev/null && \
|
|
338
|
-
mv "$GOTCHAS_FILE.tmp" "$GOTCHAS_FILE"
|
|
339
|
-
|
|
340
|
-
) 200>"$LOCK_FILE"
|
|
341
|
-
|
|
342
|
-
# ─── Workflow Violation Detection ────────────────────────────────────────
|
|
343
|
-
VIOLATION_MSG=""
|
|
344
|
-
STATE_READER=""
|
|
345
|
-
[ -f "$HOME/.arkaos/.repo-path" ] && STATE_READER="$(cat "$HOME/.arkaos/.repo-path")/core/workflow/state_reader.sh"
|
|
346
|
-
|
|
347
|
-
if [ -n "$STATE_READER" ] && [ -f "$STATE_READER" ] && bash "$STATE_READER" active 2>/dev/null; then
|
|
348
|
-
ARKAOS_PY=""
|
|
349
|
-
[ -f "$HOME/.arkaos/venv/bin/python3" ] && ARKAOS_PY="$HOME/.arkaos/venv/bin/python3"
|
|
350
|
-
[ -z "$ARKAOS_PY" ] && [ -f "$HOME/.arkaos/.venv/bin/python3" ] && ARKAOS_PY="$HOME/.arkaos/.venv/bin/python3"
|
|
351
|
-
[ -z "$ARKAOS_PY" ] && ARKAOS_PY=$(command -v python3 2>/dev/null)
|
|
352
|
-
ARKAOS_ROOT=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)
|
|
353
|
-
|
|
354
|
-
# Rule 1: Branch isolation — commit on master while workflow active
|
|
355
|
-
if [ "$TOOL_NAME" = "Bash" ]; then
|
|
356
|
-
if echo "$TOOL_OUTPUT" | grep -qE '^\[(master|main)' 2>/dev/null; then
|
|
357
|
-
CMD_TEXT=$(echo "$input" | jq -r '.command // ""' 2>/dev/null)
|
|
358
|
-
if echo "$CMD_TEXT" | grep -qE 'git commit'; then
|
|
359
|
-
[ -n "$ARKAOS_PY" ] && [ -n "$ARKAOS_ROOT" ] && \
|
|
360
|
-
PYTHONPATH="$ARKAOS_ROOT" $ARKAOS_PY -c "
|
|
361
|
-
from core.workflow.state import add_violation
|
|
362
|
-
add_violation('branch-isolation', 'Commit on master/main while workflow active', 'Bash')
|
|
363
|
-
" 2>/dev/null
|
|
364
|
-
VIOLATION_MSG="VIOLATION [branch-isolation]: Commit on master while workflow active. Use a feature branch."
|
|
365
|
-
fi
|
|
366
|
-
fi
|
|
367
|
-
fi
|
|
368
|
-
|
|
369
|
-
# Rule 2: Spec-driven — code edited without completed spec
|
|
370
|
-
if [ "$TOOL_NAME" = "Write" ] || [ "$TOOL_NAME" = "Edit" ]; then
|
|
371
|
-
FILE_PATH=$(echo "$input" | jq -r '.file_path // ""' 2>/dev/null)
|
|
372
|
-
if echo "$FILE_PATH" | grep -qE '\.(py|js|ts|vue|php|jsx|tsx)$'; then
|
|
373
|
-
if ! bash "$STATE_READER" check spec 2>/dev/null; then
|
|
374
|
-
[ -n "$ARKAOS_PY" ] && [ -n "$ARKAOS_ROOT" ] && \
|
|
375
|
-
PYTHONPATH="$ARKAOS_ROOT" _V_TOOL="$TOOL_NAME" _V_FILE="$FILE_PATH" $ARKAOS_PY -c "
|
|
376
|
-
import os; from core.workflow.state import add_violation
|
|
377
|
-
add_violation('spec-driven', 'Code edited without completed spec', os.environ['_V_TOOL'], os.environ['_V_FILE'])
|
|
378
|
-
" 2>/dev/null
|
|
379
|
-
VIOLATION_MSG="VIOLATION [spec-driven]: Code edited without completed spec ($FILE_PATH). Complete the spec phase first."
|
|
380
|
-
fi
|
|
381
|
-
fi
|
|
382
|
-
fi
|
|
383
|
-
|
|
384
|
-
# Rule 3: Sequential — implementation before planning
|
|
385
|
-
if [ -z "$VIOLATION_MSG" ] && { [ "$TOOL_NAME" = "Write" ] || [ "$TOOL_NAME" = "Edit" ]; }; then
|
|
386
|
-
FILE_PATH=$(echo "$input" | jq -r '.file_path // ""' 2>/dev/null)
|
|
387
|
-
if echo "$FILE_PATH" | grep -qE '\.(py|js|ts|vue|php|jsx|tsx)$'; then
|
|
388
|
-
IMPL_STATUS=$(bash "$STATE_READER" phase implementation 2>/dev/null)
|
|
389
|
-
if [ "$IMPL_STATUS" = "pending" ]; then
|
|
390
|
-
[ -n "$ARKAOS_PY" ] && [ -n "$ARKAOS_ROOT" ] && \
|
|
391
|
-
PYTHONPATH="$ARKAOS_ROOT" _V_TOOL="$TOOL_NAME" _V_FILE="$FILE_PATH" $ARKAOS_PY -c "
|
|
392
|
-
import os; from core.workflow.state import add_violation
|
|
393
|
-
add_violation('sequential-validation', 'Code written before implementation phase started', os.environ['_V_TOOL'], os.environ['_V_FILE'])
|
|
394
|
-
" 2>/dev/null
|
|
395
|
-
VIOLATION_MSG="VIOLATION [sequential-validation]: Implementation started before planning completed ($FILE_PATH)."
|
|
396
|
-
fi
|
|
397
|
-
fi
|
|
398
|
-
fi
|
|
399
|
-
fi
|
|
400
|
-
|
|
401
|
-
# ─── ArkaOS Enforcement Engine (All 14 Rules) ─────────────────────────────────
|
|
402
|
-
# Uses core/workflow/enforcer.py to check ALL 14 NON-NEGOTIABLE rules
|
|
403
|
-
# BLOCK violations halt operation; ESCALATE violations alert Tier 0
|
|
404
|
-
# Gotchas auto-recovery is ALWAYS active (SIM per Sprint 3 decision)
|
|
405
|
-
|
|
406
|
-
if [ -z "${ARKAOS_PY:-}" ]; then
|
|
407
|
-
[ -f "$HOME/.arkaos/venv/bin/python3" ] && ARKAOS_PY="$HOME/.arkaos/venv/bin/python3"
|
|
408
|
-
[ -z "${ARKAOS_PY:-}" ] && [ -f "$HOME/.arkaos/.venv/bin/python3" ] && ARKAOS_PY="$HOME/.arkaos/.venv/bin/python3"
|
|
409
|
-
[ -z "${ARKAOS_PY:-}" ] && ARKAOS_PY=$(command -v python3 2>/dev/null)
|
|
410
|
-
fi
|
|
411
|
-
[ -z "${ARKAOS_ROOT:-}" ] && ARKAOS_ROOT=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)
|
|
412
|
-
|
|
413
|
-
if [ -n "$ARKAOS_PY" ] && [ -n "$ARKAOS_ROOT" ] && [ -f "$ARKAOS_ROOT/core/workflow/enforcer.py" ]; then
|
|
414
|
-
ENFORCER_OUTPUT=$(PYTHONPATH="$ARKAOS_ROOT" $ARKAOS_PY -c "
|
|
415
|
-
import json, sys
|
|
416
|
-
from core.workflow.enforcer import enforce_tool
|
|
417
|
-
from core.workflow.state import add_violation
|
|
418
|
-
|
|
419
|
-
input_data = json.loads(sys.stdin.read())
|
|
420
|
-
tool_name = input_data.get('tool_name', '')
|
|
421
|
-
command = input_data.get('command', '')
|
|
422
|
-
file_path = input_data.get('file_path', '')
|
|
423
|
-
user_input = input_data.get('user_input', '')
|
|
424
|
-
|
|
425
|
-
extra = {}
|
|
426
|
-
if tool_name == 'Bash':
|
|
427
|
-
import subprocess
|
|
428
|
-
try:
|
|
429
|
-
branch = subprocess.check_output(['git', 'branch', '--show-current'], text=True, stderr=subprocess.DEVNULL).strip()
|
|
430
|
-
extra['git_branch'] = branch
|
|
431
|
-
except:
|
|
432
|
-
extra['git_branch'] = ''
|
|
433
|
-
|
|
434
|
-
result = enforce_tool(
|
|
435
|
-
tool_name=tool_name,
|
|
436
|
-
command=command,
|
|
437
|
-
file_path=file_path,
|
|
438
|
-
user_input=user_input,
|
|
439
|
-
**extra
|
|
440
|
-
)
|
|
441
|
-
|
|
442
|
-
if result.violations:
|
|
443
|
-
for v in result.violations:
|
|
444
|
-
try:
|
|
445
|
-
add_violation(v.rule_id, v.message, v.tool, v.file_path, v.severity)
|
|
446
|
-
except:
|
|
447
|
-
pass
|
|
448
|
-
|
|
449
|
-
print(json.dumps({
|
|
450
|
-
'violations': [v.to_dict() for v in result.violations],
|
|
451
|
-
'blocked': result.blocked,
|
|
452
|
-
'escalated': result.escalated,
|
|
453
|
-
'messages': result.messages
|
|
454
|
-
}))
|
|
455
|
-
else:
|
|
456
|
-
print(json.dumps({'violations': [], 'blocked': False, 'escalated': False, 'messages': []}))
|
|
457
|
-
" <<< "$input" 2>/dev/null)
|
|
458
|
-
|
|
459
|
-
if [ -n "$ENFORCER_OUTPUT" ] && echo "$ENFORCER_OUTPUT" | jq -e '.violations | length > 0' &>/dev/null; then
|
|
460
|
-
ENFORCER_BLOCKED=$(echo "$ENFORCER_OUTPUT" | jq -r '.blocked')
|
|
461
|
-
ENFORCER_MESSAGES=$(echo "$ENFORCER_OUTPUT" | jq -r '.messages | join("|")')
|
|
462
|
-
|
|
463
|
-
for msg in $(echo "$ENFORCER_MESSAGES" | tr '|' '\n'); do
|
|
464
|
-
[ -n "$msg" ] && VIOLATION_MSG="${VIOLATION_MSG}${VIOLATION_MSG:+$'\n'}${msg}"
|
|
52
|
+
# Interpreter resolution: prefer the ArkaOS venv (has pyyaml/pydantic);
|
|
53
|
+
# otherwise probe — an unrelated project venv first on PATH would give a
|
|
54
|
+
# python3 without yaml and silently degrade every gate.
|
|
55
|
+
_PY=""
|
|
56
|
+
for _cand in "$HOME/.arkaos/venv/bin/python3" "$HOME/.arkaos/.venv/bin/python3"; do
|
|
57
|
+
if [ -x "$_cand" ]; then _PY="$_cand"; break; fi
|
|
58
|
+
done
|
|
59
|
+
if [ -z "$_PY" ]; then
|
|
60
|
+
_PY="python3"
|
|
61
|
+
if ! "$_PY" -c "import yaml" 2>/dev/null; then
|
|
62
|
+
for _cand in /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3; do
|
|
63
|
+
if [ -x "$_cand" ] && "$_cand" -c "import yaml" 2>/dev/null; then _PY="$_cand"; break; fi
|
|
465
64
|
done
|
|
466
|
-
|
|
467
|
-
if [ "$ENFORCER_BLOCKED" = "true" ]; then
|
|
468
|
-
VIOLATION_MSG="🔴 BLOCK: ${VIOLATION_MSG}"
|
|
469
|
-
fi
|
|
470
65
|
fi
|
|
471
66
|
fi
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
_FORGE_ACTIVE="$HOME/.arkaos/plans/active.yaml"
|
|
475
|
-
|
|
476
|
-
# Ensure ARKAOS_PY and ARKAOS_ROOT are set (may not be set if no active workflow)
|
|
477
|
-
if [ -z "${ARKAOS_PY:-}" ]; then
|
|
478
|
-
[ -f "$HOME/.arkaos/venv/bin/python3" ] && ARKAOS_PY="$HOME/.arkaos/venv/bin/python3"
|
|
479
|
-
[ -z "${ARKAOS_PY:-}" ] && [ -f "$HOME/.arkaos/.venv/bin/python3" ] && ARKAOS_PY="$HOME/.arkaos/.venv/bin/python3"
|
|
480
|
-
[ -z "${ARKAOS_PY:-}" ] && ARKAOS_PY=$(command -v python3 2>/dev/null)
|
|
481
|
-
fi
|
|
482
|
-
[ -z "${ARKAOS_ROOT:-}" ] && ARKAOS_ROOT=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)
|
|
483
|
-
|
|
484
|
-
if [ -z "$VIOLATION_MSG" ] && [ -f "$_FORGE_ACTIVE" ] && [ -n "$ARKAOS_PY" ] && [ -n "$ARKAOS_ROOT" ]; then
|
|
485
|
-
_FORGE_ID=$(cat "$_FORGE_ACTIVE" 2>/dev/null)
|
|
486
|
-
_FORGE_FILE="$HOME/.arkaos/plans/${_FORGE_ID}.yaml"
|
|
487
|
-
|
|
488
|
-
if [ -f "$_FORGE_FILE" ]; then
|
|
489
|
-
if [ "$TOOL_NAME" = "Edit" ] || [ "$TOOL_NAME" = "Write" ]; then
|
|
490
|
-
_EDITED_FILE="${tool_input_file_path:-}"
|
|
491
|
-
# Fallback: extract file_path from input JSON
|
|
492
|
-
[ -z "$_EDITED_FILE" ] && _EDITED_FILE=$(echo "$input" | jq -r '.file_path // ""' 2>/dev/null)
|
|
493
|
-
if [ -n "$_EDITED_FILE" ]; then
|
|
494
|
-
_FORGE_VIOLATION=$(FORGE_FILE="$_FORGE_FILE" EDITED_FILE="$_EDITED_FILE" PYTHONPATH="$ARKAOS_ROOT" $ARKAOS_PY -c "
|
|
495
|
-
import yaml, sys, os
|
|
496
|
-
plan = yaml.safe_load(open(os.environ['FORGE_FILE']))
|
|
497
|
-
if plan.get('status', '') != 'executing':
|
|
498
|
-
sys.exit(0)
|
|
499
|
-
phases = plan.get('plan_phases', [])
|
|
500
|
-
all_deliverables = []
|
|
501
|
-
for p in phases:
|
|
502
|
-
all_deliverables.extend(p.get('deliverables', []))
|
|
503
|
-
edited = os.environ['EDITED_FILE']
|
|
504
|
-
match = any(d in edited or edited.endswith(d) for d in all_deliverables)
|
|
505
|
-
if not match and all_deliverables:
|
|
506
|
-
print('forge-scope-creep')
|
|
507
|
-
" 2>/dev/null)
|
|
508
|
-
if [ "$_FORGE_VIOLATION" = "forge-scope-creep" ]; then
|
|
509
|
-
VIOLATION_MSG="⚠ Forge scope-creep: editing ${_EDITED_FILE} which is outside forge plan deliverables."
|
|
510
|
-
fi
|
|
511
|
-
fi
|
|
512
|
-
fi
|
|
513
|
-
fi
|
|
514
|
-
fi
|
|
515
|
-
|
|
516
|
-
# ─── Cognitive layer capture (PR4 v2.26.0 hooks-as-retrieval) ────────────
|
|
517
|
-
# Backgrounded so the hook returns immediately. The retrieval helper has
|
|
518
|
-
# its own internal time caps (ripgrep ≤ 1s, Python fallback capped at 500
|
|
519
|
-
# files) so a runaway extract can never block the next user prompt. We
|
|
520
|
-
# disown so the child cannot zombie if the hook process exits first.
|
|
521
|
-
if [ -n "$SESSION_ID_PTU" ] && [ -n "$TOOL_OUTPUT" ]; then
|
|
522
|
-
_ARKAOS_REPO=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null || echo "")
|
|
523
|
-
if [ -n "$_ARKAOS_REPO" ] && [ -d "$_ARKAOS_REPO" ]; then
|
|
524
|
-
(
|
|
525
|
-
PYTHONPATH="$_ARKAOS_REPO" python3 -m core.cognition.retrieval capture "$SESSION_ID_PTU" <<EOF >/dev/null 2>&1
|
|
526
|
-
$TOOL_OUTPUT
|
|
527
|
-
EOF
|
|
528
|
-
) &
|
|
529
|
-
disown 2>/dev/null || true
|
|
530
|
-
fi
|
|
531
|
-
fi
|
|
532
|
-
|
|
533
|
-
# ─── Log Metrics ─────────────────────────────────────────────────────────
|
|
534
|
-
_DURATION_MS=$(_hook_ms)
|
|
535
|
-
METRICS_FILE="$HOME/.arkaos/hook-metrics.json"
|
|
536
|
-
METRICS_LOCK="$HOME/.arkaos/hook-metrics.lock"
|
|
537
|
-
mkdir -p "$HOME/.arkaos"
|
|
538
|
-
(
|
|
539
|
-
if command -v flock &>/dev/null; then flock -w 2 200; else true; fi
|
|
540
|
-
[ ! -f "$METRICS_FILE" ] && echo '[]' > "$METRICS_FILE"
|
|
541
|
-
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
542
|
-
jq --argjson dur "$_DURATION_MS" --arg ts "$NOW" --arg hook "post-tool-use" \
|
|
543
|
-
'. += [{"hook": $hook, "duration_ms": $dur, "timestamp": $ts}] | .[-500:]' \
|
|
544
|
-
"$METRICS_FILE" > "$METRICS_FILE.tmp" 2>/dev/null && mv "$METRICS_FILE.tmp" "$METRICS_FILE"
|
|
545
|
-
) 200>"$METRICS_LOCK" 2>/dev/null
|
|
546
|
-
|
|
547
|
-
# Output violation as context if detected, otherwise empty
|
|
548
|
-
if [ -n "$VIOLATION_MSG" ]; then
|
|
549
|
-
echo "{\"additionalContext\": \"$VIOLATION_MSG\"}"
|
|
550
|
-
else
|
|
551
|
-
echo '{}'
|
|
552
|
-
fi
|
|
67
|
+
PYTHONPATH="$ARKAOS_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
|
|
68
|
+
exec "$_PY" -m core.hooks.post_tool_use
|