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.
Files changed (41) hide show
  1. {pythia_plsql-0.4.10/scripts/pythia_plsql.egg-info → pythia_plsql-0.6.0}/PKG-INFO +72 -26
  2. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/README.md +71 -25
  3. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/pyproject.toml +1 -1
  4. pythia_plsql-0.6.0/queries/object-names.sql +11 -0
  5. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/scripts/pythia.py +239 -31
  6. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0/scripts/pythia_plsql.egg-info}/PKG-INFO +72 -26
  7. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/scripts/pythia_plsql.egg-info/SOURCES.txt +2 -0
  8. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-apply/SKILL.md +2 -0
  9. pythia_plsql-0.6.0/skills/pythia-conventions/SKILL.md +103 -0
  10. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-explore/SKILL.md +2 -0
  11. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-impact/SKILL.md +2 -0
  12. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-review/SKILL.md +2 -0
  13. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-setup/SKILL.md +2 -0
  14. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-skill-author/SKILL.md +2 -0
  15. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-write/SKILL.md +2 -0
  16. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/tests/test_install.py +13 -0
  17. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/tests/test_phase2.py +64 -0
  18. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/tests/test_phase5.py +29 -6
  19. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/LICENSE +0 -0
  20. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/compile-errors.sql +0 -0
  21. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/dependencies.sql +0 -0
  22. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/impact.sql +0 -0
  23. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/invalid-objects.sql +0 -0
  24. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/name-occupants.sql +0 -0
  25. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/object-source.sql +0 -0
  26. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/plscope-enabled.sql +0 -0
  27. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/plscope-statements.sql +0 -0
  28. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/plscope-usages.sql +0 -0
  29. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/session-privileges.sql +0 -0
  30. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/similar-candidates.sql +0 -0
  31. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/queries/source.sql +0 -0
  32. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
  33. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
  34. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
  35. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
  36. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/setup.cfg +0 -0
  37. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-explore/reference/data-dictionary.md +0 -0
  38. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-review/reference/antipatterns.md +0 -0
  39. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/skills/pythia-write/reference/patterns.md +0 -0
  40. {pythia_plsql-0.4.10 → pythia_plsql-0.6.0}/tests/test_phase1.py +0 -0
  41. {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.4.10
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: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
27
27
  ![python](https://img.shields.io/badge/python-3.9%2B-blue)
28
28
 
29
- An Agent Skills + CLI kit for developing PL/SQL on Oracle Database with AI coding
30
- agents (Claude Code, Codex, Cursor — any of the 76 agents `npx skills` supports).
31
- Explore schemas too big to dump, measure blast radius **before** touching anything,
32
- and land changes through a snapshot-verified write path that never lies about rollback.
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
- ## How it works
56
+ ## The operating model: Learn → Ask → Do
51
57
 
52
- ```
53
- developer chats with the agent
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
- The write path is the heart: **snapshot → impact → preview → apply → verify → report**.
63
- DDL self-commits in Oracle — the snapshot is the only real undo, so it always runs
64
- first and no flag can turn it off. A 6-hex token binds the write to exactly what was
65
- previewed; exit codes make honesty machine-readable
66
- (`0` clean · `1` refused · `3` **written but broken — never reported as success**).
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
- Seven skills teach the agent the workflow — superpowers-style gates, not suggestions:
227
+ Eight skills teach the agent the workflow — gates, not suggestions:
182
228
 
183
- `pythia-setup` · `pythia-explore` · `pythia-impact` (impact **before** any change) ·
184
- `pythia-write` (copy the codebase's conventions) · `pythia-apply` (the gate: the
185
- developer sees the preview and approves in chat before anything is written) ·
186
- `pythia-review` (seven antipatterns) · `pythia-skill-author` (capture *your team's*
187
- workflow as a new skill, mined from the live schema).
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: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
13
13
  ![python](https://img.shields.io/badge/python-3.9%2B-blue)
14
14
 
15
- An Agent Skills + CLI kit for developing PL/SQL on Oracle Database with AI coding
16
- agents (Claude Code, Codex, Cursor — any of the 76 agents `npx skills` supports).
17
- Explore schemas too big to dump, measure blast radius **before** touching anything,
18
- and land changes through a snapshot-verified write path that never lies about rollback.
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
- ## How it works
42
+ ## The operating model: Learn → Ask → Do
37
43
 
38
- ```
39
- developer chats with the agent
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
- The write path is the heart: **snapshot → impact → preview → apply → verify → report**.
49
- DDL self-commits in Oracle — the snapshot is the only real undo, so it always runs
50
- first and no flag can turn it off. A 6-hex token binds the write to exactly what was
51
- previewed; exit codes make honesty machine-readable
52
- (`0` clean · `1` refused · `3` **written but broken — never reported as success**).
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
- Seven skills teach the agent the workflow — superpowers-style gates, not suggestions:
213
+ Eight skills teach the agent the workflow — gates, not suggestions:
168
214
 
169
- `pythia-setup` · `pythia-explore` · `pythia-impact` (impact **before** any change) ·
170
- `pythia-write` (copy the codebase's conventions) · `pythia-apply` (the gate: the
171
- developer sees the preview and approves in chat before anything is written) ·
172
- `pythia-review` (seven antipatterns) · `pythia-skill-author` (capture *your team's*
173
- workflow as a new skill, mined from the live schema).
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.4.10"
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()} similar <A_TYPICAL_NAME>` shows what "
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"Create the starter pair with: {invocation()} conventions --init")
1886
- print(" conventions.json naming patterns; apply previews warn on drift")
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
- else:
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", "conventions", "install", "unistr",
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
- if ns.command in NO_DB_COMMANDS and not (
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.4.10
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: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
27
27
  ![python](https://img.shields.io/badge/python-3.9%2B-blue)
28
28
 
29
- An Agent Skills + CLI kit for developing PL/SQL on Oracle Database with AI coding
30
- agents (Claude Code, Codex, Cursor — any of the 76 agents `npx skills` supports).
31
- Explore schemas too big to dump, measure blast radius **before** touching anything,
32
- and land changes through a snapshot-verified write path that never lies about rollback.
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
- ## How it works
56
+ ## The operating model: Learn → Ask → Do
51
57
 
52
- ```
53
- developer chats with the agent
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
- The write path is the heart: **snapshot → impact → preview → apply → verify → report**.
63
- DDL self-commits in Oracle — the snapshot is the only real undo, so it always runs
64
- first and no flag can turn it off. A 6-hex token binds the write to exactly what was
65
- previewed; exit codes make honesty machine-readable
66
- (`0` clean · `1` refused · `3` **written but broken — never reported as success**).
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
- Seven skills teach the agent the workflow — superpowers-style gates, not suggestions:
227
+ Eight skills teach the agent the workflow — gates, not suggestions:
182
228
 
183
- `pythia-setup` · `pythia-explore` · `pythia-impact` (impact **before** any change) ·
184
- `pythia-write` (copy the codebase's conventions) · `pythia-apply` (the gate: the
185
- developer sees the preview and approves in chat before anything is written) ·
186
- `pythia-review` (seven antipatterns) · `pythia-skill-author` (capture *your team's*
187
- workflow as a new skill, mined from the live schema).
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 spec's completion criteria: drift table up top, honest-rollback and
113
- policy tables present, install channels, under 200 lines."""
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, index row
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
- "star-history.com", # star chart
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
- assert text.count("\n") + 1 <= 200, "README must stay under 200 lines"
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