pythia-plsql 0.4.9__tar.gz → 0.5.0__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.9/scripts/pythia_plsql.egg-info → pythia_plsql-0.5.0}/PKG-INFO +7 -7
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/README.md +6 -6
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/pyproject.toml +1 -1
- pythia_plsql-0.5.0/queries/object-names.sql +11 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/scripts/pythia.py +271 -29
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0/scripts/pythia_plsql.egg-info}/PKG-INFO +7 -7
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/scripts/pythia_plsql.egg-info/SOURCES.txt +2 -0
- pythia_plsql-0.5.0/skills/pythia-conventions/SKILL.md +101 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/skills/pythia-setup/SKILL.md +23 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/skills/pythia-write/SKILL.md +3 -1
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/tests/test_install.py +23 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/tests/test_phase2.py +64 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/tests/test_phase5.py +1 -1
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/LICENSE +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/queries/compile-errors.sql +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/queries/dependencies.sql +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/queries/impact.sql +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/queries/invalid-objects.sql +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/queries/name-occupants.sql +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/queries/object-source.sql +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/queries/plscope-enabled.sql +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/queries/plscope-statements.sql +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/queries/plscope-usages.sql +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/queries/session-privileges.sql +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/queries/similar-candidates.sql +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/queries/source.sql +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/setup.cfg +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/skills/pythia-apply/SKILL.md +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/skills/pythia-explore/SKILL.md +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/skills/pythia-explore/reference/data-dictionary.md +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/skills/pythia-impact/SKILL.md +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/skills/pythia-review/SKILL.md +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/skills/pythia-review/reference/antipatterns.md +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/skills/pythia-skill-author/SKILL.md +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/skills/pythia-write/reference/patterns.md +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/tests/test_phase1.py +0 -0
- {pythia_plsql-0.4.9 → pythia_plsql-0.5.0}/tests/test_phase3.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythia-plsql
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
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
|
|
@@ -178,13 +178,13 @@ it audits every interaction in `DBTOOLS$MCP_LOG`. **Writes never do** — only
|
|
|
178
178
|
|
|
179
179
|
## Skills
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
Eight skills teach the agent the workflow — gates, not suggestions:
|
|
182
182
|
|
|
183
|
-
`pythia-setup` · `pythia-explore` · `pythia-impact` (
|
|
184
|
-
`pythia-write` (copy the codebase's conventions) · `pythia-apply` (the gate:
|
|
185
|
-
developer
|
|
186
|
-
`pythia-
|
|
187
|
-
workflow as a
|
|
183
|
+
`pythia-setup` · `pythia-explore` · `pythia-impact` (before any change) ·
|
|
184
|
+
`pythia-write` (copy the codebase's conventions) · `pythia-apply` (the gate:
|
|
185
|
+
the developer approves the preview in chat) · `pythia-review` (antipatterns) ·
|
|
186
|
+
`pythia-conventions` (adopt a house style, verified against real names) ·
|
|
187
|
+
`pythia-skill-author` (capture your team's workflow as a skill).
|
|
188
188
|
|
|
189
189
|
## Compatibility
|
|
190
190
|
|
|
@@ -164,13 +164,13 @@ it audits every interaction in `DBTOOLS$MCP_LOG`. **Writes never do** — only
|
|
|
164
164
|
|
|
165
165
|
## Skills
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
Eight skills teach the agent the workflow — gates, not suggestions:
|
|
168
168
|
|
|
169
|
-
`pythia-setup` · `pythia-explore` · `pythia-impact` (
|
|
170
|
-
`pythia-write` (copy the codebase's conventions) · `pythia-apply` (the gate:
|
|
171
|
-
developer
|
|
172
|
-
`pythia-
|
|
173
|
-
workflow as a
|
|
169
|
+
`pythia-setup` · `pythia-explore` · `pythia-impact` (before any change) ·
|
|
170
|
+
`pythia-write` (copy the codebase's conventions) · `pythia-apply` (the gate:
|
|
171
|
+
the developer approves the preview in chat) · `pythia-review` (antipatterns) ·
|
|
172
|
+
`pythia-conventions` (adopt a house style, verified against real names) ·
|
|
173
|
+
`pythia-skill-author` (capture your team's workflow as a skill).
|
|
174
174
|
|
|
175
175
|
## Compatibility
|
|
176
176
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pythia-plsql"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
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" }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Purpose: every object name in the schema with its type, for measuring how
|
|
2
|
+
-- well a naming pattern describes what is actually there. Derived
|
|
3
|
+
-- patterns are guesses until the schema agrees with them.
|
|
4
|
+
-- Binds: :s schema (object owner)
|
|
5
|
+
-- Returns: OBJECT_TYPE, OBJECT_NAME
|
|
6
|
+
select object_type,
|
|
7
|
+
object_name
|
|
8
|
+
from all_objects
|
|
9
|
+
where owner = :s
|
|
10
|
+
and object_type not in ('INDEX', 'LOB', 'TABLE PARTITION', 'INDEX PARTITION')
|
|
11
|
+
order by object_type, object_name
|
|
@@ -85,6 +85,7 @@ QUERY_BINDS = {
|
|
|
85
85
|
"object-source.sql": {"s", "n", "t"},
|
|
86
86
|
"session-privileges.sql": set(),
|
|
87
87
|
"name-occupants.sql": {"s", "n"},
|
|
88
|
+
"object-names.sql": {"s"},
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
|
|
@@ -1332,6 +1333,199 @@ def cmd_plscope(conn, schema, ns):
|
|
|
1332
1333
|
emit_table(ns, stmt_cols, *clip(stmt_rows, ns.limit))
|
|
1333
1334
|
|
|
1334
1335
|
|
|
1336
|
+
AGENT_USER_SQL = """\
|
|
1337
|
+
-- Least-privilege agent credential for schema {owner} — run as a DBA.
|
|
1338
|
+
-- Proxy authentication: the agent logs in with its OWN password but works
|
|
1339
|
+
-- inside {owner}; it never learns the owner password, owns nothing, and
|
|
1340
|
+
-- revocation is one statement. Deliberately absent: DBA, RESOURCE, ANY
|
|
1341
|
+
-- privileges, utility grants — the agent needs none of them to develop
|
|
1342
|
+
-- PL/SQL, and every extra grant widens the blast radius.
|
|
1343
|
+
|
|
1344
|
+
{create_line}
|
|
1345
|
+
GRANT CREATE SESSION TO {agent};
|
|
1346
|
+
ALTER USER {owner} GRANT CONNECT THROUGH {agent};
|
|
1347
|
+
|
|
1348
|
+
-- Cut the agent off later (owner untouched):
|
|
1349
|
+
-- ALTER USER {owner} REVOKE CONNECT THROUGH {agent};
|
|
1350
|
+
-- Fresh owner schema instead? See examples/agent-user-setup.example.sql.
|
|
1351
|
+
"""
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
CONVENTIONS_TEMPLATE = """{
|
|
1355
|
+
"naming": {
|
|
1356
|
+
"TABLE": "^T_[A-Z0-9_]+$",
|
|
1357
|
+
"PROCEDURE": "^P_[A-Z0-9_]+$",
|
|
1358
|
+
"FUNCTION": "^F_[A-Z0-9_]+$",
|
|
1359
|
+
"PACKAGE": "^PKG_[A-Z0-9_]+$",
|
|
1360
|
+
"SEQUENCE": "^S_[A-Z0-9_]+$",
|
|
1361
|
+
"TRIGGER": "^TRG_[A-Z0-9_]+$"
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
"""
|
|
1365
|
+
|
|
1366
|
+
CONVENTIONS_PROSE_TEMPLATE = """# Project conventions
|
|
1367
|
+
|
|
1368
|
+
Rules for anyone — human or agent — writing PL/SQL in this schema. Agents are
|
|
1369
|
+
told to read this before writing, and it outranks the generic patterns the
|
|
1370
|
+
skill pack ships with.
|
|
1371
|
+
|
|
1372
|
+
`conventions.json` next to this file holds the naming patterns as regexes;
|
|
1373
|
+
every `pythia apply` preview warns when a new object's name does not match.
|
|
1374
|
+
Keep the two in step: this file explains, that file enforces.
|
|
1375
|
+
|
|
1376
|
+
## Naming
|
|
1377
|
+
|
|
1378
|
+
Replace the patterns in `conventions.json` with yours, then describe them
|
|
1379
|
+
here so the reasoning survives. `pythia similar <A_TYPICAL_NAME>` shows what
|
|
1380
|
+
the schema already does — copy that rather than inventing a scheme.
|
|
1381
|
+
|
|
1382
|
+
| Kind | Rule | Example |
|
|
1383
|
+
|---|---|---|
|
|
1384
|
+
| Table | | |
|
|
1385
|
+
| Procedure | | |
|
|
1386
|
+
| Function | | |
|
|
1387
|
+
|
|
1388
|
+
## Rules that carry a cost when broken
|
|
1389
|
+
|
|
1390
|
+
State the consequence, not just the rule — a rule with a named cost gets
|
|
1391
|
+
followed. For example: which parameter prefixes the calling layer depends on,
|
|
1392
|
+
which column every query must filter by, where a transaction may commit.
|
|
1393
|
+
|
|
1394
|
+
| Rule | Cost of breaking it |
|
|
1395
|
+
|---|---|
|
|
1396
|
+
| | |
|
|
1397
|
+
|
|
1398
|
+
## Known exceptions
|
|
1399
|
+
|
|
1400
|
+
Objects that break the pattern on purpose, and why renaming them is worse
|
|
1401
|
+
than the warning they produce on every apply.
|
|
1402
|
+
"""
|
|
1403
|
+
|
|
1404
|
+
|
|
1405
|
+
SCAN_SHARE = 0.9 # a token set must cover this much to count as the rule
|
|
1406
|
+
SCAN_MAX_ALTERNATIVES = 12
|
|
1407
|
+
|
|
1408
|
+
|
|
1409
|
+
def _dominant(tokens_at_position, total):
|
|
1410
|
+
"""The smallest set of tokens covering SCAN_SHARE of the names, or None.
|
|
1411
|
+
|
|
1412
|
+
A set only counts as a rule when its tokens repeat. As many distinct
|
|
1413
|
+
tokens as there are names is a list of names, not a convention — and
|
|
1414
|
+
writing that down produces a pattern that fails on the next object
|
|
1415
|
+
anyone adds.
|
|
1416
|
+
"""
|
|
1417
|
+
limit = min(SCAN_MAX_ALTERNATIVES, max(1, total // 2))
|
|
1418
|
+
counts = {}
|
|
1419
|
+
for tok in tokens_at_position:
|
|
1420
|
+
counts[tok] = counts.get(tok, 0) + 1
|
|
1421
|
+
ranked = sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))
|
|
1422
|
+
picked, covered = [], 0
|
|
1423
|
+
for tok, n in ranked:
|
|
1424
|
+
if len(picked) >= limit:
|
|
1425
|
+
return None
|
|
1426
|
+
picked.append(tok)
|
|
1427
|
+
covered += n
|
|
1428
|
+
if covered >= total * SCAN_SHARE:
|
|
1429
|
+
return picked
|
|
1430
|
+
return None
|
|
1431
|
+
|
|
1432
|
+
|
|
1433
|
+
def propose_pattern(names, min_names=3):
|
|
1434
|
+
"""Read the shape off real names: a dominant first token, and a dominant
|
|
1435
|
+
last token when there is one. Returns None when the names share no
|
|
1436
|
+
structure — configuring nothing beats configuring a rule that means
|
|
1437
|
+
nothing."""
|
|
1438
|
+
names = [str(n).upper() for n in names if n]
|
|
1439
|
+
if len(names) < min_names:
|
|
1440
|
+
return None
|
|
1441
|
+
split = [n.split("_") for n in names]
|
|
1442
|
+
if sum(1 for p in split if len(p) >= 2) < len(names) * SCAN_SHARE:
|
|
1443
|
+
return None # mostly single-word names: no shape
|
|
1444
|
+
heads = _dominant([p[0] for p in split], len(names))
|
|
1445
|
+
if not heads:
|
|
1446
|
+
return None
|
|
1447
|
+
head = heads[0] if len(heads) == 1 else "(" + "|".join(sorted(heads)) + ")"
|
|
1448
|
+
# the suffixed form needs a third token to sit in; without one for most
|
|
1449
|
+
# names, an alternation at the end would exclude the short ones
|
|
1450
|
+
if sum(1 for p in split if len(p) >= 3) >= len(names) * SCAN_SHARE:
|
|
1451
|
+
tails = _dominant([p[-1] for p in split], len(names))
|
|
1452
|
+
if tails:
|
|
1453
|
+
tail = (tails[0] if len(tails) == 1
|
|
1454
|
+
else "(" + "|".join(sorted(tails)) + ")")
|
|
1455
|
+
return f"^{head}_[A-Z0-9_]+_{tail}$"
|
|
1456
|
+
return f"^{head}_[A-Z0-9_]+$"
|
|
1457
|
+
|
|
1458
|
+
|
|
1459
|
+
def scan_conventions(objects):
|
|
1460
|
+
"""Propose a naming block from the schema itself. The tool tokenizes the
|
|
1461
|
+
names so the agent never has to read thousands of them into context; the
|
|
1462
|
+
developer's own document decides what is kept."""
|
|
1463
|
+
by_type = {}
|
|
1464
|
+
for otype, name in objects:
|
|
1465
|
+
by_type.setdefault(str(otype).upper(), []).append(name)
|
|
1466
|
+
naming = {}
|
|
1467
|
+
for otype, names in sorted(by_type.items()):
|
|
1468
|
+
pattern = propose_pattern(names)
|
|
1469
|
+
if pattern:
|
|
1470
|
+
naming[otype] = pattern
|
|
1471
|
+
return {"naming": naming}
|
|
1472
|
+
|
|
1473
|
+
|
|
1474
|
+
def pattern_coverage(conv, objects):
|
|
1475
|
+
"""How well each configured pattern describes the names already in the
|
|
1476
|
+
schema. objects: (object_type, object_name).
|
|
1477
|
+
|
|
1478
|
+
A pattern derived from a document, or from reading a few examples, is a
|
|
1479
|
+
guess. Measuring it against every real name turns the guess into a number
|
|
1480
|
+
and names the exceptions — which is the difference between conventions
|
|
1481
|
+
that hold and conventions that produce a warning on every apply.
|
|
1482
|
+
"""
|
|
1483
|
+
patterns = (conv or {}).get("naming") or {}
|
|
1484
|
+
out = {}
|
|
1485
|
+
for otype, pattern in patterns.items():
|
|
1486
|
+
rx = re.compile(pattern)
|
|
1487
|
+
names = sorted(n for t, n in objects if str(t).upper() == otype.upper())
|
|
1488
|
+
misses = [n for n in names if not rx.match(str(n))]
|
|
1489
|
+
out[otype] = {"pattern": pattern, "total": len(names),
|
|
1490
|
+
"matched": len(names) - len(misses), "misses": misses}
|
|
1491
|
+
return out
|
|
1492
|
+
|
|
1493
|
+
|
|
1494
|
+
def coverage_verdict(matched, total):
|
|
1495
|
+
"""Read the numbers so nobody has to. A low rate means the pattern is
|
|
1496
|
+
wrong far more often than it means the schema is."""
|
|
1497
|
+
if total == 0:
|
|
1498
|
+
return "nothing of that type in this schema — untested rule"
|
|
1499
|
+
if matched == total:
|
|
1500
|
+
return "every name matches"
|
|
1501
|
+
pct = round(100 * matched / total)
|
|
1502
|
+
if pct >= 90:
|
|
1503
|
+
return f"{pct}% match — the rest are worth listing as exceptions"
|
|
1504
|
+
return (f"only {pct}% match — the derived pattern is probably wrong; "
|
|
1505
|
+
"widen it or split it before writing it down")
|
|
1506
|
+
|
|
1507
|
+
|
|
1508
|
+
def scaffold_conventions(root):
|
|
1509
|
+
"""Write the conventions pair, skipping anything that already exists.
|
|
1510
|
+
Returns the paths actually created.
|
|
1511
|
+
|
|
1512
|
+
The docs used to say "copy examples/conventions.example.json", which is
|
|
1513
|
+
no help to anyone who installed the wheel: there is no examples directory
|
|
1514
|
+
there. The tool carries the templates instead.
|
|
1515
|
+
"""
|
|
1516
|
+
d = pathlib.Path(root) / CONFIG_DIR
|
|
1517
|
+
made = []
|
|
1518
|
+
for name, body in (("conventions.json", CONVENTIONS_TEMPLATE),
|
|
1519
|
+
("conventions.md", CONVENTIONS_PROSE_TEMPLATE)):
|
|
1520
|
+
path = d / name
|
|
1521
|
+
if path.is_file():
|
|
1522
|
+
continue
|
|
1523
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
1524
|
+
path.write_text(body, encoding="utf-8")
|
|
1525
|
+
made.append(path)
|
|
1526
|
+
return made
|
|
1527
|
+
|
|
1528
|
+
|
|
1335
1529
|
def load_conventions(root):
|
|
1336
1530
|
"""Project house style from .pythia/conventions.json — the machine half of
|
|
1337
1531
|
the customization surface (the prose half is conventions.md, for agents).
|
|
@@ -1789,43 +1983,78 @@ def cmd_journal(conn, schema, ns):
|
|
|
1789
1983
|
sys.exit("unreachable: restore is dispatched with a connection in main")
|
|
1790
1984
|
|
|
1791
1985
|
|
|
1986
|
+
def _schema_objects(conn, schema):
|
|
1987
|
+
_, rows = run_query(conn, load_query("object-names.sql"), {"s": schema})
|
|
1988
|
+
return [(r[0], r[1]) for r in rows]
|
|
1989
|
+
|
|
1990
|
+
|
|
1792
1991
|
def cmd_conventions(conn, schema, ns):
|
|
1793
|
-
|
|
1992
|
+
root = ns.project_root
|
|
1993
|
+
if getattr(ns, "init", False):
|
|
1994
|
+
made = scaffold_conventions(root)
|
|
1995
|
+
for path in made:
|
|
1996
|
+
print(f"Created {path}")
|
|
1997
|
+
if not made:
|
|
1998
|
+
print(f"Both files already exist in "
|
|
1999
|
+
f"{pathlib.Path(root) / CONFIG_DIR} — left untouched.")
|
|
2000
|
+
else:
|
|
2001
|
+
print(NL + "Edit the patterns to match this schema — "
|
|
2002
|
+
f"`{invocation()} conventions --scan` reads them off it.")
|
|
2003
|
+
return
|
|
2004
|
+
|
|
2005
|
+
if getattr(ns, "scan", False):
|
|
2006
|
+
proposed = scan_conventions(_schema_objects(conn, schema))
|
|
2007
|
+
if ns.json:
|
|
2008
|
+
print(json.dumps(proposed, indent=2))
|
|
2009
|
+
return
|
|
2010
|
+
print(f"Patterns read off {schema}. Review against your own standards, "
|
|
2011
|
+
"then save as" + NL + f"{CONFIG_DIR}/conventions.json:" + NL)
|
|
2012
|
+
print(json.dumps(proposed, indent=2))
|
|
2013
|
+
if not proposed["naming"]:
|
|
2014
|
+
print(NL + "Nothing proposed: too few objects, or names with no "
|
|
2015
|
+
"shared shape.")
|
|
2016
|
+
return
|
|
2017
|
+
|
|
2018
|
+
if getattr(ns, "check", False):
|
|
2019
|
+
conv = load_conventions(root)
|
|
2020
|
+
if not conv:
|
|
2021
|
+
sys.exit(f"No conventions to check. {invocation()} conventions "
|
|
2022
|
+
"--scan proposes some from this schema.")
|
|
2023
|
+
cov = pattern_coverage(conv, _schema_objects(conn, schema))
|
|
2024
|
+
if ns.json:
|
|
2025
|
+
print(json.dumps(cov, indent=2))
|
|
2026
|
+
return
|
|
2027
|
+
worst = 0
|
|
2028
|
+
for otype, s in sorted(cov.items()):
|
|
2029
|
+
print(f"{otype:<14} {s['matched']}/{s['total']} "
|
|
2030
|
+
f"{coverage_verdict(s['matched'], s['total'])}")
|
|
2031
|
+
if s["misses"]:
|
|
2032
|
+
shown, more = s["misses"][:8], len(s["misses"]) - 8
|
|
2033
|
+
print(" not matching: " + ", ".join(shown)
|
|
2034
|
+
+ (f", +{more} more" if more > 0 else ""))
|
|
2035
|
+
worst = max(worst, len(s["misses"]))
|
|
2036
|
+
if worst:
|
|
2037
|
+
print(NL + "Every name above warns on `apply`. Widen the pattern "
|
|
2038
|
+
"if it is the rule that is wrong," + NL + "or record them in "
|
|
2039
|
+
"conventions.md as deliberate exceptions.")
|
|
2040
|
+
return
|
|
2041
|
+
|
|
2042
|
+
conv = load_conventions(root)
|
|
1794
2043
|
if ns.json:
|
|
1795
2044
|
print(json.dumps(conv or {}))
|
|
1796
2045
|
return
|
|
1797
2046
|
if not conv:
|
|
1798
2047
|
print("No project conventions configured.")
|
|
1799
|
-
print(f"
|
|
1800
|
-
|
|
1801
|
-
"warn when a new object's name drifts from your patterns.\n"
|
|
1802
|
-
"Put the prose rules in conventions.md next to it for your agents.")
|
|
2048
|
+
print(f" {invocation()} conventions --scan read patterns off this schema")
|
|
2049
|
+
print(f" {invocation()} conventions --init start from a blank template")
|
|
1803
2050
|
return
|
|
1804
2051
|
print("Naming patterns — apply previews warn when a name drifts:")
|
|
1805
2052
|
for otype, pattern in conv.get("naming", {}).items():
|
|
1806
2053
|
print(f" {otype:<13} {pattern}")
|
|
1807
|
-
md = pathlib.Path(
|
|
2054
|
+
md = pathlib.Path(root) / CONFIG_DIR / "conventions.md"
|
|
1808
2055
|
if md.is_file():
|
|
1809
|
-
print(f"
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
AGENT_USER_SQL = """\
|
|
1813
|
-
-- Least-privilege agent credential for schema {owner} — run as a DBA.
|
|
1814
|
-
-- Proxy authentication: the agent logs in with its OWN password but works
|
|
1815
|
-
-- inside {owner}; it never learns the owner password, owns nothing, and
|
|
1816
|
-
-- revocation is one statement. Deliberately absent: DBA, RESOURCE, ANY
|
|
1817
|
-
-- privileges, utility grants — the agent needs none of them to develop
|
|
1818
|
-
-- PL/SQL, and every extra grant widens the blast radius.
|
|
1819
|
-
|
|
1820
|
-
{create_line}
|
|
1821
|
-
GRANT CREATE SESSION TO {agent};
|
|
1822
|
-
ALTER USER {owner} GRANT CONNECT THROUGH {agent};
|
|
1823
|
-
|
|
1824
|
-
-- Cut the agent off later (owner untouched):
|
|
1825
|
-
-- ALTER USER {owner} REVOKE CONNECT THROUGH {agent};
|
|
1826
|
-
-- Fresh owner schema instead? See examples/agent-user-setup.example.sql.
|
|
1827
|
-
"""
|
|
1828
|
-
|
|
2056
|
+
print(NL + f"Prose rules for agents: {md}")
|
|
2057
|
+
print(f"{NL}Measure them against the schema: {invocation()} conventions --check")
|
|
1829
2058
|
|
|
1830
2059
|
def agent_user_sql(owner, agent, password, exists):
|
|
1831
2060
|
"""exists True: the user is already there — CREATE would be ORA-01920,
|
|
@@ -2156,7 +2385,7 @@ COMMANDS = {"check": cmd_check, "ls": cmd_ls, "src": cmd_src, "args": cmd_args,
|
|
|
2156
2385
|
"unistr": cmd_unistr, "agent-user": cmd_agent_user,
|
|
2157
2386
|
"history": cmd_history}
|
|
2158
2387
|
|
|
2159
|
-
NO_DB_COMMANDS = {"policy", "journal", "
|
|
2388
|
+
NO_DB_COMMANDS = {"policy", "journal", "install", "unistr",
|
|
2160
2389
|
"history"}
|
|
2161
2390
|
|
|
2162
2391
|
|
|
@@ -2254,8 +2483,17 @@ def build_parser():
|
|
|
2254
2483
|
help="apply without stopping; the full preview still prints and journals")
|
|
2255
2484
|
s.add_argument("--depth", type=int, default=3,
|
|
2256
2485
|
help="impact depth for the preview (default 3)")
|
|
2257
|
-
sub.add_parser("conventions", parents=[common()],
|
|
2486
|
+
s = sub.add_parser("conventions", parents=[common()],
|
|
2258
2487
|
help="show the project's house-style naming patterns")
|
|
2488
|
+
s.add_argument("--init", action="store_true",
|
|
2489
|
+
help="write starter conventions.json and conventions.md "
|
|
2490
|
+
"into .pythia/ (never overwrites)")
|
|
2491
|
+
s.add_argument("--scan", action="store_true",
|
|
2492
|
+
help="read naming patterns off the live schema and "
|
|
2493
|
+
"propose them (needs a connection)")
|
|
2494
|
+
s.add_argument("--check", action="store_true",
|
|
2495
|
+
help="measure the configured patterns against the "
|
|
2496
|
+
"schema: coverage and the names that miss")
|
|
2259
2497
|
s = sub.add_parser("agent-user", parents=[common()],
|
|
2260
2498
|
help="SQL for a least-privilege proxy agent user; "
|
|
2261
2499
|
"--save adds it to connections.json")
|
|
@@ -2314,7 +2552,11 @@ def main(argv=None):
|
|
|
2314
2552
|
cwd = pathlib.Path.cwd()
|
|
2315
2553
|
cfg, root = find_config(cwd, os.environ)
|
|
2316
2554
|
ns.project_root = root if root is not None else cwd
|
|
2317
|
-
|
|
2555
|
+
# conventions reads the schema only for --scan and --check; listing what
|
|
2556
|
+
# is configured, or writing the template, must work with no database at all
|
|
2557
|
+
offline_conventions = ns.command == "conventions" and not (
|
|
2558
|
+
getattr(ns, "scan", False) or getattr(ns, "check", False))
|
|
2559
|
+
if (ns.command in NO_DB_COMMANDS or offline_conventions) and not (
|
|
2318
2560
|
ns.command == "journal" and getattr(ns, "action", "") == "restore"):
|
|
2319
2561
|
COMMANDS[ns.command](None, None, ns)
|
|
2320
2562
|
return
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythia-plsql
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
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
|
|
@@ -178,13 +178,13 @@ it audits every interaction in `DBTOOLS$MCP_LOG`. **Writes never do** — only
|
|
|
178
178
|
|
|
179
179
|
## Skills
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
Eight skills teach the agent the workflow — gates, not suggestions:
|
|
182
182
|
|
|
183
|
-
`pythia-setup` · `pythia-explore` · `pythia-impact` (
|
|
184
|
-
`pythia-write` (copy the codebase's conventions) · `pythia-apply` (the gate:
|
|
185
|
-
developer
|
|
186
|
-
`pythia-
|
|
187
|
-
workflow as a
|
|
183
|
+
`pythia-setup` · `pythia-explore` · `pythia-impact` (before any change) ·
|
|
184
|
+
`pythia-write` (copy the codebase's conventions) · `pythia-apply` (the gate:
|
|
185
|
+
the developer approves the preview in chat) · `pythia-review` (antipatterns) ·
|
|
186
|
+
`pythia-conventions` (adopt a house style, verified against real names) ·
|
|
187
|
+
`pythia-skill-author` (capture your team's workflow as a skill).
|
|
188
188
|
|
|
189
189
|
## Compatibility
|
|
190
190
|
|
|
@@ -6,6 +6,7 @@ queries/dependencies.sql
|
|
|
6
6
|
queries/impact.sql
|
|
7
7
|
queries/invalid-objects.sql
|
|
8
8
|
queries/name-occupants.sql
|
|
9
|
+
queries/object-names.sql
|
|
9
10
|
queries/object-source.sql
|
|
10
11
|
queries/plscope-enabled.sql
|
|
11
12
|
queries/plscope-statements.sql
|
|
@@ -21,6 +22,7 @@ scripts/pythia_plsql.egg-info/entry_points.txt
|
|
|
21
22
|
scripts/pythia_plsql.egg-info/requires.txt
|
|
22
23
|
scripts/pythia_plsql.egg-info/top_level.txt
|
|
23
24
|
skills/pythia-apply/SKILL.md
|
|
25
|
+
skills/pythia-conventions/SKILL.md
|
|
24
26
|
skills/pythia-explore/SKILL.md
|
|
25
27
|
skills/pythia-explore/reference/data-dictionary.md
|
|
26
28
|
skills/pythia-impact/SKILL.md
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pythia-conventions
|
|
3
|
+
description: Use when a project's house style already exists somewhere and needs to become something the tooling can check - the developer hands over a standards document, points at an older or company base schema whose naming should be adopted, says "our team does it this way", or asks why an apply preview warned about a name. Derives the patterns from the real schema, verifies them against it, and writes .pythia/conventions.json and conventions.md.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adopting a Project's Conventions
|
|
7
|
+
|
|
8
|
+
**Announce at start:** "Using pythia-conventions — I'll read the patterns off the schema first."
|
|
9
|
+
|
|
10
|
+
Conventions already exist in every mature schema. They are in the object
|
|
11
|
+
names, in a standards document, or in a senior developer's head. This turns
|
|
12
|
+
them into two files: patterns the tool checks on every write, and prose every
|
|
13
|
+
future agent session reads before writing.
|
|
14
|
+
|
|
15
|
+
## The Iron Law
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
NO PATTERN WRITTEN DOWN BEFORE THE SCHEMA HAS AGREED WITH IT
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
A pattern that does not match the objects already there is not a convention.
|
|
22
|
+
It is a guess that will warn on every apply until someone deletes it.
|
|
23
|
+
|
|
24
|
+
## The Workflow
|
|
25
|
+
|
|
26
|
+
1. **Read the shape off the database, not off your memory of it.**
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pythia conventions --scan
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
It tokenises every object name and proposes a pattern per type. Do this
|
|
33
|
+
even when a document exists — and never page through thousands of names
|
|
34
|
+
yourself: that is what the command is for, and your context is better
|
|
35
|
+
spent elsewhere.
|
|
36
|
+
|
|
37
|
+
2. **Read the developer's document, if there is one.** A base system's
|
|
38
|
+
standards, a wiki page, an older project's rules. Look for what a regex
|
|
39
|
+
cannot hold: parameter prefixes a calling layer depends on, a column every
|
|
40
|
+
query must filter by, where a transaction may commit, which date or money
|
|
41
|
+
representation is canonical. Those go in the prose half.
|
|
42
|
+
|
|
43
|
+
3. **Reconcile, and say so when the two disagree.** The document states
|
|
44
|
+
intent; the schema states fact. A rule in the document that the schema
|
|
45
|
+
contradicts is one of three things — a rule nobody follows, a rule for new
|
|
46
|
+
code only, or drift worth reporting. Ask the developer which; do not
|
|
47
|
+
silently pick.
|
|
48
|
+
|
|
49
|
+
4. **Write `.pythia/conventions.json`.** Start from the scan output, narrowed
|
|
50
|
+
by the document. `pythia conventions --init` writes a blank pair if you
|
|
51
|
+
want the skeleton first.
|
|
52
|
+
|
|
53
|
+
5. **Verify — this step is the point of the skill.**
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pythia conventions --check
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
It reports coverage per type and names what misses.
|
|
60
|
+
|
|
61
|
+
| Result | What it means |
|
|
62
|
+
|---|---|
|
|
63
|
+
| every name matches | the pattern is real; keep it |
|
|
64
|
+
| ~90%+ with a handful of misses | genuine exceptions — list them in step 6 |
|
|
65
|
+
| below 90% | **the pattern is wrong**, not the schema; widen or split it, then check again |
|
|
66
|
+
| nothing of that type | an untested rule; keep it only if new objects of that type are expected |
|
|
67
|
+
|
|
68
|
+
6. **Record the exceptions, with their reason.** Objects that break the rule
|
|
69
|
+
on purpose — ported names from a base platform, legacy entry points whose
|
|
70
|
+
names are part of a public surface. Say why renaming would cost more than
|
|
71
|
+
the warning. An unexplained exception gets "fixed" by the next person.
|
|
72
|
+
|
|
73
|
+
7. **Write `.pythia/conventions.md`.** For every rule, state **the cost of
|
|
74
|
+
breaking it**, not just the rule. "Parameter prefixes must be `b_`/`a_`"
|
|
75
|
+
is ignorable; "the calling layer binds by prefix, so a wrong one silently
|
|
76
|
+
unbinds the field" is not. Rules with named consequences get followed.
|
|
77
|
+
|
|
78
|
+
## Adopting from an older base the team already runs
|
|
79
|
+
|
|
80
|
+
Point the connection at that schema and run steps 1 and 2 there — a base
|
|
81
|
+
system with thousands of objects is the most reliable statement of a house
|
|
82
|
+
style that exists, far better than anyone's recollection of it. Then switch
|
|
83
|
+
the connection to the new project and run `--check`: coverage tells you how
|
|
84
|
+
much of the base style the new schema has actually inherited.
|
|
85
|
+
|
|
86
|
+
## Red Flags — STOP if you catch yourself thinking
|
|
87
|
+
|
|
88
|
+
| Thought | Reality |
|
|
89
|
+
|---------|---------|
|
|
90
|
+
| "I'll write the patterns from the document alone" | Documents describe intent; schemas record what happened. Scan first. |
|
|
91
|
+
| "80% coverage is good enough" | Every miss warns on every apply. Fix the pattern or record the exception. |
|
|
92
|
+
| "I'll read all the object names to work it out" | `--scan` does that without spending your context. |
|
|
93
|
+
| "The schema disagrees, so the document is wrong" | It may be a new-code-only rule. Ask. |
|
|
94
|
+
| "A rule per line is enough" | A rule without its cost is a rule people skip. |
|
|
95
|
+
|
|
96
|
+
## When NOT to use this skill
|
|
97
|
+
|
|
98
|
+
- Writing one object in an existing style → `pythia-write`, which reads these
|
|
99
|
+
files and uses `similar` for the details.
|
|
100
|
+
- Capturing a *workflow* with steps and gates → `pythia-skill-author`.
|
|
101
|
+
Naming and style belong here; procedures belong in a skill.
|
|
@@ -75,6 +75,29 @@ server: command `sql -mcp`. Example client config:
|
|
|
75
75
|
`V$SESSION.ACTION` the LLM's name; generated SQL carries an
|
|
76
76
|
`/* LLM in use */` comment.
|
|
77
77
|
|
|
78
|
+
## 4. What lives in `.pythia/`, and what creates it
|
|
79
|
+
|
|
80
|
+
Only `connections.json` is written by `install`. The rest appear when you ask
|
|
81
|
+
for them, so an empty `.pythia/` is a working one — nothing here is missing
|
|
82
|
+
until you want it.
|
|
83
|
+
|
|
84
|
+
| File | Created by | What it does |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `connections.json` | `pythia install` | Where to connect. Holds credentials; gitignored. |
|
|
87
|
+
| `journal/` | first write or snapshot | Every captured version, with a runnable rollback per entry. |
|
|
88
|
+
| `conventions.json` | `pythia conventions --init` | Naming patterns per object type. Every apply preview warns when a new name drifts. |
|
|
89
|
+
| `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. |
|
|
90
|
+
| `policy.json` | `pythia policy set <group> <value>` | Pins the write policy. Absent means the built-in defaults, which are already the safe ones. |
|
|
91
|
+
| `settings.json` | you, by hand | Optional switches, e.g. `{"auto_snapshot": false}`. |
|
|
92
|
+
|
|
93
|
+
**Capturing a team's house style is the highest-value optional step.** Run
|
|
94
|
+
`pythia conventions --init`, then replace the placeholder patterns with the
|
|
95
|
+
real ones — `pythia similar <A_TYPICAL_NAME>` shows what the schema already
|
|
96
|
+
does, which beats inventing a scheme. Fill in `conventions.md` with the rules
|
|
97
|
+
that carry a cost when broken, and say what the cost is; a rule with a named
|
|
98
|
+
consequence gets followed. Commit both files to the project repo so the whole
|
|
99
|
+
team and every agent session works from the same rules.
|
|
100
|
+
|
|
78
101
|
## Done when
|
|
79
102
|
|
|
80
103
|
- `pythia check` connects, shows the right schema, and prints **no**
|
|
@@ -9,7 +9,9 @@ description: Use when writing or modifying PL/SQL source - a procedure, function
|
|
|
9
9
|
|
|
10
10
|
**Before anything:** if the project has `.pythia/conventions.md`, read it —
|
|
11
11
|
house rules there outrank every generic pattern below, and
|
|
12
|
-
`pythia conventions` shows the naming patterns the apply preview will check.
|
|
12
|
+
`pythia conventions` shows the naming patterns the apply preview will check. If the project has none and the developer
|
|
13
|
+
describes a house style, offer `pythia conventions --init` — captured once,
|
|
14
|
+
it applies to every future session instead of being re-explained.
|
|
13
15
|
|
|
14
16
|
A codebase with thousands of procedures has already decided how procedures
|
|
15
17
|
look. Your job is to write one that a maintainer cannot tell from the
|
|
@@ -7,6 +7,7 @@ Run: python tests/test_install.py
|
|
|
7
7
|
import json
|
|
8
8
|
import os
|
|
9
9
|
import pathlib
|
|
10
|
+
import re
|
|
10
11
|
import sys
|
|
11
12
|
import tempfile
|
|
12
13
|
|
|
@@ -325,6 +326,28 @@ def test_long_path_is_flagged_before_it_silently_truncates():
|
|
|
325
326
|
assert "duplicate" in w.lower() # names the usual cause
|
|
326
327
|
|
|
327
328
|
|
|
329
|
+
def test_conventions_init_writes_both_halves_and_never_clobbers():
|
|
330
|
+
"""Telling a pip user to copy examples/conventions.example.json is no help:
|
|
331
|
+
the wheel does not ship an examples directory. The tool writes the pair."""
|
|
332
|
+
import json
|
|
333
|
+
with tempfile.TemporaryDirectory() as td:
|
|
334
|
+
made = pythia.scaffold_conventions(td)
|
|
335
|
+
d = pathlib.Path(td) / ".pythia"
|
|
336
|
+
assert sorted(p.name for p in made) == ["conventions.json",
|
|
337
|
+
"conventions.md"]
|
|
338
|
+
rules = json.loads((d / "conventions.json").read_text(encoding="utf-8"))
|
|
339
|
+
assert "naming" in rules and rules["naming"] # usable as written
|
|
340
|
+
for otype, pattern in rules["naming"].items():
|
|
341
|
+
re.compile(pattern) # every one valid
|
|
342
|
+
assert pythia.load_conventions(td) is not None # accepted by the loader
|
|
343
|
+
prose = (d / "conventions.md").read_text(encoding="utf-8")
|
|
344
|
+
assert "conventions.json" in prose # the halves reference
|
|
345
|
+
# a second run leaves existing files alone
|
|
346
|
+
(d / "conventions.json").write_text('{"naming": {}}', encoding="utf-8")
|
|
347
|
+
assert pythia.scaffold_conventions(td) == []
|
|
348
|
+
assert (d / "conventions.json").read_text(encoding="utf-8") == '{"naming": {}}'
|
|
349
|
+
|
|
350
|
+
|
|
328
351
|
def main():
|
|
329
352
|
failed = 0
|
|
330
353
|
for name, fn in sorted(globals().items()):
|
|
@@ -114,6 +114,70 @@ def test_plscope_message_distinguishes_disabled_from_missing():
|
|
|
114
114
|
assert "CALC_TAX" in missing
|
|
115
115
|
|
|
116
116
|
|
|
117
|
+
def test_pattern_coverage_measures_patterns_against_real_names():
|
|
118
|
+
"""A derived pattern is a guess until the schema agrees. Coverage turns
|
|
119
|
+
'I think procedures look like this' into a number, and names the
|
|
120
|
+
exceptions instead of leaving them to surface one apply at a time."""
|
|
121
|
+
conv = {"naming": {"PROCEDURE": "^P_[A-Z0-9_]+_(NH|LKE)$",
|
|
122
|
+
"FUNCTION": "^F_[A-Z0-9_]+$"}}
|
|
123
|
+
objects = [("PROCEDURE", "P_ORDER_NH"), ("PROCEDURE", "P_ORDER_LKE"),
|
|
124
|
+
("PROCEDURE", "LEGACY_THING"), ("FUNCTION", "F_TAX"),
|
|
125
|
+
("TABLE", "T_ORDER")] # no TABLE pattern: not reported
|
|
126
|
+
cov = pythia.pattern_coverage(conv, objects)
|
|
127
|
+
assert cov["PROCEDURE"]["matched"] == 2
|
|
128
|
+
assert cov["PROCEDURE"]["total"] == 3
|
|
129
|
+
assert cov["PROCEDURE"]["misses"] == ["LEGACY_THING"]
|
|
130
|
+
assert cov["FUNCTION"]["matched"] == 1 and cov["FUNCTION"]["misses"] == []
|
|
131
|
+
assert "TABLE" not in cov
|
|
132
|
+
|
|
133
|
+
# a pattern for a type the schema has none of is reported, not hidden:
|
|
134
|
+
# it is a rule nothing has tested yet
|
|
135
|
+
cov2 = pythia.pattern_coverage({"naming": {"TRIGGER": "^TRG_"}}, objects)
|
|
136
|
+
assert cov2["TRIGGER"]["total"] == 0
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_coverage_verdict_reads_the_numbers_for_you():
|
|
140
|
+
assert "every" in pythia.coverage_verdict(7, 7).lower()
|
|
141
|
+
poor = pythia.coverage_verdict(2, 10)
|
|
142
|
+
assert "20%" in poor and "derived" in poor.lower() # suspect the pattern
|
|
143
|
+
assert "nothing of that type" in pythia.coverage_verdict(0, 0).lower()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_propose_pattern_reads_the_shape_off_real_names():
|
|
147
|
+
"""The tool tokenizes thousands of names so the agent does not have to
|
|
148
|
+
read them. Dominant first and last tokens become alternations."""
|
|
149
|
+
names = ["PHT_NSD_LKE", "PHT_NSD_CT", "PHT_NSD_NH",
|
|
150
|
+
"PBH_HD_LKE", "PBH_HD_CT", "PBH_HD_NH"]
|
|
151
|
+
p = pythia.propose_pattern(names)
|
|
152
|
+
import re
|
|
153
|
+
assert all(re.match(p, n) for n in names), p
|
|
154
|
+
assert "PBH" in p and "PHT" in p and "LKE" in p # both ends captured
|
|
155
|
+
|
|
156
|
+
# no dominant suffix: leave the tail open rather than invent one
|
|
157
|
+
tables = ["HT_NSD", "HT_QUYEN", "HT_VAI_TRO", "HT_MA_BENH_VIEN"]
|
|
158
|
+
p2 = pythia.propose_pattern(tables)
|
|
159
|
+
assert all(re.match(p2, n) for n in tables), p2
|
|
160
|
+
assert p2.startswith("^HT_") and p2.endswith("$")
|
|
161
|
+
|
|
162
|
+
# nothing in common: say so with None rather than a pattern matching all
|
|
163
|
+
assert pythia.propose_pattern(["ALPHA", "B_TWO", "ZZZ_9"]) is None
|
|
164
|
+
assert pythia.propose_pattern([]) is None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_scan_proposal_covers_what_it_proposes():
|
|
168
|
+
"""Whatever it proposes must match the names it was derived from —
|
|
169
|
+
otherwise the very first check would contradict the scan."""
|
|
170
|
+
objects = [("PROCEDURE", "PHT_A_LKE"), ("PROCEDURE", "PHT_B_CT"),
|
|
171
|
+
("PROCEDURE", "PHT_C_NH"),
|
|
172
|
+
("TABLE", "HT_A"), ("TABLE", "HT_B"), ("TABLE", "HT_C"),
|
|
173
|
+
("SEQUENCE", "SEQ_ONE")] # too few to infer: left alone
|
|
174
|
+
conv = pythia.scan_conventions(objects)
|
|
175
|
+
cov = pythia.pattern_coverage(conv, objects)
|
|
176
|
+
for otype, stats in cov.items():
|
|
177
|
+
assert stats["misses"] == [], (otype, stats)
|
|
178
|
+
assert set(conv["naming"]) == {"PROCEDURE", "TABLE"}
|
|
179
|
+
|
|
180
|
+
|
|
117
181
|
def main():
|
|
118
182
|
failed = 0
|
|
119
183
|
for name, fn in sorted(globals().items()):
|
|
@@ -12,7 +12,7 @@ SKILLS = ROOT / "skills"
|
|
|
12
12
|
|
|
13
13
|
EXPECTED = {"pythia-setup", "pythia-explore", "pythia-impact",
|
|
14
14
|
"pythia-write", "pythia-apply", "pythia-review",
|
|
15
|
-
"pythia-skill-author"}
|
|
15
|
+
"pythia-skill-author", "pythia-conventions"}
|
|
16
16
|
|
|
17
17
|
# spec: SKILL.md under 150 lines, detail pushed to reference/
|
|
18
18
|
MAX_LINES = 150
|
|
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.9 → pythia_plsql-0.5.0}/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.9 → pythia_plsql-0.5.0}/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
|