Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions skills/add-spytial-python/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
name: add-spytial-python
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.
---

# Add sPyTial (Python)

Use this skill to produce a polished, low-friction authoring experience for Python users adopting sPyTial.

## Workflow

1. Establish target and constraints.
- Identify structure shape, desired rendering mode (`inline`, `browser`, `file`), and whether the user needs single snapshot or sequence playback.
- If shape matches standard structures, load [CLRS Patterns](references/clrs-patterns.md) and choose the closest template first.

2. Land a minimal working integration before styling.
- Add a tiny baseline with `spytial.evaluate(obj)` and `spytial.diagram(obj)`.
- Keep first patch runnable with one clear entrypoint.
- If starting from scratch, scaffold with:
`python scripts/scaffold_spytial_starter.py --shape <linked-list|tree|graph|matrix> --out <path>`

3. Add operations incrementally.
- Add one operation at a time (`orientation`, `align`, `group`, `attribute`, `inferredEdge`, `hideField`, `hideAtom`, `edgeColor`).
- Validate selectors against actual serialized data.
- Load [Selector Cheatsheet](references/selector-cheatsheet.md) for expression syntax and debugging patterns.

4. Use custom relationalizers only when needed.
- Prefer built-in relationalizers first.
- If semantics are still wrong, implement `RelationalizerBase` with `@relationalizer(priority=100+)`.
- Validate serialization with `evaluate()` before tuning layout.
- Load [Relationalizer Workflow](references/relationalizer-workflow.md).

5. Finish with verifiable handoff.
- Show run command and expected output artifact.
- If working in this repo, run scoped verification in `spytial-py` (`pytest` or targeted tests).
- Summarize changed files, chosen pattern, selector assumptions, and next tuning options.

## Quality Bar

- Deliver runnable code, not pseudocode.
- Preserve a fast feedback loop: `evaluate()` then `diagram()`.
- Prefer CLRS-derived patterns for common data structures.
- Explain non-obvious selectors inline with short comments.
- State assumptions explicitly when the data model is ambiguous.

## References

- [Authoring Workflow](references/authoring-workflow.md)
- [CLRS Patterns](references/clrs-patterns.md)
- [Selector Cheatsheet](references/selector-cheatsheet.md)
- [Relationalizer Workflow](references/relationalizer-workflow.md)
4 changes: 4 additions & 0 deletions skills/add-spytial-python/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Add sPyTial (Python)"
short_description: "Help users add sPyTial to Python programs"
default_prompt: "Use $add-spytial-python to integrate sPyTial into my Python project with clear examples and constraints."
68 changes: 68 additions & 0 deletions skills/add-spytial-python/references/authoring-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Authoring Workflow

Use this workflow to integrate sPyTial in a way that stays fast to iterate and easy to maintain.

## 1. Start with a serialization checkpoint

Use `evaluate()` before layout tuning:

```python
import spytial

spytial.evaluate(obj)
spytial.diagram(obj)
```

`evaluate()` confirms atoms/relations. `diagram()` confirms layout.

## 2. Choose output mode intentionally

- Notebook workflows: prefer default/`inline`.
- Script + local debugging: prefer `browser`.
- CI/docs artifacts: prefer `file` and commit generated screenshots only if needed.

```python
spytial.diagram(obj, method="browser")
spytial.diagram(obj, method="file", auto_open=False)
```

## 3. Layer operations gradually

Apply operations in this order unless a use case requires otherwise:

1. Structural constraints: `orientation`, `align`, `group`
2. Visibility controls: `hideField`, `hideAtom`
3. Readability directives: `attribute`, `tag`, `edgeColor`, `atomColor`
4. Derived edges: `inferredEdge`

After each layer, rerun `evaluate()` or inspect diagram output before adding more.

## 4. Sequence diagrams: decide identity policy early

Use `diagramSequence()` for state transitions.

- Reused mutable objects across frames: often no identity hook needed.
- Rebuilt objects per frame: pass `identity=...` to stabilize IDs.

```python
spytial.diagramSequence(
states,
sequence_policy="stability",
identity=lambda obj: obj.id if hasattr(obj, "id") else None,
)
```

