@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
package/yaml.py
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""Minimal YAML compatibility layer for OMG.
|
|
2
|
+
|
|
3
|
+
This keeps standalone/no-vendor flows working when PyYAML is unavailable.
|
|
4
|
+
It supports the subset used in OMG configs: mappings, lists, quoted strings,
|
|
5
|
+
booleans, numbers, nulls, and simple flow-style collections.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import io
|
|
10
|
+
import json
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _read_text(stream_or_text: Any) -> str:
|
|
15
|
+
if hasattr(stream_or_text, "read"):
|
|
16
|
+
return str(stream_or_text.read())
|
|
17
|
+
return str(stream_or_text)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _strip_comments(line: str) -> str:
|
|
21
|
+
in_single = False
|
|
22
|
+
in_double = False
|
|
23
|
+
escaped = False
|
|
24
|
+
out: list[str] = []
|
|
25
|
+
for char in line:
|
|
26
|
+
if escaped:
|
|
27
|
+
out.append(char)
|
|
28
|
+
escaped = False
|
|
29
|
+
continue
|
|
30
|
+
if char == "\\" and in_double:
|
|
31
|
+
out.append(char)
|
|
32
|
+
escaped = True
|
|
33
|
+
continue
|
|
34
|
+
if char == "'" and not in_double:
|
|
35
|
+
in_single = not in_single
|
|
36
|
+
out.append(char)
|
|
37
|
+
continue
|
|
38
|
+
if char == '"' and not in_single:
|
|
39
|
+
in_double = not in_double
|
|
40
|
+
out.append(char)
|
|
41
|
+
continue
|
|
42
|
+
if char == "#" and not in_single and not in_double:
|
|
43
|
+
break
|
|
44
|
+
out.append(char)
|
|
45
|
+
return "".join(out).rstrip()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _prepare_lines(text: str) -> list[tuple[int, str]]:
|
|
49
|
+
prepared: list[tuple[int, str]] = []
|
|
50
|
+
for raw_line in text.splitlines():
|
|
51
|
+
stripped = _strip_comments(raw_line)
|
|
52
|
+
if not stripped.strip():
|
|
53
|
+
continue
|
|
54
|
+
indent = len(stripped) - len(stripped.lstrip(" "))
|
|
55
|
+
prepared.append((indent, stripped[indent:]))
|
|
56
|
+
return prepared
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _split_key_value(text: str) -> tuple[str, str] | None:
|
|
60
|
+
in_single = False
|
|
61
|
+
in_double = False
|
|
62
|
+
escaped = False
|
|
63
|
+
for index, char in enumerate(text):
|
|
64
|
+
if escaped:
|
|
65
|
+
escaped = False
|
|
66
|
+
continue
|
|
67
|
+
if char == "\\" and in_double:
|
|
68
|
+
escaped = True
|
|
69
|
+
continue
|
|
70
|
+
if char == "'" and not in_double:
|
|
71
|
+
in_single = not in_single
|
|
72
|
+
continue
|
|
73
|
+
if char == '"' and not in_single:
|
|
74
|
+
in_double = not in_double
|
|
75
|
+
continue
|
|
76
|
+
if char == ":" and not in_single and not in_double:
|
|
77
|
+
if index + 1 < len(text) and text[index + 1] not in {" ", "\t"}:
|
|
78
|
+
continue
|
|
79
|
+
return text[:index].strip(), text[index + 1 :].strip()
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _split_flow_items(text: str) -> list[str]:
|
|
84
|
+
items: list[str] = []
|
|
85
|
+
current: list[str] = []
|
|
86
|
+
in_single = False
|
|
87
|
+
in_double = False
|
|
88
|
+
escaped = False
|
|
89
|
+
depth = 0
|
|
90
|
+
for char in text:
|
|
91
|
+
if escaped:
|
|
92
|
+
current.append(char)
|
|
93
|
+
escaped = False
|
|
94
|
+
continue
|
|
95
|
+
if char == "\\" and in_double:
|
|
96
|
+
current.append(char)
|
|
97
|
+
escaped = True
|
|
98
|
+
continue
|
|
99
|
+
if char == "'" and not in_double:
|
|
100
|
+
in_single = not in_single
|
|
101
|
+
current.append(char)
|
|
102
|
+
continue
|
|
103
|
+
if char == '"' and not in_single:
|
|
104
|
+
in_double = not in_double
|
|
105
|
+
current.append(char)
|
|
106
|
+
continue
|
|
107
|
+
if char in "[{" and not in_single and not in_double:
|
|
108
|
+
depth += 1
|
|
109
|
+
current.append(char)
|
|
110
|
+
continue
|
|
111
|
+
if char in "]}" and not in_single and not in_double:
|
|
112
|
+
depth -= 1
|
|
113
|
+
current.append(char)
|
|
114
|
+
continue
|
|
115
|
+
if char == "," and not in_single and not in_double and depth == 0:
|
|
116
|
+
item = "".join(current).strip()
|
|
117
|
+
if item:
|
|
118
|
+
items.append(item)
|
|
119
|
+
current = []
|
|
120
|
+
continue
|
|
121
|
+
current.append(char)
|
|
122
|
+
tail = "".join(current).strip()
|
|
123
|
+
if tail:
|
|
124
|
+
items.append(tail)
|
|
125
|
+
return items
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _parse_scalar(text: str) -> Any:
|
|
129
|
+
if text == "":
|
|
130
|
+
return ""
|
|
131
|
+
if text[0] in {'"', "'"} and text[-1] == text[0]:
|
|
132
|
+
return text[1:-1]
|
|
133
|
+
lowered = text.lower()
|
|
134
|
+
if lowered in {"true", "yes", "on"}:
|
|
135
|
+
return True
|
|
136
|
+
if lowered in {"false", "no", "off"}:
|
|
137
|
+
return False
|
|
138
|
+
if lowered in {"null", "~"}:
|
|
139
|
+
return None
|
|
140
|
+
if text.startswith("[") and text.endswith("]"):
|
|
141
|
+
inner = text[1:-1].strip()
|
|
142
|
+
if not inner:
|
|
143
|
+
return []
|
|
144
|
+
return [_parse_scalar(item) for item in _split_flow_items(inner)]
|
|
145
|
+
if text.startswith("{") and text.endswith("}"):
|
|
146
|
+
inner = text[1:-1].strip()
|
|
147
|
+
if not inner:
|
|
148
|
+
return {}
|
|
149
|
+
out: dict[str, Any] = {}
|
|
150
|
+
for item in _split_flow_items(inner):
|
|
151
|
+
pair = _split_key_value(item)
|
|
152
|
+
if pair is None:
|
|
153
|
+
raise ValueError(f"Invalid flow mapping item: {item!r}")
|
|
154
|
+
out[str(_parse_scalar(pair[0]))] = _parse_scalar(pair[1])
|
|
155
|
+
return out
|
|
156
|
+
try:
|
|
157
|
+
if text.startswith("0") and text not in {"0", "0.0"} and not text.startswith("0."):
|
|
158
|
+
raise ValueError
|
|
159
|
+
return int(text)
|
|
160
|
+
except ValueError:
|
|
161
|
+
pass
|
|
162
|
+
try:
|
|
163
|
+
return float(text)
|
|
164
|
+
except ValueError:
|
|
165
|
+
return text
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class _Parser:
|
|
169
|
+
def __init__(self, lines: list[tuple[int, str]]):
|
|
170
|
+
self.lines = lines
|
|
171
|
+
self.index = 0
|
|
172
|
+
|
|
173
|
+
def parse(self) -> Any:
|
|
174
|
+
if not self.lines:
|
|
175
|
+
return None
|
|
176
|
+
return self._parse_block(self.lines[0][0])
|
|
177
|
+
|
|
178
|
+
def _parse_block(self, indent: int) -> Any:
|
|
179
|
+
if self.index >= len(self.lines):
|
|
180
|
+
return None
|
|
181
|
+
current_indent, content = self.lines[self.index]
|
|
182
|
+
if current_indent < indent:
|
|
183
|
+
return None
|
|
184
|
+
if content.startswith("- "):
|
|
185
|
+
return self._parse_list(indent)
|
|
186
|
+
return self._parse_mapping(indent)
|
|
187
|
+
|
|
188
|
+
def _parse_mapping(self, indent: int) -> dict[str, Any]:
|
|
189
|
+
out: dict[str, Any] = {}
|
|
190
|
+
while self.index < len(self.lines):
|
|
191
|
+
current_indent, content = self.lines[self.index]
|
|
192
|
+
if current_indent < indent:
|
|
193
|
+
break
|
|
194
|
+
if current_indent != indent or content.startswith("- "):
|
|
195
|
+
break
|
|
196
|
+
pair = _split_key_value(content)
|
|
197
|
+
if pair is None:
|
|
198
|
+
raise ValueError(f"Invalid mapping line: {content!r}")
|
|
199
|
+
raw_key, raw_value = pair
|
|
200
|
+
key = str(_parse_scalar(raw_key))
|
|
201
|
+
self.index += 1
|
|
202
|
+
if raw_value:
|
|
203
|
+
out[key] = _parse_scalar(raw_value)
|
|
204
|
+
continue
|
|
205
|
+
if self.index < len(self.lines) and self.lines[self.index][0] > current_indent:
|
|
206
|
+
out[key] = self._parse_block(self.lines[self.index][0])
|
|
207
|
+
else:
|
|
208
|
+
out[key] = None
|
|
209
|
+
return out
|
|
210
|
+
|
|
211
|
+
def _parse_list(self, indent: int) -> list[Any]:
|
|
212
|
+
out: list[Any] = []
|
|
213
|
+
while self.index < len(self.lines):
|
|
214
|
+
current_indent, content = self.lines[self.index]
|
|
215
|
+
if current_indent < indent:
|
|
216
|
+
break
|
|
217
|
+
if current_indent != indent or not content.startswith("- "):
|
|
218
|
+
break
|
|
219
|
+
item_content = content[2:].strip()
|
|
220
|
+
self.index += 1
|
|
221
|
+
if not item_content:
|
|
222
|
+
if self.index < len(self.lines) and self.lines[self.index][0] > current_indent:
|
|
223
|
+
out.append(self._parse_block(self.lines[self.index][0]))
|
|
224
|
+
else:
|
|
225
|
+
out.append(None)
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
pair = _split_key_value(item_content)
|
|
229
|
+
if pair is not None and pair[0] and " " not in pair[0]:
|
|
230
|
+
raw_key, raw_value = pair
|
|
231
|
+
item: dict[str, Any] = {str(_parse_scalar(raw_key)): _parse_scalar(raw_value) if raw_value else None}
|
|
232
|
+
if not raw_value and self.index < len(self.lines) and self.lines[self.index][0] > current_indent:
|
|
233
|
+
item[str(_parse_scalar(raw_key))] = self._parse_block(self.lines[self.index][0])
|
|
234
|
+
if self.index < len(self.lines) and self.lines[self.index][0] > current_indent:
|
|
235
|
+
extra = self._parse_block(self.lines[self.index][0])
|
|
236
|
+
if isinstance(extra, dict):
|
|
237
|
+
item.update(extra)
|
|
238
|
+
out.append(item)
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
out.append(_parse_scalar(item_content))
|
|
242
|
+
return out
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def safe_load(stream: Any) -> Any:
|
|
246
|
+
text = _read_text(stream)
|
|
247
|
+
stripped = text.strip()
|
|
248
|
+
if not stripped:
|
|
249
|
+
return None
|
|
250
|
+
if stripped[0] in {"{", "["}:
|
|
251
|
+
try:
|
|
252
|
+
return json.loads(stripped)
|
|
253
|
+
except json.JSONDecodeError:
|
|
254
|
+
pass
|
|
255
|
+
return _Parser(_prepare_lines(text)).parse()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _dump_scalar(value: Any) -> str:
|
|
259
|
+
if value is None:
|
|
260
|
+
return "null"
|
|
261
|
+
if isinstance(value, bool):
|
|
262
|
+
return "true" if value else "false"
|
|
263
|
+
if isinstance(value, (int, float)):
|
|
264
|
+
return str(value)
|
|
265
|
+
return json.dumps(str(value), ensure_ascii=False)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _dump_lines(value: Any, indent: int) -> list[str]:
|
|
269
|
+
prefix = " " * indent
|
|
270
|
+
if isinstance(value, dict):
|
|
271
|
+
lines: list[str] = []
|
|
272
|
+
for key, item in value.items():
|
|
273
|
+
if isinstance(item, (dict, list)):
|
|
274
|
+
lines.append(f"{prefix}{key}:")
|
|
275
|
+
lines.extend(_dump_lines(item, indent + 2))
|
|
276
|
+
else:
|
|
277
|
+
lines.append(f"{prefix}{key}: {_dump_scalar(item)}")
|
|
278
|
+
return lines
|
|
279
|
+
if isinstance(value, list):
|
|
280
|
+
lines = []
|
|
281
|
+
for item in value:
|
|
282
|
+
if isinstance(item, dict):
|
|
283
|
+
nested = _dump_lines(item, indent + 2)
|
|
284
|
+
if nested:
|
|
285
|
+
first = nested[0].strip()
|
|
286
|
+
lines.append(f"{prefix}- {first}")
|
|
287
|
+
for line in nested[1:]:
|
|
288
|
+
lines.append(line)
|
|
289
|
+
else:
|
|
290
|
+
lines.append(f"{prefix}- {{}}")
|
|
291
|
+
elif isinstance(item, list):
|
|
292
|
+
lines.append(f"{prefix}-")
|
|
293
|
+
lines.extend(_dump_lines(item, indent + 2))
|
|
294
|
+
else:
|
|
295
|
+
lines.append(f"{prefix}- {_dump_scalar(item)}")
|
|
296
|
+
return lines
|
|
297
|
+
return [f"{prefix}{_dump_scalar(value)}"]
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def safe_dump(
|
|
301
|
+
data: Any,
|
|
302
|
+
stream: io.TextIOBase | None = None,
|
|
303
|
+
default_flow_style: bool | None = None,
|
|
304
|
+
sort_keys: bool = False,
|
|
305
|
+
**_: Any,
|
|
306
|
+
) -> str | None:
|
|
307
|
+
_ = default_flow_style
|
|
308
|
+
dump_data = data
|
|
309
|
+
if sort_keys and isinstance(data, dict):
|
|
310
|
+
dump_data = dict(sorted(data.items()))
|
|
311
|
+
text = "\n".join(_dump_lines(dump_data, 0)) + "\n"
|
|
312
|
+
if stream is not None:
|
|
313
|
+
stream.write(text)
|
|
314
|
+
return None
|
|
315
|
+
return text
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
dump = safe_dump
|
|
319
|
+
load = safe_load
|
|
320
|
+
|
|
321
|
+
__all__ = ["dump", "load", "safe_dump", "safe_load"]
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
-
PLUGIN_DIR="$(dirname "$SCRIPT_DIR")"
|
|
6
|
-
OMG_ROOT="$(dirname "$PLUGIN_DIR")"
|
|
7
|
-
|
|
8
|
-
echo "Installing OMG Bun plugin..."
|
|
9
|
-
exec bash "$OMG_ROOT/OMG-setup.sh" install --install-as-plugin --non-interactive
|
package/bun.lock
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"lockfileVersion": 1,
|
|
3
|
-
"configVersion": 1,
|
|
4
|
-
"workspaces": {
|
|
5
|
-
"": {
|
|
6
|
-
"name": "@trac3er/oh-my-god",
|
|
7
|
-
"devDependencies": {
|
|
8
|
-
"@types/node": "^25.3.5",
|
|
9
|
-
"bun-types": "^1.3.10",
|
|
10
|
-
"typescript": "^5.9.2",
|
|
11
|
-
},
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
"packages": {
|
|
15
|
-
"@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="],
|
|
16
|
-
|
|
17
|
-
"bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
|
|
18
|
-
|
|
19
|
-
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
20
|
-
|
|
21
|
-
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
|
22
|
-
}
|
|
23
|
-
}
|
package/bunfig.toml
DELETED
package/hooks/_budget.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const BUDGET_PROMPT_TOTAL = 1000;
|
package/hooks/_common.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
|
|
4
|
-
export function resolveProjectDir(): string {
|
|
5
|
-
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function ensureDir(path: string): void {
|
|
9
|
-
mkdirSync(path, { recursive: true });
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function ensureParent(path: string): void {
|
|
13
|
-
ensureDir(dirname(path));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function readJsonFile<T>(path: string, fallback: T): T {
|
|
17
|
-
try {
|
|
18
|
-
if (!existsSync(path)) {
|
|
19
|
-
return fallback;
|
|
20
|
-
}
|
|
21
|
-
return JSON.parse(readFileSync(path, "utf8")) as T;
|
|
22
|
-
} catch {
|
|
23
|
-
return fallback;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function writeJsonFile(path: string, value: unknown): void {
|
|
28
|
-
ensureParent(path);
|
|
29
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export async function readStdin(): Promise<string> {
|
|
33
|
-
return await Bun.stdin.text();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function readJsonFromStdin<T>(fallback: T): Promise<T> {
|
|
37
|
-
try {
|
|
38
|
-
const text = await readStdin();
|
|
39
|
-
if (!text.trim()) {
|
|
40
|
-
return fallback;
|
|
41
|
-
}
|
|
42
|
-
return JSON.parse(text) as T;
|
|
43
|
-
} catch {
|
|
44
|
-
return fallback;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function getFeatureFlag(name: string, defaultValue = true): boolean {
|
|
49
|
-
const key = `OMG_${name.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase()}_ENABLED`;
|
|
50
|
-
const raw = process.env[key];
|
|
51
|
-
if (raw == null || raw === "") {
|
|
52
|
-
return defaultValue;
|
|
53
|
-
}
|
|
54
|
-
return /^(1|true|yes|on)$/i.test(raw);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function ledgerPath(projectDir: string, filename: string): string {
|
|
58
|
-
return join(projectDir, ".omg", "state", "ledger", filename);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function noopHookMain(): void {
|
|
62
|
-
process.exit(0);
|
|
63
|
-
}
|
package/hooks/circuit-breaker.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { readJsonFile, readJsonFromStdin, writeJsonFile, ledgerPath, resolveProjectDir, ensureDir } from "./_common.ts";
|
|
3
|
-
|
|
4
|
-
type TrackerEntry = {
|
|
5
|
-
count: number;
|
|
6
|
-
last_failure: string;
|
|
7
|
-
errors: string[];
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
type Tracker = Record<string, TrackerEntry>;
|
|
11
|
-
|
|
12
|
-
function normalizeCommand(command: string): string {
|
|
13
|
-
return command
|
|
14
|
-
.trim()
|
|
15
|
-
.replace(/\s+/g, " ")
|
|
16
|
-
.replace(/\bnpm run test\b/g, "npm test")
|
|
17
|
-
.replace(/\bpnpm\b/g, "npm")
|
|
18
|
-
.replace(/\byarn\b/g, "npm")
|
|
19
|
-
.replace(/\bbunx\b/g, "")
|
|
20
|
-
.replace(/\bbun\s+run\s+test\b/g, "bun test")
|
|
21
|
-
.trim();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function patternFor(payload: any): string {
|
|
25
|
-
const tool = String(payload.tool_name || "");
|
|
26
|
-
if (tool === "Bash") {
|
|
27
|
-
return `Bash:${normalizeCommand(String(payload.tool_input?.command || ""))}`;
|
|
28
|
-
}
|
|
29
|
-
if (tool === "Write" || tool === "Edit" || tool === "MultiEdit") {
|
|
30
|
-
return `${tool}:${String(payload.tool_input?.file_path || payload.tool_input?.filePath || "")}`;
|
|
31
|
-
}
|
|
32
|
-
return tool;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function loadTracker(projectDir: string): Tracker {
|
|
36
|
-
return readJsonFile(ledgerPath(projectDir, "failure-tracker.json"), {});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function saveTracker(projectDir: string, tracker: Tracker): void {
|
|
40
|
-
ensureDir(ledgerPath(projectDir, "."));
|
|
41
|
-
writeJsonFile(ledgerPath(projectDir, "failure-tracker.json"), tracker);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function isFailure(payload: any): { failed: boolean; error: string } {
|
|
45
|
-
const tool = String(payload.tool_name || "");
|
|
46
|
-
if (tool === "Bash") {
|
|
47
|
-
const exitCode = Number(payload.tool_response?.exitCode ?? payload.tool_response?.exit_code ?? 0);
|
|
48
|
-
return {
|
|
49
|
-
failed: exitCode !== 0,
|
|
50
|
-
error: String(payload.tool_response?.stderr || payload.tool_response?.stdout || `exit ${exitCode}`)
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
if (tool === "Write" || tool === "Edit" || tool === "MultiEdit") {
|
|
54
|
-
const success = payload.tool_response?.success;
|
|
55
|
-
return { failed: success === false, error: "write failed" };
|
|
56
|
-
}
|
|
57
|
-
return { failed: false, error: "" };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function handleCircuitBreaker(payload: any, projectDir = resolveProjectDir()): void {
|
|
61
|
-
const pattern = patternFor(payload);
|
|
62
|
-
if (!pattern) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const tracker = loadTracker(projectDir);
|
|
66
|
-
const { failed, error } = isFailure(payload);
|
|
67
|
-
|
|
68
|
-
if (failed) {
|
|
69
|
-
const entry = tracker[pattern] || { count: 0, last_failure: "", errors: [] };
|
|
70
|
-
entry.count += 1;
|
|
71
|
-
entry.last_failure = new Date().toISOString();
|
|
72
|
-
entry.errors = [...entry.errors.slice(-4), error].filter(Boolean);
|
|
73
|
-
tracker[pattern] = entry;
|
|
74
|
-
saveTracker(projectDir, tracker);
|
|
75
|
-
if (entry.count === 3) {
|
|
76
|
-
process.stderr.write(`CIRCUIT BREAKER WARNING: repeated failure for ${pattern}\n`);
|
|
77
|
-
}
|
|
78
|
-
if (entry.count >= 5) {
|
|
79
|
-
process.stderr.write(`CIRCUIT BREAKER ESCALATE: ${pattern} failed ${entry.count} times\n`);
|
|
80
|
-
}
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (tracker[pattern]) {
|
|
85
|
-
delete tracker[pattern];
|
|
86
|
-
saveTracker(projectDir, tracker);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function main() {
|
|
91
|
-
const payload = await readJsonFromStdin<any>({});
|
|
92
|
-
try {
|
|
93
|
-
handleCircuitBreaker(payload);
|
|
94
|
-
} catch {
|
|
95
|
-
// Hooks must never crash the host flow.
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (import.meta.main) {
|
|
100
|
-
await main();
|
|
101
|
-
}
|
package/hooks/config-guard.ts
DELETED
package/hooks/firewall.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { readJsonFromStdin } from "./_common.ts";
|
|
3
|
-
import { evaluateBashCommand } from "./policy_engine.ts";
|
|
4
|
-
|
|
5
|
-
async function main() {
|
|
6
|
-
const payload = await readJsonFromStdin<any>({});
|
|
7
|
-
const command = String(payload.tool_input?.command || "");
|
|
8
|
-
const decision = evaluateBashCommand(command);
|
|
9
|
-
if (decision.action !== "allow") {
|
|
10
|
-
process.stdout.write(`${JSON.stringify(decision, null, 2)}\n`);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (import.meta.main) {
|
|
15
|
-
try {
|
|
16
|
-
await main();
|
|
17
|
-
} catch {
|
|
18
|
-
process.exit(0);
|
|
19
|
-
}
|
|
20
|
-
}
|