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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -0
  3. package/agents/rnd-builder.md +98 -0
  4. package/agents/rnd-integrator.md +104 -0
  5. package/agents/rnd-planner.md +208 -0
  6. package/agents/rnd-verifier.md +164 -0
  7. package/dist/doctor.js +166 -0
  8. package/dist/doctor.js.map +1 -0
  9. package/dist/gates/bash-discipline.js +27 -0
  10. package/dist/gates/bash-discipline.js.map +1 -0
  11. package/dist/gates/read-evidence-pack.js +23 -0
  12. package/dist/gates/read-evidence-pack.js.map +1 -0
  13. package/dist/gates/registry.js +24 -0
  14. package/dist/gates/registry.js.map +1 -0
  15. package/dist/gates/rnd-dir-required.js +31 -0
  16. package/dist/gates/rnd-dir-required.js.map +1 -0
  17. package/dist/index.js +20 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/orchestrator/prompts.js +58 -0
  20. package/dist/orchestrator/prompts.js.map +1 -0
  21. package/dist/orchestrator/rnd-dir.js +20 -0
  22. package/dist/orchestrator/rnd-dir.js.map +1 -0
  23. package/dist/orchestrator/spawn.js +67 -0
  24. package/dist/orchestrator/spawn.js.map +1 -0
  25. package/dist/orchestrator/start.js +195 -0
  26. package/dist/orchestrator/start.js.map +1 -0
  27. package/dist/orchestrator/state.js +15 -0
  28. package/dist/orchestrator/state.js.map +1 -0
  29. package/dist/orchestrator/types.js +2 -0
  30. package/dist/orchestrator/types.js.map +1 -0
  31. package/docs/PI-API.md +574 -0
  32. package/docs/PORTING.md +105 -0
  33. package/package.json +57 -0
  34. package/skills/fp-practices/SKILL.md +128 -0
  35. package/skills/fp-practices/bash.md +114 -0
  36. package/skills/fp-practices/duckdb.md +116 -0
  37. package/skills/fp-practices/elixir.md +115 -0
  38. package/skills/fp-practices/javascript.md +119 -0
  39. package/skills/fp-practices/koka.md +120 -0
  40. package/skills/fp-practices/lean.md +120 -0
  41. package/skills/fp-practices/postgresql.md +120 -0
  42. package/skills/fp-practices/python.md +120 -0
  43. package/skills/fp-practices/svelte.md +114 -0
  44. package/skills/kiss-practices/SKILL.md +41 -0
  45. package/skills/kiss-practices/bash.md +70 -0
  46. package/skills/kiss-practices/duckdb.md +30 -0
  47. package/skills/kiss-practices/elixir.md +38 -0
  48. package/skills/kiss-practices/javascript.md +43 -0
  49. package/skills/kiss-practices/koka.md +34 -0
  50. package/skills/kiss-practices/lean.md +45 -0
  51. package/skills/kiss-practices/markdown.md +20 -0
  52. package/skills/kiss-practices/postgresql.md +31 -0
  53. package/skills/kiss-practices/python.md +64 -0
  54. package/skills/kiss-practices/svelte.md +59 -0
  55. package/skills/rnd-building/SKILL.md +256 -0
  56. package/skills/rnd-decomposition/SKILL.md +188 -0
  57. package/skills/rnd-experiments/SKILL.md +197 -0
  58. package/skills/rnd-failure-modes/SKILL.md +222 -0
  59. package/skills/rnd-iteration/SKILL.md +170 -0
  60. package/skills/rnd-orchestration/SKILL.md +314 -0
  61. package/skills/rnd-scaling/SKILL.md +188 -0
  62. package/skills/rnd-verification/SKILL.md +248 -0
  63. 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.