@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,464 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""OMG Migration Script — Migrate OMC settings to OMG.
|
|
3
|
+
|
|
4
|
+
Handles:
|
|
5
|
+
1. Back up current ~/.claude/settings.json
|
|
6
|
+
2. Deploy OMG hooks to ~/.claude/hooks/
|
|
7
|
+
3. Deduplicate hooks in settings.json (remove double-registered entries)
|
|
8
|
+
4. Fix common format bugs (ConfigChange wrapper, etc.)
|
|
9
|
+
5. Switch statusLine from OMC HUD to OMG HUD
|
|
10
|
+
6. Remove dead/erroring plugins
|
|
11
|
+
7. Deploy OMG HUD to ~/.claude/hud/
|
|
12
|
+
|
|
13
|
+
Idempotent — safe to run multiple times. Always creates a backup first.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
python3 scripts/migrate-legacy.py # full migration
|
|
17
|
+
python3 scripts/migrate-legacy.py --dry-run # preview changes only
|
|
18
|
+
python3 scripts/migrate-legacy.py --hooks-only # only deploy hooks
|
|
19
|
+
python3 scripts/migrate-legacy.py --hud-only # only switch HUD
|
|
20
|
+
python3 scripts/migrate-legacy.py --restore BACKUP # restore a backup
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import shutil
|
|
28
|
+
import sys
|
|
29
|
+
from datetime import datetime, timezone
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
# ── Paths ────────────────────────────────────────────────────────────────────
|
|
34
|
+
OMG_ROOT = Path(__file__).resolve().parents[1]
|
|
35
|
+
HOME = Path.home()
|
|
36
|
+
CLAUDE_DIR = HOME / ".claude"
|
|
37
|
+
SETTINGS_PATH = CLAUDE_DIR / "settings.json"
|
|
38
|
+
HOOKS_DIR = CLAUDE_DIR / "hooks"
|
|
39
|
+
HUD_DIR = CLAUDE_DIR / "hud"
|
|
40
|
+
BACKUP_DIR = CLAUDE_DIR / ".omg-backups"
|
|
41
|
+
|
|
42
|
+
OMG_HOOKS_DIR = OMG_ROOT / "hooks"
|
|
43
|
+
OMG_HUD_SRC = OMG_ROOT / "hud" / "omg-hud.mjs"
|
|
44
|
+
OMG_VERSION_TAG = "omg-v1"
|
|
45
|
+
|
|
46
|
+
# ── Hook canonical definitions ───────────────────────────────────────────────
|
|
47
|
+
# Single source of truth for which hooks run on which events.
|
|
48
|
+
# This eliminates duplication regardless of how many times migration runs.
|
|
49
|
+
CANONICAL_HOOKS: dict[str, list[dict[str, Any]]] = {
|
|
50
|
+
"PreToolUse": [
|
|
51
|
+
{
|
|
52
|
+
"matcher": "Bash",
|
|
53
|
+
"hooks": [{"type": "command", "command": 'python3 "$HOME/.claude/hooks/firewall.py"', "timeout": 5}],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"matcher": "Read|Write|Edit|MultiEdit",
|
|
57
|
+
"hooks": [{"type": "command", "command": 'python3 "$HOME/.claude/hooks/secret-guard.py"', "timeout": 5}],
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
"PostToolUse": [
|
|
61
|
+
{
|
|
62
|
+
"matcher": "Bash|Write|Edit|MultiEdit",
|
|
63
|
+
"hooks": [{"type": "command", "command": 'python3 "$HOME/.claude/hooks/tool-ledger.py"', "timeout": 10}],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"matcher": "Bash",
|
|
67
|
+
"hooks": [{"type": "command", "command": 'python3 "$HOME/.claude/hooks/circuit-breaker.py"', "timeout": 10}],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
71
|
+
"hooks": [{"type": "command", "command": 'python3 "$HOME/.claude/hooks/post-write.py"', "timeout": 30}],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
75
|
+
"hooks": [{"type": "command", "command": 'python3 "$HOME/.claude/hooks/shadow_manager.py"', "timeout": 15}],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
"Stop": [
|
|
79
|
+
{
|
|
80
|
+
"hooks": [{"type": "command", "command": 'python3 "$HOME/.claude/hooks/quality-gate.py"', "timeout": 10}],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"hooks": [
|
|
84
|
+
{"type": "command", "command": 'python3 "$HOME/.claude/hooks/stop-gate.py"', "timeout": 15},
|
|
85
|
+
{"type": "command", "command": 'python3 "$HOME/.claude/hooks/test-validator.py"', "timeout": 30},
|
|
86
|
+
{"type": "command", "command": 'python3 "$HOME/.claude/hooks/quality-runner.py"', "timeout": 180},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
"PreCompact": [
|
|
91
|
+
{
|
|
92
|
+
"hooks": [{"type": "command", "command": 'python3 "$HOME/.claude/hooks/pre-compact.py"', "timeout": 15}],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
"SessionStart": [
|
|
96
|
+
{
|
|
97
|
+
"hooks": [{"type": "command", "command": 'python3 "$HOME/.claude/hooks/session-start.py"', "timeout": 10}],
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
"UserPromptSubmit": [
|
|
101
|
+
{
|
|
102
|
+
"hooks": [{"type": "command", "command": 'python3 "$HOME/.claude/hooks/prompt-enhancer.py"', "timeout": 10}],
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
"ConfigChange": [
|
|
106
|
+
{
|
|
107
|
+
"hooks": [{"type": "command", "command": 'python3 "$HOME/.claude/hooks/config-guard.py"', "timeout": 10}],
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Plugins to remove (known dead/erroring)
|
|
113
|
+
DEAD_PLUGINS = [
|
|
114
|
+
"claude-delegator@jarrodwatts-claude-delegator",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
# ── Helpers ──────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
def timestamp() -> str:
|
|
120
|
+
return datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def backup_settings() -> Path | None:
|
|
124
|
+
"""Create a timestamped backup of settings.json. Returns backup path."""
|
|
125
|
+
if not SETTINGS_PATH.exists():
|
|
126
|
+
return None
|
|
127
|
+
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
dest = BACKUP_DIR / f"settings-{timestamp()}.json"
|
|
129
|
+
shutil.copy2(SETTINGS_PATH, dest)
|
|
130
|
+
return dest
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def load_settings() -> dict[str, Any]:
|
|
134
|
+
if not SETTINGS_PATH.exists():
|
|
135
|
+
return {"$schema": "https://json.schemastore.org/claude-code-settings.json"}
|
|
136
|
+
with open(SETTINGS_PATH, "r", encoding="utf-8") as f:
|
|
137
|
+
return json.load(f)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def save_settings(settings: dict[str, Any]) -> None:
|
|
141
|
+
with open(SETTINGS_PATH, "w", encoding="utf-8") as f:
|
|
142
|
+
json.dump(settings, f, indent=2, ensure_ascii=False)
|
|
143
|
+
f.write("\n")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ── Migration steps ──────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
def step_deploy_hooks(dry_run: bool) -> list[str]:
|
|
149
|
+
"""Copy OMG hook files to ~/.claude/hooks/."""
|
|
150
|
+
changes: list[str] = []
|
|
151
|
+
HOOKS_DIR.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
|
|
153
|
+
hook_files = [f for f in OMG_HOOKS_DIR.iterdir() if f.suffix == ".py" and not f.name.startswith("__")]
|
|
154
|
+
for src in sorted(hook_files):
|
|
155
|
+
dest = HOOKS_DIR / src.name
|
|
156
|
+
# Check if update needed
|
|
157
|
+
if dest.exists():
|
|
158
|
+
src_content = src.read_bytes()
|
|
159
|
+
dest_content = dest.read_bytes()
|
|
160
|
+
if src_content == dest_content:
|
|
161
|
+
continue
|
|
162
|
+
changes.append(f" hooks/{src.name} -> ~/.claude/hooks/{src.name}")
|
|
163
|
+
if not dry_run:
|
|
164
|
+
shutil.copy2(src, dest)
|
|
165
|
+
dest.chmod(0o755)
|
|
166
|
+
|
|
167
|
+
# Write version marker
|
|
168
|
+
version_file = HOOKS_DIR / ".omg-version"
|
|
169
|
+
tag = f"{OMG_VERSION_TAG}-{timestamp()[:8]}"
|
|
170
|
+
if not dry_run:
|
|
171
|
+
version_file.write_text(tag + "\n", encoding="utf-8")
|
|
172
|
+
changes.append(f" .omg-version = {tag}")
|
|
173
|
+
|
|
174
|
+
# Write coexist marker
|
|
175
|
+
coexist_file = HOOKS_DIR / ".omg-coexist"
|
|
176
|
+
if not dry_run:
|
|
177
|
+
coexist_file.write_text("omc-coexist\n", encoding="utf-8")
|
|
178
|
+
|
|
179
|
+
return changes
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def step_deduplicate_hooks(settings: dict[str, Any], dry_run: bool) -> list[str]:
|
|
183
|
+
"""Replace all hook entries with the canonical set."""
|
|
184
|
+
changes: list[str] = []
|
|
185
|
+
old_hooks = settings.get("hooks", {})
|
|
186
|
+
|
|
187
|
+
# Count old entries for reporting
|
|
188
|
+
old_count = sum(
|
|
189
|
+
len(v) if isinstance(v, list) else 1
|
|
190
|
+
for v in old_hooks.values()
|
|
191
|
+
)
|
|
192
|
+
new_count = sum(len(v) for v in CANONICAL_HOOKS.values())
|
|
193
|
+
|
|
194
|
+
if old_hooks != CANONICAL_HOOKS:
|
|
195
|
+
changes.append(f" hooks: {old_count} entries -> {new_count} canonical entries")
|
|
196
|
+
if not dry_run:
|
|
197
|
+
settings["hooks"] = CANONICAL_HOOKS
|
|
198
|
+
|
|
199
|
+
return changes
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def step_switch_hud(settings: dict[str, Any], dry_run: bool) -> list[str]:
|
|
203
|
+
"""Switch statusLine to OMG HUD."""
|
|
204
|
+
changes: list[str] = []
|
|
205
|
+
|
|
206
|
+
# Deploy OMG HUD file
|
|
207
|
+
HUD_DIR.mkdir(parents=True, exist_ok=True)
|
|
208
|
+
hud_dest = HUD_DIR / "omg-hud.mjs"
|
|
209
|
+
if OMG_HUD_SRC.exists():
|
|
210
|
+
if hud_dest.exists():
|
|
211
|
+
if OMG_HUD_SRC.read_bytes() != hud_dest.read_bytes():
|
|
212
|
+
changes.append(f" hud/omg-hud.mjs updated")
|
|
213
|
+
if not dry_run:
|
|
214
|
+
shutil.copy2(OMG_HUD_SRC, hud_dest)
|
|
215
|
+
else:
|
|
216
|
+
changes.append(f" hud/omg-hud.mjs deployed")
|
|
217
|
+
if not dry_run:
|
|
218
|
+
shutil.copy2(OMG_HUD_SRC, hud_dest)
|
|
219
|
+
|
|
220
|
+
# Update statusLine in settings
|
|
221
|
+
old_sl = settings.get("statusLine", {})
|
|
222
|
+
new_sl = {"type": "command", "command": "node ~/.claude/hud/omg-hud.mjs"}
|
|
223
|
+
if old_sl != new_sl:
|
|
224
|
+
old_cmd = old_sl.get("command", "(none)")
|
|
225
|
+
changes.append(f" statusLine: {old_cmd} -> node ~/.claude/hud/omg-hud.mjs")
|
|
226
|
+
if not dry_run:
|
|
227
|
+
settings["statusLine"] = new_sl
|
|
228
|
+
|
|
229
|
+
return changes
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def step_clean_plugins(settings: dict[str, Any], dry_run: bool) -> list[str]:
|
|
233
|
+
"""Remove dead/erroring plugins."""
|
|
234
|
+
changes: list[str] = []
|
|
235
|
+
plugins = settings.get("enabledPlugins", {})
|
|
236
|
+
|
|
237
|
+
for plugin_id in DEAD_PLUGINS:
|
|
238
|
+
if plugin_id in plugins:
|
|
239
|
+
changes.append(f" removed plugin: {plugin_id}")
|
|
240
|
+
if not dry_run:
|
|
241
|
+
del plugins[plugin_id]
|
|
242
|
+
|
|
243
|
+
return changes
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def step_fix_plugin_manifests(dry_run: bool) -> list[str]:
|
|
247
|
+
"""Fix known plugin manifest issues (unrecognized keys)."""
|
|
248
|
+
changes: list[str] = []
|
|
249
|
+
|
|
250
|
+
# Fix legacy plugin manifest
|
|
251
|
+
manifest_path = CLAUDE_DIR / "plugins" / "cache" / "claude-plugins-official" / "omg-superpowers"
|
|
252
|
+
if manifest_path.exists():
|
|
253
|
+
for version_dir in manifest_path.iterdir():
|
|
254
|
+
plugin_json = version_dir / ".claude-plugin" / "plugin.json"
|
|
255
|
+
if plugin_json.exists():
|
|
256
|
+
try:
|
|
257
|
+
data = json.loads(plugin_json.read_text(encoding="utf-8"))
|
|
258
|
+
bad_keys = [k for k in ("category", "source") if k in data]
|
|
259
|
+
if bad_keys:
|
|
260
|
+
changes.append(f" fixed {plugin_json.relative_to(CLAUDE_DIR)}: removed {bad_keys}")
|
|
261
|
+
if not dry_run:
|
|
262
|
+
for k in bad_keys:
|
|
263
|
+
del data[k]
|
|
264
|
+
plugin_json.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
265
|
+
except (json.JSONDecodeError, OSError):
|
|
266
|
+
pass
|
|
267
|
+
|
|
268
|
+
return changes
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def step_add_comment(settings: dict[str, Any], dry_run: bool) -> list[str]:
|
|
272
|
+
"""Add OMG migration marker comment."""
|
|
273
|
+
changes: list[str] = []
|
|
274
|
+
marker = f"OMG v1 migrated {timestamp()[:8]}. Hooks canonical. HUD standalone."
|
|
275
|
+
old_comment = settings.get("_comment", "")
|
|
276
|
+
if "OMG" not in old_comment:
|
|
277
|
+
changes.append(f" _comment updated")
|
|
278
|
+
if not dry_run:
|
|
279
|
+
settings["_comment"] = marker
|
|
280
|
+
return changes
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# ── Remove hooks from project settings.json ──────────────────────────────────
|
|
284
|
+
|
|
285
|
+
def step_clean_project_settings(dry_run: bool) -> list[str]:
|
|
286
|
+
"""Remove hooks from OMG project settings.json to prevent double-execution."""
|
|
287
|
+
changes: list[str] = []
|
|
288
|
+
project_settings = OMG_ROOT / "settings.json"
|
|
289
|
+
if not project_settings.exists():
|
|
290
|
+
return changes
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
data = json.loads(project_settings.read_text(encoding="utf-8"))
|
|
294
|
+
except (json.JSONDecodeError, OSError):
|
|
295
|
+
return changes
|
|
296
|
+
|
|
297
|
+
if "hooks" in data:
|
|
298
|
+
hook_count = sum(len(v) if isinstance(v, list) else 1 for v in data["hooks"].values())
|
|
299
|
+
changes.append(f" project settings.json: removed {hook_count} hook entries (already in user-level)")
|
|
300
|
+
if not dry_run:
|
|
301
|
+
del data["hooks"]
|
|
302
|
+
project_settings.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
303
|
+
|
|
304
|
+
return changes
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# ── Restore ──────────────────────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
def restore_backup(backup_path: str) -> None:
|
|
310
|
+
p = Path(backup_path)
|
|
311
|
+
if not p.exists():
|
|
312
|
+
print(f"ERROR: backup not found: {p}", file=sys.stderr)
|
|
313
|
+
sys.exit(1)
|
|
314
|
+
# Validate JSON
|
|
315
|
+
try:
|
|
316
|
+
json.loads(p.read_text(encoding="utf-8"))
|
|
317
|
+
except json.JSONDecodeError as e:
|
|
318
|
+
print(f"ERROR: invalid JSON in backup: {e}", file=sys.stderr)
|
|
319
|
+
sys.exit(1)
|
|
320
|
+
shutil.copy2(p, SETTINGS_PATH)
|
|
321
|
+
print(f"Restored {SETTINGS_PATH} from {p}")
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# ── Main ─────────────────────────────────────────────────────────────────────
|
|
325
|
+
|
|
326
|
+
def run_migration(
|
|
327
|
+
dry_run: bool = False,
|
|
328
|
+
hooks_only: bool = False,
|
|
329
|
+
hud_only: bool = False,
|
|
330
|
+
) -> int:
|
|
331
|
+
print(f"{'[DRY RUN] ' if dry_run else ''}OMG Migration v1")
|
|
332
|
+
print(f" OMG root: {OMG_ROOT}")
|
|
333
|
+
print(f" Claude: {CLAUDE_DIR}")
|
|
334
|
+
print()
|
|
335
|
+
|
|
336
|
+
# Backup
|
|
337
|
+
if not dry_run:
|
|
338
|
+
backup = backup_settings()
|
|
339
|
+
if backup:
|
|
340
|
+
print(f"Backup: {backup}")
|
|
341
|
+
print()
|
|
342
|
+
|
|
343
|
+
settings = load_settings()
|
|
344
|
+
all_changes: list[str] = []
|
|
345
|
+
|
|
346
|
+
# Step 1: Deploy hooks
|
|
347
|
+
if not hud_only:
|
|
348
|
+
print("Step 1: Deploy hooks")
|
|
349
|
+
changes = step_deploy_hooks(dry_run)
|
|
350
|
+
all_changes.extend(changes)
|
|
351
|
+
for c in changes:
|
|
352
|
+
print(c)
|
|
353
|
+
if not changes:
|
|
354
|
+
print(" (no changes)")
|
|
355
|
+
print()
|
|
356
|
+
|
|
357
|
+
# Step 2: Deduplicate hooks in settings
|
|
358
|
+
if not hud_only:
|
|
359
|
+
print("Step 2: Deduplicate hooks in settings.json")
|
|
360
|
+
changes = step_deduplicate_hooks(settings, dry_run)
|
|
361
|
+
all_changes.extend(changes)
|
|
362
|
+
for c in changes:
|
|
363
|
+
print(c)
|
|
364
|
+
if not changes:
|
|
365
|
+
print(" (already canonical)")
|
|
366
|
+
print()
|
|
367
|
+
|
|
368
|
+
# Step 3: Switch HUD
|
|
369
|
+
if not hooks_only:
|
|
370
|
+
print("Step 3: Switch to OMG HUD")
|
|
371
|
+
changes = step_switch_hud(settings, dry_run)
|
|
372
|
+
all_changes.extend(changes)
|
|
373
|
+
for c in changes:
|
|
374
|
+
print(c)
|
|
375
|
+
if not changes:
|
|
376
|
+
print(" (already using OMG HUD)")
|
|
377
|
+
print()
|
|
378
|
+
|
|
379
|
+
# Step 4: Clean plugins
|
|
380
|
+
if not hooks_only and not hud_only:
|
|
381
|
+
print("Step 4: Clean dead plugins")
|
|
382
|
+
changes = step_clean_plugins(settings, dry_run)
|
|
383
|
+
all_changes.extend(changes)
|
|
384
|
+
for c in changes:
|
|
385
|
+
print(c)
|
|
386
|
+
if not changes:
|
|
387
|
+
print(" (none to remove)")
|
|
388
|
+
print()
|
|
389
|
+
|
|
390
|
+
# Step 5: Fix plugin manifests
|
|
391
|
+
if not hooks_only and not hud_only:
|
|
392
|
+
print("Step 5: Fix plugin manifests")
|
|
393
|
+
changes = step_fix_plugin_manifests(dry_run)
|
|
394
|
+
all_changes.extend(changes)
|
|
395
|
+
for c in changes:
|
|
396
|
+
print(c)
|
|
397
|
+
if not changes:
|
|
398
|
+
print(" (none to fix)")
|
|
399
|
+
print()
|
|
400
|
+
|
|
401
|
+
# Step 6: Clean project settings
|
|
402
|
+
if not hud_only:
|
|
403
|
+
print("Step 6: Clean project settings.json")
|
|
404
|
+
changes = step_clean_project_settings(dry_run)
|
|
405
|
+
all_changes.extend(changes)
|
|
406
|
+
for c in changes:
|
|
407
|
+
print(c)
|
|
408
|
+
if not changes:
|
|
409
|
+
print(" (already clean)")
|
|
410
|
+
print()
|
|
411
|
+
|
|
412
|
+
# Step 7: Add migration marker
|
|
413
|
+
if not hooks_only and not hud_only:
|
|
414
|
+
print("Step 7: Add migration marker")
|
|
415
|
+
changes = step_add_comment(settings, dry_run)
|
|
416
|
+
all_changes.extend(changes)
|
|
417
|
+
for c in changes:
|
|
418
|
+
print(c)
|
|
419
|
+
if not changes:
|
|
420
|
+
print(" (already marked)")
|
|
421
|
+
print()
|
|
422
|
+
|
|
423
|
+
# Save
|
|
424
|
+
if not dry_run and all_changes:
|
|
425
|
+
save_settings(settings)
|
|
426
|
+
print(f"Saved {SETTINGS_PATH}")
|
|
427
|
+
|
|
428
|
+
# Summary
|
|
429
|
+
print(f"\n{'[DRY RUN] ' if dry_run else ''}Migration complete: {len(all_changes)} changes")
|
|
430
|
+
if dry_run and all_changes:
|
|
431
|
+
print(" Run without --dry-run to apply changes.")
|
|
432
|
+
|
|
433
|
+
return 0
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
437
|
+
parser = argparse.ArgumentParser(
|
|
438
|
+
prog="migrate-legacy",
|
|
439
|
+
description="Migrate OMC settings to OMG. Idempotent — safe to run multiple times.",
|
|
440
|
+
)
|
|
441
|
+
parser.add_argument("--dry-run", action="store_true", help="Preview changes without applying")
|
|
442
|
+
parser.add_argument("--hooks-only", action="store_true", help="Only deploy hooks")
|
|
443
|
+
parser.add_argument("--hud-only", action="store_true", help="Only switch HUD")
|
|
444
|
+
parser.add_argument("--restore", metavar="BACKUP", help="Restore a backup file")
|
|
445
|
+
return parser
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def main(argv: list[str] | None = None) -> int:
|
|
449
|
+
parser = build_parser()
|
|
450
|
+
args = parser.parse_args(argv)
|
|
451
|
+
|
|
452
|
+
if args.restore:
|
|
453
|
+
restore_backup(args.restore)
|
|
454
|
+
return 0
|
|
455
|
+
|
|
456
|
+
return run_migration(
|
|
457
|
+
dry_run=args.dry_run,
|
|
458
|
+
hooks_only=args.hooks_only,
|
|
459
|
+
hud_only=args.hud_only,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
if __name__ == "__main__":
|
|
464
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Legacy wrapper for `legacy_to_omg_migrate.py`."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import runpy
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
target = Path(__file__).resolve().with_name("legacy_to_omg_migrate.py")
|
|
11
|
+
runpy.run_path(str(target), run_name="__main__")
|
|
12
|
+
|