pythia-plsql 0.14.0__tar.gz → 0.14.2__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.
Files changed (46) hide show
  1. {pythia_plsql-0.14.0/scripts/pythia_plsql.egg-info → pythia_plsql-0.14.2}/PKG-INFO +1 -1
  2. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/pyproject.toml +1 -1
  3. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/scripts/pythia.py +47 -55
  4. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2/scripts/pythia_plsql.egg-info}/PKG-INFO +1 -1
  5. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-apply/SKILL.md +1 -1
  6. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/tests/test_mcp.py +20 -33
  7. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/LICENSE +0 -0
  8. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/README.md +0 -0
  9. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/compile-errors.sql +0 -0
  10. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/current-schema.sql +0 -0
  11. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/dependencies.sql +0 -0
  12. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/impact.sql +0 -0
  13. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/invalid-objects.sql +0 -0
  14. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/name-occupants.sql +0 -0
  15. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/object-names.sql +0 -0
  16. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/object-source.sql +0 -0
  17. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/plscope-enabled.sql +0 -0
  18. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/plscope-statements.sql +0 -0
  19. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/plscope-usages.sql +0 -0
  20. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/session-privileges.sql +0 -0
  21. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/similar-candidates.sql +0 -0
  22. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/queries/source.sql +0 -0
  23. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/scripts/pythia_plsql.egg-info/SOURCES.txt +0 -0
  24. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
  25. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
  26. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
  27. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
  28. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/setup.cfg +0 -0
  29. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-comment/SKILL.md +0 -0
  30. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-conventions/SKILL.md +0 -0
  31. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-explore/SKILL.md +0 -0
  32. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-explore/reference/data-dictionary.md +0 -0
  33. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-impact/SKILL.md +0 -0
  34. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-review/SKILL.md +0 -0
  35. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-review/reference/antipatterns.md +0 -0
  36. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-setup/SKILL.md +0 -0
  37. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-skill-author/SKILL.md +0 -0
  38. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-spec/SKILL.md +0 -0
  39. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-write/SKILL.md +0 -0
  40. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/pythia-write/reference/patterns.md +0 -0
  41. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/skills/using-pythia/SKILL.md +0 -0
  42. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/tests/test_install.py +0 -0
  43. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/tests/test_phase1.py +0 -0
  44. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/tests/test_phase2.py +0 -0
  45. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/tests/test_phase3.py +0 -0
  46. {pythia_plsql-0.14.0 → pythia_plsql-0.14.2}/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.0
3
+ Version: 0.14.2
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.0"
7
+ version = "0.14.2"
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" }
@@ -2286,8 +2286,11 @@ def run_apply(conn, schema, ns, file_text, origin=None):
2286
2286
  "dim", en))
2287
2287
  if not confirmed:
2288
2288
  print("\n To apply — the developer approves, then the agent confirms:")
