plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


tooling/tree-sitter-plum/test/corpus/.recall/history.md
3971d41 1
# Recall History — corpus
3971d41 2
3971d41 3
3971d41 4
## Session afc99e8b — 2026-07-24 10:40
3971d41 5
3971d41 6
**You:** Can we update the Generic syntax to this one?
3971d41 7
# A node stores the data in a list and contains pointers to the previous and next sibling nodes
3971d41 8
type Node[T] =
3971d41 9
  value: T
3971d41 10
  prev: Option[Node]
3971d41 11
  next: Option[Node]
3971d41 12
  - `Skill: superpowers:brainstorming`
3971d41 13
3971d41 14
**You:** Base directory for this skill: /Users/pyrossh/.claude/plugins/cache/superpowers-dev/superpowers/6.1.1/skills/brainstorming
3971d41 15
3971d41 16
# Brainstorming Ideas Into Designs
3971d41 17
3971d41 18
Help turn ideas into fully formed designs and specs through natural collaborative dialogue.
3971d41 19
3971d41 20
Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design and get user approval.
3971d41 21
3971d41 22
<HARD-GATE>
3971d41 23
Do NOT invoke any implementation skill, write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it. This applies to EVERY project regardless of perceived simplicity.
3971d41 24
</HARD-GATE>
3971d41 25
3971d41 26
## Anti-Pattern: "This Is Too Simple To Need A Design"
3971d41 27
3971d41 28
Every project goes through this process. A todo list, a single-function utility, a config change — all of them. "Simple" projects are where unexamined assumptions cause the most wasted work. The design can be short (a few sentences for truly simple projects), but you MUST present it and get approval.
3971d41 29
3971d41 30
## Checklist
3971d41 31
3971d41 32
You MUST create a task for each of these items and complete them in order:
3971d41 33
3971d41 34
1. **Explore project context** — check files, docs, recent commits
3971d41 35
2. **Offer the visual companion just-in-time** — NOT upfront. The first time a question would genuinely be clearer shown than described, offer it then (its own message); on approval its browser tab opens for you. If no visual question ever arises, never offer it. See the Visual Companion section below.
3971d41 36
3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria
3971d41 37
4. **Propose 2-3 approaches** — with trade-offs and your recommendation
3971d41 38
5. **Present design** — in sections scaled to their complexity, get user approval after each section
3971d41 39
6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` and commit
3971d41 40
7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below)
3971d41 41
8. **User reviews written spec** — ask user to review the spec file before proceeding
3971d41 42
9. **Transition to implementation** — invoke writing-plans skill to create implementation plan
3971d41 43
3971d41 44
## Process Flow
3971d41 45
3971d41 46
```dot
3971d41 47
digraph brainstorming {
3971d41 48
    "Explore project context" [shape=box];
3971d41 49
    "Ask clarifying questions" [shape=box];
3971d41 50
    "Propose 2-3 approaches" [shape=box];
3971d41 51
    "Present design sections" [shape=box];
3971d41 52
    "User approves design?" [shape=diamond];
3971d41 53
    "Write design doc" [shape=box];
3971d41 54
    "Spec self-review\n(fix inline)" [shape=box];
3971d41 55
    "User reviews spec?" [shape=diamond];
3971d41 56
    "Invoke writing-plans skill" [shape=doublecircle];
3971d41 57
3971d41 58
    "Explore project context" -> "Ask clarifying questions";
3971d41 59
    "Ask clarifying questions" -> "Propose 2-3 approaches";
3971d41 60
    "Propose 2-3 approaches" -> "Present design sections";
3971d41 61
    "Present design sections" -> "User approves design?";
3971d41 62
    "User approves design?" -> "Present design sections" [label="no, revise"];
3971d41 63
    "User approves design?" -> "Write design doc" [label="yes"];
3971d41 64
    "Write design doc" -> "Spec self-review\n(fix inline)";
3971d41 65
    "Spec self-review\n(fix inline)" -> "User reviews spec?";
3971d41 66
    "User reviews spec?" -> "Write design doc" [label="changes requested"];
3971d41 67
    "User reviews spec?" -> "Invoke writing-plans skill" [label="approved"];
3971d41 68
}
3971d41 69
```
3971d41 70
3971d41 71
**The terminal state is invoking writing-plans.** Do NOT invoke frontend-design, mcp-builder, or any other implementation skill. The ONLY skill you invoke after brainstorming is writing-plans.
3971d41 72
3971d41 73
## The Process
3971d41 74
3971d41 75
**Understanding the idea:**
3971d41 76
3971d41 77
- Check out the current project state first (files, docs, recent commits)
3971d41 78
- Before asking detailed questions, assess scope: if the request describes multiple independent subsystems (e.g., "build a platform with chat, file storage, billing, and analytics"), flag this immediately. Don't spend questions refining details of a project that needs to be decomposed first.
3971d41 79
- If the project is too large for a single spec, help the user decompose into sub-projects: what are the independent pieces, how do they relate, what order should they be built? Then brainstorm the first sub-project through the normal design flow. Each sub-project gets its own spec → plan → implementation cycle.
3971d41 80
- For appropriately-scoped projects, ask questions one at a time to refine the idea
3971d41 81
- Prefer multiple choice questions when possible, but open-ended is fine too
3971d41 82
- Only one question per message - if a topic needs more exploration, break it into multiple questions
3971d41 83
- Focus on understanding: purpose, constraints, success criteria
3971d41 84
3971d41 85
**Exploring approaches:**
3971d41 86
3971d41 87
- Propose 2-3 different approaches with trade-offs
3971d41 88
- Present options conversationally with your recommendation and reasoning
3971d41 89
- Lead with your recommended option and explain why
3971d41 90
3971d41 91
**Presenting the design:**
3971d41 92
3971d41 93
- Once you believe you understand what you're building, present the design
3971d41 94
- Scale each section to its complexity: a few sentences if straightforward, up to 200-300 words if nuanced
3971d41 95
- Ask after each section whether it looks right so far
3971d41 96
- Cover: architecture, components, data flow, error handling, testing
3971d41 97
- Be ready to go back and clarify if something doesn't make sense
3971d41 98
3971d41 99
**Design for isolation and clarity:**
3971d41 100
3971d41 101
- Break the system into smaller units that each have one clear purpose, communicate through well-defined interfaces, and can be understood and tested independently
3971d41 102
- For each unit, you should be able to answer: what does it do, how do you use it, and what does it depend on?
3971d41 103
- Can someone understand what a unit does without reading its internals? Can you change the internals without breaking consumers? If not, the boundaries need work.
3971d41 104
- Smaller, well-bounded units are also easier for you to work with - you reason better about code you can hold in context at once, and your edits are more reliable when files are focused. When a file grows large, that's often a signal that it's doing too much.
3971d41 105
3971d41 106
**Working in existing codebases:**
3971d41 107
3971d41 108
- Explore the current structure before proposing changes. Follow existing patterns.
3971d41 109
- Where existing code has problems that affect the work (e.g., a file that's grown too large, unclear boundaries, tangled responsibilities), include targeted improvements as part of the design - the way a good developer improves code they're working in.
3971d41 110
- Don't propose unrelated refactoring. Stay focused on what serves the current goal.
3971d41 111
3971d41 112
## After the Design
3971d41 113
3971d41 114
**Documentation:**
3971d41 115
3971d41 116
- Write the validated design (spec) to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md`
3971d41 117
  - (User preferences for spec location override this default)
