@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.
Files changed (243) hide show
  1. package/.claude-plugin/marketplace.json +8 -8
  2. package/.claude-plugin/plugin.json +5 -4
  3. package/.claude-plugin/scripts/uninstall.sh +74 -3
  4. package/.claude-plugin/scripts/update.sh +78 -3
  5. package/.coveragerc +26 -0
  6. package/.mcp.json +4 -4
  7. package/CHANGELOG.md +14 -0
  8. package/CODE_OF_CONDUCT.md +27 -0
  9. package/CONTRIBUTING.md +62 -0
  10. package/OMG-setup.sh +1201 -355
  11. package/README.md +77 -56
  12. package/SECURITY.md +25 -0
  13. package/agents/__init__.py +1 -0
  14. package/agents/model_roles.py +196 -0
  15. package/agents/omg-architect-mode.md +3 -5
  16. package/agents/omg-backend-engineer.md +3 -5
  17. package/agents/omg-database-engineer.md +3 -5
  18. package/agents/omg-frontend-designer.md +4 -5
  19. package/agents/omg-implement-mode.md +4 -5
  20. package/agents/omg-infra-engineer.md +3 -5
  21. package/agents/omg-research-mode.md +4 -6
  22. package/agents/omg-security-auditor.md +3 -5
  23. package/agents/omg-testing-engineer.md +3 -5
  24. package/build/lib/yaml.py +321 -0
  25. package/commands/OMG:ai-commit.md +101 -14
  26. package/commands/OMG:arch.md +302 -19
  27. package/commands/OMG:ccg.md +12 -7
  28. package/commands/OMG:compat.md +25 -17
  29. package/commands/OMG:cost.md +173 -13
  30. package/commands/OMG:crazy.md +1 -1
  31. package/commands/OMG:create-agent.md +170 -20
  32. package/commands/OMG:deps.md +235 -17
  33. package/commands/OMG:domain-init.md +1 -1
  34. package/commands/OMG:escalate.md +41 -12
  35. package/commands/OMG:health-check.md +37 -13
  36. package/commands/OMG:init.md +122 -14
  37. package/commands/OMG:project-init.md +1 -1
  38. package/commands/OMG:session-branch.md +76 -9
  39. package/commands/OMG:session-fork.md +42 -5
  40. package/commands/OMG:session-merge.md +124 -8
  41. package/commands/OMG:setup.md +69 -12
  42. package/commands/OMG:stats.md +215 -14
  43. package/commands/OMG:teams.md +19 -10
  44. package/config/lsp_languages.yaml +8 -0
  45. package/hooks/__init__.py +0 -0
  46. package/hooks/_agent_registry.py +423 -0
  47. package/hooks/_analytics.py +291 -0
  48. package/hooks/_budget.py +31 -0
  49. package/hooks/_common.py +569 -0
  50. package/hooks/_compression_optimizer.py +119 -0
  51. package/hooks/_cost_ledger.py +176 -0
  52. package/hooks/_learnings.py +126 -0
  53. package/hooks/_memory.py +103 -0
  54. package/hooks/_protected_context.py +150 -0
  55. package/hooks/_token_counter.py +221 -0
  56. package/hooks/branch_manager.py +236 -0
  57. package/hooks/budget_governor.py +232 -0
  58. package/hooks/circuit-breaker.py +270 -0
  59. package/hooks/compression_feedback.py +254 -0
  60. package/hooks/config-guard.py +216 -0
  61. package/hooks/context_pressure.py +53 -0
  62. package/hooks/credential_store.py +1020 -0
  63. package/hooks/fetch-rate-limits.py +212 -0
  64. package/hooks/firewall.py +48 -0
  65. package/hooks/hashline-formatter-bridge.py +224 -0
  66. package/hooks/hashline-injector.py +273 -0
  67. package/hooks/hashline-validator.py +216 -0
  68. package/hooks/idle-detector.py +95 -0
  69. package/hooks/intentgate-keyword-detector.py +188 -0
  70. package/hooks/magic-keyword-router.py +195 -0
  71. package/hooks/policy_engine.py +505 -0
  72. package/hooks/post-tool-failure.py +19 -0
  73. package/hooks/post-write.py +219 -0
  74. package/hooks/post_write.py +46 -0
  75. package/hooks/pre-compact.py +398 -0
  76. package/hooks/pre-tool-inject.py +98 -0
  77. package/hooks/prompt-enhancer.py +672 -0
  78. package/hooks/quality-runner.py +191 -0
  79. package/hooks/query.py +512 -0
  80. package/hooks/secret-guard.py +61 -0
  81. package/hooks/secret_audit.py +144 -0
  82. package/hooks/session-end-capture.py +137 -0
  83. package/hooks/session-start.py +277 -0
  84. package/hooks/setup_wizard.py +582 -0
  85. package/hooks/shadow_manager.py +297 -0
  86. package/hooks/state_migration.py +225 -0
  87. package/hooks/stop-gate.py +7 -0
  88. package/hooks/stop_dispatcher.py +945 -0
  89. package/hooks/test-validator.py +361 -0
  90. package/hooks/test_generator_hook.py +123 -0
  91. package/hooks/todo-state-tracker.py +114 -0
  92. package/hooks/tool-ledger.py +149 -0
  93. package/hooks/trust_review.py +585 -0
  94. package/hud/omg-hud.mjs +31 -1
  95. package/lab/__init__.py +1 -0
  96. package/lab/pipeline.py +75 -0
  97. package/lab/policies.py +52 -0
  98. package/package.json +7 -18
  99. package/plugins/README.md +33 -61
  100. package/plugins/advanced/commands/OMG:deep-plan.md +3 -3
  101. package/plugins/advanced/commands/OMG:learn.md +1 -1
  102. package/plugins/advanced/commands/OMG:security-review.md +3 -3
  103. package/plugins/advanced/commands/OMG:ship.md +1 -1
  104. package/plugins/advanced/plugin.json +1 -1
  105. package/plugins/core/plugin.json +8 -3
  106. package/plugins/dephealth/__init__.py +0 -0
  107. package/plugins/dephealth/cve_scanner.py +188 -0
  108. package/plugins/dephealth/license_checker.py +135 -0
  109. package/plugins/dephealth/manifest_detector.py +423 -0
  110. package/plugins/dephealth/vuln_analyzer.py +169 -0
  111. package/plugins/testgen/__init__.py +0 -0
  112. package/plugins/testgen/codamosa_engine.py +402 -0
  113. package/plugins/testgen/edge_case_synthesizer.py +184 -0
  114. package/plugins/testgen/framework_detector.py +271 -0
  115. package/plugins/testgen/skeleton_generator.py +219 -0
  116. package/plugins/viz/__init__.py +0 -0
  117. package/plugins/viz/ast_parser.py +139 -0
  118. package/plugins/viz/diagram_generator.py +192 -0
  119. package/plugins/viz/graph_builder.py +444 -0
  120. package/plugins/viz/native_parsers.py +259 -0
  121. package/plugins/viz/regex_parser.py +112 -0
  122. package/pyproject.toml +81 -0
  123. package/rules/contextual/write-verify.md +2 -2
  124. package/rules/core/00-truth.md +1 -1
  125. package/rules/core/01-surgical.md +1 -1
  126. package/rules/core/02-circuit-breaker.md +2 -2
  127. package/rules/core/03-ensemble.md +3 -3
  128. package/rules/core/04-testing.md +3 -3
  129. package/runtime/__init__.py +32 -0
  130. package/runtime/adapters/__init__.py +13 -0
  131. package/runtime/adapters/claude.py +60 -0
  132. package/runtime/adapters/gpt.py +53 -0
  133. package/runtime/adapters/local.py +53 -0
  134. package/runtime/adoption.py +212 -0
  135. package/runtime/business_workflow.py +220 -0
  136. package/runtime/cli_provider.py +85 -0
  137. package/runtime/compat.py +1299 -0
  138. package/runtime/custom_agent_loader.py +366 -0
  139. package/runtime/dispatcher.py +47 -0
  140. package/runtime/ecosystem.py +371 -0
  141. package/runtime/legacy_compat.py +7 -0
  142. package/runtime/mcp_config_writers.py +115 -0
  143. package/runtime/mcp_lifecycle.py +153 -0
  144. package/runtime/mcp_memory_server.py +135 -0
  145. package/runtime/memory_parsers/__init__.py +0 -0
  146. package/runtime/memory_parsers/chatgpt_parser.py +257 -0
  147. package/runtime/memory_parsers/claude_import.py +107 -0
  148. package/runtime/memory_parsers/export.py +97 -0
  149. package/runtime/memory_parsers/gemini_import.py +91 -0
  150. package/runtime/memory_parsers/kimi_import.py +91 -0
  151. package/runtime/memory_store.py +215 -0
  152. package/runtime/omc_compat.py +7 -0
  153. package/runtime/providers/__init__.py +0 -0
  154. package/runtime/providers/codex_provider.py +112 -0
  155. package/runtime/providers/gemini_provider.py +128 -0
  156. package/runtime/providers/kimi_provider.py +151 -0
  157. package/runtime/providers/opencode_provider.py +144 -0
  158. package/runtime/subagent_dispatcher.py +362 -0
  159. package/runtime/team_router.py +1167 -0
  160. package/runtime/tmux_session_manager.py +169 -0
  161. package/scripts/check-omg-compat-contract-snapshot.py +137 -0
  162. package/scripts/check-omg-contract-snapshot.py +12 -0
  163. package/scripts/check-omg-public-ready.py +193 -0
  164. package/scripts/check-omg-standalone-clean.py +103 -0
  165. package/scripts/legacy_to_omg_migrate.py +29 -0
  166. package/scripts/migrate-legacy.py +464 -0
  167. package/scripts/omc_to_omg_migrate.py +12 -0
  168. package/scripts/omg.py +492 -0
  169. package/scripts/settings-merge.py +283 -0
  170. package/scripts/verify-standalone.sh +8 -4
  171. package/settings.json +126 -29
  172. package/templates/profile.yaml +1 -1
  173. package/tools/__init__.py +2 -0
  174. package/tools/browser_consent.py +289 -0
  175. package/tools/browser_stealth.py +481 -0
  176. package/tools/browser_tool.py +448 -0
  177. package/tools/changelog_generator.py +347 -0
  178. package/tools/commit_splitter.py +746 -0
  179. package/tools/config_discovery.py +151 -0
  180. package/tools/config_merger.py +449 -0
  181. package/tools/dashboard_generator.py +300 -0
  182. package/tools/git_inspector.py +298 -0
  183. package/tools/lsp_client.py +275 -0
  184. package/tools/lsp_discovery.py +231 -0
  185. package/tools/lsp_operations.py +392 -0
  186. package/tools/pr_generator.py +404 -0
  187. package/tools/python_repl.py +656 -0
  188. package/tools/python_sandbox.py +609 -0
  189. package/tools/search_providers/__init__.py +77 -0
  190. package/tools/search_providers/brave.py +115 -0
  191. package/tools/search_providers/exa.py +116 -0
  192. package/tools/search_providers/jina.py +104 -0
  193. package/tools/search_providers/perplexity.py +139 -0
  194. package/tools/search_providers/synthetic.py +74 -0
  195. package/tools/session_snapshot.py +736 -0
  196. package/tools/ssh_manager.py +912 -0
  197. package/tools/theme_engine.py +294 -0
  198. package/tools/theme_selector.py +137 -0
  199. package/tools/web_search.py +622 -0
  200. package/yaml.py +321 -0
  201. package/.claude-plugin/scripts/install.sh +0 -9
  202. package/bun.lock +0 -23
  203. package/bunfig.toml +0 -3
  204. package/hooks/_budget.ts +0 -1
  205. package/hooks/_common.ts +0 -63
  206. package/hooks/circuit-breaker.ts +0 -101
  207. package/hooks/config-guard.ts +0 -4
  208. package/hooks/firewall.ts +0 -20
  209. package/hooks/policy_engine.ts +0 -156
  210. package/hooks/post-tool-failure.ts +0 -22
  211. package/hooks/post-write.ts +0 -4
  212. package/hooks/pre-tool-inject.ts +0 -4
  213. package/hooks/prompt-enhancer.ts +0 -46
  214. package/hooks/quality-runner.ts +0 -24
  215. package/hooks/secret-guard.ts +0 -4
  216. package/hooks/session-end-capture.ts +0 -19
  217. package/hooks/session-start.ts +0 -19
  218. package/hooks/shadow_manager.ts +0 -81
  219. package/hooks/stop-gate.ts +0 -22
  220. package/hooks/stop_dispatcher.ts +0 -147
  221. package/hooks/test-generator-hook.ts +0 -4
  222. package/hooks/tool-ledger.ts +0 -27
  223. package/hooks/trust_review.ts +0 -175
  224. package/lab/pipeline.ts +0 -75
  225. package/lab/policies.ts +0 -68
  226. package/runtime/common.ts +0 -111
  227. package/runtime/compat.ts +0 -174
  228. package/runtime/dispatcher.ts +0 -25
  229. package/runtime/ecosystem.ts +0 -186
  230. package/runtime/provider_bootstrap.ts +0 -99
  231. package/runtime/provider_smoke.ts +0 -34
  232. package/runtime/release_readiness.ts +0 -186
  233. package/runtime/team_router.ts +0 -144
  234. package/scripts/check-omg-compat-contract-snapshot.ts +0 -20
  235. package/scripts/check-omg-standalone-clean.ts +0 -12
  236. package/scripts/check-runtime-clean.ts +0 -94
  237. package/scripts/omg.ts +0 -352
  238. package/scripts/settings-merge.ts +0 -93
  239. package/tools/commit_splitter.ts +0 -23
  240. package/tools/git_inspector.ts +0 -18
  241. package/tools/session_snapshot.ts +0 -47
  242. package/trac3er-oh-my-god-2.0.0.tgz +0 -0
  243. package/tsconfig.json +0 -15
