arkaos 4.0.2 → 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 (167) 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/cognition/__pycache__/dreaming.cpython-313.pyc +0 -0
  18. package/core/cognition/dreaming.py +5 -0
  19. package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
  20. package/core/cognition/scheduler/daemon.py +10 -0
  21. package/core/forge/__init__.py +2 -0
  22. package/core/forge/__pycache__/__init__.cpython-313.pyc +0 -0
  23. package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
  24. package/core/forge/__pycache__/persistence.cpython-313.pyc +0 -0
  25. package/core/forge/__pycache__/renderer.cpython-313.pyc +0 -0
  26. package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
  27. package/core/forge/__pycache__/schema.cpython-313.pyc +0 -0
  28. package/core/forge/orchestrator.py +50 -27
  29. package/core/forge/persistence.py +16 -0
  30. package/core/forge/renderer.py +16 -0
  31. package/core/forge/runtime_dispatcher.py +34 -19
  32. package/core/forge/schema.py +12 -0
  33. package/core/governance/__pycache__/__init__.cpython-312.pyc +0 -0
  34. package/core/governance/__pycache__/closing_marker_check.cpython-313.pyc +0 -0
  35. package/core/governance/__pycache__/compliance_telemetry.cpython-313.pyc +0 -0
  36. package/core/governance/__pycache__/constitution.cpython-312.pyc +0 -0
  37. package/core/governance/__pycache__/evidence_checks.cpython-313.pyc +0 -0
  38. package/core/governance/__pycache__/meta_tag_check.cpython-313.pyc +0 -0
  39. package/core/governance/__pycache__/qg_verdict.cpython-313.pyc +0 -0
  40. package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
  41. package/core/governance/__pycache__/skill_proposer.cpython-313.pyc +0 -0
  42. package/core/governance/closing_marker_check.py +9 -5
  43. package/core/governance/compliance_telemetry.py +1 -1
  44. package/core/governance/compliance_telemetry_cli.py +1 -1
  45. package/core/governance/evidence_checks.py +421 -0
  46. package/core/governance/meta_tag_check.py +36 -0
  47. package/core/governance/qg_verdict.py +78 -0
  48. package/core/governance/review_workflow.py +34 -1
  49. package/core/governance/skill_proposer.py +2 -1
  50. package/core/hooks/__init__.py +12 -0
  51. package/core/hooks/__pycache__/__init__.cpython-312.pyc +0 -0
  52. package/core/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  53. package/core/hooks/__pycache__/_shared.cpython-312.pyc +0 -0
  54. package/core/hooks/__pycache__/_shared.cpython-313.pyc +0 -0
  55. package/core/hooks/__pycache__/post_tool_use.cpython-312.pyc +0 -0
  56. package/core/hooks/__pycache__/post_tool_use.cpython-313.pyc +0 -0
  57. package/core/hooks/__pycache__/pre_tool_use.cpython-312.pyc +0 -0
  58. package/core/hooks/__pycache__/pre_tool_use.cpython-313.pyc +0 -0
  59. package/core/hooks/__pycache__/stop.cpython-312.pyc +0 -0
  60. package/core/hooks/__pycache__/stop.cpython-313.pyc +0 -0
  61. package/core/hooks/__pycache__/user_prompt_submit.cpython-312.pyc +0 -0
  62. package/core/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  63. package/core/hooks/_shared.py +110 -0
  64. package/core/hooks/post_tool_use.py +689 -0
  65. package/core/hooks/pre_tool_use.py +267 -0
  66. package/core/hooks/stop.py +395 -0
  67. package/core/hooks/user_prompt_submit.py +600 -0
  68. package/core/jobs/__pycache__/__init__.cpython-312.pyc +0 -0
  69. package/core/jobs/__pycache__/auto_doc_worker.cpython-312.pyc +0 -0
  70. package/core/jobs/__pycache__/manager.cpython-312.pyc +0 -0
  71. package/core/knowledge/__pycache__/embedder.cpython-312.pyc +0 -0
  72. package/core/knowledge/__pycache__/embedder.cpython-313.pyc +0 -0
  73. package/core/knowledge/__pycache__/vector_store.cpython-312.pyc +0 -0
  74. package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
  75. package/core/knowledge/embedder.py +118 -11
  76. package/core/knowledge/vector_store.py +111 -14
  77. package/core/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  78. package/core/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
  79. package/core/runtime/__pycache__/base.cpython-312.pyc +0 -0
  80. package/core/runtime/__pycache__/base.cpython-313.pyc +0 -0
  81. package/core/runtime/__pycache__/capabilities_cli.cpython-312.pyc +0 -0
  82. package/core/runtime/__pycache__/capabilities_cli.cpython-313.pyc +0 -0
  83. package/core/runtime/__pycache__/claude_code.cpython-312.pyc +0 -0
  84. package/core/runtime/__pycache__/claude_code.cpython-313.pyc +0 -0
  85. package/core/runtime/__pycache__/codex_cli.cpython-312.pyc +0 -0
  86. package/core/runtime/__pycache__/codex_cli.cpython-313.pyc +0 -0
  87. package/core/runtime/__pycache__/cost_governor.cpython-312.pyc +0 -0
  88. package/core/runtime/__pycache__/cost_governor.cpython-313.pyc +0 -0
  89. package/core/runtime/__pycache__/cursor.cpython-312.pyc +0 -0
  90. package/core/runtime/__pycache__/cursor.cpython-313.pyc +0 -0
  91. package/core/runtime/__pycache__/gemini_cli.cpython-312.pyc +0 -0
  92. package/core/runtime/__pycache__/gemini_cli.cpython-313.pyc +0 -0
  93. package/core/runtime/__pycache__/llm_provider.cpython-312.pyc +0 -0
  94. package/core/runtime/__pycache__/llm_provider.cpython-313.pyc +0 -0
  95. package/core/runtime/__pycache__/llm_retry.cpython-312.pyc +0 -0
  96. package/core/runtime/__pycache__/llm_retry.cpython-313.pyc +0 -0
  97. package/core/runtime/__pycache__/native_usage.cpython-312.pyc +0 -0
  98. package/core/runtime/__pycache__/native_usage.cpython-313.pyc +0 -0
  99. package/core/runtime/__pycache__/path_resolver.cpython-312.pyc +0 -0
  100. package/core/runtime/__pycache__/pricing.cpython-312.pyc +0 -0
  101. package/core/runtime/__pycache__/pricing.cpython-313.pyc +0 -0
  102. package/core/runtime/__pycache__/registry.cpython-312.pyc +0 -0
  103. package/core/runtime/__pycache__/registry.cpython-313.pyc +0 -0
  104. package/core/runtime/__pycache__/squad_orchestrator.cpython-312.pyc +0 -0
  105. package/core/runtime/base.py +29 -0
  106. package/core/runtime/capabilities_cli.py +74 -0
  107. package/core/runtime/claude_code.py +8 -0
  108. package/core/runtime/codex_cli.py +205 -37
  109. package/core/runtime/cost_governor.py +184 -0
  110. package/core/runtime/cursor.py +8 -0
  111. package/core/runtime/gemini_cli.py +8 -0
  112. package/core/runtime/llm_provider.py +32 -13
  113. package/core/runtime/llm_retry.py +233 -0
  114. package/core/runtime/native_usage.py +186 -0
  115. package/core/runtime/pricing.py +9 -0
  116. package/core/runtime/registry.py +3 -0
  117. package/core/shared/__pycache__/test_evidence.cpython-313-pytest-9.1.1.pyc +0 -0
  118. package/core/shared/__pycache__/test_evidence.cpython-313.pyc +0 -0
  119. package/core/shared/test_evidence.py +34 -0
  120. package/core/synapse/__pycache__/agent_experiences_layer.cpython-312.pyc +0 -0
  121. package/core/synapse/__pycache__/engine.cpython-312.pyc +0 -0
  122. package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
  123. package/core/synapse/__pycache__/graph_context_layer.cpython-313.pyc +0 -0
  124. package/core/synapse/__pycache__/kb_cache.cpython-312.pyc +0 -0
  125. package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
  126. package/core/synapse/__pycache__/layers.cpython-312.pyc +0 -0
  127. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  128. package/core/synapse/engine.py +7 -0
  129. package/core/synapse/graph_context_layer.py +212 -0
  130. package/core/synapse/kb_cache.py +3 -0
  131. package/core/synapse/layers.py +104 -20
  132. package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
  133. package/core/workflow/__pycache__/flow_enforcer.cpython-312.pyc +0 -0
  134. package/core/workflow/__pycache__/flow_enforcer.cpython-313.pyc +0 -0
  135. package/core/workflow/__pycache__/gate_checkpoint.cpython-312.pyc +0 -0
  136. package/core/workflow/__pycache__/gate_checkpoint.cpython-313.pyc +0 -0
  137. package/core/workflow/__pycache__/research_gate.cpython-313.pyc +0 -0
  138. package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
  139. package/core/workflow/__pycache__/specialist_enforcer.cpython-312.pyc +0 -0
  140. package/core/workflow/__pycache__/specialist_enforcer.cpython-313.pyc +0 -0
  141. package/core/workflow/enforcer.py +5 -16
  142. package/core/workflow/flow_enforcer.py +36 -11
  143. package/core/workflow/gate_checkpoint.py +149 -0
  144. package/core/workflow/research_gate.py +15 -3
  145. package/core/workflow/rules_registry.py +137 -389
  146. package/core/workflow/specialist_enforcer.py +21 -4
  147. package/dashboard/app/pages/knowledge/index.vue +4 -1
  148. package/departments/ops/skills/n8n-flow/SKILL.md +13 -0
  149. package/departments/ops/skills/workflow-automate/SKILL.md +16 -0
  150. package/departments/quality/SKILL.md +45 -5
  151. package/departments/quality/agents/copy-director.yaml +3 -1
  152. package/departments/quality/agents/tech-director.yaml +3 -1
  153. package/installer/adapters/claude-code.js +46 -2
  154. package/installer/config-seed.js +39 -14
  155. package/installer/doctor.js +8 -0
  156. package/installer/graphify.js +153 -0
  157. package/installer/index.js +20 -0
  158. package/installer/init.js +11 -0
  159. package/installer/update.js +19 -0
  160. package/package.json +1 -1
  161. package/pyproject.toml +1 -1
  162. package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
  163. package/scripts/__pycache__/knowledge-index.cpython-313.pyc +0 -0
  164. package/scripts/__pycache__/synapse-bridge.cpython-312.pyc +0 -0
  165. package/scripts/__pycache__/synapse-bridge.cpython-313.pyc +0 -0
  166. package/scripts/knowledge-index.py +8 -2
  167. package/scripts/synapse-bridge.py +102 -26
