pythia-plsql 0.14.0__tar.gz → 0.14.1__tar.gz
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.
- {pythia_plsql-0.14.0/scripts/pythia_plsql.egg-info → pythia_plsql-0.14.1}/PKG-INFO +1 -1
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/pyproject.toml +1 -1
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/scripts/pythia.py +34 -50
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1/scripts/pythia_plsql.egg-info}/PKG-INFO +1 -1
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/tests/test_mcp.py +20 -33
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/LICENSE +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/README.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/compile-errors.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/current-schema.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/dependencies.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/impact.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/invalid-objects.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/name-occupants.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/object-names.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/object-source.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/plscope-enabled.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/plscope-statements.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/plscope-usages.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/session-privileges.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/similar-candidates.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/queries/source.sql +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/scripts/pythia_plsql.egg-info/SOURCES.txt +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/setup.cfg +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-apply/SKILL.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-comment/SKILL.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-conventions/SKILL.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-explore/SKILL.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-explore/reference/data-dictionary.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-impact/SKILL.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-review/SKILL.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-review/reference/antipatterns.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-setup/SKILL.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-skill-author/SKILL.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-spec/SKILL.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-write/SKILL.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-write/reference/patterns.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/using-pythia/SKILL.md +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/tests/test_install.py +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/tests/test_phase1.py +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/tests/test_phase2.py +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/tests/test_phase3.py +0 -0
- {pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/tests/test_phase5.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythia-plsql
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.1
|
|
4
4
|
Summary: PL/SQL development for AI agents on Oracle Database - expert data-dictionary queries, impact analysis, and a snapshot-verified write path with honest rollback.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/thaildhe172591/pythia
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pythia-plsql"
|
|
7
|
-
version = "0.14.
|
|
7
|
+
version = "0.14.1"
|
|
8
8
|
description = "PL/SQL development for AI agents on Oracle Database - expert data-dictionary queries, impact analysis, and a snapshot-verified write path with honest rollback."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -3484,9 +3484,11 @@ def mcp_approve_decision(root, token, elicit):
|
|
|
3484
3484
|
prune_expired_grants(root)
|
|
3485
3485
|
mint_grant(root, token, meta.get("connection") or "",
|
|
3486
3486
|
revalidate=fingerprint_text(meta.get("row_set")), approver="mcp")
|
|
3487
|
-
return _mcp_text(f"Approved — grant minted for {token}
|
|
3488
|
-
f"
|
|
3489
|
-
|
|
3487
|
+
return _mcp_text(f"Approved — grant minted for {token} (single use, "
|
|
3488
|
+
f"{GRANT_TTL_MINUTES} min). Now run apply --confirm on the "
|
|
3489
|
+
"file you previewed YOURSELF — do not hand the developer a "
|
|
3490
|
+
f"command to type: {invocation()} apply <file> --confirm "
|
|
3491
|
+
f"{token}")
|
|
3490
3492
|
|
|
3491
3493
|
|
|
3492
3494
|
def mcp_serve(reader, writer, root):
|
|
@@ -3558,33 +3560,30 @@ def cmd_mcp(conn, schema, ns):
|
|
|
3558
3560
|
# table header inside a string/comment is not worth a parser here.
|
|
3559
3561
|
CODEX_MCP_BEGIN = "# pythia:begin (managed by `pythia install`)"
|
|
3560
3562
|
CODEX_MCP_END = "# pythia:end"
|
|
3563
|
+
# Only the server roster — nothing else. An earlier attempt also wrote an
|
|
3564
|
+
# [approval_policy.granular] table, but that table requires several fields
|
|
3565
|
+
# (sandbox_approval, rules, ...) and an incomplete one broke Codex v0.154 at
|
|
3566
|
+
# config-load time. Elicitations surface under the default interactive
|
|
3567
|
+
# approval policy without any of that, and the developer's approval policy is
|
|
3568
|
+
# theirs, not ours to set.
|
|
3561
3569
|
CODEX_MCP_BLOCK = (f"{CODEX_MCP_BEGIN}\n"
|
|
3562
3570
|
"[mcp_servers.pythia]\n"
|
|
3563
3571
|
'command = "python"\n'
|
|
3564
|
-
'args = ["-m", "pythia", "mcp"]\n
|
|
3565
|
-
"[approval_policy.granular]\n"
|
|
3566
|
-
"mcp_elicitations = true\n"
|
|
3572
|
+
'args = ["-m", "pythia", "mcp"]\n'
|
|
3567
3573
|
f"{CODEX_MCP_END}")
|
|
3568
|
-
# requirements.toml is a separate admin-enforced file; forbidding these two is
|
|
3569
|
-
# what closes the danger-full-access elicitation auto-approve hole.
|
|
3570
|
-
CODEX_REQUIREMENTS = (f"{CODEX_MCP_BEGIN}\n"
|
|
3571
|
-
"# pythia needs elicitations to reach a human, so the two\n"
|
|
3572
|
-
"# modes that would auto-answer or skip them are disallowed.\n"
|
|
3573
|
-
'allowed_approval_policies = ["untrusted", "on-request", "on-failure"]\n'
|
|
3574
|
-
'allowed_sandbox_modes = ["read-only", "workspace-write"]\n'
|
|
3575
|
-
f"{CODEX_MCP_END}")
|
|
3576
3574
|
|
|
3577
3575
|
|
|
3578
3576
|
def merge_codex_mcp_config(path):
|
|
3579
|
-
"""Append the MCP
|
|
3580
|
-
(path, action): 'created' | 'appended' | 'present' | 'conflict'.
|
|
3581
|
-
(writes nothing) if
|
|
3577
|
+
"""Append the MCP server registration to <project>/.codex/config.toml.
|
|
3578
|
+
Returns (path, action): 'created' | 'appended' | 'present' | 'conflict'.
|
|
3579
|
+
Refuses (writes nothing) if [mcp_servers.pythia] already exists without our
|
|
3580
|
+
marker — the stdlib cannot safely merge TOML, so a collision is reported,
|
|
3581
|
+
never overwritten."""
|
|
3582
3582
|
path = pathlib.Path(path)
|
|
3583
3583
|
old = path.read_text(encoding="utf-8") if path.is_file() else None
|
|
3584
3584
|
if old is not None and CODEX_MCP_BEGIN in old:
|
|
3585
3585
|
return path, "present"
|
|
3586
|
-
if old is not None and
|
|
3587
|
-
or "[approval_policy.granular]" in old):
|
|
3586
|
+
if old is not None and "[mcp_servers.pythia]" in old:
|
|
3588
3587
|
return path, "conflict"
|
|
3589
3588
|
if old is None:
|
|
3590
3589
|
new, action = CODEX_MCP_BLOCK + "\n", "created"
|
|
@@ -3596,21 +3595,6 @@ def merge_codex_mcp_config(path):
|
|
|
3596
3595
|
return path, action
|
|
3597
3596
|
|
|
3598
3597
|
|
|
3599
|
-
def merge_codex_requirements(path):
|
|
3600
|
-
"""Write <project>/.codex/requirements.toml forbidding danger-full-access
|
|
3601
|
-
and never-approval. Returns (path, action): 'created' | 'present' |
|
|
3602
|
-
'conflict'. An existing file without our marker is left untouched."""
|
|
3603
|
-
path = pathlib.Path(path)
|
|
3604
|
-
old = path.read_text(encoding="utf-8") if path.is_file() else None
|
|
3605
|
-
if old is not None and CODEX_MCP_BEGIN in old:
|
|
3606
|
-
return path, "present"
|
|
3607
|
-
if old is not None:
|
|
3608
|
-
return path, "conflict"
|
|
3609
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
3610
|
-
path.write_text(CODEX_REQUIREMENTS + "\n", encoding="utf-8")
|
|
3611
|
-
return path, "created"
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
3598
|
def install_claude_hooks(base_dir, events=None):
|
|
3615
3599
|
"""Merge the two hooks and the deny rule into <base>/.claude/settings.json
|
|
3616
3600
|
— the project's, or the home directory's for -g. The file is Claude
|
|
@@ -3693,28 +3677,28 @@ def report_codex_hooks(project_root, ns):
|
|
|
3693
3677
|
|
|
3694
3678
|
|
|
3695
3679
|
def report_codex_mcp(project_root, ns):
|
|
3696
|
-
"""Project scope: register the write-free MCP approver in Codex's config
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3680
|
+
"""Project scope: register the write-free MCP approver in Codex's config.
|
|
3681
|
+
--no-hooks skips it, as with the other Codex wiring. Needs Codex >= v0.120
|
|
3682
|
+
for elicitation; older Codex ignores the server and the console approve
|
|
3683
|
+
path still works."""
|
|
3700
3684
|
if getattr(ns, "no_hooks", False):
|
|
3701
3685
|
return
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
req_path, req = merge_codex_requirements(base / "requirements.toml")
|
|
3686
|
+
cfg_path, cfg = merge_codex_mcp_config(pathlib.Path(project_root)
|
|
3687
|
+
/ ".codex" / "config.toml")
|
|
3705
3688
|
if cfg in ("created", "appended"):
|
|
3706
3689
|
print(f"\nRegistered the pythia MCP approver in {cfg_path} "
|
|
3707
3690
|
"(Codex >= v0.120; run /mcp to trust it).")
|
|
3708
3691
|
elif cfg == "conflict":
|
|
3709
|
-
print(f"\n! {cfg_path} already defines [mcp_servers.pythia]
|
|
3710
|
-
"
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3692
|
+
print(f"\n! {cfg_path} already defines [mcp_servers.pythia]; add this "
|
|
3693
|
+
"yourself:\n" + CODEX_MCP_BLOCK)
|
|
3694
|
+
else:
|
|
3695
|
+
print(f"\npythia MCP approver already registered in {cfg_path}.")
|
|
3696
|
+
# The one bypass of the elicitation is Codex auto-approving it under the
|
|
3697
|
+
# danger-full-access sandbox. pythia does not write requirements.toml to
|
|
3698
|
+
# forbid that — it is enterprise-managed config, and the schema is
|
|
3699
|
+
# version-fragile — so it warns instead.
|
|
3700
|
+
print("Do not run Codex under `danger-full-access` with pythia: that "
|
|
3701
|
+
"sandbox auto-approves elicitations, bypassing your Approve.")
|
|
3718
3702
|
|
|
3719
3703
|
|
|
3720
3704
|
def cmd_install(conn, schema, ns):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythia-plsql
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.1
|
|
4
4
|
Summary: PL/SQL development for AI agents on Oracle Database - expert data-dictionary queries, impact analysis, and a snapshot-verified write path with honest rollback.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/thaildhe172591/pythia
|
|
@@ -79,7 +79,11 @@ def test_mint_only_on_accept_with_approve():
|
|
|
79
79
|
|
|
80
80
|
res = pythia.mcp_approve_decision(td, token, elicit)
|
|
81
81
|
assert res["isError"] is False
|
|
82
|
-
|
|
82
|
+
text = res["content"][0]["text"]
|
|
83
|
+
assert token in text
|
|
84
|
+
# the message tells the AGENT to continue itself, not the developer to
|
|
85
|
+
# type a command — 0.14.1 fix
|
|
86
|
+
assert "yourself" in text.lower()
|
|
83
87
|
assert "PKG_ORDER" in seen["message"] # the card was shown
|
|
84
88
|
assert seen["schema"]["properties"]["decision"]["enum"] == ["Approve", "Reject"]
|
|
85
89
|
g = pythia.read_grant(td, token)
|
|
@@ -200,7 +204,10 @@ def test_merge_codex_mcp_config_appends_once_when_clean():
|
|
|
200
204
|
body = path.read_text(encoding="utf-8")
|
|
201
205
|
assert "[mcp_servers.pythia]" in body
|
|
202
206
|
assert 'args = ["-m", "pythia", "mcp"]' in body
|
|
203
|
-
|
|
207
|
+
# 0.14.1: we do NOT touch approval_policy — an incomplete granular table
|
|
208
|
+
# broke Codex v0.154 config loading, and elicitations work under the
|
|
209
|
+
# default interactive policy anyway.
|
|
210
|
+
assert "approval_policy" not in body
|
|
204
211
|
assert pythia.merge_codex_mcp_config(path)[1] == "present" # idempotent
|
|
205
212
|
|
|
206
213
|
|
|
@@ -216,43 +223,20 @@ def test_merge_codex_mcp_config_preserves_other_content():
|
|
|
216
223
|
assert "[mcp_servers.pythia]" in body
|
|
217
224
|
|
|
218
225
|
|
|
219
|
-
def
|
|
226
|
+
def test_merge_codex_mcp_config_refuses_on_server_collision():
|
|
220
227
|
with tempfile.TemporaryDirectory() as td:
|
|
221
228
|
path = pathlib.Path(td) / ".codex" / "config.toml"
|
|
222
229
|
path.parent.mkdir(parents=True)
|
|
223
|
-
original =
|
|
230
|
+
original = '[mcp_servers.pythia]\ncommand = "other"\n'
|
|
224
231
|
path.write_text(original, encoding="utf-8")
|
|
225
232
|
p, action = pythia.merge_codex_mcp_config(path)
|
|
226
233
|
assert action == "conflict"
|
|
227
234
|
assert path.read_text(encoding="utf-8") == original # untouched
|
|
228
235
|
|
|
229
236
|
|
|
230
|
-
def test_merge_codex_requirements_creates_and_is_idempotent():
|
|
231
|
-
with tempfile.TemporaryDirectory() as td:
|
|
232
|
-
path = pathlib.Path(td) / ".codex" / "requirements.toml"
|
|
233
|
-
p, action = pythia.merge_codex_requirements(path)
|
|
234
|
-
assert action == "created"
|
|
235
|
-
body = path.read_text(encoding="utf-8")
|
|
236
|
-
assert "allowed_approval_policies" in body and "allowed_sandbox_modes" in body
|
|
237
|
-
assert "danger-full-access" not in body # the forbidden mode is absent
|
|
238
|
-
assert '"never"' not in body # the forbidden policy is absent
|
|
239
|
-
assert pythia.merge_codex_requirements(path)[1] == "present"
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def test_merge_codex_requirements_refuses_an_existing_file():
|
|
243
|
-
with tempfile.TemporaryDirectory() as td:
|
|
244
|
-
path = pathlib.Path(td) / ".codex" / "requirements.toml"
|
|
245
|
-
path.parent.mkdir(parents=True)
|
|
246
|
-
original = "# org policy\nallowed_sandbox_modes = []\n"
|
|
247
|
-
path.write_text(original, encoding="utf-8")
|
|
248
|
-
p, action = pythia.merge_codex_requirements(path)
|
|
249
|
-
assert action == "conflict"
|
|
250
|
-
assert path.read_text(encoding="utf-8") == original # untouched
|
|
251
|
-
|
|
252
|
-
|
|
253
237
|
# --- Task 5: wire the MCP install into cmd_install (project scope) ----------
|
|
254
238
|
|
|
255
|
-
def
|
|
239
|
+
def test_cmd_install_registers_the_mcp_server_and_warns_off_danger():
|
|
256
240
|
import argparse
|
|
257
241
|
import contextlib
|
|
258
242
|
old = os.environ.get("PATH")
|
|
@@ -261,13 +245,16 @@ def test_cmd_install_registers_the_mcp_server_and_pins_the_sandbox():
|
|
|
261
245
|
with tempfile.TemporaryDirectory() as td:
|
|
262
246
|
ns = argparse.Namespace(project_root=td, glob=False, source=None,
|
|
263
247
|
color=False, json=False, no_hooks=False)
|
|
264
|
-
|
|
248
|
+
buf = io.StringIO()
|
|
249
|
+
with contextlib.redirect_stdout(buf):
|
|
265
250
|
pythia.cmd_install(None, None, ns)
|
|
266
251
|
cfg = pathlib.Path(td) / ".codex" / "config.toml"
|
|
267
|
-
|
|
268
|
-
assert "[mcp_servers.pythia]" in
|
|
269
|
-
assert "
|
|
270
|
-
|
|
252
|
+
body = cfg.read_text(encoding="utf-8")
|
|
253
|
+
assert "[mcp_servers.pythia]" in body
|
|
254
|
+
assert "approval_policy" not in body # 0.14.1: never touched
|
|
255
|
+
# requirements.toml is no longer written; the pin became a warning
|
|
256
|
+
assert not (pathlib.Path(td) / ".codex" / "requirements.toml").exists()
|
|
257
|
+
assert "danger-full-access" in buf.getvalue() # the warning
|
|
271
258
|
finally:
|
|
272
259
|
if old is not None:
|
|
273
260
|
os.environ["PATH"] = old
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/scripts/pythia_plsql.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythia_plsql-0.14.0 → pythia_plsql-0.14.1}/skills/pythia-explore/reference/data-dictionary.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|