meta-design-expressive-typescript-7e5942 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited meta-design-expressive-typescript-7e5942 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: One test decides every change: can a reader follow the flow without simulating any part of it? Where they cannot, the part they would simulate gets a name, and the function becomes an orchestrator — guards, named calls, assembly — over small pure helpers; where they can, the code stays inline. Two compiler checks carry the shape: aneverdefault fails compilation when a union grows past itsswitch, andas const satisfieschecks a config without widening its literals.
Detailed Resources:
as const satisfiespipe helps and when it hurts<critical_requirements>
Write each orchestrator as guard clauses, then named calls, then the assembly of their results. A reader learns what every step produces from its name; a transformation written inline has to be run in their head before the next line means anything.
Extract what a reader would have to simulate, and leave what reads in one scan. A name is the only thing extraction adds, so a helper whose name restates its single line adds a jump and nothing else.
Name a function for the fact it establishes — `isContentAddition(line)`, not `startsWithPlusButNotTriplePlus(line)`. A name that describes the mechanism has to change whenever the mechanism does, and tells the reader nothing the body did not.
Pass every value an extracted helper reads as a parameter, or return the helper from a factory — `skills.filter(isRelevantTo(selection))`. A helper that closes over its caller's locals cannot sit at the bottom of the file or be tested on its own, which is what extracting it was for.
Use the plain array method where one call reads clearly, and an operation named for the concept where the manual version needs an accumulator. Object.groupBy(skills, (skill) => skill.category) states what a loop over a mutable record makes the reader reconstruct; wrapping a single .filter() in a library call states nothing the method did not.
</critical_requirements>
Auto-detection: orchestrator function, two-tier function, extract function, named predicate, named transform, guard clause, early return, arrow anti-pattern, nested if/else, discriminated union, exhaustive switch, never default, as const satisfies, sequential await, Promise.all, Object.groupBy, pipe, readability refactor, mental simulation
Applies to:
pipeHandled elsewhere:
groupBy or partition, and which runtimes have it, is settled by whatever owns the project's utilities; this skill asks only that the call site name the operation.<philosophy>
Expressive TypeScript is readability-first, practical functional programming. Two ideas carry it:
What it is not: category theory (monads, functors, effect systems); composition with no named data, such as compose(map(prop("name")), filter(isActive)) — passing a named function to filter is fine, a chain with no visible argument is not; or extraction for its own sake. Three similar lines beat a premature abstraction.
</philosophy>
<decision_framework>
Does a reader have to simulate this block to know what it produces?
├─ NO → Leave it inline. A one-expression lambda reading a named field is already named.
└─ YES → Does the same logic appear in two or more places?
├─ YES → Extract once, and call it from each.
└─ NO → Would a name say something the expression does not?
├─ YES → Extract. The name is the documentation.
└─ NO → Leave it. selectedIds.has(item.id) needs no isItemSelected.A function earns the two-tier shape when it mixes concerns — validating, transforming, assembling — or when it runs past about fifteen lines in clear steps. Past that length with no clear steps, split it by responsibility first; under it, with one concern, leave it alone. A hot path where a profile shows the extra calls matter is the one place readability yields.
pipeOne map, filter, find or some, or a simple sum?
├─ YES → The array method. It already reads in one scan.
└─ NO → Is the manual version a loop or reduce building groups, counts, a lookup or two partitions?
├─ YES → A function named for the operation: native where the platform has one
│ (Object.groupBy), otherwise from the project's existing utilities.
└─ NO → Three or more steps whose intermediate values only feed the next step?
├─ YES → pipe, if the project already uses one; otherwise named intermediates.
└─ NO → Named intermediates.Named intermediates earn their place when a later line reads them again — Pattern 1's validUsers feeds the rejected count. Read once, by the next line, they are plumbing.
</decision_framework>
<patterns>
Guards, named calls, and the assembly of their results; anything a reader would have to simulate lives in a helper below, named for what it produces. An orchestrator that keeps growing is doing transformation work inline — push it down until each line is one named step.
function processUserImport(rawData: RawImportData): ImportResult {
if (rawData.users.length === 0) return EMPTY_IMPORT_RESULT;
const validUsers = rawData.users.filter(isValidUser);
const uniqueUsers = removeDuplicatesByEmail(validUsers.map(toUserRecord));
const { existing, newUsers } = separateExistingUsers(uniqueUsers);
return {
imported: newUsers.length,
skipped: existing.length,
rejected: rawData.users.length - validUsers.length,
};
}Full code: examples/core.md
Where an expression has to be simulated, a name replaces the simulation. A lambda whose body is one self-describing field is already named, and stays.
// Simulated: the reader runs the predicate to learn what it selects
const additions = diff.filter(
(line) => line.startsWith("+") && !line.startsWith("+++"),
);
// Named: the reader is told
const contentAdditions = diff.filter(isContentAddition);
// Already named by the field it reads: leave it inline
const activeUsers = users.filter((user) => user.isActive);Full code: examples/core.md
Invert each precondition and return early. The happy path then runs at zero nesting, and every line below the guards can assume them.
function getDiscount(user: User, cart: Cart): number {
if (!user.isActive) return 0;
if (cart.items.length === 0) return 0;
if (!user.membership) return STANDARD_DISCOUNT;
return calculateMemberDiscount(user.membership, cart.total);
}Full code: examples/core.md
Give each state its own variant carrying only its own fields, and end the switch with a never default. A new variant then fails compilation at every switch that does not handle it, and the error names the variant.
function handleTaskEvent(event: TaskEvent): void {
switch (event.kind) {
case "started":
return markRunning(event.startedAt);
case "failed":
return reportFailure(event.error);
default: {
const unhandled: never = event;
return unhandled;
}
}
}Full code: examples/core.md
Await in sequence only where a step reads the previous step's result, and start independent steps together with Promise.all. The code then says which steps depend on which, and the wait is the longest step rather than the sum of them.
async function onboardNewUser(input: OnboardInput): Promise<OnboardResult> {
const user = await createUserRecord(input);
const [profile, settings] = await Promise.all([
initializeProfile(user.id, input.preferences),
applyDefaultSettings(user.id),
]);
await sendWelcomeEmail(user.email, profile.displayName);
return { user, profile, settings };
}Full code: examples/core.md
as const satisfies for Configurationas const keeps every literal; satisfies then checks the shape without widening it. A misspelled field fails compilation, and ROUTES.home.path types as "/" rather than string.
Under mutation testing it costs the constant all its mutants: StrykerJS (as of 10.1.1) makes none inside as const, and its report does not say so. Keep the pattern for constants whose literal types are read, as keyof typeof ROUTES reads them below; elsewhere a type annotation or satisfies alone checks the same shape and is still mutated.
const ROUTES = {
home: { path: "/", auth: false },
dashboard: { path: "/dashboard", auth: true },
} as const satisfies Record<string, RouteConfig>;
type RouteName = keyof typeof ROUTES; // "home" | "dashboard"Full code: examples/core.md
Where the manual version is a loop with an accumulator, call a function whose name is the operation. Where one array method already reads, keep the method.
// Named for the operation a manual loop would hide
const byCategory = Object.groupBy(skills, (skill) => skill.category);
// One method, already clear: no helper and no library
const names = skills.map((skill) => skill.name);Full code: examples/data-transforms.md
</patterns>
<red_flags>
Breaks at runtime:
map, filter or forEach when the function takes an optional second parameter — the array method calls it with (element, index, array), so ["1", "2", "3"].map(parseInt) is [1, NaN, NaN], and TypeScript accepts it. Pass a reference only to a helper written for that array; wrap anything else: .map((value) => parseInt(value, 10)).const arrow functions, with the orchestrator called while the module is still loading — a const cannot be read before its line has run, so the call throws ReferenceError. function declarations are hoisted, which is what lets them sit at the bottom of the file.Promise.all where every outcome matters — it rejects with the first rejection, the other operations keep running, and their later failures are dropped. Use Promise.allSettled when the caller needs each result.never default that returns the value, on data that was asserted into its type rather than validated — the compiler's guarantee stops at the assertion, so at runtime the unexpected value is handed back to the caller. Throw from the default there.switch over a union with no never default — a new variant falls through silently. The compiler reports it only when every case returns from a function with an annotated return type (TS2366, which names the missing return rather than the variant); a void handler, a switch that assigns or calls before the function returns, and an unannotated function without noImplicitReturns all compile. End every such switch with the never default.Surprising behaviour:
default that returns a fallback in place of the never check — every new variant lands in the fallback, so the compiler has nothing to report.satisfies Shape as const does not compile — a const assertion applies only to a literal, and a satisfies expression is not one. Write as const satisfies Shape.never value without reading it — noUnusedLocals and unused-variable lint rules report it. Returning it is a read.Object.groupBy returns a null-prototype object whose keys are all optional, so each group types as T[] | undefined.Costs the reader:
filteredMappedItems) rather than what it holds (activeDisplayRecords) — the reader re-derives the meaning the name could have stated.if — invert it into a guard that returns, and the body loses a level.utils file — it becomes the place nothing belongs. Put a helper at the bottom of the file whose orchestrator it serves or, when several files call it, in a module named for its domain beside those callers — never interspersed with the orchestrators it serves.</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.