pythia-plsql 0.6.0__tar.gz → 0.6.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.
Files changed (41) hide show
  1. {pythia_plsql-0.6.0/scripts/pythia_plsql.egg-info → pythia_plsql-0.6.1}/PKG-INFO +1 -1
  2. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/pyproject.toml +1 -1
  3. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/scripts/pythia.py +62 -2
  4. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1/scripts/pythia_plsql.egg-info}/PKG-INFO +1 -1
  5. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/skills/pythia-setup/SKILL.md +1 -1
  6. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/tests/test_phase1.py +40 -0
  7. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/tests/test_phase5.py +22 -0
  8. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/LICENSE +0 -0
  9. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/README.md +0 -0
  10. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/compile-errors.sql +0 -0
  11. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/dependencies.sql +0 -0
  12. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/impact.sql +0 -0
  13. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/invalid-objects.sql +0 -0
  14. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/name-occupants.sql +0 -0
  15. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/object-names.sql +0 -0
  16. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/object-source.sql +0 -0
  17. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/plscope-enabled.sql +0 -0
  18. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/plscope-statements.sql +0 -0
  19. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/plscope-usages.sql +0 -0
  20. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/session-privileges.sql +0 -0
  21. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/similar-candidates.sql +0 -0
  22. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/queries/source.sql +0 -0
  23. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/scripts/pythia_plsql.egg-info/SOURCES.txt +0 -0
  24. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
  25. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
  26. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
  27. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
  28. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/setup.cfg +0 -0
  29. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/skills/pythia-apply/SKILL.md +0 -0
  30. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/skills/pythia-conventions/SKILL.md +0 -0
  31. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/skills/pythia-explore/SKILL.md +0 -0
  32. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/skills/pythia-explore/reference/data-dictionary.md +0 -0
  33. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/skills/pythia-impact/SKILL.md +0 -0
  34. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/skills/pythia-review/SKILL.md +0 -0
  35. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/skills/pythia-review/reference/antipatterns.md +0 -0
  36. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/skills/pythia-skill-author/SKILL.md +0 -0
  37. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/skills/pythia-write/SKILL.md +0 -0
  38. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/skills/pythia-write/reference/patterns.md +0 -0
  39. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/tests/test_install.py +0 -0
  40. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/tests/test_phase2.py +0 -0
  41. {pythia_plsql-0.6.0 → pythia_plsql-0.6.1}/tests/test_phase3.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythia-plsql
3
- Version: 0.6.0
3
+ Version: 0.6.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.6.0"
7
+ version = "0.6.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" }
@@ -2000,6 +2000,10 @@ Nothing here writes. Reading is free; guessing is not.
2000
2000
 
2001
2001
  the problem's shape deps · impact · plscope the exact graph, not a skim
2002
2002
  the schema's truth src · args · cols · ddl · errors · invalid · check · ls · grep · sql
2003
+ what you can reach connections - names, users, targets. NEVER open
2004
+ connections.json yourself: it holds passwords, a
2005
+ permission gate should stop you, and this command
2006
+ is the answer you were reaching for.
2003
2007
  the house style conventions (--scan / --check) · the project's conventions.md
2004
2008
  how it is done here similar · history neighbours to imitate, versions that exist
2005
2009
 
