@trac3er/oh-my-god 1.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 +36 -0
- package/.claude-plugin/plugin.json +23 -0
- package/.claude-plugin/scripts/install.sh +49 -0
- package/.claude-plugin/scripts/uninstall.sh +80 -0
- package/.claude-plugin/scripts/update.sh +84 -0
- package/.mcp.json +20 -0
- package/LICENSE +21 -0
- package/OMG-setup.sh +1093 -0
- package/README.md +335 -0
- package/THIRD_PARTY_NOTICES.md +24 -0
- package/UPSTREAM_DIFF.md +20 -0
- package/agents/__init__.py +1 -0
- package/agents/_model_roles.yaml +26 -0
- package/agents/designer.md +67 -0
- package/agents/explore.md +60 -0
- package/agents/model_roles.py +196 -0
- package/agents/omg-api-builder.md +23 -0
- package/agents/omg-architect-mode.md +43 -0
- package/agents/omg-architect.md +13 -0
- package/agents/omg-backend-engineer.md +43 -0
- package/agents/omg-critic.md +16 -0
- package/agents/omg-database-engineer.md +43 -0
- package/agents/omg-escalation-router.md +17 -0
- package/agents/omg-executor.md +12 -0
- package/agents/omg-frontend-designer.md +42 -0
- package/agents/omg-implement-mode.md +50 -0
- package/agents/omg-infra-engineer.md +43 -0
- package/agents/omg-qa-tester.md +16 -0
- package/agents/omg-research-mode.md +43 -0
- package/agents/omg-security-auditor.md +43 -0
- package/agents/omg-testing-engineer.md +43 -0
- package/agents/plan.md +80 -0
- package/agents/quick_task.md +64 -0
- package/agents/reviewer.md +83 -0
- package/agents/task.md +71 -0
- package/commands/OMG:ccg.md +22 -0
- package/commands/OMG:compat.md +57 -0
- package/commands/OMG:crazy.md +125 -0
- package/commands/OMG:domain-init.md +11 -0
- package/commands/OMG:escalate.md +52 -0
- package/commands/OMG:health-check.md +45 -0
- package/commands/OMG:init.md +134 -0
- package/commands/OMG:mode.md +44 -0
- package/commands/OMG:project-init.md +11 -0
- package/commands/OMG:ralph-start.md +43 -0
- package/commands/OMG:ralph-stop.md +23 -0
- package/commands/OMG:teams.md +39 -0
- package/commands/ai-commit.md +113 -0
- package/commands/ccg.md +9 -0
- package/commands/create-agent.md +183 -0
- package/commands/omc-teams.md +9 -0
- package/commands/session-branch.md +85 -0
- package/commands/session-fork.md +53 -0
- package/commands/session-merge.md +134 -0
- package/commands/theme.md +44 -0
- package/config/lsp_languages.yaml +324 -0
- package/config/themes/catppuccin-frappe.yaml +14 -0
- package/config/themes/catppuccin-latte.yaml +14 -0
- package/config/themes/catppuccin-macchiato.yaml +14 -0
- package/config/themes/catppuccin-mocha.yaml +14 -0
- package/config/themes/dracula.yaml +14 -0
- package/config/themes/gruvbox-dark.yaml +14 -0
- package/config/themes/nord.yaml +14 -0
- package/config/themes/one-dark.yaml +14 -0
- package/config/themes/solarized-dark.yaml +14 -0
- package/config/themes/tokyo-night.yaml +14 -0
- package/control_plane/__init__.py +2 -0
- package/control_plane/openapi.yaml +109 -0
- package/control_plane/server.py +107 -0
- package/control_plane/service.py +148 -0
- package/crates/omg-natives/Cargo.toml +17 -0
- package/crates/omg-natives/src/clipboard.rs +5 -0
- package/crates/omg-natives/src/glob.rs +15 -0
- package/crates/omg-natives/src/grep.rs +15 -0
- package/crates/omg-natives/src/highlight.rs +15 -0
- package/crates/omg-natives/src/html.rs +14 -0
- package/crates/omg-natives/src/image.rs +5 -0
- package/crates/omg-natives/src/keys.rs +5 -0
- package/crates/omg-natives/src/lib.rs +36 -0
- package/crates/omg-natives/src/prof.rs +5 -0
- package/crates/omg-natives/src/ps.rs +5 -0
- package/crates/omg-natives/src/shell.rs +5 -0
- package/crates/omg-natives/src/task.rs +5 -0
- package/crates/omg-natives/src/text.rs +14 -0
- package/hooks/_agent_registry.py +421 -0
- package/hooks/_budget.py +31 -0
- package/hooks/_common.py +476 -0
- package/hooks/_learnings.py +126 -0
- package/hooks/_memory.py +103 -0
- package/hooks/circuit-breaker.py +270 -0
- package/hooks/config-guard.py +163 -0
- package/hooks/context_pressure.py +53 -0
- package/hooks/credential_store.py +801 -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 +310 -0
- package/hooks/post-tool-failure.py +19 -0
- package/hooks/post-write.py +199 -0
- package/hooks/pre-compact.py +204 -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/secret-guard.py +47 -0
- package/hooks/session-end-capture.py +137 -0
- package/hooks/session-start.py +275 -0
- package/hooks/shadow_manager.py +297 -0
- package/hooks/state_migration.py +209 -0
- package/hooks/stop-gate.py +7 -0
- package/hooks/stop_dispatcher.py +929 -0
- package/hooks/test-validator.py +138 -0
- package/hooks/todo-state-tracker.py +114 -0
- package/hooks/tool-ledger.py +126 -0
- package/hooks/trust_review.py +524 -0
- package/install.sh +9 -0
- package/omg_natives/__init__.py +186 -0
- package/omg_natives/_bindings.py +165 -0
- package/omg_natives/clipboard.py +36 -0
- package/omg_natives/glob.py +42 -0
- package/omg_natives/grep.py +61 -0
- package/omg_natives/highlight.py +54 -0
- package/omg_natives/html.py +157 -0
- package/omg_natives/image.py +51 -0
- package/omg_natives/keys.py +46 -0
- package/omg_natives/prof.py +39 -0
- package/omg_natives/ps.py +93 -0
- package/omg_natives/shell.py +58 -0
- package/omg_natives/task.py +41 -0
- package/omg_natives/text.py +50 -0
- package/package.json +26 -0
- package/plugins/README.md +82 -0
- package/plugins/advanced/commands/OMG:code-review.md +114 -0
- package/plugins/advanced/commands/OMG:deep-plan.md +221 -0
- package/plugins/advanced/commands/OMG:handoff.md +115 -0
- package/plugins/advanced/commands/OMG:learn.md +110 -0
- package/plugins/advanced/commands/OMG:maintainer.md +31 -0
- package/plugins/advanced/commands/OMG:ralph-start.md +43 -0
- package/plugins/advanced/commands/OMG:ralph-stop.md +23 -0
- package/plugins/advanced/commands/OMG:security-review.md +119 -0
- package/plugins/advanced/commands/OMG:sequential-thinking.md +20 -0
- package/plugins/advanced/commands/OMG:ship.md +46 -0
- package/plugins/advanced/plugin.json +96 -0
- package/plugins/core/plugin.json +82 -0
- package/pytest.ini +5 -0
- package/registry/__init__.py +1 -0
- package/registry/verify_artifact.py +90 -0
- package/rules/contextual/architect-mode.md +9 -0
- package/rules/contextual/big-picture.md +20 -0
- package/rules/contextual/code-hygiene.md +26 -0
- package/rules/contextual/context-management.md +19 -0
- package/rules/contextual/context-minimization.md +32 -0
- package/rules/contextual/ddd-sdd.md +28 -0
- package/rules/contextual/dependency-safety.md +16 -0
- package/rules/contextual/doc-check.md +13 -0
- package/rules/contextual/implement-mode.md +9 -0
- package/rules/contextual/infra-safety.md +14 -0
- package/rules/contextual/outside-in.md +13 -0
- package/rules/contextual/persistent-mode.md +24 -0
- package/rules/contextual/research-mode.md +9 -0
- package/rules/contextual/security-domains.md +25 -0
- package/rules/contextual/vision-detection.md +27 -0
- package/rules/contextual/web-search.md +25 -0
- package/rules/contextual/write-verify.md +23 -0
- package/rules/core/00-truth.md +20 -0
- package/rules/core/01-surgical.md +19 -0
- package/rules/core/02-circuit-breaker.md +22 -0
- package/rules/core/03-ensemble.md +28 -0
- package/rules/core/04-testing.md +30 -0
- 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/business_workflow.py +220 -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/omc_compat.py +7 -0
- package/runtime/omc_contract_snapshot.json +916 -0
- package/runtime/omg_compat_contract_snapshot.json +916 -0
- package/runtime/subagent_dispatcher.py +362 -0
- package/runtime/team_router.py +838 -0
- package/scripts/check-omc-contract-snapshot.py +12 -0
- package/scripts/check-omg-compat-contract-snapshot.py +137 -0
- package/scripts/check-omg-standalone-clean.py +102 -0
- package/scripts/legacy_to_omg_migrate.py +29 -0
- package/scripts/migrate-omc.py +464 -0
- package/scripts/omc_to_omg_migrate.py +12 -0
- package/scripts/omg.py +493 -0
- package/scripts/settings-merge.py +224 -0
- package/scripts/verify-no-omc.sh +5 -0
- package/scripts/verify-standalone.sh +21 -0
- package/templates/idea.yml +30 -0
- package/templates/policy.yaml +15 -0
- package/templates/profile.yaml +25 -0
- package/templates/runtime.yaml +12 -0
- package/templates/working-memory.md +17 -0
- 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 +268 -0
- package/tools/commit_splitter.py +361 -0
- package/tools/config_discovery.py +151 -0
- package/tools/config_merger.py +449 -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/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
|
@@ -0,0 +1,224 @@
|
|
|
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 shutil
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
|
|
20
|
+
def load_json(path):
|
|
21
|
+
if not os.path.exists(path):
|
|
22
|
+
return {}
|
|
23
|
+
try:
|
|
24
|
+
with open(path, "r") as f:
|
|
25
|
+
return json.load(f)
|
|
26
|
+
except json.JSONDecodeError as e:
|
|
27
|
+
print(f"ERROR: {path} contains invalid JSON (line {e.lineno}, col {e.colno}):", file=sys.stderr)
|
|
28
|
+
print(f" {e.msg}", file=sys.stderr)
|
|
29
|
+
print(f" Fix the file manually or delete it to start fresh.", file=sys.stderr)
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
except PermissionError:
|
|
32
|
+
print(f"ERROR: Cannot read {path} — permission denied.", file=sys.stderr)
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
print(f"ERROR: Failed to read {path}: {e}", file=sys.stderr)
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
def merge_hooks(existing_hooks, new_hooks):
|
|
39
|
+
"""Append new hook matchers to existing, avoiding duplicates."""
|
|
40
|
+
merged = dict(existing_hooks) # shallow copy
|
|
41
|
+
|
|
42
|
+
def extract_commands(entry):
|
|
43
|
+
commands = set()
|
|
44
|
+
if not isinstance(entry, dict):
|
|
45
|
+
return commands
|
|
46
|
+
hooks = entry.get("hooks")
|
|
47
|
+
if isinstance(hooks, list):
|
|
48
|
+
for hook in hooks:
|
|
49
|
+
if not isinstance(hook, dict):
|
|
50
|
+
continue
|
|
51
|
+
cmd = hook.get("command", "") or hook.get("prompt", "")
|
|
52
|
+
if cmd:
|
|
53
|
+
commands.add(cmd)
|
|
54
|
+
return commands
|
|
55
|
+
cmd = entry.get("command", "") or entry.get("prompt", "")
|
|
56
|
+
if cmd:
|
|
57
|
+
commands.add(cmd)
|
|
58
|
+
return commands
|
|
59
|
+
|
|
60
|
+
for event, matchers in new_hooks.items():
|
|
61
|
+
if event not in merged:
|
|
62
|
+
merged[event] = matchers
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
existing_matchers = merged[event]
|
|
66
|
+
|
|
67
|
+
for new_matcher in matchers:
|
|
68
|
+
new_cmds = extract_commands(new_matcher)
|
|
69
|
+
|
|
70
|
+
# Check if this exact matcher+command combo already exists
|
|
71
|
+
already_exists = False
|
|
72
|
+
for em in existing_matchers:
|
|
73
|
+
existing_cmds = extract_commands(em)
|
|
74
|
+
same_matcher = em.get("matcher") == new_matcher.get("matcher")
|
|
75
|
+
if new_cmds and same_matcher and (new_cmds & existing_cmds):
|
|
76
|
+
already_exists = True
|
|
77
|
+
break
|
|
78
|
+
if not new_cmds and em == new_matcher:
|
|
79
|
+
already_exists = True
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
if not already_exists:
|
|
83
|
+
existing_matchers.append(new_matcher)
|
|
84
|
+
|
|
85
|
+
return merged
|
|
86
|
+
|
|
87
|
+
def merge_permission_list(existing, new):
|
|
88
|
+
"""Union two permission lists, preserving order (existing first)."""
|
|
89
|
+
seen = set(existing)
|
|
90
|
+
merged = list(existing)
|
|
91
|
+
for item in new:
|
|
92
|
+
if item not in seen:
|
|
93
|
+
merged.append(item)
|
|
94
|
+
seen.add(item)
|
|
95
|
+
return merged
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def merge_mcp_servers(existing, new):
|
|
99
|
+
merged = dict(existing or {})
|
|
100
|
+
for name, config in (new or {}).items():
|
|
101
|
+
if name not in merged:
|
|
102
|
+
merged[name] = config
|
|
103
|
+
continue
|
|
104
|
+
existing_cfg = merged.get(name)
|
|
105
|
+
if isinstance(existing_cfg, dict) and isinstance(config, dict):
|
|
106
|
+
merged_cfg = dict(existing_cfg)
|
|
107
|
+
if "args" in config:
|
|
108
|
+
existing_args = merged_cfg.get("args", []) if isinstance(merged_cfg.get("args", []), list) else []
|
|
109
|
+
new_args = config.get("args", []) if isinstance(config.get("args", []), list) else []
|
|
110
|
+
merged_args = list(existing_args)
|
|
111
|
+
for arg in new_args:
|
|
112
|
+
if arg not in merged_args:
|
|
113
|
+
merged_args.append(arg)
|
|
114
|
+
merged_cfg["args"] = merged_args
|
|
115
|
+
if "env" in config and isinstance(config.get("env"), dict):
|
|
116
|
+
env = dict(merged_cfg.get("env", {})) if isinstance(merged_cfg.get("env"), dict) else {}
|
|
117
|
+
for key, value in config["env"].items():
|
|
118
|
+
env.setdefault(key, value)
|
|
119
|
+
merged_cfg["env"] = env
|
|
120
|
+
for key, value in config.items():
|
|
121
|
+
merged_cfg.setdefault(key, value)
|
|
122
|
+
merged[name] = merged_cfg
|
|
123
|
+
return merged
|
|
124
|
+
|
|
125
|
+
def merge_settings(existing, new):
|
|
126
|
+
"""
|
|
127
|
+
Merge strategy:
|
|
128
|
+
- hooks: append per-event
|
|
129
|
+
- permissions: union per-category
|
|
130
|
+
- everything else in existing: preserve
|
|
131
|
+
- $schema: use new if not present
|
|
132
|
+
"""
|
|
133
|
+
merged = dict(existing)
|
|
134
|
+
|
|
135
|
+
# Schema
|
|
136
|
+
if "$schema" not in merged and "$schema" in new:
|
|
137
|
+
merged["$schema"] = new["$schema"]
|
|
138
|
+
|
|
139
|
+
# Hooks
|
|
140
|
+
if "hooks" in new:
|
|
141
|
+
existing_hooks = merged.get("hooks", {})
|
|
142
|
+
merged["hooks"] = merge_hooks(existing_hooks, new["hooks"])
|
|
143
|
+
|
|
144
|
+
# Permissions
|
|
145
|
+
if "permissions" in new:
|
|
146
|
+
existing_perms = merged.get("permissions", {})
|
|
147
|
+
new_perms = new["permissions"]
|
|
148
|
+
merged_perms = dict(existing_perms)
|
|
149
|
+
|
|
150
|
+
for category in ("allow", "deny", "ask"):
|
|
151
|
+
if category in new_perms:
|
|
152
|
+
existing_list = existing_perms.get(category, [])
|
|
153
|
+
merged_perms[category] = merge_permission_list(
|
|
154
|
+
existing_list, new_perms[category]
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
merged["permissions"] = merged_perms
|
|
158
|
+
|
|
159
|
+
if "mcpServers" in new:
|
|
160
|
+
merged["mcpServers"] = merge_mcp_servers(merged.get("mcpServers", {}), new.get("mcpServers", {}))
|
|
161
|
+
|
|
162
|
+
return merged
|
|
163
|
+
|
|
164
|
+
def main():
|
|
165
|
+
if len(sys.argv) < 3:
|
|
166
|
+
print("Usage: settings-merge.py <existing.json> <new.json> [--dry-run]")
|
|
167
|
+
sys.exit(1)
|
|
168
|
+
|
|
169
|
+
existing_path = sys.argv[1]
|
|
170
|
+
new_path = sys.argv[2]
|
|
171
|
+
dry_run = "--dry-run" in sys.argv
|
|
172
|
+
|
|
173
|
+
existing = load_json(existing_path)
|
|
174
|
+
new = load_json(new_path)
|
|
175
|
+
|
|
176
|
+
merged = merge_settings(existing, new)
|
|
177
|
+
|
|
178
|
+
if dry_run:
|
|
179
|
+
print(json.dumps(merged, indent=2))
|
|
180
|
+
print(f"\n--- DRY RUN ---", file=sys.stderr)
|
|
181
|
+
# Show what was added
|
|
182
|
+
new_hooks = set()
|
|
183
|
+
for event, matchers in new.get("hooks", {}).items():
|
|
184
|
+
for m in matchers:
|
|
185
|
+
for h in m.get("hooks", []):
|
|
186
|
+
cmd = h.get("command", "")
|
|
187
|
+
if cmd:
|
|
188
|
+
new_hooks.add(f" {event}: {os.path.basename(cmd.split()[-1])}")
|
|
189
|
+
if new_hooks:
|
|
190
|
+
print(f"Hooks to add:", file=sys.stderr)
|
|
191
|
+
for h in sorted(new_hooks):
|
|
192
|
+
print(h, file=sys.stderr)
|
|
193
|
+
|
|
194
|
+
for cat in ("allow", "deny", "ask"):
|
|
195
|
+
existing_set = set(existing.get("permissions", {}).get(cat, []))
|
|
196
|
+
new_set = set(new.get("permissions", {}).get(cat, []))
|
|
197
|
+
added = new_set - existing_set
|
|
198
|
+
if added:
|
|
199
|
+
print(f"permissions.{cat} to add: {len(added)} rules", file=sys.stderr)
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
# Backup existing
|
|
203
|
+
if os.path.exists(existing_path):
|
|
204
|
+
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
205
|
+
backup = f"{existing_path}.bak.{ts}"
|
|
206
|
+
shutil.copy2(existing_path, backup)
|
|
207
|
+
print(f"📦 Backed up: {backup}")
|
|
208
|
+
|
|
209
|
+
with open(existing_path, "w") as f:
|
|
210
|
+
json.dump(merged, f, indent=2)
|
|
211
|
+
f.write("\n")
|
|
212
|
+
|
|
213
|
+
print(f"✅ Merged into: {existing_path}")
|
|
214
|
+
|
|
215
|
+
# Summary
|
|
216
|
+
hook_events = list(merged.get("hooks", {}).keys())
|
|
217
|
+
allow_count = len(merged.get("permissions", {}).get("allow", []))
|
|
218
|
+
deny_count = len(merged.get("permissions", {}).get("deny", []))
|
|
219
|
+
ask_count = len(merged.get("permissions", {}).get("ask", []))
|
|
220
|
+
print(f" Hooks: {', '.join(hook_events)}")
|
|
221
|
+
print(f" Permissions: {allow_count} allow, {deny_count} deny, {ask_count} ask")
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
TMP_DIR="${1:-$(mktemp -d)}"
|
|
6
|
+
|
|
7
|
+
echo "[verify-standalone] source: $ROOT_DIR"
|
|
8
|
+
echo "[verify-standalone] workdir: $TMP_DIR"
|
|
9
|
+
|
|
10
|
+
tar --exclude="./.omc" --exclude="./.pytest_cache" -cf - -C "$ROOT_DIR" . | (cd "$TMP_DIR" && tar -xf -)
|
|
11
|
+
|
|
12
|
+
cd "$TMP_DIR"
|
|
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
|
|
20
|
+
|
|
21
|
+
echo "[verify-standalone] passed"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# OMG Idea Contract
|
|
2
|
+
# Single source of intent for Idea -> Evidence -> PR automation.
|
|
3
|
+
|
|
4
|
+
goal: ""
|
|
5
|
+
constraints:
|
|
6
|
+
- ""
|
|
7
|
+
acceptance:
|
|
8
|
+
- ""
|
|
9
|
+
risk:
|
|
10
|
+
security:
|
|
11
|
+
- ""
|
|
12
|
+
performance:
|
|
13
|
+
- ""
|
|
14
|
+
compatibility:
|
|
15
|
+
- ""
|
|
16
|
+
evidence_required:
|
|
17
|
+
tests:
|
|
18
|
+
- ""
|
|
19
|
+
security_scans:
|
|
20
|
+
- ""
|
|
21
|
+
reproducibility:
|
|
22
|
+
- ""
|
|
23
|
+
artifacts:
|
|
24
|
+
- "diff"
|
|
25
|
+
- "summary"
|
|
26
|
+
metadata:
|
|
27
|
+
owner: ""
|
|
28
|
+
priority: "medium" # low | medium | high | critical
|
|
29
|
+
target_branch: ""
|
|
30
|
+
created_at: ""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
version: omg-v1
|
|
2
|
+
mode: warn_and_run
|
|
3
|
+
critical_always_block: true
|
|
4
|
+
require_evidence_pack: true
|
|
5
|
+
trust:
|
|
6
|
+
distrust_paths:
|
|
7
|
+
- ".claude/"
|
|
8
|
+
- ".omc/"
|
|
9
|
+
- ".omg/"
|
|
10
|
+
review_mcp_changes: true
|
|
11
|
+
review_hook_changes: true
|
|
12
|
+
supply_chain:
|
|
13
|
+
require_signature: false
|
|
14
|
+
require_checksum: false
|
|
15
|
+
enforce_sandbox: true
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# .omc/profile.yaml — Project identity (injected every session)
|
|
2
|
+
# Keep under 20 lines. This is a quick-reference, not documentation.
|
|
3
|
+
|
|
4
|
+
name: ""
|
|
5
|
+
description: ""
|
|
6
|
+
repo: ""
|
|
7
|
+
|
|
8
|
+
language: ""
|
|
9
|
+
framework: ""
|
|
10
|
+
database: ""
|
|
11
|
+
infra: ""
|
|
12
|
+
key_deps: []
|
|
13
|
+
|
|
14
|
+
conventions:
|
|
15
|
+
naming: "" # camelCase / snake_case / etc
|
|
16
|
+
test_cmd: "" # npm test / pytest / cargo test
|
|
17
|
+
lint_cmd: "" # eslint / ruff / clippy
|
|
18
|
+
format_cmd: "" # prettier / ruff format / rustfmt
|
|
19
|
+
|
|
20
|
+
ai_behavior:
|
|
21
|
+
communication: "direct, bilingual OK"
|
|
22
|
+
locale_hint: "auto" # auto | ko | en | ja | zh — helps keyword detection
|
|
23
|
+
when_stuck: "Ask user after 2 failed attempts, or /escalate"
|
|
24
|
+
testing: "User-journey focused. No boilerplate."
|
|
25
|
+
code_style: "Match existing patterns. Don't introduce new frameworks."
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Working Memory
|
|
2
|
+
Last updated: [date]
|
|
3
|
+
|
|
4
|
+
## Current Gomg
|
|
5
|
+
[What we're trying to achieve right now]
|
|
6
|
+
|
|
7
|
+
## Progress
|
|
8
|
+
[N/M steps] | Branch: [name]
|
|
9
|
+
|
|
10
|
+
## Active Decisions
|
|
11
|
+
- [Decision]: [rationale] (date)
|
|
12
|
+
|
|
13
|
+
## Blockers
|
|
14
|
+
- [None]
|
|
15
|
+
|
|
16
|
+
## Don't Repeat (failed approaches)
|
|
17
|
+
- [None yet]
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Browser Consent Manager for OMG
|
|
4
|
+
|
|
5
|
+
Manages explicit user consent for browser stealth mode. Provides a ToS warning
|
|
6
|
+
display, consent recording with timestamps, and persistent consent state.
|
|
7
|
+
|
|
8
|
+
Consent file: .omg/state/browser_consent.json
|
|
9
|
+
Format: {"consented": true/false, "acknowledged_at": "ISO timestamp", "version": "1.0"}
|
|
10
|
+
|
|
11
|
+
IMPORTANT:
|
|
12
|
+
- show_warning() returns text; caller decides how to display
|
|
13
|
+
- record_consent() is the ONLY function that writes consent state
|
|
14
|
+
- No consent is stored without explicit user action
|
|
15
|
+
- import alone does NOT trigger any side effects
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
|
+
from typing import Any, Dict, Optional
|
|
23
|
+
|
|
24
|
+
# --- Consent version ---
|
|
25
|
+
CONSENT_VERSION = "1.0"
|
|
26
|
+
|
|
27
|
+
# --- Consent file relative path ---
|
|
28
|
+
CONSENT_REL_PATH = os.path.join(".omg", "state", "browser_consent.json")
|
|
29
|
+
|
|
30
|
+
# --- Warning text ---
|
|
31
|
+
_WARNING_TEXT = """\
|
|
32
|
+
╔══════════════════════════════════════════════════════════════════╗
|
|
33
|
+
║ ⚠ WARNING ⚠ ║
|
|
34
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
35
|
+
║ ║
|
|
36
|
+
║ You are about to enable Browser Stealth Mode. ║
|
|
37
|
+
║ ║
|
|
38
|
+
║ This feature modifies browser fingerprints and injects ║
|
|
39
|
+
║ JavaScript to evade bot-detection systems. Using stealth ║
|
|
40
|
+
║ plugins may violate the Terms of Service of websites you ║
|
|
41
|
+
║ visit. ║
|
|
42
|
+
║ ║
|
|
43
|
+
║ By granting explicit consent, you acknowledge that: ║
|
|
44
|
+
║ ║
|
|
45
|
+
║ 1. You understand the stealth plugins alter browser behavior ║
|
|
46
|
+
║ 2. You accept responsibility for compliance with applicable ║
|
|
47
|
+
║ Terms of Service and laws ║
|
|
48
|
+
║ 3. OMG provides these tools as-is, without warranty ║
|
|
49
|
+
║ 4. You may revoke consent at any time ║
|
|
50
|
+
║ ║
|
|
51
|
+
║ Consent is required before any stealth plugin can be applied. ║
|
|
52
|
+
║ ║
|
|
53
|
+
╚══════════════════════════════════════════════════════════════════╝"""
|
|
54
|
+
|
|
55
|
+
# --- Lazy imports for hooks/_common.py ---
|
|
56
|
+
|
|
57
|
+
_atomic_json_write = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _ensure_imports():
|
|
61
|
+
"""Lazy import atomic_json_write from hooks/_common.py."""
|
|
62
|
+
global _atomic_json_write
|
|
63
|
+
if _atomic_json_write is not None:
|
|
64
|
+
return
|
|
65
|
+
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
66
|
+
hooks_dir = os.path.join(repo_root, "hooks")
|
|
67
|
+
if hooks_dir not in sys.path:
|
|
68
|
+
sys.path.insert(0, hooks_dir)
|
|
69
|
+
try:
|
|
70
|
+
from _common import atomic_json_write as _ajw
|
|
71
|
+
_atomic_json_write = _ajw
|
|
72
|
+
except ImportError:
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _write_consent_file(path: str, data: Dict[str, Any]) -> bool:
|
|
77
|
+
"""Write consent data using atomic_json_write or fallback.
|
|
78
|
+
|
|
79
|
+
Returns True on success, False on failure.
|
|
80
|
+
"""
|
|
81
|
+
_ensure_imports()
|
|
82
|
+
if _atomic_json_write is not None:
|
|
83
|
+
try:
|
|
84
|
+
_atomic_json_write(path, data)
|
|
85
|
+
return True
|
|
86
|
+
except Exception:
|
|
87
|
+
return False
|
|
88
|
+
# Fallback: direct write with makedirs
|
|
89
|
+
try:
|
|
90
|
+
parent = os.path.dirname(path)
|
|
91
|
+
if parent:
|
|
92
|
+
os.makedirs(parent, exist_ok=True)
|
|
93
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
94
|
+
json.dump(data, f, separators=(",", ":"))
|
|
95
|
+
return True
|
|
96
|
+
except Exception:
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _read_consent_file(path: str) -> Optional[Dict[str, Any]]:
|
|
101
|
+
"""Read and parse consent file. Returns None on any failure."""
|
|
102
|
+
try:
|
|
103
|
+
if not os.path.exists(path):
|
|
104
|
+
return None
|
|
105
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
106
|
+
data = json.load(f)
|
|
107
|
+
if isinstance(data, dict):
|
|
108
|
+
return data
|
|
109
|
+
return None
|
|
110
|
+
except (json.JSONDecodeError, OSError, TypeError):
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# =============================================================================
|
|
115
|
+
# ConsentManager — manages browser stealth consent lifecycle
|
|
116
|
+
# =============================================================================
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ConsentManager:
|
|
120
|
+
"""Manages user consent for browser stealth mode.
|
|
121
|
+
|
|
122
|
+
All consent state is persisted to .omg/state/browser_consent.json.
|
|
123
|
+
No consent is recorded without explicit calls to record_consent().
|
|
124
|
+
|
|
125
|
+
Attributes:
|
|
126
|
+
project_dir: Root directory containing the .omg/ state folder.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
def __init__(self, project_dir: Optional[str] = None):
|
|
130
|
+
self.project_dir = project_dir or os.environ.get(
|
|
131
|
+
"CLAUDE_PROJECT_DIR", os.getcwd()
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def consent_path(self) -> str:
|
|
136
|
+
"""Full path to the consent file."""
|
|
137
|
+
return os.path.join(self.project_dir, CONSENT_REL_PATH)
|
|
138
|
+
|
|
139
|
+
def show_warning(self) -> str:
|
|
140
|
+
"""Return the ToS warning text for browser stealth mode.
|
|
141
|
+
|
|
142
|
+
Returns the multi-line warning string. Does NOT print it —
|
|
143
|
+
the caller decides how to display the warning.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Multi-line warning string containing Terms of Service notice.
|
|
147
|
+
"""
|
|
148
|
+
return _WARNING_TEXT
|
|
149
|
+
|
|
150
|
+
def record_consent(self, acknowledged: bool = True) -> bool:
|
|
151
|
+
"""Record the user's consent decision.
|
|
152
|
+
|
|
153
|
+
Saves consent state to .omg/state/browser_consent.json with
|
|
154
|
+
a timestamp and version identifier.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
acknowledged: True if user explicitly consented, False otherwise.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
True if consent was successfully written, False on failure.
|
|
161
|
+
"""
|
|
162
|
+
data = {
|
|
163
|
+
"consented": acknowledged,
|
|
164
|
+
"acknowledged_at": datetime.now(timezone.utc).isoformat(),
|
|
165
|
+
"version": CONSENT_VERSION,
|
|
166
|
+
}
|
|
167
|
+
return _write_consent_file(self.consent_path, data)
|
|
168
|
+
|
|
169
|
+
def is_consented(self) -> bool:
|
|
170
|
+
"""Check if user has given explicit consent for stealth plugins.
|
|
171
|
+
|
|
172
|
+
Reads .omg/state/browser_consent.json and checks for
|
|
173
|
+
``{"consented": true}``.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
True if consent file exists and consented is True, False otherwise.
|
|
177
|
+
"""
|
|
178
|
+
data = _read_consent_file(self.consent_path)
|
|
179
|
+
if data is None:
|
|
180
|
+
return False
|
|
181
|
+
return data.get("consented", False) is True
|
|
182
|
+
|
|
183
|
+
def revoke_consent(self) -> bool:
|
|
184
|
+
"""Revoke previously granted consent.
|
|
185
|
+
|
|
186
|
+
Sets consented to False in the consent file while preserving
|
|
187
|
+
the timestamp of revocation.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
True if revocation was successfully written, False on failure.
|
|
191
|
+
"""
|
|
192
|
+
data = {
|
|
193
|
+
"consented": False,
|
|
194
|
+
"acknowledged_at": datetime.now(timezone.utc).isoformat(),
|
|
195
|
+
"version": CONSENT_VERSION,
|
|
196
|
+
}
|
|
197
|
+
return _write_consent_file(self.consent_path, data)
|
|
198
|
+
|
|
199
|
+
def get_consent_status(self) -> Dict[str, Any]:
|
|
200
|
+
"""Return the full consent record.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Full consent dict if file exists and is valid,
|
|
204
|
+
otherwise {"consented": False}.
|
|
205
|
+
"""
|
|
206
|
+
data = _read_consent_file(self.consent_path)
|
|
207
|
+
if data is None:
|
|
208
|
+
return {"consented": False}
|
|
209
|
+
return data
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# =============================================================================
|
|
213
|
+
# Module-level convenience function
|
|
214
|
+
# =============================================================================
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def is_consented(project_dir: Optional[str] = None) -> bool:
|
|
218
|
+
"""Module-level convenience: check if consent has been granted.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
project_dir: Root directory (auto-detected if None).
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
True if consented, False otherwise.
|
|
225
|
+
"""
|
|
226
|
+
return ConsentManager(project_dir=project_dir).is_consented()
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# =============================================================================
|
|
230
|
+
# CLI Interface
|
|
231
|
+
# =============================================================================
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _cli_main():
|
|
235
|
+
"""CLI entry point for browser_consent.py."""
|
|
236
|
+
import argparse
|
|
237
|
+
|
|
238
|
+
parser = argparse.ArgumentParser(
|
|
239
|
+
description="OMG Browser Consent — manage consent for browser stealth mode",
|
|
240
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
241
|
+
)
|
|
242
|
+
parser.add_argument(
|
|
243
|
+
"--status", action="store_true",
|
|
244
|
+
help="Show current consent status",
|
|
245
|
+
)
|
|
246
|
+
parser.add_argument(
|
|
247
|
+
"--show-warning", action="store_true",
|
|
248
|
+
help="Display the ToS warning text",
|
|
249
|
+
)
|
|
250
|
+
parser.add_argument(
|
|
251
|
+
"--grant", action="store_true",
|
|
252
|
+
help="Grant consent for stealth mode",
|
|
253
|
+
)
|
|
254
|
+
parser.add_argument(
|
|
255
|
+
"--revoke", action="store_true",
|
|
256
|
+
help="Revoke consent for stealth mode",
|
|
257
|
+
)
|
|
258
|
+
parser.add_argument(
|
|
259
|
+
"--project-dir", default=None,
|
|
260
|
+
help="Project directory (default: auto-detect)",
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
args = parser.parse_args()
|
|
264
|
+
manager = ConsentManager(project_dir=args.project_dir)
|
|
265
|
+
|
|
266
|
+
if args.show_warning:
|
|
267
|
+
print(manager.show_warning())
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
if args.grant:
|
|
271
|
+
success = manager.record_consent(acknowledged=True)
|
|
272
|
+
print(json.dumps({"granted": success}, indent=2))
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
if args.revoke:
|
|
276
|
+
success = manager.revoke_consent()
|
|
277
|
+
print(json.dumps({"revoked": success}, indent=2))
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
if args.status:
|
|
281
|
+
status = manager.get_consent_status()
|
|
282
|
+
print(json.dumps(status, indent=2))
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
parser.print_help()
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
if __name__ == "__main__":
|
|
289
|
+
_cli_main()
|