pythia-plsql 0.4.10__tar.gz → 0.6.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.10/scripts/pythia_plsql.egg-info → pythia_plsql-0.6.0}/PKG-INFO +72 -26
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/README.md +71 -25
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/pyproject.toml +1 -1
- pythia_plsql-0.6.0/queries/object-names.sql +11 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/scripts/pythia.py +239 -31
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0/scripts/pythia_plsql.egg-info}/PKG-INFO +72 -26
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/scripts/pythia_plsql.egg-info/SOURCES.txt +2 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-apply/SKILL.md +2 -0
- pythia_plsql-0.6.0/skills/pythia-conventions/SKILL.md +103 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-explore/SKILL.md +2 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-impact/SKILL.md +2 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-review/SKILL.md +2 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-setup/SKILL.md +2 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-skill-author/SKILL.md +2 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-write/SKILL.md +2 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/tests/test_install.py +13 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/tests/test_phase2.py +64 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/tests/test_phase5.py +29 -6
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/LICENSE +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/compile-errors.sql +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/dependencies.sql +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/impact.sql +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/invalid-objects.sql +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/name-occupants.sql +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/object-source.sql +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/plscope-enabled.sql +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/plscope-statements.sql +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/plscope-usages.sql +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/session-privileges.sql +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/similar-candidates.sql +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/source.sql +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/setup.cfg +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-explore/reference/data-dictionary.md +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-review/reference/antipatterns.md +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-write/reference/patterns.md +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/tests/test_phase1.py +0 -0
- {pythia_plsql-0.4.10 → pythia_plsql-0.6.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.6.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
|
|
@@ -26,10 +26,16 @@ Dynamic: license-file
|
|
|
26
26
|
[](LICENSE)
|
|
27
27
|

|
|
28
28
|
|
|
29
|
-
An Agent Skills + CLI kit for developing PL/SQL on Oracle Database with AI
|
|
30
|
-
agents (Claude Code, Codex, Cursor — any of the 76 agents `npx skills`
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
An Agent Skills + CLI kit for developing PL/SQL on Oracle Database with AI
|
|
30
|
+
coding agents (Claude Code, Codex, Cursor — any of the 76 agents `npx skills`
|
|
31
|
+
supports).
|
|
32
|
+
|
|
33
|
+
pythia is a **harness**: a book of working rules an agent studies and follows
|
|
34
|
+
when it sits next to a developer. Not a chatbot, not an autopilot — a
|
|
35
|
+
disciplined assistant with three properties a good one has: **knowledge** (it
|
|
36
|
+
asks the live schema, never its memory of one), **judgment** (it measures
|
|
37
|
+
before it proposes), and **obedience to rules it can recite** (gates it will
|
|
38
|
+
quote back to you rather than quietly skip).
|
|
33
39
|
|
|
34
40
|
## Why ask the database instead of reading dumps
|
|
35
41
|
|
|
@@ -47,23 +53,63 @@ Code that "reads fine" against the dump references types and packages the dump n
|
|
|
47
53
|
heard of. Every pythia command asks the live data dictionary instead — and every
|
|
48
54
|
truncated output says so, so an agent never mistakes a partial answer for a full one.
|
|
49
55
|
|
|
50
|
-
##
|
|
56
|
+
## The operating model: Learn → Ask → Do
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
skills/ teach the agent when to ask, when to stop, when to ask YOU
|
|
56
|
-
│
|
|
57
|
-
pythia CLI — expert queries, impact analysis, the six-step write path
|
|
58
|
-
│
|
|
59
|
-
Oracle data dictionary: ALL_SOURCE, ALL_DEPENDENCIES, ALL_ERRORS, PL/Scope
|
|
60
|
-
```
|
|
58
|
+
Give the agent a problem — "add a column and every procedure that maintains
|
|
59
|
+
it", "why does this report double rows", "port this fix" — and the kit walks
|
|
60
|
+
it through the same three movements a careful senior developer makes.
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
### 1 · Learn — understand before proposing
|
|
63
|
+
|
|
64
|
+
The agent studies four things, in order, with tools instead of guesses:
|
|
65
|
+
|
|
66
|
+
| It learns | How | Instead of |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| the problem's real shape | `deps`, `impact`, `plscope` — the exact dependency graph and usage sites | skimming code and hoping |
|
|
69
|
+
| the schema's ground truth | `src`, `cols`, `args`, `ddl`, `errors` against the live database | trusting a dump that drifted |
|
|
70
|
+
| the house style | `.pythia/conventions.md` + `conventions --scan/--check` — rules measured against real names | inventing a style per session |
|
|
71
|
+
| how this codebase already solves it | `similar` — the neighbours to imitate | writing the first thing that compiles |
|
|
72
|
+
|
|
73
|
+
Nothing in this phase writes. Reading is free, so the bar is: **no proposal
|
|
74
|
+
before the blast radius is known** (`pythia-impact`'s iron law) and **no line
|
|
75
|
+
written before the neighbours have been read** (`pythia-write`'s).
|
|
76
|
+
|
|
77
|
+
### 2 · Ask — the questions are part of the method, not an interruption
|
|
78
|
+
|
|
79
|
+
The kit makes the agent stop at exactly the moments where a human's judgment
|
|
80
|
+
is the missing input, and forbids it to guess past them:
|
|
81
|
+
|
|
82
|
+
- **Before any write**: the full preview — diff, dependents, warnings — is
|
|
83
|
+
relayed verbatim, and the agent waits for a real yes. A compliment is not a
|
|
84
|
+
yes. Silence is not a yes.
|
|
85
|
+
- **When the blast radius is large**: ten or more dependents, or anything
|
|
86
|
+
cross-schema, goes to the developer *before code is written*, not after.
|
|
87
|
+
- **When sources of truth disagree**: a standards document says one thing,
|
|
88
|
+
the schema does another — that gap is a question ("rule nobody follows,
|
|
89
|
+
new-code-only, or drift?"), never a silent pick.
|
|
90
|
+
- **When something breaks**: exit 3 means *written but broken*. The agent
|
|
91
|
+
reports it exactly so, with the ready rollback — reporting success here is
|
|
92
|
+
the one sin the whole kit is built to prevent.
|
|
93
|
+
- **When policy refuses**: the refusal is relayed as information, not routed
|
|
94
|
+
around.
|
|
95
|
+
|
|
96
|
+
### 3 · Do — act inside a pipeline that cannot lie
|
|
97
|
+
|
|
98
|
+
Only after Learn and Ask does anything touch the database, and then only
|
|
99
|
+
through one door: **snapshot → impact → preview → token → apply → verify →
|
|
100
|
+
report**. DDL self-commits in Oracle, so the snapshot is the only real undo —
|
|
101
|
+
it runs first and no flag disables it. A content-bound token guarantees what
|
|
102
|
+
lands is byte-for-byte what was approved. And the CLI enforces the gates
|
|
103
|
+
itself: a headless agent cannot `--yes` its own writes or loosen policy —
|
|
104
|
+
that takes a human at a real terminal.
|
|
105
|
+
|
|
106
|
+
The same discipline holds when the *developer* does the work: `src` and
|
|
107
|
+
`impact` silently snapshot what they read, so even a change made by hand in
|
|
108
|
+
SQL Developer has a rollback file waiting (`history` lists them, drift is
|
|
109
|
+
reported when source moved with no apply behind it).
|
|
110
|
+
|
|
111
|
+
**Học – Hỏi – Làm** — Learn, Ask, Do. If the agent cannot show which phase it
|
|
112
|
+
is in, it is doing none of them.
|
|
67
113
|
|
|
68
114
|
## Install
|
|
69
115
|
|
|
@@ -178,13 +224,13 @@ it audits every interaction in `DBTOOLS$MCP_LOG`. **Writes never do** — only
|
|
|
178
224
|
|
|
179
225
|
## Skills
|
|
180
226
|
|
|
181
|
-
|
|
227
|
+
Eight skills teach the agent the workflow — gates, not suggestions:
|
|
182
228
|
|
|
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
|
|
229
|
+
`pythia-setup` · `pythia-explore` · `pythia-impact` (before any change) ·
|
|
230
|
+
`pythia-write` (copy the codebase's conventions) · `pythia-apply` (the gate:
|
|
231
|
+
the developer approves the preview in chat) · `pythia-review` (antipatterns) ·
|
|
232
|
+
`pythia-conventions` (adopt a house style, verified against real names) ·
|
|
233
|
+
`pythia-skill-author` (capture your team's workflow as a skill).
|
|
188
234
|
|
|
189
235
|
## Compatibility
|
|
190
236
|
|
|
@@ -12,10 +12,16 @@
|
|
|
12
12
|
[](LICENSE)
|
|
13
13
|

|
|
14
14
|
|
|
15
|
-
An Agent Skills + CLI kit for developing PL/SQL on Oracle Database with AI
|
|
16
|
-
agents (Claude Code, Codex, Cursor — any of the 76 agents `npx skills`
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
An Agent Skills + CLI kit for developing PL/SQL on Oracle Database with AI
|
|
16
|
+
coding agents (Claude Code, Codex, Cursor — any of the 76 agents `npx skills`
|
|
17
|
+
supports).
|
|
18
|
+
|
|
19
|
+
pythia is a **harness**: a book of working rules an agent studies and follows
|
|
20
|
+
when it sits next to a developer. Not a chatbot, not an autopilot — a
|
|
21
|
+
disciplined assistant with three properties a good one has: **knowledge** (it
|
|
22
|
+
asks the live schema, never its memory of one), **judgment** (it measures
|
|
23
|
+
before it proposes), and **obedience to rules it can recite** (gates it will
|
|
24
|
+
quote back to you rather than quietly skip).
|
|
19
25
|
|
|
20
26
|
## Why ask the database instead of reading dumps
|
|
21
27
|
|
|
@@ -33,23 +39,63 @@ Code that "reads fine" against the dump references types and packages the dump n
|
|
|
33
39
|
heard of. Every pythia command asks the live data dictionary instead — and every
|
|
34
40
|
truncated output says so, so an agent never mistakes a partial answer for a full one.
|
|
35
41
|
|
|
36
|
-
##
|
|
42
|
+
## The operating model: Learn → Ask → Do
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
skills/ teach the agent when to ask, when to stop, when to ask YOU
|
|
42
|
-
│
|
|
43
|
-
pythia CLI — expert queries, impact analysis, the six-step write path
|
|
44
|
-
│
|
|
45
|
-
Oracle data dictionary: ALL_SOURCE, ALL_DEPENDENCIES, ALL_ERRORS, PL/Scope
|
|
46
|
-
```
|
|
44
|
+
Give the agent a problem — "add a column and every procedure that maintains
|
|
45
|
+
it", "why does this report double rows", "port this fix" — and the kit walks
|
|
46
|
+
it through the same three movements a careful senior developer makes.
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
### 1 · Learn — understand before proposing
|
|
49
|
+
|
|
50
|
+
The agent studies four things, in order, with tools instead of guesses:
|
|
51
|
+
|
|
52
|
+
| It learns | How | Instead of |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| the problem's real shape | `deps`, `impact`, `plscope` — the exact dependency graph and usage sites | skimming code and hoping |
|
|
55
|
+
| the schema's ground truth | `src`, `cols`, `args`, `ddl`, `errors` against the live database | trusting a dump that drifted |
|
|
56
|
+
| the house style | `.pythia/conventions.md` + `conventions --scan/--check` — rules measured against real names | inventing a style per session |
|
|
57
|
+
| how this codebase already solves it | `similar` — the neighbours to imitate | writing the first thing that compiles |
|
|
58
|
+
|
|
59
|
+
Nothing in this phase writes. Reading is free, so the bar is: **no proposal
|
|
60
|
+
before the blast radius is known** (`pythia-impact`'s iron law) and **no line
|
|
61
|
+
written before the neighbours have been read** (`pythia-write`'s).
|
|
62
|
+
|
|
63
|
+
### 2 · Ask — the questions are part of the method, not an interruption
|
|
64
|
+
|
|
65
|
+
The kit makes the agent stop at exactly the moments where a human's judgment
|
|
66
|
+
is the missing input, and forbids it to guess past them:
|
|
67
|
+
|
|
68
|
+
- **Before any write**: the full preview — diff, dependents, warnings — is
|
|
69
|
+
relayed verbatim, and the agent waits for a real yes. A compliment is not a
|
|
70
|
+
yes. Silence is not a yes.
|
|
71
|
+
- **When the blast radius is large**: ten or more dependents, or anything
|
|
72
|
+
cross-schema, goes to the developer *before code is written*, not after.
|
|
73
|
+
- **When sources of truth disagree**: a standards document says one thing,
|
|
74
|
+
the schema does another — that gap is a question ("rule nobody follows,
|
|
75
|
+
new-code-only, or drift?"), never a silent pick.
|
|
76
|
+
- **When something breaks**: exit 3 means *written but broken*. The agent
|
|
77
|
+
reports it exactly so, with the ready rollback — reporting success here is
|
|
78
|
+
the one sin the whole kit is built to prevent.
|
|
79
|
+
- **When policy refuses**: the refusal is relayed as information, not routed
|
|
80
|
+
around.
|
|
81
|
+
|
|
82
|
+
### 3 · Do — act inside a pipeline that cannot lie
|
|
83
|
+
|
|
84
|
+
Only after Learn and Ask does anything touch the database, and then only
|
|
85
|
+
through one door: **snapshot → impact → preview → token → apply → verify →
|
|
86
|
+
report**. DDL self-commits in Oracle, so the snapshot is the only real undo —
|
|
87
|
+
it runs first and no flag disables it. A content-bound token guarantees what
|
|
88
|
+
lands is byte-for-byte what was approved. And the CLI enforces the gates
|
|
89
|
+
itself: a headless agent cannot `--yes` its own writes or loosen policy —
|
|
90
|
+
that takes a human at a real terminal.
|
|
91
|
+
|
|
92
|
+
The same discipline holds when the *developer* does the work: `src` and
|
|
93
|
+
`impact` silently snapshot what they read, so even a change made by hand in
|
|
94
|
+
SQL Developer has a rollback file waiting (`history` lists them, drift is
|
|
95
|
+
reported when source moved with no apply behind it).
|
|
96
|
+
|
|
97
|
+
**Học – Hỏi – Làm** — Learn, Ask, Do. If the agent cannot show which phase it
|
|
98
|
+
is in, it is doing none of them.
|
|
53
99
|
|
|
54
100
|
## Install
|
|
55
101
|
|
|
@@ -164,13 +210,13 @@ it audits every interaction in `DBTOOLS$MCP_LOG`. **Writes never do** — only
|
|
|
164
210
|
|
|
165
211
|
## Skills
|
|
166
212
|
|
|
167
|
-
|
|
213
|
+
Eight skills teach the agent the workflow — gates, not suggestions:
|
|
168
214
|
|
|
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
|
|
215
|
+
`pythia-setup` · `pythia-explore` · `pythia-impact` (before any change) ·
|
|
216
|
+
`pythia-write` (copy the codebase's conventions) · `pythia-apply` (the gate:
|
|
217
|
+
the developer approves the preview in chat) · `pythia-review` (antipatterns) ·
|
|
218
|
+
`pythia-conventions` (adopt a house style, verified against real names) ·
|
|
219
|
+
`pythia-skill-author` (capture your team's workflow as a skill).
|
|
174
220
|
|
|
175
221
|
## Compatibility
|
|
176
222
|
|
|
@@ -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.6.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,24 @@ 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
|
+
|
|
1335
1354
|
CONVENTIONS_TEMPLATE = """{
|
|
1336
1355
|
"naming": {
|
|
1337
1356
|
"TABLE": "^T_[A-Z0-9_]+$",
|
|
@@ -1383,6 +1402,109 @@ than the warning they produce on every apply.
|
|
|
1383
1402
|
"""
|
|
1384
1403
|
|
|
1385
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
|
+
|
|
1386
1508
|
def scaffold_conventions(root):
|
|
1387
1509
|
"""Write the conventions pair, skipping anything that already exists.
|
|
1388
1510
|
Returns the paths actually created.
|
|
@@ -1861,6 +1983,65 @@ def cmd_journal(conn, schema, ns):
|
|
|
1861
1983
|
sys.exit("unreachable: restore is dispatched with a connection in main")
|
|
1862
1984
|
|
|
1863
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
|
+
|
|
1991
|
+
OPERATING_GUIDE = """\
|
|
1992
|
+
THE OPERATING MODEL — Learn, Ask, Do (Hoc - Hoi - Lam)
|
|
1993
|
+
|
|
1994
|
+
pythia is a harness: a book of working rules for an AI agent sitting next to
|
|
1995
|
+
a developer. Every task moves through the same three movements. If you cannot
|
|
1996
|
+
say which movement you are in, you are in none of them.
|
|
1997
|
+
|
|
1998
|
+
=== 1. LEARN — understand before proposing ======================
|
|
1999
|
+
Nothing here writes. Reading is free; guessing is not.
|
|
2000
|
+
|
|
2001
|
+
the problem's shape deps · impact · plscope the exact graph, not a skim
|
|
2002
|
+
the schema's truth src · args · cols · ddl · errors · invalid · check · ls · grep · sql
|
|
2003
|
+
the house style conventions (--scan / --check) · the project's conventions.md
|
|
2004
|
+
how it is done here similar · history neighbours to imitate, versions that exist
|
|
2005
|
+
|
|
2006
|
+
Iron laws: no proposal before the blast radius is known; no line written
|
|
2007
|
+
before the neighbours have been read.
|
|
2008
|
+
|
|
2009
|
+
=== 2. ASK — the questions are the method =======================
|
|
2010
|
+
Stop at exactly these moments; a guess past any of them is a defect.
|
|
2011
|
+
|
|
2012
|
+
before any write relay the full preview (diff, dependents, warnings)
|
|
2013
|
+
verbatim, then wait. A compliment is not a yes.
|
|
2014
|
+
blast radius >= 10 or anything cross-schema: show the developer the
|
|
2015
|
+
list BEFORE writing code.
|
|
2016
|
+
truths disagree document says one thing, schema another - ask which:
|
|
2017
|
+
rule nobody follows, new-code-only, or drift.
|
|
2018
|
+
policy refuses relay the refusal (policy shows the rules). Never
|
|
2019
|
+
route around it.
|
|
2020
|
+
it broke exit 3 = written but broken. Say exactly that, with
|
|
2021
|
+
the rollback line. journal (show/diff) is your evidence.
|
|
2022
|
+
|
|
2023
|
+
=== 3. DO — act inside a pipeline that cannot lie ===============
|
|
2024
|
+
One door for writes: snapshot -> impact -> preview -> token -> apply ->
|
|
2025
|
+
verify -> report.
|
|
2026
|
+
|
|
2027
|
+
apply the six-step write; --confirm binds to the preview
|
|
2028
|
+
journal restore undo, through the same six steps
|
|
2029
|
+
unistr exact non-ASCII literals for what you are writing
|
|
2030
|
+
install · agent-user · guide setting the harness itself up
|
|
2031
|
+
|
|
2032
|
+
The CLI enforces the gates: headless --yes is refused, policy cannot be
|
|
2033
|
+
loosened without a human at a terminal, the snapshot cannot be switched off.
|
|
2034
|
+
|
|
2035
|
+
Skills carry the full method (pythia-explore, -impact, -conventions, -write,
|
|
2036
|
+
-apply, -review, -setup, -skill-author). No skill support on this platform?
|
|
2037
|
+
This page is the contract; follow it as written.
|
|
2038
|
+
"""
|
|
2039
|
+
|
|
2040
|
+
|
|
2041
|
+
def cmd_guide(conn, schema, ns):
|
|
2042
|
+
print(OPERATING_GUIDE)
|
|
2043
|
+
|
|
2044
|
+
|
|
1864
2045
|
def cmd_conventions(conn, schema, ns):
|
|
1865
2046
|
root = ns.project_root
|
|
1866
2047
|
if getattr(ns, "init", False):
|
|
@@ -1872,19 +2053,54 @@ def cmd_conventions(conn, schema, ns):
|
|
|
1872
2053
|
f"{pathlib.Path(root) / CONFIG_DIR} — left untouched.")
|
|
1873
2054
|
else:
|
|
1874
2055
|
print(NL + "Edit the patterns to match this schema — "
|
|
1875
|
-
f"`{invocation()}
|
|
1876
|
-
"it already does." + NL + "Every apply preview then "
|
|
1877
|
-
"warns when a name drifts from them.")
|
|
2056
|
+
f"`{invocation()} conventions --scan` reads them off it.")
|
|
1878
2057
|
return
|
|
2058
|
+
|
|
2059
|
+
if getattr(ns, "scan", False):
|
|
2060
|
+
proposed = scan_conventions(_schema_objects(conn, schema))
|
|
2061
|
+
if ns.json:
|
|
2062
|
+
print(json.dumps(proposed, indent=2))
|
|
2063
|
+
return
|
|
2064
|
+
print(f"Patterns read off {schema}. Review against your own standards, "
|
|
2065
|
+
"then save as" + NL + f"{CONFIG_DIR}/conventions.json:" + NL)
|
|
2066
|
+
print(json.dumps(proposed, indent=2))
|
|
2067
|
+
if not proposed["naming"]:
|
|
2068
|
+
print(NL + "Nothing proposed: too few objects, or names with no "
|
|
2069
|
+
"shared shape.")
|
|
2070
|
+
return
|
|
2071
|
+
|
|
2072
|
+
if getattr(ns, "check", False):
|
|
2073
|
+
conv = load_conventions(root)
|
|
2074
|
+
if not conv:
|
|
2075
|
+
sys.exit(f"No conventions to check. {invocation()} conventions "
|
|
2076
|
+
"--scan proposes some from this schema.")
|
|
2077
|
+
cov = pattern_coverage(conv, _schema_objects(conn, schema))
|
|
2078
|
+
if ns.json:
|
|
2079
|
+
print(json.dumps(cov, indent=2))
|
|
2080
|
+
return
|
|
2081
|
+
worst = 0
|
|
2082
|
+
for otype, s in sorted(cov.items()):
|
|
2083
|
+
print(f"{otype:<14} {s['matched']}/{s['total']} "
|
|
2084
|
+
f"{coverage_verdict(s['matched'], s['total'])}")
|
|
2085
|
+
if s["misses"]:
|
|
2086
|
+
shown, more = s["misses"][:8], len(s["misses"]) - 8
|
|
2087
|
+
print(" not matching: " + ", ".join(shown)
|
|
2088
|
+
+ (f", +{more} more" if more > 0 else ""))
|
|
2089
|
+
worst = max(worst, len(s["misses"]))
|
|
2090
|
+
if worst:
|
|
2091
|
+
print(NL + "Every name above warns on `apply`. Widen the pattern "
|
|
2092
|
+
"if it is the rule that is wrong," + NL + "or record them in "
|
|
2093
|
+
"conventions.md as deliberate exceptions.")
|
|
2094
|
+
return
|
|
2095
|
+
|
|
1879
2096
|
conv = load_conventions(root)
|
|
1880
2097
|
if ns.json:
|
|
1881
2098
|
print(json.dumps(conv or {}))
|
|
1882
2099
|
return
|
|
1883
2100
|
if not conv:
|
|
1884
2101
|
print("No project conventions configured.")
|
|
1885
|
-
print(f"
|
|
1886
|
-
print(" conventions
|
|
1887
|
-
print(" conventions.md the prose rules your agents read first")
|
|
2102
|
+
print(f" {invocation()} conventions --scan read patterns off this schema")
|
|
2103
|
+
print(f" {invocation()} conventions --init start from a blank template")
|
|
1888
2104
|
return
|
|
1889
2105
|
print("Naming patterns — apply previews warn when a name drifts:")
|
|
1890
2106
|
for otype, pattern in conv.get("naming", {}).items():
|
|
@@ -1892,28 +2108,7 @@ def cmd_conventions(conn, schema, ns):
|
|
|
1892
2108
|
md = pathlib.Path(root) / CONFIG_DIR / "conventions.md"
|
|
1893
2109
|
if md.is_file():
|
|
1894
2110
|
print(NL + f"Prose rules for agents: {md}")
|
|
1895
|
-
|
|
1896
|
-
print(NL + f"No conventions.md yet — {invocation()} conventions "
|
|
1897
|
-
"--init writes one.")
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
AGENT_USER_SQL = """\
|
|
1901
|
-
-- Least-privilege agent credential for schema {owner} — run as a DBA.
|
|
1902
|
-
-- Proxy authentication: the agent logs in with its OWN password but works
|
|
1903
|
-
-- inside {owner}; it never learns the owner password, owns nothing, and
|
|
1904
|
-
-- revocation is one statement. Deliberately absent: DBA, RESOURCE, ANY
|
|
1905
|
-
-- privileges, utility grants — the agent needs none of them to develop
|
|
1906
|
-
-- PL/SQL, and every extra grant widens the blast radius.
|
|
1907
|
-
|
|
1908
|
-
{create_line}
|
|
1909
|
-
GRANT CREATE SESSION TO {agent};
|
|
1910
|
-
ALTER USER {owner} GRANT CONNECT THROUGH {agent};
|
|
1911
|
-
|
|
1912
|
-
-- Cut the agent off later (owner untouched):
|
|
1913
|
-
-- ALTER USER {owner} REVOKE CONNECT THROUGH {agent};
|
|
1914
|
-
-- Fresh owner schema instead? See examples/agent-user-setup.example.sql.
|
|
1915
|
-
"""
|
|
1916
|
-
|
|
2111
|
+
print(f"{NL}Measure them against the schema: {invocation()} conventions --check")
|
|
1917
2112
|
|
|
1918
2113
|
def agent_user_sql(owner, agent, password, exists):
|
|
1919
2114
|
"""exists True: the user is already there — CREATE would be ORA-01920,
|
|
@@ -2240,11 +2435,11 @@ COMMANDS = {"check": cmd_check, "ls": cmd_ls, "src": cmd_src, "args": cmd_args,
|
|
|
2240
2435
|
"invalid": cmd_invalid, "errors": cmd_errors, "deps": cmd_deps,
|
|
2241
2436
|
"impact": cmd_impact, "similar": cmd_similar, "plscope": cmd_plscope,
|
|
2242
2437
|
"policy": cmd_policy, "journal": cmd_journal, "apply": cmd_apply,
|
|
2243
|
-
"conventions": cmd_conventions, "install": cmd_install,
|
|
2438
|
+
"conventions": cmd_conventions, "guide": cmd_guide, "install": cmd_install,
|
|
2244
2439
|
"unistr": cmd_unistr, "agent-user": cmd_agent_user,
|
|
2245
2440
|
"history": cmd_history}
|
|
2246
2441
|
|
|
2247
|
-
NO_DB_COMMANDS = {"policy", "journal", "
|
|
2442
|
+
NO_DB_COMMANDS = {"policy", "journal", "install", "unistr", "guide",
|
|
2248
2443
|
"history"}
|
|
2249
2444
|
|
|
2250
2445
|
|
|
@@ -2342,11 +2537,20 @@ def build_parser():
|
|
|
2342
2537
|
help="apply without stopping; the full preview still prints and journals")
|
|
2343
2538
|
s.add_argument("--depth", type=int, default=3,
|
|
2344
2539
|
help="impact depth for the preview (default 3)")
|
|
2540
|
+
sub.add_parser("guide", parents=[common()],
|
|
2541
|
+
help="the operating model: Learn, Ask, Do — the whole "
|
|
2542
|
+
"harness on one page, no database needed")
|
|
2345
2543
|
s = sub.add_parser("conventions", parents=[common()],
|
|
2346
2544
|
help="show the project's house-style naming patterns")
|
|
2347
2545
|
s.add_argument("--init", action="store_true",
|
|
2348
2546
|
help="write starter conventions.json and conventions.md "
|
|
2349
2547
|
"into .pythia/ (never overwrites)")
|
|
2548
|
+
s.add_argument("--scan", action="store_true",
|
|
2549
|
+
help="read naming patterns off the live schema and "
|
|
2550
|
+
"propose them (needs a connection)")
|
|
2551
|
+
s.add_argument("--check", action="store_true",
|
|
2552
|
+
help="measure the configured patterns against the "
|
|
2553
|
+
"schema: coverage and the names that miss")
|
|
2350
2554
|
s = sub.add_parser("agent-user", parents=[common()],
|
|
2351
2555
|
help="SQL for a least-privilege proxy agent user; "
|
|
2352
2556
|
"--save adds it to connections.json")
|
|
@@ -2405,7 +2609,11 @@ def main(argv=None):
|
|
|
2405
2609
|
cwd = pathlib.Path.cwd()
|
|
2406
2610
|
cfg, root = find_config(cwd, os.environ)
|
|
2407
2611
|
ns.project_root = root if root is not None else cwd
|
|
2408
|
-
|
|
2612
|
+
# conventions reads the schema only for --scan and --check; listing what
|
|
2613
|
+
# is configured, or writing the template, must work with no database at all
|
|
2614
|
+
offline_conventions = ns.command == "conventions" and not (
|
|
2615
|
+
getattr(ns, "scan", False) or getattr(ns, "check", False))
|
|
2616
|
+
if (ns.command in NO_DB_COMMANDS or offline_conventions) and not (
|
|
2409
2617
|
ns.command == "journal" and getattr(ns, "action", "") == "restore"):
|
|
2410
2618
|
COMMANDS[ns.command](None, None, ns)
|
|
2411
2619
|
return
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythia-plsql
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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
|
|
@@ -26,10 +26,16 @@ Dynamic: license-file
|
|
|
26
26
|
[](LICENSE)
|
|
27
27
|

|
|
28
28
|
|
|
29
|
-
An Agent Skills + CLI kit for developing PL/SQL on Oracle Database with AI
|
|
30
|
-
agents (Claude Code, Codex, Cursor — any of the 76 agents `npx skills`
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
An Agent Skills + CLI kit for developing PL/SQL on Oracle Database with AI
|
|
30
|
+
coding agents (Claude Code, Codex, Cursor — any of the 76 agents `npx skills`
|
|
31
|
+
supports).
|
|
32
|
+
|
|
33
|
+
pythia is a **harness**: a book of working rules an agent studies and follows
|
|
34
|
+
when it sits next to a developer. Not a chatbot, not an autopilot — a
|
|
35
|
+
disciplined assistant with three properties a good one has: **knowledge** (it
|
|
36
|
+
asks the live schema, never its memory of one), **judgment** (it measures
|
|
37
|
+
before it proposes), and **obedience to rules it can recite** (gates it will
|
|
38
|
+
quote back to you rather than quietly skip).
|
|
33
39
|
|
|
34
40
|
## Why ask the database instead of reading dumps
|
|
35
41
|
|
|
@@ -47,23 +53,63 @@ Code that "reads fine" against the dump references types and packages the dump n
|
|
|
47
53
|
heard of. Every pythia command asks the live data dictionary instead — and every
|
|
48
54
|
truncated output says so, so an agent never mistakes a partial answer for a full one.
|
|
49
55
|
|
|
50
|
-
##
|
|
56
|
+
## The operating model: Learn → Ask → Do
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
skills/ teach the agent when to ask, when to stop, when to ask YOU
|
|
56
|
-
│
|
|
57
|
-
pythia CLI — expert queries, impact analysis, the six-step write path
|
|
58
|
-
│
|
|
59
|
-
Oracle data dictionary: ALL_SOURCE, ALL_DEPENDENCIES, ALL_ERRORS, PL/Scope
|
|
60
|
-
```
|
|
58
|
+
Give the agent a problem — "add a column and every procedure that maintains
|
|
59
|
+
it", "why does this report double rows", "port this fix" — and the kit walks
|
|
60
|
+
it through the same three movements a careful senior developer makes.
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
### 1 · Learn — understand before proposing
|
|
63
|
+
|
|
64
|
+
The agent studies four things, in order, with tools instead of guesses:
|
|
65
|
+
|
|
66
|
+
| It learns | How | Instead of |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| the problem's real shape | `deps`, `impact`, `plscope` — the exact dependency graph and usage sites | skimming code and hoping |
|
|
69
|
+
| the schema's ground truth | `src`, `cols`, `args`, `ddl`, `errors` against the live database | trusting a dump that drifted |
|
|
70
|
+
| the house style | `.pythia/conventions.md` + `conventions --scan/--check` — rules measured against real names | inventing a style per session |
|
|
71
|
+
| how this codebase already solves it | `similar` — the neighbours to imitate | writing the first thing that compiles |
|
|
72
|
+
|
|
73
|
+
Nothing in this phase writes. Reading is free, so the bar is: **no proposal
|
|
74
|
+
before the blast radius is known** (`pythia-impact`'s iron law) and **no line
|
|
75
|
+
written before the neighbours have been read** (`pythia-write`'s).
|
|
76
|
+
|
|
77
|
+
### 2 · Ask — the questions are part of the method, not an interruption
|
|
78
|
+
|
|
79
|
+
The kit makes the agent stop at exactly the moments where a human's judgment
|
|
80
|
+
is the missing input, and forbids it to guess past them:
|
|
81
|
+
|
|
82
|
+
- **Before any write**: the full preview — diff, dependents, warnings — is
|
|
83
|
+
relayed verbatim, and the agent waits for a real yes. A compliment is not a
|
|
84
|
+
yes. Silence is not a yes.
|
|
85
|
+
- **When the blast radius is large**: ten or more dependents, or anything
|
|
86
|
+
cross-schema, goes to the developer *before code is written*, not after.
|
|
87
|
+
- **When sources of truth disagree**: a standards document says one thing,
|
|
88
|
+
the schema does another — that gap is a question ("rule nobody follows,
|
|
89
|
+
new-code-only, or drift?"), never a silent pick.
|
|
90
|
+
- **When something breaks**: exit 3 means *written but broken*. The agent
|
|
91
|
+
reports it exactly so, with the ready rollback — reporting success here is
|
|
92
|
+
the one sin the whole kit is built to prevent.
|
|
93
|
+
- **When policy refuses**: the refusal is relayed as information, not routed
|
|
94
|
+
around.
|
|
95
|
+
|
|
96
|
+
### 3 · Do — act inside a pipeline that cannot lie
|
|
97
|
+
|
|
98
|
+
Only after Learn and Ask does anything touch the database, and then only
|
|
99
|
+
through one door: **snapshot → impact → preview → token → apply → verify →
|
|
100
|
+
report**. DDL self-commits in Oracle, so the snapshot is the only real undo —
|
|
101
|
+
it runs first and no flag disables it. A content-bound token guarantees what
|
|
102
|
+
lands is byte-for-byte what was approved. And the CLI enforces the gates
|
|
103
|
+
itself: a headless agent cannot `--yes` its own writes or loosen policy —
|
|
104
|
+
that takes a human at a real terminal.
|
|
105
|
+
|
|
106
|
+
The same discipline holds when the *developer* does the work: `src` and
|
|
107
|
+
`impact` silently snapshot what they read, so even a change made by hand in
|
|
108
|
+
SQL Developer has a rollback file waiting (`history` lists them, drift is
|
|
109
|
+
reported when source moved with no apply behind it).
|
|
110
|
+
|
|
111
|
+
**Học – Hỏi – Làm** — Learn, Ask, Do. If the agent cannot show which phase it
|
|
112
|
+
is in, it is doing none of them.
|
|
67
113
|
|
|
68
114
|
## Install
|
|
69
115
|
|
|
@@ -178,13 +224,13 @@ it audits every interaction in `DBTOOLS$MCP_LOG`. **Writes never do** — only
|
|
|
178
224
|
|
|
179
225
|
## Skills
|
|
180
226
|
|
|
181
|
-
|
|
227
|
+
Eight skills teach the agent the workflow — gates, not suggestions:
|
|
182
228
|
|
|
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
|
|
229
|
+
`pythia-setup` · `pythia-explore` · `pythia-impact` (before any change) ·
|
|
230
|
+
`pythia-write` (copy the codebase's conventions) · `pythia-apply` (the gate:
|
|
231
|
+
the developer approves the preview in chat) · `pythia-review` (antipatterns) ·
|
|
232
|
+
`pythia-conventions` (adopt a house style, verified against real names) ·
|
|
233
|
+
`pythia-skill-author` (capture your team's workflow as a skill).
|
|
188
234
|
|
|
189
235
|
## Compatibility
|
|
190
236
|
|
|
@@ -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
|
|
@@ -7,6 +7,8 @@ description: Use when a PL/SQL change is ready to reach the database - applying
|
|
|
7
7
|
|
|
8
8
|
**Announce at start:** "Using pythia-apply — I'll preview the change first."
|
|
9
9
|
|
|
10
|
+
**Phase:** Ask → Do — the preview is relayed and approved before the one write door opens
|
|
11
|
+
|
|
10
12
|
DDL in Oracle commits itself. There is no transaction to roll back — the
|
|
11
13
|
snapshot pythia takes before writing is the only undo that exists. This skill
|
|
12
14
|
exists so that safety net is always used, and used honestly.
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
**Phase:** Learn → Ask — derive from the schema, ask where the document disagrees, only then write the config
|
|
11
|
+
|
|
12
|
+
Conventions already exist in every mature schema. They are in the object
|
|
13
|
+
names, in a standards document, or in a senior developer's head. This turns
|
|
14
|
+
them into two files: patterns the tool checks on every write, and prose every
|
|
15
|
+
future agent session reads before writing.
|
|
16
|
+
|
|
17
|
+
## The Iron Law
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
NO PATTERN WRITTEN DOWN BEFORE THE SCHEMA HAS AGREED WITH IT
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
A pattern that does not match the objects already there is not a convention.
|
|
24
|
+
It is a guess that will warn on every apply until someone deletes it.
|
|
25
|
+
|
|
26
|
+
## The Workflow
|
|
27
|
+
|
|
28
|
+
1. **Read the shape off the database, not off your memory of it.**
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pythia conventions --scan
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
It tokenises every object name and proposes a pattern per type. Do this
|
|
35
|
+
even when a document exists — and never page through thousands of names
|
|
36
|
+
yourself: that is what the command is for, and your context is better
|
|
37
|
+
spent elsewhere.
|
|
38
|
+
|
|
39
|
+
2. **Read the developer's document, if there is one.** A base system's
|
|
40
|
+
standards, a wiki page, an older project's rules. Look for what a regex
|
|
41
|
+
cannot hold: parameter prefixes a calling layer depends on, a column every
|
|
42
|
+
query must filter by, where a transaction may commit, which date or money
|
|
43
|
+
representation is canonical. Those go in the prose half.
|
|
44
|
+
|
|
45
|
+
3. **Reconcile, and say so when the two disagree.** The document states
|
|
46
|
+
intent; the schema states fact. A rule in the document that the schema
|
|
47
|
+
contradicts is one of three things — a rule nobody follows, a rule for new
|
|
48
|
+
code only, or drift worth reporting. Ask the developer which; do not
|
|
49
|
+
silently pick.
|
|
50
|
+
|
|
51
|
+
4. **Write `.pythia/conventions.json`.** Start from the scan output, narrowed
|
|
52
|
+
by the document. `pythia conventions --init` writes a blank pair if you
|
|
53
|
+
want the skeleton first.
|
|
54
|
+
|
|
55
|
+
5. **Verify — this step is the point of the skill.**
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pythia conventions --check
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
It reports coverage per type and names what misses.
|
|
62
|
+
|
|
63
|
+
| Result | What it means |
|
|
64
|
+
|---|---|
|
|
65
|
+
| every name matches | the pattern is real; keep it |
|
|
66
|
+
| ~90%+ with a handful of misses | genuine exceptions — list them in step 6 |
|
|
67
|
+
| below 90% | **the pattern is wrong**, not the schema; widen or split it, then check again |
|
|
68
|
+
| nothing of that type | an untested rule; keep it only if new objects of that type are expected |
|
|
69
|
+
|
|
70
|
+
6. **Record the exceptions, with their reason.** Objects that break the rule
|
|
71
|
+
on purpose — ported names from a base platform, legacy entry points whose
|
|
72
|
+
names are part of a public surface. Say why renaming would cost more than
|
|
73
|
+
the warning. An unexplained exception gets "fixed" by the next person.
|
|
74
|
+
|
|
75
|
+
7. **Write `.pythia/conventions.md`.** For every rule, state **the cost of
|
|
76
|
+
breaking it**, not just the rule. "Parameter prefixes must be `b_`/`a_`"
|
|
77
|
+
is ignorable; "the calling layer binds by prefix, so a wrong one silently
|
|
78
|
+
unbinds the field" is not. Rules with named consequences get followed.
|
|
79
|
+
|
|
80
|
+
## Adopting from an older base the team already runs
|
|
81
|
+
|
|
82
|
+
Point the connection at that schema and run steps 1 and 2 there — a base
|
|
83
|
+
system with thousands of objects is the most reliable statement of a house
|
|
84
|
+
style that exists, far better than anyone's recollection of it. Then switch
|
|
85
|
+
the connection to the new project and run `--check`: coverage tells you how
|
|
86
|
+
much of the base style the new schema has actually inherited.
|
|
87
|
+
|
|
88
|
+
## Red Flags — STOP if you catch yourself thinking
|
|
89
|
+
|
|
90
|
+
| Thought | Reality |
|
|
91
|
+
|---------|---------|
|
|
92
|
+
| "I'll write the patterns from the document alone" | Documents describe intent; schemas record what happened. Scan first. |
|
|
93
|
+
| "80% coverage is good enough" | Every miss warns on every apply. Fix the pattern or record the exception. |
|
|
94
|
+
| "I'll read all the object names to work it out" | `--scan` does that without spending your context. |
|
|
95
|
+
| "The schema disagrees, so the document is wrong" | It may be a new-code-only rule. Ask. |
|
|
96
|
+
| "A rule per line is enough" | A rule without its cost is a rule people skip. |
|
|
97
|
+
|
|
98
|
+
## When NOT to use this skill
|
|
99
|
+
|
|
100
|
+
- Writing one object in an existing style → `pythia-write`, which reads these
|
|
101
|
+
files and uses `similar` for the details.
|
|
102
|
+
- Capturing a *workflow* with steps and gates → `pythia-skill-author`.
|
|
103
|
+
Naming and style belong here; procedures belong in a skill.
|
|
@@ -7,6 +7,8 @@ description: Use when you need to understand anything in an Oracle schema - find
|
|
|
7
7
|
|
|
8
8
|
**Announce at start:** "Using pythia-explore — asking the database directly."
|
|
9
9
|
|
|
10
|
+
**Phase:** Learn
|
|
11
|
+
|
|
10
12
|
## The principle: ask the database, never the dump
|
|
11
13
|
|
|
12
14
|
Repositories of exported `.sql` files go stale the day after export. A real
|
|
@@ -7,6 +7,8 @@ description: Use before proposing or writing any change to an Oracle object - a
|
|
|
7
7
|
|
|
8
8
|
**Announce at start:** "Using pythia-impact to measure the blast radius first."
|
|
9
9
|
|
|
10
|
+
**Phase:** Learn — and the first Ask gate: a large blast radius reaches the developer before any code is written
|
|
11
|
+
|
|
10
12
|
Changing an Oracle object recompiles or invalidates everything that depends
|
|
11
13
|
on it — immediately, schema-wide, for every user of a shared database. The
|
|
12
14
|
cost of knowing first is one command.
|
|
@@ -7,6 +7,8 @@ description: Use when reviewing PL/SQL - a changed procedure or package, a propo
|
|
|
7
7
|
|
|
8
8
|
**Announce at start:** "Using pythia-review — checking the database's signals first."
|
|
9
9
|
|
|
10
|
+
**Phase:** Learn → Ask — the database's own signals first, findings back to the developer
|
|
11
|
+
|
|
10
12
|
Review in two passes: what the database *knows* is wrong, then what the
|
|
11
13
|
checklist says is *likely* wrong. Machine signals first — they are free and
|
|
12
14
|
exact.
|
|
@@ -7,6 +7,8 @@ description: Use when setting pythia up for a project or a machine - writing con
|
|
|
7
7
|
|
|
8
8
|
**Announce at start:** "Using pythia-setup to configure the database access."
|
|
9
9
|
|
|
10
|
+
**Phase:** Do — one-time harness setup; everything after runs through Learn → Ask → Do
|
|
11
|
+
|
|
10
12
|
Set up in this order: connection first (everything else needs it), the
|
|
11
13
|
least-privilege user second (the only protection that cannot be bypassed),
|
|
12
14
|
SQLcl MCP last (optional, reads only).
|
|
@@ -7,6 +7,8 @@ description: Use when the developer wants their own way of working captured as a
|
|
|
7
7
|
|
|
8
8
|
**Announce at start:** "Using pythia-skill-author — let's capture how you actually work."
|
|
9
9
|
|
|
10
|
+
**Phase:** Learn → Ask → Do — interview, mine the schema, verify triggering
|
|
11
|
+
|
|
10
12
|
A skill is a decision captured so it never has to be re-argued. Capture what
|
|
11
13
|
the team *actually does* — with evidence from the database — not what anyone
|
|
12
14
|
remembers about it.
|
|
@@ -7,6 +7,8 @@ description: Use when writing or modifying PL/SQL source - a procedure, function
|
|
|
7
7
|
|
|
8
8
|
**Announce at start:** "Using pythia-write — mining the codebase's conventions first."
|
|
9
9
|
|
|
10
|
+
**Phase:** Learn → Do — mine the neighbours first, then draft; landing it is pythia-apply's job
|
|
11
|
+
|
|
10
12
|
**Before anything:** if the project has `.pythia/conventions.md`, read it —
|
|
11
13
|
house rules there outrank every generic pattern below, and
|
|
12
14
|
`pythia conventions` shows the naming patterns the apply preview will check. If the project has none and the developer
|
|
@@ -348,6 +348,19 @@ def test_conventions_init_writes_both_halves_and_never_clobbers():
|
|
|
348
348
|
assert (d / "conventions.json").read_text(encoding="utf-8") == '{"naming": {}}'
|
|
349
349
|
|
|
350
350
|
|
|
351
|
+
def test_guide_places_every_command_in_the_model():
|
|
352
|
+
"""`pythia guide` is the book an agent can open on any platform, skills
|
|
353
|
+
support or not. Coherence is enforced: every command the CLI exposes must
|
|
354
|
+
appear somewhere in the guide -- adding a command without giving it a
|
|
355
|
+
place in Learn/Ask/Do fails here."""
|
|
356
|
+
text = pythia.OPERATING_GUIDE
|
|
357
|
+
for phase in ("LEARN", "ASK", "DO"):
|
|
358
|
+
assert phase in text, f"guide missing the {phase} movement"
|
|
359
|
+
for cmd in pythia.COMMANDS:
|
|
360
|
+
assert cmd in text, f"command {cmd!r} has no place in the guide"
|
|
361
|
+
assert "guide" in pythia.NO_DB_COMMANDS # the book opens with no database
|
|
362
|
+
|
|
363
|
+
|
|
351
364
|
def main():
|
|
352
365
|
failed = 0
|
|
353
366
|
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
|
|
@@ -109,17 +109,40 @@ def test_reference_files_exist_where_promised():
|
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
def test_readme_carries_the_required_tables():
|
|
112
|
-
"""The
|
|
113
|
-
|
|
112
|
+
"""The line cap is gone by the owner's call — the README now explains
|
|
113
|
+
the operating model in full. What stays pinned is content: the drift
|
|
114
|
+
table, the honest-rollback and policy tables, the install channels, and
|
|
115
|
+
the Learn-Ask-Do frame the whole kit is organised around."""
|
|
114
116
|
text = (ROOT / "README.md").read_text(encoding="utf-8")
|
|
115
|
-
for needle in ("1,016", # drift table
|
|
117
|
+
for needle in ("1,016", # drift table
|
|
116
118
|
"Flashback Query", "Recycle Bin", # rollback honesty
|
|
117
119
|
"plsql_source", "data_dml", # policy table
|
|
118
120
|
"npx skills add", # install channel
|
|
119
|
-
"
|
|
121
|
+
"Learn", "Ask", "Do", # the operating model
|
|
122
|
+
"star-history.com",
|
|
120
123
|
"MIT"):
|
|
121
124
|
assert needle in text, f"README missing {needle!r}"
|
|
122
|
-
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_vietnamese_readme_carries_the_operating_model():
|
|
128
|
+
text = (ROOT / "README.vi.md").read_text(encoding="utf-8")
|
|
129
|
+
for needle in ("Học", "Hỏi", "Làm"):
|
|
130
|
+
assert needle in text, f"README.vi missing {needle!r}"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_every_skill_declares_its_phase():
|
|
134
|
+
"""The kit's operating model is Learn - Ask - Do. A skill that cannot say
|
|
135
|
+
which movement it serves is not part of the method, it is a loose page."""
|
|
136
|
+
for name in sorted(EXPECTED):
|
|
137
|
+
path = SKILLS / name / "SKILL.md"
|
|
138
|
+
if not path.is_file():
|
|
139
|
+
continue
|
|
140
|
+
text = path.read_text(encoding="utf-8")
|
|
141
|
+
m = re.search(r"\*\*Phase:\*\* (.+)", text)
|
|
142
|
+
assert m, f"{name}: no '**Phase:**' declaration"
|
|
143
|
+
words = set(re.findall(r"Learn|Ask|Do", m.group(1)))
|
|
144
|
+
assert words and words <= {"Learn", "Ask", "Do"}, \
|
|
145
|
+
f"{name}: phase must name Learn/Ask/Do, got {m.group(1)!r}"
|
|
123
146
|
|
|
124
147
|
|
|
125
148
|
def main():
|
|
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.10 → pythia_plsql-0.6.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
|
{pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-explore/reference/data-dictionary.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|