patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited patterns (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.
Bias toward senior-authored code patterns on any code-writing task so the output is easier for junior readers and AI agents to maintain. Produce inline code that leans these patterns; do not propose enforcement infrastructure, ADRs, or audit reports. For that, the companion convention skill runs in Audit/Bootstrap/Authoring modes.
Before applying any pattern below, MUST detect the surrounding conventions:
Glob with pattern resolved from the target file's parent directory and primary extension. Derive <target-file-parent> from the path of the file the user asked to write/modify; derive <ext> from the detected language. Example: target src/features/auth/login.ts in a TypeScript project → pattern: "src/features/auth/**/*.{ts,tsx}". Python target → pattern: "<parent>/**/*.py". Emit the resolved pattern in the tool call — NEVER pass literal angle brackets.Grep in parallel for each detection probe — every call uses output_mode: "count":| Signal | pattern | Typical glob | ||
|---|---|---|---|---|
| Branded type (TS) | __brand: | **/*.{ts,tsx} | ||
| Sealed/union (Kotlin/Scala) | `sealed (class\ | interface\ | trait)` | **/*.{kt,scala} |
| Discriminated union (TS) | `\ | \{ status:` | **/*.{ts,tsx} | |
| FSM | `createMachine\ | useReducer\ | typestate` | (language-appropriate) |
| Raw-scalar domain signature | function \w+\([a-z]\w*: string\b | **/*.{ts,tsx,js} |
Every Grep call above MUST also pass path scoped to the target file's parent directory (or the nearest source root when writing a new file at the tree root). Omitting path causes an unscoped global search that silently inflates counts in large monorepos and breaks the density signal.
The patterns are defaults, not mandates. They apply when the surrounding code doesn't contradict them.
For a parent directory with > 2,000 source files of the target language, MUST invoke Agent:
Agent
subagent_type: "Explore"
name: "surroundings-scan"
description: "Iceberg surroundings density scan"
prompt: "<self-contained: file glob, language, every Grep pattern from the table above, return as a count summary line>"
max_turns: 6
run_in_background: false
isolation: "none"run_in_background: false is load-bearing: the density-scan result gates subsequent code generation, and foreground subagents can be backgrounded mid-run (Ctrl+B). Explicitly setting false prevents the session from proceeding without the scan. name surfaces the scan in the user's agent panel with a readable label rather than an opaque id. isolation: "none" is correct because the scan is read-only — worktree isolation would waste disk and add latency with no benefit.
The prompt MUST be self-contained. Do NOT instruct the subagent to invoke AskUserQuestion.
convention skillIf the request shifts toward enforcement, audit, or bootstrap, STOP writing code. Tell the user: "This is a `convention` skill task — I am invoking it now." Then MUST invoke the Skill tool with skill: "iceberg:convention". Do NOT attempt a slash-command route — the harness routes via the Skill tool.
Domain identifiers (UserId, OrderId, SKU, etc.), money amounts, durations, percentages, email/URL — use the language's nominal-type mechanism when available.
type UserId = string & { __brand: 'UserId' })typing.NewType (check-time only via mypy --strict — runtime is indistinguishable; note the gap if it matters)type UserId string (weaker — implicit convertible from untyped literals; flag it)Why: a junior reads transfer(from: UserId, to: AccountId, amount: Money) and the signature teaches the domain. transfer(from: string, to: string, amount: number) teaches nothing and will eventually be called with swapped arguments.
For values whose shape is mutually exclusive across states (API fetch, form submit, auth, payment, any lifecycle):
Good:
type FetchState<T, E> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success', data: T }
| { status: 'failure', error: E }Bad:
{ loading?: bool, error?: Error, data?: T, hasRetried?: bool }Why: the record represents 16 combinations; most are impossible, and a junior will write code defending against them forever.
When two or more booleans derive from the same underlying process (isLoading + isPending + isSuccess + isCancelled), that's an unadmitted FSM. Prefer a single status discriminator with a pure reducer (state, event) -> state, or the idiomatic FSM library for the target ecosystem (XState for TS/React, typestate pattern for Rust, transitions for Python, Redux/Zustand with a status field, etc.).
Enumerate all states up front: idle, in_flight, success, recoverable_failure, terminal_failure, cancelled. Cancellation is a state, not an afterthought. If you skip it, a junior's test won't cover it.
Keep decisions (pricing, validation, formatting, routing, business rules) in pure functions; wrap them in a thin shell that handles I/O, time, randomness, and logging. If the user asks for a small feature, put the business decision in a pure function even when the surrounding shell is messy.
Why: pure functions are trivially testable and readable. A junior can read one function and understand the rule. An imperative controller scattered across I/O calls hides the rule.
Any cast from a raw primitive into a branded/nominal type goes through a single named constructor (createUserId(raw): UserId | ValidationError). Direct coercion (raw as UserId, UserId(raw), unsafeCoerce) in business-logic code is a smell — the junior sees the cast and wonders whether validation happened.
When matching on a discriminated union / sum type, include a compile-fail sink for unhandled variants.
default: return assertNever(state) with function assertNever(x: never): never { throw new Error(...) }case _: typing.assert_never(state) in a matchWhy: adding a new variant upstream should break every consumer loudly. A silent default clause is a state-handling bug waiting to happen.
doThingEagerly() / doThingLazily() beat doThing(true) / doThing(false). A bare boolean at a call site is a puzzle the junior has to resolve by reading the function signature. Split into two functions, use an enum, or accept a string literal union.
catch (e) { } and catch (e) { log(e) } without rethrowing, translating to a domain error, or transitioning a state machine are silent failure modes. Juniors read the call site and assume nothing can fail. Either:
throw new PaymentFailedError({ cause: e }))dispatch({ type: 'failed', error: e }))Don't expose raw async primitives (futures, promises, observables, tasks, channels) in the signatures of business-logic functions unless the call site can't avoid it. Wrap them in framework-provided primitives that hide the await ceremony — React Server Components returning Promise<JSX>, server actions, Suspense-boundary hooks, structured concurrency blocks, GenServer.call, etc. Frameworks that have no such primitive (raw tokio in Rust, raw goroutines in Go) are exceptions — be honest.
console.log, println!, print() in tip-layer code is junior-authored tracing. Use the project's structured logger or omit the log. For debugging sessions, console.log is fine; for code that ships, it's not.
If you're writing an HTTP client, DB client, cache client, or queue publisher, wrap tracing/logging at the adapter construction (e.g., a decorator, middleware, or tracing::instrument attribute), not at call sites. Juniors get observability for free without having to remember to add it.
convention skill.convention skill (Audit mode).convention insteadDefer when the user's task is about the framework, not the code:
In those cases, do NOT engage — MUST invoke the Skill tool with skill: "iceberg:convention" and let that skill run.
Your output on an patterns task is code that fits where the user asked for it. A table component stays a table component. A util function stays a util function. The patterns above bias the internal structure of what you write; they do not grow the scope into an architectural document.
Skill with skill: "iceberg:convention" — cross-skill dispatch only; no reference file in this skill.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.