@@ -2038,6 +2042,59 @@ This page is the contract; follow it as written.
2038
2042
  """
2039
2043
 
2040
2044
 
2045
+ def connection_summary(cfg):
2046
+ """Everything about the configured connections except the secrets.
2047
+
2048
+ An agent has to know which connections exist. Without a sanctioned way to
2049
+ ask, it reads connections.json itself — which is exactly the access a
2050
+ permission classifier should stop, and did. So this exists, and it is
2051
+ built to be provably safe: fields are copied in by name, never by
2052
+ iterating the entry, so a key added to the config later cannot leak
2053
+ through here by accident.
2054
+ """
2055
+ cfg = dict(cfg or {})
2056
+ default = cfg.pop("default", None)
2057
+ rows = []
2058
+ for name, entry in cfg.items():
2059
+ if not isinstance(entry, dict):
2060
+ continue
2061
+ dsn = entry.get("dsn") or ""
2062
+ if not dsn and entry.get("host"):
2063
+ svc = entry.get("service_name") or entry.get("sid") or ""
2064
+ dsn = f"{entry['host']}:{entry.get('port', 1521)}"
2065
+ if svc:
2066
+ dsn += f"/{svc}"
2067
+ user = str(entry.get("user") or "")
2068
+ rows.append({
2069
+ "name": name,
2070
+ "user": user,
2071
+ "target": dsn or "\u2014",
2072
+ "schema": (entry.get("schema") or user.split("[")[-1].rstrip("]")
2073
+ or "\u2014").upper(),
2074
+ "default": isinstance(default, str) and default.upper() == name.upper(),
2075
+ })
2076
+ return rows
2077
+
2078
+
2079
+ def cmd_connections(conn, schema, ns):
2080
+ cfg, _ = find_config(pathlib.Path.cwd(), os.environ)
2081
+ rows = connection_summary(cfg)
2082
+ if ns.json:
2083
+ print(json.dumps(rows, indent=2))
2084
+ return
2085
+ if not rows:
2086
+ print("No connections configured. "
2087
+ f"{invocation()} install scaffolds .pythia/connections.json.")
2088
+ return
2089
+ print(f"{'':1} {'NAME':<16} {'USER':<26} {'SCHEMA':<20} TARGET")
2090
+ for r in rows:
2091
+ mark = "*" if r["default"] else " "
2092
+ print(f"{mark} {r['name']:<16} {r['user']:<26} {r['schema']:<20} "
2093
+ f"{r['target']}")
2094
+ print("\n* default. Passwords are never printed — this command reads the "
2095
+ "config so you\n do not have to open it. Pick one with --conn NAME.")
2096
+
2097
+
2041
2098
  def cmd_guide(conn, schema, ns):
2042
2099
  print(OPERATING_GUIDE)
2043
2100
 
@@ -2435,11 +2492,11 @@ COMMANDS = {"check": cmd_check, "ls": cmd_ls, "src": cmd_src, "args": cmd_args,
2435
2492
  "invalid": cmd_invalid, "errors": cmd_errors, "deps": cmd_deps,
2436
2493
  "impact": cmd_impact, "similar": cmd_similar, "plscope": cmd_plscope,
2437
2494
  "policy": cmd_policy, "journal": cmd_journal, "apply": cmd_apply,
2438
- "conventions": cmd_conventions, "guide": cmd_guide, "install": cmd_install,
2495
+ "conventions": cmd_conventions, "guide": cmd_guide, "connections": cmd_connections, "install": cmd_install,
2439
2496
  "unistr": cmd_unistr, "agent-user": cmd_agent_user,
2440
2497
  "history": cmd_history}
2441
2498
 
2442
- NO_DB_COMMANDS = {"policy", "journal", "install", "unistr", "guide",
2499
+ NO_DB_COMMANDS = {"policy", "journal", "install", "unistr", "guide", "connections",
2443
2500
  "history"}
2444
2501
 
2445
2502
 
@@ -2537,6 +2594,9 @@ def build_parser():
2537
2594
  help="apply without stopping; the full preview still prints and journals")
2538
2595
  s.add_argument("--depth", type=int, default=3,
2539
2596
  help="impact depth for the preview (default 3)")
2597
+ sub.add_parser("connections", parents=[common()],
2598
+ help="list configured connections — names, users, "
2599
+ "targets; never passwords")
2540
2600
  sub.add_parser("guide", parents=[common()],
2541
2601
  help="the operating model: Learn, Ask, Do — the whole "
2542
2602
  "harness on one page, no database needed")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythia-plsql
3
- Version: 0.6.0
3
+ Version: 0.6.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
@@ -85,7 +85,7 @@ until you want it.
85
85
 
86
86
  | File | Created by | What it does |
87
87
  |---|---|---|
88
- | `connections.json` | `pythia install` | Where to connect. Holds credentials; gitignored. |
88
+ | `connections.json` | `pythia install` | Where to connect. **Holds passwords — never open it**; `pythia connections` lists names, users, schemas and targets with no secrets. Gitignored. |
89
89
  | `journal/` | first write or snapshot | Every captured version, with a runnable rollback per entry. |
90
90
  | `conventions.json` | `pythia conventions --init` | Naming patterns per object type. Every apply preview warns when a new name drifts. |
91
91
  | `conventions.md` | `pythia conventions --init` | The house rules in prose. **`pythia-write` reads this before writing anything**, and it outranks the generic patterns this pack ships with. |
@@ -327,6 +327,46 @@ def test_oracle_account_taken_from_a_proxy_connect_string():
327
327
  assert pythia.authenticating_account(None) is None
328
328
 
329
329
 
330
+ SECRET = "hunter2-do-not-leak"
331
+
332
+
333
+ def _cfg():
334
+ return {
335
+ "dev": {"host": "db.example.com", "port": 1521,
336
+ "service_name": "orcl", "user": "app_agent[app_owner]",
337
+ "password": SECRET, "schema": "APP_OWNER"},
338
+ "stage": {"dsn": "stage.example.com:1521/orcl", "user": "u2",
339
+ "password": SECRET},
340
+ "default": "dev",
341
+ }
342
+
343
+
344
+ def test_connection_summary_never_carries_a_secret():
345
+ """An agent needs to know which connections exist. Until it could ask,
346
+ it parsed connections.json itself -- which is exactly the read a
347
+ permission classifier should block. This is the sanctioned answer, so it
348
+ must be provably secret-free, in every field and both output modes."""
349
+ import json
350
+ rows = pythia.connection_summary(_cfg())
351
+ blob = json.dumps(rows)
352
+ assert SECRET not in blob
353
+ assert "password" not in blob.lower()
354
+ names = [r["name"] for r in rows]
355
+ assert names == ["dev", "stage"] # the "default" key is not one
356
+ dev = rows[0]
357
+ assert dev["default"] is True and rows[1]["default"] is False
358
+ assert dev["user"] == "app_agent[app_owner]" # proxy shape is safe to show
359
+ assert dev["target"] == "db.example.com:1521/orcl"
360
+ assert rows[1]["target"] == "stage.example.com:1521/orcl" # dsn form
361
+ assert dev["schema"] == "APP_OWNER"
362
+
363
+
364
+ def test_connection_summary_survives_a_thin_entry():
365
+ rows = pythia.connection_summary({"env": {"user": "u"}})
366
+ assert rows[0]["target"] == "—" and rows[0]["schema"] == "U"
367
+ assert pythia.connection_summary(None) == []
368
+
369
+
330
370
  def main():
331
371
  failed = 0
332
372
  for name, fn in sorted(globals().items()):
@@ -145,6 +145,28 @@ def test_every_skill_declares_its_phase():
145
145
  f"{name}: phase must name Learn/Ask/Do, got {m.group(1)!r}"
146
146
 
147
147
 
148
+ def test_no_manifest_hardcodes_a_stale_skill_count():
149
+ """The pack grew from seven skills to eight and three shipped manifests
150
+ still said seven. Counting in prose is a contradiction waiting to happen,
151
+ so the check is: no manifest states a number the code can disprove."""
152
+ import json
153
+ n = len(EXPECTED)
154
+ words = {7: "seven", 8: "eight", 9: "nine"}
155
+ stale = [w for k, w in words.items() if k != n]
156
+ for rel in (".claude-plugin/marketplace.json", "npm/package.json",
157
+ "npm/README.md"):
158
+ text = (ROOT / rel).read_text(encoding="utf-8").lower()
159
+ for word in stale:
160
+ assert word + " skill" not in text and word + "-skill" not in text, \
161
+ f"{rel} claims {word} skills; there are {n}"
162
+ mk = json.loads((ROOT / ".claude-plugin/marketplace.json")
163
+ .read_text(encoding="utf-8"))
164
+ declared = {p.rsplit("/", 1)[-1] for p in mk["plugins"][0]["skills"]}
165
+ assert declared == EXPECTED, (
166
+ f"marketplace.json and the pack disagree: "
167
+ f"missing {sorted(EXPECTED - declared)}, extra {sorted(declared - EXPECTED)}")
168
+
169
+
148
170
  def main():
149
171
  failed = 0
150
172
  for name, fn in sorted(globals().items()):
File without changes
File without changes
File without changes