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