Skip to content

Add effect skill layered on Effect's own skills - #172

Merged
alycda merged 9 commits into
mainfrom
claude/effectts-skill-g8nxna
Oct 16, 2026
Merged

alycda merged 9 commits into
mainfrom
claude/effectts-skill-g8nxna

Conversation

@alycda

@alycda alycda commented Sep 22, 2026

Copy link
Copy Markdown
Owner

Effect v4 is at 4.0.0-rc.117 and npm latest is still 3.22.2. The v4
npm tarball ships AGENTS.md and ai-docs/, and the Effect team's
effect-ts/skills repo is already in the pinned nix-skills index.
Its effect-ts skill only points at that AGENTS.md. So this follows
the ste100 layer-not-fork pattern: pin the upstream skills and write a
thin local skill that holds only what upstream lacks.

What the layer adds:

  • A gate on the installed major version. v3 code and v4 code do not
    compile against each other, and the v3 tarball ships no agent docs.
    Upstream effect-ts starts with pnpm add effect@rc, which in a v3
    project (effect-playground is one) is an unrequested major upgrade.
  • The effect-playground Rust translation table, with a v4 column.
  • The v3/v4 traps found by typechecking effect-playground against
    rc.117: Schema.Date keeps its name with a different meaning
    (v3 string->Date is now DateFromString, and v4 Schema.Date rejects
    the string at runtime); Schedule.intersect -> Schedule.max takes an
    array and cannot be piped; Cause guards moved to cause.reasons; and
    the misleading TS2379 "add undefined" cascade under
    exactOptionalPropertyTypes.

Each v4 entry was resolved against migration/v3-to-v4.md on
Effect-TS/effect main. The entries marked "checked" were also
typechecked and run against rc.117, including a v4 port of lesson
06's backoff (5 attempts transient, 1 permanent, same as v3). One name
written from memory, Schema.TaggedErrorClass, turned out not to exist.
That is why CLAUDE.md now says to verify version claims with a
typecheck against the real package.

Specific changes:

  • lib/skills-sh.nix: pin effect-ts and effect-v3-to-v4
    (effect-ts/skills @ 2309e6f, which is also upstream HEAD)
  • home-manager/modules/tools/agent-skills.nix: deploy both, plus the
    repo skill effect
  • tools/agents/skills/effect/SKILL.md: the layer
  • CLAUDE.md: "look inside the package before writing a skill for a
    library" lesson

Not verified here: this sandbox has no nix, so statix, deadnix and
flake evaluation are left to CI.

Co-Authored-By: Claude Opus 5.5 [email protected]
Claude-Session: https://claude.ai/code/session_01Tw8tuRUyKtkcjmN4upeawF

Effect v4 is at 4.0.0-rc.117 and npm `latest` is still 3.22.2. The v4
npm tarball ships AGENTS.md and ai-docs/, and the Effect team's
effect-ts/skills repo is already in the pinned nix-skills index.
Its `effect-ts` skill only points at that AGENTS.md. So this follows
the ste100 layer-not-fork pattern: pin the upstream skills and write a
thin local skill that holds only what upstream lacks.

What the layer adds:
- A gate on the installed major version. v3 code and v4 code do not
  compile against each other, and the v3 tarball ships no agent docs.
  Upstream effect-ts starts with `pnpm add effect@rc`, which in a v3
  project (effect-playground is one) is an unrequested major upgrade.
- The effect-playground Rust translation table, with a v4 column.
- The v3/v4 traps found by typechecking effect-playground against
  rc.117: Schema.Date keeps its name with a different meaning
  (v3 string->Date is now DateFromString, and v4 Schema.Date rejects
  the string at runtime); Schedule.intersect -> Schedule.max takes an
  array and cannot be piped; Cause guards moved to cause.reasons; and
  the misleading TS2379 "add undefined" cascade under
  exactOptionalPropertyTypes.