3971d41 118
- Use elements-of-style:writing-clearly-and-concisely skill if available
3971d41 119
- Commit the design document to git
3971d41 120
3971d41 121
**Spec Self-Review:**
3971d41 122
After writing the spec document, look at it with fresh eyes:
3971d41 123
3971d41 124
1. **Placeholder scan:** Any "TBD", "TODO", incomplete sections, or vague requirements? Fix them.
3971d41 125
2. **Internal consistency:** Do any sections contradict each other? Does the architecture match the feature descriptions?
3971d41 126
3. **Scope check:** Is this focused enough for a single implementation plan, or does it need decomposition?
3971d41 127
4. **Ambiguity check:** Could any requirement be interpreted two different ways? If so, pick one and make it explicit.
3971d41 128
3971d41 129
Fix any issues inline. No need to re-review — just fix and move on.
3971d41 130
3971d41 131
**User Review Gate:**
3971d41 132
After the spec review loop passes, ask the user to review the written spec before proceeding:
3971d41 133
3971d41 134
> "Spec written and committed to `<path>`. Please review it and let me know if you want to make any changes before we start writing out the implementation plan."
3971d41 135
3971d41 136
Wait for the user's response. If they request changes, make them and re-run the spec review loop. Only proceed once the user approves.
3971d41 137
3971d41 138
**Implementation:**
3971d41 139
3971d41 140
- Invoke the writing-plans skill to create a detailed implementation plan
3971d41 141
- Do NOT invoke any other skill. writing-plans is the next step.
3971d41 142
3971d41 143
## Key Principles
3971d41 144
3971d41 145
- **One question at a time** - Don't overwhelm with multiple questions
3971d41 146
- **Multiple choice preferred** - Easier to answer than open-ended when possible
3971d41 147
- **YAGNI ruthlessly** - Remove unnecessary features from all designs
3971d41 148
- **Explore alternatives** - Always propose 2-3 approaches before settling
3971d41 149
- **Incremental validation** - Present design, get approval before moving on
3971d41 150
- **Be flexible** - Go back and clarify when something doesn't make sense
3971d41 151
3971d41 152
## Visual Companion
3971d41 153
3971d41 154
A browser-based companion for showing mockups, diagrams, and visual options during brainstorming. Available as a tool — not a mode. Accepting the companion means it's available for questions that benefit from visual treatment; it does NOT mean every question goes through the browser.
3971d41 155
3971d41 156
**Offering the companion (just-in-time):** Do NOT offer it upfront. Wait until a question would genuinely be clearer shown than told — a real mockup / layout / diagram question, not merely a UI *topic*. The first time that happens, offer it then, as its own message:
3971d41 157
> "This next part might be easier if I show you — I can put together mockups, diagrams, and comparisons in a browser tab as we go. It's still new and can be token-intensive. Want me to? I'll open it for you."
3971d41 158
3971d41 159
**This offer MUST be its own message.** Only the offer — no clarifying question, summary, or other content. Wait for the user's response. If they accept, start the server with `--open` so their browser opens to the first screen automatically. If they decline, continue text-only and don't offer again unless they raise it.
3971d41 160
3971d41 161
**Per-question decision:** Even after the user accepts, decide FOR EACH QUESTION whether to use the browser or the terminal. The test: **would the user understand this better by seeing it than reading it?**
3971d41 162
3971d41 163
- **Use the browser** for content that IS visual — mockups, wireframes, layout comparisons, architecture diagrams, side-by-side visual designs
3971d41 164
- **Use the terminal** for content that is text — requirements questions, conceptual choices, tradeoff lists, A/B/C/D text options, scope decisions
3971d41 165
3971d41 166
A question about a UI topic is not automatically a visual question. "What does personality mean in this context?" is a conceptual question — use the terminal. "Which wizard layout works better?" is a visual question — use the browser.
3971d41 167
3971d41 168
If they agree to the companion, read the detailed guide before proceeding:
3971d41 169
`skills/brainstorming/visual-companion.md`
3971d41 170
  - `Bash: grep -n "Node\[" /Users/pyrossh/Code/plum/libs/std/*.plum /Users/pyrossh/Code/plum/examples/*.plum 2>/dev/null | head -30`
3971d41 171
  - `Bash: grep -rn "type.*\[T\]\|Option\[" /Users/pyrossh/Code/plum/plum-core/src/*.rs /Users/pyrossh/Code/plum/tooling/tree-sitter-plum/grammar.js 2>/dev/null | head -40`
3971d41 172
  - `Read: /Users/pyrossh/Code/plum/libs/std/list.plum`
3971d41 173
  - `Bash: grep -n "generic\|type_param\|\\[T\\]\|angle" /Users/pyrossh/Code/plum/tooling/tree-sitter-plum/grammar.js | head -40`
3971d41 174
  - `Bash: echo "=== grammar.js generics-related section ===" && sed -n '50,170p' tooling/tree-sitter-plum/grammar.js`
