pythia-plsql 0.4.0__tar.gz → 0.4.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 (39) hide show
  1. {pythia_plsql-0.4.0/scripts/pythia_plsql.egg-info → pythia_plsql-0.4.2}/PKG-INFO +1 -1
  2. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/pyproject.toml +1 -1
  3. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/scripts/pythia.py +24 -8
  4. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2/scripts/pythia_plsql.egg-info}/PKG-INFO +1 -1
  5. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/skills/pythia-apply/SKILL.md +12 -1
  6. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/tests/test_phase3.py +26 -29
  7. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/LICENSE +0 -0
  8. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/README.md +0 -0
  9. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/queries/compile-errors.sql +0 -0
  10. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/queries/dependencies.sql +0 -0
  11. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/queries/impact.sql +0 -0
  12. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/queries/invalid-objects.sql +0 -0
  13. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/queries/name-occupants.sql +0 -0
  14. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/queries/object-source.sql +0 -0
  15. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/queries/plscope-enabled.sql +0 -0
  16. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/queries/plscope-statements.sql +0 -0
  17. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/queries/plscope-usages.sql +0 -0
  18. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/queries/session-privileges.sql +0 -0
  19. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/queries/similar-candidates.sql +0 -0
  20. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/queries/source.sql +0 -0
  21. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/scripts/pythia_plsql.egg-info/SOURCES.txt +0 -0
  22. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
  23. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
  24. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
  25. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
  26. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/setup.cfg +0 -0
  27. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/skills/pythia-explore/SKILL.md +0 -0
  28. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/skills/pythia-explore/reference/data-dictionary.md +0 -0
  29. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/skills/pythia-impact/SKILL.md +0 -0
  30. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/skills/pythia-review/SKILL.md +0 -0
  31. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/skills/pythia-review/reference/antipatterns.md +0 -0
  32. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/skills/pythia-setup/SKILL.md +0 -0
  33. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/skills/pythia-skill-author/SKILL.md +0 -0
  34. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/skills/pythia-write/SKILL.md +0 -0
  35. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/skills/pythia-write/reference/patterns.md +0 -0
  36. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/tests/test_install.py +0 -0
  37. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/tests/test_phase1.py +0 -0
  38. {pythia_plsql-0.4.0 → pythia_plsql-0.4.2}/tests/test_phase2.py +0 -0
  39. {pythia_plsql-0.4.0 → pythia_plsql-0.4.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.4.0
3
+ Version: 0.4.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.4.0"
7
+ version = "0.4.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" }
@@ -1337,6 +1337,11 @@ def run_apply(conn, schema, ns, file_text, origin=None):
1337
1337
  print(f" {paint_diff_line(ln, en)}")
1338
1338
  print(paint(f"\n Snapshot saved: {journal_root(ns.project_root) / entry}",
1339
1339
  "dim", en))
1340
+ if not confirmed:
1341
+ print(paint(" Rollback file for the current database version — "
1342
+ "use it if this is\n run by hand instead: "
1343
+ f"{journal_root(ns.project_root) / entry / 'restore.sql'}",
1344
+ "dim", en))
1340
1345
  if not confirmed:
