pythia-plsql 0.4.6__tar.gz → 0.4.7__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.6/scripts/pythia_plsql.egg-info → pythia_plsql-0.4.7}/PKG-INFO +1 -1
  2. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/pyproject.toml +1 -1
  3. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/scripts/pythia.py +90 -5
  4. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7/scripts/pythia_plsql.egg-info}/PKG-INFO +1 -1
  5. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/tests/test_install.py +62 -2
  6. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/LICENSE +0 -0
  7. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/README.md +0 -0
  8. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/compile-errors.sql +0 -0
  9. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/dependencies.sql +0 -0
  10. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/impact.sql +0 -0
  11. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/invalid-objects.sql +0 -0
  12. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/name-occupants.sql +0 -0
  13. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/object-source.sql +0 -0
  14. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/plscope-enabled.sql +0 -0
  15. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/plscope-statements.sql +0 -0
  16. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/plscope-usages.sql +0 -0
  17. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/session-privileges.sql +0 -0
  18. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/similar-candidates.sql +0 -0
  19. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/source.sql +0 -0
  20. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/scripts/pythia_plsql.egg-info/SOURCES.txt +0 -0
  21. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
  22. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
  23. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
  24. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
  25. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/setup.cfg +0 -0
  26. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-apply/SKILL.md +0 -0
  27. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-explore/SKILL.md +0 -0
  28. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-explore/reference/data-dictionary.md +0 -0
  29. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-impact/SKILL.md +0 -0
  30. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-review/SKILL.md +0 -0
  31. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-review/reference/antipatterns.md +0 -0
  32. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-setup/SKILL.md +0 -0
  33. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-skill-author/SKILL.md +0 -0
  34. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-write/SKILL.md +0 -0
  35. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-write/reference/patterns.md +0 -0
  36. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/tests/test_phase1.py +0 -0
  37. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/tests/test_phase2.py +0 -0
  38. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/tests/test_phase3.py +0 -0
  39. {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/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.6
3
+ Version: 0.4.7
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.6"
7
+ version = "0.4.7"
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" }
@@ -185,6 +185,83 @@ def paint_diff_line(ln, enabled):
185
185
  return ln
186
186
 
187
187
 
188
+ NL = chr(10) # a literal escape does not survive every editing path
189
+
190
+
191
+ def offer_path_fix(scripts_dir, interactive, forced=False):
192
+ """Put pythia on PATH for the developer instead of handing them a command
193
+ to run. Only ever touches the user PATH, and only with a yes."""
194
+ if not scripts_dir or os.name != "nt":
195
+ return False
196
+ if not forced:
197
+ if not interactive:
198
+ return False
199
+ try:
200
+ answer = input(NL + "Add it to your PATH now? [Y/n] ").strip().lower()
201
+ except (EOFError, KeyboardInterrupt):
202
+ print()
203
+ return False
204
+ if answer not in ("", "y", "yes"):
205
+ return False
206
+ try:
207
+ changed = add_to_user_path(scripts_dir)
208
+ except OSError as e:
209
+ print(f"Could not update your PATH: {e}")
210
+ return False
211
+ print(NL + f" {'Added' if changed else 'Already in'} your user PATH: {scripts_dir}")
212
+ print(" Open a NEW terminal for it to take effect — an already-running")
213
+ print(" one keeps the environment it started with, and so do its tabs.")
214
+ print(f" Until then: {pathlib.Path(sys.executable).stem} -m pythia <command>")
215
+ return True
216
+
217
+
218
+ def path_contains(path_value, directory):
219
+ """Is this directory already listed in a PATH string? Compared the way
220
+ the platform does: case-insensitively on Windows, ignoring trailing
221
+ separators and empty entries."""
222
+ want = os.path.normcase(os.path.normpath(str(directory)))
223
+ return any(os.path.normcase(os.path.normpath(p)) == want
224
+ for p in str(path_value).split(os.pathsep) if p.strip())
225
+
226
+
227
+ def _read_user_path():
228
+ import winreg
229
+ with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment") as k:
230
+ try:
231
+ return winreg.QueryValueEx(k, "Path")[0]
232
+ except FileNotFoundError:
233
+ return ""
234
+
235
+
236
+ def _write_user_path(value):
237
+ import winreg
238
+ with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment", 0,
239
+ winreg.KEY_SET_VALUE) as k:
240
+ winreg.SetValueEx(k, "Path", 0, winreg.REG_EXPAND_SZ, value)
241
+
242
+
243
+ def add_to_user_path(directory, read=None, write=None):
244
+ """Append a directory to the *user* PATH, returning whether anything
245
+ changed.
246
+
247
+ Reads the stored user value, never `os.environ["PATH"]`. The process
248
+ environment is the system and user values merged, so writing it back into
249
+ user scope copies every system entry into the user's -- doubling the
250
+ effective PATH and leaving a stale snapshot that shadows the real system
251
+ one after it next changes. That mistake is easy to make and outlives the
252
+ install, which is why this function exists rather than a one-line command.
253
+ """
254
+ read = read or _read_user_path
255
+ write = write or _write_user_path
256
+ current = read() or ""
257
+ if path_contains(current, directory):
258
+ return False
259
+ joined = str(directory) if not current.strip() else \
260
+ current.rstrip(os.pathsep) + os.pathsep + str(directory)
261
+ write(joined)
262
+ return True
263
+
264
+
188
265
  def installed_scripts_dir():