## 5. Introduce custom relationalizers only if built-ins miss semantics

Built-ins already cover primitives, `dict`/`list`/`tuple`/`set`, dataclasses, and generic objects.
Use a custom relationalizer when domain meaning is not captured.

Use priority `>=100` for custom relationalizers.

## 6. Acceptance checklist

- `evaluate()` output matches expected domain structure.
- Diagram uses at least one intentional structure cue (direction, alignment, grouping, or inferred edge).
- Selectors are valid and readable.
- Output mode matches user context (notebook/script/CI).
- If sequence: identity behavior is explicit and stable.
112 changes: 112 additions & 0 deletions skills/add-spytial-python/references/clrs-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# CLRS Patterns

Use these patterns as first defaults for standard data structures before inventing new selectors.

## Linked structures (stack/queue/list)

Recommended defaults:

- Linear flow: `orientation(selector="next", directions=["directlyRight"])`
- Show payload: `attribute(field="data")`
- Hide sentinels/primitive noise: `hideAtom(selector="NoneType + int + str")` (adjust per model)

```python
import spytial

@spytial.orientation(selector="next", directions=["directlyRight"])
@spytial.attribute(field="data")
class Node:
def __init__(self, data, nxt=None):
self.data = data
self.next = nxt
```

## Trees (BST/RB/heap-like)

Recommended defaults:

- Left branch: `orientation(..., ["below", "left"])`
- Right branch: `orientation(..., ["below", "right"])`
- Optional sibling alignment: `align(..., direction="horizontal")`
- Surface key metadata with `attribute(field="key")`

```python
import spytial

@spytial.orientation(selector="left & (TreeNode->TreeNode)", directions=["below", "left"])
@spytial.orientation(selector="right & (TreeNode->TreeNode)", directions=["below", "right"])
@spytial.attribute(field="key")
class TreeNode:
def __init__(self, key, left=None, right=None):
self.key = key
self.left = left
self.right = right
```

## Graphs from adjacency structures

Recommended defaults:

- Derive explicit edges with `inferredEdge`
- Hide raw container atoms (`list`, tuples, helper wrappers)
- Keep node labels through `attribute(field="key")` or domain field names

```python
import spytial

graph = spytial.inferredEdge(
selector="{a, b : Node | b in a.neighbors}",
name="edge",
)(graph_obj)
graph = spytial.hideAtom(selector="list + tuple")(graph)
spytial.diagram(graph)
```

## Hash-table and bucketed layouts

Recommended defaults:

- Group buckets with selector-based `group(...)`
- Orient chain relations directly left/right
- Hide housekeeping fields such as `prev` where needed

Selectors from CLRS-style examples often look like:

- `group(selector="(NoneType.~key) - ((iden & next).Node)", name="T")`
- `orientation(selector="next & (Node->Node)", directions=["directlyRight"])`

## Matrix / DP table layouts

Recommended defaults:

- Build row and column selectors
- `align` rows and columns
- Add directional orientation across row/column deltas

Pattern from memoization examples:

- `align(selector=SAME_ROW, direction="horizontal")`
- `align(selector=SAME_COL, direction="vertical")`
- `orientation(selector=DIFF_ROWS, directions=["below"])`
- `orientation(selector=DIFF_COLS, directions=["right"])`

## Disjoint sets / grouped regions

Recommended defaults:

- Group by selector to expose set membership
- Hide technical atoms (`int`, helper lists) after structure checks
- Keep representative fields visible with `attribute(...)`

## Picking the closest notebook

Map use case to source notebook:

- Stacks/queues: `spytial-clrs/src/stacksqueues.ipynb`
- Linked lists: `spytial-clrs/src/linked-lists.ipynb`
- Heaps: `spytial-clrs/src/heaps.ipynb`
- Trees: `spytial-clrs/src/trees.ipynb`
- Hash tables: `spytial-clrs/src/hash-tables.ipynb`
- Graphs: `spytial-clrs/src/graphs.ipynb`
- Disjoint sets: `spytial-clrs/src/disjoint-sets.ipynb`
- Memoization tables: `spytial-clrs/src/memoization.ipynb`
58 changes: 58 additions & 0 deletions skills/add-spytial-python/references/relationalizer-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Relationalizer Workflow

