@gallopsystems/agent-skills 1.11.0 → 1.13.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 +1 -1
- package/plugins/doctl/skills/doctl/other-services.md +3 -1
- package/plugins/git-github/skills/git-github/SKILL.md +1 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/SKILL.md +47 -0
- package/plugins/nitro-testing/skills/nitro-testing/frontend-testing.md +40 -0
- package/plugins/vue-nuxt/skills/vue-nuxt/SKILL.md +5 -0
- package/plugins/vue-nuxt/skills/vue-nuxt/vueuse.md +13 -5
- package/plugins/vue-nuxt/skills/vue-nuxt/watch.md +6 -6
package/package.json
CHANGED
|
@@ -10,7 +10,9 @@ doctl databases ca <db-id> # cluster CA certi
|
|
|
10
10
|
|
|
11
11
|
- **The connection URI contains live credentials** — treat command output as a secret. Don't echo it into logs; pipe it directly to where it's needed.
|
|
12
12
|
- **Check `Version`** and keep CI/local database versions in sync with production — a test suite running `postgres:15` against a pg-18 production cluster hides version-specific behavior.
|
|
13
|
-
- Connections use port 25060 with `sslmode=require`. **TLS trap**: some clients (e.g.
|
|
13
|
+
- Connections use port 25060 with `sslmode=require`. **TLS trap**: some clients (e.g. `pg-connection-string` as bundled with `pg` >= 8.16) silently treat `require` as `verify-full`, which rejects DO's self-signed CA under the default trust store (`SELF_SIGNED_CERT_IN_CHAIN`). Two non-obvious parts:
|
|
14
|
+
- **An ssl option set in code does NOT override an `sslmode` already in the URI.** Passing `ssl: { rejectUnauthorized: false }` while the connection string still ends in `?sslmode=require` keeps failing — the URI's `sslmode` wins. The fix has to land in the URI itself: drop/replace `sslmode`, append `uselibpqcompat=true` (restores libpq semantics: encrypt but don't verify), or supply the CA from `doctl databases ca <db-id>` explicitly.
|
|
15
|
+
- **When you can't edit the URI** — specifically, when you bind App Platform's generated `${db.DATABASE_URL}` straight into an env var in the app spec, it always carries `?sslmode=require` and you don't author the string — append the param to the bound value: `value: ${db.DATABASE_URL}&uselibpqcompat=true`. This only applies to that bound-pipethrough case; if you declare the connection-string env var yourself, just put the right params (or none) in from the start and an in-code ssl option is enough.
|
|
14
16
|
|
|
15
17
|
## Spaces
|
|
16
18
|
|
|
@@ -51,6 +51,7 @@ EOF
|
|
|
51
51
|
- After merge: `git switch main && git pull --ff-only`, clean up `[gone]` branches, start the next branch from fresh main.
|
|
52
52
|
- One concern per PR — hotfixes and review findings go in separate PRs unless told otherwise.
|
|
53
53
|
- Stacked PRs: `gh pr create --base <parent-branch>`; after the parent merges, retarget with `gh pr edit <n> --base main` (and see [getting-unstuck.md](getting-unstuck.md) for rebasing onto main after the parent was squash-merged).
|
|
54
|
+
- If you discover uncommitted work on the wrong branch and the PR must be "off main", do not commit to the wrong branch. With a cleanly applicable worktree, `git fetch origin main && git switch -c feat/<short-description> origin/main` carries the unstaged changes onto a new branch from `origin/main`. Verify with `git status` and tests. If checkout would overwrite/conflict, stash with `-u`. Only resort to worktree if stash gets too complicated.
|
|
54
55
|
|
|
55
56
|
## Reading PR and CI State
|
|
56
57
|
|
|
@@ -964,6 +964,53 @@ await db.schema.createIndex("idx_order_user_id").on("order").column("user_id").e
|
|
|
964
964
|
.addColumn("price", sql`numeric(10, 2)`)
|
|
965
965
|
```
|
|
966
966
|
|
|
967
|
+
### Migration Ordering Is Append-Only — Out-of-Order Timestamps Break Prod Deploys
|
|
968
|
+
|
|
969
|
+
Kysely's migrator enforces a **strict append-only ledger**: it refuses to run any
|
|
970
|
+
unexecuted migration whose timestamp sorts *before* the last-executed one
|
|
971
|
+
(throwing `Corrupted migrations: previously executed migration ... is missing`).
|
|
972
|
+
This bites when two branches each add a migration, and the one that merges *second*
|
|
973
|
+
carries the *earlier* timestamp:
|
|
974
|
+
|
|
975
|
+
```
|
|
976
|
+
branch A merges first → 1700000200000_drop_thing (runs in prod)
|
|
977
|
+
branch B merges second → 1700000100000_add_table (timestamp is EARLIER)
|
|
978
|
+
→ 1700000100001_add_column
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
Prod has already recorded `...200000_drop_thing` as executed. On the next deploy
|
|
982
|
+
the `migrate` job sees two pending migrations that sort *before* it, throws
|
|
983
|
+
"Corrupted migrations", and **exits non-zero**. If migrations run as a pre-deploy
|
|
984
|
+
job (common on PaaS like DigitalOcean App Platform), the failed job fails the whole
|
|
985
|
+
deploy and the platform **auto-rolls-back to the previous image** — so prod silently
|
|
986
|
+
stays on stale code and every subsequent deploy fails the same way. A self-reinforcing
|
|
987
|
+
loop that looks like a deploy/token problem but is really a migration-ledger problem.
|
|
988
|
+
|
|
989
|
+
**Prevent it:** before merging a long-lived branch, check whether `main` has merged
|
|
990
|
+
any migration with a *later* timestamp than yours. If so, regenerate your migration's
|
|
991
|
+
timestamp so it sorts last (`migrate:make` again, or rename the file) **before it
|
|
992
|
+
merges** — only safe while the migration has not yet run in any shared DB. Never
|
|
993
|
+
re-stamp a migration that prod has already executed; that forces it to re-run.
|
|
994
|
+
|
|
995
|
+
**Fix it once prod is wedged:** reconcile the ledger so the executed set is a clean
|
|
996
|
+
*prefix* again, then let the normal strict migrate job run. Do **not** reach for
|
|
997
|
+
`allowUnorderedMigrations: true` — it works (kysely-ctl spreads the `migrations`
|
|
998
|
+
config into the `Migrator`), but it permanently weakens the ordering guard to paper
|
|
999
|
+
over one bad state. Instead, surgically remove the prematurely-recorded row from the
|
|
1000
|
+
migration ledger table (default `kysely_migration`):
|
|
1001
|
+
|
|
1002
|
+
```sql
|
|
1003
|
+
-- prod ledger has the later-timestamp migration recorded, blocking the two earlier ones
|
|
1004
|
+
DELETE FROM kysely_migration WHERE name = '1700000200000_drop_thing';
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
Now `add_table → add_column → drop_thing` are all pending in true timestamp order, and
|
|
1008
|
+
the next deploy's strict migrate job applies them cleanly. This only works when the
|
|
1009
|
+
removed migration is **idempotent to re-run** (e.g. a `dropTable`/`dropColumn` written
|
|
1010
|
+
with `ifExists`, so re-applying it after the others is a safe no-op). Verify the
|
|
1011
|
+
ledger is a contiguous prefix after the DELETE, and prefer letting the deploy's own
|
|
1012
|
+
migrate job re-apply rather than running migrations from a laptop against prod.
|
|
1013
|
+
|
|
967
1014
|
## Type Generation
|
|
968
1015
|
|
|
969
1016
|
Use `kysely-codegen` to generate types from your database:
|
|
@@ -526,6 +526,19 @@ await flushPromises();
|
|
|
526
526
|
expect(replace.mock.calls.some((c) => (c[0] as any)?.query?.q === "foo")).toBe(true);
|
|
527
527
|
```
|
|
528
528
|
|
|
529
|
+
If the component's source of truth is `useRoute()`/`useRouter()`, `route:` is not
|
|
530
|
+
always the most direct test seam: app middleware, route rules, and redirects still
|
|
531
|
+
run. For component-level behavior, mock the Nuxt imports before mounting:
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
mockNuxtImport("useRoute", () => () => ({ query: { tab: "activity" }, params: {} }));
|
|
535
|
+
mockNuxtImport("useRouter", () => () => ({ replace: vi.fn(), push: vi.fn() }));
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
If the component imports router helpers directly from `vue-router`, mock that
|
|
539
|
+
module too. Keep the test focused on what the component reads or writes; use an
|
|
540
|
+
end-to-end/page test when the middleware behavior itself is under test.
|
|
541
|
+
|
|
529
542
|
### 9. Testing a composable that needs a component scope
|
|
530
543
|
|
|
531
544
|
For a composable using lifecycle hooks or `provide`/`inject`, mount a throwaway
|
|
@@ -541,6 +554,12 @@ Control a VueUse dependency with `vi.mock("@vueuse/core", …)` to return refs y
|
|
|
541
554
|
drive. And make sure the vitest `include` glob covers `app/composables/**` — a
|
|
542
555
|
composables dir is easy to leave out of the frontend config.
|
|
543
556
|
|
|
557
|
+
Do this for lifecycle composables even when the return value looks plain:
|
|
558
|
+
`onMounted`, `onUnmounted`, `useEventListener`, `useScrollLock`, and similar
|
|
559
|
+
helpers need a component effect scope to attach and clean up correctly. Calling
|
|
560
|
+
the composable directly in a Vitest test can produce Vue warnings and, worse,
|
|
561
|
+
skip the listener or cleanup path you meant to verify.
|
|
562
|
+
|
|
544
563
|
### 10. Reset the shared `useFetch` cache between mounts
|
|
545
564
|
|
|
546
565
|
`useFetch`/`useAsyncData` cache by key, and the cache is **shared across
|
|
@@ -553,6 +572,27 @@ import { clearNuxtData } from "#imports"; // not exported from @nuxt/test-utils/
|
|
|
553
572
|
beforeEach(() => clearNuxtData());
|
|
554
573
|
```
|
|
555
574
|
|
|
575
|
+
### 11. Mock Nuxt fetch behavior at the right layer
|
|
576
|
+
|
|
577
|
+
`mockNuxtImport("$fetch", ...)` is not a reliable target: `$fetch` is not a normal
|
|
578
|
+
Nuxt auto-import in the same way `useRoute` or a composable is, so the transform
|
|
579
|
+
may fail before the test even runs. Prefer `registerEndpoint` when the component
|
|
580
|
+
calls `useFetch`, `useAsyncData`, or `$fetch` against an app route:
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
registerEndpoint("/api/search", {
|
|
584
|
+
method: "GET",
|
|
585
|
+
handler: (event) => {
|
|
586
|
+
const q = new URL(event.node.req.url!, "http://localhost").searchParams.get("q");
|
|
587
|
+
return [{ id: 1, name: q ?? "" }];
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
If the component calls a local service/composable that wraps `$fetch`, mock that
|
|
593
|
+
service/composable instead. Mock the boundary you own; use `registerEndpoint` for
|
|
594
|
+
Nuxt's fetch path.
|
|
595
|
+
|
|
556
596
|
## File Organization
|
|
557
597
|
|
|
558
598
|
Co-locate tests with source files:
|
|
@@ -39,6 +39,11 @@ cross-links to those rather than restating them.
|
|
|
39
39
|
- [page-structure.md](./page-structure.md) — keep pages thin: route-param parsing + layout in the page, data/logic/forms in components
|
|
40
40
|
- [formatters.md](./formatters.md) — never inline a currency/date/number formatter; centralize in `useFormatters`, prefer Intl/date-fns
|
|
41
41
|
|
|
42
|
+
Testing note: when a Vue/Nuxt refactor changes component behavior, route/query
|
|
43
|
+
state, or a composable with lifecycle hooks, use the Nuxt frontend testing
|
|
44
|
+
guidance in `nitro-testing`'s [frontend-testing.md](../../../../nitro-testing/skills/nitro-testing/frontend-testing.md)
|
|
45
|
+
instead of testing those pieces as plain Vue functions.
|
|
46
|
+
|
|
42
47
|
## Core Principles
|
|
43
48
|
|
|
44
49
|
1. **Lean on auto-imports.** `app/components`, `app/composables`, `app/utils`, and the Vue/Nuxt APIs all auto-import. Add an explicit `import` only for third-party symbols and TS types. A nested component's tag carries its directory as a prefix (`components/customers/ProfileCard.vue` → `<CustomersProfileCard>`).
|
|
@@ -24,6 +24,9 @@ export default defineNuxtConfig({ modules: ['@vueuse/nuxt'] })
|
|
|
24
24
|
|
|
25
25
|
The **`@vueuse/router`** and **`@vueuse/integrations`** add-ons are separate
|
|
26
26
|
installs and are **not** auto-imported by the module — import them explicitly.
|
|
27
|
+
For URL query sync, install `@vueuse/router` and use its router composables before
|
|
28
|
+
hand-rolling `useRoute()`/`useRouter()` glue; the package exists exactly for that
|
|
29
|
+
boundary.
|
|
27
30
|
|
|
28
31
|
## The boundary cases (mapped from `watch.md`)
|
|
29
32
|
|
|
@@ -42,8 +45,9 @@ installs and are **not** auto-imported by the module — import them explicitly.
|
|
|
42
45
|
|
|
43
46
|
URL sync (`router.replace({ query: { ...route.query, tab } })`) →
|
|
44
47
|
`useRouteQuery('tab')` from `@vueuse/router` gives a ref bound two-way to the query
|
|
45
|
-
param. Nuxt's own `useRoute()` is already reactive for *reads*;
|
|
46
|
-
`useRouteQuery` when you want a **writable** ref bound to a single param
|
|
48
|
+
param. Nuxt's own `useRoute()` is already reactive for *reads*; install and import
|
|
49
|
+
`useRouteQuery` when you want a **writable** ref bound to a single param instead of
|
|
50
|
+
open-coding the same replace/query merge logic.
|
|
47
51
|
|
|
48
52
|
### `useLocalStorage` reads at setup — guard SSR hydration
|
|
49
53
|
|
|
@@ -67,9 +71,13 @@ Because the bound ref reads from and writes to the query param, the URL *is* the
|
|
|
67
71
|
state — so it **can't represent a ref with two distinct "empty" states** (e.g. a
|
|
68
72
|
filter that defaults to `"Open"` on load but clears to `null`; both would be "param
|
|
69
73
|
absent"). When you need that distinction, keep a plain `ref` + a projecting `watch`
|
|
70
|
-
(a sanctioned `watch.md` "URL sync" case).
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
(a sanctioned `watch.md` "URL sync" case). The same exception applies when one
|
|
75
|
+
source object fans out to several query params and the projection itself is the
|
|
76
|
+
behavior being tested. Otherwise, adding `@vueuse/router` is preferable to writing
|
|
77
|
+
your own route-query synchronization. And **don't mix `useUrlSearchParams`
|
|
78
|
+
(History API) with `useRouteQuery` (vue-router) in the same component** — they
|
|
79
|
+
write the URL through different mechanisms and clobber each other's params; pick
|
|
80
|
+
one.
|
|
73
81
|
|
|
74
82
|
## VueUse's `watch` sugar — when a `watch` IS warranted
|
|
75
83
|
|
|
@@ -69,12 +69,12 @@ Only when the effect crosses **out of** the reactive graph:
|
|
|
69
69
|
- **Persist** — a `localStorage`/`useCookie` write, debounced auto-save of a
|
|
70
70
|
deep-watched form.
|
|
71
71
|
- **URL sync** — `router.replace({ query: { ...route.query, tab } })`. For a single
|
|
72
|
-
ref ↔ one query param, prefer `useRouteQuery` (see
|
|
73
|
-
the right tool only when the URL can't model the
|
|
74
|
-
"empty" states** (e.g. a filter that defaults to
|
|
75
|
-
`null` — an absent param can map to only one of
|
|
76
|
-
fanning out to many params** (a PrimeVue filter
|
|
77
|
-
`useRouteQuery` can't express.
|
|
72
|
+
ref ↔ one query param, install `@vueuse/router` and prefer `useRouteQuery` (see
|
|
73
|
+
below). A write-back watch is the right tool only when the URL can't model the
|
|
74
|
+
state: a ref with **two distinct "empty" states** (e.g. a filter that defaults to
|
|
75
|
+
`"Open"` on load but clears to `null` — an absent param can map to only one of
|
|
76
|
+
them), or a **composite object fanning out to many params** (a PrimeVue filter
|
|
77
|
+
object) that a one-ref-per-param `useRouteQuery` can't express.
|
|
78
78
|
- **Re-seed local state on dialog open** — `watch(visible, (v) => { if (v) initForm() })`.
|
|
79
79
|
The single most common legit pattern. Its one-liner is `whenever(visible, initForm)`
|
|
80
80
|
(see `vueuse.md`); a compound guard keeps its inner half —
|