Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f708c58
research: init plutus data annotation loop
solidsnakedev Apr 14, 2026
c05eeb1
research: phase 1 complete — effect schema annotation deep-dive
solidsnakedev Apr 14, 2026
65b4c3d
research: phase 2 complete — plutus data pattern catalog
solidsnakedev Apr 14, 2026
8a2ff0e
research: phase 3 complete — 4 API design candidates
solidsnakedev Apr 14, 2026
9a00bbc
research: phase 4 complete — candidate D (hybrid) wins
solidsnakedev Apr 14, 2026
4b002e2
research: rewrite loop instruction — phases 5-10 using annotation system
solidsnakedev Apr 15, 2026
f03f3cd
research: phase 5 complete — AST compiler implementation study
solidsnakedev Apr 15, 2026
d821da7
research: phase 6 complete — annotation symbols defined
solidsnakedev Apr 15, 2026
d8809db
research: phase 7 complete — AST compiler built
solidsnakedev Apr 15, 2026
40a3d8a
research: phase 8 complete — public API wired
solidsnakedev Apr 15, 2026
76ff9af
research: phase 9 complete — edge cases and limitations
solidsnakedev Apr 15, 2026
3ee261b
research: phase 10 complete — real-world validation
solidsnakedev Apr 15, 2026
a3b9dfd
research: add phase 11 — challenge the implementation
solidsnakedev Apr 15, 2026
db45471
research: phase 11 complete — adversarial review
solidsnakedev Apr 15, 2026
fd8efa4
research: add phase 12+ continuous improvement loop
solidsnakedev Apr 15, 2026
5ca77a8
perf: fast-path TSchema codecs in Transformation handler
solidsnakedev Apr 15, 2026
ad0a781
feat: implement flatFields in annotation compiler
solidsnakedev Apr 15, 2026
19381f6
feat: Schema.Class/TaggedClass support in compiler
solidsnakedev Apr 15, 2026
a20d8bd
feat: Map auto-derivation in annotation compiler
solidsnakedev Apr 15, 2026
798420f
feat: mutual recursion proven, Effect errors deferred
solidsnakedev Apr 15, 2026
fd0350e
feat: module augmentation for type-safe Plutus annotations
solidsnakedev Apr 15, 2026
0e83076
docs: migration guide — TSchema vs Plutus.data()
solidsnakedev Apr 15, 2026
77d4ba2
refactor: eliminate all as any from production code
solidsnakedev Apr 15, 2026
ba2446e
refactor: eliminate as any from test files (31→2)
solidsnakedev Apr 15, 2026
89dd67e
fix: update loop instructions for fresh agent compatibility
solidsnakedev Apr 15, 2026
c295158
loop: add edge case sweep to backlog
solidsnakedev Apr 15, 2026
143aaff
test: edge case sweep — 30 tests, no bugs found
solidsnakedev Apr 15, 2026
a422974
fix: throw for unknown Declarations, add Set/List/Chunk support
solidsnakedev Apr 15, 2026
f414e3c
loop: add benchmark improvements to backlog
solidsnakedev Apr 15, 2026
9f36e89
bench: comprehensive benchmarks — Plutus.data() at parity with TSchema
solidsnakedev Apr 15, 2026
69725ee
loop: add enum shorthand, newtype flattening, auto-index sum types
solidsnakedev Apr 15, 2026
b0e8291
feat: Plutus.makeEnum for nullary constructor enums
solidsnakedev Apr 15, 2026
77b3eea
loop: drop newtype flattening and auto-index sum types
solidsnakedev Apr 15, 2026
3f9e2ea
refactor: rewrite loop instructions per loop-manager review
solidsnakedev Apr 15, 2026
34e7921
watchdog: cycle 15 — all clear
solidsnakedev Apr 15, 2026
f444c12
refactor: remove makeIsData, makeIsDataIndexed, makeEnum
solidsnakedev Apr 15, 2026
88acb17
loop: rewrite for cleanup — 5 phases to PR-ready
solidsnakedev Apr 16, 2026
7357751
test: consolidate 8 Plutus test files into PlutusData.test.ts
solidsnakedev Apr 16, 2026
528e786
polish: clean up PlutusSchema.ts, remove stale exports
solidsnakedev Apr 16, 2026
8980280
feat: export PlutusSchema and PlutusAnnotation from package
solidsnakedev Apr 16, 2026
469d874
chore: cleanup loop complete — PR-ready
solidsnakedev Apr 16, 2026
99063b9
chore: fix lint — import style, array types, console suppression
solidsnakedev Apr 16, 2026
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
26 changes: 26 additions & 0 deletions .claude/research/.loop-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Cleanup Loop Log

