@trac3er/oh-my-god 2.0.8 → 2.0.9
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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.gemini/settings.json +11 -0
- package/.kimi/mcp.json +11 -0
- package/CHANGELOG.md +10 -0
- package/OMG-setup.sh +1 -1
- package/OMG_COMPAT_CONTRACT.md +10 -4
- package/README.md +1 -0
- package/build/lib/commands/OMG:forge.md +92 -0
- package/build/lib/commands/OMG:mode.md +13 -13
- package/build/lib/commands/OMG:session-branch.md +17 -1
- package/build/lib/commands/OMG:session-fork.md +5 -1
- package/build/lib/commands/OMG:session-merge.md +5 -1
- package/build/lib/control_plane/server.py +4 -0
- package/build/lib/control_plane/service.py +55 -0
- package/build/lib/hooks/setup_wizard.py +21 -1
- package/build/lib/hooks/shadow_manager.py +25 -2
- package/build/lib/hooks/state_migration.py +3 -0
- package/build/lib/plugins/dephealth/cve_scanner.py +91 -0
- package/build/lib/plugins/dephealth/vuln_analyzer.py +7 -0
- package/build/lib/registry/omg-capability.schema.json +83 -1
- package/build/lib/runtime/adoption.py +12 -4
- package/build/lib/runtime/artifact_parsers.py +161 -0
- package/build/lib/runtime/background_verification.py +48 -0
- package/build/lib/runtime/claim_judge.py +184 -7
- package/build/lib/runtime/contract_compiler.py +118 -9
- package/build/lib/runtime/evidence_query.py +203 -0
- package/build/lib/runtime/omg_mcp_server.py +19 -0
- package/build/lib/runtime/playwright_adapter.py +39 -0
- package/build/lib/runtime/proof_chain.py +136 -8
- package/build/lib/runtime/proof_gate.py +102 -0
- package/build/lib/runtime/providers/gemini_provider.py +7 -0
- package/build/lib/runtime/providers/kimi_provider.py +7 -0
- package/build/lib/runtime/repro_pack.py +292 -0
- package/build/lib/runtime/runtime_profile.py +87 -15
- package/build/lib/runtime/security_check.py +86 -3
- package/build/lib/runtime/test_intent_lock.py +47 -0
- package/build/lib/runtime/tracebank.py +33 -3
- package/build/lib/runtime/verification_loop.py +73 -0
- package/commands/OMG:forge.md +92 -0
- package/commands/OMG:mode.md +13 -13
- package/commands/OMG:session-branch.md +17 -1
- package/commands/OMG:session-fork.md +5 -1
- package/commands/OMG:session-merge.md +5 -1
- package/control_plane/server.py +4 -0
- package/control_plane/service.py +55 -0
- package/dist/enterprise/bundle/.gemini/settings.json +11 -0
- package/dist/enterprise/bundle/.kimi/mcp.json +11 -0
- package/dist/enterprise/bundle/OMG_COMPAT_CONTRACT.md +9 -3
- package/dist/enterprise/bundle/registry/omg-capability.schema.json +83 -1
- package/dist/enterprise/bundle/settings.json +1 -0
- package/dist/enterprise/manifest.json +17 -3
- package/dist/public/bundle/.agents/skills/omg/incident-replay/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/incident-replay/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/lsp-pack/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/lsp-pack/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/plan-council/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/plan-council/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/preflight/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/preflight/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/proof-gate/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/proof-gate/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/remote-supervisor/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/remote-supervisor/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/robotics/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/robotics/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/security-check/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/security-check/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/test-intent-lock/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/test-intent-lock/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/tracebank/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/tracebank/openai.yaml +1 -1
- package/dist/public/bundle/.agents/skills/omg/vision/SKILL.md +1 -1
- package/dist/public/bundle/.agents/skills/omg/vision/openai.yaml +1 -1
- package/dist/public/bundle/.gemini/settings.json +11 -0
- package/dist/public/bundle/.kimi/mcp.json +11 -0
- package/dist/public/bundle/OMG_COMPAT_CONTRACT.md +9 -3
- package/dist/public/bundle/registry/omg-capability.schema.json +83 -1
- package/dist/public/bundle/settings.json +2 -1
- package/dist/public/manifest.json +43 -29
- package/docs/proof.md +1 -0
- package/hooks/setup_wizard.py +21 -1
- package/hooks/shadow_manager.py +25 -2
- package/hooks/state_migration.py +3 -0
- package/hud/omg-hud.mjs +66 -3
- package/package.json +1 -1
- package/plugins/advanced/plugin.json +1 -1
- package/plugins/core/plugin.json +1 -1
- package/plugins/dephealth/cve_scanner.py +91 -0
- package/plugins/dephealth/vuln_analyzer.py +7 -0
- package/pyproject.toml +1 -1
- package/registry/omg-capability.schema.json +83 -1
- package/runtime/adoption.py +13 -5
- package/runtime/artifact_parsers.py +161 -0
- package/runtime/background_verification.py +48 -0
- package/runtime/claim_judge.py +184 -7
- package/runtime/contract_compiler.py +118 -9
- package/runtime/evidence_query.py +203 -0
- package/runtime/omg_mcp_server.py +19 -0
- package/runtime/playwright_adapter.py +39 -0
- package/runtime/proof_chain.py +136 -8
- package/runtime/proof_gate.py +102 -0
- package/runtime/providers/gemini_provider.py +7 -0
- package/runtime/providers/kimi_provider.py +7 -0
- package/runtime/repro_pack.py +292 -0
- package/runtime/runtime_profile.py +87 -15
- package/runtime/security_check.py +86 -3
- package/runtime/test_intent_lock.py +47 -0
- package/runtime/tracebank.py +33 -3
- package/runtime/verification_loop.py +73 -0
- package/scripts/omg.py +30 -3
- package/settings.json +4 -3
- package/tools/python_sandbox.py +9 -6
- package/tools/session_snapshot.py +146 -40
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import cast
|
|
8
|
+
|
|
9
|
+
from runtime.evidence_query import (
|
|
10
|
+
JsonObject,
|
|
11
|
+
JsonValue,
|
|
12
|
+
get_eval,
|
|
13
|
+
get_evidence_pack,
|
|
14
|
+
get_lineage,
|
|
15
|
+
get_trace,
|
|
16
|
+
get_verification_state,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _now() -> str:
|
|
21
|
+
return datetime.now(timezone.utc).isoformat()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_json(path: Path) -> JsonObject | None:
|
|
25
|
+
try:
|
|
26
|
+
payload: object = json.loads(path.read_text(encoding="utf-8")) # pyright: ignore[reportAny]
|
|
27
|
+
except (OSError, json.JSONDecodeError):
|
|
28
|
+
return None
|
|
29
|
+
if not isinstance(payload, dict):
|
|
30
|
+
return None
|
|
31
|
+
return cast(JsonObject, payload)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _hash_path(path: Path) -> str:
|
|
35
|
+
if not path.exists() or not path.is_file():
|
|
36
|
+
return ""
|
|
37
|
+
digest = hashlib.sha256()
|
|
38
|
+
with path.open("rb") as handle:
|
|
39
|
+
while True:
|
|
40
|
+
chunk = handle.read(8192)
|
|
41
|
+
if not chunk:
|
|
42
|
+
break
|
|
43
|
+
digest.update(chunk)
|
|
44
|
+
return digest.hexdigest()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _rel(path: Path, root: Path) -> str:
|
|
48
|
+
try:
|
|
49
|
+
return path.resolve().relative_to(root.resolve()).as_posix()
|
|
50
|
+
except ValueError:
|
|
51
|
+
return path.as_posix()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _as_object_list(value: JsonValue | None) -> list[JsonObject]:
|
|
55
|
+
if not isinstance(value, list):
|
|
56
|
+
return []
|
|
57
|
+
return [item for item in value if isinstance(item, dict)]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _as_string_list(value: JsonValue | None) -> list[str]:
|
|
61
|
+
if not isinstance(value, list):
|
|
62
|
+
return []
|
|
63
|
+
return [item for item in value if isinstance(item, str)]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _string_field(payload: JsonObject, key: str) -> str:
|
|
67
|
+
value = payload.get(key)
|
|
68
|
+
return value if isinstance(value, str) else ""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _artifact_ref(*, kind: str, path: str, sha256: str = "", extras: JsonObject | None = None) -> JsonObject:
|
|
72
|
+
artifact: JsonObject = {
|
|
73
|
+
"kind": kind,
|
|
74
|
+
"path": path,
|
|
75
|
+
"sha256": sha256,
|
|
76
|
+
}
|
|
77
|
+
if extras:
|
|
78
|
+
artifact.update(extras)
|
|
79
|
+
return artifact
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _trace_reference(root: Path, trace_ids: list[str]) -> JsonObject | None:
|
|
83
|
+
trace_path = root / ".omg" / "tracebank" / "events.jsonl"
|
|
84
|
+
if not trace_path.exists():
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
requested = sorted({trace_id for trace_id in trace_ids if trace_id})
|
|
88
|
+
matched: list[str] = []
|
|
89
|
+
matched_count = 0
|
|
90
|
+
try:
|
|
91
|
+
for line in trace_path.read_text(encoding="utf-8").splitlines():
|
|
92
|
+
line = line.strip()
|
|
93
|
+
if not line:
|
|
94
|
+
continue
|
|
95
|
+
try:
|
|
96
|
+
row: object = json.loads(line) # pyright: ignore[reportAny]
|
|
97
|
+
except json.JSONDecodeError:
|
|
98
|
+
continue
|
|
99
|
+
if not isinstance(row, dict):
|
|
100
|
+
continue
|
|
101
|
+
row_payload = cast(JsonObject, row)
|
|
102
|
+
trace_id = _string_field(row_payload, "trace_id")
|
|
103
|
+
if trace_id in requested:
|
|
104
|
+
matched_count += 1
|
|
105
|
+
if trace_id not in matched:
|
|
106
|
+
matched.append(trace_id)
|
|
107
|
+
except OSError:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
return _artifact_ref(
|
|
111
|
+
kind="trace_events",
|
|
112
|
+
path=".omg/tracebank/events.jsonl",
|
|
113
|
+
sha256=_hash_path(trace_path),
|
|
114
|
+
extras=cast(JsonObject, {"trace_ids": sorted(matched), "event_count": matched_count}),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _find_lineage_path(root: Path, lineage_id: str) -> str:
|
|
119
|
+
if not lineage_id:
|
|
120
|
+
return ""
|
|
121
|
+
lineage_dir = root / ".omg" / "lineage"
|
|
122
|
+
if not lineage_dir.exists():
|
|
123
|
+
return ""
|
|
124
|
+
|
|
125
|
+
for path in sorted(lineage_dir.glob("*.json")):
|
|
126
|
+
payload = _load_json(path)
|
|
127
|
+
if payload is None:
|
|
128
|
+
continue
|
|
129
|
+
if _string_field(payload, "lineage_id") == lineage_id:
|
|
130
|
+
return _rel(path, root)
|
|
131
|
+
return ""
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _security_artifacts(root: Path, scans: JsonValue | None) -> list[JsonObject]:
|
|
135
|
+
artifacts: list[JsonObject] = []
|
|
136
|
+
for item in _as_object_list(scans):
|
|
137
|
+
path = _string_field(item, "path").strip()
|
|
138
|
+
if not path:
|
|
139
|
+
continue
|
|
140
|
+
artifacts.append(
|
|
141
|
+
_artifact_ref(
|
|
142
|
+
kind="security_evidence",
|
|
143
|
+
path=path,
|
|
144
|
+
sha256=_hash_path(root / path),
|
|
145
|
+
extras={"schema": _string_field(item, "schema")},
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
return artifacts
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _browser_artifacts(root: Path, records: JsonValue | None) -> list[JsonObject]:
|
|
152
|
+
artifacts: list[JsonObject] = []
|
|
153
|
+
for item in _as_object_list(records):
|
|
154
|
+
if _string_field(item, "kind") != "browser_trace":
|
|
155
|
+
continue
|
|
156
|
+
path = _string_field(item, "path").strip()
|
|
157
|
+
if not path:
|
|
158
|
+
continue
|
|
159
|
+
artifacts.append(
|
|
160
|
+
_artifact_ref(
|
|
161
|
+
kind="browser_trace",
|
|
162
|
+
path=path,
|
|
163
|
+
sha256=_hash_path(root / path),
|
|
164
|
+
extras={"trace_id": _string_field(item, "trace_id")},
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
return artifacts
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _incident_artifacts(root: Path, records: JsonValue | None) -> list[JsonObject]:
|
|
171
|
+
artifacts: list[JsonObject] = []
|
|
172
|
+
for item in _as_object_list(records):
|
|
173
|
+
kind = _string_field(item, "kind")
|
|
174
|
+
if "incident" not in kind:
|
|
175
|
+
continue
|
|
176
|
+
path = _string_field(item, "path").strip()
|
|
177
|
+
if not path:
|
|
178
|
+
continue
|
|
179
|
+
artifacts.append(
|
|
180
|
+
_artifact_ref(
|
|
181
|
+
kind=kind,
|
|
182
|
+
path=path,
|
|
183
|
+
sha256=_hash_path(root / path),
|
|
184
|
+
extras={"trace_id": _string_field(item, "trace_id")},
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
return artifacts
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _dedupe_artifacts(artifacts: list[JsonObject]) -> list[JsonObject]:
|
|
191
|
+
seen: set[tuple[str, str, str]] = set()
|
|
192
|
+
deduped: list[JsonObject] = []
|
|
193
|
+
sorted_items = sorted(artifacts, key=lambda item: (_string_field(item, "kind"), _string_field(item, "path")))
|
|
194
|
+
for artifact in sorted_items:
|
|
195
|
+
key = (
|
|
196
|
+
_string_field(artifact, "kind"),
|
|
197
|
+
_string_field(artifact, "path"),
|
|
198
|
+
_string_field(artifact, "sha256"),
|
|
199
|
+
)
|
|
200
|
+
if key in seen:
|
|
201
|
+
continue
|
|
202
|
+
seen.add(key)
|
|
203
|
+
deduped.append(artifact)
|
|
204
|
+
return deduped
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def build_repro_pack(project_dir: str, run_id: str) -> dict[str, str]:
|
|
208
|
+
root = Path(project_dir)
|
|
209
|
+
evidence_pack = get_evidence_pack(project_dir, run_id)
|
|
210
|
+
if evidence_pack is None:
|
|
211
|
+
return {
|
|
212
|
+
"status": "error",
|
|
213
|
+
"run_id": run_id,
|
|
214
|
+
"reason": "evidence_pack_not_found",
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
evidence_pack_path = f".omg/evidence/{run_id}.json"
|
|
218
|
+
artifacts: list[JsonObject] = [
|
|
219
|
+
_artifact_ref(kind="evidence_pack", path=evidence_pack_path, sha256=_hash_path(root / evidence_pack_path))
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
trace_ids = sorted(set(_as_string_list(evidence_pack.get("trace_ids"))))
|
|
223
|
+
trace_file = root / ".omg" / "tracebank" / "events.jsonl"
|
|
224
|
+
for trace_id in trace_ids:
|
|
225
|
+
if get_trace(project_dir, trace_id) is None:
|
|
226
|
+
continue
|
|
227
|
+
artifacts.append(
|
|
228
|
+
_artifact_ref(
|
|
229
|
+
kind="trace",
|
|
230
|
+
path=".omg/tracebank/events.jsonl",
|
|
231
|
+
sha256=_hash_path(trace_file),
|
|
232
|
+
extras={"trace_id": trace_id},
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
trace_reference = _trace_reference(root, trace_ids)
|
|
236
|
+
if trace_reference is not None:
|
|
237
|
+
artifacts.append(trace_reference)
|
|
238
|
+
|
|
239
|
+
eval_path = root / ".omg" / "evals" / "latest.json"
|
|
240
|
+
if get_eval(project_dir) is not None and eval_path.exists():
|
|
241
|
+
artifacts.append(_artifact_ref(kind="eval", path=".omg/evals/latest.json", sha256=_hash_path(eval_path)))
|
|
242
|
+
|
|
243
|
+
lineage_payload = evidence_pack.get("lineage")
|
|
244
|
+
lineage_id = ""
|
|
245
|
+
if isinstance(lineage_payload, dict):
|
|
246
|
+
lineage_payload_obj = cast(JsonObject, lineage_payload)
|
|
247
|
+
lineage_id = _string_field(lineage_payload_obj, "lineage_id")
|
|
248
|
+
if lineage_id and get_lineage(project_dir, lineage_id) is not None:
|
|
249
|
+
lineage_path = _find_lineage_path(root, lineage_id)
|
|
250
|
+
if lineage_path:
|
|
251
|
+
artifacts.append(
|
|
252
|
+
_artifact_ref(
|
|
253
|
+
kind="lineage",
|
|
254
|
+
path=lineage_path,
|
|
255
|
+
sha256=_hash_path(root / lineage_path),
|
|
256
|
+
extras={"lineage_id": lineage_id},
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
artifacts.extend(_security_artifacts(root, evidence_pack.get("security_scans")))
|
|
261
|
+
artifacts.extend(_browser_artifacts(root, evidence_pack.get("artifacts")))
|
|
262
|
+
artifacts.extend(_incident_artifacts(root, evidence_pack.get("artifacts")))
|
|
263
|
+
|
|
264
|
+
verification_path = root / ".omg" / "state" / "background-verification.json"
|
|
265
|
+
if get_verification_state(project_dir) is not None and verification_path.exists():
|
|
266
|
+
artifacts.append(
|
|
267
|
+
_artifact_ref(
|
|
268
|
+
kind="verification_state",
|
|
269
|
+
path=".omg/state/background-verification.json",
|
|
270
|
+
sha256=_hash_path(verification_path),
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
unresolved_risks = _as_string_list(evidence_pack.get("unresolved_risks"))
|
|
275
|
+
manifest: dict[str, object] = {
|
|
276
|
+
"schema": "ReproPack",
|
|
277
|
+
"schema_version": 1,
|
|
278
|
+
"run_id": run_id,
|
|
279
|
+
"evidence_pack_path": evidence_pack_path,
|
|
280
|
+
"artifacts": _dedupe_artifacts(artifacts),
|
|
281
|
+
"unresolved_risks": unresolved_risks,
|
|
282
|
+
"assembled_at": _now(),
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
out_path = root / ".omg" / "evidence" / f"repro-pack-{run_id}.json"
|
|
286
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
287
|
+
_ = out_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
|
|
288
|
+
return {
|
|
289
|
+
"status": "ok",
|
|
290
|
+
"run_id": run_id,
|
|
291
|
+
"path": _rel(out_path, root),
|
|
292
|
+
}
|
|
@@ -2,31 +2,93 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import TypedDict, cast
|
|
6
6
|
|
|
7
7
|
import yaml
|
|
8
8
|
|
|
9
|
+
from .adoption import CANONICAL_MODE_NAMES
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
|
|
12
|
+
class RuntimeProfile(TypedDict):
|
|
13
|
+
profile: str
|
|
14
|
+
max_workers: int
|
|
15
|
+
background_polling: bool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CanonicalModeProfile(TypedDict):
|
|
19
|
+
concurrency: int
|
|
20
|
+
background_verification: bool
|
|
21
|
+
context_window: str
|
|
22
|
+
noise_level: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
PROFILE_PRESETS: dict[str, RuntimeProfile] = {
|
|
11
26
|
"eco": {"profile": "eco", "max_workers": 2, "background_polling": False},
|
|
12
27
|
"balanced": {"profile": "balanced", "max_workers": 3, "background_polling": False},
|
|
13
28
|
"turbo": {"profile": "turbo", "max_workers": 5, "background_polling": True},
|
|
14
29
|
}
|
|
15
30
|
|
|
31
|
+
RUNTIME_CONCURRENCY_PROFILE_NAMES = tuple(PROFILE_PRESETS.keys())
|
|
32
|
+
RESERVED_CANONICAL_MODE_NAMES = CANONICAL_MODE_NAMES
|
|
33
|
+
|
|
34
|
+
_CANONICAL_MODE_PROFILES: dict[str, CanonicalModeProfile] = {
|
|
35
|
+
"chill": {
|
|
36
|
+
"concurrency": 1,
|
|
37
|
+
"background_verification": False,
|
|
38
|
+
"context_window": "minimal",
|
|
39
|
+
"noise_level": "quiet",
|
|
40
|
+
},
|
|
41
|
+
"focused": {
|
|
42
|
+
"concurrency": 2,
|
|
43
|
+
"background_verification": False,
|
|
44
|
+
"context_window": "standard",
|
|
45
|
+
"noise_level": "normal",
|
|
46
|
+
},
|
|
47
|
+
"exploratory": {
|
|
48
|
+
"concurrency": 4,
|
|
49
|
+
"background_verification": True,
|
|
50
|
+
"context_window": "extended",
|
|
51
|
+
"noise_level": "verbose",
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def load_canonical_mode_profile(mode: str) -> dict[str, object]:
|
|
57
|
+
normalized_mode = mode.strip().lower()
|
|
58
|
+
profile = _CANONICAL_MODE_PROFILES.get(normalized_mode)
|
|
59
|
+
if profile is None:
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"Unknown canonical mode: {mode!r}. Valid: chill, focused, exploratory"
|
|
62
|
+
)
|
|
63
|
+
return dict(profile)
|
|
16
64
|
|
|
17
|
-
|
|
65
|
+
|
|
66
|
+
def load_runtime_profile(project_dir: str) -> RuntimeProfile:
|
|
18
67
|
runtime_path = Path(project_dir) / ".omg" / "runtime.yaml"
|
|
19
68
|
profile_name = "balanced"
|
|
20
69
|
if runtime_path.exists():
|
|
21
70
|
try:
|
|
22
|
-
|
|
71
|
+
raw_payload: object = yaml.safe_load(runtime_path.read_text(encoding="utf-8")) or {}
|
|
23
72
|
except Exception:
|
|
24
|
-
|
|
25
|
-
if isinstance(
|
|
26
|
-
|
|
27
|
-
|
|
73
|
+
raw_payload = {}
|
|
74
|
+
if isinstance(raw_payload, dict):
|
|
75
|
+
payload_map = cast(dict[object, object], raw_payload)
|
|
76
|
+
payload: dict[str, object] = {}
|
|
77
|
+
for key, value in payload_map.items():
|
|
78
|
+
if isinstance(key, str):
|
|
79
|
+
payload[key] = value
|
|
80
|
+
candidate_obj = payload.get("profile", profile_name)
|
|
81
|
+
candidate = candidate_obj.strip() if isinstance(candidate_obj, str) else profile_name
|
|
82
|
+
if candidate in RUNTIME_CONCURRENCY_PROFILE_NAMES:
|
|
28
83
|
profile_name = candidate
|
|
29
|
-
|
|
84
|
+
|
|
85
|
+
preset = PROFILE_PRESETS[profile_name]
|
|
86
|
+
result: RuntimeProfile = {
|
|
87
|
+
"profile": preset["profile"],
|
|
88
|
+
"max_workers": preset["max_workers"],
|
|
89
|
+
"background_polling": preset["background_polling"],
|
|
90
|
+
}
|
|
91
|
+
return result
|
|
30
92
|
|
|
31
93
|
|
|
32
94
|
def resolve_parallel_workers(project_dir: str, *, requested_workers: int) -> int:
|
|
@@ -43,19 +105,29 @@ def _load_cli_parallel_cap(project_dir: str) -> int | None:
|
|
|
43
105
|
if not config_path.exists():
|
|
44
106
|
return None
|
|
45
107
|
try:
|
|
46
|
-
|
|
108
|
+
raw_payload: object = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
|
47
109
|
except Exception:
|
|
48
110
|
return None
|
|
49
|
-
if not isinstance(
|
|
111
|
+
if not isinstance(raw_payload, dict):
|
|
50
112
|
return None
|
|
51
|
-
|
|
52
|
-
|
|
113
|
+
|
|
114
|
+
payload_map = cast(dict[object, object], raw_payload)
|
|
115
|
+
payload: dict[str, object] = {}
|
|
116
|
+
for key, value in payload_map.items():
|
|
117
|
+
if isinstance(key, str):
|
|
118
|
+
payload[key] = value
|
|
119
|
+
|
|
120
|
+
cli_configs_obj = payload.get("cli_configs")
|
|
121
|
+
if not isinstance(cli_configs_obj, dict):
|
|
53
122
|
return None
|
|
54
|
-
|
|
123
|
+
|
|
124
|
+
cli_configs = cast(dict[object, object], cli_configs_obj)
|
|
125
|
+
caps: list[int] = []
|
|
55
126
|
for config in cli_configs.values():
|
|
56
127
|
if not isinstance(config, dict):
|
|
57
128
|
continue
|
|
58
|
-
|
|
129
|
+
config_map = cast(dict[object, object], config)
|
|
130
|
+
value = config_map.get("max_parallel_agents")
|
|
59
131
|
if isinstance(value, int) and value > 0:
|
|
60
132
|
caps.append(value)
|
|
61
133
|
return min(caps) if caps else None
|
|
@@ -8,6 +8,7 @@ from hashlib import sha256
|
|
|
8
8
|
import json
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
import re
|
|
11
|
+
import shutil
|
|
11
12
|
import subprocess
|
|
12
13
|
from typing import Any
|
|
13
14
|
|
|
@@ -128,6 +129,8 @@ def run_security_check(
|
|
|
128
129
|
"severity": finding.get("severity"),
|
|
129
130
|
"exploitability": finding.get("exploitability", "unknown"),
|
|
130
131
|
"reachability": finding.get("reachability", "unknown"),
|
|
132
|
+
"kev_listed": finding.get("kev_listed", False),
|
|
133
|
+
"epss_score": finding.get("epss_score"),
|
|
131
134
|
"waived": bool(finding.get("waived")),
|
|
132
135
|
"waiver_justification": finding.get("waiver_justification", ""),
|
|
133
136
|
"message": finding.get("message", ""),
|
|
@@ -267,9 +270,89 @@ def _scan_python_ast(scope_path: Path) -> list[dict[str, Any]]:
|
|
|
267
270
|
continue
|
|
268
271
|
findings.extend(_scan_python_file(py_file, source))
|
|
269
272
|
findings.extend(_run_bandit_if_available(scope_path))
|
|
273
|
+
findings.extend(_scan_semgrep(scope_path))
|
|
270
274
|
return findings
|
|
271
275
|
|
|
272
276
|
|
|
277
|
+
def run_semgrep_scan(project_dir: str, rules: str = "auto") -> dict[str, Any]:
|
|
278
|
+
unavailable = {"status": "unavailable", "findings": [], "error": "semgrep not found"}
|
|
279
|
+
if shutil.which("semgrep") is None:
|
|
280
|
+
return unavailable
|
|
281
|
+
|
|
282
|
+
cmd = ["semgrep", "--json", "--config", rules, project_dir]
|
|
283
|
+
try:
|
|
284
|
+
proc = subprocess.run(cmd, capture_output=True, text=True, check=False, timeout=60)
|
|
285
|
+
except Exception:
|
|
286
|
+
return unavailable
|
|
287
|
+
|
|
288
|
+
if proc.returncode not in {0, 1}:
|
|
289
|
+
return unavailable
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
payload = json.loads(proc.stdout or "{}")
|
|
293
|
+
except Exception:
|
|
294
|
+
return unavailable
|
|
295
|
+
|
|
296
|
+
findings: list[dict[str, Any]] = []
|
|
297
|
+
for item in payload.get("results", []):
|
|
298
|
+
extra = item.get("extra") if isinstance(item.get("extra"), dict) else {}
|
|
299
|
+
start = item.get("start") if isinstance(item.get("start"), dict) else {}
|
|
300
|
+
findings.append(
|
|
301
|
+
{
|
|
302
|
+
"severity": _normalize_semgrep_severity(str(extra.get("severity", "WARNING"))),
|
|
303
|
+
"rule": str(item.get("check_id", "semgrep")),
|
|
304
|
+
"path": str(item.get("path", "")),
|
|
305
|
+
"line": _safe_int(start.get("line", 1), default=1),
|
|
306
|
+
"message": str(extra.get("message", "Semgrep finding")),
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
return {"status": "ok", "findings": findings, "error": ""}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _normalize_semgrep_severity(raw: str) -> str:
|
|
313
|
+
lowered = raw.lower()
|
|
314
|
+
if lowered in {"error", "critical"}:
|
|
315
|
+
return "high"
|
|
316
|
+
if lowered in {"warning", "warn"}:
|
|
317
|
+
return "medium"
|
|
318
|
+
if lowered in {"info", "note", "low"}:
|
|
319
|
+
return "low"
|
|
320
|
+
return _normalize_severity(lowered)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _scan_semgrep(scope_path: Path) -> list[dict[str, Any]]:
|
|
324
|
+
result = run_semgrep_scan(str(scope_path))
|
|
325
|
+
if result.get("status") != "ok":
|
|
326
|
+
return []
|
|
327
|
+
|
|
328
|
+
findings: list[dict[str, Any]] = []
|
|
329
|
+
for item in result.get("findings", []):
|
|
330
|
+
if not isinstance(item, dict):
|
|
331
|
+
continue
|
|
332
|
+
file_path = Path(str(item.get("path", "")))
|
|
333
|
+
findings.append(
|
|
334
|
+
_finding(
|
|
335
|
+
rule_id=str(item.get("rule", "semgrep")),
|
|
336
|
+
source_name="semgrep-ce",
|
|
337
|
+
category="python_ast",
|
|
338
|
+
severity=_normalize_severity(str(item.get("severity", "medium"))),
|
|
339
|
+
path=file_path,
|
|
340
|
+
line=_safe_int(item.get("line", 1), default=1),
|
|
341
|
+
message=str(item.get("message", "Semgrep finding")),
|
|
342
|
+
recommendation="Review Semgrep finding and apply the suggested remediation.",
|
|
343
|
+
snippet="",
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
return findings
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _safe_int(value: Any, *, default: int) -> int:
|
|
350
|
+
try:
|
|
351
|
+
return int(value)
|
|
352
|
+
except (TypeError, ValueError):
|
|
353
|
+
return default
|
|
354
|
+
|
|
355
|
+
|
|
273
356
|
def _scan_secret_patterns(scope_path: Path) -> list[dict[str, Any]]:
|
|
274
357
|
findings: list[dict[str, Any]] = []
|
|
275
358
|
for candidate in _iter_text_candidates(scope_path):
|
|
@@ -488,9 +571,7 @@ def _run_bandit_if_available(scope_path: Path) -> list[dict[str, Any]]:
|
|
|
488
571
|
|
|
489
572
|
|
|
490
573
|
def _command_exists(command: str) -> bool:
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return which(command) is not None
|
|
574
|
+
return shutil.which(command) is not None
|
|
494
575
|
|
|
495
576
|
|
|
496
577
|
def _scan_dependency_health(scope_path: Path, include_live_enrichment: bool) -> list[dict[str, Any]]:
|
|
@@ -530,6 +611,8 @@ def _scan_dependency_health(scope_path: Path, include_live_enrichment: bool) ->
|
|
|
530
611
|
"severity": _normalize_severity(str(vuln.get("severity", "unknown"))),
|
|
531
612
|
"exploitability": _risk_to_exploitability(str(reachability.get("risk_level", ""))),
|
|
532
613
|
"reachability": _normalize_reachability(str(reachability.get("reachability", "unknown"))),
|
|
614
|
+
"kev_listed": reachability.get("kev_listed", False),
|
|
615
|
+
"epss_score": reachability.get("epss_score"),
|
|
533
616
|
"evidence": {
|
|
534
617
|
"package": package_name,
|
|
535
618
|
"version": dependency["version"],
|
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
3
5
|
from typing import Any
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def lock_intent(project_dir: str, intent: dict[str, Any]) -> dict[str, Any]:
|
|
10
|
+
lock_id = str(uuid4())
|
|
11
|
+
lock_dir = Path(project_dir) / ".omg" / "state" / "test-intent-lock"
|
|
12
|
+
lock_dir.mkdir(parents=True, exist_ok=True)
|
|
13
|
+
|
|
14
|
+
lock_path = lock_dir / f"{lock_id}.json"
|
|
15
|
+
payload = {"schema": "TestIntentLock", "lock_id": lock_id, "intent": intent}
|
|
16
|
+
lock_path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
|
|
17
|
+
|
|
18
|
+
return {"lock_id": lock_id, "status": "locked", "path": str(lock_path)}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def verify_intent(project_dir: str, lock_id: str, results: dict[str, Any]) -> dict[str, Any]:
|
|
22
|
+
lock_path = Path(project_dir) / ".omg" / "state" / "test-intent-lock" / f"{lock_id}.json"
|
|
23
|
+
if not lock_path.exists():
|
|
24
|
+
return {"status": "missing_lock", "lock_id": lock_id, "reasons": ["missing lock state"]}
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
payload = json.loads(lock_path.read_text(encoding="utf-8"))
|
|
28
|
+
except (OSError, json.JSONDecodeError):
|
|
29
|
+
return {"status": "missing_lock", "lock_id": lock_id, "reasons": ["missing lock state"]}
|
|
30
|
+
|
|
31
|
+
intent = payload.get("intent") if isinstance(payload, dict) else {}
|
|
32
|
+
intent_tests = _normalize_string_list(intent.get("tests") if isinstance(intent, dict) else None)
|
|
33
|
+
result_tests = _normalize_string_list(results.get("tests"))
|
|
34
|
+
weakened_assertions = results.get("weakened_assertions")
|
|
35
|
+
|
|
36
|
+
reasons: list[str] = []
|
|
37
|
+
if isinstance(weakened_assertions, list) and weakened_assertions:
|
|
38
|
+
reasons.append("weakened_assertions_present")
|
|
39
|
+
|
|
40
|
+
if result_tests != intent_tests:
|
|
41
|
+
reasons.append("tests_mismatch")
|
|
42
|
+
|
|
43
|
+
status = "ok" if not reasons else "fail"
|
|
44
|
+
return {"status": status, "lock_id": lock_id, "reasons": reasons}
|
|
4
45
|
|
|
5
46
|
|
|
6
47
|
def evaluate_test_delta(delta: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -89,3 +130,9 @@ def _normalize_tests(value: Any) -> list[dict[str, Any]]:
|
|
|
89
130
|
}
|
|
90
131
|
)
|
|
91
132
|
return tests
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _normalize_string_list(value: Any) -> list[str]:
|
|
136
|
+
if not isinstance(value, list):
|
|
137
|
+
return []
|
|
138
|
+
return [str(item).strip() for item in value if str(item).strip()]
|
|
@@ -40,6 +40,7 @@ def record_trace(
|
|
|
40
40
|
trace_type: str,
|
|
41
41
|
route: str,
|
|
42
42
|
status: str,
|
|
43
|
+
schema_version: int | None = None,
|
|
43
44
|
plan: dict[str, Any] | None = None,
|
|
44
45
|
patch: dict[str, Any] | None = None,
|
|
45
46
|
verify: dict[str, Any] | None = None,
|
|
@@ -49,7 +50,7 @@ def record_trace(
|
|
|
49
50
|
) -> dict[str, Any]:
|
|
50
51
|
trace_id = f"trace-{uuid4().hex}"
|
|
51
52
|
timestamp = _now()
|
|
52
|
-
record = {
|
|
53
|
+
record: dict[str, Any] = {
|
|
53
54
|
"schema": "TracebankRecord",
|
|
54
55
|
"trace_id": trace_id,
|
|
55
56
|
"timestamp": timestamp,
|
|
@@ -66,6 +67,10 @@ def record_trace(
|
|
|
66
67
|
"rejections": rejections or [],
|
|
67
68
|
"metadata": metadata or {},
|
|
68
69
|
}
|
|
70
|
+
if schema_version is not None:
|
|
71
|
+
record["schema_version"] = schema_version
|
|
72
|
+
elif isinstance(metadata, dict) and metadata.get("schema_version") is not None:
|
|
73
|
+
record["schema_version"] = metadata.get("schema_version")
|
|
69
74
|
|
|
70
75
|
path = Path(project_dir) / TRACEBANK_REL_PATH
|
|
71
76
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -73,11 +78,34 @@ def record_trace(
|
|
|
73
78
|
_ = handle.write(json.dumps(record, ensure_ascii=True) + "\n")
|
|
74
79
|
|
|
75
80
|
record["path"] = TRACEBANK_REL_PATH.as_posix()
|
|
81
|
+
|
|
82
|
+
verification_status = (metadata or {}).get("verification_status")
|
|
83
|
+
if verification_status:
|
|
84
|
+
try:
|
|
85
|
+
from runtime.background_verification import publish_verification_state
|
|
86
|
+
|
|
87
|
+
publish_verification_state(
|
|
88
|
+
project_dir=project_dir,
|
|
89
|
+
run_id=trace_id,
|
|
90
|
+
status=str(verification_status),
|
|
91
|
+
blockers=(metadata or {}).get("verification_blockers", []),
|
|
92
|
+
evidence_links=(metadata or {}).get("verification_evidence_links", []),
|
|
93
|
+
progress=(metadata or {}).get("verification_progress", {}),
|
|
94
|
+
)
|
|
95
|
+
except Exception:
|
|
96
|
+
pass
|
|
97
|
+
|
|
76
98
|
return record
|
|
77
99
|
|
|
78
100
|
|
|
79
|
-
def link_evidence(
|
|
80
|
-
|
|
101
|
+
def link_evidence(
|
|
102
|
+
project_dir: str,
|
|
103
|
+
*,
|
|
104
|
+
trace_id: str,
|
|
105
|
+
evidence_path: str,
|
|
106
|
+
schema_version: int | None = None,
|
|
107
|
+
) -> dict[str, Any]:
|
|
108
|
+
link: dict[str, Any] = {
|
|
81
109
|
"schema": "TraceEvidenceLink",
|
|
82
110
|
"trace_id": trace_id,
|
|
83
111
|
"evidence_path": evidence_path,
|
|
@@ -85,6 +113,8 @@ def link_evidence(project_dir: str, *, trace_id: str, evidence_path: str) -> dic
|
|
|
85
113
|
"executor": _executor(),
|
|
86
114
|
"environment": _environment(),
|
|
87
115
|
}
|
|
116
|
+
if schema_version is not None:
|
|
117
|
+
link["schema_version"] = schema_version
|
|
88
118
|
|
|
89
119
|
path = Path(project_dir) / TRACEBANK_EVIDENCE_LINKS_REL_PATH
|
|
90
120
|
path.parent.mkdir(parents=True, exist_ok=True)
|