Each v4 entry was resolved against migration/v3-to-v4.md on
Effect-TS/effect main. The entries marked "checked" were also
typechecked and run against rc.117, including a v4 port of lesson
06's backoff (5 attempts transient, 1 permanent, same as v3). One name
written from memory, Schema.TaggedErrorClass, turned out not to exist.
That is why CLAUDE.md now says to verify version claims with a
typecheck against the real package.

Specific changes:
- lib/skills-sh.nix: pin effect-ts and effect-v3-to-v4
  (effect-ts/skills @ 2309e6f, which is also upstream HEAD)
- home-manager/modules/tools/agent-skills.nix: deploy both, plus the
  repo skill `effect`
- tools/agents/skills/effect/SKILL.md: the layer
- CLAUDE.md: "look inside the package before writing a skill for a
  library" lesson

Not verified here: this sandbox has no nix, so statix, deadnix and
flake evaluation are left to CI.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tw8tuRUyKtkcjmN4upeawF
Topcoat (tokio-rs/topcoat, 0.8.1, MSRV 1.98) is a server-rendered
Rust web framework. create-epoch-app (Rhys Sullivan) is a full-stack
Effect starter: Effect + Convex + Next.js + effect-atom. Put side by
side, they are the Rust and Effect versions of one app, so the new
`topcoat` skill carries the pairing table and `effect` hands off to it.