3971d41 175
  - `Bash: git diff libs/std/list.plum examples/methods.plum | head -100`
3971d41 176
  - `Read: /Users/pyrossh/Code/plum/docs/superpowers/plans/2026-07-24-list-methods.md`
3971d41 177
3971d41 178
**Claude:** I want to scope this properly since it touches the grammar, parser, checker, codegen, and stdlib — not just a stdlib file. Quick question first:
3971d41 179
  - `AskUserQuestion`
3971d41 180
  - `AskUserQuestion`
3971d41 181
3971d41 182
**Claude:** The user wants to describe a different form for the trait-bounded case. What should it look like?
3971d41 183
3971d41 184
**You:** type List[T: Stringable](Stringable) is the format
3971d41 185
  - `AskUserQuestion`
3971d41 186
  - `Agent: Survey generic syntax usage across plum codebase`
3971d41 187
3971d41 188
**Claude:** I've kicked off a research agent to survey how generics syntax is used across the grammar, parser, checker, codegen, and stdlib/examples. I'll report back once it's done, then we can nail down the full design.
3971d41 189
3971d41 190
**You:** <task-notification>
3971d41 191
<task-id>a91ff831a50382494</task-id>
3971d41 192
<tool-use-id>toolu_011XrLpAW4pMK314FtB8sGSi</tool-use-id>
3971d41 193
<output-file>/private/tmp/claude-501/-Users-pyrossh-Code-plum/afc99e8b-d8ff-47b5-bbcd-86e87353f6e2/tasks/a91ff831a50382494.output</output-file>
3971d41 194
<status>completed</status>
3971d41 195
<summary>Agent "Survey generic syntax usage across plum codebase" finished</summary>
3971d41 196
<note>A task-notification fires each time this agent stops with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note>
3971d41 197
<result># Inventory Report: Generic-Type Syntax Migration `(a)` → `[T]`
3971d41 198
3971d41 199
## 1. tree-sitter grammar (`tooling/tree-sitter-plum/grammar.js`)
3971d41 200
3971d41 201
Declaration-site generics (paren-only today):
3971d41 202
- `generics: seq("(", commaSep1($.generic_type), ")")` (line 72) — used by `class` (98), `trait` (110), and `return_type` (142, reusing the same paren `generics` rule).
3971d41 203
- `generic_type: seq($.generic, optional(seq(":", sep1($.type_identifier, "+"))))` (73-74) — the bound.
3971d41 204
- `generic: choice($.a, $.b, $.c, $.d)` (529) with literal tokens `a`/`b`/`c`/`d` (530-533) — **hardcoded to exactly 4 single lowercase letters**; no `e`, `f`, etc. This is the single biggest grammar constraint to lift.
3971d41 205
- `class` (93-103): `"type" type_identifier ("(" type_identifier,* ")")?[implements] generics?[paren]"=" ...` — implements-list and generics-list are both parenthesized today; disambiguated only by the parser reading `implements` as leading `type_identifier`s before any `field`, and `generics` as the trailing `(generic_type,*)`.
3971d41 206
- `trait` (106-115): `"trait" type_identifier generics? "=" ...` — same paren `generics`.
3971d41 207
- `enum_field` (154-159): variant field types are `choice($.type_identifier, $.generic)` inside `(...)` — concrete types and single-letter generics share the same paren list; no separate generics-declaration concept for enums (generic-ness is inferred structurally in the checker, see §3).
3971d41 208
3971d41 209
Usage-site generics (**already dual bracket/paren** — the one place ahead of the rest):
3971d41 210
- `type` rule (75-90) already accepts `choice(seq("[", commaSep1($.type), "]"), seq("(", commaSep1($.type), ")"))` for a type's generic arguments — i.e. `Option[Node]` and `Option(a)` both parse today, producing the same `type` node shape either way.
3971d41 211
- `return_type` (141-142) is NOT this rule — it points at the paren-only `generics` rule, so `-&gt; Option[Node]`-shaped return types (bracket usage) are **not currently parseable as return types**, only `-&gt; Option(a)` is. This is an existing gap/inconsistency to note.
3971d41 212
3971d41 213
`fn`'s angle-bracket receiver syntax is a **separate, orthogonal mechanism** — not generics at all:
3971d41 214
- `fn_type: seq("&lt;", commaSep1($.type_identifier), "&gt;")` (177), used as `field("type", optional(alias($.fn_type, $.type)))` in `fn` (165). This is purely a **method-receiver annotation** (`get&lt;List&gt;(self, ...)`, `toStr&lt;Cat&gt;()`), naming which class/enum the method is dispatched on — it names a concrete/generic type by its bare name, never introduces or binds a type parameter itself, and syntactically occupies a completely different slot (`fn_identifier &lt;...&gt;` vs. `type_identifier (...)`/`[...]`). No interaction/overlap with the class/trait `generics` rule other than that a receiver name (`List`) may itself refer to a generic class declared with `generics`.
3971d41 215
3971d41 216
Corpus files with generic examples: `tooling/tree-sitter-plum/test/corpus/type.txt` (field `age: b`, `Cat(Stringable)`), `trait.txt` (no generics currently, plain traits only), `enum.txt` (`enum.txt:36-50`, `| Some(a)`), `function.txt` (`function.txt:72-96` `add(param: a, param2: List(b)) -&gt; List(b)`; `function.txt:123-137` `remove&lt;List&gt;(self, v: a)`; `function.txt:357-374` `each(cb: fn(a) -&gt; b) -&gt; Bool`). All of these bake in lowercase-letter/paren generics and will need updated expected trees.
3971d41 217
3971d41 218
## 2. plum-core (`ast.rs`, `parser.rs`)
3971d41 219
3971d41 220
AST (`plum-core/src/ast.rs`):
3971d41 221
- `GenericParam { name: String, bounds: Vec&lt;String&gt; }` (38-41) — generic name is a plain `String`, no letter/case constraint encoded structurally; used by `Class.generics` (33), `Trait.generics` (52), and oddly by `ReturnType.generics: Vec&lt;GenericParam&gt;` (118) even though grammatically a return type's generics come from the same paren-usage `generics` rule as declarations, not from a `type`'s bracket/paren generics (`Type.generics: Vec&lt;Type&gt;`, line 112) — this is an existing asymmetry between `Type` (usage, `Vec&lt;Type&gt;`) and `ReturnType` (also usage, but oddly `Vec&lt;GenericParam&gt;`).
3971d41 222
- `EnumVariant.fields: Vec&lt;String&gt;` (70-73) — variant field types stored as bare name strings only, no `Type`/generic structure; the checker infers generic-ness later by string-testing each name (see §3).
3971d41 223
- `Fn.type_param: Option&lt;String&gt;` (77-85) — the `&lt;List&gt;`/`&lt;Cat&gt;` receiver annotation, confirmed separate from `generics`.
3971d41 224
3971d41 225
Parser (`plum-core/src/parser.rs`):
3971d41 226
- `parse_type` (289-301): handles usage-site generics uniformly — it just filters named children by `kind() == "type"`, so it is **already agnostic to `[` vs `(`** (both grammar branches produce `type` nodes). No change needed here for usage-site brackets vs parens.
3971d41 227
- `parse_generics_field` (99-128): **the parser's own hardcoded logic**, distinct from `parse_type`. It walks the `generics` node's flattened children and matches `child.kind()` against the literal strings `"a" | "b" | "c" | "d"` (line 116) to detect a generic-letter node, attaching any following `type_identifier` as a bound. This is grammar-shape-coupled twice: once via the grammar's `a`/`b`/`c`/`d` node kinds, and again via this exact match arm. Used by `parse_class` (88), `parse_trait` (143), `parse_return_type` (307).
3971d41 228
- `parse_enum_variant` (171-183): matches `n.kind()` against `"type_identifier" | "a" | "b" | "c" | "d"` (179) to decide which variant-field children are "generic" vs concrete — same hardcoded set.
3971d41 229
- `parse_class` (73-97) derives `implements` by taking leading `type_identifier` children before the first `field` (81-86) — this convention (implements-list is whatever comes before fields, since generics are consumed separately by `parse_generics_field`) will need re-deriving once implements moves to `(Trait)` and generics to `[T: Trait]` — order/field-boundaries in the grammar output will change.
3971d41 230
3971d41 231
## 3. plum-checker (`types.rs`, `monomorphize.rs`, `lib.rs`)
3971d41 232
3971d41 233
- `types.rs`: no generic-parameter-name assumptions; `PlumType::TVar(String)` is a plain string type variable (used for inference, not tied to source syntax). `InferState::fresh_var` (64-68) generates internal names like `a0`, `a1`, ... (unrelated to source syntax, just an internal fresh-name convention — not something the migration touches, though the shared prefix `a` is worth noting to avoid confusion in code review).
3971d41 234
- `monomorphize.rs` — **this is where the naming convention is load-bearing, not just cosmetic**:
3971d41 235
  - `is_generic_param_name` (9-15): `true` iff the name is exactly one ASCII **lowercase** letter. This is the sole test used everywhere to decide "is this type name a generic parameter" for `Fn` and `Enum` (which carry no explicit `generics` list in the AST, unlike `Class`/`Trait`).
