@gallopsystems/agent-skills 1.17.0 → 1.19.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gallopsystems/agent-skills",
3
- "version": "1.17.0",
3
+ "version": "1.19.0",
4
4
  "description": "Gallop Systems agent skills, symlinked into .claude/skills (Claude Code) and .agents/skills (Codex) on install.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -20,6 +20,35 @@
20
20
  )
21
21
  ```
22
22
 
23
+ **…but only when the expression references the schema.** The whole payoff of
24
+ dropping raw `sql` is letting Kysely validate column/table names against your
25
+ generated types — so the value is proportional to how many schema identifiers the
26
+ expression names. A `sql` template made only of **bound parameters and constants**
27
+ has nothing to check; converting it is lateral churn, and occasionally worse:
28
+
29
+ ```typescript
30
+ // WORTH CONVERTING - names a column, so eb.ref/eb() validate it exists
31
+ .where(sql`lower(name)`, "=", value)
32
+ .where((eb) => eb(eb.fn<string>("lower", [eb.ref("name")]), "=", value))
33
+
34
+ // WORTH CONVERTING - correlated EXISTS validates table + both column refs
35
+ sql<boolean>`EXISTS (SELECT 1 FROM child WHERE child.parent_id = parent.id)`
36
+ eb.exists(eb.selectFrom("child").select(eb.lit(1).as("one"))
37
+ .whereRef("child.parent_id", "=", "parent.id"))
38
+
39
+ // LEAVE RAW - a bound param + a constant cast: no column, nothing to verify.
40
+ // The "typed" form is also a schema MISMATCH: eb.cast(...) is Expression<Date>,
41
+ // but a DATE column is codegen'd as `string` (with --date-parser string).
42
+ sql`${value}::date` // keep it
43
+
44
+ // LEAVE RAW - bare string/number/bool literals have no schema surface
45
+ sql.lit("Unassigned") // keep it (or eb.lit for number/bool, but no real gain)
46
+ ```
47
+
48
+ Rule of thumb: **convert when the raw SQL names a column or table; leave it when
49
+ it is only parameters and literals.** The latter compiles to identical SQL and the
50
+ "type-safe" rewrite verifies nothing.
51
+
23
52
  ### 2. Don't Forget .execute()
24
53
 
25
54
  Queries are lazy - they won't run without calling an execute method:
@@ -130,3 +159,40 @@ The `"between"` operator looks like it should work but compiles to a tuple, whic
130
159
  .where((eb) => eb.betweenSymmetric("age", 65, 18)) // swaps bounds if needed
131
160
  ```
132
161
 
162
+ ### 9. Builder Argument Strings Are Column References, Not Literals
163
+
164
+ This bites hardest when converting raw `sql` to builders. In `eb.fn.coalesce`,
165
+ binary `eb(lhs, op, rhs)`, and most `eb.fn` calls, a bare string argument is
166
+ interpreted as a **column reference**, not a string value. So porting
167
+ `` sql`coalesce(status, 'pending')` `` naively produces a query that looks for a
168
+ column named `pending`:
169
+
170
+ ```typescript
171
+ // WRONG - 'pending' is read as a column name -> "column \"pending\" does not exist"
172
+ eb.fn.coalesce("status", "pending")
173
+
174
+ // RIGHT - wrap a literal default in eb.val() (parameterized) ...
175
+ eb.fn.coalesce("status", eb.val("pending"))
176
+ // ... or a typed sql`` literal when you need it inlined (e.g. inside GROUP BY)
177
+ eb.fn.coalesce("status", sql<string>`'pending'`)
178
+
179
+ // Number/boolean/null literals can use eb.lit (eb.lit rejects strings, see
180
+ // expression-builder.md):
181
+ eb.fn.coalesce("score", eb.lit(0))
182
+
183
+ // Coalescing two real COLUMNS is the case where bare strings are correct:
184
+ eb.fn.coalesce("preferred_name", "legal_name") // both are column refs
185
+ ```
186
+
187
+ A companion trap: when you feed a `sql`` fragment as the right-hand operand of a
188
+ typed operator (e.g. a full-text `@@`), the fragment must carry a type parameter
189
+ or it infers `unknown` and the operator overload rejects it:
190
+
191
+ ```typescript
192
+ // WRONG - RawBuilder<unknown> is not a valid operand -> TS2345
193
+ .where("search_vector", "@@", sql`to_tsquery('english', ${q})`)
194
+
195
+ // RIGHT - type the fragment so it satisfies the operator's operand type
196
+ .where("search_vector", "@@", sql<string>`to_tsquery('english', ${q})`)
197
+ ```
198
+