Skip to content

Commit 3427a7c

Browse files
committed
Add Codex skill for sPyTial Python authoring
1 parent 0cb99f4 commit 3427a7c

7 files changed

Lines changed: 539 additions & 0 deletions

File tree

skills/add-spytial-python/SKILL.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
name: add-spytial-python
3+
description: Integrate sPyTial into Python programs with a high-quality authoring workflow. Use when users ask to add, tune, or debug spytial diagram/evaluator usage, selector-driven constraints and directives, CLRS-style data-structure layouts, sequence diagrams, or custom relationalizers in Python codebases and notebooks.
4+
---
5+
6+
# Add sPyTial (Python)
7+
8+
Use this skill to produce a polished, low-friction authoring experience for Python users adopting sPyTial.
9+
10+
## Workflow
11+
12+
1. Establish target and constraints.
13+
- Identify structure shape, desired rendering mode (`inline`, `browser`, `file`), and whether the user needs single snapshot or sequence playback.
14+
- If shape matches standard structures, load [CLRS Patterns](references/clrs-patterns.md) and choose the closest template first.
15+
16+
2. Land a minimal working integration before styling.
17+
- Add a tiny baseline with `spytial.evaluate(obj)` and `spytial.diagram(obj)`.
18+
- Keep first patch runnable with one clear entrypoint.
19+
- If starting from scratch, scaffold with:
20+
`python scripts/scaffold_spytial_starter.py --shape <linked-list|tree|graph|matrix> --out <path>`
21+
22+
3. Add operations incrementally.
23+
- Add one operation at a time (`orientation`, `align`, `group`, `attribute`, `inferredEdge`, `hideField`, `hideAtom`, `edgeColor`).
24+
- Validate selectors against actual serialized data.
25+
- Load [Selector Cheatsheet](references/selector-cheatsheet.md) for expression syntax and debugging patterns.
26+
27+
4. Use custom relationalizers only when needed.
28+
- Prefer built-in relationalizers first.
29+
- If semantics are still wrong, implement `RelationalizerBase` with `@relationalizer(priority=100+)`.
30+
- Validate serialization with `evaluate()` before tuning layout.
31+
- Load [Relationalizer Workflow](references/relationalizer-workflow.md).
32+
33+
5. Finish with verifiable handoff.
34+
- Show run command and expected output artifact.
35+
- If working in this repo, run scoped verification in `spytial-py` (`pytest` or targeted tests).
36+
- Summarize changed files, chosen pattern, selector assumptions, and next tuning options.
37+
38+
## Quality Bar
39+
40+
- Deliver runnable code, not pseudocode.
41+
- Preserve a fast feedback loop: `evaluate()` then `diagram()`.
42+
- Prefer CLRS-derived patterns for common data structures.
43+
- Explain non-obvious selectors inline with short comments.
44+
- State assumptions explicitly when the data model is ambiguous.
45+
46+
## References
47+
48+
- [Authoring Workflow](references/authoring-workflow.md)
49+
- [CLRS Patterns](references/clrs-patterns.md)
50+
- [Selector Cheatsheet](references/selector-cheatsheet.md)
51+
- [Relationalizer Workflow](references/relationalizer-workflow.md)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
interface:
2+
display_name: "Add sPyTial (Python)"
3+
short_description: "Help users add sPyTial to Python programs"
4+
default_prompt: "Use $add-spytial-python to integrate sPyTial into my Python project with clear examples and constraints."
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Authoring Workflow
2+
3+
Use this workflow to integrate sPyTial in a way that stays fast to iterate and easy to maintain.
4+
5+
## 1. Start with a serialization checkpoint
6+
7+
Use `evaluate()` before layout tuning:
8+
9+
```python
10+
import spytial
11+
12+
spytial.evaluate(obj)
13+
spytial.diagram(obj)
14+
```
15+
16+
`evaluate()` confirms atoms/relations. `diagram()` confirms layout.
17+
18+
## 2. Choose output mode intentionally
19+
20+
- Notebook workflows: prefer default/`inline`.
21+
- Script + local debugging: prefer `browser`.
22+
- CI/docs artifacts: prefer `file` and commit generated screenshots only if needed.
23+
24+
```python
25+
spytial.diagram(obj, method="browser")
26+
spytial.diagram(obj, method="file", auto_open=False)
27+
```
28+
29+
## 3. Layer operations gradually
30+
31+
Apply operations in this order unless a use case requires otherwise:
32+
33+
1. Structural constraints: `orientation`, `align`, `group`
34+
2. Visibility controls: `hideField`, `hideAtom`
35+
3. Readability directives: `attribute`, `tag`, `edgeColor`, `atomColor`
36+
4. Derived edges: `inferredEdge`
37+
38+
After each layer, rerun `evaluate()` or inspect diagram output before adding more.
39+
40+
## 4. Sequence diagrams: decide identity policy early
41+
42+
Use `diagramSequence()` for state transitions.
43+
44+
- Reused mutable objects across frames: often no identity hook needed.
45+
- Rebuilt objects per frame: pass `identity=...` to stabilize IDs.
46+
47+
```python
48+
spytial.diagramSequence(
49+
states,
50+
sequence_policy="stability",
51+
identity=lambda obj: obj.id if hasattr(obj, "id") else None,
52+
)
53+
```
54+
55+
## 5. Introduce custom relationalizers only if built-ins miss semantics
56+
57+
Built-ins already cover primitives, `dict`/`list`/`tuple`/`set`, dataclasses, and generic objects.
58+
Use a custom relationalizer when domain meaning is not captured.
59+
60+
Use priority `>=100` for custom relationalizers.
61+
62+
## 6. Acceptance checklist
63+
64+
- `evaluate()` output matches expected domain structure.
65+
- Diagram uses at least one intentional structure cue (direction, alignment, grouping, or inferred edge).
66+
- Selectors are valid and readable.
67+
- Output mode matches user context (notebook/script/CI).
68+
- If sequence: identity behavior is explicit and stable.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# CLRS Patterns
2+
3+
Use these patterns as first defaults for standard data structures before inventing new selectors.
4+
5+
## Linked structures (stack/queue/list)
6+
7+
Recommended defaults:
8+
9+
- Linear flow: `orientation(selector="next", directions=["directlyRight"])`
10+
- Show payload: `attribute(field="data")`
11+
- Hide sentinels/primitive noise: `hideAtom(selector="NoneType + int + str")` (adjust per model)
12+
13+
```python
14+
import spytial
15+
16+
@spytial.orientation(selector="next", directions=["directlyRight"])
17+
@spytial.attribute(field="data")
18+
class Node:
19+
def __init__(self, data, nxt=None):
20+
self.data = data
21+
self.next = nxt
22+
```
23+
24+
## Trees (BST/RB/heap-like)
25+
26+
Recommended defaults:
27+
28+
- Left branch: `orientation(..., ["below", "left"])`
29+
- Right branch: `orientation(..., ["below", "right"])`
30+
- Optional sibling alignment: `align(..., direction="horizontal")`
31+
- Surface key metadata with `attribute(field="key")`
32+
33+
```python
34+
import spytial
35+
36+
@spytial.orientation(selector="left & (TreeNode->TreeNode)", directions=["below", "left"])
37+
@spytial.orientation(selector="right & (TreeNode->TreeNode)", directions=["below", "right"])
38+
@spytial.attribute(field="key")
39+
class TreeNode:
40+
def __init__(self, key, left=None, right=None):
41+
self.key = key
42+
self.left = left
43+
self.right = right
44+
```
45+
46+
## Graphs from adjacency structures
47+
48+
Recommended defaults:
49+
50+
- Derive explicit edges with `inferredEdge`
51+
- Hide raw container atoms (`list`, tuples, helper wrappers)
52+
- Keep node labels through `attribute(field="key")` or domain field names
53+
54+
```python
55+
import spytial
56+
57+
graph = spytial.inferredEdge(
58+
selector="{a, b : Node | b in a.neighbors}",
59+
name="edge",
60+
)(graph_obj)
61+
graph = spytial.hideAtom(selector="list + tuple")(graph)
62+
spytial.diagram(graph)
63+
```
64+
65+
## Hash-table and bucketed layouts
66+
67+
Recommended defaults:
68+
69+
- Group buckets with selector-based `group(...)`
70+
- Orient chain relations directly left/right
71+
- Hide housekeeping fields such as `prev` where needed
72+
73+
Selectors from CLRS-style examples often look like:
74+
75+
- `group(selector="(NoneType.~key) - ((iden & next).Node)", name="T")`
76+
- `orientation(selector="next & (Node->Node)", directions=["directlyRight"])`
77+
78+
## Matrix / DP table layouts
79+
80+
Recommended defaults:
81+
82+
- Build row and column selectors
83+
- `align` rows and columns
84+
- Add directional orientation across row/column deltas
85+
86+
Pattern from memoization examples:
87+
88+
- `align(selector=SAME_ROW, direction="horizontal")`
89+
- `align(selector=SAME_COL, direction="vertical")`
90+
- `orientation(selector=DIFF_ROWS, directions=["below"])`
91+
- `orientation(selector=DIFF_COLS, directions=["right"])`
92+
93+
## Disjoint sets / grouped regions
94+
95+
Recommended defaults:
96+
97+
- Group by selector to expose set membership
98+
- Hide technical atoms (`int`, helper lists) after structure checks
99+
- Keep representative fields visible with `attribute(...)`
100+
101+
## Picking the closest notebook
102+
103+
Map use case to source notebook:
104+
105+
- Stacks/queues: `spytial-clrs/src/stacksqueues.ipynb`
106+
- Linked lists: `spytial-clrs/src/linked-lists.ipynb`
107+
- Heaps: `spytial-clrs/src/heaps.ipynb`
108+
- Trees: `spytial-clrs/src/trees.ipynb`
109+
- Hash tables: `spytial-clrs/src/hash-tables.ipynb`
110+
- Graphs: `spytial-clrs/src/graphs.ipynb`
111+
- Disjoint sets: `spytial-clrs/src/disjoint-sets.ipynb`
112+
- Memoization tables: `spytial-clrs/src/memoization.ipynb`
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Relationalizer Workflow
2+
3+
Use this only when built-in relationalizers cannot express domain semantics cleanly.
4+
5+
## Decision rule
6+
7+
Implement a custom relationalizer when at least one is true:
8+
9+
- Built-in output merges concepts that should be separate node types.
10+
- Required domain edges do not exist in serialized output.
11+
- You need stable, explicit IDs/labels not derivable from defaults.
12+
13+
Stay with built-ins when you only need visual/layout tuning.
14+
15+
## Minimal implementation
16+
17+
```python
18+
from spytial import RelationalizerBase, relationalizer, Atom, Relation
19+
20+
@relationalizer(priority=100)
21+
class WidgetRelationalizer(RelationalizerBase):
22+
def can_handle(self, obj):
23+
return hasattr(obj, "widget_id")
24+
25+
def relationalize(self, obj, walker_func):
26+
widget_atom = Atom(
27+
id=f"widget:{obj.widget_id}",
28+
type="Widget",
29+
label=getattr(obj, "name", str(obj.widget_id)),
30+
)
31+
rels = []
32+
if getattr(obj, "parent", None) is not None:
33+
parent_id = walker_func._get_id(obj.parent)
34+
rels.append(Relation(name="parent", tuples=[(widget_atom.id, parent_id)]))
35+
return [widget_atom], rels
36+
```
37+
38+
## Required checks
39+
40+
1. Import path registers class (decorator executes on import).
41+
2. Priority is `>=100` (built-ins reserve lower range).
42+
3. `spytial.evaluate(sample)` shows expected atoms/relations.
43+
4. `spytial.diagram(sample)` renders without missing-edge surprises.
44+
45+
## Common mistakes
46+
47+
- Implementing relationalizer when selectors/operations were enough.
48+
- Returning unstable IDs across runs, breaking sequence/view consistency.
49+
- Skipping `evaluate()` and debugging only in diagram view.
50+
- Forgetting to import module containing the decorated class.
51+
52+
## Handoff expectations
53+
54+
When adding a custom relationalizer, include:
55+
56+
- Why built-ins were insufficient.
57+
- Chosen atom types and relation names.
58+
- One or two selectors that rely on the new relations.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Selector Cheatsheet
2+
3+
This cheatsheet is based on `simple-graph-query` syntax as used by sPyTial selectors.
4+
5+
## Core set and relation operators
6+
7+
- Union: `A + B`
8+
- Intersection: `A & B`
9+
- Difference: `A - B`
10+
- Join: `A.rel` or `rel.Type`
11+
- Product: `A -> B`
12+
- Transpose: `~rel`
13+
- Transitive closure: `^rel`
14+
- Reflexive-transitive closure: `*rel`
15+
16+
Examples:
17+
18+
- `Node.key`
19+
- `edge.edge`
20+
- `~parent`
21+
- `^next`
22+
- `Node - NoneType`
23+
24+
## Predicates and logic
25+
26+
- Membership: `x in S`
27+
- Equality/inequality: `x = y`, `x != y`
28+
- Boolean ops: `and`, `or`, `!`, `=>`, `<=>`
29+
- Quantifiers: `all`, `some`, `no`, `one`, `lone`
30+
31+
Examples:
32+
33+
- `some x: Node | x in roots`
34+
- `all x: Item | some y: Item | x != y`
35+
- `all disj i, j: Int | not i = j`
36+
37+
## Comprehensions (great for `group`/`orientation` selectors)
38+
39+
Unary:
40+
41+
```txt
42+
{x : Item | x.value > 10}
43+
```
44+
45+
Binary:
46+
47+
```txt
48+
{b : Basket, a : Fruit | (a in b.fruit) and a.status = Rotten}
49+
```
50+
51+
Numeric ordering (common for array-backed structures):
52+
53+
```txt
54+
{x, y : idx[object][object] | @num:(x[idx[object]]) < @num:(y[idx[object]])}
55+
```
56+
57+
## Built-ins that matter most
58+
59+
- `univ`: all atoms
60+
- `iden`: identity relation (all `(a, a)` pairs)
61+
- `Int`: integer atoms
62+
63+
## Label conversion helpers
64+
65+
- `@:(expr)` convert to string label form
66+
- `@num:(expr)` convert to number for numeric comparisons
67+
68+
Examples:
69+
70+
- `@:(n14) = @:(12)`
71+
- `@num:(x[idx[object]]) < @num:(y[idx[object]])`
72+
73+
## Reserved keyword identifiers
74+
75+
If a field/type name conflicts with a keyword, use backticks:
76+
77+
- `` `set` ``
78+
- `` item0.`in` ``
79+
80+
## Debugging workflow for selectors
81+
82+
1. Open `spytial.evaluate(obj)` to inspect available atoms/relations.
83+
2. Start with a broad selector (`TypeName` or relation name).
84+
3. Add one operator at a time (`&`, `-`, join, then comprehension).
85+
4. Re-run and verify before using it in an annotation.
86+
5. Keep selectors readable; factor complex expressions into local constants.

0 commit comments

Comments
 (0)