@trac3er/oh-my-god 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +8 -8
- package/.claude-plugin/plugin.json +5 -4
- package/.claude-plugin/scripts/uninstall.sh +74 -3
- package/.claude-plugin/scripts/update.sh +78 -3
- package/.coveragerc +26 -0
- package/.mcp.json +4 -4
- package/CHANGELOG.md +14 -0
- package/CODE_OF_CONDUCT.md +27 -0
- package/CONTRIBUTING.md +62 -0
- package/OMG-setup.sh +1201 -355
- package/README.md +77 -56
- package/SECURITY.md +25 -0
- package/agents/__init__.py +1 -0
- package/agents/model_roles.py +196 -0
- package/agents/omg-architect-mode.md +3 -5
- package/agents/omg-backend-engineer.md +3 -5
- package/agents/omg-database-engineer.md +3 -5
- package/agents/omg-frontend-designer.md +4 -5
- package/agents/omg-implement-mode.md +4 -5
- package/agents/omg-infra-engineer.md +3 -5
- package/agents/omg-research-mode.md +4 -6
- package/agents/omg-security-auditor.md +3 -5
- package/agents/omg-testing-engineer.md +3 -5
- package/build/lib/yaml.py +321 -0
- package/commands/OMG:ai-commit.md +101 -14
- package/commands/OMG:arch.md +302 -19
- package/commands/OMG:ccg.md +12 -7
- package/commands/OMG:compat.md +25 -17
- package/commands/OMG:cost.md +173 -13
- package/commands/OMG:crazy.md +1 -1
- package/commands/OMG:create-agent.md +170 -20
- package/commands/OMG:deps.md +235 -17
- package/commands/OMG:domain-init.md +1 -1
- package/commands/OMG:escalate.md +41 -12
- package/commands/OMG:health-check.md +37 -13
- package/commands/OMG:init.md +122 -14
- package/commands/OMG:project-init.md +1 -1
- package/commands/OMG:session-branch.md +76 -9
- package/commands/OMG:session-fork.md +42 -5
- package/commands/OMG:session-merge.md +124 -8
- package/commands/OMG:setup.md +69 -12
- package/commands/OMG:stats.md +215 -14
- package/commands/OMG:teams.md +19 -10
- package/config/lsp_languages.yaml +8 -0
- package/hooks/__init__.py +0 -0
- package/hooks/_agent_registry.py +423 -0
- package/hooks/_analytics.py +291 -0
- package/hooks/_budget.py +31 -0
- package/hooks/_common.py +569 -0
- package/hooks/_compression_optimizer.py +119 -0
- package/hooks/_cost_ledger.py +176 -0
- package/hooks/_learnings.py +126 -0
- package/hooks/_memory.py +103 -0
- package/hooks/_protected_context.py +150 -0
- package/hooks/_token_counter.py +221 -0
- package/hooks/branch_manager.py +236 -0
- package/hooks/budget_governor.py +232 -0
- package/hooks/circuit-breaker.py +270 -0
- package/hooks/compression_feedback.py +254 -0
- package/hooks/config-guard.py +216 -0
- package/hooks/context_pressure.py +53 -0
- package/hooks/credential_store.py +1020 -0
- package/hooks/fetch-rate-limits.py +212 -0
- package/hooks/firewall.py +48 -0
- package/hooks/hashline-formatter-bridge.py +224 -0
- package/hooks/hashline-injector.py +273 -0
- package/hooks/hashline-validator.py +216 -0
- package/hooks/idle-detector.py +95 -0
- package/hooks/intentgate-keyword-detector.py +188 -0
- package/hooks/magic-keyword-router.py +195 -0
- package/hooks/policy_engine.py +505 -0
- package/hooks/post-tool-failure.py +19 -0
- package/hooks/post-write.py +219 -0
- package/hooks/post_write.py +46 -0
- package/hooks/pre-compact.py +398 -0
- package/hooks/pre-tool-inject.py +98 -0
- package/hooks/prompt-enhancer.py +672 -0
- package/hooks/quality-runner.py +191 -0
- package/hooks/query.py +512 -0
- package/hooks/secret-guard.py +61 -0
- package/hooks/secret_audit.py +144 -0
- package/hooks/session-end-capture.py +137 -0
- package/hooks/session-start.py +277 -0
- package/hooks/setup_wizard.py +582 -0
- package/hooks/shadow_manager.py +297 -0
- package/hooks/state_migration.py +225 -0
- package/hooks/stop-gate.py +7 -0
- package/hooks/stop_dispatcher.py +945 -0
- package/hooks/test-validator.py +361 -0
- package/hooks/test_generator_hook.py +123 -0
- package/hooks/todo-state-tracker.py +114 -0
- package/hooks/tool-ledger.py +149 -0
- package/hooks/trust_review.py +585 -0
- package/hud/omg-hud.mjs +31 -1
- package/lab/__init__.py +1 -0
- package/lab/pipeline.py +75 -0
- package/lab/policies.py +52 -0
- package/package.json +7 -18
- package/plugins/README.md +33 -61
- package/plugins/advanced/commands/OMG:deep-plan.md +3 -3
- package/plugins/advanced/commands/OMG:learn.md +1 -1
- package/plugins/advanced/commands/OMG:security-review.md +3 -3
- package/plugins/advanced/commands/OMG:ship.md +1 -1
- package/plugins/advanced/plugin.json +1 -1
- package/plugins/core/plugin.json +8 -3
- package/plugins/dephealth/__init__.py +0 -0
- package/plugins/dephealth/cve_scanner.py +188 -0
- package/plugins/dephealth/license_checker.py +135 -0
- package/plugins/dephealth/manifest_detector.py +423 -0
- package/plugins/dephealth/vuln_analyzer.py +169 -0
- package/plugins/testgen/__init__.py +0 -0
- package/plugins/testgen/codamosa_engine.py +402 -0
- package/plugins/testgen/edge_case_synthesizer.py +184 -0
- package/plugins/testgen/framework_detector.py +271 -0
- package/plugins/testgen/skeleton_generator.py +219 -0
- package/plugins/viz/__init__.py +0 -0
- package/plugins/viz/ast_parser.py +139 -0
- package/plugins/viz/diagram_generator.py +192 -0
- package/plugins/viz/graph_builder.py +444 -0
- package/plugins/viz/native_parsers.py +259 -0
- package/plugins/viz/regex_parser.py +112 -0
- package/pyproject.toml +81 -0
- package/rules/contextual/write-verify.md +2 -2
- package/rules/core/00-truth.md +1 -1
- package/rules/core/01-surgical.md +1 -1
- package/rules/core/02-circuit-breaker.md +2 -2
- package/rules/core/03-ensemble.md +3 -3
- package/rules/core/04-testing.md +3 -3
- package/runtime/__init__.py +32 -0
- package/runtime/adapters/__init__.py +13 -0
- package/runtime/adapters/claude.py +60 -0
- package/runtime/adapters/gpt.py +53 -0
- package/runtime/adapters/local.py +53 -0
- package/runtime/adoption.py +212 -0
- package/runtime/business_workflow.py +220 -0
- package/runtime/cli_provider.py +85 -0
- package/runtime/compat.py +1299 -0
- package/runtime/custom_agent_loader.py +366 -0
- package/runtime/dispatcher.py +47 -0
- package/runtime/ecosystem.py +371 -0
- package/runtime/legacy_compat.py +7 -0
- package/runtime/mcp_config_writers.py +115 -0
- package/runtime/mcp_lifecycle.py +153 -0
- package/runtime/mcp_memory_server.py +135 -0
- package/runtime/memory_parsers/__init__.py +0 -0
- package/runtime/memory_parsers/chatgpt_parser.py +257 -0
- package/runtime/memory_parsers/claude_import.py +107 -0
- package/runtime/memory_parsers/export.py +97 -0
- package/runtime/memory_parsers/gemini_import.py +91 -0
- package/runtime/memory_parsers/kimi_import.py +91 -0
- package/runtime/memory_store.py +215 -0
- package/runtime/omc_compat.py +7 -0
- package/runtime/providers/__init__.py +0 -0
- package/runtime/providers/codex_provider.py +112 -0
- package/runtime/providers/gemini_provider.py +128 -0
- package/runtime/providers/kimi_provider.py +151 -0
- package/runtime/providers/opencode_provider.py +144 -0
- package/runtime/subagent_dispatcher.py +362 -0
- package/runtime/team_router.py +1167 -0
- package/runtime/tmux_session_manager.py +169 -0
- package/scripts/check-omg-compat-contract-snapshot.py +137 -0
- package/scripts/check-omg-contract-snapshot.py +12 -0
- package/scripts/check-omg-public-ready.py +193 -0
- package/scripts/check-omg-standalone-clean.py +103 -0
- package/scripts/legacy_to_omg_migrate.py +29 -0
- package/scripts/migrate-legacy.py +464 -0
- package/scripts/omc_to_omg_migrate.py +12 -0
- package/scripts/omg.py +492 -0
- package/scripts/settings-merge.py +283 -0
- package/scripts/verify-standalone.sh +8 -4
- package/settings.json +126 -29
- package/templates/profile.yaml +1 -1
- package/tools/__init__.py +2 -0
- package/tools/browser_consent.py +289 -0
- package/tools/browser_stealth.py +481 -0
- package/tools/browser_tool.py +448 -0
- package/tools/changelog_generator.py +347 -0
- package/tools/commit_splitter.py +746 -0
- package/tools/config_discovery.py +151 -0
- package/tools/config_merger.py +449 -0
- package/tools/dashboard_generator.py +300 -0
- package/tools/git_inspector.py +298 -0
- package/tools/lsp_client.py +275 -0
- package/tools/lsp_discovery.py +231 -0
- package/tools/lsp_operations.py +392 -0
- package/tools/pr_generator.py +404 -0
- package/tools/python_repl.py +656 -0
- package/tools/python_sandbox.py +609 -0
- package/tools/search_providers/__init__.py +77 -0
- package/tools/search_providers/brave.py +115 -0
- package/tools/search_providers/exa.py +116 -0
- package/tools/search_providers/jina.py +104 -0
- package/tools/search_providers/perplexity.py +139 -0
- package/tools/search_providers/synthetic.py +74 -0
- package/tools/session_snapshot.py +736 -0
- package/tools/ssh_manager.py +912 -0
- package/tools/theme_engine.py +294 -0
- package/tools/theme_selector.py +137 -0
- package/tools/web_search.py +622 -0
- package/yaml.py +321 -0
- package/.claude-plugin/scripts/install.sh +0 -9
- package/bun.lock +0 -23
- package/bunfig.toml +0 -3
- package/hooks/_budget.ts +0 -1
- package/hooks/_common.ts +0 -63
- package/hooks/circuit-breaker.ts +0 -101
- package/hooks/config-guard.ts +0 -4
- package/hooks/firewall.ts +0 -20
- package/hooks/policy_engine.ts +0 -156
- package/hooks/post-tool-failure.ts +0 -22
- package/hooks/post-write.ts +0 -4
- package/hooks/pre-tool-inject.ts +0 -4
- package/hooks/prompt-enhancer.ts +0 -46
- package/hooks/quality-runner.ts +0 -24
- package/hooks/secret-guard.ts +0 -4
- package/hooks/session-end-capture.ts +0 -19
- package/hooks/session-start.ts +0 -19
- package/hooks/shadow_manager.ts +0 -81
- package/hooks/stop-gate.ts +0 -22
- package/hooks/stop_dispatcher.ts +0 -147
- package/hooks/test-generator-hook.ts +0 -4
- package/hooks/tool-ledger.ts +0 -27
- package/hooks/trust_review.ts +0 -175
- package/lab/pipeline.ts +0 -75
- package/lab/policies.ts +0 -68
- package/runtime/common.ts +0 -111
- package/runtime/compat.ts +0 -174
- package/runtime/dispatcher.ts +0 -25
- package/runtime/ecosystem.ts +0 -186
- package/runtime/provider_bootstrap.ts +0 -99
- package/runtime/provider_smoke.ts +0 -34
- package/runtime/release_readiness.ts +0 -186
- package/runtime/team_router.ts +0 -144
- package/scripts/check-omg-compat-contract-snapshot.ts +0 -20
- package/scripts/check-omg-standalone-clean.ts +0 -12
- package/scripts/check-runtime-clean.ts +0 -94
- package/scripts/omg.ts +0 -352
- package/scripts/settings-merge.ts +0 -93
- package/tools/commit_splitter.ts +0 -23
- package/tools/git_inspector.ts +0 -18
- package/tools/session_snapshot.ts +0 -47
- package/trac3er-oh-my-god-2.0.0.tgz +0 -0
- package/tsconfig.json +0 -15
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Local model runtime adapter (v1 stub)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LocalAdapter:
|
|
9
|
+
runtime = "local"
|
|
10
|
+
|
|
11
|
+
def plan(self, idea: dict[str, Any]) -> dict[str, Any]:
|
|
12
|
+
goal = str(idea.get("goal", "")).strip() or "unspecified-goal"
|
|
13
|
+
return {
|
|
14
|
+
"runtime": self.runtime,
|
|
15
|
+
"phase": "plan",
|
|
16
|
+
"status": "planned",
|
|
17
|
+
"goal": goal,
|
|
18
|
+
"steps": ["analyze-goal", "generate-plan", "emit-checklist"],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def execute(self, plan: dict[str, Any]) -> dict[str, Any]:
|
|
22
|
+
return {
|
|
23
|
+
"runtime": self.runtime,
|
|
24
|
+
"phase": "execute",
|
|
25
|
+
"status": "executed",
|
|
26
|
+
"operations": ["apply-plan", "collect-diff"],
|
|
27
|
+
"errors": [],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def verify(self, execution_result: dict[str, Any]) -> dict[str, Any]:
|
|
31
|
+
return {
|
|
32
|
+
"runtime": self.runtime,
|
|
33
|
+
"phase": "verify",
|
|
34
|
+
"status": "verified",
|
|
35
|
+
"ok": True,
|
|
36
|
+
"checks": [
|
|
37
|
+
{"name": "tests", "passed": True},
|
|
38
|
+
{"name": "security", "passed": True},
|
|
39
|
+
],
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def collect_evidence(self, verify_result: dict[str, Any]) -> dict[str, Any]:
|
|
43
|
+
return {
|
|
44
|
+
"runtime": self.runtime,
|
|
45
|
+
"phase": "evidence",
|
|
46
|
+
"status": "collected",
|
|
47
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
48
|
+
"tests": verify_result.get("checks", []),
|
|
49
|
+
"security_scans": [],
|
|
50
|
+
"diff_summary": {},
|
|
51
|
+
"reproducibility": {"seed": "deterministic"},
|
|
52
|
+
"unresolved_risks": [],
|
|
53
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Native OMG adoption helpers for setup and trust-release surfaces."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
CANONICAL_BRAND = "OMG"
|
|
10
|
+
CANONICAL_REPO_URL = "https://github.com/trac3er00/OMG"
|
|
11
|
+
CANONICAL_PACKAGE_NAME = "@trac3er/oh-my-god"
|
|
12
|
+
CANONICAL_PLUGIN_ID = "omg"
|
|
13
|
+
CANONICAL_MARKETPLACE_ID = "omg"
|
|
14
|
+
CANONICAL_VERSION = "2.0.2"
|
|
15
|
+
|
|
16
|
+
VALID_ADOPTION_MODES = ("omg-only", "coexist")
|
|
17
|
+
VALID_PRESETS = ("safe", "balanced", "interop", "labs")
|
|
18
|
+
|
|
19
|
+
MANAGED_PRESET_FLAGS = (
|
|
20
|
+
"SETUP",
|
|
21
|
+
"SETUP_WIZARD",
|
|
22
|
+
"MEMORY_AUTOSTART",
|
|
23
|
+
"SESSION_ANALYTICS",
|
|
24
|
+
"CONTEXT_MANAGER",
|
|
25
|
+
"COST_TRACKING",
|
|
26
|
+
"MEMORY_SERVER",
|
|
27
|
+
"GIT_WORKFLOW",
|
|
28
|
+
"TEST_GENERATION",
|
|
29
|
+
"DEP_HEALTH",
|
|
30
|
+
"CODEBASE_VIZ",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
PRESET_FEATURES: dict[str, dict[str, bool]] = {
|
|
34
|
+
"safe": {flag: False for flag in MANAGED_PRESET_FLAGS},
|
|
35
|
+
"balanced": {
|
|
36
|
+
"SETUP": True,
|
|
37
|
+
"SETUP_WIZARD": True,
|
|
38
|
+
"MEMORY_AUTOSTART": True,
|
|
39
|
+
"SESSION_ANALYTICS": True,
|
|
40
|
+
"CONTEXT_MANAGER": True,
|
|
41
|
+
"COST_TRACKING": True,
|
|
42
|
+
"MEMORY_SERVER": False,
|
|
43
|
+
"GIT_WORKFLOW": False,
|
|
44
|
+
"TEST_GENERATION": False,
|
|
45
|
+
"DEP_HEALTH": False,
|
|
46
|
+
"CODEBASE_VIZ": False,
|
|
47
|
+
},
|
|
48
|
+
"interop": {
|
|
49
|
+
"SETUP": True,
|
|
50
|
+
"SETUP_WIZARD": True,
|
|
51
|
+
"MEMORY_AUTOSTART": True,
|
|
52
|
+
"SESSION_ANALYTICS": True,
|
|
53
|
+
"CONTEXT_MANAGER": True,
|
|
54
|
+
"COST_TRACKING": True,
|
|
55
|
+
"MEMORY_SERVER": True,
|
|
56
|
+
"GIT_WORKFLOW": False,
|
|
57
|
+
"TEST_GENERATION": False,
|
|
58
|
+
"DEP_HEALTH": False,
|
|
59
|
+
"CODEBASE_VIZ": False,
|
|
60
|
+
},
|
|
61
|
+
"labs": {
|
|
62
|
+
"SETUP": True,
|
|
63
|
+
"SETUP_WIZARD": True,
|
|
64
|
+
"MEMORY_AUTOSTART": True,
|
|
65
|
+
"SESSION_ANALYTICS": True,
|
|
66
|
+
"CONTEXT_MANAGER": True,
|
|
67
|
+
"COST_TRACKING": True,
|
|
68
|
+
"MEMORY_SERVER": True,
|
|
69
|
+
"GIT_WORKFLOW": True,
|
|
70
|
+
"TEST_GENERATION": True,
|
|
71
|
+
"DEP_HEALTH": True,
|
|
72
|
+
"CODEBASE_VIZ": True,
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_SUPERPOWERS_SENTINELS = (
|
|
77
|
+
("skills", "brainstorming"),
|
|
78
|
+
("commands", "superpowers.md"),
|
|
79
|
+
("commands", "superpower.md"),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def resolve_preset(preset: str | None) -> str:
|
|
84
|
+
if preset in VALID_PRESETS:
|
|
85
|
+
return preset
|
|
86
|
+
return "safe"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_preset_features(preset: str | None) -> dict[str, bool]:
|
|
90
|
+
resolved = resolve_preset(preset)
|
|
91
|
+
return dict(PRESET_FEATURES[resolved])
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _resolve_claude_dir(base_dir: Path) -> Path:
|
|
95
|
+
nested = base_dir / ".claude"
|
|
96
|
+
if nested.exists():
|
|
97
|
+
return nested
|
|
98
|
+
return base_dir
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def detect_ecosystems(project_dir: str | Path) -> list[str]:
|
|
102
|
+
root = Path(project_dir)
|
|
103
|
+
claude_dir = _resolve_claude_dir(root)
|
|
104
|
+
detected: list[str] = []
|
|
105
|
+
|
|
106
|
+
if (root / ".omc").exists():
|
|
107
|
+
detected.append("omc")
|
|
108
|
+
if (root / ".omx").exists():
|
|
109
|
+
detected.append("omx")
|
|
110
|
+
|
|
111
|
+
if any((claude_dir / Path(*parts)).exists() for parts in _SUPERPOWERS_SENTINELS):
|
|
112
|
+
detected.append("superpowers")
|
|
113
|
+
|
|
114
|
+
return detected
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def recommend_mode(detected_ecosystems: list[str]) -> str:
|
|
118
|
+
if detected_ecosystems:
|
|
119
|
+
return "omg-only"
|
|
120
|
+
return "omg-only"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _build_actions(mode: str, detected_ecosystems: list[str]) -> list[str]:
|
|
124
|
+
if mode == "coexist":
|
|
125
|
+
actions = [
|
|
126
|
+
"Keep OMG in a non-destructive coexistence mode.",
|
|
127
|
+
"Avoid claiming ownership of third-party command namespaces or HUD surfaces.",
|
|
128
|
+
"Install OMG hooks, MCP, and runtime where they do not overwrite existing ecosystems.",
|
|
129
|
+
]
|
|
130
|
+
if detected_ecosystems:
|
|
131
|
+
actions.append(
|
|
132
|
+
"Record overlapping ecosystems and route around conflicts instead of disabling them."
|
|
133
|
+
)
|
|
134
|
+
return actions
|
|
135
|
+
|
|
136
|
+
actions = [
|
|
137
|
+
"Promote OMG to the primary orchestration, HUD, and MCP layer.",
|
|
138
|
+
"Back up existing OMG-adjacent surfaces before disabling overlaps.",
|
|
139
|
+
"Keep compat available for legacy skill routing without using it as onboarding."
|
|
140
|
+
]
|
|
141
|
+
if detected_ecosystems:
|
|
142
|
+
actions.append(
|
|
143
|
+
"Adopt portable state where safe and disable overlapping third-party surfaces."
|
|
144
|
+
)
|
|
145
|
+
return actions
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _build_skipped_overlaps(mode: str, detected_ecosystems: list[str]) -> list[str]:
|
|
149
|
+
if not detected_ecosystems:
|
|
150
|
+
return []
|
|
151
|
+
if mode == "coexist":
|
|
152
|
+
return [
|
|
153
|
+
"Third-party slash-command namespaces remain untouched.",
|
|
154
|
+
"Existing HUD and hook ownership is preserved unless explicitly replaced later.",
|
|
155
|
+
]
|
|
156
|
+
return [
|
|
157
|
+
"Destructive cross-ecosystem migration is skipped unless OMG can back up the prior state first.",
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _build_follow_up(mode: str, preset: str, detected_ecosystems: list[str]) -> list[str]:
|
|
162
|
+
follow_up = [
|
|
163
|
+
"Run /OMG:setup again whenever providers or auth state change.",
|
|
164
|
+
f"Current preset: {preset}.",
|
|
165
|
+
]
|
|
166
|
+
if mode == "coexist":
|
|
167
|
+
follow_up.append("Review overlaps before enabling additional OMG labs surfaces.")
|
|
168
|
+
else:
|
|
169
|
+
follow_up.append("Run /OMG:crazy <goal> after setup to use the recommended front door.")
|
|
170
|
+
if detected_ecosystems:
|
|
171
|
+
follow_up.append(
|
|
172
|
+
"Review the adoption report before deleting any previous OMC, OMX, or Superpowers files."
|
|
173
|
+
)
|
|
174
|
+
return follow_up
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def build_adoption_report(
|
|
178
|
+
project_dir: str | Path,
|
|
179
|
+
*,
|
|
180
|
+
requested_mode: str | None = None,
|
|
181
|
+
preset: str | None = None,
|
|
182
|
+
adopt: str = "auto",
|
|
183
|
+
) -> dict[str, Any]:
|
|
184
|
+
detected_ecosystems = detect_ecosystems(project_dir) if adopt == "auto" else []
|
|
185
|
+
recommended_mode = recommend_mode(detected_ecosystems)
|
|
186
|
+
selected_mode = requested_mode if requested_mode in VALID_ADOPTION_MODES else recommended_mode
|
|
187
|
+
resolved_preset = resolve_preset(preset)
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
"schema": "OmgAdoptionReport",
|
|
191
|
+
"brand": CANONICAL_BRAND,
|
|
192
|
+
"version": CANONICAL_VERSION,
|
|
193
|
+
"repo": CANONICAL_REPO_URL,
|
|
194
|
+
"package": CANONICAL_PACKAGE_NAME,
|
|
195
|
+
"plugin_id": CANONICAL_PLUGIN_ID,
|
|
196
|
+
"marketplace_id": CANONICAL_MARKETPLACE_ID,
|
|
197
|
+
"detected_ecosystems": detected_ecosystems,
|
|
198
|
+
"recommended_mode": recommended_mode,
|
|
199
|
+
"selected_mode": selected_mode,
|
|
200
|
+
"preset": resolved_preset,
|
|
201
|
+
"actions": _build_actions(selected_mode, detected_ecosystems),
|
|
202
|
+
"skipped_overlaps": _build_skipped_overlaps(selected_mode, detected_ecosystems),
|
|
203
|
+
"follow_up": _build_follow_up(selected_mode, resolved_preset, detected_ecosystems),
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def write_adoption_report(project_dir: str | Path, report: dict[str, Any]) -> str:
|
|
208
|
+
state_dir = Path(project_dir) / ".omg" / "state"
|
|
209
|
+
state_dir.mkdir(parents=True, exist_ok=True)
|
|
210
|
+
report_path = state_dir / "adoption-report.json"
|
|
211
|
+
report_path.write_text(json.dumps(report, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
|
|
212
|
+
return str(report_path)
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
DEFAULT_WORKFLOW_PATH: tuple[str, ...] = (
|
|
7
|
+
"plan",
|
|
8
|
+
"implement",
|
|
9
|
+
"qa",
|
|
10
|
+
"simulate",
|
|
11
|
+
"final_test",
|
|
12
|
+
"production",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
_STAGE_ALIASES = {
|
|
16
|
+
"plan": "plan",
|
|
17
|
+
"planning": "plan",
|
|
18
|
+
"implement": "implement",
|
|
19
|
+
"implementation": "implement",
|
|
20
|
+
"build": "implement",
|
|
21
|
+
"qa": "qa",
|
|
22
|
+
"quality": "qa",
|
|
23
|
+
"quality_assurance": "qa",
|
|
24
|
+
"test": "final_test",
|
|
25
|
+
"testing": "final_test",
|
|
26
|
+
"final_test": "final_test",
|
|
27
|
+
"final-testing": "final_test",
|
|
28
|
+
"simulate": "simulate",
|
|
29
|
+
"simulation": "simulate",
|
|
30
|
+
"prod": "production",
|
|
31
|
+
"production": "production",
|
|
32
|
+
"deploy": "production",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _as_string_list(value: Any) -> list[str]:
|
|
37
|
+
if isinstance(value, str):
|
|
38
|
+
text = value.strip()
|
|
39
|
+
return [text] if text else []
|
|
40
|
+
if isinstance(value, Iterable):
|
|
41
|
+
items: list[str] = []
|
|
42
|
+
for raw in value:
|
|
43
|
+
if isinstance(raw, str):
|
|
44
|
+
text = raw.strip()
|
|
45
|
+
if text:
|
|
46
|
+
items.append(text)
|
|
47
|
+
return items
|
|
48
|
+
return []
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _resolve_workflow_path(idea: dict[str, Any]) -> list[str]:
|
|
52
|
+
raw_path = idea.get("workflow") or idea.get("path") or idea.get("delivery_path") or idea.get("workflow_path")
|
|
53
|
+
requested = _as_string_list(raw_path)
|
|
54
|
+
resolved: list[str] = []
|
|
55
|
+
for stage in requested:
|
|
56
|
+
key = _STAGE_ALIASES.get(stage.strip().lower())
|
|
57
|
+
if key and key not in resolved:
|
|
58
|
+
resolved.append(key)
|
|
59
|
+
if not resolved:
|
|
60
|
+
return list(DEFAULT_WORKFLOW_PATH)
|
|
61
|
+
for required in DEFAULT_WORKFLOW_PATH:
|
|
62
|
+
if required not in resolved:
|
|
63
|
+
resolved.append(required)
|
|
64
|
+
return resolved
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def build_business_task_plan(idea: dict[str, Any]) -> dict[str, Any]:
|
|
68
|
+
goal = str(idea.get("goal", "")).strip() or "unspecified-goal"
|
|
69
|
+
constraints = _as_string_list(idea.get("constraints", []))
|
|
70
|
+
acceptance = _as_string_list(idea.get("acceptance", []))
|
|
71
|
+
user_instructions = _as_string_list(idea.get("user_instructions", []))
|
|
72
|
+
workflow_path = _resolve_workflow_path(idea)
|
|
73
|
+
|
|
74
|
+
tasks: list[dict[str, Any]] = []
|
|
75
|
+
task_id = 1
|
|
76
|
+
|
|
77
|
+
if user_instructions:
|
|
78
|
+
for instruction in user_instructions:
|
|
79
|
+
tasks.append(
|
|
80
|
+
{
|
|
81
|
+
"id": f"T{task_id}",
|
|
82
|
+
"stage": "plan",
|
|
83
|
+
"title": "Capture user instruction",
|
|
84
|
+
"detail": instruction,
|
|
85
|
+
"source": "user_instructions",
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
task_id += 1
|
|
89
|
+
|
|
90
|
+
for item in constraints:
|
|
91
|
+
tasks.append(
|
|
92
|
+
{
|
|
93
|
+
"id": f"T{task_id}",
|
|
94
|
+
"stage": "plan",
|
|
95
|
+
"title": "Capture delivery constraint",
|
|
96
|
+
"detail": item,
|
|
97
|
+
"source": "constraints",
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
task_id += 1
|
|
101
|
+
|
|
102
|
+
if "implement" in workflow_path:
|
|
103
|
+
tasks.append(
|
|
104
|
+
{
|
|
105
|
+
"id": f"T{task_id}",
|
|
106
|
+
"stage": "implement",
|
|
107
|
+
"title": "Implement scoped changes",
|
|
108
|
+
"detail": "Apply changes that satisfy user instructions and follow repository patterns.",
|
|
109
|
+
"source": "workflow",
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
task_id += 1
|
|
113
|
+
|
|
114
|
+
if "qa" in workflow_path:
|
|
115
|
+
tasks.append(
|
|
116
|
+
{
|
|
117
|
+
"id": f"T{task_id}",
|
|
118
|
+
"stage": "qa",
|
|
119
|
+
"title": "Run QA checks",
|
|
120
|
+
"detail": "Validate behavior against constraints and acceptance criteria.",
|
|
121
|
+
"source": "workflow",
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
task_id += 1
|
|
125
|
+
|
|
126
|
+
if "simulate" in workflow_path:
|
|
127
|
+
tasks.append(
|
|
128
|
+
{
|
|
129
|
+
"id": f"T{task_id}",
|
|
130
|
+
"stage": "simulate",
|
|
131
|
+
"title": "Simulate delivery scenarios",
|
|
132
|
+
"detail": "Exercise expected paths and edge cases before final testing.",
|
|
133
|
+
"source": "workflow",
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
task_id += 1
|
|
137
|
+
|
|
138
|
+
for item in acceptance:
|
|
139
|
+
tasks.append(
|
|
140
|
+
{
|
|
141
|
+
"id": f"T{task_id}",
|
|
142
|
+
"stage": "final_test",
|
|
143
|
+
"title": "Validate acceptance criterion",
|
|
144
|
+
"detail": item,
|
|
145
|
+
"source": "acceptance",
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
task_id += 1
|
|
149
|
+
|
|
150
|
+
if "production" in workflow_path:
|
|
151
|
+
tasks.append(
|
|
152
|
+
{
|
|
153
|
+
"id": f"T{task_id}",
|
|
154
|
+
"stage": "production",
|
|
155
|
+
"title": "Prepare production handoff",
|
|
156
|
+
"detail": "Confirm release readiness, residual risks, and deployment checks.",
|
|
157
|
+
"source": "workflow",
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
stage_summaries = []
|
|
162
|
+
for stage in workflow_path:
|
|
163
|
+
stage_tasks = [task for task in tasks if task["stage"] == stage]
|
|
164
|
+
stage_summaries.append(
|
|
165
|
+
{
|
|
166
|
+
"stage": stage,
|
|
167
|
+
"task_count": len(stage_tasks),
|
|
168
|
+
"tasks": stage_tasks,
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
"goal": goal,
|
|
174
|
+
"requested_path": _as_string_list(
|
|
175
|
+
idea.get("workflow") or idea.get("path") or idea.get("delivery_path") or idea.get("workflow_path")
|
|
176
|
+
),
|
|
177
|
+
"resolved_path": workflow_path,
|
|
178
|
+
"constraints": constraints,
|
|
179
|
+
"acceptance": acceptance,
|
|
180
|
+
"user_instructions": user_instructions,
|
|
181
|
+
"stages": stage_summaries,
|
|
182
|
+
"task_count": len(tasks),
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def build_business_workflow_result(
|
|
187
|
+
*,
|
|
188
|
+
idea: dict[str, Any],
|
|
189
|
+
plan: dict[str, Any],
|
|
190
|
+
execution: dict[str, Any],
|
|
191
|
+
verification: dict[str, Any],
|
|
192
|
+
) -> dict[str, Any]:
|
|
193
|
+
plan_payload = build_business_task_plan(idea)
|
|
194
|
+
checks_value = verification.get("checks") if isinstance(verification, dict) else None
|
|
195
|
+
checks = checks_value if isinstance(checks_value, list) else []
|
|
196
|
+
checks_ok = bool(checks) and all(isinstance(check, dict) and check.get("passed") is True for check in checks)
|
|
197
|
+
|
|
198
|
+
stage_status = {
|
|
199
|
+
"plan": "completed" if plan.get("status") == "planned" else "failed",
|
|
200
|
+
"implement": "completed" if execution.get("status") == "executed" else "failed",
|
|
201
|
+
"qa": "completed" if checks_ok else "failed",
|
|
202
|
+
"simulate": "completed" if checks_ok else "failed",
|
|
203
|
+
"final_test": "completed" if checks_ok else "failed",
|
|
204
|
+
"production": "ready" if checks_ok else "blocked",
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
"goal": plan_payload["goal"],
|
|
209
|
+
"workflow_path": plan_payload["resolved_path"],
|
|
210
|
+
"requested_workflow_path": plan_payload["requested_path"],
|
|
211
|
+
"task_plan": plan_payload,
|
|
212
|
+
"stage_status": [
|
|
213
|
+
{
|
|
214
|
+
"stage": stage,
|
|
215
|
+
"status": stage_status.get(stage, "pending"),
|
|
216
|
+
}
|
|
217
|
+
for stage in plan_payload["resolved_path"]
|
|
218
|
+
],
|
|
219
|
+
"ready_for_production": checks_ok,
|
|
220
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""CLI provider abstractions and provider registry."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _run_tool(cmd: list[str], *, timeout: int = 30) -> subprocess.CompletedProcess[str]:
|
|
11
|
+
"""Run an external tool with a mandatory timeout."""
|
|
12
|
+
return subprocess.run(
|
|
13
|
+
cmd,
|
|
14
|
+
capture_output=True,
|
|
15
|
+
text=True,
|
|
16
|
+
check=False,
|
|
17
|
+
timeout=timeout,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CLIProvider(ABC):
|
|
22
|
+
"""Abstract contract for external CLI providers."""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def get_name(self) -> str:
|
|
26
|
+
"""Return the provider name (for example: codex or gemini)."""
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def detect(self) -> bool:
|
|
30
|
+
"""Return True when the provider CLI binary is available on PATH."""
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def check_auth(self) -> tuple[bool | None, str]:
|
|
34
|
+
"""Return provider authentication state and a human-readable message."""
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def invoke(self, prompt: str, project_dir: str, timeout: int = 120) -> dict[str, Any]: # pyright: ignore[reportExplicitAny]
|
|
38
|
+
"""Invoke provider directly using subprocess mode."""
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def invoke_tmux(self, prompt: str, project_dir: str, timeout: int = 120) -> dict[str, Any]: # pyright: ignore[reportExplicitAny]
|
|
42
|
+
"""Invoke provider through tmux-managed execution mode."""
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def get_non_interactive_cmd(self, prompt: str) -> list[str]:
|
|
46
|
+
"""Return non-interactive command arguments for the provider CLI."""
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def get_config_path(self) -> str:
|
|
50
|
+
"""Return provider configuration file path."""
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def write_mcp_config(self, server_url: str, server_name: str = "memory-server") -> None:
|
|
54
|
+
"""Write or update MCP server configuration for this provider."""
|
|
55
|
+
|
|
56
|
+
def run_tool(self, cmd: list[str], *, timeout: int = 30) -> subprocess.CompletedProcess[str]:
|
|
57
|
+
"""Execute subprocess commands via mandatory timeout helper."""
|
|
58
|
+
return _run_tool(cmd, timeout=timeout)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
_PROVIDER_REGISTRY: dict[str, CLIProvider] = {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def register_provider(provider: CLIProvider) -> None:
|
|
65
|
+
"""Register a CLI provider by its canonical name."""
|
|
66
|
+
_PROVIDER_REGISTRY[provider.get_name()] = provider
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_provider(name: str) -> CLIProvider | None:
|
|
70
|
+
"""Return a registered CLI provider by name."""
|
|
71
|
+
return _PROVIDER_REGISTRY.get(name)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def list_available_providers() -> list[str]:
|
|
75
|
+
"""Return registered provider names in insertion order."""
|
|
76
|
+
return list(_PROVIDER_REGISTRY)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
__all__ = [
|
|
80
|
+
"CLIProvider",
|
|
81
|
+
"_PROVIDER_REGISTRY",
|
|
82
|
+
"get_provider",
|
|
83
|
+
"list_available_providers",
|
|
84
|
+
"register_provider",
|
|
85
|
+
]
|