@trac3er/oh-my-god 2.0.2 → 2.0.4

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 (97) hide show
  1. package/.agents/skills/omg/AGENTS.fragment.md +5 -0
  2. package/.agents/skills/omg/codex-mcp.toml +4 -0
  3. package/.agents/skills/omg/control-plane/SKILL.md +11 -0
  4. package/.agents/skills/omg/control-plane/openai.yaml +14 -0
  5. package/.agents/skills/omg/hook-governor/SKILL.md +11 -0
  6. package/.agents/skills/omg/hook-governor/openai.yaml +11 -0
  7. package/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
  8. package/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
  9. package/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
  10. package/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
  11. package/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
  12. package/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
  13. package/.claude-plugin/marketplace.json +3 -3
  14. package/.claude-plugin/plugin.json +1 -1
  15. package/.mcp.json +20 -4
  16. package/CHANGELOG.md +16 -0
  17. package/OMG-setup.sh +9 -3
  18. package/OMG_COMPAT_CONTRACT.md +92 -0
  19. package/README.md +26 -8
  20. package/SECURITY.md +6 -0
  21. package/commands/OMG:api-twin.md +22 -0
  22. package/commands/OMG:preflight.md +26 -0
  23. package/commands/OMG:security-check.md +28 -0
  24. package/commands/OMG:setup.md +1 -2
  25. package/dist/enterprise/bundle/.agents/skills/omg/AGENTS.fragment.md +5 -0
  26. package/dist/enterprise/bundle/.agents/skills/omg/codex-mcp.toml +4 -0
  27. package/dist/enterprise/bundle/.agents/skills/omg/control-plane/SKILL.md +11 -0
  28. package/dist/enterprise/bundle/.agents/skills/omg/control-plane/openai.yaml +14 -0
  29. package/dist/enterprise/bundle/.agents/skills/omg/hook-governor/SKILL.md +11 -0
  30. package/dist/enterprise/bundle/.agents/skills/omg/hook-governor/openai.yaml +11 -0
  31. package/dist/enterprise/bundle/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
  32. package/dist/enterprise/bundle/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
  33. package/dist/enterprise/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
  34. package/dist/enterprise/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
  35. package/dist/enterprise/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
  36. package/dist/enterprise/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
  37. package/dist/enterprise/bundle/.claude-plugin/marketplace.json +36 -0
  38. package/dist/enterprise/bundle/.claude-plugin/plugin.json +23 -0
  39. package/dist/enterprise/bundle/.mcp.json +40 -0
  40. package/dist/enterprise/bundle/OMG_COMPAT_CONTRACT.md +92 -0
  41. package/dist/enterprise/bundle/settings.json +366 -0
  42. package/dist/enterprise/manifest.json +99 -0
  43. package/dist/public/bundle/.agents/skills/omg/AGENTS.fragment.md +5 -0
  44. package/dist/public/bundle/.agents/skills/omg/codex-mcp.toml +4 -0
  45. package/dist/public/bundle/.agents/skills/omg/control-plane/SKILL.md +11 -0
  46. package/dist/public/bundle/.agents/skills/omg/control-plane/openai.yaml +14 -0
  47. package/dist/public/bundle/.agents/skills/omg/hook-governor/SKILL.md +11 -0
  48. package/dist/public/bundle/.agents/skills/omg/hook-governor/openai.yaml +11 -0
  49. package/dist/public/bundle/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
  50. package/dist/public/bundle/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
  51. package/dist/public/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
  52. package/dist/public/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
  53. package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
  54. package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
  55. package/dist/public/bundle/.claude-plugin/marketplace.json +36 -0
  56. package/dist/public/bundle/.claude-plugin/plugin.json +23 -0
  57. package/dist/public/bundle/.mcp.json +40 -0
  58. package/dist/public/bundle/OMG_COMPAT_CONTRACT.md +92 -0
  59. package/dist/public/bundle/settings.json +366 -0
  60. package/dist/public/manifest.json +99 -0
  61. package/hooks/policy_engine.py +38 -7
  62. package/hooks/post-write.py +1 -1
  63. package/hooks/prompt-enhancer.py +2 -2
  64. package/hooks/security_validators.py +75 -0
  65. package/hooks/setup_wizard.py +44 -20
  66. package/hooks/shadow_manager.py +22 -2
  67. package/package.json +1 -1
  68. package/plugins/README.md +4 -2
  69. package/plugins/advanced/commands/OMG:deep-plan.md +1 -1
  70. package/plugins/advanced/commands/OMG:security-review.md +10 -113
  71. package/plugins/advanced/commands/OMG:ship.md +1 -1
  72. package/plugins/advanced/plugin.json +1 -10
  73. package/plugins/core/plugin.json +25 -2
  74. package/pyproject.toml +1 -1
  75. package/runtime/adoption.py +1 -1
  76. package/runtime/api_twin.py +130 -0
  77. package/runtime/compat.py +21 -1
  78. package/runtime/contract_compiler.py +698 -0
  79. package/runtime/domain_packs.py +34 -0
  80. package/runtime/guide_assert.py +45 -0
  81. package/runtime/mcp_config_writers.py +145 -39
  82. package/runtime/omg_compat_contract_snapshot.json +8 -7
  83. package/runtime/omg_contract_snapshot.json +8 -7
  84. package/runtime/omg_mcp_server.py +205 -0
  85. package/runtime/preflight.py +52 -0
  86. package/runtime/providers/codex_provider.py +2 -12
  87. package/runtime/providers/gemini_provider.py +2 -21
  88. package/runtime/providers/kimi_provider.py +2 -21
  89. package/runtime/runtime_profile.py +61 -0
  90. package/runtime/security_check.py +347 -0
  91. package/runtime/subagent_dispatcher.py +117 -10
  92. package/runtime/team_router.py +3 -3
  93. package/runtime/untrusted_content.py +102 -0
  94. package/scripts/omg.py +174 -1
  95. package/settings.json +66 -18
  96. package/tools/python_repl.py +33 -3
  97. package/runtime/providers/opencode_provider.py +0 -144