3971d41 236
  - `fn_generic_params` (25-45) and `enum_generic_params` (49-59) both call `is_generic_param_name` to *infer* a function's/enum's generic parameters implicitly, by scanning param/return/variant-field type names for single lowercase letters.
3971d41 237
  - `class_generic_params` (18-20) instead reads `Class.generics` explicitly (populated by the parser's `parse_generics_field`), so a class's generic names aren't string-sniffed — but everywhere else (`Fn`, `Enum`) the *entire* generic-detection mechanism depends on the lowercase-single-letter convention holding.
3971d41 238
  - Extensive downstream logic (specialization, mangling, bare-generic-reference detection: `fns_bare_generic`, `enums_generic_by_variant`, `resolve_bare_generic_fn_instantiation`, etc., throughout lines 60-882) all consumes the parameter-name lists produced above, so switching the convention to uppercase requires updating `is_generic_param_name` (and its doc comment, which explicitly says "single lowercase letter... is the grammar's only legal spelling") plus re-verifying no other code path re-derives the convention independently (a search shows this is the only definition site — good, it's centralized).
3971d41 239
- `lib.rs`: no direct generic-name-convention logic found; only constructs placeholder `ast::Type`/`ast::ReturnType` with empty `generics` in various spots (95, 126, 191, 211) — unaffected by the syntax change itself.
3971d41 240
3971d41 241
## 4. plum-wasm-codegen
3971d41 242
3971d41 243
No string/pattern-based parsing of generic syntax in `src/` — codegen operates purely on the already-monomorphized (fully concrete, generic-free) AST; the one `generic` mention in `src/lib.rs:487` is just a comment. All generic syntax exposure is in **test fixtures**, which embed `.plum` source as string literals:
3971d41 244
- `plum-wasm-codegen/tests/codegen_tests.rs`: ~30 lines matching old-syntax generic patterns, including explicit `type Box(a) =` declarations at lines **856, 891, 1010**, and `identity(value: a) -&gt; a =` at **877, 909, 1177**, plus additional `Option(a)`/`&lt;List&gt;`-style occurrences throughout.
3971d41 245
- `plum-checker/tests/checker_tests.rs`: 19 matching lines using paren/lowercase generics.
3971d41 246
- `plum-checker/tests/monomorphize_tests.rs`: 27 matching lines (heaviest concentration — this suite specifically exercises the monomorphization pass described in §3).
3971d41 247
- `plum-core/tests/parser_test.rs` and `formatter_test.rs`: 0 matches — apparently don't currently exercise generic syntax at all.
3971d41 248
3971d41 249
## 5. Full list of `.plum` files using generics
3971d41 250
3971d41 251
**`libs/std/list.plum`** (183 lines) — mixed old/new syntax already:
3971d41 252
- Line 6: `type Node[T] =` — **already migrated to bracket/uppercase** (with field types `value: T`, `prev: Option[Node]`, `next: Option[Node]` at lines 7-9, also already bracket-style, though `Option[Node]` omits `Node`'s own type argument, likely intentionally scoped-out/pre-existing).
3971d41 253
- Line 13: `type List(Stringable)(a: Stringable) =` — old-style, both implements-parens and generics-parens with lowercase `a`.
3971d41 254
- Lines 18, 22, 37, 45, 49, 53, 61, 65, 69, 73, 77, 88, 102, 106, 110, 114, 118, 122, 126, 134, 142, 146, 150, 154, 158, 162, 166, 170, 174, 177: every method using `a`/`b` as generic type in params/returns (e.g. `get&lt;List&gt;(self, i: Int) -&gt; Option(a)` at 22; `map&lt;List&gt;(self, cb: fn(a) -&gt; b) -&gt; List(b)` at 88; `reduce&lt;List&gt;(self, acc: b, cb: fn(a) -&gt; a) -&gt; Option(b)` at 122).
3971d41 255
3971d41 256
**`libs/std/map.plum`** (36 lines):
3971d41 257
- Line 4: `type Pair(a, b) =`
3971d41 258
- Line 10: `type Map(a, b) =`
3971d41 259
- Line 11: `items: List(Pair(a, b))`
3971d41 260
- Lines 21, 28, 32: `get&lt;Map&gt;(self, k: a) -&gt; Option(b)`, `set&lt;Map&gt;(self, k: a, v: b)`, `putIfAbsent&lt;Map&gt;(self, k: a, v: b)`
3971d41 261
- Line 35: `map&lt;Map&gt;(self, cb: fn(Pair(a, b)) -&gt; Pair(c, d)) -&gt; Map(c, d) =` — uses all four grammar-supported letters (`a,b,c,d`) simultaneously, a good stress case for the migration.
3971d41 262
3971d41 263
**`libs/std/option.plum`** (5 lines): line 4, `| Some(a)`.
3971d41 264
3971d41 265
**`libs/std/result.plum`** (13 lines): line 4 `| Ok(a)`, line 5 `| Err(b)`.
3971d41 266
3971d41 267
**`libs/std/json.plum`** (71 lines): line 23, `type JsonParseError(Err) =` — this is an `implements`-list usage (paren, trait name `Err`), not a generic-parameter declaration; flagged here only because it shares surface syntax with `class`'s parens and is worth double-checking it's unaffected by the migration (implements stays parenthesized).
3971d41 268
3971d41 269
**`libs/std/str.plum`** (132 lines): line 10, `type Str(Comparable, Stringable, Readable, Writable) =` — also purely an `implements` list, no generics; unaffected.
3971d41 270
3971d41 271
**`examples/types.plum`** (31 lines):
3971d41 272
- Line 5: `type Named(Stringable) =` (implements only, unaffected)
3971d41 273
- Line 8: `type Box(a) =` (generic declaration, needs migration)
3971d41 274
- Line 15: `trait Comparable(a: Ord) =` (generic trait declaration with bound, needs migration to `trait Comparable[T: Ord] =`)
3971d41 275
3971d41 276
**`examples/match.plum`** (48 lines): line 40, `describeOption(opt: Option) -&gt; Int =` — bare (non-generic-argument) reference to generic enum `Option`; no letter/paren syntax present, unaffected by the syntax migration itself.
3971d41 277
3971d41 278
No other `.plum` files (checked `libs/std/*` fully, `examples/*`, no `.plum` under `test/`, `website/`, or `hica/`) contain generic syntax.
3971d41 279
3971d41 280
## 6. Existing design docs — syntax assumptions
3971d41 281
3971d41 282
Both committed specs assume the **old** `(a)`/lowercase syntax explicitly and will need re-reading against the new syntax before/after this migration lands:
3971d41 283
- `docs/superpowers/specs/2026-07-20-generics-monomorphization-design.md`: line 6, `type Box(a) = value: a`; line 7, `trait Comparable(a: Ord) = compareTo(other: a) -&gt; Int`; line 37, explicitly notes "both bracket and paren syntax are already accepted per the grammar" for *usage* sites (`Box[Int]`, `Box(Int)`) — i.e. this doc is aware of the dual-syntax usage rule but still treats `a`/`b`/`c`/`d` paren-declarations as canonical; line 68, states directly "(`a`, `b`, `c`, `d` — the grammar's only legal generic-parameter spelling)".
3971d41 284
- `docs/superpowers/specs/2026-07-20-generic-enum-multi-instantiation-design.md`: repeatedly refers to "lowercase-letter generic params" and "bare generic-typed param" throughout — same old-syntax assumption, no bracket mentions.
3971d41 285
- `docs/superpowers/specs/2026-07-24-list-methods-design.md` and `docs/superpowers/plans/2026-07-24-list-methods.md` (pending, not yet executed): **entirely old-syntax** — every code sample uses `type Node(a) =`, `type List(a) =`, `Option(a)`, `Some(a)`, `get&lt;List&gt;(self, i: Int) -&gt; Option(a)`, etc. (dozens of occurrences throughout the plan's inline test fixtures, e.g. plan lines 43, 46, 51, 56, 152, 159, 192, 206, 211, 216, 244, and repeating through ~line 1032). Notably this plan's own target file, `libs/std/list.plum`, **already has `Node[T]` in bracket form on disk** (line 6) while the plan still describes it as `Node(a)` — the plan predates that on-disk edit and is now out of sync with the current file it's meant to extend, independent of this migration. This is worth flagging to whoever executes that plan next: it needs re-basing onto both the bracket-generics syntax and the current `list.plum` contents before execution.</result>
3971d41 286
<usage><subagent_tokens>65652</subagent_tokens><tool_uses>31</tool_uses><duration_ms>130912</duration_ms></usage>
3971d41 287
</task-notification>
3971d41 288
  - `Bash: grep -n "type_identifier\s*:\|var_identifier\s*:\|fn_identifier\s*:" /Users/pyrossh/Code/plum/tooling/tree-sitter-plum/grammar.js`
3971d41 289
3971d41 290
**Claude:** I now have a full picture. Here's the design, in sections — let me know if each looks right before I move to the next.
3971d41 291
3971d41 292
**1. The core lexical trade-off (needs your call)**
3971d41 293
3971d41 294
Today `generic` is a fixed set of 4 lowercase tokens (`a`,`b`,`c`,`d`) — zero conflict with `type_identifier` (`/[A-Z][a-zA-Z0-9]*/`) since real types must start uppercase. Switching generics to uppercase creates a genuine collision: `type_identifier`'s regex already matches single uppercase letters like `"T"`.
3971d41 295
3971d41 296
The clean fix: change `type_identifier` to require **2+ characters** (`/[A-Z][a-zA-Z0-9]+/`) and make `generic` a single uppercase letter (`/[A-Z]/`). Result: **any bare single uppercase letter is always a generic parameter; any multi-character capitalized name is always a concrete type.** This also removes the old 4-letter cap (`a`-`d` only) — any of A-Z works as a generic name now.
3971d41 297
3971d41 298
The cost: you can no longer name a real type `T`, `A`, `X`, etc. (single letter type names become permanently reserved for generics). Given real types in this codebase are always words (`List`, `Option`, `Node`...), this seems like a fine trade — but it's a real, permanent constraint worth confirming.
3971d41 299
  - `AskUserQuestion`
3971d41 300
3971d41 301
**Claude:** **2. Grammar structure changes**
3971d41 302
3971d41 303
- `generics` declaration: `(` → `[`, i.e. `generics: "[" commaSep1(generic_type) "]"`.
3971d41 304
- `class`/`trait` field order swaps to match your example: generics come first, then implements — `type List[T: Stringable](Stringable) =`. (Today it's implements-then-generics; the parser's "implements = leading type_identifiers before generics" logic needs re-deriving for the new order.)
3971d41 305
- `return_type` currently duplicates generics via its own paren-only rule (`ReturnType.generics: Vec<GenericParam>`), which is why `-> Option[Node]` doesn't even parse today — only `-> Option(a)` does. I'll unify `return_type` to just reuse `$.type` (same shape as field types), fixing this existing asymmetry as part of the migration rather than carrying it forward.
3971d41 306
- `enum_field` payload lists (`| Some(a)`, `| Ok(a)`) — I'd leave these as parens. They read as constructor/argument shape (mirrors `Some(5)` at the value level), not a type-parameter declaration, so migrating them to brackets (`| Some[a]`) would look odd and isn't structurally the same thing as a field type. Flagging this as a scoping call — want brackets here too, or parens is right?
3971d41 307
  - `AskUserQuestion`
3971d41 308
3971d41 309
**Claude:** **3. Implementation scope & impact**
3971d41 310
3971d41 311
This touches every layer:
3971d41 312
- **Grammar** (`tooling/tree-sitter-plum/grammar.js`): rules above, plus regenerating the parser and updating corpus tests (`type.txt`, `trait.txt`, `enum.txt`, `function.txt`) to the new bracket/uppercase forms.
3971d41 313
- **`plum-core`**: `ast.rs` — fix the `Type` vs `ReturnType` generics asymmetry (`ReturnType` reuses `Type`'s shape now); `parser.rs` — `parse_generics_field` and `parse_enum_variant` currently hardcode node-kind checks against literal `"a"|"b"|"c"|"d"`; since `generic` becomes a single regex-based token, these become a single check (single uppercase letter) instead of an enumerated match. Also re-derive `implements` now that it comes *after* generics in field order.
3971d41 314
- **`plum-checker`**: `monomorphize.rs`'s `is_generic_param_name` flips from "single lowercase letter" to "single uppercase letter" — this is the one load-bearing convention check, and it's centralized (good — nothing else re-derives it).
3971d41 315
- **`plum-wasm-codegen`**: no source changes (codegen only sees fully-monomorphized, generic-free AST) — only test fixtures change.
3971d41 316
- **Stdlib rewrite**: `libs/std/list.plum`, `map.plum`, `option.plum`, `result.plum` — every `(a)`/`(a, b)` declaration and lowercase usage becomes bracket/uppercase (`Node[T]`, `List[T: Stringable](Stringable)`, `Pair[K, V]`, `Some[T]`, `Ok[T]`/`Err[E]`, etc. — I'll pick sensible per-slot letters, e.g. `T`/`U` for generic lists, `K`/`V` for maps, rather than always `T`).
3971d41 317
- **Examples**: `examples/types.plum` (`Box(a)` → `Box[T]`, `Comparable(a: Ord)` → `Comparable[T: Ord]`).
3971d41 318
- **Tests**: `plum-checker/tests/checker_tests.rs` (19 lines), `monomorphize_tests.rs` (27 lines), `plum-wasm-codegen/tests/codegen_tests.rs` (~30 lines) all need their embedded `.plum` source strings updated.
3971d41 319
- **Design docs**: the two committed generics specs (`2026-07-20-generics-monomorphization-design.md`, `2026-07-20-generic-enum-multi-instantiation-design.md`) explicitly document the *old* syntax as canonical — I'll note in the new spec that they describe pre-migration syntax, without rewriting them (they're historical records of already-shipped work).
3971d41 320
3971d41 321
**Important conflict to flag:** the untracked plan `docs/superpowers/plans/2026-07-24-list-methods.md` is written entirely in old syntax (`type Node(a) =`) and is now out of sync with `list.plum`'s current on-disk state (which already has `Node[T]` on line 6). If we do this syntax migration first, that plan's inline test fixtures will all need rewriting before anyone executes it — I'd flag it as blocked/needs-rebase rather than touch it now, since it's a separate, not-yet-started plan.
3971d41 322
3971d41 323
Does this scope and approach look right? If so, I'll write it up as a formal design spec next.
3971d41 324
3971d41 325
**You:** Yes
3971d41 326
  - `Write: /Users/pyrossh/Code/plum/docs/superpowers/specs/2026-07-24-bracket-generics-syntax-design.md`
3971d41 327
3971d41 328
**Claude:** Self-review pass: no placeholders, sections are internally consistent (lexical rule → grammar rules → per-layer impact all agree), scope is bounded to syntax only, and the one known ambiguity (list-methods plan conflict) is called out rather than silently ignored. Committing it now.
3971d41 329
  - `Bash: git add docs/superpowers/specs/2026-07-24-bracket-generics-syntax-design.md && git commit -m "$(cat <<'EOF' docs: add design spec for bracket generics syntax mi`
3971d41 330
3971d41 331
**Claude:** Spec written and committed to `docs/superpowers/specs/2026-07-24-bracket-generics-syntax-design.md`. Please review it and let me know if you want any changes before we write the implementation plan.
3971d41 332
3971d41 333
**You:** Yes
3971d41 334
  - `Skill: superpowers:writing-plans`
3971d41 335
3971d41 336
**You:** Base directory for this skill: /Users/pyrossh/.claude/plugins/cache/superpowers-dev/superpowers/6.1.1/skills/writing-plans
3971d41 337
3971d41 338
# Writing Plans
3971d41 339
3971d41 340
## Overview
3971d41 341
3971d41 342
Write comprehensive implementation plans assuming the engineer has zero context for our codebase and questionable taste. Document everything they need to know: which files to touch for each task, code, testing, docs they might need to check, how to test it. Give them the whole plan as bite-sized tasks. DRY. YAGNI. TDD. Frequent commits.
3971d41 343
3971d41 344
Assume they are a skilled developer, but know almost nothing about our toolset or problem domain. Assume they don't know good test design very well.
3971d41 345
3971d41 346
**Announce at start:** "I'm using the writing-plans skill to create the implementation plan."
3971d41 347
3971d41 348
**Context:** If working in an isolated worktree, it should have been created via the `superpowers:using-git-worktrees` skill at execution time.
3971d41 349
3971d41 350
**Save plans to:** `docs/superpowers/plans/YYYY-MM-DD-<feature-name>.md`
3971d41 351
- (User preferences for plan location override this default)
3971d41 352
3971d41 353
## Scope Check
3971d41 354
3971d41 355
If the spec covers multiple independent subsystems, it should have been broken into sub-project specs during brainstorming. If it wasn't, suggest breaking this into separate plans — one per subsystem. Each plan should produce working, testable software on its own.
3971d41 356
3971d41 357
## File Structure
3971d41 358
3971d41 359
Before defining tasks, map out which files will be created or modified and what each one is responsible for. This is where decomposition decisions get locked in.
3971d41 360
3971d41 361
- Design units with clear boundaries and well-defined interfaces. Each file should have one clear responsibility.
3971d41 362
- You reason best about code you can hold in context at once, and your edits are more reliable when files are focused. Prefer smaller, focused files over large ones that do too much.
3971d41 363
- Files that change together should live together. Split by responsibility, not by technical layer.
3971d41 364
- In existing codebases, follow established patterns. If the codebase uses large files, don't unilaterally restructure - but if a file you're modifying has grown unwieldy, including a split in the plan is reasonable.
3971d41 365
3971d41 366
This structure informs the task decomposition. Each task should produce self-contained changes that make sense independently.
3971d41 367
3971d41 368
## Task Right-Sizing
3971d41 369
3971d41 370
A task is the smallest unit that carries its own test cycle and is worth a
3971d41 371
fresh reviewer's gate. When drawing task boundaries: fold setup,
3971d41 372
configuration, scaffolding, and documentation steps into the task whose
3971d41 373
deliverable needs them; split only where a reviewer could meaningfully
3971d41 374
reject one task while approving its neighbor. Each task ends with an
3971d41 375
independently testable deliverable.
3971d41 376
3971d41 377
## Bite-Sized Task Granularity
3971d41 378
3971d41 379
**Each step is one action (2-5 minutes):**
3971d41 380
- "Write the failing test" - step
3971d41 381
- "Run it to make sure it fails" - step
3971d41 382
- "Implement the minimal code to make the test pass" - step
3971d41 383
- "Run the tests and make sure they pass" - step
3971d41 384
- "Commit" - step
3971d41 385
3971d41 386
## Plan Document Header
3971d41 387
3971d41 388
**Every plan MUST start with this header:**
3971d41 389
3971d41 390
```markdown
3971d41 391
# [Feature Name] Implementation Plan
3971d41 392
3971d41 393
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
3971d41 394
3971d41 395
**Goal:** [One sentence describing what this builds]
3971d41 396
3971d41 397
**Architecture:** [2-3 sentences about approach]
3971d41 398
3971d41 399
**Tech Stack:** [Key technologies/libraries]
3971d41 400
3971d41 401
## Global Constraints
3971d41 402
3971d41 403
[The spec's project-wide requirements — version floors, dependency limits,
3971d41 404
naming and copy rules, platform requirements — one line each, with exact
3971d41 405
values copied verbatim from the spec. Every task's requirements implicitly
3971d41 406
include this section.]
3971d41 407
3971d41 408
---
3971d41 409
```
3971d41 410
3971d41 411
## Task Structure
3971d41 412
3971d41 413
````markdown
3971d41 414
### Task N: [Component Name]
3971d41 415
3971d41 416
**Files:**
3971d41 417
- Create: `exact/path/to/file.py`
3971d41 418
- Modify: `exact/path/to/existing.py:123-145`
3971d41 419
- Test: `tests/exact/path/to/test.py`
3971d41 420
3971d41 421
**Interfaces:**
3971d41 422
- Consumes: [what this task uses from earlier tasks — exact signatures]
3971d41 423
- Produces: [what later tasks rely on — exact function names, parameter
3971d41 424
  and return types. A task's implementer sees only their own task; this
3971d41 425
  block is how they learn the names and types neighboring tasks use.]
3971d41 426
3971d41 427
- [ ] **Step 1: Write the failing test**
3971d41 428
3971d41 429
```python
3971d41 430
def test_specific_behavior():
3971d41 431
    result = function(input)
3971d41 432
    assert result == expected
3971d41 433
```
3971d41 434
3971d41 435
- [ ] **Step 2: Run test to verify it fails**
3971d41 436
3971d41 437
Run: `pytest tests/path/test.py::test_name -v`
3971d41 438
Expected: FAIL with "function not defined"
3971d41 439
3971d41 440
- [ ] **Step 3: Write minimal implementation**
3971d41 441
3971d41 442
```python
3971d41 443
def function(input):
3971d41 444
    return expected
3971d41 445
```
3971d41 446
3971d41 447
- [ ] **Step 4: Run test to verify it passes**
3971d41 448
3971d41 449
Run: `pytest tests/path/test.py::test_name -v`
3971d41 450
Expected: PASS
3971d41 451
3971d41 452
- [ ] **Step 5: Commit**
3971d41 453
3971d41 454
```bash
3971d41 455
git add tests/path/test.py src/path/file.py
3971d41 456
git commit -m "feat: add specific feature"
3971d41 457
```
3971d41 458
````
3971d41 459
3971d41 460
## No Placeholders
3971d41 461
3971d41 462
Every step must contain the actual content an engineer needs. These are **plan failures** — never write them:
3971d41 463
- "TBD", "TODO", "implement later", "fill in details"
3971d41 464
- "Add appropriate error handling" / "add validation" / "handle edge cases"
3971d41 465
- "Write tests for the above" (without actual test code)
3971d41 466
- "Similar to Task N" (repeat the code — the engineer may be reading tasks out of order)
3971d41 467
- Steps that describe what to do without showing how (code blocks required for code steps)
3971d41 468
- References to types, functions, or methods not defined in any task
3971d41 469
3971d41 470
## Remember
3971d41 471
- Exact file paths always
3971d41 472
- Complete code in every step — if a step changes code, show the code
3971d41 473
- Exact commands with expected output
3971d41 474
- DRY, YAGNI, TDD, frequent commits
3971d41 475
3971d41 476
## Self-Review
3971d41 477
3971d41 478
After writing the complete plan, look at the spec with fresh eyes and check the plan against it. This is a checklist you run yourself — not a subagent dispatch.
3971d41 479
3971d41 480
**1. Spec coverage:** Skim each section/requirement in the spec. Can you point to a task that implements it? List any gaps.
3971d41 481
3971d41 482
**2. Placeholder scan:** Search your plan for red flags — any of the patterns from the "No Placeholders" section above. Fix them.
3971d41 483
3971d41 484
**3. Type consistency:** Do the types, method signatures, and property names you used in later tasks match what you defined in earlier tasks? A function called `clearLayers()` in Task 3 but `clearFullLayers()` in Task 7 is a bug.
3971d41 485
3971d41 486
If you find issues, fix them inline. No need to re-review — just fix and move on. If you find a spec requirement with no task, add the task.
3971d41 487
3971d41 488
## Execution Handoff
3971d41 489
3971d41 490
After saving the plan, offer execution choice:
3971d41 491
3971d41 492
**"Plan complete and saved to `docs/superpowers/plans/<filename>.md`. Two execution options:**
3971d41 493
3971d41 494
**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration
3971d41 495
3971d41 496
**2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints
3971d41 497
3971d41 498
**Which approach?"**
3971d41 499
3971d41 500
**If Subagent-Driven chosen:**
3971d41 501
- **REQUIRED SUB-SKILL:** Use superpowers:subagent-driven-development
3971d41 502
- Fresh subagent per task + two-stage review
3971d41 503
3971d41 504
**If Inline Execution chosen:**
3971d41 505
- **REQUIRED SUB-SKILL:** Use superpowers:executing-plans
3971d41 506
- Batch execution with checkpoints for review
3971d41 507
  - `Bash: sed -n '1,20p' tooling/tree-sitter-plum/grammar.js && echo "---FULL---" && wc -l tooling/tree-sitter-plum/grammar.js plum-core/src/ast.rs plum-core/src/parser.r`
3971d41 508
  - `Read: /Users/pyrossh/Code/plum/tooling/tree-sitter-plum/grammar.js`
3971d41 509
  - `Read: /Users/pyrossh/Code/plum/plum-core/src/ast.rs`
3971d41 510
  - `Bash: grep -n "fn parse_generics_field\|fn parse_class\|fn parse_trait\|fn parse_enum_variant\|fn parse_return_type\|fn parse_type\b\|fn parse_fn\b" /Users/pyrossh/Co`
3971d41 511
  - `Read: /Users/pyrossh/Code/plum/plum-core/src/parser.rs`
3971d41 512
  - `Bash: sed -n '1,70p' /Users/pyrossh/Code/plum/plum-checker/src/monomorphize.rs`
3971d41 513
  - `Read: /Users/pyrossh/Code/plum/tooling/tree-sitter-plum/test/corpus/type.txt`
3971d41 514
  - `Read: /Users/pyrossh/Code/plum/tooling/tree-sitter-plum/test/corpus/trait.txt`
3971d41 515
  - `Bash: grep -rn "ReturnType\|\.returns\b" plum-checker/src plum-wasm-codegen/src plum-core/src | grep -v "^Binary"`
3971d41 516
  - `Read: /Users/pyrossh/Code/plum/libs/std/option.plum`
3971d41 517
  - `Read: /Users/pyrossh/Code/plum/libs/std/result.plum`
3971d41 518
  - `Read: /Users/pyrossh/Code/plum/libs/std/map.plum`
3971d41 519
  - `Read: /Users/pyrossh/Code/plum/examples/types.plum`
3971d41 520
  - `Read: /Users/pyrossh/Code/plum/tooling/tree-sitter-plum/test/corpus/enum.txt`
3971d41 521
  - `Bash: wc -l /Users/pyrossh/Code/plum/tooling/tree-sitter-plum/test/corpus/function.txt`
3971d41 522
  - `Bash: cd /Users/pyrossh/Code/plum/tooling/tree-sitter-plum/test/corpus && grep -l "return_type" *.txt && echo --- && grep -c "return_type" *.txt`
3971d41 523
  - `Bash: cd /Users/pyrossh/Code/plum && grep -n "return_type\|(a)\|(b)\|(a,\|, b)\| a)\| b)\|: a\b\|: b\b\|List(b)\|Option(a)\|Option(b)\|Pair(a\|fn(a)\|-> a\b\|-> b\b"`
3971d41 524
  - `Read: /Users/pyrossh/Code/plum/tooling/tree-sitter-plum/test/corpus/function.txt`
3971d41 525
  - `Bash: cd /Users/pyrossh/Code/plum/tooling/tree-sitter-plum/test/corpus && grep -n -A2 "return_type" function.txt | grep -B2 "generics"`