Topcoat follows the same pattern as `effect`, with its docs in a
different place. The guides are embedded in the published crates, so
after a build they sit in ~/.cargo/registry/src/*/topcoat-<ver>/docs/.
The AGENTS.md index is only in the repo, and main had already changed
13 guide files in the nine days after v0.8.1. So the skill reads the
crate at the Cargo.lock version and the index at the matching tag.

The pairing's main row was checked on both sides, not taken from the
docs. Asking for a missing dependency:
- Effect: the service stays in R, and runPromise gives TS2345 (rc.117).
- Topcoat: app_context::<Missing>(cx) compiles, then panics on each
  request that reaches it. The server catches the panic, returns 500
  and keeps serving other routes (probe crate on crates.io 0.8.1,
  rustc 1.98.1).
The two share a design rule (epoch-app's "yield* the dependency, don't
pass it", Topcoat's "functions, not middlewares") but check it at
different times. The skill states that as the tradeoff to name.

create-epoch-app: I reviewed it and pinned nothing from it. It is a
template with Cursor .mdc rules, on Effect ^3.19.8, and was last
committed 2026-01-11. Its dependency rule went into `effect`. The
skill records two rules not to import unchanged:
- effect-primitives: prefers v3 names that v4 renames, and prefers
  Chunk/HashMap by default with no benchmark behind it.
- never-use-as-any: bans `unknown`, which Schema decoding takes as
  input by design.
Rhys Sullivan's effect-client-wrapper skill is pinned from
rhyssullivan/skills. It is v3-shaped, so `effect` says to keep the
pattern and translate the code on v4.

Correction: the previous commit put a rule into `effect` that I had not
verified: a Data.TaggedError payload field cannot be named `cause`.
The rule came from a comment in effect-playground's lesson 06. It is
false on 3.22.2 and rc.117. Renaming lesson 06's field to
`readonly cause` typechecks under the playground's own tsconfig and
passes its 6 tests. The skill now says so, and CLAUDE.md extends the
lesson: a trusted source needs the same check as memory.

Specific changes:
- tools/agents/skills/topcoat/SKILL.md: new layer skill
- tools/agents/skills/effect/SKILL.md: drop the false `cause` rule;
  add the yield* dependency rule; add hand-offs to
  effect-client-wrapper and topcoat; add a note on the reference
  projects
- lib/skills-sh.nix: pin effect-client-wrapper (rhyssullivan/skills
  @ ab3a455, upstream HEAD)
- home-manager/modules/tools/agent-skills.nix: deploy topcoat and
  effect-client-wrapper
- CLAUDE.md: extend the "look inside the package" lesson

Not verified here: nix is not installed in this sandbox, so statix,
deadnix and flake evaluation are left to CI.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tw8tuRUyKtkcjmN4upeawF
@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

⊕ Entity-level changes

CLAUDE.md

Status Type Name
Δ heading External agent skills (lib/skills-sh.nix)
Δ heading Meta: Updating This Document

home-manager/modules/tools/agent-skills.nix

Status Type Name
~ orphan module-level
Δ binding repoSkills
Δ binding externalSkills

lib/skills-sh.nix

Status Type Name
+ orphan module-level
+ binding effect-skills
Δ binding skills-sh

tools/agents/skills/effect/SKILL.md

Status Type Name
+ preamble (preamble)
+ heading Effect
+ heading Step 0: find the installed version
+ heading Rust translation
+ heading v3 → v4 traps that a rename table hides
+ heading Writing rules for both versions
+ heading Verify
+ heading Hand-offs
+ heading Reference projects, and what not to take from them

tools/agents/skills/topcoat/SKILL.md

Status Type Name
+ preamble (preamble)
+ heading Topcoat
+ heading Step 0: version and docs
+ heading Contracts the compiler does not check
+ heading Coming from Effect: the pairing
+ heading Tooling
+ heading Unsafe
+ heading Hand-offs

Summary: 19 added, 6 modified across 5 files (1 added orphan, 1 modified orphan)

functions and classes, not lines · sem

A question about whether the self-hosted Ghost server replaces Convex
showed the pairing table left that open. It doesn't. Ghost provisions
disposable Postgres databases; Convex is an application backend with
server functions, auth and live queries. Ghost does match
create-epoch-app's convex-test: a cheap TEMPLATE fork per test run.
Added that as its own row, with the three reasons (one shared role,
unverified TLS, disposable by design) that keep Ghost out of the
persistent-store role.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tw8tuRUyKtkcjmN4upeawF
@alycda
alycda added this pull request to stack #174 September 22, 2026 19:55
Two conflicts:
- lib/skills-sh.nix: main dropped this branch's mkSkill helper (which
  read nix-skills' per-letter data shards) and now goes through
  agents.nix's public overlay. Took main's file and re-added this
  branch's three skills in its style: github.effect-ts.skills.effect-ts,
  github.effect-ts.skills.effect-v3-to-v4 and
  github.rhyssullivan.skills.effect-client-wrapper. All three are in the
  locked index (agents.nix 0b4d80e) at the revs the branch was written
  against: effect-ts/skills 2309e6f and rhyssullivan/skills ab3a455.
- CLAUDE.md: only the "Last updated" footer conflicted. Kept both sides,
  newest first.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tw8tuRUyKtkcjmN4upeawF
effect 4.0.0 went out on 2026-10-01; npm `latest` is now 4.0.5, and
the `rc` tag has stayed at 4.0.0-rc.118 since 2026-09-28. The locked
index still has effect-ts/skills at 2309e6f, written during the RC.
Both of its skills now give wrong instructions:
- effect-ts says `pnpm add effect@rc`, which installs rc.118, older
  than every stable 4.0.x.
- effect-v3-to-v4 says to use `effect/unstable/*` imports where the
  reference maps to them. GA removed those paths with no compatibility
  exports.
Upstream 155c50c ("docs: align Effect skills with v4 GA", 2026-10-09)
fixes both. It is not in the index yet, so `nix flake update
nix-skills` cannot be relied on to pick it up. This pins it by hand,
the way main pins supabase-postgres-best-practices.

Asked for: effect-ts. Also done: effect-v3-to-v4. Same repo, one fetch,
and leaving the migration skill at 2309e6f would keep the removed
paths in it.

The hash was computed without nix, which this sandbox lacks: a NAR
serialization of `git archive 155c50c`, hashed with sha256. The same
method reproduced the index's hash for 2309e6f and main's supabase pin
exactly, so the method is checked against two known values; the result
itself still needs a real fetch (`nix flake prefetch
github:effect-ts/skills/155c50c`) or CI to confirm.

Neither skill has symlinks, and each SKILL.md `name` matches its deploy
directory, so plain subdirectories of the source work, with no builder.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tw8tuRUyKtkcjmN4upeawF
Effect 4.0.0 went out on 2026-10-01 (npm `latest` is 4.0.5, `prev` is
3.22.3, `rc` has stayed at rc.118). The skill was written while v4 was a
release candidate, and three of its statements turned around.

- The new-project row said npm `latest` is v3 and v4 is a release
  candidate. Now v4 is the default and v3 needs a stated reason. A
  resolved -rc/-beta version gets its own row: it is a pre-GA build with
  the old import paths.
- The version gate named `effect@rc` as the accidental major upgrade.
  Now it is `effect@latest`. Before writing that, I tested which
  commands cross the major in a project pinned to ^3.22.2 (npm 10 and
  pnpm 10): a bare `npm install effect` or `pnpm add effect` keeps the
  ^3 range; only `effect@latest` rewrites it to ^4. The skill says
  exactly that rather than "never install effect".
- A new trap: GA moved `effect/unstable/*` modules to plain paths
  (`effect/unstable/http` -> `effect/http`) with no compatibility
  exports. On 4.0.5 the old path fails with TS2307. The note that
  stability is now a JSDoc tag was checked against the 4.0.5 source:
  6894 `@stability stable`, 5595 `unstable`, 271 `experimental`.

Re-checked against 4.0.5 (item 5), using the same probes as rc.117:
- Every v4 name in the Rust table and the trap list typechecks. That
  covers more than the four entries marked "checked" before.
- Schema.Date still rejects an ISO string at runtime, and
  DateFromString decodes it.
- The lesson-06 backoff port still makes 5 attempts (transient) and
  1 (permanent).
- A `readonly cause` payload field still compiles.
- `ParseResult` and `Schema.Schema.Encoded` are still gone.
- effect-playground still gives the same 71 typecheck errors as on
  rc.117.
- topcoat's missing-service row now names both codes it produces, on
  4.0.5 as on rc.117: TS2345, or TS2379 under exactOptionalPropertyTypes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tw8tuRUyKtkcjmN4upeawF
The lesson on library skills still quoted effect-ts's first step as
`pnpm add effect@rc`. That was true at 2309e6f; the pin is now 155c50c,
which says `pnpm add effect@latest`, and skills-sh.nix and the effect
skill were updated in 19200b5 while this file was not. CLAUDE.md is
always loaded, so the stale line told every agent the external skill
installs the RC, when in a v3 project it installs v4 - the exact upgrade
the version gate exists to stop. Also: "stale at each release
candidate" -> "at each release", since v4 is GA.

Found by /code-review (finding 1).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tw8tuRUyKtkcjmN4upeawF
Step 0's ls had a second glob, topcoat-*<ver>/macro/docs, which is the
repository layout (crates/<crate>/macro/docs). In the cargo registry
each macro crate is its own package, topcoat-<crate>-macro-<ver>/docs,
which the first glob already matches. So the second glob never matched,
2>/dev/null hid that, and the skill described two layouts for one set
of files. Checked against the registry after building topcoat 0.8.1:
the one glob finds 8 docs directories, including topcoat-router-<ver>/docs,
which the prose had not mentioned at all; it does now.

Found by /code-review (finding 8).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tw8tuRUyKtkcjmN4upeawF
The skill told agents to search the migration reference using the
recipes in effect-v3-to-v4. Those recipes grep
.repos/effect/migration/v3-to-v4.md, a clone that only the migration
skill's setup step creates. In an ordinary v4 project an agent following
the pointer gets "No such file" from rg, and the cheapest next move is
the guess the paragraph forbids.

Now the paragraph says to search node_modules/effect/src first. Then it
gives the reference's own fetch: a sparse, blobless clone of just
migration/ into the same .repos/effect path, so upstream's recipes also
work afterwards. The package does not ship the reference (checked in
4.0.5). The commands were run as written: 6.5 MB on disk, and the rg
recipe returns `Effect.catchAll` -> `Effect.catch`.

Found by /code-review (finding 9).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tw8tuRUyKtkcjmN4upeawF
@alycda
alycda merged commit b245b53 into main Oct 16, 2026
3 checks passed
@alycda
alycda deleted the claude/effectts-skill-g8nxna branch October 16, 2026 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants