pi-rnd 0.2.1
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.
- package/LICENSE +21 -0
- package/README.md +74 -0
- package/agents/rnd-builder.md +98 -0
- package/agents/rnd-integrator.md +104 -0
- package/agents/rnd-planner.md +208 -0
- package/agents/rnd-verifier.md +164 -0
- package/dist/doctor.js +166 -0
- package/dist/doctor.js.map +1 -0
- package/dist/gates/bash-discipline.js +27 -0
- package/dist/gates/bash-discipline.js.map +1 -0
- package/dist/gates/read-evidence-pack.js +23 -0
- package/dist/gates/read-evidence-pack.js.map +1 -0
- package/dist/gates/registry.js +24 -0
- package/dist/gates/registry.js.map +1 -0
- package/dist/gates/rnd-dir-required.js +31 -0
- package/dist/gates/rnd-dir-required.js.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator/prompts.js +58 -0
- package/dist/orchestrator/prompts.js.map +1 -0
- package/dist/orchestrator/rnd-dir.js +20 -0
- package/dist/orchestrator/rnd-dir.js.map +1 -0
- package/dist/orchestrator/spawn.js +67 -0
- package/dist/orchestrator/spawn.js.map +1 -0
- package/dist/orchestrator/start.js +195 -0
- package/dist/orchestrator/start.js.map +1 -0
- package/dist/orchestrator/state.js +15 -0
- package/dist/orchestrator/state.js.map +1 -0
- package/dist/orchestrator/types.js +2 -0
- package/dist/orchestrator/types.js.map +1 -0
- package/docs/PI-API.md +574 -0
- package/docs/PORTING.md +105 -0
- package/package.json +57 -0
- package/skills/fp-practices/SKILL.md +128 -0
- package/skills/fp-practices/bash.md +114 -0
- package/skills/fp-practices/duckdb.md +116 -0
- package/skills/fp-practices/elixir.md +115 -0
- package/skills/fp-practices/javascript.md +119 -0
- package/skills/fp-practices/koka.md +120 -0
- package/skills/fp-practices/lean.md +120 -0
- package/skills/fp-practices/postgresql.md +120 -0
- package/skills/fp-practices/python.md +120 -0
- package/skills/fp-practices/svelte.md +114 -0
- package/skills/kiss-practices/SKILL.md +41 -0
- package/skills/kiss-practices/bash.md +70 -0
- package/skills/kiss-practices/duckdb.md +30 -0
- package/skills/kiss-practices/elixir.md +38 -0
- package/skills/kiss-practices/javascript.md +43 -0
- package/skills/kiss-practices/koka.md +34 -0
- package/skills/kiss-practices/lean.md +45 -0
- package/skills/kiss-practices/markdown.md +20 -0
- package/skills/kiss-practices/postgresql.md +31 -0
- package/skills/kiss-practices/python.md +64 -0
- package/skills/kiss-practices/svelte.md +59 -0
- package/skills/rnd-building/SKILL.md +256 -0
- package/skills/rnd-decomposition/SKILL.md +188 -0
- package/skills/rnd-experiments/SKILL.md +197 -0
- package/skills/rnd-failure-modes/SKILL.md +222 -0
- package/skills/rnd-iteration/SKILL.md +170 -0
- package/skills/rnd-orchestration/SKILL.md +314 -0
- package/skills/rnd-scaling/SKILL.md +188 -0
- package/skills/rnd-verification/SKILL.md +248 -0
- package/skills/using-rnd-framework/SKILL.md +65 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Koka — FP Patterns
|
|
2
|
+
|
|
3
|
+
Koka-specific patterns. Leverages algebraic effects, Perceus reference counting, and FBIP-aware functional style.
|
|
4
|
+
|
|
5
|
+
## 1. Algebraic Effects as Composition Primitives
|
|
6
|
+
|
|
7
|
+
Declare effects explicitly; compose operations through effect types rather than hidden state.
|
|
8
|
+
|
|
9
|
+
**Do:**
|
|
10
|
+
```koka
|
|
11
|
+
effect log
|
|
12
|
+
fun emit(msg: string): ()
|
|
13
|
+
|
|
14
|
+
effect storage
|
|
15
|
+
fun get(key: string): maybe<string>
|
|
16
|
+
fun put(key: string, value: string): ()
|
|
17
|
+
|
|
18
|
+
fun process(key: string): <log,storage> string
|
|
19
|
+
emit("processing " ++ key)
|
|
20
|
+
match get(key)
|
|
21
|
+
Just(v) -> v
|
|
22
|
+
Nothing -> { put(key, "default"); "default" }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Don't:** use mutable globals or hidden IO to thread context — `<log,storage>` makes dependencies explicit and composable.
|
|
26
|
+
|
|
27
|
+
## 2. Effect Handlers as Dependency Injection
|
|
28
|
+
|
|
29
|
+
Swap implementations by swapping handlers — no interface objects needed.
|
|
30
|
+
|
|
31
|
+
**Do:**
|
|
32
|
+
```koka
|
|
33
|
+
fun with-mock-storage(action: () -> <storage|e> a): e a
|
|
34
|
+
var store := []
|
|
35
|
+
with handler
|
|
36
|
+
fun get(k) = store.lookup(k)
|
|
37
|
+
fun put(k, v) = store := store ++ [(k, v)]
|
|
38
|
+
action()
|
|
39
|
+
|
|
40
|
+
fun with-file-storage(path: string, action: () -> <storage|e> a): <io|e> a
|
|
41
|
+
with handler
|
|
42
|
+
fun get(k) = file-read(path ++ "/" ++ k).ok
|
|
43
|
+
fun put(k, v) = file-write(path ++ "/" ++ k, v)
|
|
44
|
+
action()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Don't:** pass `StorageImpl` objects as arguments and match on variants — handlers achieve the same substitution with less boilerplate.
|
|
48
|
+
|
|
49
|
+
## 3. Value Types and Immutability (Perceus)
|
|
50
|
+
|
|
51
|
+
Prefer values over references; Perceus makes in-place mutation safe for uniquely owned values.
|
|
52
|
+
|
|
53
|
+
**Do:**
|
|
54
|
+
```koka
|
|
55
|
+
struct config
|
|
56
|
+
host: string
|
|
57
|
+
port: int
|
|
58
|
+
timeout: int
|
|
59
|
+
|
|
60
|
+
fun with-timeout(c: config, t: int): config
|
|
61
|
+
Config(c.host, c.port, t) // Perceus reuses storage when c is unique
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Don't:** thread `ref<config>` through the call stack — use `ref` only when mutation is the explicit intent.
|
|
65
|
+
|
|
66
|
+
## 4. FBIP-Aware Functional Style
|
|
67
|
+
|
|
68
|
+
Recursive algorithms over unique structures reuse memory automatically (Functional But In-Place).
|
|
69
|
+
|
|
70
|
+
**Do:**
|
|
71
|
+
```koka
|
|
72
|
+
fun map-list(xs: list<a>, f: a -> e b): e list<b>
|
|
73
|
+
match xs
|
|
74
|
+
Nil -> Nil
|
|
75
|
+
Cons(h, t) -> Cons(f(h), map-list(t, f))
|
|
76
|
+
// Perceus reuses the Cons cell when xs is unique — zero allocation.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Don't:** introduce an explicit accumulator when the input is consumed — let the compiler exploit uniqueness for zero-copy reuse.
|
|
80
|
+
|
|
81
|
+
## 5. Higher-Order Functions with Effect Types
|
|
82
|
+
|
|
83
|
+
Effect polymorphism lets HOFs compose without wrapping or lifting.
|
|
84
|
+
|
|
85
|
+
**Do:**
|
|
86
|
+
```koka
|
|
87
|
+
fun filter(xs: list<a>, pred: a -> <e> bool): <e> list<a>
|
|
88
|
+
match xs
|
|
89
|
+
Nil -> Nil
|
|
90
|
+
Cons(h, t) ->
|
|
91
|
+
val rest = filter(t, pred)
|
|
92
|
+
if pred(h) then Cons(h, rest) else rest
|
|
93
|
+
|
|
94
|
+
fun pipeline(items: list<string>): <log,storage> list<string>
|
|
95
|
+
items.filter(fn(s) { emit(s); s.length > 3 })
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Don't:** monomorphise HOF signatures to `io` when the actual effects are narrower — use effect variables so callers compose freely.
|
|
99
|
+
|
|
100
|
+
## 6. Effect-Based Command-Query Separation
|
|
101
|
+
|
|
102
|
+
Separate queries (pure or read-only effects) from commands (write effects).
|
|
103
|
+
|
|
104
|
+
**Do:**
|
|
105
|
+
```koka
|
|
106
|
+
// Query: read-only effect
|
|
107
|
+
fun current-user(): <auth> user
|
|
108
|
+
auth/whoami()
|
|
109
|
+
|
|
110
|
+
// Command: write effects
|
|
111
|
+
fun grant-role(u: user, role: string): <storage,log> ()
|
|
112
|
+
storage/put("roles/" ++ u.name, role)
|
|
113
|
+
log/emit("granted " ++ role ++ " to " ++ u.name)
|
|
114
|
+
|
|
115
|
+
// Composition keeps concerns separate
|
|
116
|
+
fun promote(name: string): <auth,storage,log> ()
|
|
117
|
+
grant-role(current-user(), "admin")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Don't:** mix read and write operations in one function — keep them separate so queries are usable in pure contexts.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Lean 4 — FP Patterns
|
|
2
|
+
|
|
3
|
+
## 1. Dependent Types for Data Integrity
|
|
4
|
+
|
|
5
|
+
Encode invariants in types so violations are compile-time errors, not runtime panics.
|
|
6
|
+
|
|
7
|
+
**Do:**
|
|
8
|
+
```lean
|
|
9
|
+
def NonEmpty (α : Type) := { xs : List α // xs ≠ [] }
|
|
10
|
+
|
|
11
|
+
def head (xs : NonEmpty α) : α :=
|
|
12
|
+
match xs.val, xs.property with
|
|
13
|
+
| x :: _, _ => x
|
|
14
|
+
| [], h => absurd rfl h
|
|
15
|
+
|
|
16
|
+
def BoundedNat (n : Nat) := { k : Nat // k < n }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Don't:** use bare `List` and add runtime `if xs.isEmpty then panic!` — the type carries no proof, so callers can't trust safety without reading the implementation.
|
|
20
|
+
|
|
21
|
+
## 2. Structure / Inductive Types as Immutable Data
|
|
22
|
+
|
|
23
|
+
Prefer `structure` for product types, `inductive` for sum types. Both are immutable; update via `{ s with field := v }`.
|
|
24
|
+
|
|
25
|
+
**Do:**
|
|
26
|
+
```lean
|
|
27
|
+
structure Point where
|
|
28
|
+
x : Float
|
|
29
|
+
y : Float
|
|
30
|
+
deriving Repr
|
|
31
|
+
|
|
32
|
+
def translate (p : Point) (dx dy : Float) : Point :=
|
|
33
|
+
{ p with x := p.x + dx, y := p.y + dy }
|
|
34
|
+
|
|
35
|
+
inductive Shape
|
|
36
|
+
| circle (center : Point) (r : Float)
|
|
37
|
+
| rect (topLeft : Point) (w h : Float)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Don't:** define a class with mutable fields (`var`) for pure data — record update syntax is enough, and mutable classes break referential transparency.
|
|
41
|
+
|
|
42
|
+
## 3. Pure Functions and `where` Clauses
|
|
43
|
+
|
|
44
|
+
Keep top-level definitions pure; factor helper logic into `where` clauses to avoid polluting the module namespace.
|
|
45
|
+
|
|
46
|
+
**Do:**
|
|
47
|
+
```lean
|
|
48
|
+
def normalise (xs : List Float) : List Float :=
|
|
49
|
+
if total == 0.0 then xs else xs.map (· / total)
|
|
50
|
+
where total := xs.foldl (· + ·) 0.0
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Don't:** split a pure transformation into multiple top-level `def`s only meaningful together — `where` keeps helpers co-located and signals they are not public API.
|
|
54
|
+
|
|
55
|
+
## 4. Do Notation for Monadic Composition
|
|
56
|
+
|
|
57
|
+
Use `do`/`let`/`←` to sequence monadic steps without nesting. Reserve `IO` for actual I/O; keep business logic pure.
|
|
58
|
+
|
|
59
|
+
**Do:**
|
|
60
|
+
```lean
|
|
61
|
+
def readAndDouble (path : String) : IO Nat := do
|
|
62
|
+
let contents ← IO.FS.readFile path
|
|
63
|
+
let n := contents.trim.toNat!
|
|
64
|
+
pure (n * 2)
|
|
65
|
+
|
|
66
|
+
def pureDouble (n : Nat) : Nat := n * 2 -- pure, unit-testable
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Don't:** nest `>>=` manually for multi-step IO — do-notation is idiomatic Lean 4. Don't put pure computations inside `IO` unless they genuinely perform I/O.
|
|
70
|
+
|
|
71
|
+
## 5. Pattern Matching Over Conditionals
|
|
72
|
+
|
|
73
|
+
Use exhaustive `match` instead of chains of `if/else`. The compiler enforces totality; missing cases become errors.
|
|
74
|
+
|
|
75
|
+
**Do:**
|
|
76
|
+
```lean
|
|
77
|
+
inductive Color | red | green | blue deriving BEq
|
|
78
|
+
|
|
79
|
+
def complementOf : Color → Color
|
|
80
|
+
| .red => .cyan
|
|
81
|
+
| .green => .magenta
|
|
82
|
+
| .blue => .yellow
|
|
83
|
+
|
|
84
|
+
def describe : Option String → String
|
|
85
|
+
| some s => s!"Value: {s}"
|
|
86
|
+
| none => "Empty"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Don't:** write `if c == .red then ... else if c == .green then ...` — exhaustiveness is not checked; new constructors silently fall to the `else` branch.
|
|
90
|
+
|
|
91
|
+
## 6. IO Boundary Separation
|
|
92
|
+
|
|
93
|
+
Pure computation belongs outside `IO`. Pass values in, return values out; side effects live at the boundary.
|
|
94
|
+
|
|
95
|
+
**Do:**
|
|
96
|
+
```lean
|
|
97
|
+
-- Pure: compose, test in isolation
|
|
98
|
+
def buildReport (entries : List String) : String :=
|
|
99
|
+
entries.map (s!"- {·}") |>.intercalate "\n"
|
|
100
|
+
|
|
101
|
+
-- Effectful: only the narrow boundary touches IO
|
|
102
|
+
def writeReport (path : String) (entries : List String) : IO Unit := do
|
|
103
|
+
IO.FS.writeFile path (buildReport entries)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Don't:** mix `IO.println` calls with business logic — it couples computation to output, making the function impossible to unit-test without capturing stdout.
|
|
107
|
+
|
|
108
|
+
## 7. Tactic Composition
|
|
109
|
+
|
|
110
|
+
Lean 4 proofs are programs. Compose tactics in `by` blocks; use `<;>` to broadcast a tactic to all remaining goals.
|
|
111
|
+
|
|
112
|
+
**Do:**
|
|
113
|
+
```lean
|
|
114
|
+
theorem add_comm_zero (n : Nat) : n + 0 = n := by
|
|
115
|
+
induction n with
|
|
116
|
+
| zero => rfl
|
|
117
|
+
| succ n ih => simp [Nat.succ_add, ih]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Don't:** use `sorry` as a permanent placeholder — it makes theorems unsound and lets downstream proofs compile while being logically broken.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# PostgreSQL — FP Patterns
|
|
2
|
+
|
|
3
|
+
## 1. CTEs as Named Pipeline Stages
|
|
4
|
+
|
|
5
|
+
Express multi-step transformations as a chain of named CTEs instead of nested subqueries.
|
|
6
|
+
|
|
7
|
+
**Do:**
|
|
8
|
+
```sql
|
|
9
|
+
WITH
|
|
10
|
+
active_orders AS (
|
|
11
|
+
SELECT id, customer_id, total FROM orders WHERE status = 'active'
|
|
12
|
+
),
|
|
13
|
+
with_discounts AS (
|
|
14
|
+
SELECT ao.id, ao.customer_id,
|
|
15
|
+
ao.total * (1 - COALESCE(d.pct, 0)) AS discounted_total
|
|
16
|
+
FROM active_orders ao LEFT JOIN discounts d USING (customer_id)
|
|
17
|
+
),
|
|
18
|
+
aggregated AS (
|
|
19
|
+
SELECT customer_id, SUM(discounted_total) AS revenue
|
|
20
|
+
FROM with_discounts GROUP BY customer_id
|
|
21
|
+
)
|
|
22
|
+
SELECT * FROM aggregated ORDER BY revenue DESC;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Don't:** nest subqueries three levels deep — each level hides its stage name and makes independent testing impossible.
|
|
26
|
+
|
|
27
|
+
## 2. Set-Based Operations Over Row-by-Row Cursors
|
|
28
|
+
|
|
29
|
+
Operate on entire result sets; avoid `FETCH`/`LOOP` cursors.
|
|
30
|
+
|
|
31
|
+
**Do:**
|
|
32
|
+
```sql
|
|
33
|
+
UPDATE orders
|
|
34
|
+
SET status = 'fulfilled'
|
|
35
|
+
FROM shipments
|
|
36
|
+
WHERE orders.id = shipments.order_id
|
|
37
|
+
AND shipments.delivered_at IS NOT NULL;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Don't:**
|
|
41
|
+
```sql
|
|
42
|
+
FOR rec IN SELECT * FROM orders LOOP -- one row at a time
|
|
43
|
+
IF EXISTS (SELECT 1 FROM shipments WHERE order_id = rec.id ...) THEN
|
|
44
|
+
UPDATE orders SET status = 'fulfilled' WHERE id = rec.id;
|
|
45
|
+
END IF;
|
|
46
|
+
END LOOP;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 3. IMMUTABLE / STABLE / VOLATILE Annotations
|
|
50
|
+
|
|
51
|
+
Declare the correct volatility so the planner can cache and inline results.
|
|
52
|
+
|
|
53
|
+
**Do:**
|
|
54
|
+
```sql
|
|
55
|
+
-- Pure math → IMMUTABLE: no DB access, same inputs always give same output
|
|
56
|
+
CREATE FUNCTION tax_rate(region TEXT) RETURNS NUMERIC
|
|
57
|
+
LANGUAGE sql IMMUTABLE PARALLEL SAFE
|
|
58
|
+
RETURN CASE region WHEN 'EU' THEN 0.20 ELSE 0.10 END;
|
|
59
|
+
|
|
60
|
+
-- Reads rows, stable within a statement → STABLE
|
|
61
|
+
CREATE FUNCTION user_email(uid UUID) RETURNS TEXT
|
|
62
|
+
LANGUAGE sql STABLE
|
|
63
|
+
RETURN (SELECT email FROM users WHERE id = uid);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Don't:** leave functions as `VOLATILE` (the default) when they only read data — the planner re-executes them per row instead of caching.
|
|
67
|
+
|
|
68
|
+
## 4. Window Functions as Pure Transformations
|
|
69
|
+
|
|
70
|
+
Compute derived columns without subqueries or self-joins.
|
|
71
|
+
|
|
72
|
+
**Do:**
|
|
73
|
+
```sql
|
|
74
|
+
SELECT order_id, customer_id, amount,
|
|
75
|
+
SUM(amount) OVER w AS customer_total,
|
|
76
|
+
RANK() OVER (w ORDER BY amount DESC) AS rank_by_value,
|
|
77
|
+
amount / NULLIF(SUM(amount) OVER w, 0) AS share
|
|
78
|
+
FROM orders
|
|
79
|
+
WINDOW w AS (PARTITION BY customer_id);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Don't:** use correlated subqueries for running totals or ranks — they execute once per row and cannot compose without re-scanning.
|
|
83
|
+
|
|
84
|
+
## 5. Pure SQL Functions
|
|
85
|
+
|
|
86
|
+
Write functions as SQL expressions, not procedural routines, when no control flow is needed.
|
|
87
|
+
|
|
88
|
+
**Do:**
|
|
89
|
+
```sql
|
|
90
|
+
CREATE FUNCTION full_name(first TEXT, last TEXT) RETURNS TEXT
|
|
91
|
+
LANGUAGE sql IMMUTABLE PARALLEL SAFE
|
|
92
|
+
RETURN trim(first || ' ' || last);
|
|
93
|
+
|
|
94
|
+
CREATE FUNCTION fiscal_quarter(d DATE) RETURNS INT
|
|
95
|
+
LANGUAGE sql IMMUTABLE PARALLEL SAFE
|
|
96
|
+
RETURN EXTRACT(QUARTER FROM d)::INT;
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Don't:** wrap simple expressions in `PL/pgSQL` `BEGIN … END` — the planner cannot inline procedural bodies, losing IMMUTABLE caching.
|
|
100
|
+
|
|
101
|
+
## 6. Command-Query Separation
|
|
102
|
+
|
|
103
|
+
Read-only queries use SQL functions; mutations use procedures or explicit DML.
|
|
104
|
+
|
|
105
|
+
**Do:**
|
|
106
|
+
```sql
|
|
107
|
+
-- Query: pure SELECT, no side-effects
|
|
108
|
+
CREATE FUNCTION orders_for_customer(cid UUID)
|
|
109
|
+
RETURNS SETOF orders LANGUAGE sql STABLE
|
|
110
|
+
RETURN SELECT * FROM orders WHERE customer_id = cid;
|
|
111
|
+
|
|
112
|
+
-- Command: procedure that mutates, returns nothing
|
|
113
|
+
CREATE PROCEDURE cancel_order(oid UUID) LANGUAGE sql
|
|
114
|
+
BEGIN ATOMIC
|
|
115
|
+
UPDATE orders SET status = 'cancelled', updated_at = now()
|
|
116
|
+
WHERE id = oid;
|
|
117
|
+
END;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Don't:** return rows AND mutate in the same function — such functions cannot be called inside read-only transactions (`SET TRANSACTION READ ONLY`).
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Python — FP Patterns
|
|
2
|
+
|
|
3
|
+
Python-specific patterns for the five FP rules in SKILL.md. Idiomatic Python — not Haskell translated to Python.
|
|
4
|
+
|
|
5
|
+
## 1. Generators Over Eager Lists
|
|
6
|
+
|
|
7
|
+
Prefer generators and `itertools` for transformations. They compose without materializing intermediate collections.
|
|
8
|
+
|
|
9
|
+
**Do:**
|
|
10
|
+
```python
|
|
11
|
+
from itertools import islice
|
|
12
|
+
|
|
13
|
+
def active_names(users): return (u.name for u in users if u.active)
|
|
14
|
+
def first_ten_active(users): return list(islice(active_names(users), 10))
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Don't:** build an intermediate list just to take a prefix — `[u.name for u in users][:10]` allocates the full result when you only need 10.
|
|
18
|
+
|
|
19
|
+
## 2. Comprehensions as Transformations
|
|
20
|
+
|
|
21
|
+
Use list/dict/set comprehensions as data transformation expressions, not as substitutes for imperative loops with side effects.
|
|
22
|
+
|
|
23
|
+
**Do:**
|
|
24
|
+
```python
|
|
25
|
+
counts = {word: text.count(word) for word in vocabulary}
|
|
26
|
+
evens = [x for x in numbers if x % 2 == 0]
|
|
27
|
+
unique = {x.lower() for x in tags}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Don't:**
|
|
31
|
+
```python
|
|
32
|
+
# comprehension with side effects — this is a loop in disguise
|
|
33
|
+
[print(x) for x in items] # Don't: use a for loop instead
|
|
34
|
+
[results.append(f(x)) for x in items] # Don't: just write the for loop
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 3. functools — Partial Application and Caching
|
|
38
|
+
|
|
39
|
+
Use `functools.partial` to specialize functions, `reduce` for folds, and `lru_cache` to memoize pure functions.
|
|
40
|
+
|
|
41
|
+
**Do:**
|
|
42
|
+
```python
|
|
43
|
+
from functools import partial, reduce, lru_cache
|
|
44
|
+
|
|
45
|
+
add_tax = partial(round, ndigits=2)
|
|
46
|
+
total = reduce(lambda acc, x: acc + x.price, cart, 0.0)
|
|
47
|
+
@lru_cache(maxsize=256)
|
|
48
|
+
def fib(n: int) -> int:
|
|
49
|
+
return n if n < 2 else fib(n - 1) + fib(n - 2)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Don't:** write a custom `Memoize` class or `_cache` dict when `lru_cache` handles it. Don't use `reduce` for simple sums — `sum()` is clearer.
|
|
53
|
+
|
|
54
|
+
## 4. Frozen Dataclasses as Values
|
|
55
|
+
|
|
56
|
+
Use `@dataclass(frozen=True)` for value objects. Frozen instances are hashable, safe to use as dict keys, and cannot be mutated after construction.
|
|
57
|
+
|
|
58
|
+
**Do:**
|
|
59
|
+
```python
|
|
60
|
+
from dataclasses import dataclass, replace
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class Point:
|
|
64
|
+
x: float
|
|
65
|
+
y: float
|
|
66
|
+
|
|
67
|
+
origin = Point(0.0, 0.0)
|
|
68
|
+
shifted = replace(origin, x=1.0) # new Point; origin unchanged
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Don't:** use `@dataclass` (mutable) for domain values that should be immutable — mutation bugs surface late and are hard to trace.
|
|
72
|
+
|
|
73
|
+
## 5. NamedTuple for Lightweight Records
|
|
74
|
+
|
|
75
|
+
Use `typing.NamedTuple` for simple immutable records. Cheaper than `@dataclass(frozen=True)` and supports tuple unpacking.
|
|
76
|
+
|
|
77
|
+
**Do:**
|
|
78
|
+
```python
|
|
79
|
+
from typing import NamedTuple
|
|
80
|
+
|
|
81
|
+
class Rect(NamedTuple):
|
|
82
|
+
width: float
|
|
83
|
+
height: float
|
|
84
|
+
def area(self) -> float:
|
|
85
|
+
return self.width * self.height
|
|
86
|
+
|
|
87
|
+
w, h = Rect(3.0, 4.0) # tuple unpacking works
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Don't:** use plain `tuple` with positional indices — `rect[0] * rect[1]` breaks silently when the layout changes.
|
|
91
|
+
|
|
92
|
+
## 6. Type Hints for Immutability Intent
|
|
93
|
+
|
|
94
|
+
Use `Sequence`, `Mapping`, and `frozenset` in signatures to signal read-only intent. Use `Final` for module-level constants.
|
|
95
|
+
|
|
96
|
+
**Do:**
|
|
97
|
+
```python
|
|
98
|
+
from typing import Final, Sequence, Mapping
|
|
99
|
+
|
|
100
|
+
MAX_RETRIES: Final = 3
|
|
101
|
+
def summarize(items: Sequence[str]) -> Mapping[str, int]:
|
|
102
|
+
return {item: len(item) for item in items}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Don't:** annotate parameters as `list` or `dict` when the function only reads them — `Sequence`/`Mapping` signals the contract and prevents accidental mutation.
|
|
106
|
+
|
|
107
|
+
## 7. Command-Query Separation
|
|
108
|
+
|
|
109
|
+
A function either returns a value (query) or causes a side effect (command) — not both.
|
|
110
|
+
|
|
111
|
+
**Do:**
|
|
112
|
+
```python
|
|
113
|
+
def load_config(path: str) -> dict: # query — pure read
|
|
114
|
+
with open(path) as f: return json.load(f)
|
|
115
|
+
|
|
116
|
+
def save_config(path: str, cfg: dict) -> None: # command — write only
|
|
117
|
+
with open(path, "w") as f: json.dump(cfg, f)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Don't:** write a function that both persists data and returns it — the caller cannot use the output without triggering the side effect.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Svelte 5 — FP Patterns
|
|
2
|
+
|
|
3
|
+
Svelte 5-specific patterns for pure component logic using runes. Assumes Svelte 5 with runes mode.
|
|
4
|
+
|
|
5
|
+
## 1. $derived as Pure Computation
|
|
6
|
+
|
|
7
|
+
`$derived` is the runes equivalent of a pure function applied to reactive inputs. Never put side effects inside `$derived`.
|
|
8
|
+
|
|
9
|
+
**Do:**
|
|
10
|
+
```svelte
|
|
11
|
+
<script>
|
|
12
|
+
let { items } = $props();
|
|
13
|
+
const total = $derived(items.reduce((sum, item) => sum + item.price, 0));
|
|
14
|
+
const discounted = $derived(total > 100 ? total * 0.9 : total);
|
|
15
|
+
</script>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Don't:** put mutations or async calls inside `$derived` — use `$effect` for side effects and keep `$derived` to pure transformations only.
|
|
19
|
+
|
|
20
|
+
## 2. Pure Helper Functions for Component Logic
|
|
21
|
+
|
|
22
|
+
Extract multi-step transformations into plain functions outside the `<script>` block. Pure functions are testable without mounting a component.
|
|
23
|
+
|
|
24
|
+
**Do:**
|
|
25
|
+
```svelte
|
|
26
|
+
<script>
|
|
27
|
+
function filterActive(items) {
|
|
28
|
+
return items.filter(i => i.active);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function sortByName(items) {
|
|
32
|
+
return [...items].sort((a, b) => a.name.localeCompare(b.name));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let { items } = $props();
|
|
36
|
+
const visible = $derived(sortByName(filterActive(items)));
|
|
37
|
+
</script>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Don't:** inline multi-step logic directly in `$derived` expressions — extract named functions so each transformation is separately testable.
|
|
41
|
+
|
|
42
|
+
## 3. Immutable State Patterns with $state
|
|
43
|
+
|
|
44
|
+
Treat `$state` values as immutable: replace the whole value rather than mutating in place. Mutation bypasses Svelte's reactivity for nested objects.
|
|
45
|
+
|
|
46
|
+
**Do:**
|
|
47
|
+
```svelte
|
|
48
|
+
<script>
|
|
49
|
+
let todos = $state([]);
|
|
50
|
+
|
|
51
|
+
function addTodo(text) {
|
|
52
|
+
todos = [...todos, { id: crypto.randomUUID(), text, done: false }];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toggleTodo(id) {
|
|
56
|
+
todos = todos.map(t => t.id === id ? { ...t, done: !t.done } : t);
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Don't:** call `.push()`, `.splice()`, or assign to `array[index]` directly — mutations bypass Svelte's reactivity tracking for object and array values.
|
|
62
|
+
|
|
63
|
+
## 4. Reactive Pipelines via $derived Chains
|
|
64
|
+
|
|
65
|
+
Compose a sequence of transformations as a chain of `$derived` values, each named for the stage it represents.
|
|
66
|
+
|
|
67
|
+
**Do:**
|
|
68
|
+
```svelte
|
|
69
|
+
<script>
|
|
70
|
+
let { orders } = $props();
|
|
71
|
+
const pending = $derived(orders.filter(o => o.status === 'pending'));
|
|
72
|
+
const sorted = $derived([...pending].sort((a, b) => a.createdAt - b.createdAt));
|
|
73
|
+
const displayRows = $derived(sorted.map(o => ({ ...o, label: `#${o.id}` })));
|
|
74
|
+
</script>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Don't:** merge all transformation steps into a single large `$derived` expression — named stages make the data flow readable and each stage independently inspectable.
|
|
78
|
+
|
|
79
|
+
## 5. Snippet Composition
|
|
80
|
+
|
|
81
|
+
Use snippets (`{#snippet}`) as composable, pure rendering units. Pass data in; emit markup out. Treat snippets like pure functions from data to markup.
|
|
82
|
+
|
|
83
|
+
**Do:**
|
|
84
|
+
```svelte
|
|
85
|
+
{#snippet badge(label, variant)}
|
|
86
|
+
<span class="badge badge--{variant}">{label}</span>
|
|
87
|
+
{/snippet}
|
|
88
|
+
|
|
89
|
+
{#each items as item}
|
|
90
|
+
{@render badge(item.status, item.urgent ? 'danger' : 'info')}
|
|
91
|
+
{/each}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Don't:** write duplicated inline markup for the same visual pattern — extract it into a named snippet and `@render` it with arguments.
|
|
95
|
+
|
|
96
|
+
## 6. Command-Query Separation in Event Handlers
|
|
97
|
+
|
|
98
|
+
Event handlers are commands (they cause mutations). `$derived` values are queries (they compute from state). Never compute derived data inside an event handler.
|
|
99
|
+
|
|
100
|
+
**Do:**
|
|
101
|
+
```svelte
|
|
102
|
+
<script>
|
|
103
|
+
let items = $state([]);
|
|
104
|
+
const count = $derived(items.length); // query
|
|
105
|
+
const hasItems = $derived(items.length > 0); // query
|
|
106
|
+
|
|
107
|
+
function removeItem(id) { // command
|
|
108
|
+
items = items.filter(i => i.id !== id);
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Don't:** compute derived values (counts, labels, totals) inside event handlers — those computations belong in `$derived` so they stay in sync automatically.
|
|
114
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kiss-practices
|
|
3
|
+
description: Language-specific KISS (Keep It Simple) rules to prevent over-engineering. Load during Phase 0 discovery — detect project languages and read only the relevant files from the skill directory.
|
|
4
|
+
effort: low
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# KISS Practices
|
|
8
|
+
|
|
9
|
+
## General Rules (always apply)
|
|
10
|
+
|
|
11
|
+
- Don't add features nobody asked for
|
|
12
|
+
- Don't add error handling for scenarios that can't happen — trust internal code and framework guarantees
|
|
13
|
+
- Don't create abstractions for one-time operations — three similar lines is better than a premature helper
|
|
14
|
+
- Don't design for hypothetical future requirements — solve today's problem
|
|
15
|
+
- Don't add backwards-compatibility shims — just change the code
|
|
16
|
+
- Don't wrap framework calls in service layers unless there's real business logic to encapsulate
|
|
17
|
+
- Simple, readable code beats clever, compact code
|
|
18
|
+
- Validate at system boundaries (user input, external APIs), not at every internal function
|
|
19
|
+
|
|
20
|
+
## How to Use
|
|
21
|
+
|
|
22
|
+
**During Phase 0 (Discovery):**
|
|
23
|
+
1. Detect which languages/frameworks are present in the project (by file extensions, config files, or dependencies)
|
|
24
|
+
2. Read only the relevant language files from the `kiss-practices` skill directory (skill is auto-injected via PI's skill discovery — sibling `.md` files are in the same directory as this file)
|
|
25
|
+
3. Include the language-specific KISS rules in the discovery context passed to the Planner
|
|
26
|
+
|
|
27
|
+
**Language detection heuristics:**
|
|
28
|
+
|
|
29
|
+
| Files present | Load |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `*.ex`, `*.exs`, `mix.exs` | `elixir.md` |
|
|
32
|
+
| `*.js`, `*.ts`, `*.jsx`, `*.tsx`, `*.css`, `*.html` | `javascript.md` |
|
|
33
|
+
| `*.svelte`, `svelte.config.*` | `svelte.md` |
|
|
34
|
+
| `*.py`, `pyproject.toml`, `requirements.txt` | `python.md` |
|
|
35
|
+
| `*.sh`, `*.bash`, `Makefile` | `bash.md` |
|
|
36
|
+
| `*.md`, `CLAUDE.md`, `README.md` | `markdown.md` |
|
|
37
|
+
| `mix.exs` with `:postgrex` or `:ecto`, or `*.sql` files | `postgresql.md` |
|
|
38
|
+
| DuckDB usage, `*.duckdb` files, or analytical/data tasks | `duckdb.md` |
|
|
39
|
+
| `*.kk`, `koka.json` | `koka.md` |
|
|
40
|
+
|
|
41
|
+
**Overriding:** Projects can ship their own `kiss-practices` skill in `.pi/skills/kiss-practices/SKILL.md` to override these defaults with project-specific rules.
|