## Cycle 1 — Phase 1 — 2026-04-15
**Action:** Consolidated 8 test files into `PlutusData.test.ts` with 6 sections. Deleted old files.
**Result:** 9 files → 1 file. 176 Plutus tests, 273 total passing. -1131 lines net.
**Next:** Phase 2

## Cycle 1 — Phase 2 — 2026-04-15
**Action:** Polished production code. Removed `fromSchema` alias, duplicate `Variant` export, stale header comment. Narrowed `applyAnnotations` from `any` to `SchemaAST.AST`. Removed stale test.
**Result:** Zero `as any` in production. 272 tests, zero TS errors. Clean JSDoc on all exports.
**Next:** Phase 3

## Cycle 1 — Phase 3 — 2026-04-15
**Action:** Added `PlutusSchema` and `PlutusAnnotation` to `index.ts` barrel exports. Fixed `applyAnnotations` to use `SchemaAST.annotations()` instead of manual property descriptor clone (build was failing). `PlutusCompiler` stays internal — accessible via wildcard `"./*"` but not in barrel.
**Result:** Build passes (225 files compiled). 272 tests pass. Package exports work via both `@evolution-sdk/evolution` and `@evolution-sdk/evolution/PlutusSchema`.
**Next:** Phase 4

## Cycle 1 — Phase 4 — 2026-04-15
**Action:** Final review. Read all 5 changed files. Checked: no TODO/FIXME/HACK, no unused imports, zero `as any` in production (1 intentional in tests), describe/it names clear, no dead code. Ran full package test suite.
**Result:** 1223 tests passed, 63 skipped, 0 failed. Build passes. No issues found — PR-ready.
**Next:** Phase 5

## Cycle 1 — Phase 5 — 2026-04-15
**Action:** PR prep. Reviewed commit history (40+ commits). Prepared PR description. Checked main divergence (5 commits ahead). Did NOT push or create PR.
**Result:** Branch ready for PR. Needs rebase onto latest main before merge. Squashing is optional (user decision).
**Next:** Done — cleanup loop complete
2 changes: 2 additions & 0 deletions .claude/research/.loop-phase
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
phase: done
cycle: 1
335 changes: 335 additions & 0 deletions .claude/research/migration-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
# Migration Guide: TSchema → Plutus.data()

Side-by-side examples showing how to migrate from manual TSchema combinators to annotation-driven `Plutus.data()`.

Both paths produce **identical CBOR** — this is a developer experience improvement, not a breaking change. Existing TSchema code continues to work unchanged.

## When to Use Which

| Use `Plutus.data()` when... | Use TSchema directly when... |
|---|---|
| Starting new code | Existing code already works |
| Want standard Effect Schema types | Need TSchema-specific features |
| Want auto-inference (bigint → Integer) | Want explicit control over encoding |
| Using Schema.Class/TaggedClass | Using Variant (Aiken-style wrapper API) |

## Primitives

```typescript
// ─── TSchema ───
const Hash = TSchema.ByteArray // Uint8Array
const Amount = TSchema.Integer // bigint
const Active = TSchema.Boolean // boolean → Constr(0/1)

// ─── Plutus.data() ───
// Inside Plutus.data(), use standard Effect Schema types:
const MyStruct = Plutus.data(Schema.Struct({
hash: Schema.Uint8ArrayFromSelf, // auto-inferred as ByteArray
amount: Schema.BigIntFromSelf, // auto-inferred as Integer
active: Schema.Boolean // auto-inferred as Boolean Constr(0/1)
}))

// Standalone primitives: use Plutus re-exports (same as TSchema)
const Hash = Plutus.ByteArray
const Amount = Plutus.Integer
```

## Struct (S1-S2)

```typescript
// ─── TSchema ───
const MyDatum = TSchema.Struct({
owner: TSchema.ByteArray,
amount: TSchema.Integer
})
// Constr(0, [ownerBytes, amountInt])

const MyAction = TSchema.Struct(
{ value: TSchema.Integer },
{ index: 5 }
)
// Constr(5, [value])

// ─── Plutus.data() ───
const MyDatum = Plutus.data(Schema.Struct({
owner: Schema.Uint8ArrayFromSelf,
amount: Schema.BigIntFromSelf
}))

const MyAction = Plutus.data(
Schema.Struct({ value: Schema.BigIntFromSelf }),
{ index: 5 }
)

// Or with annotations directly:
const MyAction = Plutus.data(
Schema.Struct({ value: Schema.BigIntFromSelf })
.annotations({ [Plutus.ConstrIndexId]: 5 })
)

// Or using makeIsData shorthand:
const MyDatum = Plutus.makeIsData({
owner: Schema.Uint8ArrayFromSelf,
amount: Schema.BigIntFromSelf
})
```