@@ -1,37 +1,28 @@
1
1
  #!/usr/bin/env bash
2
2
  # ============================================================================
3
- # ArkaOS v2 — PreToolUse Hook (Flow Enforcement Gate)
3
+ # ArkaOS v2 — PreToolUse Hook (thin wrapper, PR-6 v4.1.0 hook hygiene)
4
4
  #
5
- # Blocks Write/Edit/MultiEdit when the mandatory 13-phase flow is required
6
- # for the session AND the assistant has not emitted a flow marker
7
- # (`[arka:routing]`, `[arka:trivial]`, or `[arka:phase:`) in its last
8
- # 3 messages of the transcript.
5
+ # Delegates the ENTIRE event to ONE python process:
6
+ # python3 -m core.hooks.pre_tool_use
9
7
  #
10
- # Delegates the decision to core/workflow/flow_enforcer.py (single source
11
- # of truth, pytest-covered). This shell script is a thin wrapper — anti
12
- # pattern `duplicated-security-logic` compliance.
8
+ # The entrypoint preserves the full gate chain and behavior contract of the
9
+ # previous 5-8-process version:
10
+ # 1. KB-first gate — core/workflow/research_gate.py::evaluate_research_gate
11
+ # (nudge on first external research call, deny on the second)
12
+ # 2. Specialist-dispatch gate — core/workflow/specialist_enforcer.py
13
+ # 3. Fast allow for non-flow-gated tools
14
+ # 4. CostGovernor budget check (stdlib-only; runs even when PyYAML is
15
+ # missing from the ambient python3)
16
+ # 5. Evidence-flow gate — core/workflow/flow_enforcer.py
13
17
  #
