arkaos 4.0.1 → 4.1.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.
Files changed (200) hide show
  1. package/README.md +2 -2
  2. package/VERSION +1 -1
  3. package/arka/SKILL.md +16 -22
  4. package/arka/skills/flow/SKILL.md +108 -234
  5. package/config/claude-agents/eduardo-copy.md +53 -0
  6. package/config/claude-agents/francisca-tech.md +52 -0
  7. package/config/claude-agents/marta-cqo.md +52 -0
  8. package/config/claude-agents/paulo-tech-lead.md +50 -0
  9. package/config/cognition/schedules.yaml +15 -0
  10. package/config/constitution.yaml +17 -21
  11. package/config/hooks/_lib/workflow-classifier.sh +1 -1
  12. package/config/hooks/post-tool-use.sh +40 -539
  13. package/config/hooks/pre-tool-use.sh +32 -281
  14. package/config/hooks/session-start.sh +8 -10
  15. package/config/hooks/stop.sh +30 -425
  16. package/config/hooks/user-prompt-submit.sh +42 -459
  17. package/core/__pycache__/__init__.cpython-312.pyc +0 -0
  18. package/core/cognition/__pycache__/__init__.cpython-312.pyc +0 -0
  19. package/core/cognition/__pycache__/dreaming.cpython-313.pyc +0 -0
  20. package/core/cognition/__pycache__/retrieval.cpython-312.pyc +0 -0
  21. package/core/cognition/capture/__pycache__/__init__.cpython-312.pyc +0 -0
  22. package/core/cognition/capture/__pycache__/store.cpython-312.pyc +0 -0
  23. package/core/cognition/dreaming.py +5 -0
  24. package/core/cognition/insights/__pycache__/__init__.cpython-312.pyc +0 -0
  25. package/core/cognition/insights/__pycache__/store.cpython-312.pyc +0 -0
  26. package/core/cognition/memory/__pycache__/__init__.cpython-312.pyc +0 -0
  27. package/core/cognition/memory/__pycache__/obsidian.cpython-312.pyc +0 -0
  28. package/core/cognition/memory/__pycache__/schemas.cpython-312.pyc +0 -0
  29. package/core/cognition/memory/__pycache__/vector.cpython-312.pyc +0 -0
  30. package/core/cognition/memory/__pycache__/writer.cpython-312.pyc +0 -0
  31. package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
  32. package/core/cognition/scheduler/daemon.py +10 -0
  33. package/core/forge/__init__.py +2 -0
  34. package/core/forge/__pycache__/__init__.cpython-313.pyc +0 -0
  35. package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
  36. package/core/forge/__pycache__/persistence.cpython-313.pyc +0 -0
  37. package/core/forge/__pycache__/renderer.cpython-313.pyc +0 -0
  38. package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
  39. package/core/forge/__pycache__/schema.cpython-313.pyc +0 -0
  40. package/core/forge/orchestrator.py +50 -27
  41. package/core/forge/persistence.py +16 -0
  42. package/core/forge/renderer.py +16 -0
  43. package/core/forge/runtime_dispatcher.py +34 -19
  44. package/core/forge/schema.py +12 -0
  45. package/core/governance/__pycache__/__init__.cpython-312.pyc +0 -0
  46. package/core/governance/__pycache__/closing_marker_check.cpython-313.pyc +0 -0
  47. package/core/governance/__pycache__/compliance_telemetry.cpython-313.pyc +0 -0
  48. package/core/governance/__pycache__/constitution.cpython-312.pyc +0 -0
  49. package/core/governance/__pycache__/evidence_checks.cpython-313.pyc +0 -0
  50. package/core/governance/__pycache__/meta_tag_check.cpython-313.pyc +0 -0
  51. package/core/governance/__pycache__/qg_verdict.cpython-313.pyc +0 -0
  52. package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
  53. package/core/governance/__pycache__/skill_proposer.cpython-313.pyc +0 -0
  54. package/core/governance/closing_marker_check.py +9 -5
  55. package/core/governance/compliance_telemetry.py +1 -1
  56. package/core/governance/compliance_telemetry_cli.py +1 -1
  57. package/core/governance/evidence_checks.py +421 -0
  58. package/core/governance/meta_tag_check.py +36 -0
  59. package/core/governance/qg_verdict.py +78 -0
  60. package/core/governance/review_workflow.py +34 -1
  61. package/core/governance/skill_proposer.py +2 -1
  62. package/core/hooks/__init__.py +12 -0
  63. package/core/hooks/__pycache__/__init__.cpython-312.pyc +0 -0
  64. package/core/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  65. package/core/hooks/__pycache__/_shared.cpython-312.pyc +0 -0
  66. package/core/hooks/__pycache__/_shared.cpython-313.pyc +0 -0
  67. package/core/hooks/__pycache__/post_tool_use.cpython-312.pyc +0 -0
  68. package/core/hooks/__pycache__/post_tool_use.cpython-313.pyc +0 -0
  69. package/core/hooks/__pycache__/pre_tool_use.cpython-312.pyc +0 -0
  70. package/core/hooks/__pycache__/pre_tool_use.cpython-313.pyc +0 -0
  71. package/core/hooks/__pycache__/stop.cpython-312.pyc +0 -0
  72. package/core/hooks/__pycache__/stop.cpython-313.pyc +0 -0
  73. package/core/hooks/__pycache__/user_prompt_submit.cpython-312.pyc +0 -0
  74. package/core/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  75. package/core/hooks/_shared.py +110 -0
  76. package/core/hooks/post_tool_use.py +689 -0
  77. package/core/hooks/pre_tool_use.py +267 -0
  78. package/core/hooks/stop.py +395 -0
  79. package/core/hooks/user_prompt_submit.py +600 -0
  80. package/core/jobs/__pycache__/__init__.cpython-312.pyc +0 -0
  81. package/core/jobs/__pycache__/auto_doc_worker.cpython-312.pyc +0 -0
  82. package/core/jobs/__pycache__/manager.cpython-312.pyc +0 -0
  83. package/core/knowledge/__pycache__/__init__.cpython-312.pyc +0 -0
  84. package/core/knowledge/__pycache__/chunker.cpython-312.pyc +0 -0
  85. package/core/knowledge/__pycache__/embedder.cpython-312.pyc +0 -0
  86. package/core/knowledge/__pycache__/embedder.cpython-313.pyc +0 -0
  87. package/core/knowledge/__pycache__/vector_store.cpython-312.pyc +0 -0
  88. package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
  89. package/core/knowledge/embedder.py +118 -11
  90. package/core/knowledge/vector_store.py +111 -14
  91. package/core/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  92. package/core/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
  93. package/core/runtime/__pycache__/base.cpython-312.pyc +0 -0
  94. package/core/runtime/__pycache__/base.cpython-313.pyc +0 -0
  95. package/core/runtime/__pycache__/capabilities_cli.cpython-312.pyc +0 -0
  96. package/core/runtime/__pycache__/capabilities_cli.cpython-313.pyc +0 -0
  97. package/core/runtime/__pycache__/claude_code.cpython-312.pyc +0 -0
  98. package/core/runtime/__pycache__/claude_code.cpython-313.pyc +0 -0
  99. package/core/runtime/__pycache__/codex_cli.cpython-312.pyc +0 -0
  100. package/core/runtime/__pycache__/codex_cli.cpython-313.pyc +0 -0
  101. package/core/runtime/__pycache__/cost_governor.cpython-312.pyc +0 -0
  102. package/core/runtime/__pycache__/cost_governor.cpython-313.pyc +0 -0
  103. package/core/runtime/__pycache__/cursor.cpython-312.pyc +0 -0
  104. package/core/runtime/__pycache__/cursor.cpython-313.pyc +0 -0
  105. package/core/runtime/__pycache__/gemini_cli.cpython-312.pyc +0 -0
  106. package/core/runtime/__pycache__/gemini_cli.cpython-313.pyc +0 -0
  107. package/core/runtime/__pycache__/llm_cost_telemetry.cpython-312.pyc +0 -0
  108. package/core/runtime/__pycache__/llm_provider.cpython-312.pyc +0 -0
  109. package/core/runtime/__pycache__/llm_provider.cpython-313.pyc +0 -0
  110. package/core/runtime/__pycache__/llm_retry.cpython-312.pyc +0 -0
  111. package/core/runtime/__pycache__/llm_retry.cpython-313.pyc +0 -0
  112. package/core/runtime/__pycache__/native_usage.cpython-312.pyc +0 -0
  113. package/core/runtime/__pycache__/native_usage.cpython-313.pyc +0 -0
  114. package/core/runtime/__pycache__/ollama_provider.cpython-312.pyc +0 -0
  115. package/core/runtime/__pycache__/path_resolver.cpython-312.pyc +0 -0
  116. package/core/runtime/__pycache__/pricing.cpython-312.pyc +0 -0
  117. package/core/runtime/__pycache__/pricing.cpython-313.pyc +0 -0
  118. package/core/runtime/__pycache__/registry.cpython-312.pyc +0 -0
  119. package/core/runtime/__pycache__/registry.cpython-313.pyc +0 -0
  120. package/core/runtime/__pycache__/squad_orchestrator.cpython-312.pyc +0 -0
  121. package/core/runtime/__pycache__/user_paths.cpython-312.pyc +0 -0
  122. package/core/runtime/base.py +29 -0
  123. package/core/runtime/capabilities_cli.py +74 -0
  124. package/core/runtime/claude_code.py +8 -0
  125. package/core/runtime/codex_cli.py +205 -37
  126. package/core/runtime/cost_governor.py +184 -0
  127. package/core/runtime/cursor.py +8 -0
  128. package/core/runtime/gemini_cli.py +8 -0
  129. package/core/runtime/llm_provider.py +32 -13
  130. package/core/runtime/llm_retry.py +233 -0
  131. package/core/runtime/native_usage.py +186 -0
  132. package/core/runtime/pricing.py +9 -0
  133. package/core/runtime/registry.py +3 -0
  134. package/core/shared/__pycache__/__init__.cpython-312.pyc +0 -0
  135. package/core/shared/__pycache__/safe_session_id.cpython-312.pyc +0 -0
  136. package/core/shared/__pycache__/test_evidence.cpython-313-pytest-9.1.1.pyc +0 -0
  137. package/core/shared/__pycache__/test_evidence.cpython-313.pyc +0 -0
  138. package/core/shared/test_evidence.py +34 -0
  139. package/core/synapse/__pycache__/__init__.cpython-312.pyc +0 -0
  140. package/core/synapse/__pycache__/agent_experiences_layer.cpython-312.pyc +0 -0
  141. package/core/synapse/__pycache__/cache.cpython-312.pyc +0 -0
  142. package/core/synapse/__pycache__/engine.cpython-312.pyc +0 -0
  143. package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
  144. package/core/synapse/__pycache__/graph_context_layer.cpython-313.pyc +0 -0
  145. package/core/synapse/__pycache__/kb_cache.cpython-312.pyc +0 -0
  146. package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
  147. package/core/synapse/__pycache__/layers.cpython-312.pyc +0 -0
  148. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  149. package/core/synapse/engine.py +7 -0
  150. package/core/synapse/graph_context_layer.py +212 -0
  151. package/core/synapse/kb_cache.py +3 -0
  152. package/core/synapse/layers.py +104 -20
  153. package/core/workflow/__pycache__/__init__.cpython-312.pyc +0 -0
  154. package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
  155. package/core/workflow/__pycache__/engine.cpython-312.pyc +0 -0
  156. package/core/workflow/__pycache__/flow_enforcer.cpython-312.pyc +0 -0
  157. package/core/workflow/__pycache__/flow_enforcer.cpython-313.pyc +0 -0
  158. package/core/workflow/__pycache__/gate_checkpoint.cpython-312.pyc +0 -0
  159. package/core/workflow/__pycache__/gate_checkpoint.cpython-313.pyc +0 -0
  160. package/core/workflow/__pycache__/loader.cpython-312.pyc +0 -0
  161. package/core/workflow/__pycache__/research_gate.cpython-313.pyc +0 -0
  162. package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
  163. package/core/workflow/__pycache__/schema.cpython-312.pyc +0 -0
  164. package/core/workflow/__pycache__/specialist_enforcer.cpython-312.pyc +0 -0
  165. package/core/workflow/__pycache__/specialist_enforcer.cpython-313.pyc +0 -0
  166. package/core/workflow/enforcer.py +5 -16
  167. package/core/workflow/flow_enforcer.py +36 -11
  168. package/core/workflow/gate_checkpoint.py +149 -0
  169. package/core/workflow/research_gate.py +15 -3
  170. package/core/workflow/rules_registry.py +137 -389
  171. package/core/workflow/specialist_enforcer.py +21 -4
  172. package/dashboard/app/pages/knowledge/index.vue +4 -1
  173. package/departments/brand/agents/ux-designer.yaml +1 -0
  174. package/departments/brand/agents/visual-designer.yaml +2 -0
  175. package/departments/dev/agents/frontend-dev.yaml +2 -0
  176. package/departments/ops/skills/n8n-flow/SKILL.md +13 -0
  177. package/departments/ops/skills/workflow-automate/SKILL.md +16 -0
  178. package/departments/quality/SKILL.md +45 -5
  179. package/departments/quality/agents/copy-director.yaml +3 -1
  180. package/departments/quality/agents/tech-director.yaml +3 -1
  181. package/installer/adapters/claude-code.js +46 -2
  182. package/installer/config-seed.js +39 -14
  183. package/installer/doctor.js +8 -0
  184. package/installer/graphify.js +153 -0
  185. package/installer/index.js +24 -0
  186. package/installer/init.js +11 -0
  187. package/installer/update.js +23 -0
  188. package/package.json +1 -1
  189. package/pyproject.toml +1 -1
  190. package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
  191. package/scripts/__pycache__/knowledge-index.cpython-313.pyc +0 -0
  192. package/scripts/__pycache__/synapse-bridge.cpython-312.pyc +0 -0
  193. package/scripts/__pycache__/synapse-bridge.cpython-313.pyc +0 -0
  194. package/scripts/bench/__pycache__/__init__.cpython-313.pyc +0 -0
  195. package/scripts/bench/__pycache__/harness.cpython-313.pyc +0 -0
  196. package/scripts/bench/__pycache__/run.cpython-313.pyc +0 -0
  197. package/scripts/knowledge-index.py +8 -2
  198. package/scripts/synapse-bridge.py +102 -26
  199. package/scripts/tools/__pycache__/sync_wiki.cpython-313.pyc +0 -0
  200. package/scripts/tools/sync_wiki.py +163 -0
