@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,169 @@
|
|
|
1
|
+
"""tmux session lifecycle manager for persistent model invocations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import time
|
|
10
|
+
import uuid
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TmuxSessionManager:
|
|
17
|
+
"""Manage lightweight tmux sessions used for provider command execution."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, session_prefix: str = "omg") -> None:
|
|
20
|
+
"""Initialize manager with a session name prefix."""
|
|
21
|
+
self.session_prefix: str = session_prefix
|
|
22
|
+
|
|
23
|
+
def is_tmux_available(self) -> bool:
|
|
24
|
+
"""Return True when tmux is available on PATH."""
|
|
25
|
+
return shutil.which("tmux") is not None
|
|
26
|
+
|
|
27
|
+
def session_exists(self, name: str) -> bool:
|
|
28
|
+
"""Return True if a tmux session exists."""
|
|
29
|
+
if not self.is_tmux_available():
|
|
30
|
+
return False
|
|
31
|
+
try:
|
|
32
|
+
result = subprocess.run(
|
|
33
|
+
["tmux", "has-session", "-t", name],
|
|
34
|
+
capture_output=True,
|
|
35
|
+
text=True,
|
|
36
|
+
check=False,
|
|
37
|
+
timeout=5,
|
|
38
|
+
)
|
|
39
|
+
return result.returncode == 0
|
|
40
|
+
except Exception as exc:
|
|
41
|
+
_logger.warning("Failed to check tmux session %r: %s", name, exc)
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
def create_session(self, name: str) -> bool:
|
|
45
|
+
"""Create a detached tmux session and return success state."""
|
|
46
|
+
if not self.is_tmux_available():
|
|
47
|
+
return False
|
|
48
|
+
try:
|
|
49
|
+
result = subprocess.run(
|
|
50
|
+
["tmux", "new-session", "-d", "-s", name],
|
|
51
|
+
capture_output=True,
|
|
52
|
+
text=True,
|
|
53
|
+
check=False,
|
|
54
|
+
timeout=10,
|
|
55
|
+
)
|
|
56
|
+
return result.returncode == 0
|
|
57
|
+
except Exception as exc:
|
|
58
|
+
_logger.warning("Failed to create tmux session %r: %s", name, exc)
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
def kill_session(self, name: str) -> bool:
|
|
62
|
+
"""Kill a tmux session and return success state."""
|
|
63
|
+
if not self.is_tmux_available():
|
|
64
|
+
return False
|
|
65
|
+
try:
|
|
66
|
+
result = subprocess.run(
|
|
67
|
+
["tmux", "kill-session", "-t", name],
|
|
68
|
+
capture_output=True,
|
|
69
|
+
text=True,
|
|
70
|
+
check=False,
|
|
71
|
+
timeout=10,
|
|
72
|
+
)
|
|
73
|
+
return result.returncode == 0
|
|
74
|
+
except Exception as exc:
|
|
75
|
+
_logger.warning("Failed to kill tmux session %r: %s", name, exc)
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
def send_command(self, name: str, command: str, timeout: int = 120) -> str:
|
|
79
|
+
"""Send a command to a tmux session and return captured pane output."""
|
|
80
|
+
if not self.is_tmux_available():
|
|
81
|
+
return ""
|
|
82
|
+
|
|
83
|
+
sentinel = f"__OMG_DONE_{uuid.uuid4().hex}__"
|
|
84
|
+
deadline = time.monotonic() + timeout
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
send_main = subprocess.run(
|
|
88
|
+
["tmux", "send-keys", "-t", name, command, "Enter"],
|
|
89
|
+
capture_output=True,
|
|
90
|
+
text=True,
|
|
91
|
+
check=False,
|
|
92
|
+
timeout=10,
|
|
93
|
+
)
|
|
94
|
+
if send_main.returncode != 0:
|
|
95
|
+
return ""
|
|
96
|
+
|
|
97
|
+
send_marker = subprocess.run(
|
|
98
|
+
["tmux", "send-keys", "-t", name, f"echo {sentinel}", "Enter"],
|
|
99
|
+
capture_output=True,
|
|
100
|
+
text=True,
|
|
101
|
+
check=False,
|
|
102
|
+
timeout=10,
|
|
103
|
+
)
|
|
104
|
+
if send_marker.returncode != 0:
|
|
105
|
+
return ""
|
|
106
|
+
|
|
107
|
+
last_output = ""
|
|
108
|
+
while time.monotonic() < deadline:
|
|
109
|
+
captured = subprocess.run(
|
|
110
|
+
["tmux", "capture-pane", "-t", name, "-p"],
|
|
111
|
+
capture_output=True,
|
|
112
|
+
text=True,
|
|
113
|
+
check=False,
|
|
114
|
+
timeout=10,
|
|
115
|
+
)
|
|
116
|
+
if captured.returncode == 0:
|
|
117
|
+
last_output = captured.stdout
|
|
118
|
+
if sentinel in last_output:
|
|
119
|
+
return last_output.split(sentinel, 1)[0].rstrip()
|
|
120
|
+
time.sleep(0.25)
|
|
121
|
+
|
|
122
|
+
return last_output.rstrip()
|
|
123
|
+
except Exception as exc:
|
|
124
|
+
_logger.warning("Failed to send tmux command to %r: %s", name, exc)
|
|
125
|
+
return ""
|
|
126
|
+
|
|
127
|
+
def get_or_create_session(self, name: str) -> str:
|
|
128
|
+
"""Return a fresh tmux session name, recreating stale sessions if needed."""
|
|
129
|
+
if self.session_exists(name):
|
|
130
|
+
_ = self.kill_session(name)
|
|
131
|
+
if not self.create_session(name):
|
|
132
|
+
raise RuntimeError(f"Unable to create tmux session: {name}")
|
|
133
|
+
return name
|
|
134
|
+
|
|
135
|
+
def make_session_name(self, provider: str, unique_id: str | None = None) -> str:
|
|
136
|
+
"""Build a normalized tmux session name using provider and optional id."""
|
|
137
|
+
provider_clean = re.sub(r"[^a-zA-Z0-9_-]", "-", provider).strip("-").lower() or "session"
|
|
138
|
+
base_name = f"{self.session_prefix}-{provider_clean}"
|
|
139
|
+
if unique_id:
|
|
140
|
+
unique_clean = re.sub(r"[^a-zA-Z0-9_-]", "-", unique_id).strip("-").lower()
|
|
141
|
+
if unique_clean:
|
|
142
|
+
return f"{base_name}-{unique_clean}"
|
|
143
|
+
return base_name
|
|
144
|
+
|
|
145
|
+
def cleanup_stale_sessions(self) -> int:
|
|
146
|
+
"""Kill all tmux sessions matching this manager prefix and return count."""
|
|
147
|
+
if not self.is_tmux_available():
|
|
148
|
+
return 0
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
listed = subprocess.run(
|
|
152
|
+
["tmux", "list-sessions", "-F", "#{session_name}"],
|
|
153
|
+
capture_output=True,
|
|
154
|
+
text=True,
|
|
155
|
+
check=False,
|
|
156
|
+
timeout=10,
|
|
157
|
+
)
|
|
158
|
+
if listed.returncode != 0:
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
killed = 0
|
|
162
|
+
prefix = f"{self.session_prefix}-"
|
|
163
|
+
for session_name in listed.stdout.splitlines():
|
|
164
|
+
if session_name.startswith(prefix) and self.kill_session(session_name):
|
|
165
|
+
killed += 1
|
|
166
|
+
return killed
|
|
167
|
+
except Exception as exc:
|
|
168
|
+
_logger.warning("Failed to cleanup tmux sessions for prefix %r: %s", self.session_prefix, exc)
|
|
169
|
+
return 0
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate committed OMG compatibility snapshot against runtime contracts."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
11
|
+
if str(ROOT) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(ROOT))
|
|
13
|
+
|
|
14
|
+
from runtime.compat import ( # noqa: E402
|
|
15
|
+
CONTRACT_SNAPSHOT_SCHEMA,
|
|
16
|
+
CONTRACT_SNAPSHOT_VERSION,
|
|
17
|
+
DEFAULT_CONTRACT_SNAPSHOT_PATH,
|
|
18
|
+
LEGACY_CONTRACT_SNAPSHOT_PATH,
|
|
19
|
+
build_contract_snapshot_payload,
|
|
20
|
+
migrate_contract_snapshot_payload,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_snapshot(path: Path) -> dict:
|
|
25
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
26
|
+
payload = json.load(f)
|
|
27
|
+
if not isinstance(payload, dict):
|
|
28
|
+
raise ValueError("snapshot must be a JSON object")
|
|
29
|
+
return payload
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main() -> int:
|
|
33
|
+
parser = argparse.ArgumentParser(description="Check OMG compatibility contract snapshot drift")
|
|
34
|
+
parser.add_argument("--snapshot", default="")
|
|
35
|
+
parser.add_argument("--strict-version", action="store_true")
|
|
36
|
+
args = parser.parse_args()
|
|
37
|
+
|
|
38
|
+
if args.snapshot:
|
|
39
|
+
snapshot_path = Path(args.snapshot)
|
|
40
|
+
else:
|
|
41
|
+
primary = ROOT / DEFAULT_CONTRACT_SNAPSHOT_PATH
|
|
42
|
+
legacy = ROOT / LEGACY_CONTRACT_SNAPSHOT_PATH
|
|
43
|
+
snapshot_path = primary if primary.exists() else legacy
|
|
44
|
+
|
|
45
|
+
if not snapshot_path.exists():
|
|
46
|
+
print(
|
|
47
|
+
json.dumps(
|
|
48
|
+
{"status": "error", "message": f"snapshot not found: {snapshot_path}"},
|
|
49
|
+
indent=2,
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
return 2
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
current = _load_snapshot(snapshot_path)
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
print(json.dumps({"status": "error", "message": f"invalid snapshot json: {exc}"}, indent=2))
|
|
58
|
+
return 2
|
|
59
|
+
|
|
60
|
+
migrated, migrations = migrate_contract_snapshot_payload(current)
|
|
61
|
+
expected = build_contract_snapshot_payload(include_generated_at=False)
|
|
62
|
+
|
|
63
|
+
if migrated.get("schema") != CONTRACT_SNAPSHOT_SCHEMA:
|
|
64
|
+
print(
|
|
65
|
+
json.dumps(
|
|
66
|
+
{
|
|
67
|
+
"status": "error",
|
|
68
|
+
"message": "snapshot schema mismatch",
|
|
69
|
+
"expected_schema": CONTRACT_SNAPSHOT_SCHEMA,
|
|
70
|
+
"actual_schema": migrated.get("schema"),
|
|
71
|
+
},
|
|
72
|
+
indent=2,
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
return 3
|
|
76
|
+
|
|
77
|
+
if args.strict_version and current.get("contract_version") != CONTRACT_SNAPSHOT_VERSION:
|
|
78
|
+
print(
|
|
79
|
+
json.dumps(
|
|
80
|
+
{
|
|
81
|
+
"status": "error",
|
|
82
|
+
"message": "snapshot contract_version mismatch (strict)",
|
|
83
|
+
"expected_version": CONTRACT_SNAPSHOT_VERSION,
|
|
84
|
+
"actual_version": current.get("contract_version"),
|
|
85
|
+
},
|
|
86
|
+
indent=2,
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
return 3
|
|
90
|
+
|
|
91
|
+
if migrated.get("contract_version") != CONTRACT_SNAPSHOT_VERSION:
|
|
92
|
+
print(
|
|
93
|
+
json.dumps(
|
|
94
|
+
{
|
|
95
|
+
"status": "error",
|
|
96
|
+
"message": "snapshot contract_version unsupported",
|
|
97
|
+
"expected_version": CONTRACT_SNAPSHOT_VERSION,
|
|
98
|
+
"actual_version": migrated.get("contract_version"),
|
|
99
|
+
},
|
|
100
|
+
indent=2,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
return 3
|
|
104
|
+
|
|
105
|
+
if migrated.get("count") != expected.get("count") or migrated.get("contracts") != expected.get("contracts"):
|
|
106
|
+
print(
|
|
107
|
+
json.dumps(
|
|
108
|
+
{
|
|
109
|
+
"status": "error",
|
|
110
|
+
"message": "snapshot drift detected",
|
|
111
|
+
"expected_count": expected.get("count"),
|
|
112
|
+
"actual_count": migrated.get("count"),
|
|
113
|
+
"migrations_applied": migrations,
|
|
114
|
+
},
|
|
115
|
+
indent=2,
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
return 3
|
|
119
|
+
|
|
120
|
+
print(
|
|
121
|
+
json.dumps(
|
|
122
|
+
{
|
|
123
|
+
"status": "ok",
|
|
124
|
+
"message": "snapshot matches runtime contracts",
|
|
125
|
+
"contract_version": CONTRACT_SNAPSHOT_VERSION,
|
|
126
|
+
"migrations_applied": migrations,
|
|
127
|
+
"snapshot": str(snapshot_path),
|
|
128
|
+
},
|
|
129
|
+
indent=2,
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
return 0
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
raise SystemExit(main())
|
|
137
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Legacy wrapper for OMG compatibility snapshot checker."""
|
|
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("check-omg-compat-contract-snapshot.py")
|
|
11
|
+
runpy.run_path(str(target), run_name="__main__")
|
|
12
|
+
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Check that the repo is safe and polished enough for a public launch."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
11
|
+
|
|
12
|
+
REQUIRED_PUBLIC_DOCS = [
|
|
13
|
+
"README.md",
|
|
14
|
+
"CONTRIBUTING.md",
|
|
15
|
+
"SECURITY.md",
|
|
16
|
+
"CODE_OF_CONDUCT.md",
|
|
17
|
+
"CHANGELOG.md",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
REQUIRED_COMMUNITY_TEMPLATES = [
|
|
21
|
+
".github/ISSUE_TEMPLATE/bug_report.yml",
|
|
22
|
+
".github/ISSUE_TEMPLATE/feature_request.yml",
|
|
23
|
+
".github/pull_request_template.md",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
PUBLIC_DOC_GLOBS = [
|
|
27
|
+
"README.md",
|
|
28
|
+
"CONTRIBUTING.md",
|
|
29
|
+
"SECURITY.md",
|
|
30
|
+
"CODE_OF_CONDUCT.md",
|
|
31
|
+
"CHANGELOG.md",
|
|
32
|
+
"docs/**/*.md",
|
|
33
|
+
"plugins/README.md",
|
|
34
|
+
".github/**/*.md",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
TEXT_GLOBS = [
|
|
38
|
+
"README.md",
|
|
39
|
+
"CONTRIBUTING.md",
|
|
40
|
+
"SECURITY.md",
|
|
41
|
+
"CODE_OF_CONDUCT.md",
|
|
42
|
+
"CHANGELOG.md",
|
|
43
|
+
"docs/**/*.md",
|
|
44
|
+
"commands/**/*.md",
|
|
45
|
+
"plugins/**/*.md",
|
|
46
|
+
"agents/**/*.md",
|
|
47
|
+
"rules/**/*.md",
|
|
48
|
+
"runtime/**/*.py",
|
|
49
|
+
"hooks/**/*.py",
|
|
50
|
+
"scripts/**/*.py",
|
|
51
|
+
".github/workflows/**/*.yml",
|
|
52
|
+
".github/workflows/**/*.yaml",
|
|
53
|
+
"OMG-setup.sh",
|
|
54
|
+
"install.sh",
|
|
55
|
+
"package.json",
|
|
56
|
+
"settings.json",
|
|
57
|
+
".claude-plugin/**/*.json",
|
|
58
|
+
"plugins/**/*.json",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
MARKDOWN_LINK_RE = re.compile(r"!\[[^\]]*\]\(([^)]+)\)|\[[^\]]+\]\(([^)]+)\)")
|
|
62
|
+
ALLOW_PATTERN_REFERENCES = {
|
|
63
|
+
ROOT / "scripts" / "check-omg-public-ready.py",
|
|
64
|
+
}
|
|
65
|
+
ALLOW_DEPRECATED_MARKETPLACE = {
|
|
66
|
+
ROOT / "OMG-setup.sh",
|
|
67
|
+
ROOT / "scripts" / "check-omg-public-ready.py",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _iter_files(root: Path, globs: list[str]) -> list[Path]:
|
|
72
|
+
files: list[Path] = []
|
|
73
|
+
for pattern in globs:
|
|
74
|
+
files.extend(path for path in root.glob(pattern) if path.is_file())
|
|
75
|
+
return sorted(set(files))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _read(path: Path) -> str:
|
|
79
|
+
return path.read_text(encoding="utf-8")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _find_text_violations(root: Path) -> list[str]:
|
|
83
|
+
violations: list[str] = []
|
|
84
|
+
for path in _iter_files(root, TEXT_GLOBS):
|
|
85
|
+
rel = path.relative_to(root)
|
|
86
|
+
content = _read(path)
|
|
87
|
+
if "/Users/" in content:
|
|
88
|
+
if path not in ALLOW_PATTERN_REFERENCES:
|
|
89
|
+
violations.append(f"{rel}: absolute local path found in public repo content")
|
|
90
|
+
if ".sisyphus/" in content:
|
|
91
|
+
if path not in ALLOW_PATTERN_REFERENCES:
|
|
92
|
+
violations.append(f"{rel}: stale internal path reference found (.sisyphus/)")
|
|
93
|
+
if "trac3er00/OAL" in content:
|
|
94
|
+
if path not in ALLOW_PATTERN_REFERENCES:
|
|
95
|
+
violations.append(f"{rel}: old repo identifier found (trac3er00/OAL)")
|
|
96
|
+
if "oh-advanced-layer" in content:
|
|
97
|
+
if path not in ALLOW_DEPRECATED_MARKETPLACE:
|
|
98
|
+
violations.append(f"{rel}: deprecated marketplace identifier found (oh-advanced-layer)")
|
|
99
|
+
return violations
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _find_missing_docs(root: Path) -> list[str]:
|
|
103
|
+
violations: list[str] = []
|
|
104
|
+
for rel in REQUIRED_PUBLIC_DOCS:
|
|
105
|
+
if not (root / rel).exists():
|
|
106
|
+
violations.append(f"{rel}: missing required public doc")
|
|
107
|
+
return violations
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _find_missing_templates(root: Path) -> list[str]:
|
|
111
|
+
violations: list[str] = []
|
|
112
|
+
for rel in REQUIRED_COMMUNITY_TEMPLATES:
|
|
113
|
+
if not (root / rel).exists():
|
|
114
|
+
violations.append(f"{rel}: missing required community template")
|
|
115
|
+
return violations
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _find_internal_docs(root: Path) -> list[str]:
|
|
119
|
+
plans_dir = root / "docs" / "plans"
|
|
120
|
+
if plans_dir.exists():
|
|
121
|
+
return [f"{plans_dir.relative_to(root)}: internal planning docs must not ship in public branch"]
|
|
122
|
+
return []
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _normalize_link_target(raw: str) -> str:
|
|
126
|
+
target = raw.strip()
|
|
127
|
+
if target.startswith("<") and target.endswith(">"):
|
|
128
|
+
target = target[1:-1].strip()
|
|
129
|
+
return target
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _is_relative_markdown_link(target: str) -> bool:
|
|
133
|
+
if not target:
|
|
134
|
+
return False
|
|
135
|
+
if target.startswith("#"):
|
|
136
|
+
return False
|
|
137
|
+
lowered = target.lower()
|
|
138
|
+
return not (
|
|
139
|
+
lowered.startswith("http://")
|
|
140
|
+
or lowered.startswith("https://")
|
|
141
|
+
or lowered.startswith("mailto:")
|
|
142
|
+
or lowered.startswith("data:")
|
|
143
|
+
or lowered.startswith("file:")
|
|
144
|
+
or target.startswith("/")
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _link_target_exists(doc_path: Path, target: str) -> bool:
|
|
149
|
+
file_part = target.split("#", 1)[0].split("?", 1)[0]
|
|
150
|
+
if not file_part:
|
|
151
|
+
return True
|
|
152
|
+
resolved = (doc_path.parent / file_part).resolve()
|
|
153
|
+
return resolved.exists()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _find_broken_markdown_links(root: Path) -> list[str]:
|
|
157
|
+
violations: list[str] = []
|
|
158
|
+
for path in _iter_files(root, PUBLIC_DOC_GLOBS):
|
|
159
|
+
rel = path.relative_to(root)
|
|
160
|
+
content = _read(path)
|
|
161
|
+
for match in MARKDOWN_LINK_RE.finditer(content):
|
|
162
|
+
target = _normalize_link_target(match.group(1) or match.group(2) or "")
|
|
163
|
+
if not _is_relative_markdown_link(target):
|
|
164
|
+
continue
|
|
165
|
+
if not _link_target_exists(path, target):
|
|
166
|
+
violations.append(f"{rel}: broken markdown link -> {target}")
|
|
167
|
+
return violations
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def main() -> int:
|
|
171
|
+
parser = argparse.ArgumentParser(description="Check OMG public-readiness hygiene")
|
|
172
|
+
parser.add_argument("--root", default=str(ROOT))
|
|
173
|
+
args = parser.parse_args()
|
|
174
|
+
root = Path(args.root).resolve()
|
|
175
|
+
|
|
176
|
+
violations = []
|
|
177
|
+
violations.extend(_find_missing_docs(root))
|
|
178
|
+
violations.extend(_find_missing_templates(root))
|
|
179
|
+
violations.extend(_find_internal_docs(root))
|
|
180
|
+
violations.extend(_find_text_violations(root))
|
|
181
|
+
violations.extend(_find_broken_markdown_links(root))
|
|
182
|
+
violations = sorted(set(violations))
|
|
183
|
+
|
|
184
|
+
if violations:
|
|
185
|
+
print(json.dumps({"status": "error", "violations": violations}, indent=2))
|
|
186
|
+
return 1
|
|
187
|
+
|
|
188
|
+
print(json.dumps({"status": "ok", "message": "public readiness check passed"}, indent=2))
|
|
189
|
+
return 0
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Check that standalone/OMG-first naming rules are preserved."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
11
|
+
|
|
12
|
+
# Paths where legacy aliases are explicitly allowed.
|
|
13
|
+
ALLOW_COMPAT_CLI = {
|
|
14
|
+
ROOT / "commands" / "OMG:compat.md",
|
|
15
|
+
ROOT / "tests" / "scripts" / "test_omg_cli.py",
|
|
16
|
+
ROOT / "tests" / "scripts" / "test_standalone_clean_check.py",
|
|
17
|
+
ROOT / "scripts" / "check-omg-standalone-clean.py",
|
|
18
|
+
ROOT / ".github" / "workflows" / "omg-compat-gate.yml",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
ALLOW_LEGACY_MIGRATOR = {
|
|
22
|
+
ROOT / "scripts" / "migrate-legacy.py",
|
|
23
|
+
ROOT / "tests" / "e2e" / "test_standalone_ga.py",
|
|
24
|
+
ROOT / "scripts" / "check-omg-standalone-clean.py",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
ALLOW_LEGACY_SNAPSHOT_CHECKER = {
|
|
28
|
+
ROOT / "scripts" / "check-omg-contract-snapshot.py",
|
|
29
|
+
ROOT / "tests" / "scripts" / "test_compat_snapshot_check.py",
|
|
30
|
+
ROOT / "scripts" / "check-omg-standalone-clean.py",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
ALLOW_LEGACY_RUNTIME_IMPORT = {
|
|
34
|
+
ROOT / "tests" / "scripts" / "test_standalone_clean_check.py",
|
|
35
|
+
ROOT / "scripts" / "check-omg-standalone-clean.py",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
SCAN_GLOBS = [
|
|
39
|
+
"README.md",
|
|
40
|
+
"OMG-setup.sh",
|
|
41
|
+
"install.sh",
|
|
42
|
+
"runtime/**/*.py",
|
|
43
|
+
"hooks/**/*.py",
|
|
44
|
+
"scripts/**/*.py",
|
|
45
|
+
"commands/**/*.md",
|
|
46
|
+
".github/workflows/**/*.yml",
|
|
47
|
+
"tests/**/*.py",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _iter_files(root: Path) -> list[Path]:
|
|
52
|
+
files: list[Path] = []
|
|
53
|
+
for pattern in SCAN_GLOBS:
|
|
54
|
+
files.extend(p for p in root.glob(pattern) if p.is_file())
|
|
55
|
+
# deterministic order
|
|
56
|
+
return sorted(set(files))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _contains(path: Path, token: str) -> bool:
|
|
60
|
+
return token in path.read_text(encoding="utf-8")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main() -> int:
|
|
64
|
+
parser = argparse.ArgumentParser(description="Check OMG standalone naming hygiene")
|
|
65
|
+
parser.add_argument("--root", default=str(ROOT))
|
|
66
|
+
args = parser.parse_args()
|
|
67
|
+
root = Path(args.root).resolve()
|
|
68
|
+
|
|
69
|
+
violations: list[str] = []
|
|
70
|
+
|
|
71
|
+
deprecated_workflow = root / ".github" / "workflows" / "compat-gate.yml"
|
|
72
|
+
if deprecated_workflow.exists():
|
|
73
|
+
violations.append(f"deprecated workflow exists: {deprecated_workflow.relative_to(root)}")
|
|
74
|
+
|
|
75
|
+
for path in _iter_files(root):
|
|
76
|
+
rel = path.relative_to(root)
|
|
77
|
+
|
|
78
|
+
if _contains(path, "python3 scripts/omg.py compat ") or _contains(path, "python3 scripts/omg.py omc "):
|
|
79
|
+
if path not in ALLOW_COMPAT_CLI:
|
|
80
|
+
violations.append(f"{rel}: legacy CLI namespace used outside allowlist")
|
|
81
|
+
|
|
82
|
+
if _contains(path, "migrate-legacy.py"):
|
|
83
|
+
if path not in ALLOW_LEGACY_MIGRATOR:
|
|
84
|
+
violations.append(f"{rel}: legacy migrator path reference outside allowlist")
|
|
85
|
+
|
|
86
|
+
if _contains(path, "check-omg-contract-snapshot.py"):
|
|
87
|
+
if path not in ALLOW_LEGACY_SNAPSHOT_CHECKER:
|
|
88
|
+
violations.append(f"{rel}: legacy snapshot checker reference outside allowlist")
|
|
89
|
+
|
|
90
|
+
if _contains(path, "runtime.legacy_compat"):
|
|
91
|
+
if path not in ALLOW_LEGACY_RUNTIME_IMPORT:
|
|
92
|
+
violations.append(f"{rel}: legacy runtime import outside allowlist")
|
|
93
|
+
|
|
94
|
+
if violations:
|
|
95
|
+
print(json.dumps({"status": "error", "violations": violations}, indent=2))
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
print(json.dumps({"status": "ok", "message": "standalone naming check passed"}, indent=2))
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CLI utility: migrate legacy state into canonical `.omg` layout."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
12
|
+
if str(ROOT) not in sys.path:
|
|
13
|
+
sys.path.insert(0, str(ROOT))
|
|
14
|
+
|
|
15
|
+
from hooks.state_migration import migrate_legacy_to_omg # noqa: E402
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> int:
|
|
19
|
+
parser = argparse.ArgumentParser(description="Migrate legacy state to .omg")
|
|
20
|
+
parser.add_argument("--project-dir", default=os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()))
|
|
21
|
+
args = parser.parse_args()
|
|
22
|
+
|
|
23
|
+
report = migrate_legacy_to_omg(args.project_dir)
|
|
24
|
+
print(json.dumps(report, indent=2))
|
|
25
|
+
return 0 if report.get("result") in {"ok", "no_legacy"} else 1
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
raise SystemExit(main())
|