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.
- {pythia_plsql-0.4.6/scripts/pythia_plsql.egg-info → pythia_plsql-0.4.7}/PKG-INFO +1 -1
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/pyproject.toml +1 -1
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/scripts/pythia.py +90 -5
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7/scripts/pythia_plsql.egg-info}/PKG-INFO +1 -1
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/tests/test_install.py +62 -2
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/LICENSE +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/README.md +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/compile-errors.sql +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/dependencies.sql +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/impact.sql +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/invalid-objects.sql +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/name-occupants.sql +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/object-source.sql +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/plscope-enabled.sql +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/plscope-statements.sql +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/plscope-usages.sql +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/session-privileges.sql +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/similar-candidates.sql +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/queries/source.sql +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/scripts/pythia_plsql.egg-info/SOURCES.txt +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/setup.cfg +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-apply/SKILL.md +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-explore/SKILL.md +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-explore/reference/data-dictionary.md +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-impact/SKILL.md +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-review/SKILL.md +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-review/reference/antipatterns.md +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-setup/SKILL.md +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-skill-author/SKILL.md +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-write/SKILL.md +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/skills/pythia-write/reference/patterns.md +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/tests/test_phase1.py +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/tests/test_phase2.py +0 -0
- {pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/tests/test_phase3.py +0 -0
- {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.
|
|
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.
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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.
|
|
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=
|
|
191
|
+
hint = pythia.entry_point_hint(found=None, scripts_dir=SCRIPTS)
|
|
191
192
|
assert hint is not None
|
|
192
|
-
assert
|
|
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
|
|
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.4.6 → pythia_plsql-0.4.7}/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
|
{pythia_plsql-0.4.6 → pythia_plsql-0.4.7}/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
|