14
- # Timeout: 10s.
15
- # Allow semantics: no stdout, exit 0.
18
+ # Allow semantics: no stdout, exit 0 (nudges/warnings on stderr).
16
19
  # Deny semantics: hookSpecificOutput.permissionDecision=deny JSON on stdout,
17
- # `[ARKA:ENFORCEMENT] ...` on stderr, exit 2.
20
+ # `[ARKA:ENFORCEMENT]` / `[ARKA:KB-FIRST]` / `[ARKA:SPECIALIST]` on stderr,
21
+ # exit 2. Env bypasses (ARKA_BYPASS_FLOW, ARKA_BYPASS_KB_FIRST) and feature
22
+ # flags are honored inside the python modules. Timeout: 10s.
18
23
  # ============================================================================
19
24
 
20
- input=$(cat)
21
-
22
- # ─── Extract fields (docs: session_id, transcript_path, cwd, tool_name)
23
- TOOL_NAME=""
24
- TRANSCRIPT_PATH=""
25
- SESSION_ID=""
26
- CWD=""
27
- if command -v jq &>/dev/null; then
28
- TOOL_NAME=$(echo "$input" | jq -r '.tool_name // ""' 2>/dev/null)
29
- TRANSCRIPT_PATH=$(echo "$input" | jq -r '.transcript_path // ""' 2>/dev/null)
30
- SESSION_ID=$(echo "$input" | jq -r '.session_id // ""' 2>/dev/null)
31
- CWD=$(echo "$input" | jq -r '.cwd // ""' 2>/dev/null)
32
- fi
33
-
34
- # ─── Resolve ARKAOS_ROOT (same rules as user-prompt-submit.sh) ──────────
25
+ # ─── Resolve ARKAOS_ROOT (env → .repo-path → ~/.arkaos → portable) ──────
35
26
  if [ -z "${ARKAOS_ROOT:-}" ]; then
