Official agent skill

fp-go Pattern Matching

by IBM in IBM/fp-go

Guides writing or reviewing point-free, first-match-wins case lists in fp-go v2 Go code instead of switch statements and if-else chains.

OfficialApache-2.0Auto-check passedDevelopment

Install fp-go Pattern Matching

skills CLI
$ npx skills add IBM/fp-go --skill fp-go-pattern-matching -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install IBM/fp-go fp-go-pattern-matching --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/IBM/fp-go.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/fp-go-pattern-matching .claude/skills/fp-go-pattern-matching && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
fp-go-pattern-matching
GitHub stars
2k
Token cost
~2.8k tokens
SKILL.md length
760 words
Files
1
Skills in repo
10
Repo updated
First seen
Licence
Apache-2.0

At a glance

Guides writing or reviewing point-free, first-match-wins case lists in fp-go v2 Go code instead of switch statements and if-else chains.

  • Works in 8 steps: Cases are ordered specific -> general (a… → No AltAllArray([]Option{c(x), ...}) over… → Point-free: no lambdas that only compare… → …
  • Replacing a Go switch or if-else chain with composable pattern matching
  • SKILL.md covers Before You Generate, The Model, The Canonical Idiom (Use This… and Point-Free Building Blocks, plus 7 more sections
  • Calls go

What it does

This skill applies to Go code written with the fp-go v2 functional library, covering a model where a case is a function returning an Option and a match is an ordered list of cases in which the first Some wins. It steers you toward composing cases from guards, getters, and formatters, reserving lambdas for leaf accessors or deliberate side effects inside tests.

It points to the library's own long-form reference and tested examples instead of relying on memorized signatures, since fp-go is described as rare in training data. After writing code it expects you to run the Go build and vet commands and fix any type-parameter or argument-order mistakes before presenting the result, and it recommends looking up unfamiliar combinators through a project MCP server rather than guessing at them.

When your agent uses it

  • Replacing a Go switch or if-else chain with composable pattern matching
  • Writing a router, classifier, or multi-format parser in fp-go v2
  • Reviewing fp-go code that chains Alt, FindFirstMap, or similar combinators

Example prompts

  • “Rewrite this switch statement as a fp-go point-free match.”
  • “Add a fallback case for an unrecognized request format.”
  • “Review this classifier for point-free style and argument-order mistakes.”

Requirements

  • Go with the github.com/IBM/fp-go/v2 package

Workflow steps

8 steps, taken from the first numbered list in SKILL.md.

  1. Cases are ordered specific -> general (a general case first shadows later ones).
  2. No AltAllArray([]Option{c(x), ...}) over case applications. Rewrite to A.Fold(RO.AltMonoid...).
  3. Point-free: no lambdas that only compare a field, call one stdlib function,
  4. The matcher is built by a constructor func, once, not inside a loop or per request.
  5. Intended total functions end in RO.GetOrElse, so Option does not leak.
  6. Cases are pure. If a branch needs IO, return the effect (Option[IOResult[B]]) instead of running it in the guard.
  7. Each case has a unit test, plus a test of the combined match, including the default and priority.
  8. Fixed, local, non-reused branching stays a plain switch. Do not force this pattern where it adds nothing.

What it can do on your machine

Read from SKILL.md and the folder at commit 1c4245d. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • go

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

fp-go Pattern Matching loads about 2.8k tokens when it runs. Until then it costs about 168 tokens; SKILL.md has 760 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~168
When it runs · the whole SKILL.md, loaded when a task matches
~2.8k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from IBM/fp-go at commit 1c4245d, republished under its Apache-2.0 licence (© IBM). 760 words, ~2,782 tokens.

Download SKILL.mdSave it as .claude/skills/fp-go-pattern-matching/SKILL.md (or your agent's skills folder).
name
fp-go-pattern-matching
description
Use this skill when writing, refactoring, or reviewing fp-go v2 code that replaces switch/case, if-else chains, or type switches with composable, point-free pattern matching: first-match-wins case lists, routers, classifiers, multi-format parsers, fallback chains, or matching over sum types and slices. Trigger on any mention of pattern matching in Go, match/case, switch to functional, AltAllArray, AltAllSeq, AltMonoid, readeroption.AltMonoid, readeroption.Alt, FindFirstMap, FindLastMap, FilterMap, option.Alt, first Some, fallback chain, InstanceOf, prism GetOption as matcher, GetOrElse default branch, option.Fold, either.Fold, or array.Match.

fp-go Pattern Matching

All imports come from github.com/IBM/fp-go/v2 and use the canonical aliases of the fp-go skill. The canonical long-form reference is v2/docs/PATTERN_MATCHING.md. Tested examples are in v2/array/example_pattern_matching_test.go and v2/array/pattern_matching_test.go.

go
import (
    A  "github.com/IBM/fp-go/v2/array"
    F  "github.com/IBM/fp-go/v2/function"
    N  "github.com/IBM/fp-go/v2/number"
    O  "github.com/IBM/fp-go/v2/option"
    P  "github.com/IBM/fp-go/v2/predicate"
    R  "github.com/IBM/fp-go/v2/result"
    RD "github.com/IBM/fp-go/v2/reader"
    RO "github.com/IBM/fp-go/v2/readeroption"
    S  "github.com/IBM/fp-go/v2/string"
    "github.com/IBM/fp-go/v2/optics/prism"
    "github.com/IBM/fp-go/v2/readerresult" // generic ReaderResult[T, B]; not context/readerresult
)

Before You Generate

fp-go is low-frequency in training data, so signatures are easy to misremember. For any combinator not shown below, look it up via the fp-go MCP server's search_examples / get_example tools (see the fp-go-mcp skill) instead of guessing. After writing code, run go build ./... and go vet ./... and fix any type-parameter or argument-order errors before presenting it.

The Model

  • A case is func(T) Option[B]: Some(b) if it matches, None otherwise. RO.ReaderOption[T, B], O.Kleisli[T, B] and prism.Prism[T, B].GetOption are all aliases of this type, so values from these packages mix freely in one slice.
  • A match is an ordered slice of cases, and the first Some wins.
  • A default turns the partial match (Option[B]) into a total function.
  • Everything is point-free. Cases are composed from guards, getters and formatters with Flow/Pipe. Lambdas are allowed only for leaves (field accessors, multi-field formatters, deliberate side effects in tests).

The Canonical Idiom (Use This by Default)

go
func getMethod(r Request) string { return r.Method }   // leaf accessor (or lens.Get)
func getPath(r Request) string   { return r.Path }

func on(guard P.Predicate[Request], branch func(Request) string) RO.ReaderOption[Request, string] {
    return F.Flow2(O.FromPredicate(guard), O.Map(branch))
}

func isMethod(m string) P.Predicate[Request] {
    return F.Flow2(getMethod, P.IsStrictEqual[string]()(m))
}

func HandleRequest() func(Request) string {
    return F.Pipe2(
        A.From(                                           // ordered, specific -> general
            on(isMethod("GET"), F.Flow2(getPath, S.Format[string]("Fetching: %s"))),
            on(isMethod("DELETE"), F.Flow2(getPath, S.Format[string]("Deleting: %s"))),
        ),
        A.Fold(RO.AltMonoid[Request, string]()),          // lazy first-match
        RO.GetOrElse(F.Flow2(getMethod, S.Format[string]("Unsupported: %s"))), // default
    )
}
  • Lazy: stops at the first match. Later cases are never invoked.
  • Building the matcher runs no case. Wrap it in a constructor func (never a package-level var), call the constructor once during setup, and reuse the result.
  • The folded match is itself a case, so matches nest.
  • Zero cases give Empty(), which is None for all inputs.

Generic helper for the most common case shape:

go
func when[T, B any](guard P.Predicate[T], b B) RO.ReaderOption[T, B] {
    return F.Flow2(O.FromPredicate(guard), O.Map(F.Constant1[T](b)))
}

Alternatives, all lazy and point-free:

go
// append one or two fallback cases
F.Pipe2(c1, RO.Alt(F.Constant(c2)), RO.Alt(F.Constant(c3)))

// FindFirstMap over a slice of cases
F.Flow3(
    RD.Read[O.Option[B], T],                   // x -> apply-a-case-to-x
    A.FindFirstMap[RO.ReaderOption[T, B], B], // -> first case that matches
    RD.Read[O.Option[B]](cases),               // run over the cases
)

Point-Free Building Blocks

Instead ofWrite
func(r Req) bool { return r.Method == m }F.Flow2(getMethod, P.IsStrictEqual[string]()(m))
func(e Ev) bool { return e.Priority > 8 }F.Flow2(getPriority, N.MoreThan(8))
p(x) && q(x)F.Pipe1(p, P.And(q)), also P.Or, P.Not
strings.HasPrefix(s, p)F.Bind2nd(strings.HasPrefix, p)
strings.TrimPrefix(s, p)F.Bind2nd(strings.TrimPrefix, p)
fmt.Sprintf("x %v", v)S.Format[T]("x %v")
"INFO: " + sS.Prepend("INFO: "), S.Append
func(T) B { return b }F.Constant1[T](b)
strconv.ParseInt(s, base, 64)F.Bind23of3(R.Eitherize3(strconv.ParseInt))(base, 64)
strconv.Atoi(s)R.Eitherize1(strconv.Atoi)
Result -> OptionR.ToOption[A]
func() Option[B] { return c(x) }F.Nullary2(F.Constant(x), c)
func(s Shape) any { return s }F.ToAny[Shape]
replace any errorR.MapLeft[A](F.Constant1[error](err))

(number has no Equal/LessThanEqual. Use P.IsStrictEqual, P.IsZero[T](), N.LessThan(n+1).)

Critical Pitfall: AltAllArray over Applied Cases Is Eager

go
// BAD: all three cases run, because Go evaluates the slice literal first
O.AltAllArray(O.None[B]())([]O.Option[B]{c1(x), c2(x), c3(x)})
// its point-free equivalent shows why: SequenceArray runs every case first
F.Pipe2(cases, RD.SequenceArray[T, O.Option[B]], RD.Map[T](O.AltAllArray(O.None[B]())))

AltAllArray stops scanning at the first Some but cannot prevent the calls that built the slice. Use it only when the Option values already exist. When reviewing code, rewrite this form to A.Fold(RO.AltMonoid[T, B]())(A.From(c1, c2, c3)). The same applies to either.AltAllArray and result.AltAllArray.

Building Cases

NeedBuild with
guard -> constantwhen(guard, b) = F.Flow2(O.FromPredicate(guard), O.Map(F.Constant1[T](b)))
guard -> computedF.Flow2(O.FromPredicate(guard), O.Map(f))
guard -> transform -> partial parseF.Flow3(O.FromPredicate(g), O.Map(f), O.Chain(k))
func(A) (B, bool)O.FromValidation(f), for example O.FromValidation(os.LookupEnv)
type switch on anyF.Flow2(O.InstanceOf[Variant], O.Map(handler))
type switch on named interfaceF.Flow3(F.ToAny[Shape], O.InstanceOf[Variant], O.Map(handler))
parse / extractprism.ParseInt().GetOption, ParseInt64, ParseFloat64, ParseBool, ParseDate(layout), RegexMatcher(re)
pointer non-nilO.FromNillable2
catch-allRO.Of[T](b) as the last case
no combinator fitssmall named func(T) O.Option[B] leaf

Prefix-parse case, fully point-free:

go
func withPrefix(prefix string, base int) RO.ReaderOption[string, int64] {
    return F.Flow3(
        O.FromPredicate(F.Bind2nd(strings.HasPrefix, prefix)),
        O.Map(F.Bind2nd(strings.TrimPrefix, prefix)),
        O.Chain(F.Flow2(F.Bind23of3(R.Eitherize3(strconv.ParseInt))(base, 64), R.ToOption[int64])),
    )
}

Sum-type cases via an adapter:

go
func caseOf[T Shape, B any](f func(T) B) RO.ReaderOption[Shape, B] {
    return F.Flow3(F.ToAny[Shape], O.InstanceOf[T], O.Map(f))
}
describe := A.Fold(RO.AltMonoid[Shape, string]())(A.From(
    caseOf(S.Format[Circle]("circle %v")),
    caseOf(S.Format[Rect]("rect %v")),
))

Go has no exhaustiveness checking. A default guarantees a result, not coverage. Add one test per variant.

Show full SKILL.md (277 more words)Show less

Closing the Match

go
F.Pipe1(match, RO.GetOrElse(F.Flow2(getX, S.Format[X]("no match: %v")))) // default sees input
F.Pipe1(match, RO.GetOrElse(F.Constant1[T](dflt)))                       // constant default
A.From(c1, c2, RO.Of[T](dflt))                                           // catch-all case, still Option

Two-Way Matches: Use the Eliminator, Not a Case List

InputUse
Option[A]O.Fold(onNone func() B, onSome func(A) B), e.g. O.Fold(F.Constant("none"), S.Format[int]("got %d"))
Either[E, A]either.Fold(onLeft, onRight)
Result[A]R.Fold(onErr func(error) B, onOk func(A) B)
predicateP.Fold(onFalse, onTrue func(A) B)(pred)
slice empty/non-emptyA.Match(onEmpty func() B, onNonEmpty func([]A) B), A.MatchLeft(onEmpty, func(head A, tail []A) B)

Matching over Slices

GoalUse
first element matching any caseA.FindFirstMap(match) (lazy over elements)
last elementA.FindLastMap(match)
with indexA.FindFirstMapWithIndex(func(int, A) O.Option[B])
all matchesA.FilterMap(match) -> []B

Matching with Errors

When a failing case should say why, use Result cases and the error-aware monoid:

go
func withPrefixR(prefix string, base int, err error) readerresult.ReaderResult[string, int64] {
    return F.Flow4(
        R.FromPredicate(F.Bind2nd(strings.HasPrefix, prefix), F.Constant1[string](err)),
        R.Map(F.Bind2nd(strings.TrimPrefix, prefix)),
        R.Chain(F.Bind23of3(R.Eitherize3(strconv.ParseInt))(base, 64)),
        R.MapLeft[int64](F.Constant1[error](err)),
    )
}

parse := A.Fold(readerresult.AltMonoid(F.Constant(readerresult.Left[string, int64](errNoMatch))))(A.From(
    withPrefixR("0x", 16, errHex),
    withPrefixR("", 10, errDec),
))
// two alternatives: F.Pipe1(c1, readerresult.Alt(F.Constant(c2)))
  • Lazy. If all cases fail, the last case's error is returned. With no cases, zero is returned.
  • If the cases are declared as plain func(string) R.Result[int64], A.From may need an explicit type argument: A.From[readerresult.ReaderResult[string, int64]](...).
  • readereither.AltMonoid(zero) is the equivalent for custom error types.

Review Checklist

  1. Cases are ordered specific -> general (a general case first shadows later ones).
  2. No AltAllArray([]Option{c(x), ...}) over case applications. Rewrite to A.Fold(RO.AltMonoid...).
  3. Point-free: no lambdas that only compare a field, call one stdlib function, format a value or return a constant. Replace them using the building-blocks table.
  4. The matcher is built by a constructor func, once, not inside a loop or per request.
  5. Intended total functions end in RO.GetOrElse, so Option does not leak.
  6. Cases are pure. If a branch needs IO, return the effect (Option[IOResult[B]]) instead of running it in the guard.
  7. Each case has a unit test, plus a test of the combined match, including the default and priority.
  8. Fixed, local, non-reused branching stays a plain switch. Do not force this pattern where it adds nothing.

© IBM, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in skills/fp-go-pattern-matching of IBM/fp-go.

Open the folder on GitHubat commit 1c4245d

Compare with similar skills

fp-go Pattern Matching next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

fp-go Pattern Matching compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
fp-go Pattern Matching this skillIBM/fp-go2k—~2.8kAutomated safety check: PassApache-2.0
Golang Dependency Injectionsamber/cc-skills-golang3.4k—~3.2kAutomated safety check: PassMIT
Golang Samber Docontext-labs/whip1.1k1 repos~2.3kAutomated safety check: PassMIT
Swiftui View RefactorDimillian/Skills4k5 repos~2kAutomated safety check: PassMIT
RTK Rust Design Patternsrtk-ai/rtk83k—~1.9kAutomated safety check: PassApache-2.0
Architecture PatternsKartikLabhshetwar/better-shot2.4k2 repos~1.4kAutomated safety check: PassCustom licence

Similar skills

  • Golang Dependency Injection

    samber/cc-skills-golang

    Comprehensive guide for dependency injection (DI) in Golang.

    3.4k GitHub stars~3.2k tokensUpdated 7 days ago
    DevelopmentAuto-check passed
  • Golang Samber Do

    context-labs/whip

    Dependency injection in Go using samber/do — service containers, lifecycle, scopes, health checks, graceful shutdown, module organization.

    1.1k GitHub starsUsed in 1 repo~2.3k tokens
    DevelopmentAuto-check passed
  • Swiftui View Refactor

    Dimillian/Skills

    Refactor and review SwiftUI view files with strong defaults for small dedicated subviews, MV-over-MVVM data flow, stable view trees, explicit dependency injection, and correct Observation usage.

    4k GitHub starsUsed in 5 repos~2k tokens
    DevelopmentAuto-check passed
  • Describes seven Rust design patterns for the RTK CLI filter modules, with when to use each, RTK examples, and notes on when a pattern is overkill.

    83k GitHub stars~1.9k tokensUpdated yesterday
    DevelopmentAuto-check passed
  • Architecture Patterns

    KartikLabhshetwar/better-shot

    Deep dive into software architecture for macOS. An agent skill from KartikLabhshetwar/better-shot.

    2.4k GitHub starsUsed in 2 repos~1.4k tokens
    DevelopmentAuto-check passed
  • Official

    Replaces a plain TypeScript union plus switch statements with frozen subclasses and a visitor interface when several places dispatch on the same variants.

    48k GitHub stars~830 tokensUpdated today
    DevelopmentAuto-check passed

More from IBM/fp-go

All 10 skills in this repo
  • Official

    Covers handling Go's context.Context idiomatically in fp-go code: reading and scoping context through operators, timeouts, cancellation and converting ctx-first functions.

    2k GitHub stars~4.2k tokensUpdated yesterday
    Auto-check passed
  • Teaches an agent to write fp-go v2 services with the Effect type, carrying dependencies in its type parameter instead of in context.Context or parameters.

    2k GitHub stars~4.3k tokensUpdated yesterday
    Auto-check passed
  • Official

    Shows how to build composable, context-aware HTTP pipelines in Go using fp-go's ReaderIOResult monad instead of raw net/http calls.

    2k GitHub stars~4.4k tokensUpdated yesterday
    Auto-check passed
  • Official

    Generates or hand-writes composable lenses for the fp-go library so nested Go structs can be read and updated immutably.

    2k GitHub stars~4.3k tokensUpdated yesterday
    Auto-check passed
  • Official

    Adds logging to fp-go functional pipelines with Tap operators, entry and exit logs and error context, so that logging never changes the value or error flowing through.

    2k GitHub stars~3.7k tokensUpdated yesterday
    Auto-check passed
  • Official

    Configures and queries the fp-go MCP server so Claude Code, Claude Desktop or another MCP client can search fp-go's examples and skills directly.

    2k GitHub stars~3.3k tokensUpdated yesterday
    Auto-check passed

Works with

Categories

Questions about fp-go Pattern Matching

What does fp-go Pattern Matching do?

Guides writing or reviewing point-free, first-match-wins case lists in fp-go v2 Go code instead of switch statements and if-else chains. This skill applies to Go code written with the fp-go v2 functional library, covering a model where a case is a function returning an Option and a match is an ordered list of cases in which the first Some wins. It steers you toward composing cases from guards, getters, and formatters, reserving lambdas for leaf accessors or deliberate side effects inside tests.

When should I use fp-go Pattern Matching?

fp-go Pattern Matching fits situations like: replacing a Go switch or if-else chain with composable pattern matching; writing a router, classifier, or multi-format parser in fp-go v2; reviewing fp-go code that chains Alt, FindFirstMap, or similar combinators.

How do I install fp-go Pattern Matching in Claude Code?

Run `npx skills add IBM/fp-go --skill fp-go-pattern-matching -a claude-code`. Or copy the skill folder (skills/fp-go-pattern-matching in IBM/fp-go) into .claude/skills/fp-go-pattern-matching in your project. Claude Code loads it when a task matches its description.

How do I install fp-go Pattern Matching in Codex?

Run `npx skills add IBM/fp-go --skill fp-go-pattern-matching -a codex`. Or copy the skill folder (skills/fp-go-pattern-matching in IBM/fp-go) into .agents/skills/fp-go-pattern-matching in your project. Codex loads it when a task matches its description.

Can I use fp-go Pattern Matching in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add IBM/fp-go --skill fp-go-pattern-matching -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/fp-go-pattern-matching, .gemini/skills/fp-go-pattern-matching, .github/skills/fp-go-pattern-matching and .opencode/skills/fp-go-pattern-matching in your project.

What does fp-go Pattern Matching need to run?

Going by SKILL.md and its folder, fp-go Pattern Matching needs the command-line tools its instructions call (go). Our summary lists: Go with the github.com/IBM/fp-go/v2 package.

Does fp-go Pattern Matching access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is fp-go Pattern Matching safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does fp-go Pattern Matching use?

fp-go Pattern Matching is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does fp-go Pattern Matching use?

About 2.8k tokens (SKILL.md is roughly 11k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to fp-go Pattern Matching?

Skills that share tags, products or a category with fp-go Pattern Matching: Golang Dependency Injection (samber/cc-skills-golang, 3.4k stars), Golang Samber Do (context-labs/whip, 1.1k stars), Swiftui View Refactor (Dimillian/Skills, 4k stars) and RTK Rust Design Patterns (rtk-ai/rtk, 83k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains fp-go Pattern Matching?

IBM (a GitHub organization, an official publisher) maintains it in IBM/fp-go, which has 2,029 GitHub stars. The repository holds 10 skills in this directory. The repository was last updated on October 7, 2026.

Source: IBM/fp-go on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.