189
266
  """The directory pip put our executable in. The default scheme is wrong
190
267
  for `pip install --user` — the common case on Windows — so the candidate
@@ -215,9 +292,13 @@ def entry_point_hint(found, scripts_dir):
215
292
  if scripts_dir:
216
293
  out.append(" It lives in: " + str(scripts_dir))
217
294
  if os.name == "nt":
218
- out.append(" Add it once, in PowerShell, then reopen the terminal:")
219
- out.append(" [Environment]::SetEnvironmentVariable('PATH',")
220
- out.append(" \"$env:PATH;" + str(scripts_dir) + "\", 'User')")
295
+ # Deliberately not a PowerShell one-liner over $env:PATH:
296
+ # that variable is system and user merged, so writing it
297
+ # back into user scope copies every system entry into the
298
+ # user's. Let the tool do it correctly instead.
299
+ out.append(" Put it there with: "
300
+ + pathlib.Path(sys.executable).stem
301
+ + " -m pythia install --add-to-path")
221
302
  else:
222
303
  out.append(" Add it to your shell profile:")
223
304
  out.append(" export PATH=\"$PATH:" + str(scripts_dir) + "\"")
@@ -1974,11 +2055,12 @@ def cmd_install(conn, schema, ns):
1974
2055
  clean_legacy_skills(ns.project_root)
1975
2056
  print(f"\nNext: fill in {path}")
1976
2057
  print(f"Then: {invocation()} check")
1977
- hint = entry_point_hint(shutil.which("pythia"),
1978
- installed_scripts_dir())
2058
+ hint = entry_point_hint(shutil.which("pythia"), installed_scripts_dir())
1979
2059
  if hint:
1980
2060
  print()
1981
2061
  print(hint)
2062
+ offer_path_fix(installed_scripts_dir(), interactive,
2063
+ getattr(ns, "add_to_path", False))
1982
2064
 
1983
2065
 
1984
2066
  COMMANDS = {"check": cmd_check, "ls": cmd_ls, "src": cmd_src, "args": cmd_args,
@@ -2112,6 +2194,9 @@ def build_parser():
2112
2194
  s.add_argument("-g", "--global", dest="glob", action="store_true",
2113
2195
  help="install the skill pack machine-wide (serves every "
2114
2196
  "project; per-project installs then skip skills)")
2197
+ s.add_argument("--add-to-path", action="store_true", dest="add_to_path",
2198
+ help="put the pythia command on your PATH without asking "
2199
+ "(Windows; user PATH only)")
2115
2200
  s.add_argument("--source", default=DEFAULT_SKILLS_SOURCE,
2116
2201
  help="skills repo for `npx skills add` "
2117
2202
  f"(default {DEFAULT_SKILLS_SOURCE})")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythia-plsql
3
- Version: 0.4.6
3
+ Version: 0.4.7
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
@@ -5,6 +5,7 @@ no network, no npx needed.
5
5
  Run: python tests/test_install.py
6
6
  """
7
7
  import json
8
+ import os
8
9
  import pathlib