2289
- print(" in chat: the agent asks (AskUserQuestion) with the text of "
2289
+ print(" in chat (Claude): the agent asks (AskUserQuestion) with the text of "
2290
2290
  + paint(f"{invocation()} approve --card {token}", "cyan", en))
2291
+ print(" on Codex: the agent calls the "
2292
+ + paint("pythia_approve", "cyan", en)
2293
+ + f" MCP tool with token {token}")
2291
2294
  print(" or in a terminal: "
2292
2295
  + paint(f"{invocation()} approve {token}", "cyan", en))
2293
2296
  print(" then the agent: "
@@ -2826,15 +2829,20 @@ def agents_md_block():
2826
2829
  """The pythia section for AGENTS.md — Codex's always-loaded instruction
2827
2830
  file. Body is BRIEF_GUIDE verbatim (the same constant `guide --brief`
2828
2831
  prints and the Claude SessionStart hook runs, so the two cannot drift),
2829
- plus the one line Codex needs that Claude gets from its hook: where the
2832
+ plus the lines Codex needs that Claude gets from its hook: where the
2830
2833
  approval door is, since Codex has no chat-mint. Marker-wrapped so a re-run
2831
2834
  replaces it in place and the developer's own text is untouched."""
2832
2835
  return (f"{AGENTS_BEGIN}\n"
2833
2836
  "## pythia — developing PL/SQL on Oracle\n\n"
2834
2837
  f"{BRIEF_GUIDE.rstrip()}\n\n"
2835
- "On Codex there is no chat approval hook: the developer approves a "
2836
- "preview in this terminal with `pythia approve <token>`, then the "
2837
- "agent runs `pythia apply <file> --confirm <token>`.\n"
2838
+ "APPROVAL ON CODEX: your approval door is the `pythia_approve` MCP "
2839
+ "tool, not AskUserQuestion (Codex has none). After `pythia apply "
2840
+ "<file>` prints a token, call the `pythia_approve` tool with that "
2841
+ "token — the developer answers a select prompt, and on Approve the "
2842
+ "grant is minted. Then YOU run `pythia apply <file> --confirm "
2843
+ "<token>` yourself; do not stop at the preview or hand the "
2844
+ "developer a command to type. If the tool is not available "
2845
+ "(older Codex), relay `pythia approve <token>` for their terminal.\n"
2838
2846
  f"{AGENTS_END}")
2839
2847
 
2840
2848
 
@@ -3484,9 +3492,11 @@ def mcp_approve_decision(root, token, elicit):
3484
3492
  prune_expired_grants(root)
3485
3493
  mint_grant(root, token, meta.get("connection") or "",
3486
3494
  revalidate=fingerprint_text(meta.get("row_set")), approver="mcp")
3487
- return _mcp_text(f"Approved — grant minted for {token}, single use, expires "
3488
- f"in {GRANT_TTL_MINUTES} minutes. Now run: "
3489
- f"{invocation()} apply <file> --confirm {token}")
3495
+ return _mcp_text(f"Approved — grant minted for {token} (single use, "
3496
+ f"{GRANT_TTL_MINUTES} min). Now run apply --confirm on the "
3497
+ "file you previewed YOURSELF — do not hand the developer a "
3498
+ f"command to type: {invocation()} apply <file> --confirm "
3499
+ f"{token}")
3490
3500
 
3491
3501
 
3492
3502
  def mcp_serve(reader, writer, root):
@@ -3558,33 +3568,30 @@ def cmd_mcp(conn, schema, ns):
3558
3568
  # table header inside a string/comment is not worth a parser here.
3559
3569
  CODEX_MCP_BEGIN = "# pythia:begin (managed by `pythia install`)"
3560
3570
  CODEX_MCP_END = "# pythia:end"
3571
+ # Only the server roster — nothing else. An earlier attempt also wrote an
3572
+ # [approval_policy.granular] table, but that table requires several fields
3573
+ # (sandbox_approval, rules, ...) and an incomplete one broke Codex v0.154 at
3574
+ # config-load time. Elicitations surface under the default interactive
3575
+ # approval policy without any of that, and the developer's approval policy is
3576
+ # theirs, not ours to set.
3561
3577
  CODEX_MCP_BLOCK = (f"{CODEX_MCP_BEGIN}\n"
3562
3578
  "[mcp_servers.pythia]\n"
3563
3579
  'command = "python"\n'
3564
- 'args = ["-m", "pythia", "mcp"]\n\n'
3565
- "[approval_policy.granular]\n"
3566
- "mcp_elicitations = true\n"
3580
+ 'args = ["-m", "pythia", "mcp"]\n'
3567
3581
  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
3582
 
3577
3583
 
3578
3584
  def merge_codex_mcp_config(path):
3579
- """Append the MCP block to <project>/.codex/config.toml. Returns
3580
- (path, action): 'created' | 'appended' | 'present' | 'conflict'. Refuses
3581
- (writes nothing) if either table already exists without our marker."""
3585
+ """Append the MCP server registration to <project>/.codex/config.toml.
3586
+ Returns (path, action): 'created' | 'appended' | 'present' | 'conflict'.
3587
+ Refuses (writes nothing) if [mcp_servers.pythia] already exists without our
3588
+ marker — the stdlib cannot safely merge TOML, so a collision is reported,
3589
+ never overwritten."""
3582
3590
  path = pathlib.Path(path)
3583
3591
  old = path.read_text(encoding="utf-8") if path.is_file() else None
3584
3592
  if old is not None and CODEX_MCP_BEGIN in old:
3585
3593
  return path, "present"
3586
- if old is not None and ("[mcp_servers.pythia]" in old
3587
- or "[approval_policy.granular]" in old):
3594
+ if old is not None and "[mcp_servers.pythia]" in old:
3588
3595
  return path, "conflict"
3589
3596
  if old is None:
3590
3597
  new, action = CODEX_MCP_BLOCK + "\n", "created"
@@ -3596,21 +3603,6 @@ def merge_codex_mcp_config(path):
3596
3603
  return path, action
3597
3604
 
3598
3605
 
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
3606
  def install_claude_hooks(base_dir, events=None):
3615
3607
  """Merge the two hooks and the deny rule into <base>/.claude/settings.json
3616
3608
  — the project's, or the home directory's for -g. The file is Claude
@@ -3693,28 +3685,28 @@ def report_codex_hooks(project_root, ns):
3693
3685
 
3694
3686
 
3695
3687
  def report_codex_mcp(project_root, ns):
3696
- """Project scope: register the write-free MCP approver in Codex's config and
3697
- pin the sandbox so elicitations always reach a human. --no-hooks skips it,
3698
- as with the other Codex wiring. Needs Codex >= v0.120; older Codex ignores
3699
- the server and the console approve path still works."""
3688
+ """Project scope: register the write-free MCP approver in Codex's config.
3689
+ --no-hooks skips it, as with the other Codex wiring. Needs Codex >= v0.120
3690
+ for elicitation; older Codex ignores the server and the console approve
3691
+ path still works."""
3700
3692
  if getattr(ns, "no_hooks", False):
3701
3693
  return
3702
- base = pathlib.Path(project_root) / ".codex"
3703
- cfg_path, cfg = merge_codex_mcp_config(base / "config.toml")
3704
- req_path, req = merge_codex_requirements(base / "requirements.toml")
3694
+ cfg_path, cfg = merge_codex_mcp_config(pathlib.Path(project_root)
3695
+ / ".codex" / "config.toml")
3705
3696
  if cfg in ("created", "appended"):
3706
3697
  print(f"\nRegistered the pythia MCP approver in {cfg_path} "
3707
3698
  "(Codex >= v0.120; run /mcp to trust it).")
3708
3699
  elif cfg == "conflict":
3709
- print(f"\n! {cfg_path} already defines [mcp_servers.pythia] or "
3710
- "[approval_policy.granular]; add this yourself:\n"
3711
- + CODEX_MCP_BLOCK)
3712
- if req == "created":
3713
- print(f"Pinned the sandbox in {req_path} (no danger-full-access, so "
3714
- "elicitations always ask a human).")
3715
- elif req == "conflict":
3716
- print(f"\n! {req_path} already exists; add these constraints to it:\n"
3717
- + CODEX_REQUIREMENTS)
3700
+ print(f"\n! {cfg_path} already defines [mcp_servers.pythia]; add this "
3701
+ "yourself:\n" + CODEX_MCP_BLOCK)
3702
+ else:
3703
+ print(f"\npythia MCP approver already registered in {cfg_path}.")
3704
+ # The one bypass of the elicitation is Codex auto-approving it under the
3705
+ # danger-full-access sandbox. pythia does not write requirements.toml to
3706
+ # forbid that — it is enterprise-managed config, and the schema is
3707
+ # version-fragile — so it warns instead.
3708
+ print("Do not run Codex under `danger-full-access` with pythia: that "
3709
+ "sandbox auto-approves elicitations, bypassing your Approve.")
3718
3710
 
3719
3711
 
3720
3712
  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.0
3
+ Version: 0.14.2
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
@@ -60,7 +60,7 @@ confirms a number you already knew.
60
60
  in the option descriptions; the hook does not read those. Header `pythia`;
61
61
  options exactly `Approve` / `Reject`, no "(Recommended)"; up to four tokens
62
62
  per call. Anything but `Approve` is not approval: stop and ask what should
63
- change. File changed → step 1. No hook (Codex, or none)? Relay `pythia approve <token>` in the terminal — Codex has no chat-mint.
63
+ change. File changed → step 1. On Codex: call the `pythia_approve` MCP tool with the token — its select prompt is your door — then run `--confirm` yourself. No tool/hook? Relay `pythia approve <token>` for the terminal.
64
64
  4. **Apply** by running the exact `then the agent:` line pythia printed. Two
65
65
  refusals are normal here, and neither is a malfunction:
66
66
  - *"no developer approval is on file"* — the answer was not `Approve`, or
@@ -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
- assert token in res["content"][0]["text"]
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
- assert "mcp_elicitations = true" in body
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 test_merge_codex_mcp_config_refuses_on_table_collision():
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 = "[approval_policy.granular]\nmcp_elicitations = false\n"
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 test_cmd_install_registers_the_mcp_server_and_pins_the_sandbox():
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
- with contextlib.redirect_stdout(io.StringIO()):
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
- req = pathlib.Path(td) / ".codex" / "requirements.toml"
268
- assert "[mcp_servers.pythia]" in cfg.read_text(encoding="utf-8")
269
- assert "mcp_elicitations = true" in cfg.read_text(encoding="utf-8")
270
- assert "allowed_sandbox_modes" in req.read_text(encoding="utf-8")
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