## Nested Struct (S3)

```typescript
// ─── TSchema ───
const Inner = TSchema.Struct({ x: TSchema.Integer, y: TSchema.Integer })
const Outer = TSchema.Struct({ inner: Inner, z: TSchema.Integer })
// Constr(0, [Constr(0, [x, y]), z])

// ─── Plutus.data() ───
const Outer = Plutus.data(Schema.Struct({
inner: Schema.Struct({
x: Schema.BigIntFromSelf,
y: Schema.BigIntFromSelf
}),
z: Schema.BigIntFromSelf
}))
```

## Flat Fields (S4)

```typescript
// ─── TSchema ───
const Inner = TSchema.Struct(
{ x: TSchema.Integer, y: TSchema.Integer },
{ flatFields: true }
)
const Outer = TSchema.Struct({ inner: Inner, z: TSchema.Integer })
// Constr(0, [x, y, z]) — inner fields inlined

// ─── Plutus.data() ───
const Inner = Schema.Struct({
x: Schema.BigIntFromSelf,
y: Schema.BigIntFromSelf
}).annotations({ [Plutus.FlatFieldsId]: true })

const Outer = Plutus.data(Schema.Struct({
inner: Inner,
z: Schema.BigIntFromSelf
}))
```

## Tag Field (S5-S7)

```typescript
// ─── TSchema ───
// Auto-detected (_tag, type, kind, variant)
const Tagged = TSchema.Struct({
_tag: TSchema.Literal("Mint"),
amount: TSchema.Integer
})
// Constr(0, [amount]) — _tag stripped

// ─── Plutus.data() ───
const Tagged = Plutus.data(Schema.Struct({
_tag: Schema.Literal("Mint"),
amount: Schema.BigIntFromSelf
}))
// Same: _tag auto-detected and stripped

// Disable tag stripping:
const NoStrip = Plutus.data(
Schema.Struct({
_tag: Schema.Literal("Mint"),
amount: Schema.BigIntFromSelf
}),
{ tagField: false }
)
```

## Sum Types / Variant (U2, U5)

```typescript
// ─── TSchema ───
const Credential = TSchema.Variant({
VerificationKey: { hash: TSchema.ByteArray },
Script: { hash: TSchema.ByteArray }
})
// Usage: { VerificationKey: { hash: bytes } }
// PubKey → Constr(0, [hash]), Script → Constr(1, [hash])

// ─── Plutus.data() — makeIsDataIndexed ───
const Credential = Plutus.makeIsDataIndexed(
{
VerificationKey: { hash: Schema.Uint8ArrayFromSelf },
Script: { hash: Schema.Uint8ArrayFromSelf }
},
{ VerificationKey: 0, Script: 1 }
)
// Usage: { _tag: "VerificationKey", hash: bytes }
// Same CBOR: Constr(0, [hash]) / Constr(1, [hash])

// ─── Plutus.data() — manual annotations ───
const Credential = Plutus.data(Schema.Union(
Schema.Struct({
_tag: Schema.Literal("VerificationKey"),
hash: Schema.Uint8ArrayFromSelf
}).annotations({ [Plutus.ConstrIndexId]: 0, [Plutus.FlatInUnionId]: true }),
Schema.Struct({
_tag: Schema.Literal("Script"),
hash: Schema.Uint8ArrayFromSelf
}).annotations({ [Plutus.ConstrIndexId]: 1, [Plutus.FlatInUnionId]: true })
))

// ─── TSchema Variant still available via Plutus.Variant ───
const Credential = Plutus.Variant({
VerificationKey: { hash: Plutus.ByteArray },
Script: { hash: Plutus.ByteArray }
})
```

**API difference**: TSchema.Variant uses `{ Name: { fields } }` wrapper objects. `makeIsDataIndexed` uses `{ _tag: "Name", ...fields }` discriminated unions. CBOR output is identical.

## Option / Nullable (N1-N2)

```typescript
// ─── TSchema ───
const OptInt = TSchema.NullOr(TSchema.Integer)
const MaybeBytes = TSchema.UndefinedOr(TSchema.ByteArray)

// ─── Plutus.data() ───
const OptInt = Plutus.data(Schema.NullOr(Schema.BigIntFromSelf))
const MaybeBytes = Plutus.data(Schema.UndefinedOr(Schema.Uint8ArrayFromSelf))

// In struct fields — auto-detected:
const WithOptional = Plutus.data(Schema.Struct({
value: Schema.BigIntFromSelf,
optional: Schema.NullOr(Schema.BigIntFromSelf)
}))
```

## Map (C2)

