@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.
- package/.claude-plugin/marketplace.json +8 -8
- package/.claude-plugin/plugin.json +5 -4
- package/.claude-plugin/scripts/uninstall.sh +74 -3
- package/.claude-plugin/scripts/update.sh +78 -3
- package/.coveragerc +26 -0
- package/.mcp.json +4 -4
- package/CHANGELOG.md +14 -0
- package/CODE_OF_CONDUCT.md +27 -0
- package/CONTRIBUTING.md +62 -0
- package/OMG-setup.sh +1201 -355
- package/README.md +77 -56
- package/SECURITY.md +25 -0
- package/agents/__init__.py +1 -0
- package/agents/model_roles.py +196 -0
- package/agents/omg-architect-mode.md +3 -5
- package/agents/omg-backend-engineer.md +3 -5
- package/agents/omg-database-engineer.md +3 -5
- package/agents/omg-frontend-designer.md +4 -5
- package/agents/omg-implement-mode.md +4 -5
- package/agents/omg-infra-engineer.md +3 -5
- package/agents/omg-research-mode.md +4 -6
- package/agents/omg-security-auditor.md +3 -5
- package/agents/omg-testing-engineer.md +3 -5
- package/build/lib/yaml.py +321 -0
- package/commands/OMG:ai-commit.md +101 -14
- package/commands/OMG:arch.md +302 -19
- package/commands/OMG:ccg.md +12 -7
- package/commands/OMG:compat.md +25 -17
- package/commands/OMG:cost.md +173 -13
- package/commands/OMG:crazy.md +1 -1
- package/commands/OMG:create-agent.md +170 -20
- package/commands/OMG:deps.md +235 -17
- package/commands/OMG:domain-init.md +1 -1
- package/commands/OMG:escalate.md +41 -12
- package/commands/OMG:health-check.md +37 -13
- package/commands/OMG:init.md +122 -14
- package/commands/OMG:project-init.md +1 -1
- package/commands/OMG:session-branch.md +76 -9
- package/commands/OMG:session-fork.md +42 -5
- package/commands/OMG:session-merge.md +124 -8
- package/commands/OMG:setup.md +69 -12
- package/commands/OMG:stats.md +215 -14
- package/commands/OMG:teams.md +19 -10
- package/config/lsp_languages.yaml +8 -0
- package/hooks/__init__.py +0 -0
- package/hooks/_agent_registry.py +423 -0
- package/hooks/_analytics.py +291 -0
- package/hooks/_budget.py +31 -0
- package/hooks/_common.py +569 -0
- package/hooks/_compression_optimizer.py +119 -0
- package/hooks/_cost_ledger.py +176 -0
- package/hooks/_learnings.py +126 -0
- package/hooks/_memory.py +103 -0
- package/hooks/_protected_context.py +150 -0
- package/hooks/_token_counter.py +221 -0
- package/hooks/branch_manager.py +236 -0
- package/hooks/budget_governor.py +232 -0
- package/hooks/circuit-breaker.py +270 -0
- package/hooks/compression_feedback.py +254 -0
- package/hooks/config-guard.py +216 -0
- package/hooks/context_pressure.py +53 -0
- package/hooks/credential_store.py +1020 -0
- package/hooks/fetch-rate-limits.py +212 -0
- package/hooks/firewall.py +48 -0
- package/hooks/hashline-formatter-bridge.py +224 -0
- package/hooks/hashline-injector.py +273 -0
- package/hooks/hashline-validator.py +216 -0
- package/hooks/idle-detector.py +95 -0
- package/hooks/intentgate-keyword-detector.py +188 -0
- package/hooks/magic-keyword-router.py +195 -0
- package/hooks/policy_engine.py +505 -0
- package/hooks/post-tool-failure.py +19 -0
- package/hooks/post-write.py +219 -0
- package/hooks/post_write.py +46 -0
- package/hooks/pre-compact.py +398 -0
- package/hooks/pre-tool-inject.py +98 -0
- package/hooks/prompt-enhancer.py +672 -0
- package/hooks/quality-runner.py +191 -0
- package/hooks/query.py +512 -0
- package/hooks/secret-guard.py +61 -0
- package/hooks/secret_audit.py +144 -0
- package/hooks/session-end-capture.py +137 -0
- package/hooks/session-start.py +277 -0
- package/hooks/setup_wizard.py +582 -0
- package/hooks/shadow_manager.py +297 -0
- package/hooks/state_migration.py +225 -0
- package/hooks/stop-gate.py +7 -0
- package/hooks/stop_dispatcher.py +945 -0
- package/hooks/test-validator.py +361 -0
- package/hooks/test_generator_hook.py +123 -0
- package/hooks/todo-state-tracker.py +114 -0
- package/hooks/tool-ledger.py +149 -0
- package/hooks/trust_review.py +585 -0
- package/hud/omg-hud.mjs +31 -1
- package/lab/__init__.py +1 -0
- package/lab/pipeline.py +75 -0
- package/lab/policies.py +52 -0
- package/package.json +7 -18
- package/plugins/README.md +33 -61
- package/plugins/advanced/commands/OMG:deep-plan.md +3 -3
- package/plugins/advanced/commands/OMG:learn.md +1 -1
- package/plugins/advanced/commands/OMG:security-review.md +3 -3
- package/plugins/advanced/commands/OMG:ship.md +1 -1
- package/plugins/advanced/plugin.json +1 -1
- package/plugins/core/plugin.json +8 -3
- package/plugins/dephealth/__init__.py +0 -0
- package/plugins/dephealth/cve_scanner.py +188 -0
- package/plugins/dephealth/license_checker.py +135 -0
- package/plugins/dephealth/manifest_detector.py +423 -0
- package/plugins/dephealth/vuln_analyzer.py +169 -0
- package/plugins/testgen/__init__.py +0 -0
- package/plugins/testgen/codamosa_engine.py +402 -0
- package/plugins/testgen/edge_case_synthesizer.py +184 -0
- package/plugins/testgen/framework_detector.py +271 -0
- package/plugins/testgen/skeleton_generator.py +219 -0
- package/plugins/viz/__init__.py +0 -0
- package/plugins/viz/ast_parser.py +139 -0
- package/plugins/viz/diagram_generator.py +192 -0
- package/plugins/viz/graph_builder.py +444 -0
- package/plugins/viz/native_parsers.py +259 -0
- package/plugins/viz/regex_parser.py +112 -0
- package/pyproject.toml +81 -0
- package/rules/contextual/write-verify.md +2 -2
- package/rules/core/00-truth.md +1 -1
- package/rules/core/01-surgical.md +1 -1
- package/rules/core/02-circuit-breaker.md +2 -2
- package/rules/core/03-ensemble.md +3 -3
- package/rules/core/04-testing.md +3 -3
- package/runtime/__init__.py +32 -0
- package/runtime/adapters/__init__.py +13 -0
- package/runtime/adapters/claude.py +60 -0
- package/runtime/adapters/gpt.py +53 -0
- package/runtime/adapters/local.py +53 -0
- package/runtime/adoption.py +212 -0
- package/runtime/business_workflow.py +220 -0
- package/runtime/cli_provider.py +85 -0
- package/runtime/compat.py +1299 -0
- package/runtime/custom_agent_loader.py +366 -0
- package/runtime/dispatcher.py +47 -0
- package/runtime/ecosystem.py +371 -0
- package/runtime/legacy_compat.py +7 -0
- package/runtime/mcp_config_writers.py +115 -0
- package/runtime/mcp_lifecycle.py +153 -0
- package/runtime/mcp_memory_server.py +135 -0
- package/runtime/memory_parsers/__init__.py +0 -0
- package/runtime/memory_parsers/chatgpt_parser.py +257 -0
- package/runtime/memory_parsers/claude_import.py +107 -0
- package/runtime/memory_parsers/export.py +97 -0
- package/runtime/memory_parsers/gemini_import.py +91 -0
- package/runtime/memory_parsers/kimi_import.py +91 -0
- package/runtime/memory_store.py +215 -0
- package/runtime/omc_compat.py +7 -0
- package/runtime/providers/__init__.py +0 -0
- package/runtime/providers/codex_provider.py +112 -0
- package/runtime/providers/gemini_provider.py +128 -0
- package/runtime/providers/kimi_provider.py +151 -0
- package/runtime/providers/opencode_provider.py +144 -0
- package/runtime/subagent_dispatcher.py +362 -0
- package/runtime/team_router.py +1167 -0
- package/runtime/tmux_session_manager.py +169 -0
- package/scripts/check-omg-compat-contract-snapshot.py +137 -0
- package/scripts/check-omg-contract-snapshot.py +12 -0
- package/scripts/check-omg-public-ready.py +193 -0
- package/scripts/check-omg-standalone-clean.py +103 -0
- package/scripts/legacy_to_omg_migrate.py +29 -0
- package/scripts/migrate-legacy.py +464 -0
- package/scripts/omc_to_omg_migrate.py +12 -0
- package/scripts/omg.py +492 -0
- package/scripts/settings-merge.py +283 -0
- package/scripts/verify-standalone.sh +8 -4
- package/settings.json +126 -29
- package/templates/profile.yaml +1 -1
- package/tools/__init__.py +2 -0
- package/tools/browser_consent.py +289 -0
- package/tools/browser_stealth.py +481 -0
- package/tools/browser_tool.py +448 -0
- package/tools/changelog_generator.py +347 -0
- package/tools/commit_splitter.py +746 -0
- package/tools/config_discovery.py +151 -0
- package/tools/config_merger.py +449 -0
- package/tools/dashboard_generator.py +300 -0
- package/tools/git_inspector.py +298 -0
- package/tools/lsp_client.py +275 -0
- package/tools/lsp_discovery.py +231 -0
- package/tools/lsp_operations.py +392 -0
- package/tools/pr_generator.py +404 -0
- package/tools/python_repl.py +656 -0
- package/tools/python_sandbox.py +609 -0
- package/tools/search_providers/__init__.py +77 -0
- package/tools/search_providers/brave.py +115 -0
- package/tools/search_providers/exa.py +116 -0
- package/tools/search_providers/jina.py +104 -0
- package/tools/search_providers/perplexity.py +139 -0
- package/tools/search_providers/synthetic.py +74 -0
- package/tools/session_snapshot.py +736 -0
- package/tools/ssh_manager.py +912 -0
- package/tools/theme_engine.py +294 -0
- package/tools/theme_selector.py +137 -0
- package/tools/web_search.py +622 -0
- package/yaml.py +321 -0
- package/.claude-plugin/scripts/install.sh +0 -9
- package/bun.lock +0 -23
- package/bunfig.toml +0 -3
- package/hooks/_budget.ts +0 -1
- package/hooks/_common.ts +0 -63
- package/hooks/circuit-breaker.ts +0 -101
- package/hooks/config-guard.ts +0 -4
- package/hooks/firewall.ts +0 -20
- package/hooks/policy_engine.ts +0 -156
- package/hooks/post-tool-failure.ts +0 -22
- package/hooks/post-write.ts +0 -4
- package/hooks/pre-tool-inject.ts +0 -4
- package/hooks/prompt-enhancer.ts +0 -46
- package/hooks/quality-runner.ts +0 -24
- package/hooks/secret-guard.ts +0 -4
- package/hooks/session-end-capture.ts +0 -19
- package/hooks/session-start.ts +0 -19
- package/hooks/shadow_manager.ts +0 -81
- package/hooks/stop-gate.ts +0 -22
- package/hooks/stop_dispatcher.ts +0 -147
- package/hooks/test-generator-hook.ts +0 -4
- package/hooks/tool-ledger.ts +0 -27
- package/hooks/trust_review.ts +0 -175
- package/lab/pipeline.ts +0 -75
- package/lab/policies.ts +0 -68
- package/runtime/common.ts +0 -111
- package/runtime/compat.ts +0 -174
- package/runtime/dispatcher.ts +0 -25
- package/runtime/ecosystem.ts +0 -186
- package/runtime/provider_bootstrap.ts +0 -99
- package/runtime/provider_smoke.ts +0 -34
- package/runtime/release_readiness.ts +0 -186
- package/runtime/team_router.ts +0 -144
- package/scripts/check-omg-compat-contract-snapshot.ts +0 -20
- package/scripts/check-omg-standalone-clean.ts +0 -12
- package/scripts/check-runtime-clean.ts +0 -94
- package/scripts/omg.ts +0 -352
- package/scripts/settings-merge.ts +0 -93
- package/tools/commit_splitter.ts +0 -23
- package/tools/git_inspector.ts +0 -18
- package/tools/session_snapshot.ts +0 -47
- package/trac3er-oh-my-god-2.0.0.tgz +0 -0
- package/tsconfig.json +0 -15
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
settings-merge.py
|
|
4
|
+
Merges v2 hooks + permissions INTO existing ~/.claude/settings.json
|
|
5
|
+
without destroying existing non-OMG configuration.
|
|
6
|
+
|
|
7
|
+
Strategy:
|
|
8
|
+
- hooks{}: APPEND our matchers to each event (never replace existing)
|
|
9
|
+
- permissions.allow[]: UNION (add ours, keep theirs)
|
|
10
|
+
- permissions.deny[]: UNION
|
|
11
|
+
- permissions.ask[]: UNION
|
|
12
|
+
- Everything else: PRESERVE as-is
|
|
13
|
+
"""
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import shutil
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
|
|
21
|
+
def load_json(path):
|
|
22
|
+
if not os.path.exists(path):
|
|
23
|
+
return {}
|
|
24
|
+
try:
|
|
25
|
+
with open(path, "r") as f:
|
|
26
|
+
return json.load(f)
|
|
27
|
+
except json.JSONDecodeError as e:
|
|
28
|
+
print(f"ERROR: {path} contains invalid JSON (line {e.lineno}, col {e.colno}):", file=sys.stderr)
|
|
29
|
+
print(f" {e.msg}", file=sys.stderr)
|
|
30
|
+
print(f" Fix the file manually or delete it to start fresh.", file=sys.stderr)
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
except PermissionError:
|
|
33
|
+
print(f"ERROR: Cannot read {path} — permission denied.", file=sys.stderr)
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
except Exception as e:
|
|
36
|
+
print(f"ERROR: Failed to read {path}: {e}", file=sys.stderr)
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
def merge_hooks(existing_hooks, new_hooks):
|
|
40
|
+
"""Append new hook matchers to existing, avoiding duplicates."""
|
|
41
|
+
merged = dict(existing_hooks) # shallow copy
|
|
42
|
+
|
|
43
|
+
def _normalize_command_name(name):
|
|
44
|
+
return re.sub(r"[^a-z0-9]+", "", name.lower())
|
|
45
|
+
|
|
46
|
+
def _matcher_identity(entry):
|
|
47
|
+
if not isinstance(entry, dict):
|
|
48
|
+
return None
|
|
49
|
+
matcher = entry.get("matcher")
|
|
50
|
+
return None if matcher in ("", None) else matcher
|
|
51
|
+
|
|
52
|
+
def _command_identity(command):
|
|
53
|
+
if not command:
|
|
54
|
+
return ""
|
|
55
|
+
tokens = re.findall(r'"[^"]*"|\'[^\']*\'|\S+', command)
|
|
56
|
+
if not tokens:
|
|
57
|
+
return ""
|
|
58
|
+
candidate = tokens[-1].strip("\"'")
|
|
59
|
+
base = os.path.basename(candidate)
|
|
60
|
+
stem, _ = os.path.splitext(base)
|
|
61
|
+
normalized = _normalize_command_name(stem or base)
|
|
62
|
+
return normalized or stem or base
|
|
63
|
+
|
|
64
|
+
def extract_commands(entry):
|
|
65
|
+
commands = set()
|
|
66
|
+
if not isinstance(entry, dict):
|
|
67
|
+
return commands
|
|
68
|
+
hooks = entry.get("hooks")
|
|
69
|
+
if isinstance(hooks, list):
|
|
70
|
+
for hook in hooks:
|
|
71
|
+
if not isinstance(hook, dict):
|
|
72
|
+
continue
|
|
73
|
+
cmd = hook.get("command", "") or hook.get("prompt", "")
|
|
74
|
+
if cmd:
|
|
75
|
+
commands.add(_command_identity(cmd))
|
|
76
|
+
return commands
|
|
77
|
+
cmd = entry.get("command", "") or entry.get("prompt", "")
|
|
78
|
+
if cmd:
|
|
79
|
+
commands.add(_command_identity(cmd))
|
|
80
|
+
return commands
|
|
81
|
+
|
|
82
|
+
for event, matchers in new_hooks.items():
|
|
83
|
+
if event not in merged:
|
|
84
|
+
merged[event] = matchers
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
existing_matchers = merged[event]
|
|
88
|
+
|
|
89
|
+
for new_matcher in matchers:
|
|
90
|
+
new_cmds = extract_commands(new_matcher)
|
|
91
|
+
|
|
92
|
+
overlap_indices = []
|
|
93
|
+
already_exists = False
|
|
94
|
+
for idx, em in enumerate(existing_matchers):
|
|
95
|
+
existing_cmds = extract_commands(em)
|
|
96
|
+
same_matcher = _matcher_identity(em) == _matcher_identity(new_matcher)
|
|
97
|
+
if new_cmds and same_matcher and (new_cmds & existing_cmds):
|
|
98
|
+
overlap_indices.append(idx)
|
|
99
|
+
continue
|
|
100
|
+
if not new_cmds and em == new_matcher:
|
|
101
|
+
already_exists = True
|
|
102
|
+
break
|
|
103
|
+
|
|
104
|
+
if overlap_indices:
|
|
105
|
+
first = overlap_indices[0]
|
|
106
|
+
existing_matchers[first] = new_matcher
|
|
107
|
+
for idx in reversed(overlap_indices[1:]):
|
|
108
|
+
existing_matchers.pop(idx)
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
if not already_exists:
|
|
112
|
+
existing_matchers.append(new_matcher)
|
|
113
|
+
|
|
114
|
+
return merged
|
|
115
|
+
|
|
116
|
+
def merge_permission_list(existing, new):
|
|
117
|
+
"""Union two permission lists, preserving order (existing first)."""
|
|
118
|
+
seen = set(existing)
|
|
119
|
+
merged = list(existing)
|
|
120
|
+
for item in new:
|
|
121
|
+
if item not in seen:
|
|
122
|
+
merged.append(item)
|
|
123
|
+
seen.add(item)
|
|
124
|
+
return merged
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def merge_mcp_servers(existing, new):
|
|
128
|
+
merged = dict(existing or {})
|
|
129
|
+
for name, config in (new or {}).items():
|
|
130
|
+
if name not in merged:
|
|
131
|
+
merged[name] = config
|
|
132
|
+
continue
|
|
133
|
+
existing_cfg = merged.get(name)
|
|
134
|
+
if isinstance(existing_cfg, dict) and isinstance(config, dict):
|
|
135
|
+
merged_cfg = dict(existing_cfg)
|
|
136
|
+
if "args" in config:
|
|
137
|
+
existing_args = merged_cfg.get("args", []) if isinstance(merged_cfg.get("args", []), list) else []
|
|
138
|
+
new_args = config.get("args", []) if isinstance(config.get("args", []), list) else []
|
|
139
|
+
merged_args = list(existing_args)
|
|
140
|
+
for arg in new_args:
|
|
141
|
+
if arg not in merged_args:
|
|
142
|
+
merged_args.append(arg)
|
|
143
|
+
merged_cfg["args"] = merged_args
|
|
144
|
+
if "env" in config and isinstance(config.get("env"), dict):
|
|
145
|
+
env = dict(merged_cfg.get("env", {})) if isinstance(merged_cfg.get("env"), dict) else {}
|
|
146
|
+
for key, value in config["env"].items():
|
|
147
|
+
env.setdefault(key, value)
|
|
148
|
+
merged_cfg["env"] = env
|
|
149
|
+
for key, value in config.items():
|
|
150
|
+
merged_cfg.setdefault(key, value)
|
|
151
|
+
merged[name] = merged_cfg
|
|
152
|
+
return merged
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def merge_omg_settings(existing, new):
|
|
156
|
+
"""Merge the OMG namespace while preserving explicit user feature overrides."""
|
|
157
|
+
merged = dict(existing or {})
|
|
158
|
+
incoming = dict(new or {})
|
|
159
|
+
|
|
160
|
+
for key, value in incoming.items():
|
|
161
|
+
if key == "features":
|
|
162
|
+
continue
|
|
163
|
+
if key == "_version":
|
|
164
|
+
merged[key] = value
|
|
165
|
+
continue
|
|
166
|
+
if key not in merged:
|
|
167
|
+
merged[key] = value
|
|
168
|
+
|
|
169
|
+
existing_features = existing.get("features", {}) if isinstance(existing, dict) else {}
|
|
170
|
+
incoming_features = incoming.get("features", {}) if isinstance(incoming, dict) else {}
|
|
171
|
+
if isinstance(existing_features, dict) or isinstance(incoming_features, dict):
|
|
172
|
+
merged_features = dict(incoming_features) if isinstance(incoming_features, dict) else {}
|
|
173
|
+
if isinstance(existing_features, dict):
|
|
174
|
+
merged_features.update(existing_features)
|
|
175
|
+
merged["features"] = merged_features
|
|
176
|
+
|
|
177
|
+
return merged
|
|
178
|
+
|
|
179
|
+
def merge_settings(existing, new):
|
|
180
|
+
"""
|
|
181
|
+
Merge strategy:
|
|
182
|
+
- hooks: append per-event
|
|
183
|
+
- permissions: union per-category
|
|
184
|
+
- everything else in existing: preserve
|
|
185
|
+
- $schema: use new if not present
|
|
186
|
+
"""
|
|
187
|
+
merged = dict(existing)
|
|
188
|
+
|
|
189
|
+
# Schema
|
|
190
|
+
if "$schema" not in merged and "$schema" in new:
|
|
191
|
+
merged["$schema"] = new["$schema"]
|
|
192
|
+
|
|
193
|
+
# Hooks
|
|
194
|
+
if "hooks" in new:
|
|
195
|
+
existing_hooks = merged.get("hooks", {})
|
|
196
|
+
merged["hooks"] = merge_hooks(existing_hooks, new["hooks"])
|
|
197
|
+
|
|
198
|
+
# Permissions
|
|
199
|
+
if "permissions" in new:
|
|
200
|
+
existing_perms = merged.get("permissions", {})
|
|
201
|
+
new_perms = new["permissions"]
|
|
202
|
+
merged_perms = dict(existing_perms)
|
|
203
|
+
managed_rules = set()
|
|
204
|
+
for category in ("allow", "deny", "ask"):
|
|
205
|
+
managed_rules.update(new_perms.get(category, []))
|
|
206
|
+
|
|
207
|
+
for category in ("allow", "deny", "ask"):
|
|
208
|
+
existing_list = existing_perms.get(category, [])
|
|
209
|
+
filtered_existing = [item for item in existing_list if item not in managed_rules]
|
|
210
|
+
new_list = new_perms.get(category, [])
|
|
211
|
+
merged_perms[category] = merge_permission_list(filtered_existing, new_list)
|
|
212
|
+
|
|
213
|
+
merged["permissions"] = merged_perms
|
|
214
|
+
|
|
215
|
+
if "mcpServers" in new:
|
|
216
|
+
merged["mcpServers"] = merge_mcp_servers(merged.get("mcpServers", {}), new.get("mcpServers", {}))
|
|
217
|
+
|
|
218
|
+
if "_omg" in new:
|
|
219
|
+
merged["_omg"] = merge_omg_settings(merged.get("_omg", {}), new.get("_omg", {}))
|
|
220
|
+
|
|
221
|
+
return merged
|
|
222
|
+
|
|
223
|
+
def main():
|
|
224
|
+
if len(sys.argv) < 3:
|
|
225
|
+
print("Usage: settings-merge.py <existing.json> <new.json> [--dry-run]")
|
|
226
|
+
sys.exit(1)
|
|
227
|
+
|
|
228
|
+
existing_path = sys.argv[1]
|
|
229
|
+
new_path = sys.argv[2]
|
|
230
|
+
dry_run = "--dry-run" in sys.argv
|
|
231
|
+
|
|
232
|
+
existing = load_json(existing_path)
|
|
233
|
+
new = load_json(new_path)
|
|
234
|
+
|
|
235
|
+
merged = merge_settings(existing, new)
|
|
236
|
+
|
|
237
|
+
if dry_run:
|
|
238
|
+
print(json.dumps(merged, indent=2))
|
|
239
|
+
print(f"\n--- DRY RUN ---", file=sys.stderr)
|
|
240
|
+
# Show what was added
|
|
241
|
+
new_hooks = set()
|
|
242
|
+
for event, matchers in new.get("hooks", {}).items():
|
|
243
|
+
for m in matchers:
|
|
244
|
+
for h in m.get("hooks", []):
|
|
245
|
+
cmd = h.get("command", "")
|
|
246
|
+
if cmd:
|
|
247
|
+
new_hooks.add(f" {event}: {os.path.basename(cmd.split()[-1])}")
|
|
248
|
+
if new_hooks:
|
|
249
|
+
print(f"Hooks to add:", file=sys.stderr)
|
|
250
|
+
for h in sorted(new_hooks):
|
|
251
|
+
print(h, file=sys.stderr)
|
|
252
|
+
|
|
253
|
+
for cat in ("allow", "deny", "ask"):
|
|
254
|
+
existing_set = set(existing.get("permissions", {}).get(cat, []))
|
|
255
|
+
new_set = set(new.get("permissions", {}).get(cat, []))
|
|
256
|
+
added = new_set - existing_set
|
|
257
|
+
if added:
|
|
258
|
+
print(f"permissions.{cat} to add: {len(added)} rules", file=sys.stderr)
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
# Backup existing
|
|
262
|
+
if os.path.exists(existing_path):
|
|
263
|
+
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
264
|
+
backup = f"{existing_path}.bak.{ts}"
|
|
265
|
+
shutil.copy2(existing_path, backup)
|
|
266
|
+
print(f"📦 Backed up: {backup}")
|
|
267
|
+
|
|
268
|
+
with open(existing_path, "w") as f:
|
|
269
|
+
json.dump(merged, f, indent=2)
|
|
270
|
+
f.write("\n")
|
|
271
|
+
|
|
272
|
+
print(f"✅ Merged into: {existing_path}")
|
|
273
|
+
|
|
274
|
+
# Summary
|
|
275
|
+
hook_events = list(merged.get("hooks", {}).keys())
|
|
276
|
+
allow_count = len(merged.get("permissions", {}).get("allow", []))
|
|
277
|
+
deny_count = len(merged.get("permissions", {}).get("deny", []))
|
|
278
|
+
ask_count = len(merged.get("permissions", {}).get("ask", []))
|
|
279
|
+
print(f" Hooks: {', '.join(hook_events)}")
|
|
280
|
+
print(f" Permissions: {allow_count} allow, {deny_count} deny, {ask_count} ask")
|
|
281
|
+
|
|
282
|
+
if __name__ == "__main__":
|
|
283
|
+
main()
|
|
@@ -7,11 +7,15 @@ TMP_DIR="${1:-$(mktemp -d)}"
|
|
|
7
7
|
echo "[verify-standalone] source: $ROOT_DIR"
|
|
8
8
|
echo "[verify-standalone] workdir: $TMP_DIR"
|
|
9
9
|
|
|
10
|
-
tar --exclude="./.omc" --exclude="
|
|
10
|
+
tar --exclude="./.omc" --exclude="./.pytest_cache" -cf - -C "$ROOT_DIR" . | (cd "$TMP_DIR" && tar -xf -)
|
|
11
11
|
|
|
12
12
|
cd "$TMP_DIR"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
python3 scripts/omg.py compat gate --max-bridge 0 --output .omg/evidence/omg-compat-gap.json
|
|
14
|
+
|
|
15
|
+
if command -v pyenv >/dev/null 2>&1 && pyenv prefix 3.12.7 >/dev/null 2>&1; then
|
|
16
|
+
PYENV_VERSION=3.12.7 python -m pytest -q
|
|
17
|
+
else
|
|
18
|
+
python3 -m pytest -q
|
|
19
|
+
fi
|
|
16
20
|
|
|
17
21
|
echo "[verify-standalone] passed"
|
package/settings.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
3
|
-
"_comment": "OMG
|
|
3
|
+
"_comment": "OMG 2.0.2 - project-level config with hook registrations, presets, and feature flags.",
|
|
4
4
|
"permissions": {
|
|
5
5
|
"allow": [
|
|
6
6
|
"Agent",
|
|
@@ -60,38 +60,30 @@
|
|
|
60
60
|
"Bash(yarn *)",
|
|
61
61
|
"Bash(pnpm *)",
|
|
62
62
|
"Bash(bun *)",
|
|
63
|
-
"Bash(bunx *)",
|
|
64
63
|
"Bash(node *)",
|
|
64
|
+
"Bash(python *)",
|
|
65
|
+
"Bash(python3 *)",
|
|
66
|
+
"Bash(pip *)",
|
|
67
|
+
"Bash(pip3 *)",
|
|
68
|
+
"Bash(uv *)",
|
|
69
|
+
"Bash(go *)",
|
|
70
|
+
"Bash(cargo *)",
|
|
71
|
+
"Bash(rustc *)",
|
|
65
72
|
"Bash(tsc *)",
|
|
66
73
|
"Bash(eslint *)",
|
|
67
74
|
"Bash(prettier *)",
|
|
75
|
+
"Bash(mypy *)",
|
|
76
|
+
"Bash(ruff *)",
|
|
77
|
+
"Bash(ruff format *)",
|
|
78
|
+
"Bash(pytest *)",
|
|
68
79
|
"Bash(jest *)",
|
|
69
80
|
"Bash(vitest *)",
|
|
70
81
|
"Bash(shellcheck *)",
|
|
71
|
-
"Bash(docker *)",
|
|
72
|
-
"Bash(docker-compose *)",
|
|
73
|
-
"Bash(kubectl get *)",
|
|
74
|
-
"Bash(kubectl describe *)",
|
|
75
|
-
"Bash(kubectl logs *)",
|
|
76
|
-
"Bash(kubectl apply *)",
|
|
77
|
-
"Bash(kubectl exec *)",
|
|
78
|
-
"Bash(kubectl edit *)",
|
|
79
|
-
"Bash(kubectl patch *)",
|
|
80
|
-
"Bash(kubectl rollout *)",
|
|
81
|
-
"Bash(kubectl scale *)",
|
|
82
|
-
"Bash(kubectl port-forward *)",
|
|
83
|
-
"Bash(kubectl config *)",
|
|
84
|
-
"Bash(kubectl top *)",
|
|
85
82
|
"Bash(terraform validate *)",
|
|
86
83
|
"Bash(terraform fmt *)",
|
|
87
84
|
"Bash(terraform plan *)",
|
|
88
85
|
"Bash(terraform show *)",
|
|
89
86
|
"Bash(terraform state list *)",
|
|
90
|
-
"Bash(curl *)",
|
|
91
|
-
"Bash(wget *)",
|
|
92
|
-
"Bash(ssh *)",
|
|
93
|
-
"Bash(scp *)",
|
|
94
|
-
"Bash(rsync *)",
|
|
95
87
|
"Bash(tmux *)",
|
|
96
88
|
"Bash(zip *)",
|
|
97
89
|
"Bash(unzip *)",
|
|
@@ -101,6 +93,11 @@
|
|
|
101
93
|
"Bash(gh *)"
|
|
102
94
|
],
|
|
103
95
|
"ask": [
|
|
96
|
+
"Bash(curl *)",
|
|
97
|
+
"Bash(wget *)",
|
|
98
|
+
"Bash(ssh *)",
|
|
99
|
+
"Bash(scp *)",
|
|
100
|
+
"Bash(rsync *)",
|
|
104
101
|
"Bash(rm *)",
|
|
105
102
|
"Bash(sudo *)",
|
|
106
103
|
"Bash(kill *)",
|
|
@@ -118,6 +115,20 @@
|
|
|
118
115
|
"Bash(terraform apply *)",
|
|
119
116
|
"Bash(terraform destroy *)",
|
|
120
117
|
"Bash(terraform import *)",
|
|
118
|
+
"Bash(docker *)",
|
|
119
|
+
"Bash(docker-compose *)",
|
|
120
|
+
"Bash(kubectl get *)",
|
|
121
|
+
"Bash(kubectl describe *)",
|
|
122
|
+
"Bash(kubectl logs *)",
|
|
123
|
+
"Bash(kubectl apply *)",
|
|
124
|
+
"Bash(kubectl exec *)",
|
|
125
|
+
"Bash(kubectl edit *)",
|
|
126
|
+
"Bash(kubectl patch *)",
|
|
127
|
+
"Bash(kubectl rollout *)",
|
|
128
|
+
"Bash(kubectl scale *)",
|
|
129
|
+
"Bash(kubectl port-forward *)",
|
|
130
|
+
"Bash(kubectl config *)",
|
|
131
|
+
"Bash(kubectl top *)",
|
|
121
132
|
"Bash(kubectl delete *)"
|
|
122
133
|
],
|
|
123
134
|
"deny": [
|
|
@@ -142,6 +153,7 @@
|
|
|
142
153
|
"Read(**/*.p12)",
|
|
143
154
|
"Read(**/*.pfx)",
|
|
144
155
|
"Read(**/.npmrc)",
|
|
156
|
+
"Read(**/.pypirc)",
|
|
145
157
|
"Read(**/.netrc)",
|
|
146
158
|
"Read(**/id_rsa*)",
|
|
147
159
|
"Read(**/id_ed25519*)",
|
|
@@ -154,22 +166,33 @@
|
|
|
154
166
|
]
|
|
155
167
|
},
|
|
156
168
|
"hooks": {
|
|
169
|
+
"SessionStart": [
|
|
170
|
+
{
|
|
171
|
+
"hooks": [
|
|
172
|
+
{
|
|
173
|
+
"type": "command",
|
|
174
|
+
"command": "python3 \"$HOME/.claude/hooks/session-start.py\"",
|
|
175
|
+
"timeout": 10
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
],
|
|
157
180
|
"SessionEnd": [
|
|
158
181
|
{
|
|
159
182
|
"hooks": [
|
|
160
183
|
{
|
|
161
184
|
"type": "command",
|
|
162
|
-
"command": "\"$HOME/.claude/hooks/session-end-capture.
|
|
185
|
+
"command": "python3 \"$HOME/.claude/hooks/session-end-capture.py\""
|
|
163
186
|
}
|
|
164
187
|
]
|
|
165
188
|
}
|
|
166
189
|
],
|
|
167
|
-
"
|
|
190
|
+
"PreToolUse": [
|
|
168
191
|
{
|
|
169
192
|
"hooks": [
|
|
170
193
|
{
|
|
171
194
|
"type": "command",
|
|
172
|
-
"command": "\"$HOME/.claude/hooks/
|
|
195
|
+
"command": "python3 \"$HOME/.claude/hooks/pre-tool-inject.py\""
|
|
173
196
|
}
|
|
174
197
|
]
|
|
175
198
|
}
|
|
@@ -180,7 +203,17 @@
|
|
|
180
203
|
"hooks": [
|
|
181
204
|
{
|
|
182
205
|
"type": "command",
|
|
183
|
-
"command": "\"$HOME/.claude/hooks/circuit-breaker.
|
|
206
|
+
"command": "python3 \"$HOME/.claude/hooks/circuit-breaker.py\"",
|
|
207
|
+
"timeout": 10
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
213
|
+
"hooks": [
|
|
214
|
+
{
|
|
215
|
+
"type": "command",
|
|
216
|
+
"command": "python3 \"$HOME/.claude/hooks/tool-ledger.py\"",
|
|
184
217
|
"timeout": 10
|
|
185
218
|
}
|
|
186
219
|
]
|
|
@@ -190,7 +223,16 @@
|
|
|
190
223
|
"hooks": [
|
|
191
224
|
{
|
|
192
225
|
"type": "command",
|
|
193
|
-
"command": "\"$HOME/.claude/hooks/
|
|
226
|
+
"command": "python3 \"$HOME/.claude/hooks/test_generator_hook.py\"",
|
|
227
|
+
"timeout": 10
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"hooks": [
|
|
233
|
+
{
|
|
234
|
+
"type": "command",
|
|
235
|
+
"command": "python3 \"$HOME/.claude/hooks/budget_governor.py\"",
|
|
194
236
|
"timeout": 10
|
|
195
237
|
}
|
|
196
238
|
]
|
|
@@ -201,21 +243,76 @@
|
|
|
201
243
|
"hooks": [
|
|
202
244
|
{
|
|
203
245
|
"type": "command",
|
|
204
|
-
"command": "\"$HOME/.claude/hooks/post-tool-failure.
|
|
246
|
+
"command": "python3 \"$HOME/.claude/hooks/post-tool-failure.py\""
|
|
205
247
|
}
|
|
206
248
|
]
|
|
207
249
|
}
|
|
208
250
|
],
|
|
209
251
|
"Stop": [
|
|
210
252
|
{
|
|
253
|
+
"matcher": "",
|
|
211
254
|
"hooks": [
|
|
212
255
|
{
|
|
213
256
|
"type": "command",
|
|
214
|
-
"command": "\"$HOME/.claude/hooks/
|
|
215
|
-
"timeout":
|
|
257
|
+
"command": "python3 \"$HOME/.claude/hooks/stop_dispatcher.py\"",
|
|
258
|
+
"timeout": 90
|
|
216
259
|
}
|
|
217
260
|
]
|
|
218
261
|
}
|
|
219
262
|
]
|
|
263
|
+
},
|
|
264
|
+
"_omg": {
|
|
265
|
+
"_version": "2.0.2",
|
|
266
|
+
"preset": "safe",
|
|
267
|
+
"default_mode": "ulw+ralph",
|
|
268
|
+
"vision_auto": true,
|
|
269
|
+
"false_fix_detection": true,
|
|
270
|
+
"cost_budget": {
|
|
271
|
+
"session_limit_usd": 5.0,
|
|
272
|
+
"thresholds": [
|
|
273
|
+
50,
|
|
274
|
+
80,
|
|
275
|
+
95
|
|
276
|
+
],
|
|
277
|
+
"pricing": {
|
|
278
|
+
"input_per_mtok": 3.0,
|
|
279
|
+
"output_per_mtok": 15.0
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
"context_budget": {
|
|
283
|
+
"session_start_max_chars": 2000,
|
|
284
|
+
"prompt_enhancer_max_chars": 800,
|
|
285
|
+
"prompt_enhancer_max_injections": 10,
|
|
286
|
+
"full_turns": 10,
|
|
287
|
+
"summarize_turns": 50,
|
|
288
|
+
"batch_size": 21
|
|
289
|
+
},
|
|
290
|
+
"credentials": {
|
|
291
|
+
"rotation_schedule_days": 90,
|
|
292
|
+
"expiry_warning_days": 14
|
|
293
|
+
},
|
|
294
|
+
"features": {
|
|
295
|
+
"memory": false,
|
|
296
|
+
"ralph_loop": true,
|
|
297
|
+
"planning_enforcement": true,
|
|
298
|
+
"compound_learning": false,
|
|
299
|
+
"simplifier": true,
|
|
300
|
+
"model_routing": true,
|
|
301
|
+
"agent_registry": true,
|
|
302
|
+
"circuit_breaker_v2": true,
|
|
303
|
+
"cognitive_modes": true,
|
|
304
|
+
"agent_routing": true,
|
|
305
|
+
"SETUP": false,
|
|
306
|
+
"SETUP_WIZARD": false,
|
|
307
|
+
"MEMORY_SERVER": false,
|
|
308
|
+
"MEMORY_AUTOSTART": false,
|
|
309
|
+
"COST_TRACKING": false,
|
|
310
|
+
"GIT_WORKFLOW": false,
|
|
311
|
+
"SESSION_ANALYTICS": false,
|
|
312
|
+
"TEST_GENERATION": false,
|
|
313
|
+
"DEP_HEALTH": false,
|
|
314
|
+
"CODEBASE_VIZ": false,
|
|
315
|
+
"CONTEXT_MANAGER": false
|
|
316
|
+
}
|
|
220
317
|
}
|
|
221
318
|
}
|
package/templates/profile.yaml
CHANGED
|
@@ -13,7 +13,7 @@ key_deps: []
|
|
|
13
13
|
|
|
14
14
|
conventions:
|
|
15
15
|
naming: "" # camelCase / snake_case / etc
|
|
16
|
-
test_cmd: "" #
|
|
16
|
+
test_cmd: "" # npm test / pytest / cargo test
|
|
17
17
|
lint_cmd: "" # eslint / ruff / clippy
|
|
18
18
|
format_cmd: "" # prettier / ruff format / rustfmt
|
|
19
19
|
|