@trac3er/oh-my-god 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/.claude-plugin/marketplace.json +36 -0
  2. package/.claude-plugin/plugin.json +23 -0
  3. package/.claude-plugin/scripts/install.sh +49 -0
  4. package/.claude-plugin/scripts/uninstall.sh +80 -0
  5. package/.claude-plugin/scripts/update.sh +84 -0
  6. package/.mcp.json +20 -0
  7. package/LICENSE +21 -0
  8. package/OMG-setup.sh +1093 -0
  9. package/README.md +335 -0
  10. package/THIRD_PARTY_NOTICES.md +24 -0
  11. package/UPSTREAM_DIFF.md +20 -0
  12. package/agents/__init__.py +1 -0
  13. package/agents/_model_roles.yaml +26 -0
  14. package/agents/designer.md +67 -0
  15. package/agents/explore.md +60 -0
  16. package/agents/model_roles.py +196 -0
  17. package/agents/omg-api-builder.md +23 -0
  18. package/agents/omg-architect-mode.md +43 -0
  19. package/agents/omg-architect.md +13 -0
  20. package/agents/omg-backend-engineer.md +43 -0
  21. package/agents/omg-critic.md +16 -0
  22. package/agents/omg-database-engineer.md +43 -0
  23. package/agents/omg-escalation-router.md +17 -0
  24. package/agents/omg-executor.md +12 -0
  25. package/agents/omg-frontend-designer.md +42 -0
  26. package/agents/omg-implement-mode.md +50 -0
  27. package/agents/omg-infra-engineer.md +43 -0
  28. package/agents/omg-qa-tester.md +16 -0
  29. package/agents/omg-research-mode.md +43 -0
  30. package/agents/omg-security-auditor.md +43 -0
  31. package/agents/omg-testing-engineer.md +43 -0
  32. package/agents/plan.md +80 -0
  33. package/agents/quick_task.md +64 -0
  34. package/agents/reviewer.md +83 -0
  35. package/agents/task.md +71 -0
  36. package/commands/OMG:ccg.md +22 -0
  37. package/commands/OMG:compat.md +57 -0
  38. package/commands/OMG:crazy.md +125 -0
  39. package/commands/OMG:domain-init.md +11 -0
  40. package/commands/OMG:escalate.md +52 -0
  41. package/commands/OMG:health-check.md +45 -0
  42. package/commands/OMG:init.md +134 -0
  43. package/commands/OMG:mode.md +44 -0
  44. package/commands/OMG:project-init.md +11 -0
  45. package/commands/OMG:ralph-start.md +43 -0
  46. package/commands/OMG:ralph-stop.md +23 -0
  47. package/commands/OMG:teams.md +39 -0
  48. package/commands/ai-commit.md +113 -0
  49. package/commands/ccg.md +9 -0
  50. package/commands/create-agent.md +183 -0
  51. package/commands/omc-teams.md +9 -0
  52. package/commands/session-branch.md +85 -0
  53. package/commands/session-fork.md +53 -0
  54. package/commands/session-merge.md +134 -0
  55. package/commands/theme.md +44 -0
  56. package/config/lsp_languages.yaml +324 -0
  57. package/config/themes/catppuccin-frappe.yaml +14 -0
  58. package/config/themes/catppuccin-latte.yaml +14 -0
  59. package/config/themes/catppuccin-macchiato.yaml +14 -0
  60. package/config/themes/catppuccin-mocha.yaml +14 -0
  61. package/config/themes/dracula.yaml +14 -0
  62. package/config/themes/gruvbox-dark.yaml +14 -0
  63. package/config/themes/nord.yaml +14 -0
  64. package/config/themes/one-dark.yaml +14 -0
  65. package/config/themes/solarized-dark.yaml +14 -0
  66. package/config/themes/tokyo-night.yaml +14 -0
  67. package/control_plane/__init__.py +2 -0
  68. package/control_plane/openapi.yaml +109 -0
  69. package/control_plane/server.py +107 -0
  70. package/control_plane/service.py +148 -0
  71. package/crates/omg-natives/Cargo.toml +17 -0
  72. package/crates/omg-natives/src/clipboard.rs +5 -0
  73. package/crates/omg-natives/src/glob.rs +15 -0
  74. package/crates/omg-natives/src/grep.rs +15 -0
  75. package/crates/omg-natives/src/highlight.rs +15 -0
  76. package/crates/omg-natives/src/html.rs +14 -0
  77. package/crates/omg-natives/src/image.rs +5 -0
  78. package/crates/omg-natives/src/keys.rs +5 -0
  79. package/crates/omg-natives/src/lib.rs +36 -0
  80. package/crates/omg-natives/src/prof.rs +5 -0
  81. package/crates/omg-natives/src/ps.rs +5 -0
  82. package/crates/omg-natives/src/shell.rs +5 -0
  83. package/crates/omg-natives/src/task.rs +5 -0
  84. package/crates/omg-natives/src/text.rs +14 -0
  85. package/hooks/_agent_registry.py +421 -0
  86. package/hooks/_budget.py +31 -0
  87. package/hooks/_common.py +476 -0
  88. package/hooks/_learnings.py +126 -0
  89. package/hooks/_memory.py +103 -0
  90. package/hooks/circuit-breaker.py +270 -0
  91. package/hooks/config-guard.py +163 -0
  92. package/hooks/context_pressure.py +53 -0
  93. package/hooks/credential_store.py +801 -0
  94. package/hooks/fetch-rate-limits.py +212 -0
  95. package/hooks/firewall.py +48 -0
  96. package/hooks/hashline-formatter-bridge.py +224 -0
  97. package/hooks/hashline-injector.py +273 -0
  98. package/hooks/hashline-validator.py +216 -0
  99. package/hooks/idle-detector.py +95 -0
  100. package/hooks/intentgate-keyword-detector.py +188 -0
  101. package/hooks/magic-keyword-router.py +195 -0
  102. package/hooks/policy_engine.py +310 -0
  103. package/hooks/post-tool-failure.py +19 -0
  104. package/hooks/post-write.py +199 -0
  105. package/hooks/pre-compact.py +204 -0
  106. package/hooks/pre-tool-inject.py +98 -0
  107. package/hooks/prompt-enhancer.py +672 -0
  108. package/hooks/quality-runner.py +191 -0
  109. package/hooks/secret-guard.py +47 -0
  110. package/hooks/session-end-capture.py +137 -0
  111. package/hooks/session-start.py +275 -0
  112. package/hooks/shadow_manager.py +297 -0
  113. package/hooks/state_migration.py +209 -0
  114. package/hooks/stop-gate.py +7 -0
  115. package/hooks/stop_dispatcher.py +929 -0
  116. package/hooks/test-validator.py +138 -0
  117. package/hooks/todo-state-tracker.py +114 -0
  118. package/hooks/tool-ledger.py +126 -0
  119. package/hooks/trust_review.py +524 -0
  120. package/install.sh +9 -0
  121. package/omg_natives/__init__.py +186 -0
  122. package/omg_natives/_bindings.py +165 -0
  123. package/omg_natives/clipboard.py +36 -0
  124. package/omg_natives/glob.py +42 -0
  125. package/omg_natives/grep.py +61 -0
  126. package/omg_natives/highlight.py +54 -0
  127. package/omg_natives/html.py +157 -0
  128. package/omg_natives/image.py +51 -0
  129. package/omg_natives/keys.py +46 -0
  130. package/omg_natives/prof.py +39 -0
  131. package/omg_natives/ps.py +93 -0
  132. package/omg_natives/shell.py +58 -0
  133. package/omg_natives/task.py +41 -0
  134. package/omg_natives/text.py +50 -0
  135. package/package.json +26 -0
  136. package/plugins/README.md +82 -0
  137. package/plugins/advanced/commands/OMG:code-review.md +114 -0
  138. package/plugins/advanced/commands/OMG:deep-plan.md +221 -0
  139. package/plugins/advanced/commands/OMG:handoff.md +115 -0
  140. package/plugins/advanced/commands/OMG:learn.md +110 -0
  141. package/plugins/advanced/commands/OMG:maintainer.md +31 -0
  142. package/plugins/advanced/commands/OMG:ralph-start.md +43 -0
  143. package/plugins/advanced/commands/OMG:ralph-stop.md +23 -0
  144. package/plugins/advanced/commands/OMG:security-review.md +119 -0
  145. package/plugins/advanced/commands/OMG:sequential-thinking.md +20 -0
  146. package/plugins/advanced/commands/OMG:ship.md +46 -0
  147. package/plugins/advanced/plugin.json +96 -0
  148. package/plugins/core/plugin.json +82 -0
  149. package/pytest.ini +5 -0
  150. package/registry/__init__.py +1 -0
  151. package/registry/verify_artifact.py +90 -0
  152. package/rules/contextual/architect-mode.md +9 -0
  153. package/rules/contextual/big-picture.md +20 -0
  154. package/rules/contextual/code-hygiene.md +26 -0
  155. package/rules/contextual/context-management.md +19 -0
  156. package/rules/contextual/context-minimization.md +32 -0
  157. package/rules/contextual/ddd-sdd.md +28 -0
  158. package/rules/contextual/dependency-safety.md +16 -0
  159. package/rules/contextual/doc-check.md +13 -0
  160. package/rules/contextual/implement-mode.md +9 -0
  161. package/rules/contextual/infra-safety.md +14 -0
  162. package/rules/contextual/outside-in.md +13 -0
  163. package/rules/contextual/persistent-mode.md +24 -0
  164. package/rules/contextual/research-mode.md +9 -0
  165. package/rules/contextual/security-domains.md +25 -0
  166. package/rules/contextual/vision-detection.md +27 -0
  167. package/rules/contextual/web-search.md +25 -0
  168. package/rules/contextual/write-verify.md +23 -0
  169. package/rules/core/00-truth.md +20 -0
  170. package/rules/core/01-surgical.md +19 -0
  171. package/rules/core/02-circuit-breaker.md +22 -0
  172. package/rules/core/03-ensemble.md +28 -0
  173. package/rules/core/04-testing.md +30 -0
  174. package/runtime/__init__.py +32 -0
  175. package/runtime/adapters/__init__.py +13 -0
  176. package/runtime/adapters/claude.py +60 -0
  177. package/runtime/adapters/gpt.py +53 -0
  178. package/runtime/adapters/local.py +53 -0
  179. package/runtime/business_workflow.py +220 -0
  180. package/runtime/compat.py +1299 -0
  181. package/runtime/custom_agent_loader.py +366 -0
  182. package/runtime/dispatcher.py +47 -0
  183. package/runtime/ecosystem.py +371 -0
  184. package/runtime/legacy_compat.py +7 -0
  185. package/runtime/omc_compat.py +7 -0
  186. package/runtime/omc_contract_snapshot.json +916 -0
  187. package/runtime/omg_compat_contract_snapshot.json +916 -0
  188. package/runtime/subagent_dispatcher.py +362 -0
  189. package/runtime/team_router.py +838 -0
  190. package/scripts/check-omc-contract-snapshot.py +12 -0
  191. package/scripts/check-omg-compat-contract-snapshot.py +137 -0
  192. package/scripts/check-omg-standalone-clean.py +102 -0
  193. package/scripts/legacy_to_omg_migrate.py +29 -0
  194. package/scripts/migrate-omc.py +464 -0
  195. package/scripts/omc_to_omg_migrate.py +12 -0
  196. package/scripts/omg.py +493 -0
  197. package/scripts/settings-merge.py +224 -0
  198. package/scripts/verify-no-omc.sh +5 -0
  199. package/scripts/verify-standalone.sh +21 -0
  200. package/templates/idea.yml +30 -0
  201. package/templates/policy.yaml +15 -0
  202. package/templates/profile.yaml +25 -0
  203. package/templates/runtime.yaml +12 -0
  204. package/templates/working-memory.md +17 -0
  205. package/tools/__init__.py +2 -0
  206. package/tools/browser_consent.py +289 -0
  207. package/tools/browser_stealth.py +481 -0
  208. package/tools/browser_tool.py +448 -0
  209. package/tools/changelog_generator.py +268 -0
  210. package/tools/commit_splitter.py +361 -0
  211. package/tools/config_discovery.py +151 -0
  212. package/tools/config_merger.py +449 -0
  213. package/tools/git_inspector.py +298 -0
  214. package/tools/lsp_client.py +275 -0
  215. package/tools/lsp_discovery.py +231 -0
  216. package/tools/lsp_operations.py +392 -0
  217. package/tools/python_repl.py +656 -0
  218. package/tools/python_sandbox.py +609 -0
  219. package/tools/search_providers/__init__.py +77 -0
  220. package/tools/search_providers/brave.py +115 -0
  221. package/tools/search_providers/exa.py +116 -0
  222. package/tools/search_providers/jina.py +104 -0
  223. package/tools/search_providers/perplexity.py +139 -0
  224. package/tools/search_providers/synthetic.py +74 -0
  225. package/tools/session_snapshot.py +736 -0
  226. package/tools/ssh_manager.py +912 -0
  227. package/tools/theme_engine.py +294 -0
  228. package/tools/theme_selector.py +137 -0
  229. package/tools/web_search.py +622 -0