```typescript
// ─── TSchema ───
const Value = TSchema.Map(TSchema.ByteArray, TSchema.Map(TSchema.ByteArray, TSchema.Integer))

// ─── Plutus.data() ───
const Value = Plutus.data(Schema.MapFromSelf({
key: Schema.Uint8ArrayFromSelf,
value: Schema.MapFromSelf({
key: Schema.Uint8ArrayFromSelf,
value: Schema.BigIntFromSelf
})
}))

// Or use Plutus.Map (re-export of TSchema.Map):
const Value = Plutus.Map(Plutus.ByteArray, Plutus.Map(Plutus.ByteArray, Plutus.Integer))
```

## Array / List (C1)

```typescript
// ─── TSchema ───
const Hashes = TSchema.Array(TSchema.ByteArray)

// ─── Plutus.data() ───
const Hashes = Plutus.data(Schema.Array(Schema.Uint8ArrayFromSelf))
```

## Recursive Types (R1-R3)

```typescript
// ─── TSchema ───
interface LinkedList { value: bigint; next: LinkedList | null }
const LinkedList: Schema.Schema<LinkedList> = TSchema.Struct({
value: TSchema.Integer,
next: TSchema.NullOr(Schema.suspend(() => LinkedList))
})

// ─── Plutus.data() ───
const LinkedList: Schema.Schema<LinkedList, Data.Data> = Plutus.data(
Schema.Struct({
value: Schema.BigIntFromSelf,
next: Schema.NullOr(Schema.suspend(() => LinkedList as any))
})
) as any

// MultisigScript (recursive sum type):
const NativeScript = Plutus.makeIsDataIndexed(
{
ScriptPubkey: { key_hash: Schema.Uint8ArrayFromSelf },
ScriptAll: { scripts: Schema.Array(Schema.suspend(() => NativeScript as any)) },
ScriptAny: { scripts: Schema.Array(Schema.suspend(() => NativeScript as any)) },
ScriptNOfK: {
n: Schema.BigIntFromSelf,
scripts: Schema.Array(Schema.suspend(() => NativeScript as any))
},
TimelockStart: { time: Schema.BigIntFromSelf },
TimelockExpiry: { time: Schema.BigIntFromSelf }
},
{ ScriptPubkey: 0, ScriptAll: 1, ScriptAny: 2, ScriptNOfK: 3, TimelockStart: 4, TimelockExpiry: 5 }
)
```

## Schema.Class / Schema.TaggedClass

```typescript
// ─── Schema.Class works directly with Plutus.data() ───
class MyDatum extends Schema.Class<MyDatum>("MyDatum")({
owner: Schema.Uint8ArrayFromSelf,
amount: Schema.BigIntFromSelf
}) {}

const PlutusMyDatum = Plutus.data(MyDatum)
const codec = Plutus.codec(PlutusMyDatum)
codec.toData(new MyDatum({ owner: bytes, amount: 42n }))
// Constr(0, [ownerBytes, 42n])

// TaggedClass — _tag auto-stripped
class Action extends Schema.TaggedClass<Action>()("Mint", {
amount: Schema.BigIntFromSelf
}) {}

const PlutusAction = Plutus.data(Action)
// Constr(0, [amount]) — _tag:"Mint" stripped during encoding, injected during decoding
```

## Codec Usage

```typescript
// Both TSchema and Plutus.data() work with Plutus.codec() / Data.withSchema()
const codec = Plutus.codec(mySchema)

codec.toData(value) // TS value → Data.Data
codec.fromData(data) // Data.Data → TS value
codec.toCBORHex(value) // TS value → CBOR hex string
codec.fromCBORHex(hex) // CBOR hex string → TS value
codec.toCBORBytes(value) // TS value → Uint8Array
codec.fromCBORBytes(bytes) // Uint8Array → TS value
```

## Real-World Example: Cardano Address

```typescript
// ─── TSchema (current) ───
const Credential = TSchema.Variant({
VerificationKey: { hash: TSchema.ByteArray },
Script: { hash: TSchema.ByteArray }
})
const Address = TSchema.Struct({
payment_credential: Credential,
stake_credential: TSchema.UndefinedOr(StakeCredential)
})

// ─── Plutus.data() (new) ───
const Credential = Plutus.makeIsDataIndexed(
{
VerificationKey: { hash: Schema.Uint8ArrayFromSelf },
Script: { hash: Schema.Uint8ArrayFromSelf }
},
{ VerificationKey: 0, Script: 1 }
)
const Address = Plutus.data(Schema.Struct({
payment_credential: Credential,
stake_credential: Schema.UndefinedOr(StakeCredential)
}))

// CBOR output is byte-for-byte identical.
```
Loading
Loading