@trac3er/oh-my-god 2.0.0 → 2.0.2

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 (243) hide show
  1. package/.claude-plugin/marketplace.json +8 -8
  2. package/.claude-plugin/plugin.json +5 -4
  3. package/.claude-plugin/scripts/uninstall.sh +74 -3
  4. package/.claude-plugin/scripts/update.sh +78 -3
  5. package/.coveragerc +26 -0
  6. package/.mcp.json +4 -4
  7. package/CHANGELOG.md +14 -0
  8. package/CODE_OF_CONDUCT.md +27 -0
  9. package/CONTRIBUTING.md +62 -0
  10. package/OMG-setup.sh +1201 -355
  11. package/README.md +77 -56
  12. package/SECURITY.md +25 -0
  13. package/agents/__init__.py +1 -0
  14. package/agents/model_roles.py +196 -0
  15. package/agents/omg-architect-mode.md +3 -5
  16. package/agents/omg-backend-engineer.md +3 -5
  17. package/agents/omg-database-engineer.md +3 -5
  18. package/agents/omg-frontend-designer.md +4 -5
  19. package/agents/omg-implement-mode.md +4 -5
  20. package/agents/omg-infra-engineer.md +3 -5
  21. package/agents/omg-research-mode.md +4 -6
  22. package/agents/omg-security-auditor.md +3 -5
  23. package/agents/omg-testing-engineer.md +3 -5
  24. package/build/lib/yaml.py +321 -0
  25. package/commands/OMG:ai-commit.md +101 -14
  26. package/commands/OMG:arch.md +302 -19
  27. package/commands/OMG:ccg.md +12 -7
  28. package/commands/OMG:compat.md +25 -17
  29. package/commands/OMG:cost.md +173 -13
  30. package/commands/OMG:crazy.md +1 -1
  31. package/commands/OMG:create-agent.md +170 -20
  32. package/commands/OMG:deps.md +235 -17
  33. package/commands/OMG:domain-init.md +1 -1
  34. package/commands/OMG:escalate.md +41 -12
  35. package/commands/OMG:health-check.md +37 -13
  36. package/commands/OMG:init.md +122 -14
  37. package/commands/OMG:project-init.md +1 -1
  38. package/commands/OMG:session-branch.md +76 -9
  39. package/commands/OMG:session-fork.md +42 -5
  40. package/commands/OMG:session-merge.md +124 -8
  41. package/commands/OMG:setup.md +69 -12
  42. package/commands/OMG:stats.md +215 -14
  43. package/commands/OMG:teams.md +19 -10
  44. package/config/lsp_languages.yaml +8 -0
  45. package/hooks/__init__.py +0 -0
  46. package/hooks/_agent_registry.py +423 -0
  47. package/hooks/_analytics.py +291 -0
  48. package/hooks/_budget.py +31 -0
  49. package/hooks/_common.py +569 -0
  50. package/hooks/_compression_optimizer.py +119 -0
  51. package/hooks/_cost_ledger.py +176 -0
  52. package/hooks/_learnings.py +126 -0
  53. package/hooks/_memory.py +103 -0
  54. package/hooks/_protected_context.py +150 -0
  55. package/hooks/_token_counter.py +221 -0
  56. package/hooks/branch_manager.py +236 -0
  57. package/hooks/budget_governor.py +232 -0
  58. package/hooks/circuit-breaker.py +270 -0
  59. package/hooks/compression_feedback.py +254 -0
  60. package/hooks/config-guard.py +216 -0
  61. package/hooks/context_pressure.py +53 -0
  62. package/hooks/credential_store.py +1020 -0
  63. package/hooks/fetch-rate-limits.py +212 -0
  64. package/hooks/firewall.py +48 -0
  65. package/hooks/hashline-formatter-bridge.py +224 -0
  66. package/hooks/hashline-injector.py +273 -0
  67. package/hooks/hashline-validator.py +216 -0
  68. package/hooks/idle-detector.py +95 -0
  69. package/hooks/intentgate-keyword-detector.py +188 -0
  70. package/hooks/magic-keyword-router.py +195 -0
  71. package/hooks/policy_engine.py +505 -0
  72. package/hooks/post-tool-failure.py +19 -0
  73. package/hooks/post-write.py +219 -0
  74. package/hooks/post_write.py +46 -0
  75. package/hooks/pre-compact.py +398 -0
  76. package/hooks/pre-tool-inject.py +98 -0
  77. package/hooks/prompt-enhancer.py +672 -0
  78. package/hooks/quality-runner.py +191 -0
  79. package/hooks/query.py +512 -0
  80. package/hooks/secret-guard.py +61 -0
  81. package/hooks/secret_audit.py +144 -0
  82. package/hooks/session-end-capture.py +137 -0
  83. package/hooks/session-start.py +277 -0
  84. package/hooks/setup_wizard.py +582 -0
  85. package/hooks/shadow_manager.py +297 -0
  86. package/hooks/state_migration.py +225 -0
  87. package/hooks/stop-gate.py +7 -0
  88. package/hooks/stop_dispatcher.py +945 -0
  89. package/hooks/test-validator.py +361 -0
  90. package/hooks/test_generator_hook.py +123 -0
  91. package/hooks/todo-state-tracker.py +114 -0
  92. package/hooks/tool-ledger.py +149 -0
  93. package/hooks/trust_review.py +585 -0
  94. package/hud/omg-hud.mjs +31 -1
  95. package/lab/__init__.py +1 -0
  96. package/lab/pipeline.py +75 -0
  97. package/lab/policies.py +52 -0
  98. package/package.json +7 -18
  99. package/plugins/README.md +33 -61
  100. package/plugins/advanced/commands/OMG:deep-plan.md +3 -3
  101. package/plugins/advanced/commands/OMG:learn.md +1 -1
  102. package/plugins/advanced/commands/OMG:security-review.md +3 -3
  103. package/plugins/advanced/commands/OMG:ship.md +1 -1
  104. package/plugins/advanced/plugin.json +1 -1
  105. package/plugins/core/plugin.json +8 -3
  106. package/plugins/dephealth/__init__.py +0 -0
  107. package/plugins/dephealth/cve_scanner.py +188 -0
  108. package/plugins/dephealth/license_checker.py +135 -0
  109. package/plugins/dephealth/manifest_detector.py +423 -0
  110. package/plugins/dephealth/vuln_analyzer.py +169 -0
  111. package/plugins/testgen/__init__.py +0 -0
  112. package/plugins/testgen/codamosa_engine.py +402 -0
  113. package/plugins/testgen/edge_case_synthesizer.py +184 -0
  114. package/plugins/testgen/framework_detector.py +271 -0
  115. package/plugins/testgen/skeleton_generator.py +219 -0
  116. package/plugins/viz/__init__.py +0 -0
  117. package/plugins/viz/ast_parser.py +139 -0
  118. package/plugins/viz/diagram_generator.py +192 -0
  119. package/plugins/viz/graph_builder.py +444 -0
  120. package/plugins/viz/native_parsers.py +259 -0
  121. package/plugins/viz/regex_parser.py +112 -0
  122. package/pyproject.toml +81 -0
  123. package/rules/contextual/write-verify.md +2 -2
  124. package/rules/core/00-truth.md +1 -1
  125. package/rules/core/01-surgical.md +1 -1
  126. package/rules/core/02-circuit-breaker.md +2 -2
  127. package/rules/core/03-ensemble.md +3 -3
  128. package/rules/core/04-testing.md +3 -3
  129. package/runtime/__init__.py +32 -0
  130. package/runtime/adapters/__init__.py +13 -0
  131. package/runtime/adapters/claude.py +60 -0
  132. package/runtime/adapters/gpt.py +53 -0
  133. package/runtime/adapters/local.py +53 -0
  134. package/runtime/adoption.py +212 -0
  135. package/runtime/business_workflow.py +220 -0
  136. package/runtime/cli_provider.py +85 -0
  137. package/runtime/compat.py +1299 -0
  138. package/runtime/custom_agent_loader.py +366 -0
  139. package/runtime/dispatcher.py +47 -0
  140. package/runtime/ecosystem.py +371 -0
  141. package/runtime/legacy_compat.py +7 -0
  142. package/runtime/mcp_config_writers.py +115 -0
  143. package/runtime/mcp_lifecycle.py +153 -0
  144. package/runtime/mcp_memory_server.py +135 -0
  145. package/runtime/memory_parsers/__init__.py +0 -0
  146. package/runtime/memory_parsers/chatgpt_parser.py +257 -0
  147. package/runtime/memory_parsers/claude_import.py +107 -0
  148. package/runtime/memory_parsers/export.py +97 -0
  149. package/runtime/memory_parsers/gemini_import.py +91 -0
  150. package/runtime/memory_parsers/kimi_import.py +91 -0
  151. package/runtime/memory_store.py +215 -0
  152. package/runtime/omc_compat.py +7 -0
  153. package/runtime/providers/__init__.py +0 -0
  154. package/runtime/providers/codex_provider.py +112 -0
  155. package/runtime/providers/gemini_provider.py +128 -0
  156. package/runtime/providers/kimi_provider.py +151 -0
  157. package/runtime/providers/opencode_provider.py +144 -0
  158. package/runtime/subagent_dispatcher.py +362 -0
  159. package/runtime/team_router.py +1167 -0
  160. package/runtime/tmux_session_manager.py +169 -0
  161. package/scripts/check-omg-compat-contract-snapshot.py +137 -0
  162. package/scripts/check-omg-contract-snapshot.py +12 -0
  163. package/scripts/check-omg-public-ready.py +193 -0
  164. package/scripts/check-omg-standalone-clean.py +103 -0
  165. package/scripts/legacy_to_omg_migrate.py +29 -0
  166. package/scripts/migrate-legacy.py +464 -0
  167. package/scripts/omc_to_omg_migrate.py +12 -0
  168. package/scripts/omg.py +492 -0
  169. package/scripts/settings-merge.py +283 -0
  170. package/scripts/verify-standalone.sh +8 -4
  171. package/settings.json +126 -29
  172. package/templates/profile.yaml +1 -1
  173. package/tools/__init__.py +2 -0
  174. package/tools/browser_consent.py +289 -0
  175. package/tools/browser_stealth.py +481 -0
  176. package/tools/browser_tool.py +448 -0
  177. package/tools/changelog_generator.py +347 -0
  178. package/tools/commit_splitter.py +746 -0
  179. package/tools/config_discovery.py +151 -0
  180. package/tools/config_merger.py +449 -0
  181. package/tools/dashboard_generator.py +300 -0
  182. package/tools/git_inspector.py +298 -0
  183. package/tools/lsp_client.py +275 -0
  184. package/tools/lsp_discovery.py +231 -0
  185. package/tools/lsp_operations.py +392 -0
  186. package/tools/pr_generator.py +404 -0
  187. package/tools/python_repl.py +656 -0
  188. package/tools/python_sandbox.py +609 -0
  189. package/tools/search_providers/__init__.py +77 -0
  190. package/tools/search_providers/brave.py +115 -0
  191. package/tools/search_providers/exa.py +116 -0
  192. package/tools/search_providers/jina.py +104 -0
  193. package/tools/search_providers/perplexity.py +139 -0
  194. package/tools/search_providers/synthetic.py +74 -0
  195. package/tools/session_snapshot.py +736 -0
  196. package/tools/ssh_manager.py +912 -0
  197. package/tools/theme_engine.py +294 -0
  198. package/tools/theme_selector.py +137 -0
  199. package/tools/web_search.py +622 -0
  200. package/yaml.py +321 -0
  201. package/.claude-plugin/scripts/install.sh +0 -9
  202. package/bun.lock +0 -23
  203. package/bunfig.toml +0 -3
  204. package/hooks/_budget.ts +0 -1
  205. package/hooks/_common.ts +0 -63
  206. package/hooks/circuit-breaker.ts +0 -101
  207. package/hooks/config-guard.ts +0 -4
  208. package/hooks/firewall.ts +0 -20
  209. package/hooks/policy_engine.ts +0 -156
  210. package/hooks/post-tool-failure.ts +0 -22
  211. package/hooks/post-write.ts +0 -4
  212. package/hooks/pre-tool-inject.ts +0 -4
  213. package/hooks/prompt-enhancer.ts +0 -46
  214. package/hooks/quality-runner.ts +0 -24
  215. package/hooks/secret-guard.ts +0 -4
  216. package/hooks/session-end-capture.ts +0 -19
  217. package/hooks/session-start.ts +0 -19
  218. package/hooks/shadow_manager.ts +0 -81
  219. package/hooks/stop-gate.ts +0 -22
  220. package/hooks/stop_dispatcher.ts +0 -147
  221. package/hooks/test-generator-hook.ts +0 -4
  222. package/hooks/tool-ledger.ts +0 -27
  223. package/hooks/trust_review.ts +0 -175
  224. package/lab/pipeline.ts +0 -75
  225. package/lab/policies.ts +0 -68
  226. package/runtime/common.ts +0 -111
  227. package/runtime/compat.ts +0 -174
  228. package/runtime/dispatcher.ts +0 -25
  229. package/runtime/ecosystem.ts +0 -186
  230. package/runtime/provider_bootstrap.ts +0 -99
  231. package/runtime/provider_smoke.ts +0 -34
  232. package/runtime/release_readiness.ts +0 -186
  233. package/runtime/team_router.ts +0 -144
  234. package/scripts/check-omg-compat-contract-snapshot.ts +0 -20
  235. package/scripts/check-omg-standalone-clean.ts +0 -12
  236. package/scripts/check-runtime-clean.ts +0 -94
  237. package/scripts/omg.ts +0 -352
  238. package/scripts/settings-merge.ts +0 -93
  239. package/tools/commit_splitter.ts +0 -23
  240. package/tools/git_inspector.ts +0 -18
  241. package/tools/session_snapshot.ts +0 -47
  242. package/trac3er-oh-my-god-2.0.0.tgz +0 -0
  243. package/tsconfig.json +0 -15
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ IntentGate Keyword Detection Hook — OMG v1.2
4
+
5
+ UserPromptSubmit hook that detects magic keywords in user prompts,
6
+ maps them to intents with confidence scoring, and injects a LEADER_HINT
7
+ into the hook output for downstream routing. Detection only — no execution.
8
+
9
+ Classification v1.2:
10
+ - Each detected intent includes a confidence score (0.0–1.0)
11
+ - Compound intent parsing: multiple keywords → multiple intents
12
+
13
+ Confidence rules:
14
+ - Exact keyword match (standalone): 0.95
15
+ - Keyword embedded in context: 0.90
16
+ - Keyword in compound phrase (multi-intent): 0.85
17
+ - Multiple occurrences of same keyword: 0.98 (cap)
18
+
19
+ Magic keywords:
20
+ - ultrawork, autopilot, ralph → execution modes
21
+ - plan this, tdd, search → task types
22
+ - stop, crazy → special directives
23
+ """
24
+ import json
25
+ import sys
26
+ import os
27
+ import re
28
+ import time
29
+
30
+ HOOKS_DIR = os.path.dirname(__file__)
31
+ if HOOKS_DIR not in sys.path:
32
+ sys.path.insert(0, HOOKS_DIR)
33
+
34
+ try:
35
+ from hooks._common import (
36
+ setup_crash_handler,
37
+ json_input,
38
+ get_feature_flag,
39
+ _resolve_project_dir,
40
+ check_performance_budget,
41
+ PRE_TOOL_INJECT_MAX_MS,
42
+ )
43
+ except ImportError:
44
+ import importlib
45
+ _common = importlib.import_module("_common")
46
+ setup_crash_handler = _common.setup_crash_handler
47
+ json_input = _common.json_input
48
+ get_feature_flag = _common.get_feature_flag
49
+ _resolve_project_dir = _common._resolve_project_dir
50
+ check_performance_budget = _common.check_performance_budget
51
+ PRE_TOOL_INJECT_MAX_MS = _common.PRE_TOOL_INJECT_MAX_MS
52
+
53
+ setup_crash_handler("intentgate-keyword-detector", fail_closed=False)
54
+
55
+ # ═══════════════════════════════════════════════════════════
56
+ # KEYWORD → INTENT MAPPING
57
+ # ═══════════════════════════════════════════════════════════
58
+ KEYWORD_INTENT_MAP = {
59
+ "ultrawork": "INTENT_MAX_EFFORT",
60
+ "autopilot": "INTENT_AUTONOMOUS",
61
+ "ralph": "INTENT_LOOP",
62
+ "plan this": "INTENT_PLAN",
63
+ "tdd": "INTENT_TEST_DRIVEN",
64
+ "search": "INTENT_SEARCH",
65
+ "stop": "INTENT_STOP",
66
+ "crazy": "INTENT_CRAZY",
67
+ }
68
+
69
+
70
+ # ═══════════════════════════════════════════════════════════
71
+ # CONFIDENCE SCORING ENGINE
72
+ # ═══════════════════════════════════════════════════════════
73
+
74
+ def _count_keyword_occurrences(keyword, prompt_lower):
75
+ """Count occurrences of keyword in prompt (respecting matching rules)."""
76
+ if " " in keyword:
77
+ # Multi-word: non-overlapping substring count
78
+ count = 0
79
+ start = 0
80
+ while True:
81
+ idx = prompt_lower.find(keyword, start)
82
+ if idx == -1:
83
+ break
84
+ count += 1
85
+ start = idx + len(keyword)
86
+ return count
87
+ else:
88
+ # Single-word: word boundary count
89
+ pattern = r'\b' + re.escape(keyword) + r'\b'
90
+ return len(re.findall(pattern, prompt_lower))
91
+
92
+
93
+ def _compute_confidence(keyword, prompt_lower, occurrence_count, total_intents):
94
+ """Compute confidence score for a detected intent.
95
+
96
+ Rules (applied in priority order):
97
+ - Multiple occurrences of same keyword: 0.98 (capped)
98
+ - Keyword in compound phrase (multiple intents): 0.85
99
+ - Exact keyword match (standalone prompt): 0.95
100
+ - Keyword embedded in context signals: 0.90
101
+ """
102
+ # Multiple occurrences of same keyword → highest confidence (cap)
103
+ if occurrence_count > 1:
104
+ return 0.98
105
+
106
+ # Compound intent (multiple different keywords detected)
107
+ if total_intents > 1:
108
+ return 0.85
109
+
110
+ # Single intent, single occurrence
111
+ stripped = prompt_lower.strip()
112
+
113
+ # Exact/standalone match → high confidence
114
+ if stripped == keyword:
115
+ return 0.95
116
+
117
+ # Keyword embedded in surrounding context → slightly lower
118
+ return 0.90
119
+ # ═══════════════════════════════════════════════════════════
120
+ # FEATURE FLAG CHECK
121
+ # ═══════════════════════════════════════════════════════════
122
+ start_time = time.time()
123
+
124
+ if not get_feature_flag("INTENTGATE", default=False):
125
+ # Feature disabled — return no-op JSON
126
+ json.dump({}, sys.stdout)
127
+ sys.exit(0)
128
+
129
+ # ═══════════════════════════════════════════════════════════
130
+ # INPUT PARSING
131
+ # ═══════════════════════════════════════════════════════════
132
+ data = json_input()
133
+
134
+ prompt = data.get("tool_input", {}).get("user_message", "") or data.get("user_message", "")
135
+ if not prompt:
136
+ json.dump({}, sys.stdout)
137
+ sys.exit(0)
138
+
139
+ prompt_lower = prompt.lower().strip()
140
+
141
+ # ═══════════════════════════════════════════════════════════
142
+ # KEYWORD DETECTION (case-insensitive, multi-keyword, confidence scoring)
143
+ # ═══════════════════════════════════════════════════════════
144
+
145
+ # First pass: detect keywords with occurrence counts
146
+ detected_raw = []
147
+
148
+ for keyword, intent in KEYWORD_INTENT_MAP.items():
149
+ occurrences = _count_keyword_occurrences(keyword, prompt_lower)
150
+ if occurrences > 0:
151
+ detected_raw.append((keyword, intent, occurrences))
152
+
153
+ # Second pass: compute confidence scores
154
+ num_intents = len(detected_raw)
155
+ detected_intents = []
156
+
157
+ for keyword, intent, occurrences in detected_raw:
158
+ confidence = _compute_confidence(keyword, prompt_lower, occurrences, num_intents)
159
+ detected_intents.append({
160
+ "intent": intent,
161
+ "confidence": confidence,
162
+ "keyword": keyword,
163
+ })
164
+ # ═══════════════════════════════════════════════════════════
165
+ # OUTPUT CONSTRUCTION
166
+ # ═══════════════════════════════════════════════════════════
167
+ output = {}
168
+
169
+ if detected_intents:
170
+ # Inject LEADER_HINT with detected intents and confidence scores
171
+ output["LEADER_HINT"] = {
172
+ "detected_intents": detected_intents,
173
+ "keyword_count": len(detected_intents),
174
+ "routing_enabled": True,
175
+ "classification_version": "1.2",
176
+ }
177
+
178
+ # ═══════════════════════════════════════════════════════════
179
+ # PERFORMANCE BUDGET CHECK
180
+ # ═══════════════════════════════════════════════════════════
181
+ elapsed_ms = (time.time() - start_time) * 1000
182
+ check_performance_budget("intentgate-keyword-detector", elapsed_ms, PRE_TOOL_INJECT_MAX_MS)
183
+
184
+ # ═══════════════════════════════════════════════════════════
185
+ # OUTPUT
186
+ # ═══════════════════════════════════════════════════════════
187
+ json.dump(output, sys.stdout)
188
+ sys.exit(0)
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Magic Keyword Router — OMG v1.2
4
+
5
+ PostToolUse hook that reads the LEADER_HINT produced by
6
+ intentgate-keyword-detector, selects the appropriate agent based on intent,
7
+ and writes a routing result to `.omg/state/routing_result.json`.
8
+
9
+ Decision only — no subprocess calls or agent execution.
10
+
11
+ Input sources (checked in priority order):
12
+ 1. LEADER_HINT in stdin JSON (from hook pipeline)
13
+ 2. `.omg/state/leader_hint.json` (persisted by future integrations)
14
+
15
+ Feature flag: OMG_MAGIC_ROUTER_ENABLED (default off)
16
+ """
17
+ import json
18
+ import os
19
+ import sys
20
+ import time
21
+
22
+ HOOKS_DIR = os.path.dirname(__file__)
23
+ if HOOKS_DIR not in sys.path:
24
+ sys.path.insert(0, HOOKS_DIR)
25
+
26
+ try:
27
+ from hooks._common import (
28
+ setup_crash_handler,
29
+ json_input,
30
+ get_feature_flag,
31
+ atomic_json_write,
32
+ get_project_dir,
33
+ check_performance_budget,
34
+ PRE_TOOL_INJECT_MAX_MS,
35
+ )
36
+ except ImportError:
37
+ import importlib
38
+ _common = importlib.import_module("_common")
39
+ setup_crash_handler = _common.setup_crash_handler
40
+ json_input = _common.json_input
41
+ get_feature_flag = _common.get_feature_flag
42
+ atomic_json_write = _common.atomic_json_write
43
+ get_project_dir = _common.get_project_dir
44
+ check_performance_budget = _common.check_performance_budget
45
+ PRE_TOOL_INJECT_MAX_MS = _common.PRE_TOOL_INJECT_MAX_MS
46
+
47
+ try:
48
+ from hooks._agent_registry import INTENT_ROUTING
49
+ except ImportError:
50
+ _registry = importlib.import_module("_agent_registry")
51
+ INTENT_ROUTING = _registry.INTENT_ROUTING
52
+
53
+ setup_crash_handler("magic-keyword-router", fail_closed=False)
54
+
55
+ # ═══════════════════════════════════════════════════════════
56
+ # CONSTANTS
57
+ # ═══════════════════════════════════════════════════════════
58
+ ROUTING_RESULT_PATH = ".omg/state/routing_result.json"
59
+ LEADER_HINT_PATH = ".omg/state/leader_hint.json"
60
+
61
+
62
+ # ═══════════════════════════════════════════════════════════
63
+ # FEATURE FLAG CHECK
64
+ # ═══════════════════════════════════════════════════════════
65
+ start_time = time.time()
66
+
67
+ if not get_feature_flag("MAGIC_ROUTER", default=False):
68
+ # Feature disabled — no-op
69
+ json.dump({}, sys.stdout)
70
+ sys.exit(0)
71
+
72
+ # ═══════════════════════════════════════════════════════════
73
+ # INPUT PARSING
74
+ # ═══════════════════════════════════════════════════════════
75
+ data = json_input()
76
+
77
+
78
+ def _extract_leader_hint(hook_data):
79
+ """Extract LEADER_HINT from hook stdin data.
80
+
81
+ Checks multiple locations where the hint may appear:
82
+ - Top-level "LEADER_HINT" key
83
+ - Nested under "tool_output"
84
+ - Nested under "hookSpecificOutput"
85
+ """
86
+ if not isinstance(hook_data, dict):
87
+ return None
88
+ # Direct top-level
89
+ hint = hook_data.get("LEADER_HINT")
90
+ if hint:
91
+ return hint
92
+ # Nested in tool_output
93
+ tool_output = hook_data.get("tool_output", {})
94
+ if isinstance(tool_output, dict):
95
+ hint = tool_output.get("LEADER_HINT")
96
+ if hint:
97
+ return hint
98
+ # Nested in hookSpecificOutput
99
+ hso = hook_data.get("hookSpecificOutput", {})
100
+ if isinstance(hso, dict):
101
+ hint = hso.get("LEADER_HINT")
102
+ if hint:
103
+ return hint
104
+ return None
105
+
106
+
107
+ def _read_leader_hint_file(project_dir):
108
+ """Read LEADER_HINT from persisted file (secondary source)."""
109
+ path = os.path.join(project_dir, LEADER_HINT_PATH)
110
+ if not os.path.exists(path):
111
+ return None
112
+ try:
113
+ with open(path, "r", encoding="utf-8") as f:
114
+ data = json.load(f)
115
+ # File may contain {"LEADER_HINT": {...}} or the hint directly
116
+ if isinstance(data, dict):
117
+ return data.get("LEADER_HINT", data) if "detected_intents" not in data else data
118
+ return None
119
+ except (json.JSONDecodeError, OSError):
120
+ return None
121
+
122
+
123
+ def _resolve_routing(leader_hint):
124
+ """Given a LEADER_HINT dict, resolve the target agent.
125
+
126
+ Returns (target_agent, intent, confidence) tuple.
127
+ target_agent may be None (e.g., INTENT_STOP → halt).
128
+ """
129
+ detected = leader_hint.get("detected_intents", [])
130
+ if not detected:
131
+ return None, None, 0.0
132
+
133
+ # Pick first non-None routable intent (highest priority = first in list)
134
+ for intent_entry in detected:
135
+ intent_name = intent_entry.get("intent", "")
136
+ confidence = intent_entry.get("confidence", 0.0)
137
+ target = INTENT_ROUTING.get(intent_name)
138
+
139
+ # INTENT_STOP maps to None explicitly — that IS a valid routing result
140
+ if intent_name in INTENT_ROUTING:
141
+ return target, intent_name, confidence
142
+
143
+ # No known intent found
144
+ return None, None, 0.0
145
+
146
+
147
+ # ═══════════════════════════════════════════════════════════
148
+ # LEADER_HINT RESOLUTION (stdin preferred, file fallback)
149
+ # ═══════════════════════════════════════════════════════════
150
+ project_dir = get_project_dir()
151
+ leader_hint = _extract_leader_hint(data)
152
+
153
+ if leader_hint is None:
154
+ leader_hint = _read_leader_hint_file(project_dir)
155
+
156
+ # ═══════════════════════════════════════════════════════════
157
+ # ROUTING DECISION
158
+ # ═══════════════════════════════════════════════════════════
159
+ from datetime import datetime, timezone
160
+
161
+ routing_result_path = os.path.join(project_dir, ROUTING_RESULT_PATH)
162
+ ts = datetime.now(timezone.utc).isoformat()
163
+
164
+ if leader_hint and leader_hint.get("detected_intents"):
165
+ target_agent, intent, confidence = _resolve_routing(leader_hint)
166
+ routing_result = {
167
+ "target_agent": target_agent,
168
+ "intent": intent,
169
+ "confidence": confidence,
170
+ "fallback": False,
171
+ "timestamp": ts,
172
+ }
173
+ else:
174
+ # No LEADER_HINT → fallback
175
+ routing_result = {
176
+ "target_agent": None,
177
+ "intent": None,
178
+ "confidence": 0.0,
179
+ "fallback": True,
180
+ "timestamp": ts,
181
+ }
182
+
183
+ atomic_json_write(routing_result_path, routing_result)
184
+
185
+ # ═══════════════════════════════════════════════════════════
186
+ # PERFORMANCE BUDGET CHECK
187
+ # ═══════════════════════════════════════════════════════════
188
+ elapsed_ms = (time.time() - start_time) * 1000
189
+ check_performance_budget("magic-keyword-router", elapsed_ms, PRE_TOOL_INJECT_MAX_MS)
190
+
191
+ # ═══════════════════════════════════════════════════════════
192
+ # OUTPUT (no-op for PostToolUse — just exit clean)
193
+ # ═══════════════════════════════════════════════════════════
194
+ json.dump({}, sys.stdout)
195
+ sys.exit(0)