@@ -0,0 +1,361 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ AI Commit Splitter for OMG
4
+
5
+ Analyzes git changes and groups them into logical atomic commits
6
+ with hunk-level staging support. Read-only analysis — never runs git commit.
7
+
8
+ Feature flag: OMG_AI_COMMIT_ENABLED (default: False)
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import sys
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ # Lazy imports for git_inspector and feature flags
17
+ _git_inspector = None
18
+ _get_feature_flag = None
19
+
20
+
21
+ def _ensure_imports():
22
+ """Lazy import git_inspector and feature flag helper."""
23
+ global _git_inspector, _get_feature_flag
24
+ if _git_inspector is None:
25
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
26
+ from tools import git_inspector as _gi
27
+ from hooks._common import get_feature_flag as _gff
28
+ _git_inspector = _gi
29
+ _get_feature_flag = _gff
30
+
31
+
32
+ def _is_enabled() -> bool:
33
+ """Check if AI commit splitter feature is enabled."""
34
+ _ensure_imports()
35
+ return _get_feature_flag("ai_commit", default=False)
36
+
37
+
38
+ # --- File Classification ---
39
+
40
+ # Extension → category mapping
41
+ _EXT_CATEGORY = {
42
+ # Python
43
+ ".py": "python",
44
+ ".pyi": "python",
45
+ ".pyx": "python",
46
+ # JavaScript/TypeScript
47
+ ".js": "javascript",
48
+ ".jsx": "javascript",
49
+ ".ts": "javascript",
50
+ ".tsx": "javascript",
51
+ ".mjs": "javascript",
52
+ ".cjs": "javascript",
53
+ # Config
54
+ ".json": "config",
55
+ ".yaml": "config",
56
+ ".yml": "config",
57
+ ".toml": "config",
58
+ ".ini": "config",
59
+ ".cfg": "config",
60
+ ".env": "config",
61
+ ".conf": "config",
62
+ ".properties": "config",
63
+ # Docs
64
+ ".md": "docs",
65
+ ".rst": "docs",
66
+ ".txt": "docs",
67
+ ".adoc": "docs",
68
+ # Shell
69
+ ".sh": "shell",
70
+ ".bash": "shell",
71
+ ".zsh": "shell",
72
+ # CSS/Styles
73
+ ".css": "styles",
74
+ ".scss": "styles",
75
+ ".less": "styles",
76
+ ".sass": "styles",
77
+ # HTML/Templates
78
+ ".html": "markup",
79
+ ".htm": "markup",
80
+ ".xml": "markup",
81
+ ".svg": "markup",
82
+ }
83
+
84
+ # Test path indicators
85
+ _TEST_INDICATORS = (
86
+ "test_",
87
+ "_test.",
88
+ "tests/",
89
+ "test/",
90
+ "__tests__/",
91
+ ".test.",
92
+ ".spec.",
93
+ "spec/",
94
+ "conftest.py",
95
+ )
96
+
97
+ # Category → default suggested commit type
98
+ _CATEGORY_DEFAULT_TYPE = {
99
+ "python": "feat",
100
+ "javascript": "feat",
101
+ "shell": "chore",
102
+ "config": "chore",
103
+ "docs": "docs",
104
+ "styles": "style",
105
+ "markup": "feat",
106
+ "tests": "test",
107
+ "other": "chore",
108
+ }
109
+
110
+
111
+ def _classify_file(file_path: str) -> str:
112
+ """Classify a file path into a category.
113
+
114
+ Test files are always classified as 'tests' regardless of extension.
115
+
116
+ Args:
117
+ file_path: Relative file path from git diff.
118
+
119
+ Returns:
120
+ Category string: 'python', 'javascript', 'config', 'docs',
121
+ 'tests', 'shell', 'styles', 'markup', or 'other'.
122
+ """
123
+ if file_path is None:
124
+ return "other"
125
+
126
+ lower_path = file_path.lower()
127
+
128
+ # Check for test files first — they always get their own group
129
+ for indicator in _TEST_INDICATORS:
130
+ if indicator in lower_path:
131
+ return "tests"
132
+
133
+ # Classify by extension
134
+ _, ext = os.path.splitext(lower_path)
135
+ return _EXT_CATEGORY.get(ext, "other")
136
+
137
+
138
+ def _derive_scope(files: List[str]) -> str:
139
+ """Derive a scope name from a list of files.
140
+
141
+ Uses the most common parent directory or module name.
142
+
143
+ Args:
144
+ files: List of file paths.
145
+
146
+ Returns:
147
+ Scope string suitable for conventional commit format.
148
+ """
149
+ if not files:
150
+ return "general"
151
+
152
+ # Collect directory components
153
+ dirs: List[str] = []
154
+ for f in files:
155
+ parts = f.replace("\\", "/").split("/")
156
+ if len(parts) > 1:
157
+ dirs.append(parts[0])
158
+ else:
159
+ # Single file at root — use filename without extension
160
+ name, _ = os.path.splitext(parts[0])
161
+ dirs.append(name)
162
+
163
+ if not dirs:
164
+ return "general"
165
+
166
+ # Most common directory
167
+ from collections import Counter
168
+ counts = Counter(dirs)
169
+ scope = counts.most_common(1)[0][0]
170
+ return scope
171
+
172
+
173
+ def _derive_description(category: str, files: List[str]) -> str:
174
+ """Generate a short description for a commit group.
175
+
176
+ Args:
177
+ category: File category (e.g., 'python', 'tests').
178
+ files: List of affected files.
179
+
180
+ Returns:
181
+ Human-readable description string.
182
+ """
183
+ n = len(files)
184
+ if category == "tests":
185
+ if n == 1:
186
+ return f"update test {os.path.basename(files[0])}"
187
+ return f"update {n} test files"
188
+ if category == "docs":
189
+ if n == 1:
190
+ return f"update {os.path.basename(files[0])}"
191
+ return f"update {n} documentation files"
192
+ if category == "config":
193
+ if n == 1:
194
+ return f"update {os.path.basename(files[0])}"
195
+ return f"update {n} config files"
196
+ # Source code
197
+ if n == 1:
198
+ return f"update {os.path.basename(files[0])}"
199
+ return f"update {n} {category} files"
200
+
201
+
202
+ # --- Public API ---
203
+
204
+
205
+ def analyze_changes(cwd: str = ".") -> List[Dict[str, Any]]:
206
+ """Analyze git changes and group hunks by logical concern.
207
+
208
+ Groups changes by file type/category, always separating test files
209
+ from source code. Returns an empty list when the feature flag
210
+ ``OMG_AI_COMMIT_ENABLED`` is ``False``.
211
+
212
+ Args:
213
+ cwd: Working directory (default: current directory).
214
+
215
+ Returns:
216
+ List of dicts, each with keys:
217
+ - group_name (str): Human-readable group name.
218
+ - files (list[str]): Affected file paths.
219
+ - hunks (list[dict]): Raw hunk dicts from git_hunk().
220
+ - suggested_type (str): Conventional commit type.
221
+ """
222
+ if not _is_enabled():
223
+ return []
224
+
225
+ _ensure_imports()
226
+ hunks = _git_inspector.git_hunk(cwd)
227
+
228
+ if not hunks:
229
+ return []
230
+
231
+ # Bucket hunks by category
232
+ buckets: Dict[str, Dict[str, Any]] = {}
233
+ for hunk in hunks:
234
+ file_path = hunk.get("file", "")
235
+ category = _classify_file(file_path)
236
+
237
+ if category not in buckets:
238
+ buckets[category] = {
239
+ "files_set": set(),
240
+ "hunks": [],
241
+ }
242
+ buckets[category]["files_set"].add(file_path)
243
+ buckets[category]["hunks"].append(hunk)
244
+
245
+ # Build result groups
246
+ groups: List[Dict[str, Any]] = []
247
+ for category, data in sorted(buckets.items()):
248
+ files = sorted(data["files_set"])
249
+ groups.append({
250
+ "group_name": category,
251
+ "files": files,
252
+ "hunks": data["hunks"],
253
+ "suggested_type": _CATEGORY_DEFAULT_TYPE.get(category, "chore"),
254
+ })
255
+
256
+ return groups
257
+
258
+
259
+ def generate_commit_plan(cwd: str = ".") -> Dict[str, Any]:
260
+ """Generate a full commit plan with proposed messages.
261
+
262
+ Calls ``analyze_changes()`` and builds conventional commit messages
263
+ for each group. Returns an empty plan when the feature flag is off.
264
+
265
+ Args:
266
+ cwd: Working directory (default: current directory).
267
+
268
+ Returns:
269
+ Dict with keys:
270
+ - groups (list[dict]): Raw groups from analyze_changes().
271
+ - proposed_commits (list[dict]): Each with ``message``,
272
+ ``files``, and ``hunks``.
273
+ - total_commits (int): Number of proposed commits.
274
+ """
275
+ groups = analyze_changes(cwd)
276
+
277
+ if not groups:
278
+ return {
279
+ "groups": [],
280
+ "proposed_commits": [],
281
+ "total_commits": 0,
282
+ }
283
+
284
+ proposed: List[Dict[str, Any]] = []
285
+ for group in groups:
286
+ commit_type = group["suggested_type"]
287
+ scope = _derive_scope(group["files"])
288
+ description = _derive_description(group["group_name"], group["files"])
289
+ message = f"{commit_type}({scope}): {description}"
290
+
291
+ proposed.append({
292
+ "message": message,
293
+ "files": group["files"],
294
+ "hunks": group["hunks"],
295
+ })
296
+
297
+ return {
298
+ "groups": groups,
299
+ "proposed_commits": proposed,
300
+ "total_commits": len(proposed),
301
+ }
302
+
303
+
304
+ def preview_commit_plan(cwd: str = ".") -> str:
305
+ """Human-readable preview of the commit plan.
306
+
307
+ Args:
308
+ cwd: Working directory (default: current directory).
309
+
310
+ Returns:
311
+ Formatted string showing each proposed commit and affected files.
312
+ Returns a notice string if the feature flag is off or no changes found.
313
+ """
314
+ plan = generate_commit_plan(cwd)
315
+
316
+ if plan["total_commits"] == 0:
317
+ if not _is_enabled():
318
+ return "[OMG] AI Commit Splitter is disabled. Set OMG_AI_COMMIT_ENABLED=1 to enable."
319
+ return "[OMG] No uncommitted changes found."
320
+
321
+ lines: List[str] = []
322
+ lines.append("=" * 60)
323
+ lines.append(" OMG AI Commit Splitter — Proposed Commit Plan")
324
+ lines.append("=" * 60)
325
+ lines.append("")
326
+ lines.append(f" Total proposed commits: {plan['total_commits']}")
327
+ lines.append("")
328
+
329
+ for idx, commit in enumerate(plan["proposed_commits"], 1):
330
+ lines.append(f" Commit {idx}: {commit['message']}")
331
+ lines.append(f" {'─' * 50}")
332
+ for f in commit["files"]:
333
+ lines.append(f" • {f}")
334
+ hunk_count = len(commit["hunks"])
335
+ lines.append(f" ({hunk_count} hunk{'s' if hunk_count != 1 else ''})")
336
+ lines.append("")
337
+
338
+ lines.append("=" * 60)
339
+ lines.append(" NOTE: This is a preview only. No commits were made.")
340
+ lines.append("=" * 60)
341
+
342
+ return "\n".join(lines)
343
+
344
+
345
+ # --- CLI ---
346
+
347
+
348
+ def main():
349
+ """CLI entry point."""
350
+ if len(sys.argv) < 2 or sys.argv[1] != "--dry-run":
351
+ print("Usage:", file=sys.stderr)
352
+ print(" python3 tools/commit_splitter.py --dry-run", file=sys.stderr)
353
+ sys.exit(1)
354
+
355
+ cwd = os.getcwd()
356
+ output = preview_commit_plan(cwd)
357
+ print(output)
358
+
359
+
360
+ if __name__ == "__main__":
361
+ main()
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Config Discovery Framework for AI Coding Tools
4
+
5
+ Scans a project directory for configuration files from 8 AI coding tools
6
+ and produces a JSON discovery report. Read-only operation.
7
+
8
+ Feature flag: OMG_CONFIG_DISCOVERY_ENABLED (default: off)
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import sys
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+ from typing import Any, Dict, List
17
+
18
+ # Feature flag
19
+ OMG_CONFIG_DISCOVERY_ENABLED = os.getenv("OMG_CONFIG_DISCOVERY_ENABLED", "false").lower() == "true"
20
+
21
+ # Tool detection patterns
22
+ TOOL_PATTERNS = {
23
+ "claude_code": [".claude/", ".claude/CLAUDE.md", "CLAUDE.md"],
24
+ "cursor": [".cursorrules", ".cursor/rules/", ".cursor/"],
25
+ "windsurf": [".windsurf/", ".windsurfrules"],
26
+ "gemini": ["system.md", ".gemini/"],
27
+ "codex": ["AGENTS.md"],
28
+ "cline": [".clinerules"],
29
+ "github_copilot": [".github/copilot-instructions.md"],
30
+ "vscode": [".vscode/settings.json", ".vscode/"],
31
+ }
32
+
33
+
34
+ def get_file_size(path: Path) -> int:
35
+ """Get file size in bytes. Returns 0 if not a file."""
36
+ try:
37
+ if path.is_file():
38
+ return path.stat().st_size
39
+ return 0
40
+ except (OSError, ValueError):
41
+ return 0
42
+
43
+
44
+ def is_readable(path: Path) -> bool:
45
+ """Check if path is readable."""
46
+ try:
47
+ return os.access(str(path), os.R_OK)
48
+ except (OSError, ValueError):
49
+ return False
50
+
51
+
52
+ def get_format(path: Path) -> str:
53
+ """Determine file format from extension or path."""
54
+ if path.is_dir():
55
+ return "directory"
56
+
57
+ suffix = path.suffix.lower()
58
+ if suffix == ".md":
59
+ return "markdown"
60
+ elif suffix == ".json":
61
+ return "json"
62
+ elif suffix == ".yaml" or suffix == ".yml":
63
+ return "yaml"
64
+ elif suffix == ".txt":
65
+ return "text"
66
+ else:
67
+ return "unknown"
68
+
69
+
70
+ def discover_configs(project_dir: str) -> Dict[str, Any]:
71
+ """
72
+ Scan project_dir for AI tool configs.
73
+
74
+ Returns:
75
+ {
76
+ "discovered": [
77
+ {
78
+ "tool": "claude_code",
79
+ "paths": [".claude/CLAUDE.md"],
80
+ "format": "markdown",
81
+ "size_bytes": 1234,
82
+ "readable": true
83
+ },
84
+ ...
85
+ ],
86
+ "scan_dir": "/path/to/project",
87
+ "timestamp": "2025-03-02T10:30:45.123456"
88
+ }
89
+ """
90
+ project_path = Path(project_dir).resolve()
91
+
92
+ if not project_path.exists():
93
+ return {
94
+ "discovered": [],
95
+ "scan_dir": str(project_path),
96
+ "timestamp": datetime.now().isoformat(),
97
+ "error": f"Project directory does not exist: {project_dir}"
98
+ }
99
+
100
+ discovered = []
101
+
102
+ for tool_name, patterns in TOOL_PATTERNS.items():
103
+ tool_paths = []
104
+
105
+ for pattern in patterns:
106
+ # Handle directory patterns (ending with /)
107
+ if pattern.endswith("/"):
108
+ dir_path = project_path / pattern.rstrip("/")
109
+ if dir_path.exists() and dir_path.is_dir():
110
+ tool_paths.append(pattern.rstrip("/"))
111
+ else:
112
+ # Handle file patterns
113
+ file_path = project_path / pattern
114
+ if file_path.exists():
115
+ tool_paths.append(pattern)
116
+
117
+ if tool_paths:
118
+ # Get info from first discovered path
119
+ first_path = project_path / tool_paths[0]
120
+ size_bytes = get_file_size(first_path)
121
+ readable = is_readable(first_path)
122
+ format_type = get_format(first_path)
123
+
124
+ discovered.append({
125
+ "tool": tool_name,
126
+ "paths": tool_paths,
127
+ "format": format_type,
128
+ "size_bytes": size_bytes,
129
+ "readable": readable
130
+ })
131
+
132
+ return {
133
+ "discovered": discovered,
134
+ "scan_dir": str(project_path),
135
+ "timestamp": datetime.now().isoformat()
136
+ }
137
+
138
+
139
+ def main():
140
+ """CLI entry point."""
141
+ if len(sys.argv) < 3 or sys.argv[1] != "--scan":
142
+ print("Usage: python3 config_discovery.py --scan <directory>", file=sys.stderr)
143
+ sys.exit(1)
144
+
145
+ project_dir = sys.argv[2]
146
+ result = discover_configs(project_dir)
147
+ print(json.dumps(result, indent=2))
148
+
149
+
150
+ if __name__ == "__main__":
151
+ main()