9
10
  import sys
10
11
  import tempfile
@@ -187,9 +188,9 @@ def test_entry_point_hint_names_the_directory_to_add():
187
188
  """pip install --user on Windows puts pythia.exe in a Scripts directory
188
189
  that is not on PATH, so the very next documented command fails with
189
190
  CommandNotFound -- naming nothing that helps. Seen on a real machine."""
190
- hint = pythia.entry_point_hint(found=None, scripts_dir=r"C:\Py\Scripts")
191
+ hint = pythia.entry_point_hint(found=None, scripts_dir=SCRIPTS)
191
192
  assert hint is not None
192
- assert r"C:\Py\Scripts" in hint # the exact directory to add
193
+ assert SCRIPTS in hint # the exact directory to add
193
194
  assert "-m pythia" in hint # the fallback that works now
194
195
  # once the command resolves, say nothing
195
196
  assert pythia.entry_point_hint(found="/usr/local/bin/pythia",
@@ -235,6 +236,65 @@ def test_cmd_install_runs_end_to_end():
235
236
  assert env is not None
236
237
 
237
238
 
239
+ # Native paths: on POSIX ':' is the PATH separator and would cut a
240
+ # Windows path in half, so these are built from os.sep.
241
+ SCRIPTS = os.path.join(os.sep + 'opt', 'a', 'Scripts')
242
+ USERBIN = os.path.join(os.sep + 'opt', 'a', 'bin')
243
+ SYS1 = os.sep + 'windows'
244
+ SYS2 = os.path.join(os.sep + 'windows', 'system32')
245
+
246
+
247
+ def test_path_contains_normalises_like_the_platform_does():
248
+ """Paths must be native: on POSIX the separator is ':', which cuts a
249
+ Windows path in half, and normcase does not fold case."""
250
+ import os
251
+ here = os.path.join(os.sep + "opt", "a", "Scripts")
252
+ other = os.path.join(os.sep + "opt", "a", "Other")
253
+ p = os.pathsep.join([os.sep + "usr", here, ""]) # trailing empty entry
254
+ assert pythia.path_contains(p, here)
255
+ assert pythia.path_contains(p, here + os.sep) # trailing separator
256
+ assert not pythia.path_contains(p, other)
257
+ assert not pythia.path_contains("", here)
258
+ if os.name == "nt": # case folds on Windows only
259
+ assert pythia.path_contains(p, here.upper())
260
+
261
+
262
+ def test_add_to_user_path_reads_the_stored_value_not_the_process_env():
263
+ """The process PATH is system+user merged. Writing that back into user
264
+ scope copies every system entry into the user's -- a mess that outlives
265
+ the install. This is the mistake this function exists to not repeat."""
266
+ import os
267
+ written = []
268
+ stored = USERBIN # what the registry holds: user only
269
+ os.environ["PATH"] = os.pathsep.join( # what the process sees: much more
270
+ [SYS1, SYS2, stored])
271
+ changed = pythia.add_to_user_path(SCRIPTS,
272
+ read=lambda: stored,
273
+ write=written.append)
274
+ assert changed is True
275
+ assert written == [stored + os.pathsep + SCRIPTS]
276
+ assert SYS1 not in written[0] # no system entries leaked in
277
+
278
+
279
+ def test_add_to_user_path_is_idempotent():
280
+ written = []
281
+ changed = pythia.add_to_user_path(SCRIPTS,
282
+ read=lambda: SCRIPTS,
283
+ write=written.append)
284
+ assert changed is False and written == []
285
+
286
+
287
+ def test_add_to_user_path_handles_an_empty_and_untidy_value():
288
+ import os
289
+ written = []
290
+ pythia.add_to_user_path(SCRIPTS, read=lambda: "", write=written.append)
291
+ assert written == [SCRIPTS]
292
+ written.clear()
293
+ pythia.add_to_user_path(SCRIPTS, read=lambda: USERBIN + os.pathsep,
294
+ write=written.append)
295
+ assert written == [USERBIN + os.pathsep + SCRIPTS] # no doubled separator
296
+
297
+
238
298
  def main():
239
299
  failed = 0
240
300
  for name, fn in sorted(globals().items()):
File without changes
File without changes
File without changes