1341
1346
  print(f"\n To apply:\n "
1342
1347
  + paint(f"{invocation()} apply {ns.file} --confirm {token}",
@@ -1536,18 +1541,29 @@ def cmd_journal(conn, schema, ns):
1536
1541
  if ns.action == "prune":
1537
1542
  import shutil
1538
1543
  removed = 0
1539
- for eid in list_journal_entries(root):
1540
- meta = read_journal_entry(root, eid)["meta"]
1544
+ # A preview wrote nothing to the database, but its restore.sql holds
1545
+ # the version that was live at the time — the only rollback there is
1546
+ # for a change the developer then ran by hand. So drop a preview only
1547
+ # when its rollback is byte-identical to one a newer entry already
1548
+ # keeps: redundant captures go, unique ones never do.
1549
+ kept = {}
1550
+ for eid in list_journal_entries(root): # newest first
1551
+ e = read_journal_entry(root, eid)
1552
+ meta, obj = e["meta"], str(e["meta"].get("object", ""))
1541
1553
  if meta.get("applied") or meta.get("snapshot"):
1542
- continue # applied = a real write; snapshot = the only
1543
- # undo a hand-edit outside pythia will ever have
1544
- shutil.rmtree(journal_root(root) / eid)
1545
- removed += 1
1554
+ kept.setdefault(obj, set()).add(e["restore"])
1555
+ continue
1556
+ if e["restore"] in kept.get(obj, set()):
1557
+ shutil.rmtree(journal_root(root) / eid)
1558
+ removed += 1
1559
+ else:
1560
+ kept.setdefault(obj, set()).add(e["restore"])
1546
1561
  if ns.json:
1547
1562
  print(json.dumps({"pruned": removed}))
1548
1563
  else:
1549
- print(f"-- pruned {removed} preview-only entries; applied "
1550
- "entries and snapshots are all kept")
1564
+ print(f"-- pruned {removed} redundant previews; applied entries, "
1565
+ "snapshots, and any preview holding a rollback nothing "
1566
+ "else has are all kept")
1551
1567
  return
1552
1568
  if not ns.id:
1553
1569
  sys.exit(f"Usage: {invocation()} journal "
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythia-plsql
3
- Version: 0.4.0
3
+ Version: 0.4.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
@@ -31,6 +31,15 @@ Three moves are FORBIDDEN for agents, and the CLI enforces the first two
31
31
  your turn; the token is only used after the developer's approval
32
32
  arrives as a NEW message.
33
33
 
34
+ ## When the change will NOT go through apply
35
+
36
+ Sometimes the developer runs the file themselves — a DBA executes it, a
37
+ release process owns it, or policy denies the group. The preview still ran,
38
+ so a rollback file for the version currently live already exists at
39
+ `.pythia/journal/<entry>/restore.sql`. Never let a change leave your hands
40
+ for manual execution without naming that path in the same message as the
41
+ .sql file. `pythia history <OBJECT>` lists every captured version.
42
+
34
43
  ## The Workflow
35
44
 
36
45
  **Before step 1:** if this conversation has not yet seen a standalone impact
@@ -54,7 +63,8 @@ first time anyone sees it, a step was skipped.
54
63
  If the fresh preview's before-side no longer matches what you last saw,
55
64
  say so explicitly: someone else may have changed the object on this
56
65
  shared database, and the developer must know that before approving.
57
- 5. **Read the exit code — it is the verdict:**
66
+ 5. **Read the exit code — it is the verdict. Never through a pipe:**
67
+ `apply … | tail` returns *tail's* code, always 0. Unpiped, or `${PIPESTATUS[0]}`.
58
68
 
59
69
  | Exit | Meaning | What you must do |
60
70
  |------|---------|-----------------|
@@ -120,6 +130,7 @@ the stakes are highest.
120
130
  | "Token is stale, I'll just take the new one" | The content changed. The developer must see the new preview. |
121
131
  | "apply refused it; run-sql will take it" | The refusal is the product working. Relay it. |
122
132
  | "I'll restore quietly to clean up my mistake" | Restores are writes. Same gate, same visibility. |
133
+ | "`$?` said 0 after I piped to tail" | That was tail's 0. Read pythia's own words, or its unpiped code. |
123
134
  | "The dev said 'stop asking' once" | That covered that task, not forever. Re-confirm scope on the next one. |
124
135
 
125
136
  ## When NOT to use this skill
@@ -540,25 +540,42 @@ def test_privilege_warning_speaks_proxy():
540
540
  FakeConn(direct), "APP", "APP")
541
541
 
542
542
 
543
- def test_journal_prune_keeps_applied_entries():
543
+ def test_prune_drops_only_redundant_previews():
544
+ """A preview wrote nothing, but its restore.sql is the live version at
545
+ that moment — the only rollback for a change run by hand. Prune may drop
546
+ it only when a newer entry already keeps the identical rollback."""
544
547
  import argparse
548
+ import contextlib
545
549
  import datetime
550
+ import io
546
551
  with tempfile.TemporaryDirectory() as td:
547
- base = datetime.datetime(2026, 8, 26, 12, 0, 0)
548
- pythia.write_journal_entry(td, "PROCEDURE", "P_PREVIEW", "a", "b",
552
+ base = datetime.datetime(2026, 8, 27, 9, 0, 0)
553
+ # two previews of the same object, same live version -> one is spare
554
+ pythia.write_journal_entry(td, "PROCEDURE", "P_A", "old", "new1",
549
555
  {"applied": False}, now=base)
550
- kept = pythia.write_journal_entry(
551
- td, "PROCEDURE", "P_APPLIED", "a", "b", {"applied": True},
556
+ keep_dup = pythia.write_journal_entry(
557
+ td, "PROCEDURE", "P_A", "old", "new2", {"applied": False},
552
558
  now=base.replace(minute=1))
559
+ # a preview of another object, unique rollback -> must survive
560
+ lone = pythia.write_journal_entry(
561
+ td, "PROCEDURE", "P_B", "only_copy", "x", {"applied": False},
562
+ now=base.replace(minute=2))
563
+ applied = pythia.write_journal_entry(
564
+ td, "PROCEDURE", "P_C", "a", "b", {"applied": True},
565
+ now=base.replace(minute=3))
566
+ snap = pythia.write_journal_entry(
567
+ td, "PROCEDURE", "P_D", "a", "a",
568
+ {"snapshot": True, "applied": False}, now=base.replace(minute=4))
553
569
  ns = argparse.Namespace(action="prune", project_root=td, json=False,
554
570
  id=None)
555
- import contextlib
556
- import io
557
571
  buf = io.StringIO()
558
572
  with contextlib.redirect_stdout(buf):
559
573
  pythia.cmd_journal(None, None, ns)
560
- assert "pruned 1" in buf.getvalue()
561
- assert pythia.list_journal_entries(td) == [kept]
574
+ left = pythia.list_journal_entries(td)
575
+ assert "pruned 1" in buf.getvalue(), buf.getvalue()
576
+ assert applied in left and snap in left # never touched
577
+ assert lone in left # unique rollback survives
578
+ assert keep_dup in left # newest of the pair kept
562
579
 
563
580
 
564
581
  def test_agent_user_alter_form_and_warning_prediction():
@@ -742,26 +759,6 @@ def test_auto_snapshot_reports_drift_and_keeps_the_old_rollback():
742
759
  assert len(pythia.list_journal_entries(td)) == 2
743
760
 
744
761
 
745
- def test_prune_never_eats_a_snapshot():
746
- import argparse
747
- import contextlib
748
- import io
749
- import datetime
750
- with tempfile.TemporaryDirectory() as td:
751
- base = datetime.datetime(2026, 8, 27, 9, 0, 0)
752
- pythia.write_journal_entry(td, "PROCEDURE", "P_A", "a", "b",
753
- {"applied": False}, now=base)
754
- snap = pythia.write_journal_entry(
755
- td, "PROCEDURE", "P_B", "a", "a", {"snapshot": True,
756
- "applied": False},
757
- now=base.replace(minute=1))
758
- ns = argparse.Namespace(action="prune", project_root=td, json=False,
759
- id=None)
760
- with contextlib.redirect_stdout(io.StringIO()):
761
- pythia.cmd_journal(None, None, ns)
762
- assert pythia.list_journal_entries(td) == [snap]
763
-
764
-
765
762
  def main():
766
763
  failed = 0
767
764
  for name, fn in sorted(globals().items()):
File without changes
File without changes
File without changes