@@ -26,11 +26,12 @@ _logger = logging.getLogger(__name__)
26
26
  try:
27
27
  import runtime.providers.codex_provider # noqa: F401 # pyright: ignore[reportUnusedImport]
28
28
  import runtime.providers.gemini_provider # noqa: F401 # pyright: ignore[reportUnusedImport]
29
- import runtime.providers.opencode_provider # noqa: F401 # pyright: ignore[reportUnusedImport]
30
29
  import runtime.providers.kimi_provider # noqa: F401 # pyright: ignore[reportUnusedImport]
31
30
  except ImportError:
32
31
  pass
33
32
 
33
+ from runtime.runtime_profile import resolve_parallel_workers
34
+
34
35
  @dataclass
35
36
  class TeamDispatchRequest:
36
37
  target: str # codex | gemini | ccg | auto
@@ -550,7 +551,6 @@ def dispatch_to_model(agent_name: str, user_prompt: str, project_dir: str) -> di
550
551
  provider_name_map = {
551
552
  "codex-cli": "codex",
552
553
  "gemini-cli": "gemini",
553
- "opencode-cli": "opencode",
554
554
  "kimi-cli": "kimi",
555
555
  }
556
556
  provider_name = provider_name_map.get(preferred)
@@ -661,7 +661,7 @@ def execute_agents_parallel(
661
661
  if not sorted_tasks:
662
662
  return []
663
663
 
664
- max_workers = min(len(sorted_tasks), 5)
664
+ max_workers = resolve_parallel_workers(project_dir, requested_workers=min(len(sorted_tasks), 5))
665
665
  results_by_index: dict[int, dict[str, Any]] = {}
666
666
 
667
667
  with ThreadPoolExecutor(max_workers=max_workers) as pool:
@@ -0,0 +1,102 @@
1
+ """State and provenance tracking for untrusted external content."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+ import re
7
+ from typing import Any
8
+
9
+
10
+ STATE_REL_PATH = Path(".omg") / "state" / "untrusted-content.json"
11
+ _INSTRUCTION_PATTERNS: tuple[re.Pattern[str], ...] = (
12
+ re.compile(r"ignore\s+previous\s+instructions", re.IGNORECASE),
13
+ re.compile(r"\b(system|assistant|developer)\s*:", re.IGNORECASE),
14
+ re.compile(r"\b(run|execute|commit|push|apply_patch|edit)\b", re.IGNORECASE),
15
+ )
16
+
17
+
18
+ def mark_untrusted_content(
19
+ project_dir: str,
20
+ *,
21
+ source_type: str,
22
+ content: str,
23
+ source_ref: str = "",
24
+ ) -> dict[str, Any]:
25
+ state_path = _state_path(project_dir)
26
+ state = get_untrusted_content_state(project_dir)
27
+ sanitized, quarantined = quarantine_instruction_like_text(content)
28
+ entry = {
29
+ "source_type": source_type,
30
+ "source_ref": source_ref,
31
+ "quarantined_fragments": quarantined,
32
+ "trust_score": 0.0,
33
+ }
34
+ provenance = list(state.get("provenance", []))
35
+ provenance.append(entry)
36
+ next_state = {
37
+ "active": True,
38
+ "last_source_type": source_type,
39
+ "last_source_ref": source_ref,
40
+ "sanitized_content": sanitized,
41
+ "quarantined_fragments": quarantined,
42
+ "provenance": provenance[-20:],
43
+ "trust_scores": {"external_content": 0.0},
44
+ }
45
+ _write_state(state_path, next_state)
46
+ return next_state
47
+
48
+
49
+ def clear_untrusted_content(project_dir: str, *, reason: str) -> dict[str, Any]:
50
+ state_path = _state_path(project_dir)
51
+ existing = get_untrusted_content_state(project_dir)
52
+ next_state = {
53
+ **existing,
54
+ "active": False,
55
+ "cleared_reason": reason,
56
+ }
57
+ _write_state(state_path, next_state)
58
+ return next_state
59
+
60
+
61
+ def get_untrusted_content_state(project_dir: str) -> dict[str, Any]:
62
+ state_path = _state_path(project_dir)
63
+ if not state_path.exists():
64
+ return {
65
+ "active": False,
66
+ "provenance": [],
67
+ "trust_scores": {},
68
+ }
69
+ try:
70
+ payload = json.loads(state_path.read_text(encoding="utf-8"))
71
+ except (OSError, json.JSONDecodeError):
72
+ return {
73
+ "active": False,
74
+ "provenance": [],
75
+ "trust_scores": {},
76
+ }
77
+ return payload if isinstance(payload, dict) else {"active": False, "provenance": [], "trust_scores": {}}
78
+
79
+
80
+ def is_untrusted_content_mode_active(project_dir: str) -> bool:
81
+ return bool(get_untrusted_content_state(project_dir).get("active", False))
82
+
83
+
84
+ def quarantine_instruction_like_text(content: str) -> tuple[str, list[str]]:
85
+ sanitized_lines: list[str] = []
86
+ quarantined: list[str] = []
87
+ for raw_line in content.splitlines():
88
+ line = raw_line.strip()
89
+ if any(pattern.search(line) for pattern in _INSTRUCTION_PATTERNS):
90
+ quarantined.append(raw_line)
91
+ continue
92
+ sanitized_lines.append(raw_line)
93
+ return "\n".join(sanitized_lines).strip(), quarantined
94
+
95
+
96
+ def _state_path(project_dir: str) -> Path:
97
+ return Path(project_dir) / STATE_REL_PATH
98
+
99
+
100
+ def _write_state(path: Path, payload: dict[str, Any]) -> None:
101
+ path.parent.mkdir(parents=True, exist_ok=True)
102
+ path.write_text(json.dumps(payload, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
package/scripts/omg.py CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env python3
2
- """OMG 2.0.2 CLI entrypoint.
2
+ """OMG 2.0.4 CLI entrypoint.
3
3
 
4
4
  Implements practical command-line flows for:
5
5
  - omg ship
6
6
  - omg fix --issue
7
7
  - omg secure
8
+ - omg security check
8
9
  - omg maintainer
9
10
  - omg trust review
10
11
  - omg runtime dispatch
@@ -31,6 +32,15 @@ from hooks.shadow_manager import create_evidence_pack
31
32
  from hooks.trust_review import review_config_change, write_trust_manifest
32
33
  from lab.pipeline import publish_artifact, run_pipeline
33
34
  from runtime.dispatcher import dispatch_runtime
35
+ from runtime.api_twin import ingest_contract, record_fixture, serve_fixture, verify_fixture
36
+ from runtime.domain_packs import get_domain_pack_contract
37
+ from runtime.preflight import run_preflight
38
+ from runtime.security_check import run_security_check
39
+ from runtime.contract_compiler import (
40
+ build_release_readiness,
41
+ compile_contract_outputs,
42
+ validate_contract_registry,
43
+ )
34
44
  from runtime.compat import (
35
45
  DEFAULT_CONTRACT_SNAPSHOT_PATH,
36
46
  DEFAULT_GAP_REPORT_PATH,
@@ -155,6 +165,68 @@ def cmd_secure(args: argparse.Namespace) -> int:
155
165
  return 0 if decision.action != "deny" else 3
156
166
 
157
167
 
168
+ def cmd_security_check(args: argparse.Namespace) -> int:
169
+ result = run_security_check(
170
+ project_dir=_ensure_project_dir(),
171
+ scope=args.scope,
172
+ include_live_enrichment=bool(args.live_enrichment),
173
+ )
174
+ print(json.dumps(result, indent=2))
175
+ return 0
176
+
177
+
178
+ def cmd_api_twin_ingest(args: argparse.Namespace) -> int:
179
+ result = ingest_contract(_ensure_project_dir(), name=args.name, source_path=args.source)
180
+ print(json.dumps(result, indent=2))
181
+ return 0
182
+
183
+
184
+ def cmd_api_twin_record(args: argparse.Namespace) -> int:
185
+ result = record_fixture(
186
+ _ensure_project_dir(),
187
+ name=args.name,
188
+ request=json.loads(args.request_json),
189
+ response=json.loads(args.response_json),
190
+ validated=bool(args.validated),
191
+ )
192
+ print(json.dumps(result, indent=2))
193
+ return 0
194
+
195
+
196
+ def cmd_api_twin_serve(args: argparse.Namespace) -> int:
197
+ result = serve_fixture(
198
+ _ensure_project_dir(),
199
+ name=args.name,
200
+ latency_ms=int(args.latency_ms),
201
+ failure_mode=args.failure_mode,
202
+ schema_drift=bool(args.schema_drift),
203
+ )
204
+ print(json.dumps(result, indent=2))
205
+ return 0
206
+
207
+
208
+ def cmd_api_twin_verify(args: argparse.Namespace) -> int:
209
+ result = verify_fixture(
210
+ _ensure_project_dir(),
211
+ name=args.name,
212
+ live_response=json.loads(args.live_response_json),
213
+ )
214
+ print(json.dumps(result, indent=2))
215
+ return 0
216
+
217
+
218
+ def cmd_preflight(args: argparse.Namespace) -> int:
219
+ result = run_preflight(_ensure_project_dir(), goal=args.goal)
220
+ print(json.dumps(result, indent=2))
221
+ return 0
222
+
223
+
224
+ def cmd_domain_pack(args: argparse.Namespace) -> int:
225
+ result = get_domain_pack_contract(args.name)
226
+ print(json.dumps(result, indent=2))
227
+ return 0
228
+
229
+
158
230
  def cmd_maintainer(args: argparse.Namespace) -> int:
159
231
  project_dir = _ensure_project_dir()
160
232
  out_dir = Path(project_dir) / ".omg" / "evidence"
@@ -357,6 +429,34 @@ def cmd_ecosystem_sync(args: argparse.Namespace) -> int:
357
429
  return 0 if not errors else 2
358
430
 
359
431
 
432
+ def cmd_contract_validate(args: argparse.Namespace) -> int:
433
+ result = validate_contract_registry(ROOT_DIR)
434
+ print(json.dumps(result, indent=2))
435
+ return 0 if result.get("status") == "ok" else 2
436
+
437
+
438
+ def cmd_contract_compile(args: argparse.Namespace) -> int:
439
+ hosts = args.hosts or []
440
+ result = compile_contract_outputs(
441
+ root_dir=ROOT_DIR,
442
+ output_root=args.output_root,
443
+ hosts=hosts,
444
+ channel=args.channel,
445
+ )
446
+ print(json.dumps(result, indent=2))
447
+ return 0 if result.get("status") == "ok" else 2
448
+
449
+
450
+ def cmd_release_readiness(args: argparse.Namespace) -> int:
451
+ result = build_release_readiness(
452
+ root_dir=ROOT_DIR,
453
+ output_root=args.output_root,
454
+ channel=args.channel,
455
+ )
456
+ print(json.dumps(result, indent=2))
457
+ return 0 if result.get("status") == "ok" else 2
458
+
459
+
360
460
  def _add_compat_subcommands(parent: argparse.ArgumentParser, *, dest: str) -> None:
361
461
  compat_sub = parent.add_subparsers(dest=dest, required=True)
362
462
  compat_list = compat_sub.add_parser("list", help="List supported legacy skill names")
@@ -399,6 +499,35 @@ def _add_ecosystem_subcommands(parent: argparse.ArgumentParser, *, dest: str) ->
399
499
  ecosystem_sync.set_defaults(func=cmd_ecosystem_sync)
400
500
 
401
501
 
502
+ def _add_contract_subcommands(parent: argparse.ArgumentParser, *, dest: str) -> None:
503
+ contract_sub = parent.add_subparsers(dest=dest, required=True)
504
+
505
+ contract_validate = contract_sub.add_parser("validate", help="Validate contract doc, schema, and bundle registry")
506
+ contract_validate.set_defaults(func=cmd_contract_validate)
507
+
508
+ contract_compile = contract_sub.add_parser("compile", help="Compile Claude/Codex artifacts from the canonical contract")
509
+ contract_compile.add_argument(
510
+ "--host",
511
+ dest="hosts",
512
+ action="append",
513
+ choices=["claude", "codex"],
514
+ required=True,
515
+ help="Host to compile (repeat for multiple hosts)",
516
+ )
517
+ contract_compile.add_argument("--channel", default="public", choices=["public", "enterprise"])
518
+ contract_compile.add_argument("--output-root", default="", help="Write outputs to this root instead of the repo root")
519
+ contract_compile.set_defaults(func=cmd_contract_compile)
520
+
521
+
522
+ def _add_release_subcommands(parent: argparse.ArgumentParser, *, dest: str) -> None:
523
+ release_sub = parent.add_subparsers(dest=dest, required=True)
524
+
525
+ release_readiness = release_sub.add_parser("readiness", help="Check production release readiness for compiled artifacts")
526
+ release_readiness.add_argument("--channel", default="dual", choices=["public", "enterprise", "dual"])
527
+ release_readiness.add_argument("--output-root", default="", help="Check compiled outputs from this root instead of the repo root")
528
+ release_readiness.set_defaults(func=cmd_release_readiness)
529
+
530
+
402
531
  def build_parser() -> argparse.ArgumentParser:
403
532
  parser = argparse.ArgumentParser(prog="omg", description=f"OMG {CANONICAL_VERSION} CLI")
404
533
  sub = parser.add_subparsers(dest="command", required=True)
@@ -418,6 +547,44 @@ def build_parser() -> argparse.ArgumentParser:
418
547
  secure.add_argument("--command", required=True)
419
548
  secure.set_defaults(func=cmd_secure)
420
549
 
550
+ security = sub.add_parser("security", help="Canonical OMG security workflows")
551
+ security_sub = security.add_subparsers(dest="security_command", required=True)
552
+ security_check = security_sub.add_parser("check", help="Run canonical OMG security check")
553
+ security_check.add_argument("--scope", default=".")
554
+ security_check.add_argument("--live-enrichment", action="store_true")
555
+ security_check.set_defaults(func=cmd_security_check)
556
+
557
+ api_twin = sub.add_parser("api-twin", help="Contract replay and fixture-based API simulation")
558
+ api_twin_sub = api_twin.add_subparsers(dest="api_twin_command", required=True)
559
+ api_twin_ingest = api_twin_sub.add_parser("ingest", help="Ingest OpenAPI/Postman/example contract input")
560
+ api_twin_ingest.add_argument("--name", required=True)
561
+ api_twin_ingest.add_argument("--source", required=True)
562
+ api_twin_ingest.set_defaults(func=cmd_api_twin_ingest)
563
+ api_twin_record = api_twin_sub.add_parser("record", help="Record approved fixture response")
564
+ api_twin_record.add_argument("--name", required=True)
565
+ api_twin_record.add_argument("--request-json", required=True)
566
+ api_twin_record.add_argument("--response-json", required=True)
567
+ api_twin_record.add_argument("--validated", action="store_true")
568
+ api_twin_record.set_defaults(func=cmd_api_twin_record)
569
+ api_twin_serve = api_twin_sub.add_parser("serve", help="Replay a fixture with optional drift/failure injection")
570
+ api_twin_serve.add_argument("--name", required=True)
571
+ api_twin_serve.add_argument("--latency-ms", type=int, default=0)
572
+ api_twin_serve.add_argument("--failure-mode", default="")
573
+ api_twin_serve.add_argument("--schema-drift", action="store_true")
574
+ api_twin_serve.set_defaults(func=cmd_api_twin_serve)
575
+ api_twin_verify = api_twin_sub.add_parser("verify", help="Validate a fixture against a live response")
576
+ api_twin_verify.add_argument("--name", required=True)
577
+ api_twin_verify.add_argument("--live-response-json", required=True)
578
+ api_twin_verify.set_defaults(func=cmd_api_twin_verify)
579
+
580
+ preflight = sub.add_parser("preflight", help="Structured OMG preflight routing")
581
+ preflight.add_argument("--goal", required=True)
582
+ preflight.set_defaults(func=cmd_preflight)
583
+
584
+ domain_pack = sub.add_parser("domain-pack", help="Inspect optional domain pack contracts")
585
+ domain_pack.add_argument("--name", required=True, choices=["robotics", "vision", "algorithms", "health"])
586
+ domain_pack.set_defaults(func=cmd_domain_pack)
587
+
421
588
  maintainer = sub.add_parser("maintainer", help="OSS maintainer evidence helper")
422
589
  maintainer.add_argument("--mode", default="impact", choices=["triage", "release", "review", "impact"])
423
590
  maintainer.set_defaults(func=cmd_maintainer)
@@ -479,6 +646,12 @@ def build_parser() -> argparse.ArgumentParser:
479
646
  ecosystem = sub.add_parser("ecosystem", help="Upstream ecosystem sync and status")
480
647
  _add_ecosystem_subcommands(ecosystem, dest="ecosystem_command")
481
648
 
649
+ contract = sub.add_parser("contract", help="Canonical OMG contract validation and compilation")
650
+ _add_contract_subcommands(contract, dest="contract_command")
651
+
652
+ release = sub.add_parser("release", help="OMG release-readiness checks")
653
+ _add_release_subcommands(release, dest="release_command")
654
+
482
655
  return parser
483
656
 
484
657
 
package/settings.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
- "_comment": "OMG 2.0.2 - project-level config with hook registrations, presets, and feature flags.",
3
+ "_comment": "OMG 2.0.4 - project-level config with hook registrations, presets, and feature flags.",
4
4
  "permissions": {
5
5
  "allow": [
6
6
  "Agent",
@@ -42,7 +42,6 @@
42
42
  "Bash(tree *)",
43
43
  "Bash(du *)",
44
44
  "Bash(df *)",
45
- "Bash(env *)",
46
45
  "Bash(type *)",
47
46
  "Bash(command *)",
48
47
  "Bash(test *)",
@@ -52,17 +51,12 @@
52
51
  "Bash(ln *)",
53
52
  "Bash(cp *)",
54
53
  "Bash(mv *)",
55
- "Bash(chmod *)",
56
- "Bash(chown *)",
57
54
  "Bash(git *)",
58
55
  "Bash(npm *)",
59
56
  "Bash(npx *)",
60
57
  "Bash(yarn *)",
61
58
  "Bash(pnpm *)",
62
59
  "Bash(bun *)",
63
- "Bash(node *)",
64
- "Bash(python *)",
65
- "Bash(python3 *)",
66
60
  "Bash(pip *)",
67
61
  "Bash(pip3 *)",
68
62
  "Bash(uv *)",
@@ -115,6 +109,12 @@
115
109
  "Bash(terraform apply *)",
116
110
  "Bash(terraform destroy *)",
117
111
  "Bash(terraform import *)",
112
+ "Bash(env *)",
113
+ "Bash(node *)",
114
+ "Bash(python *)",
115
+ "Bash(python3 *)",
116
+ "Bash(chmod *)",
117
+ "Bash(chown *)",
118
118
  "Bash(docker *)",
119
119
  "Bash(docker-compose *)",
120
120
  "Bash(kubectl get *)",
@@ -188,45 +188,66 @@
188
188
  }
189
189
  ],
190
190
  "PreToolUse": [
191
+ {
192
+ "hooks": [
193
+ {
194
+ "type": "command",
195
+ "command": "python3 \"$HOME/.claude/hooks/firewall.py\"",
196
+ "timeout": 10
197
+ }
198
+ ],
199
+ "matcher": "Bash"
200
+ },
201
+ {
202
+ "hooks": [
203
+ {
204
+ "type": "command",
205
+ "command": "python3 \"$HOME/.claude/hooks/secret-guard.py\"",
206
+ "timeout": 10
207
+ }
208
+ ],
209
+ "matcher": "Read|Write|Edit|MultiEdit"
210
+ },
191
211
  {
192
212
  "hooks": [
193
213
  {
194
214
  "type": "command",
195
215
  "command": "python3 \"$HOME/.claude/hooks/pre-tool-inject.py\""
196
216
  }
197
- ]
217
+ ],
218
+ "matcher": ""
198
219
  }
199
220
  ],
200
221
  "PostToolUse": [
201
222
  {
202
- "matcher": "Bash",
203
223
  "hooks": [
204
224
  {
205
225
  "type": "command",
206
226
  "command": "python3 \"$HOME/.claude/hooks/circuit-breaker.py\"",
207
227
  "timeout": 10
208
228
  }
209
- ]
229
+ ],
230
+ "matcher": "Bash"
210
231
  },
211
232
  {
212
- "matcher": "Write|Edit|MultiEdit",
213
233
  "hooks": [
214
234
  {
215
235
  "type": "command",
216
236
  "command": "python3 \"$HOME/.claude/hooks/tool-ledger.py\"",
217
237
  "timeout": 10
218
238
  }
219
- ]
239
+ ],
240
+ "matcher": "Write|Edit|MultiEdit"
220
241
  },
221
242
  {
222
- "matcher": "Write|Edit|MultiEdit",
223
243
  "hooks": [
224
244
  {
225
245
  "type": "command",
226
246
  "command": "python3 \"$HOME/.claude/hooks/test_generator_hook.py\"",
227
247
  "timeout": 10
228
248
  }
229
- ]
249
+ ],
250
+ "matcher": "Write|Edit|MultiEdit"
230
251
  },
231
252
  {
232
253
  "hooks": [
@@ -235,7 +256,8 @@
235
256
  "command": "python3 \"$HOME/.claude/hooks/budget_governor.py\"",
236
257
  "timeout": 10
237
258
  }
238
- ]
259
+ ],
260
+ "matcher": ""
239
261
  }
240
262
  ],
241
263
  "PostToolUseFailure": [
@@ -250,19 +272,19 @@
250
272
  ],
251
273
  "Stop": [
252
274
  {
253
- "matcher": "",
254
275
  "hooks": [
255
276
  {
256
277
  "type": "command",
257
278
  "command": "python3 \"$HOME/.claude/hooks/stop_dispatcher.py\"",
258
279
  "timeout": 90
259
280
  }
260
- ]
281
+ ],
282
+ "matcher": ""
261
283
  }
262
284
  ]
263
285
  },
264
286
  "_omg": {
265
- "_version": "2.0.2",
287
+ "_version": "2.0.4",
266
288
  "preset": "safe",
267
289
  "default_mode": "ulw+ralph",
268
290
  "vision_auto": true,
@@ -313,6 +335,32 @@
313
335
  "DEP_HEALTH": false,
314
336
  "CODEBASE_VIZ": false,
315
337
  "CONTEXT_MANAGER": false
338
+ },
339
+ "generated": {
340
+ "contract_version": "2.0.4",
341
+ "channel": "public",
342
+ "required_bundles": [
343
+ "control-plane",
344
+ "hook-governor",
345
+ "mcp-fabric",
346
+ "lsp-pack",
347
+ "secure-worktree-pipeline"
348
+ ],
349
+ "protected_paths": [
350
+ ".omg/**",
351
+ ".agents/**",
352
+ ".codex/**",
353
+ ".claude/**"
354
+ ],
355
+ "emulated_events": [
356
+ "PreCompact",
357
+ "ConfigChange",
358
+ "WorktreeCreate",
359
+ "WorktreeRemove",
360
+ "SubagentStart",
361
+ "SubagentStop",
362
+ "TaskCompleted"
363
+ ]
316
364
  }
317
365
  }
318
366
  }
@@ -25,11 +25,13 @@ from typing import Any, Dict, Generator, List, Optional, Union
25
25
 
26
26
  _get_feature_flag = None
27
27
  _atomic_json_write = None
28
+ _validate_opaque_identifier = None
29
+ _ensure_path_within_dir = None
28
30
 
29
31
 
30
32
  def _ensure_imports():
31
33
  """Lazy import feature flag and atomic write from hooks/_common.py."""
32
- global _get_feature_flag, _atomic_json_write
34
+ global _get_feature_flag, _atomic_json_write, _validate_opaque_identifier, _ensure_path_within_dir
33
35
  if _get_feature_flag is not None:
34
36
  return
35
37
  repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -38,8 +40,12 @@ def _ensure_imports():
38
40
  try:
39
41
  from hooks._common import get_feature_flag as _gff
40
42
  from hooks._common import atomic_json_write as _ajw
43
+ from hooks.security_validators import ensure_path_within_dir as _epwd
44
+ from hooks.security_validators import validate_opaque_identifier as _voi
41
45
  _get_feature_flag = _gff
42
46
  _atomic_json_write = _ajw
47
+ _validate_opaque_identifier = _voi
48
+ _ensure_path_within_dir = _epwd
43
49
  except ImportError:
44
50
  pass
45
51
 
@@ -217,6 +223,23 @@ def _now_iso() -> str:
217
223
  return datetime.now(timezone.utc).isoformat()
218
224
 
219
225
 
226
+ def _validate_session_id(session_id: str) -> str:
227
+ """Validate a caller-supplied session identifier."""
228
+ _ensure_imports()
229
+ if _validate_opaque_identifier is None:
230
+ raise ValueError("Invalid session_id: validator unavailable")
231
+ return _validate_opaque_identifier(session_id, "session_id")
232
+
233
+
234
+ def _resolve_session_path(session_id: str) -> str:
235
+ """Resolve the on-disk session path and reject directory escapes."""
236
+ _ensure_imports()
237
+ if _ensure_path_within_dir is None:
238
+ raise ValueError("Invalid session_id: path validator unavailable")
239
+ state_dir = os.path.abspath(_STATE_DIR)
240
+ return _ensure_path_within_dir(state_dir, os.path.join(state_dir, f"{session_id}.json"))
241
+
242
+
220
243
  def _persist_session(session_id: str) -> None:
221
244
  """Persist session metadata to disk (best-effort)."""
222
245
  if session_id not in _sessions:
@@ -232,10 +255,11 @@ def _persist_session(session_id: str) -> None:
232
255
  "exec_count": session["exec_count"],
233
256
  "backend": session.get("backend", "stdlib"),
234
257
  }
235
- path = os.path.join(_STATE_DIR, f"{session_id}.json")
236
258
  try:
259
+ safe_session_id = _validate_session_id(session_id)
260
+ path = _resolve_session_path(safe_session_id)
237
261
  _atomic_json_write(path, meta)
238
- except Exception:
262
+ except (OSError, ValueError):
239
263
  pass # best-effort
240
264
 
241
265
 
@@ -411,6 +435,12 @@ def start_repl_session(session_id: Optional[str] = None) -> Dict[str, Any]:
411
435
  if not _is_enabled():
412
436
  return {"error": _DISABLED_MSG}
413
437
 
438
+ if session_id is not None:
439
+ try:
440
+ session_id = _validate_session_id(session_id)
441
+ except ValueError as exc:
442
+ return {"error": str(exc)}
443
+
414
444
  # Resume existing session
415
445
  if session_id and session_id in _sessions:
416
446
  session = _sessions[session_id]