@@ -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,26 +1,113 @@
1
1
  ---
2
- description: "Split staged work into coherent commits with the Bun commit tools."
3
- allowed-tools: Read, Grep, Glob, Bash(git:*), Bash(rg:*), Bash(bun:*)
4
- argument-hint: "[--dry-run] [optional scope]"
2
+ description: "Analyze uncommitted git changes and propose logical atomic commits grouped by concern."
3
+ allowed-tools: Read, Bash(python*:*), Grep, Glob
4
+ argument-hint: "[optional: working directory path]"
5
5
  ---
6
6
 
7
- # /OMG:ai-commit
7
+ # /OMG:ai-commit — AI Commit Splitter
8
8
 
9
- Use this command when staged work needs to be grouped into reviewable commits before release.
9
+ Analyze uncommitted git changes and propose logical atomic commits grouped by concern.
10
10
 
11
- ## Tooling
11
+ ## Usage
12
12
 
13
- - inspect hunks with `tools/git_inspector.ts`
14
- - draft commit groupings with `tools/commit_splitter.ts`
13
+ ```
14
+ /OMG:ai-commit
15
+ ```
16
+
17
+ ## What It Does
18
+
19
+ 1. Reads hunk-level git diffs via `git_hunk()` from `tools/git_inspector.py`
20
+ 2. Groups hunks by file type/category (python, javascript, config, docs, tests, etc.)
21
+ 3. Separates test files from source code automatically
22
+ 4. Generates conventional commit messages: `{type}({scope}): {description}`
23
+ 5. Displays a human-readable preview of proposed commits
24
+
25
+ ## Feature Flag
26
+
27
+ - **Flag name**: `OMG_AI_COMMIT_ENABLED`
28
+ - **Default**: `False` (disabled)
29
+ - **Enable**: `export OMG_AI_COMMIT_ENABLED=1`
30
+
31
+ Or set in `settings.json`:
32
+
33
+ ```json
34
+ {
35
+ "_omg": {
36
+ "features": {
37
+ "ai_commit": true
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ ## Output Example
44
+
45
+ ```
46
+ ============================================================
47
+ OMG AI Commit Splitter — Proposed Commit Plan
48
+ ============================================================
49
+
50
+ Total proposed commits: 3
15
51
 
16
- ## Example
52
+ Commit 1: feat(tools): update commit_splitter.py
53
+ ──────────────────────────────────────────────────
54
+ • tools/commit_splitter.py
55
+ (2 hunks)
56
+
57
+ Commit 2: test(tests): update 2 test files
58
+ ──────────────────────────────────────────────────
59
+ • tests/tools/test_commit_splitter.py
60
+ • tests/tools/test_git_inspector.py
61
+ (4 hunks)
62
+
63
+ Commit 3: docs(commands): update ai-commit.md
64
+ ──────────────────────────────────────────────────
65
+ • commands/ai-commit.md
66
+ (1 hunk)
67
+
68
+ ============================================================
69
+ NOTE: This is a preview only. No commits were made.
70
+ ============================================================
71
+ ```
72
+
73
+ ## Commit Type Mapping
74
+
75
+ | Category | Default Type | Description |
76
+ |------------|-------------|--------------------------|
77
+ | python | `feat` | Python source files |
78
+ | javascript | `feat` | JS/TS source files |
79
+ | tests | `test` | Test files (any language) |
80
+ | docs | `docs` | Documentation files |
81
+ | config | `chore` | Configuration files |
82
+ | shell | `chore` | Shell scripts |
83
+ | styles | `style` | CSS/SCSS/LESS files |
84
+ | markup | `feat` | HTML/XML/SVG files |
85
+ | other | `chore` | Unclassified files |
86
+
87
+ ## CLI Usage
17
88
 
18
89
  ```bash
19
- bun tools/commit_splitter.ts --dry-run
90
+ # Preview commit plan from terminal
91
+ python3 tools/commit_splitter.py --dry-run
20
92
  ```
21
93
 
22
- Expected outcome:
94
+ ## Safety
23
95
 
24
- - coherent commit groups
25
- - a short message per group
26
- - no unrelated files mixed into the same commit
96
+ - **Read-only**: Never executes `git commit` or modifies the working tree
97
+ - **Feature-gated**: Returns empty results when disabled
98
+ - **Non-destructive**: Safe to run at any time
99
+
100
+ ## API
101
+
102
+ ```python
103
+ from tools.commit_splitter import analyze_changes, generate_commit_plan, preview_commit_plan
104
+
105
+ # Get grouped hunks
106
+ groups = analyze_changes(cwd=".")
107
+
108
+ # Get full commit plan with messages
109
+ plan = generate_commit_plan(cwd=".")
110
+
111
+ # Get human-readable preview string
112
+ preview = preview_commit_plan(cwd=".")
113
+ ```