Use this only when built-in relationalizers cannot express domain semantics cleanly.

## Decision rule

Implement a custom relationalizer when at least one is true:

- Built-in output merges concepts that should be separate node types.
- Required domain edges do not exist in serialized output.
- You need stable, explicit IDs/labels not derivable from defaults.

Stay with built-ins when you only need visual/layout tuning.

## Minimal implementation

```python
from spytial import RelationalizerBase, relationalizer, Atom, Relation

@relationalizer(priority=100)
class WidgetRelationalizer(RelationalizerBase):
def can_handle(self, obj):
return hasattr(obj, "widget_id")

def relationalize(self, obj, walker_func):
widget_atom = Atom(
id=f"widget:{obj.widget_id}",
type="Widget",
label=getattr(obj, "name", str(obj.widget_id)),
)
rels = []
if getattr(obj, "parent", None) is not None:
parent_id = walker_func._get_id(obj.parent)
rels.append(Relation(name="parent", tuples=[(widget_atom.id, parent_id)]))
return [widget_atom], rels
```

## Required checks

1. Import path registers class (decorator executes on import).
2. Priority is `>=100` (built-ins reserve lower range).
3. `spytial.evaluate(sample)` shows expected atoms/relations.
4. `spytial.diagram(sample)` renders without missing-edge surprises.

## Common mistakes

- Implementing relationalizer when selectors/operations were enough.
- Returning unstable IDs across runs, breaking sequence/view consistency.
- Skipping `evaluate()` and debugging only in diagram view.
- Forgetting to import module containing the decorated class.

## Handoff expectations

When adding a custom relationalizer, include:

- Why built-ins were insufficient.
- Chosen atom types and relation names.
- One or two selectors that rely on the new relations.
86 changes: 86 additions & 0 deletions skills/add-spytial-python/references/selector-cheatsheet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Selector Cheatsheet

This cheatsheet is based on `simple-graph-query` syntax as used by sPyTial selectors.

## Core set and relation operators

- Union: `A + B`
- Intersection: `A & B`
- Difference: `A - B`
- Join: `A.rel` or `rel.Type`
- Product: `A -> B`
- Transpose: `~rel`
- Transitive closure: `^rel`
- Reflexive-transitive closure: `*rel`

Examples:

- `Node.key`
- `edge.edge`
- `~parent`
- `^next`
- `Node - NoneType`

## Predicates and logic

- Membership: `x in S`
- Equality/inequality: `x = y`, `x != y`
- Boolean ops: `and`, `or`, `!`, `=>`, `<=>`
- Quantifiers: `all`, `some`, `no`, `one`, `lone`

Examples:

- `some x: Node | x in roots`
- `all x: Item | some y: Item | x != y`
- `all disj i, j: Int | not i = j`

## Comprehensions (great for `group`/`orientation` selectors)

Unary:

```txt
{x : Item | x.value > 10}
```

Binary:

```txt
{b : Basket, a : Fruit | (a in b.fruit) and a.status = Rotten}
```

Numeric ordering (common for array-backed structures):

```txt
{x, y : idx[object][object] | @num:(x[idx[object]]) < @num:(y[idx[object]])}
```

## Built-ins that matter most

- `univ`: all atoms
- `iden`: identity relation (all `(a, a)` pairs)
- `Int`: integer atoms

## Label conversion helpers

- `@:(expr)` convert to string label form
- `@num:(expr)` convert to number for numeric comparisons

Examples:

- `@:(n14) = @:(12)`
- `@num:(x[idx[object]]) < @num:(y[idx[object]])`

## Reserved keyword identifiers

If a field/type name conflicts with a keyword, use backticks:

- `` `set` ``
- `` item0.`in` ``

## Debugging workflow for selectors

1. Open `spytial.evaluate(obj)` to inspect available atoms/relations.
2. Start with a broad selector (`TypeName` or relation name).
3. Add one operator at a time (`&`, `-`, join, then comprehension).
4. Re-run and verify before using it in an annotation.
5. Keep selectors readable; factor complex expressions into local constants.
Loading