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
package/config/hooks/stop.sh
CHANGED
|
@@ -1,113 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# ============================================================================
|
|
3
|
-
# ArkaOS v2 — Stop Hook (
|
|
3
|
+
# ArkaOS v2 — Stop Hook (thin wrapper, PR-6 v4.1.0 hook hygiene)
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# [arka:trivial]. If absent, a structured warning is appended to
|
|
8
|
-
# ~/.arkaos/telemetry/enforcement.jsonl. The hook NEVER blocks in v1.
|
|
5
|
+
# Delegates the ENTIRE event to ONE python process:
|
|
6
|
+
# python3 -m core.hooks.stop
|
|
9
7
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
8
|
+
# WARN mode: this hook NEVER blocks — always exit 0. The entrypoint
|
|
9
|
+
# preserves the behavior contract of the previous multi-heredoc version:
|
|
10
|
+
# - DNA fidelity check on the closing assistant message.
|
|
11
|
+
# - Native usage cost capture (native:session rows, pre-WF_MARKER).
|
|
12
|
+
# - Flow completion validation: closing markers ([arka:gate:4],
|
|
13
|
+
# legacy [arka:phase:13], [arka:trivial]), meta_tag_found for the
|
|
14
|
+
# [arka:meta] transparency tag, KB citation soft-block, sycophancy
|
|
15
|
+
# detection, closing-marker soft-block, skill proposer, kb=N
|
|
16
|
+
# reconciliation — all recorded to
|
|
17
|
+
# ~/.arkaos/telemetry/enforcement.jsonl with mode "warn".
|
|
18
|
+
# - [arka:gate:N] checkpoint persistence for structured resume.
|
|
19
|
+
# - Auto-documentor enqueue (flow-required + QG APPROVED + external
|
|
20
|
+
# research). Transcript parsed ONCE and shared by every consumer.
|
|
12
21
|
#
|
|
13
22
|
# Timeout: 5s | Always exit 0.
|
|
14
23
|
# ============================================================================
|
|
15
24
|
|
|
16
|
-
input=$(cat)
|
|
17
|
-
|
|
18
|
-
SESSION_ID=""
|
|
19
|
-
TRANSCRIPT_PATH=""
|
|
20
|
-
STOP_HOOK_ACTIVE=""
|
|
21
|
-
CWD=""
|
|
22
|
-
EFFORT_LEVEL=""
|
|
23
|
-
ASSISTANT_MSG_STOP=""
|
|
24
|
-
if command -v jq &>/dev/null; then
|
|
25
|
-
SESSION_ID=$(echo "$input" | jq -r '.session_id // ""' 2>/dev/null)
|
|
26
|
-
TRANSCRIPT_PATH=$(echo "$input" | jq -r '.transcript_path // ""' 2>/dev/null)
|
|
27
|
-
STOP_HOOK_ACTIVE=$(echo "$input" | jq -r '.stop_hook_active // ""' 2>/dev/null)
|
|
28
|
-
CWD=$(echo "$input" | jq -r '.cwd // ""' 2>/dev/null)
|
|
29
|
-
# PR46 v2.65.0 — Claude Code W19 ships effort.level in hook stdin and
|
|
30
|
-
# $CLAUDE_EFFORT env var. Soft-block checks (kb-cite, meta-tag) only
|
|
31
|
-
# run at high|xhigh; hard enforcement runs regardless.
|
|
32
|
-
EFFORT_LEVEL=$(echo "$input" | jq -r '.effort.level // ""' 2>/dev/null)
|
|
33
|
-
# PR5 v3.76.0 — DNA fidelity check needs the closing assistant message.
|
|
34
|
-
ASSISTANT_MSG_STOP=$(echo "$input" | jq -r '.assistant_message // ""' 2>/dev/null)
|
|
35
|
-
fi
|
|
36
|
-
# Fallback to env var if stdin didn't carry it
|
|
37
|
-
[ -z "$EFFORT_LEVEL" ] && EFFORT_LEVEL="${CLAUDE_EFFORT:-}"
|
|
38
|
-
|
|
39
|
-
# Telemetry-only signal. Soft-block checks (kb_cite, meta_tag, sycophancy)
|
|
40
|
-
# always run here because they're cheap and feed /arka compliance.
|
|
41
|
-
# What is effort-gated is the NUDGE SURFACING in user-prompt-submit.sh
|
|
42
|
-
# (whether the next turn sees a [arka:suggest] line). Record the level
|
|
43
|
-
# on the telemetry row so we can later analyze suppression rates.
|
|
44
|
-
|
|
45
|
-
# ─── DNA Fidelity Check (PR5 v3.76.0) ───────────────────────────────────
|
|
46
|
-
# Fires for every session (no WF_MARKER dependency — fidelity is always
|
|
47
|
-
# worth measuring). Extracts the dispatched persona from the last routing/
|
|
48
|
-
# dispatch marker in the closing message, then calls check_fidelity() +
|
|
49
|
-
# record_fidelity(). Soft-warn only, never blocks. Zero violations are
|
|
50
|
-
# recorded too — absence of violations is signal.
|
|
51
|
-
if [ -n "$ASSISTANT_MSG_STOP" ] && [ -n "$SESSION_ID" ] && command -v python3 &>/dev/null; then
|
|
52
|
-
_FID_ROOT="${ARKAOS_ROOT:-}"
|
|
53
|
-
if [ -z "$_FID_ROOT" ] && [ -f "$HOME/.arkaos/.repo-path" ]; then
|
|
54
|
-
_FID_ROOT=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)
|
|
55
|
-
fi
|
|
56
|
-
[ -z "$_FID_ROOT" ] && _FID_ROOT="$HOME/.arkaos"
|
|
57
|
-
|
|
58
|
-
# Extract persona: dispatch marker takes precedence over routing.
|
|
59
|
-
# Latest match wins (tail -1). Both patterns: [arka:dispatch] X -> Y
|
|
60
|
-
# and [arka:routing] X -> Y. Persona is the right-hand side, lowercased.
|
|
61
|
-
_FIDELITY_PERSONA=""
|
|
62
|
-
_DISPATCH_HIT=$(printf '%s' "$ASSISTANT_MSG_STOP" \
|
|
63
|
-
| grep -ioE '\[arka:dispatch\][[:space:]]*[A-Za-z0-9_-]+[[:space:]]*->[[:space:]]*[A-Za-z0-9_-]+' \
|
|
64
|
-
| tail -1)
|
|
65
|
-
if [ -n "$_DISPATCH_HIT" ]; then
|
|
66
|
-
_FIDELITY_PERSONA=$(printf '%s' "$_DISPATCH_HIT" \
|
|
67
|
-
| sed -E 's/.*->[[:space:]]*//' | tr '[:upper:]' '[:lower:]')
|
|
68
|
-
else
|
|
69
|
-
_ROUTING_HIT=$(printf '%s' "$ASSISTANT_MSG_STOP" \
|
|
70
|
-
| grep -ioE '\[arka:routing\][[:space:]]*[A-Za-z0-9_-]+[[:space:]]*->[[:space:]]*[A-Za-z0-9_-]+' \
|
|
71
|
-
| tail -1)
|
|
72
|
-
if [ -n "$_ROUTING_HIT" ]; then
|
|
73
|
-
_FIDELITY_PERSONA=$(printf '%s' "$_ROUTING_HIT" \
|
|
74
|
-
| sed -E 's/.*->[[:space:]]*//' | tr '[:upper:]' '[:lower:]')
|
|
75
|
-
fi
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
|
-
if [ -n "$_FIDELITY_PERSONA" ]; then
|
|
79
|
-
FIDELITY_PERSONA="$_FIDELITY_PERSONA" \
|
|
80
|
-
FIDELITY_SESSION_ID="$SESSION_ID" \
|
|
81
|
-
FIDELITY_MSG="$ASSISTANT_MSG_STOP" \
|
|
82
|
-
ARKAOS_ROOT="$_FID_ROOT" \
|
|
83
|
-
python3 - <<'PY' 2>/dev/null || true
|
|
84
|
-
import os, sys
|
|
85
|
-
sys.path.insert(0, os.environ["ARKAOS_ROOT"])
|
|
86
|
-
try:
|
|
87
|
-
from core.governance.dna_fidelity import check_fidelity, record_fidelity
|
|
88
|
-
agent_id = os.environ.get("FIDELITY_PERSONA", "")
|
|
89
|
-
session_id = os.environ.get("FIDELITY_SESSION_ID", "")
|
|
90
|
-
output = os.environ.get("FIDELITY_MSG", "")
|
|
91
|
-
violations = check_fidelity(agent_id, output)
|
|
92
|
-
record_fidelity(agent_id, session_id, violations)
|
|
93
|
-
except Exception:
|
|
94
|
-
pass
|
|
95
|
-
PY
|
|
96
|
-
fi
|
|
97
|
-
fi
|
|
98
|
-
|
|
99
|
-
# Prevent infinite loops when Stop hook was triggered by its own decision.
|
|
100
|
-
if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
|
|
101
|
-
exit 0
|
|
102
|
-
fi
|
|
103
|
-
|
|
104
|
-
# Only evaluate sessions where the classifier flagged a creation intent.
|
|
105
|
-
WF_MARKER="/tmp/arkaos-wf-required/$SESSION_ID"
|
|
106
|
-
if [ -z "$SESSION_ID" ] || [ ! -f "$WF_MARKER" ]; then
|
|
107
|
-
exit 0
|
|
108
|
-
fi
|
|
109
|
-
|
|
110
|
-
# Resolve ARKAOS_ROOT
|
|
111
25
|
if [ -z "${ARKAOS_ROOT:-}" ]; then
|
|
112
26
|
if [ -f "$HOME/.arkaos/.repo-path" ]; then
|
|
113
27
|
ARKAOS_ROOT=$(cat "$HOME/.arkaos/.repo-path")
|
|
@@ -117,333 +31,39 @@ if [ -z "${ARKAOS_ROOT:-}" ]; then
|
|
|
117
31
|
ARKAOS_ROOT="${ARKA_OS:-$HOME/.claude/skills/arkaos}"
|
|
118
32
|
fi
|
|
119
33
|
fi
|
|
34
|
+
export ARKAOS_ROOT
|
|
120
35
|
|
|
121
36
|
if ! command -v python3 &>/dev/null; then
|
|
122
37
|
exit 0
|
|
123
38
|
fi
|
|
124
|
-
|
|
125
|
-
|
|
39
|
+
# Self-root fallback: the wrapper ships next to its python entrypoint, so
|
|
40
|
+
# when ARKAOS_ROOT points at a pre-PR-6 install without core/hooks/, use
|
|
41
|
+
# the root this script lives in.
|
|
42
|
+
if [ ! -f "$ARKAOS_ROOT/core/hooks/stop.py" ]; then
|
|
43
|
+
_SELF_ROOT="$(cd "$(dirname "$0")/../.." 2>/dev/null && pwd)"
|
|
44
|
+
if [ -n "$_SELF_ROOT" ] && [ -f "$_SELF_ROOT/core/hooks/stop.py" ]; then
|
|
45
|
+
ARKAOS_ROOT="$_SELF_ROOT"
|
|
46
|
+
export ARKAOS_ROOT
|
|
47
|
+
else
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
126
50
|
fi
|
|
127
51
|
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
import
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
sys.path.insert(0, os.environ["ARKAOS_ROOT_VAL"])
|
|
143
|
-
try:
|
|
144
|
-
from core.workflow.flow_enforcer import (
|
|
145
|
-
_load_last_assistant_messages,
|
|
146
|
-
TELEMETRY_PATH,
|
|
147
|
-
clear_flow_required,
|
|
148
|
-
)
|
|
149
|
-
except Exception:
|
|
150
|
-
sys.exit(0)
|
|
151
|
-
|
|
152
|
-
session_id = os.environ.get("SESSION_ID_VAL", "")
|
|
153
|
-
transcript_path = os.environ.get("TRANSCRIPT_PATH_VAL", "")
|
|
154
|
-
cwd = os.environ.get("CWD_VAL", "")
|
|
155
|
-
|
|
156
|
-
# Only inspect the very last assistant message for closing markers.
|
|
157
|
-
messages = _load_last_assistant_messages(transcript_path, n=1)
|
|
158
|
-
last = messages[-1] if messages else ""
|
|
159
|
-
|
|
160
|
-
phase13 = bool(re.search(r"\[arka:phase:13\]", last, re.IGNORECASE))
|
|
161
|
-
trivial = bool(re.search(r"\[arka:trivial\]", last, re.IGNORECASE))
|
|
162
|
-
closing_ok = phase13 or trivial
|
|
163
|
-
|
|
164
|
-
# PR12 v2.34.0 — Transparency tag measurement (warn-only).
|
|
165
|
-
# The [arka:meta] tag should appear on every substantive response that
|
|
166
|
-
# consulted KB / research / persona. Absence here is recorded so we can
|
|
167
|
-
# measure compliance before promoting to enforcement in a later PR.
|
|
168
|
-
meta_tag_found = bool(re.search(r"\[arka:meta\]", last, re.IGNORECASE))
|
|
169
|
-
|
|
170
|
-
# PR13 v2.35.0 — Sycophancy detection (warn-only).
|
|
171
|
-
# Heuristic detector records when a response shows agreement-without-
|
|
172
|
-
# critique, pure-agreement-standalone, recommendation-without-reference-
|
|
173
|
-
# company, or missing-critic-verdict signals. Telemetry only; never
|
|
174
|
-
# blocks. Promotion to hard enforce is a later PR after baseline data.
|
|
175
|
-
sycophancy_signals: list = []
|
|
176
|
-
sycophancy_confidence = 0.0
|
|
177
|
-
is_sycophantic = False
|
|
178
|
-
try:
|
|
179
|
-
from core.governance.sycophancy_detector import detect_sycophancy
|
|
180
|
-
sv = detect_sycophancy(last)
|
|
181
|
-
sycophancy_signals = sv.signals
|
|
182
|
-
sycophancy_confidence = sv.confidence
|
|
183
|
-
is_sycophantic = sv.is_sycophantic
|
|
184
|
-
except Exception:
|
|
185
|
-
pass
|
|
186
|
-
|
|
187
|
-
# PR18 v2.40.0 — KB citation soft-block. Records whether the closing
|
|
188
|
-
# assistant message cited the vault on an ArkaOS topic. Result is also
|
|
189
|
-
# persisted to /tmp/arkaos-cite/<session>.json so the next UserPromptSubmit
|
|
190
|
-
# can surface a nudge if passed=False. Non-blocking; never raises.
|
|
191
|
-
cite_passed = True
|
|
192
|
-
cite_reason = "trivial"
|
|
193
|
-
cite_count = 0
|
|
194
|
-
cite_topic_score = 0.0
|
|
195
|
-
cite_suggestion: str | None = None
|
|
196
|
-
try:
|
|
197
|
-
from core.governance.kb_cite_check import check_citation
|
|
198
|
-
cr = check_citation(last)
|
|
199
|
-
cite_passed = cr.passed
|
|
200
|
-
cite_reason = cr.reason
|
|
201
|
-
cite_count = cr.citation_count
|
|
202
|
-
cite_topic_score = cr.topic_score
|
|
203
|
-
cite_suggestion = cr.suggestion
|
|
204
|
-
# PR18 security fix: session_id comes from the runtime via stdin JSON
|
|
205
|
-
# and is untrusted. Reuse the shared allowlist before building any
|
|
206
|
-
# filesystem path. Reject (skip write) on anything outside
|
|
207
|
-
# [A-Za-z0-9._-]{1,128} — no `..`, no slashes, no whitespace, no NUL.
|
|
208
|
-
try:
|
|
209
|
-
from core.shared.safe_session_id import safe_session_id as _safe_sid
|
|
210
|
-
safe_sid = _safe_sid(session_id)
|
|
211
|
-
except Exception:
|
|
212
|
-
safe_sid = None
|
|
213
|
-
if safe_sid:
|
|
214
|
-
# PR25 v2.46.1 — restrict cite-file permissions on shared
|
|
215
|
-
# multi-user systems. umask(0o077) makes the directory and the
|
|
216
|
-
# JSON file owner-only (mode 0600 / 0700). No-op on single-user
|
|
217
|
-
# dev boxes; defence in depth otherwise.
|
|
218
|
-
prev_umask = os.umask(0o077)
|
|
219
|
-
try:
|
|
220
|
-
cite_dir = Path("/tmp/arkaos-cite")
|
|
221
|
-
cite_dir.mkdir(parents=True, exist_ok=True)
|
|
222
|
-
cite_path = cite_dir / f"{safe_sid}.json"
|
|
223
|
-
cite_payload = {
|
|
224
|
-
"passed": cr.passed,
|
|
225
|
-
"reason": cr.reason,
|
|
226
|
-
"suggestion": cr.suggestion,
|
|
227
|
-
"citation_count": cr.citation_count,
|
|
228
|
-
"topic_score": cr.topic_score,
|
|
229
|
-
}
|
|
230
|
-
cite_path.write_text(json.dumps(cite_payload), encoding="utf-8")
|
|
231
|
-
finally:
|
|
232
|
-
os.umask(prev_umask)
|
|
233
|
-
except Exception:
|
|
234
|
-
pass
|
|
235
|
-
|
|
236
|
-
# PR44 v2.63.0 — Mandatory post-task skill evaluation
|
|
237
|
-
# (NON-NEGOTIABLE constitution rule mandatory-skill-evaluation).
|
|
238
|
-
# Classifier decides whether closing message represents a repeatable
|
|
239
|
-
# capability worth capturing as a permanent skill. Proposals written
|
|
240
|
-
# to ~/.arkaos/skill-proposals/<date>-<slug>.md. Never raises.
|
|
241
|
-
try:
|
|
242
|
-
from core.governance.skill_proposer import evaluate as _eval_skill
|
|
243
|
-
_eval_skill(last)
|
|
244
|
-
except Exception:
|
|
245
|
-
pass
|
|
246
|
-
|
|
247
|
-
# PR30 v2.49.0 — Meta-tag soft block. Mirrors the KB cite-check
|
|
248
|
-
# pipeline. Records whether the closing message carried the required
|
|
249
|
-
# [arka:meta] one-liner; persists result to /tmp/arkaos-meta/<session>.json
|
|
250
|
-
# so the next UserPromptSubmit can surface a nudge if missing.
|
|
251
|
-
meta_passed = True
|
|
252
|
-
meta_reason = "trivial"
|
|
253
|
-
meta_suggestion: str | None = None
|
|
254
|
-
try:
|
|
255
|
-
from core.governance.meta_tag_check import check_meta_tag
|
|
256
|
-
mr = check_meta_tag(last)
|
|
257
|
-
meta_passed = mr.passed
|
|
258
|
-
meta_reason = mr.reason
|
|
259
|
-
meta_suggestion = mr.suggestion
|
|
260
|
-
if safe_sid:
|
|
261
|
-
prev_umask = os.umask(0o077)
|
|
262
|
-
try:
|
|
263
|
-
meta_dir = Path("/tmp/arkaos-meta")
|
|
264
|
-
meta_dir.mkdir(parents=True, exist_ok=True)
|
|
265
|
-
meta_path = meta_dir / f"{safe_sid}.json"
|
|
266
|
-
meta_path.write_text(
|
|
267
|
-
json.dumps({
|
|
268
|
-
"passed": mr.passed,
|
|
269
|
-
"reason": mr.reason,
|
|
270
|
-
"suggestion": mr.suggestion,
|
|
271
|
-
}),
|
|
272
|
-
encoding="utf-8",
|
|
273
|
-
)
|
|
274
|
-
finally:
|
|
275
|
-
os.umask(prev_umask)
|
|
276
|
-
except Exception:
|
|
277
|
-
pass
|
|
278
|
-
|
|
279
|
-
# PR59 v2.76.0 — Closing-marker soft block. Telemetry analysis showed
|
|
280
|
-
# 0% [arka:phase:13]/[arka:trivial] rate on flow-required turns. Persist
|
|
281
|
-
# result to /tmp/arkaos-closing/<session>.json so the next
|
|
282
|
-
# UserPromptSubmit can surface a nudge if missing.
|
|
283
|
-
closing_check_passed = True
|
|
284
|
-
closing_check_reason = "trivial"
|
|
285
|
-
closing_check_suggestion: str | None = None
|
|
286
|
-
try:
|
|
287
|
-
from core.governance.closing_marker_check import check_closing_marker
|
|
288
|
-
cmr = check_closing_marker(last)
|
|
289
|
-
closing_check_passed = cmr.passed
|
|
290
|
-
closing_check_reason = cmr.reason
|
|
291
|
-
closing_check_suggestion = cmr.suggestion
|
|
292
|
-
if safe_sid:
|
|
293
|
-
prev_umask = os.umask(0o077)
|
|
294
|
-
try:
|
|
295
|
-
closing_dir = Path("/tmp/arkaos-closing")
|
|
296
|
-
closing_dir.mkdir(parents=True, exist_ok=True)
|
|
297
|
-
closing_path = closing_dir / f"{safe_sid}.json"
|
|
298
|
-
closing_path.write_text(
|
|
299
|
-
json.dumps({
|
|
300
|
-
"passed": cmr.passed,
|
|
301
|
-
"reason": cmr.reason,
|
|
302
|
-
"suggestion": cmr.suggestion,
|
|
303
|
-
}),
|
|
304
|
-
encoding="utf-8",
|
|
305
|
-
)
|
|
306
|
-
finally:
|
|
307
|
-
os.umask(prev_umask)
|
|
308
|
-
except Exception:
|
|
309
|
-
pass
|
|
310
|
-
|
|
311
|
-
entry = {
|
|
312
|
-
"ts": datetime.now(timezone.utc).isoformat(),
|
|
313
|
-
"session_id": session_id,
|
|
314
|
-
"cwd": cwd,
|
|
315
|
-
"event": "stop-hook-flow-check",
|
|
316
|
-
"closing_marker_found": closing_ok,
|
|
317
|
-
"phase13": phase13,
|
|
318
|
-
"trivial": trivial,
|
|
319
|
-
"meta_tag_found": meta_tag_found,
|
|
320
|
-
"sycophancy_is_flagged": is_sycophantic,
|
|
321
|
-
"sycophancy_signals": sycophancy_signals,
|
|
322
|
-
"sycophancy_confidence": sycophancy_confidence,
|
|
323
|
-
"kb_cite_passed": cite_passed,
|
|
324
|
-
"kb_cite_reason": cite_reason,
|
|
325
|
-
"kb_cite_count": cite_count,
|
|
326
|
-
"kb_cite_topic_score": cite_topic_score,
|
|
327
|
-
"meta_tag_check_passed": meta_passed,
|
|
328
|
-
"meta_tag_check_reason": meta_reason,
|
|
329
|
-
# PR59 v2.76.0 — Closing-marker soft-block telemetry.
|
|
330
|
-
"closing_marker_check_passed": closing_check_passed,
|
|
331
|
-
"closing_marker_check_reason": closing_check_reason,
|
|
332
|
-
# PR46 v2.65.0 — Claude Code effort level captured for later analysis
|
|
333
|
-
# of nudge-suppression rates. Unset / unknown values land as "".
|
|
334
|
-
"effort_level": os.environ.get("EFFORT_LEVEL_VAL", ""),
|
|
335
|
-
"mode": "warn",
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
try:
|
|
339
|
-
TELEMETRY_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
340
|
-
with TELEMETRY_PATH.open("a", encoding="utf-8") as fh:
|
|
341
|
-
fh.write(json.dumps(entry) + "\n")
|
|
342
|
-
except Exception:
|
|
343
|
-
pass
|
|
344
|
-
|
|
345
|
-
# Clean up the session marker once Stop has evaluated.
|
|
346
|
-
try:
|
|
347
|
-
clear_flow_required(session_id)
|
|
348
|
-
except Exception:
|
|
349
|
-
pass
|
|
350
|
-
PY
|
|
351
|
-
|
|
352
|
-
# ─── Auto-documentor enqueue (fire-and-forget) ──────────────────────────
|
|
353
|
-
# Queues a background job when:
|
|
354
|
-
# (a) the session was flagged flow-required (classifier match above),
|
|
355
|
-
# (b) Quality Gate approved — last assistant message carries
|
|
356
|
-
# `[arka:qg:approved]`, OR the most recent entry for this session
|
|
357
|
-
# in ~/.arkaos/telemetry/qg.jsonl has verdict APPROVED, AND
|
|
358
|
-
# (c) at least one external research tool was invoked this session
|
|
359
|
-
# (URL, WebFetch/WebSearch, Context7, Firecrawl in the transcript).
|
|
360
|
-
# The actual documentation runs async via `core/jobs/auto_doc_worker.py`;
|
|
361
|
-
# this block never blocks Stop — 2s Python budget, errors swallowed.
|
|
362
|
-
if command -v timeout &>/dev/null; then
|
|
363
|
-
_ARKA_AUTO_DOC_RUNNER=(timeout 2s python3 -)
|
|
364
|
-
elif command -v gtimeout &>/dev/null; then
|
|
365
|
-
_ARKA_AUTO_DOC_RUNNER=(gtimeout 2s python3 -)
|
|
366
|
-
else
|
|
367
|
-
_ARKA_AUTO_DOC_RUNNER=(python3 -)
|
|
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
|
|
64
|
+
done
|
|
65
|
+
fi
|
|
368
66
|
fi
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
TRANSCRIPT_PATH_VAL="$TRANSCRIPT_PATH" \
|
|
372
|
-
CWD_VAL="$CWD" \
|
|
373
|
-
ARKAOS_ROOT_VAL="$ARKAOS_ROOT" \
|
|
374
|
-
"${_ARKA_AUTO_DOC_RUNNER[@]}" <<'PY' 2>/dev/null || true
|
|
375
|
-
import json
|
|
376
|
-
import os
|
|
377
|
-
import re
|
|
378
|
-
import sys
|
|
379
|
-
from pathlib import Path
|
|
380
|
-
|
|
381
|
-
sys.path.insert(0, os.environ["ARKAOS_ROOT_VAL"])
|
|
382
|
-
try:
|
|
383
|
-
from core.jobs.auto_doc_worker import enqueue_job
|
|
384
|
-
from core.workflow.flow_enforcer import _load_last_assistant_messages
|
|
385
|
-
except Exception:
|
|
386
|
-
sys.exit(0)
|
|
387
|
-
|
|
388
|
-
session_id = os.environ.get("SESSION_ID_VAL", "")
|
|
389
|
-
transcript_path = os.environ.get("TRANSCRIPT_PATH_VAL", "")
|
|
390
|
-
if not session_id or not transcript_path:
|
|
391
|
-
sys.exit(0)
|
|
392
|
-
|
|
393
|
-
last = ""
|
|
394
|
-
try:
|
|
395
|
-
msgs = _load_last_assistant_messages(transcript_path, n=1)
|
|
396
|
-
last = msgs[-1] if msgs else ""
|
|
397
|
-
except Exception:
|
|
398
|
-
last = ""
|
|
399
|
-
|
|
400
|
-
qg_approved = bool(re.search(r"\[arka:qg:approved\]", last, re.IGNORECASE))
|
|
401
|
-
if not qg_approved:
|
|
402
|
-
qg_log = Path.home() / ".arkaos" / "telemetry" / "qg.jsonl"
|
|
403
|
-
if qg_log.exists():
|
|
404
|
-
try:
|
|
405
|
-
for line in reversed(qg_log.read_text(encoding="utf-8").splitlines()):
|
|
406
|
-
if not line.strip():
|
|
407
|
-
continue
|
|
408
|
-
try:
|
|
409
|
-
rec = json.loads(line)
|
|
410
|
-
except Exception:
|
|
411
|
-
continue
|
|
412
|
-
if rec.get("session_id") == session_id:
|
|
413
|
-
qg_approved = rec.get("verdict", "").upper() == "APPROVED"
|
|
414
|
-
break
|
|
415
|
-
except Exception:
|
|
416
|
-
pass
|
|
417
|
-
if not qg_approved:
|
|
418
|
-
sys.exit(0)
|
|
419
|
-
|
|
420
|
-
# Heuristic check that some external research happened this session.
|
|
421
|
-
external_markers = (
|
|
422
|
-
"WebFetch", "WebSearch", "mcp__context7", "mcp__firecrawl",
|
|
423
|
-
"http://", "https://",
|
|
424
|
-
)
|
|
425
|
-
has_external = False
|
|
426
|
-
try:
|
|
427
|
-
data = Path(transcript_path).read_text(encoding="utf-8", errors="replace")
|
|
428
|
-
has_external = any(marker in data for marker in external_markers)
|
|
429
|
-
except Exception:
|
|
430
|
-
has_external = False
|
|
431
|
-
if not has_external:
|
|
432
|
-
sys.exit(0)
|
|
433
|
-
|
|
434
|
-
try:
|
|
435
|
-
enqueue_job(session_id, transcript_path, "APPROVED")
|
|
436
|
-
except Exception:
|
|
437
|
-
pass
|
|
438
|
-
PY
|
|
439
|
-
|
|
440
|
-
# Belt-and-braces: remove the marker at shell level in case the Python
|
|
441
|
-
# block above crashed before reaching clear_flow_required(). Session_id
|
|
442
|
-
# is already validated by the Python helper; this shell remove is scoped
|
|
443
|
-
# to the exact marker path and is idempotent.
|
|
444
|
-
case "$SESSION_ID" in
|
|
445
|
-
*[!A-Za-z0-9._-]*|"") ;; # reject unsafe/empty session ids
|
|
446
|
-
*) rm -f "$WF_MARKER" 2>/dev/null ;;
|
|
447
|
-
esac
|
|
448
|
-
|
|
67
|
+
PYTHONPATH="$ARKAOS_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
|
|
68
|
+
"$_PY" -m core.hooks.stop
|
|
449
69
|
exit 0
|