@@ -1,113 +1,27 @@
1
1
  #!/usr/bin/env bash
2
2
  # ============================================================================
3
- # ArkaOS v2 — Stop Hook (Flow Completion Validator, WARN mode v1)
3
+ # ArkaOS v2 — Stop Hook (thin wrapper, PR-6 v4.1.0 hook hygiene)
4
4
  #
5
- # When the classifier marked the session as flow-required, this hook checks
6
- # whether the final assistant message contains [arka:phase:13] or
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
- # Promotion to strict mode is planned for v2.21.0 after 2 weeks of clean
11
- # telemetry. Until then, this hook is observation only.
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,24 @@ 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
- if [ ! -f "$ARKAOS_ROOT/core/workflow/flow_enforcer.py" ]; then
125
- exit 0
126
- fi
127
-
128
- # Reuse the last-messages reader to check for a closing phase marker.
129
- SESSION_ID_VAL="$SESSION_ID" \
130
- TRANSCRIPT_PATH_VAL="$TRANSCRIPT_PATH" \
131
- CWD_VAL="$CWD" \
132
- ARKAOS_ROOT_VAL="$ARKAOS_ROOT" \
133
- EFFORT_LEVEL_VAL="$EFFORT_LEVEL" \
134
- python3 - <<'PY' 2>/dev/null
135
- import json
136
- import os
137
- import re
138
- import sys
139
- from datetime import datetime, timezone
140
- from pathlib import Path
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 -)
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
368
50
  fi
369
51
 
370
- SESSION_ID_VAL="$SESSION_ID" \
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
-
52
+ PYTHONPATH="$ARKAOS_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
53
+ python3 -m core.hooks.stop
449
54
  exit 0