mcpbeat Sign in

Small Collection Utilities Agent Skill

Four small helpers worth carrying in a shared module, each with the one way it misleads — a symmetric set difference for diffing two id sets, a position index for constant-time lookups, a tolerant parse/serialize pair for timestamped tokens that returns null instead of throwing, and a translator that rewrites an external link into your own scheme. Use when a diff reports every item as changed, when a position lookup returns the wrong index for a repeated element, when one malformed line takes down a whole screen, or when pasting a link into a search box searches for the link.

2k tokens
context cost
the whole folder, loaded on every use
1
files
instructions only
0
copies elsewhere
how many repositories repackaged it
100
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/maxrave-dev/kotlin-footguns --skill small-collection-utilities

The instruction itself

7 sections, as written by the author

Four helpers, four ways they mislead

Small enough to write from memory, which is exactly why nobody re-reads them once they are in.

Symmetric difference

infix fun <E> Collection<E>.symmetricDifference(other: Collection<E>): Set<E> {
    val left = this subtract other
    val right = other subtract this
    return left union right
}

Answers "which elements are on exactly one side" — additions and removals in one pass, when

reconciling a stored list of ids against a freshly fetched one.

Position index

fun <T> Iterable<T>.indexMap(): Map<T, Int> {
    val map = mutableMapOf<T, Int>()
    forEachIndexed { i, v -> map[v] = i }
    return map
}

Turns a repeated indexOf scan inside a loop into one pass plus constant-time lookups — the fix for

sorting one list into another list's order.

A tolerant parse/serialize pair for timestamped tokens

For <MM:SS.mm> word <MM:SS.mm> word …, match only the stamps and slice the text between them:

val stampRegex = Regex("""<(\d{2}):(\d{2})\.(\d{2,3})>""")
val stamps = stampRegex.findAll(payload).toList()
stamps.forEachIndexed { index, match ->
    val (minutes, seconds, fraction) = match.destructured
    val fractionMs = fraction.toLongOrNull() ?: 0L
    val timeMs = (minutes.toLongOrNull() ?: 0L) * 60_000L + (seconds.toLongOrNull() ?: 0L) * 1_000L +
        if (fraction.length == 2) fractionMs * 10L else fractionMs      // 2 digits are hundredths
    val to = if (index < stamps.size - 1) stamps[index + 1].range.first else payload.length
    val text = payload.substring(match.range.last + 1, to).trim()
    if (text.isNotBlank()) out += Token(text, timeMs)
}
if (out.isEmpty()) return null      // the caller falls back to the coarser format

Write the serializer beside it, and re-add whatever the parser stripped — noticing which parser.

The line-level parse strips the space after the line stamp, and its writer puts that back:

if (lines.isNullOrEmpty() || syncType != RICH) return null    // refuse to write another format
return lines.joinToString("\n") { "[$stamp] ${it.payload}" }  // pad back the separator trim() ate
fun String.toAppDeepLinkOrNull(): Uri? {
    val trimmed = trim()
    if (!trimmed.startsWith("http://", true) && !trimmed.startsWith("https://", true)) return null
    val uri = runCatching { Uri.parse(trimmed) }.getOrNull() ?: return null
    val host = uri.host?.lowercase()?.removePrefix("www.") ?: return null
    …
    return Uri.parse("yourapp://watch?v=$id")     // hand to the handler you already have
}

Traps

Symmetric difference is set arithmetic, so membership is all it can answer. Duplicates collapse,

order is gone, and an element that merely *moved* is on both sides and reports as unchanged. Run it

over models rather than ids and the answer depends on the model's equals — for a data class that

is every field, so editing a title reports the item as both removed and added. Diff the ids, then

compare matched pairs separately for content changes: two answers, two questions.

A position index's assignment is unconditional, so the last occurrence wins. A repeated element

maps to its final position and the map comes out shorter than the list, silently. Right for a list

of unique ids, wrong for anything else; build it with getOrPut if you want the first occurrence

instead. Same caveat as above on the key type — keying on a model means keying on its whole equals.

A tolerant parser's trap is threefold, and the first two are silent. *Matching stamp-and-text in

one regex ties the text's shape to the pattern:* punctuation, another angle bracket, a script the

character class never considered — all dropped, and the payload comes back looking merely short.

Slicing to the next delimiter has no opinion about the text at all. *The fraction's digit count is

its unit* — read the digits as a number without checking length and every two-digit stamp lands at

a tenth of where it belongs, early and plausible enough to look like a tuning problem rather than a

parsing one. *Every numeric field falls back rather than failing*, so "unparseable" and "zero" become

one answer — legitimate, but keep the whole-payload failure as null so the caller falls back to

a coarser rendering instead of losing the screen. The per-slice trim()s are restored by nothing,

safe only while the tokens stay a render-time value: serialize the parsed list back to text and the

words run together, so re-add the separator there too, and guard on the format tag either way, since

writing a payload you were not given parses into nonsense.

The temptation with an external link is to handle it — the answer is to translate it. Your

existing deep-link handler already knows the special cases (which id prefixes mean which kind of

collection, which need a prefix added), and a second handler starts as a copy and drifts the next

time one of those rules changes. Rewrite the URL into your own scheme and hand it to the same entry

point, so there is only ever one set of rules. Four details make the translator trustworthy: reject

non-http(s) text before parsing at all; return null for anything unsupported, so a pasted link

you do not handle becomes a search, not a failure; normalize the host before matching (lowercase(),

drop a leading www.), or the same link works or fails depending on where it was copied; and decide

the ambiguous case — a link carrying both an item and a collection — here, once, so every caller

agrees instead of guessing differently.

Verifying it

The parse entry point should have exactly one failure mode, and it should be null:

PARSER=composeApp/src/commonMain/kotlin/com/maxrave/simpmusic/extension/RichSyncParser.kt   # your equivalent
grep -nE "return null|throw |!!|require\(|checkNotNull\(" "$PARSER"

Pass condition: every hit is return null — here, twice — never throw, !!, require( or checkNotNull(. Confirm it is the right file with grep -n "Regex(\|fraction.length" "$PARSER": the timestamp regex and the fraction-length unit check both print.

Every caller of a translator that can return null has to say what null means:

FN=toAppDeepLinkOrNull
grep -rn -A3 "\.$FN()" --include="*.kt" .

The two collection helpers are only correct over distinct elements, so look at what reaches them:

grep -rn -B2 "\.indexMap()\|symmetricDifference" --include="*.kt" .

Per hit, confirm the receiver holds ids rather than models and cannot repeat. No hits at all is its

own finding: a helper nobody calls is one nobody has checked either.

How to use it

Copy the folder

Take maxrave-dev/small-collection-utilities from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.