36
27
  if [ -f "$HOME/.arkaos/.repo-path" ]; then
37
28
  ARKAOS_ROOT=$(cat "$HOME/.arkaos/.repo-path")
@@ -41,265 +32,25 @@ if [ -z "${ARKAOS_ROOT:-}" ]; then
41
32
  ARKAOS_ROOT="${ARKA_OS:-$HOME/.claude/skills/arkaos}"
42
33
  fi
43
34
  fi
35
+ export ARKAOS_ROOT
44
36
 
45
- # ─── Degrade gracefully if Python is unavailable ────────────────────────
37
+ # ─── Degrade gracefully (fail open, same as before) ─────────────────────
46
38
  if ! command -v python3 &>/dev/null; then
47
39
  exit 0
48
40
  fi
49
-
50
- # ─── KB-first gate (Task #6, independent from flow_enforcer) ────────────
51
- # Runs BEFORE the flow-marker gate so that external research tools are
52
- # always KB-checked regardless of whether the tool is Write/Edit/MultiEdit.
53
- # Extract the user query (if present) to feed the nudge generator.
54
- QUERY_HINT=""
55
- if command -v jq &>/dev/null; then
56
- QUERY_HINT=$(echo "$input" | jq -r '.tool_input.query // .tool_input.prompt // .tool_input.url // ""' 2>/dev/null | head -c 500)
57
- fi
58
-
59
- if [ -f "$ARKAOS_ROOT/core/workflow/research_gate.py" ]; then
60
- KB_DECISION_JSON=$(TOOL_NAME="$TOOL_NAME" \
61
- SESSION_ID="$SESSION_ID" \
62
- QUERY_HINT="$QUERY_HINT" \
63
- ARKAOS_ROOT="$ARKAOS_ROOT" \
64
- python3 - <<'PY' 2>/dev/null
65
- import json
66
- import os
67
- import sys
68
-
69
- sys.path.insert(0, os.environ["ARKAOS_ROOT"])
70
- try:
71
- from core.workflow.research_gate import evaluate_research_gate, record_telemetry
72
- except Exception:
73
- print(json.dumps({"allow": True, "nudge": False, "reason": "kb-gate-import-failed"}))
74
- sys.exit(0)
75
-
76
- decision = evaluate_research_gate(
77
- tool_name=os.environ.get("TOOL_NAME", ""),
78
- session_id=os.environ.get("SESSION_ID", ""),
79
- query=os.environ.get("QUERY_HINT", ""),
80
- )
81
- try:
82
- record_telemetry(
83
- session_id=os.environ.get("SESSION_ID", ""),
84
- tool=os.environ.get("TOOL_NAME", ""),
85
- decision=decision,
86
- )
87
- except Exception:
88
- pass
89
- print(json.dumps({
90
- "allow": decision.allow,
91
- "nudge": decision.nudge,
92
- "reason": decision.reason,
93
- "stderr_msg": decision.to_stderr_message(),
94
- }))
95
- PY
96
- )
97
-
98
- if [ -n "$KB_DECISION_JSON" ]; then
99
- KB_ALLOW=$(echo "$KB_DECISION_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('allow', True))" 2>/dev/null)
100
- KB_NUDGE=$(echo "$KB_DECISION_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('nudge', False))" 2>/dev/null)
101
- KB_STDERR=$(echo "$KB_DECISION_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('stderr_msg',''))" 2>/dev/null)
102
-
103
- if [ "$KB_ALLOW" != "True" ] && [ "$KB_ALLOW" != "true" ]; then
104
- echo "$KB_STDERR" >&2
105
- STDERR_MSG="$KB_STDERR" python3 - <<'PY'
106
- import json, os
107
- print(json.dumps({"hookSpecificOutput": {
108
- "hookEventName": "PreToolUse",
109
- "permissionDecision": "deny",
110
- "permissionDecisionReason": os.environ.get("STDERR_MSG", ""),
111
- }}))
112
- PY
113
- exit 2
114
- fi
115
-
116
- # Nudge path: emit advisory to stderr, continue to flow-marker gate.
117
- if [ "$KB_NUDGE" = "True" ] || [ "$KB_NUDGE" = "true" ]; then
118
- [ -n "$KB_STDERR" ] && echo "$KB_STDERR" >&2
119
- fi
41
+ # Self-root fallback: the wrapper ships next to its python entrypoint, so
42
+ # when ARKAOS_ROOT points at a pre-PR-6 install without core/hooks/, use
43
+ # the root this script lives in.
44
+ if [ ! -f "$ARKAOS_ROOT/core/hooks/pre_tool_use.py" ]; then
45
+ _SELF_ROOT="$(cd "$(dirname "$0")/../.." 2>/dev/null && pwd)"
46
+ if [ -n "$_SELF_ROOT" ] && [ -f "$_SELF_ROOT/core/hooks/pre_tool_use.py" ]; then
47
+ ARKAOS_ROOT="$_SELF_ROOT"
48
+ export ARKAOS_ROOT
49
+ else
50
+ exit 0
120
51
  fi
121
52
  fi
122
53
 
123
- # ─── Specialist-dispatch gate (between KB-gate and flow-gate) ──────────
124
- # Blocks Tier-1 leads from writing to specialist-owned files without
125
- # dispatching first. Only fires for file-mutation tools. Independent
126
- # feature flag (`hooks.specialistEnforcement`) — fails open if the
127
- # Python module is absent or raises.
128
- if [ -f "$ARKAOS_ROOT/core/workflow/specialist_enforcer.py" ]; then
129
- case "$TOOL_NAME" in
130
- Write|Edit|MultiEdit|NotebookEdit)
131
- TOOL_INPUT_JSON_SP="{}"
132
- if command -v jq &>/dev/null; then
133
- TOOL_INPUT_JSON_SP=$(echo "$input" | jq -c '.tool_input // {}' 2>/dev/null)
134
- fi
135
- SP_DECISION_JSON=$(TOOL_NAME="$TOOL_NAME" \
136
- TRANSCRIPT_PATH="$TRANSCRIPT_PATH" \
137
- SESSION_ID="$SESSION_ID" \
138
- CWD="$CWD" \
139
- TOOL_INPUT_JSON="$TOOL_INPUT_JSON_SP" \
140
- ARKAOS_ROOT="$ARKAOS_ROOT" \
141
- python3 - <<'PY' 2>/dev/null
142
- import json
143
- import os
144
- import sys
145
-
146
- sys.path.insert(0, os.environ["ARKAOS_ROOT"])
147
- try:
148
- from core.workflow.specialist_enforcer import evaluate, record_telemetry
149
- except Exception:
150
- print(json.dumps({"allow": True, "reason": "specialist-import-failed"}))
151
- sys.exit(0)
152
-
153
- try:
154
- tool_input = json.loads(os.environ.get("TOOL_INPUT_JSON", "{}"))
155
- except json.JSONDecodeError:
156
- tool_input = {}
157
-
158
- decision = evaluate(
159
- tool_name=os.environ.get("TOOL_NAME", ""),
160
- transcript_path=os.environ.get("TRANSCRIPT_PATH", ""),
161
- session_id=os.environ.get("SESSION_ID", ""),
162
- cwd=os.environ.get("CWD", ""),
163
- tool_input=tool_input,
164
- )
165
- try:
166
- record_telemetry(
167
- session_id=os.environ.get("SESSION_ID", ""),
168
- tool=os.environ.get("TOOL_NAME", ""),
169
- decision=decision,
170
- cwd=os.environ.get("CWD", ""),
171
- target_file=str(tool_input.get("file_path", "")),
172
- )
173
- except Exception:
174
- pass
175
- print(json.dumps({
176
- "allow": decision.allow,
177
- "reason": decision.reason,
178
- "stderr_msg": decision.to_stderr_message(),
179
- }))
180
- PY
181
- )
182
-
183
- if [ -n "$SP_DECISION_JSON" ]; then
184
- SP_ALLOW=$(echo "$SP_DECISION_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('allow', True))" 2>/dev/null)
185
- if [ "$SP_ALLOW" != "True" ] && [ "$SP_ALLOW" != "true" ]; then
186
- SP_STDERR=$(echo "$SP_DECISION_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('stderr_msg',''))" 2>/dev/null)
187
- echo "$SP_STDERR" >&2
188
- STDERR_MSG="$SP_STDERR" python3 - <<'PY'
189
- import json, os
190
- print(json.dumps({"hookSpecificOutput": {
191
- "hookEventName": "PreToolUse",
192
- "permissionDecision": "deny",
193
- "permissionDecisionReason": os.environ.get("STDERR_MSG", ""),
194
- }}))
195
- PY
196
- exit 2
197
- fi
198
- fi
199
- ;;
200
- esac
201
- fi
202
-
203
- # ─── Fast allow: not a flow-gated tool ──────────────────────────────────
204
- # PR11 v2.33.0 expanded the gated set to include all EFFECT tools.
205
- # Bash is special — handled per-command by the Python enforcer via
206
- # bash_is_effect(). Skip fast-path for Bash so the enforcer can classify.
207
- case "$TOOL_NAME" in
208
- Write|Edit|MultiEdit|NotebookEdit|Task|Skill|Bash) ;;
209
- *) exit 0 ;;
210
- esac
211
-
212
- if [ ! -f "$ARKAOS_ROOT/core/workflow/flow_enforcer.py" ]; then
213
- exit 0
214
- fi
215
-
216
- # ─── Extract tool_input (needed for Bash command classification) ───────
217
- TOOL_INPUT_JSON="{}"
218
- if command -v jq &>/dev/null; then
219
- TOOL_INPUT_JSON=$(echo "$input" | jq -c '.tool_input // {}' 2>/dev/null)
220
- fi
221
-
222
- # ─── Delegate to Python enforcer ────────────────────────────────────────
223
- DECISION_JSON=$(TOOL_NAME="$TOOL_NAME" \
224
- TRANSCRIPT_PATH="$TRANSCRIPT_PATH" \
225
- SESSION_ID="$SESSION_ID" \
226
- CWD="$CWD" \
227
- TOOL_INPUT_JSON="$TOOL_INPUT_JSON" \
228
- ARKAOS_ROOT="$ARKAOS_ROOT" \
229
- python3 - <<'PY' 2>/dev/null
230
- import json
231
- import os
232
- import sys
233
-
234
- sys.path.insert(0, os.environ["ARKAOS_ROOT"])
235
- try:
236
- from core.workflow.flow_enforcer import evaluate, record_telemetry
237
- except Exception:
238
- print(json.dumps({"allow": True, "reason": "enforcer-import-failed"}))
239
- sys.exit(0)
240
-
241
- try:
242
- tool_input = json.loads(os.environ.get("TOOL_INPUT_JSON", "{}"))
243
- except json.JSONDecodeError:
244
- tool_input = {}
245
-
246
- decision = evaluate(
247
- tool_name=os.environ.get("TOOL_NAME", ""),
248
- transcript_path=os.environ.get("TRANSCRIPT_PATH", ""),
249
- session_id=os.environ.get("SESSION_ID", ""),
250
- cwd=os.environ.get("CWD", ""),
251
- tool_input=tool_input,
252
- )
253
- try:
254
- record_telemetry(
255
- session_id=os.environ.get("SESSION_ID", ""),
256
- tool=os.environ.get("TOOL_NAME", ""),
257
- decision=decision,
258
- cwd=os.environ.get("CWD", ""),
259
- )
260
- except Exception:
261
- pass
262
- print(json.dumps({
263
- "allow": decision.allow,
264
- "reason": decision.reason,
265
- "stderr_msg": decision.to_stderr_message(),
266
- }))
267
- PY
268
- )
269
-
270
- if [ -z "$DECISION_JSON" ]; then
271
- exit 0
272
- fi
273
-
274
- ALLOW=$(echo "$DECISION_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('allow', True))" 2>/dev/null)
275
-
276
- if [ "$ALLOW" = "True" ] || [ "$ALLOW" = "true" ]; then
277
- exit 0
278
- fi
279
-
280
- # ─── Deny path: structured hookSpecificOutput + stderr fallback ─────────
281
- REASON=$(echo "$DECISION_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('reason',''))" 2>/dev/null)
282
- STDERR_MSG=$(echo "$DECISION_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('stderr_msg',''))" 2>/dev/null)
283
-
284
- # Emit stderr (visible to the model per Claude Code hook spec) ─────────
285
- echo "$STDERR_MSG" >&2
286
-
287
- # Emit structured deny JSON (preferred path when runtime understands it).
288
- # STDERR_MSG is passed via env var and read inside a single-quoted heredoc
289
- # so no shell interpolation occurs inside the Python source — this closes
290
- # the command-injection surface flagged by Francisca's tech review.
291
- STDERR_MSG="$STDERR_MSG" python3 - <<'PY'
292
- import json
293
- import os
294
-
295
- out = {
296
- "hookSpecificOutput": {
297
- "hookEventName": "PreToolUse",
298
- "permissionDecision": "deny",
299
- "permissionDecisionReason": os.environ.get("STDERR_MSG", ""),
300
- }
301
- }
302
- print(json.dumps(out))
303
- PY
304
-
305
- exit 2
54
+ # ─── Single python process; stdin/stdout/stderr/exit-code pass through ──
55
+ PYTHONPATH="$ARKAOS_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
56
+ exec python3 -m core.hooks.pre_tool_use
@@ -89,16 +89,14 @@ MSG+="ArkaOS v${VERSION} | 65 agents | 17 departments | 244+ skills"
89
89
  [ -n "$_FORGE_LINE" ] && MSG+="\\n${_FORGE_LINE}"
90
90
  MSG+="${DRIFT}"
91
91
 
92
- # ─── Mandatory Flow Contract (top-of-session, highest priority) ─────────
93
- MSG+="\\n\\n[ARKA:MANDATORY-FLOW] NON-NEGOTIABLE. Every non-trivial request runs the 13-phase canonical flow (constitution rule mandatory-flow; source arka/skills/flow/SKILL.md):"
94
- MSG+="\\n 1.Input 2.Get-context 3.Route ([arka:routing] <dept> -> <lead>) 4.Call-hierarchy"
95
- MSG+="\\n 5.Research (Obsidian+vector DB, cite or declare gap) 6.Call-team (Agent tool)"
96
- MSG+="\\n 7.Plan with 6 parallel reviewers (positive / devils-advocate / Q&A / KB-research / best-solution / pessimistic)"
97
- MSG+="\\n 8.Present+save (Obsidian + vector DB + ~/.arkaos/plans/) 9.Wait-approval (silence != approval)"
98
- MSG+="\\n 10.TODO list 11.Per-todo: team-call -> complete -> QA -> Security -> Quality Gate (Marta+Eduardo+Francisca Opus) -> Document"
99
- MSG+="\\n 12.Loop until TODO exhausted 13.Detailed summary"
100
- MSG+="\\nEmit [arka:phase:N] before each step. Bypass ONLY via [arka:trivial] <reason> for single-file edits under 10 lines."
101
- MSG+="\\nNo runtime setting, no user convenience, no task type can opt out. This contract overrides generic-assistant defaults."
92
+ # ─── Evidence Flow Contract (top-of-session, highest priority) ──────────
93
+ MSG+="\\n\\n[ARKA:EVIDENCE-FLOW] NON-NEGOTIABLE. Every non-trivial request runs the 4-gate evidence flow (constitution rule evidence-flow; source arka/skills/flow/SKILL.md):"
94
+ MSG+="\\n G1 CONTEXT ([arka:routing] <dept> -> <lead> + KB/graph grounding, cite or declare gap)"
95
+ MSG+="\\n G2 PLAN (short plan -> EXPLICIT user approval; silence != approval)"
96
+ MSG+="\\n G3 EXECUTE (closes only with real test run on record: command + exit 0)"
97
+ MSG+="\\n G4 REVIEW (executable checks: lint/type/coverage/security/spell -> honest summary)"
98
+ MSG+="\\nEmit [arka:gate:N] at each gate start. Gates pass on evidence, never on narration."
99
+ MSG+="\\nBypass ONLY via [arka:trivial] <reason> for single-file edits under 10 lines."
102
100
 
103
101
  # ─── Transparency tag contract (PR12 v2.34.0) ───────────────────────────
104
102
  MSG+="\\n\\n[ARKA:META-TAG] Every substantive response ends with a single line:"