📦 deps(thirdparty): update snapshots
This commit is contained in:
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "antigravity-awesome-skills",
|
||||
"version": "12.7.0",
|
||||
"description": "Plugin-safe Claude Code distribution of Antigravity Awesome Skills with 1,536 supported skills.",
|
||||
"version": "12.8.0",
|
||||
"description": "Plugin-safe Claude Code distribution of Antigravity Awesome Skills with 1,561 supported skills.",
|
||||
"author": {
|
||||
"name": "sickn33 and contributors",
|
||||
"url": "https://github.com/sickn33/antigravity-awesome-skills"
|
||||
|
||||
+14
@@ -127,6 +127,20 @@ Estimate:
|
||||
|
||||
---
|
||||
|
||||
### Tracking Verification (Required before Gate 8)
|
||||
|
||||
Before entering the Execution Readiness Gate below, run through this checklist to make "Tracking is verified" mean something concrete:
|
||||
|
||||
1. **Event firing:** Trigger each event the primary and secondary metrics depend on (sign-up, add-to-cart, custom event) on staging or a debug page, and confirm it lands in your analytics destination within 30 seconds.
|
||||
2. **Variant attribution:** Verify that the variant assignment ID is attached to every fired event — not just the entry event. Use your analytics' raw event view to compare a sample of 5+ events per variant.
|
||||
3. **De-duplication:** Confirm that a user reloading the page does not cause double-counted events. If your stack uses client-side de-duping, the variant ID must be part of the dedup key.
|
||||
4. **Sample randomization:** Pull the first 100 assignment records from your assignment table; the variant split should be within ±5% of the configured allocation.
|
||||
5. **Guardrail metric pipeline:** Each guardrail metric defined in §6️⃣ must have a working dashboard or alert by the time the test launches.
|
||||
|
||||
If any of the above fails, stop and resolve it before Gate 8.
|
||||
|
||||
---
|
||||
|
||||
## 8️⃣ Execution Readiness Gate (Hard Stop)
|
||||
|
||||
You may proceed to implementation **only if all are true**:
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
# astropy.coordinates — Reference
|
||||
|
||||
This file is a placeholder for the extended `astropy.coordinates` reference promised in the skill workflow.
|
||||
|
||||
For detailed coordinate frame descriptions, transformations, observer-dependent frames (AltAz), catalog matching, and performance tips, see the upstream astropy docs:
|
||||
|
||||
- https://docs.astropy.org/en/stable/coordinates/
|
||||
- https://docs.astropy.org/en/stable/coordinates/frames.html
|
||||
- https://docs.astropy.org/en/stable/coordinates/matchsep.html
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# astropy.cosmology — Reference
|
||||
|
||||
This file is a placeholder for the extended `astropy.cosmology` reference promised in the skill workflow.
|
||||
|
||||
For available models, distance calculations, time calculations, density parameters, and neutrino effects, see the upstream astropy docs:
|
||||
|
||||
- https://docs.astropy.org/en/stable/cosmology/
|
||||
- https://docs.astropy.org/en/stable/cosmology/realizations.html
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# astropy.io.fits — Reference
|
||||
|
||||
This file is a placeholder for the extended `astropy.io.fits` reference promised in the skill workflow.
|
||||
|
||||
For file operations, header manipulation, image and table handling, multi-extension files, and performance considerations, see the upstream astropy docs:
|
||||
|
||||
- https://docs.astropy.org/en/stable/io/fits/
|
||||
- https://docs.astropy.org/en/stable/io/fits/usage/headers.html
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# astropy.table — Reference
|
||||
|
||||
This file is a placeholder for the extended `astropy.table` reference promised in the skill workflow.
|
||||
|
||||
For table creation, I/O operations, data manipulation, sorting, filtering, joins, grouping, and performance tips, see the upstream astropy docs:
|
||||
|
||||
- https://docs.astropy.org/en/stable/table/
|
||||
- https://docs.astropy.org/en/stable/table/operations.html
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
# astropy.time — Reference
|
||||
|
||||
This file is a placeholder for the extended `astropy.time` reference promised in the skill workflow.
|
||||
|
||||
For time formats, time scales, conversions, arithmetic, observing features, and precision handling, see the upstream astropy docs:
|
||||
|
||||
- https://docs.astropy.org/en/stable/time/
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
# astropy.units — Reference
|
||||
|
||||
This file is a placeholder for the extended `astropy.units` reference promised in the skill workflow.
|
||||
|
||||
For comprehensive documentation, unit systems, equivalencies, performance optimization, and unit arithmetic, see the upstream astropy docs:
|
||||
|
||||
- https://docs.astropy.org/en/stable/units/
|
||||
- https://docs.astropy.org/en/stable/units/equivalencies.html
|
||||
- https://docs.astropy.org/en/stable/units/standards.html
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
# astropy.wcs and other modules — Reference
|
||||
|
||||
This file is a placeholder for the extended `astropy.wcs` (and related modules) reference promised in the skill workflow.
|
||||
|
||||
For WCS operations and transformations, plus related modules (`astropy.modeling`, `astropy.constants`, `astropy.convolution`, etc.), see the upstream astropy docs:
|
||||
|
||||
- https://docs.astropy.org/en/stable/wcs/
|
||||
- https://docs.astropy.org/en/stable/modeling/
|
||||
- https://docs.astropy.org/en/stable/constants/
|
||||
+1
-1
@@ -44,7 +44,7 @@ use daily. You know the difference between a toy and a tool.
|
||||
|
||||
## Patterns
|
||||
|
||||
### Extension Architecture
|
||||
### Architecture Patterns
|
||||
|
||||
Structure for modern browser extensions
|
||||
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
---
|
||||
name: ckw-design
|
||||
description: "Frontend design entry point: direction, design system, visual philosophy. Use whenever building or touching the look of any web UI (components, pages, dashboards, React/Vue/HTML-CSS) or when the user says \"make this look better\", \"fix the spacing/layout\", or mentions styling, color, type, or polish."
|
||||
risk: safe
|
||||
source: community
|
||||
source_type: community
|
||||
source_repo: connerkward/ckw-design-skill
|
||||
date_added: "2026-06-16"
|
||||
author: Conner K Ward
|
||||
license: MIT
|
||||
tags:
|
||||
- design
|
||||
- frontend
|
||||
- ui
|
||||
- css
|
||||
- typography
|
||||
- responsive
|
||||
tools:
|
||||
- claude-code
|
||||
- antigravity
|
||||
- cursor
|
||||
- gemini-cli
|
||||
- codex-cli
|
||||
---
|
||||
## When to Use
|
||||
|
||||
Use whenever building or styling web UIs — components, pages, dashboards, landing pages, React/Vue/HTML-CSS layouts — or whenever the user asks to make something "look better/nicer", fix spacing/layout, or mentions styling, color, typography, fonts, responsive design, polish, or aesthetics, even without the word "design".
|
||||
|
||||
_Source: [connerkward/ckw-design-skill](https://github.com/connerkward/ckw-design-skill) (MIT)._
|
||||
|
||||
# Design (entry)
|
||||
|
||||
Use this skill when the user asks to build or style web UIs: components, pages, dashboards, landing pages, React/Vue/HTML-CSS layouts, or any frontend interface. Goal: distinctive, production-grade output that avoids generic AI aesthetics.
|
||||
|
||||
**Before reporting any design "done": render it and have a *separate* judge critique the image** (not the code, not self-grading) — see design-spatial §1. Blind generation can't see its own collisions; this applies to all design output, not just spatial work.
|
||||
|
||||
> **MANDATORY HORIZONTAL-OVERFLOW GATE — runs before ANY web UI is "done".**
|
||||
> Measure `document.documentElement.scrollWidth - document.documentElement.clientWidth`
|
||||
> at a **narrow width (~390px and ~1024px), this turn**, and confirm it's `0`. This
|
||||
> bug is invisible at desktop width and re-appears every time a row (header, nav,
|
||||
> toolbar) gains an item, so it ships repeatedly. Default to `flex-wrap:wrap` on
|
||||
> header/toolbar rows + `body{overflow-x:clip}`, and **re-measure after adding any
|
||||
> element to a horizontal row.** Full procedure + recurrence cases: design-spatial §4.
|
||||
> If you haven't measured narrow, you are not done — don't claim it.
|
||||
|
||||
## Sub-skills (load when relevant)
|
||||
|
||||
- **design-thinking** — Load for every design task. Defines purpose, tone, domain, color world, review bar, and cross-domain lens (cinema, architecture, marketing, UX, automotive, industrial design). See [design-thinking/SKILL.md](https://github.com/connerkward/ckw-design-skill/blob/main/design-thinking/SKILL.md).
|
||||
- **design-system** — Load when implementing: tokens, typography, motion, color semantics, backgrounds. Use when building components, pages, or design systems. See [design-system/SKILL.md](https://github.com/connerkward/ckw-design-skill/blob/main/design-system/SKILL.md).
|
||||
- **design-spatial** — Load when composing layout: explicit grid + 8-point spacing constraints, visual-weight/balance/alignment, and a render-then-critique vision loop. The fix for "spatial understanding is off" — generated layout that's centered mush, misaligned, or breaks at some widths. See [design-spatial/SKILL.md](https://github.com/connerkward/ckw-design-skill/blob/main/deterministic-design/design-spatial/SKILL.md).
|
||||
- **design-ux** — Load when auditing USABILITY (not just looks): a UI that "feels off"/"sucks to use", is hard to learn, needs an instruction wall, or any interactive tool/editor/app before shipping. Scores the rendered UI against Nielsen's 10 + interaction heuristics via a SEPARATE fresh-eyes judge → prioritized fix list. Usability ≠ aesthetics. See [design-ux/SKILL.md](https://github.com/connerkward/ckw-design-skill/blob/main/deterministic-design/design-ux/SKILL.md).
|
||||
- **design-philosophy** — Load for high-concept work, campaigns, or when the user asks for a visual philosophy, manifesto, or unmistakable art-like aesthetic. See [design-philosophy/SKILL.md](https://github.com/connerkward/ckw-design-skill/blob/main/design-philosophy/SKILL.md).
|
||||
|
||||
## Visual assets — generate or source
|
||||
|
||||
When design-thinking identifies a need for visual assets (logos, icons, hero images, textures, backgrounds):
|
||||
|
||||
1. **Generate** → use an image-generation model or API for synthetic/branded assets.
|
||||
2. **Source a real/archival one** → free stock or archival image search, often cheaper and more authentic than generating.
|
||||
3. Use design-thinking output (tone, domain, color world) to craft prompts / queries.
|
||||
4. Evaluate against the design philosophy, refine, integrate into the build.
|
||||
|
||||
## LLM-assisted work — always annotate model + cost
|
||||
|
||||
When design work involves running an LLM (generative assets, VLM analysis, layout critique, prompt generation, etc.):
|
||||
|
||||
- **Before running:** state which model will be used and the estimated cost (e.g., "gpt-4o-mini · ~$0.005/image" or "FLUX v1 · ~$0.006 per gen").
|
||||
- **After results:** annotate the output with the model used, actual cost if different from estimate, and any key params (seed, prompt, settings). Cost goes *visible to the user* (in the message, contact sheet header, or asset caption), not buried in logs.
|
||||
- **Why:** the user is deciding whether the cost-to-quality trade-off is worth it. Unlabeled or hidden costs hide the most important lever. This rule mirrors `media-attribution-rule` for generative assets and extends it to any LLM operation in the design workflow.
|
||||
|
||||
**Examples:**
|
||||
- "Running gpt-4o-mini layout critique on 8 designs · est. ~$0.04 total" (before).
|
||||
- Contact sheet header: "FLUX v1 · $0.48 total (6 gen × $0.08)" (after).
|
||||
- Asset caption: "hero_banner_flux-dev_seed3891.jpg" (seeds enable reproducibility).
|
||||
- Uncertainty slider result: "VLM triage on 46,978 images · gpt-4o-mini · ~$9.40" (before); "✓ Completed: 12,447 images classified · gpt-4o-mini · $7.62" (after).
|
||||
|
||||
## Algorithm / model explainers — show the equation, annotate the terms
|
||||
|
||||
Whenever a UI surfaces an algorithm or model to the user (an "ⓘ how this works"
|
||||
panel, a model breakdown, a methods note), **include the actual equation, typeset,
|
||||
with its key terms annotated** — don't settle for prose. A scorer described only in
|
||||
words ("ranks by how much of the picked color is present") is unfalsifiable hand-
|
||||
waving; the formula `score = Σ fracᵢ · max(0, 1 − ΔEᵢ/τ)` with each term labelled
|
||||
tells the user *exactly* what the knob does and builds trust that there's real math
|
||||
under the hood.
|
||||
|
||||
**How to apply:**
|
||||
- Render one clean, central equation per algorithm — the "sexy" core, not every
|
||||
detail. Use proper notation: σ for sigmoid, Σ for sums, ‖·‖ for norms,
|
||||
superscripts, ΔE, ∇²; a monospace/serif-math block set off from the prose.
|
||||
- **Annotate every symbol** immediately below: what `e_x`, `w`, `τ`, `Q` each are,
|
||||
in one line each. An unlabelled equation is decoration; a labelled one is a spec.
|
||||
- Keep it dependency-light — styled HTML/Unicode math is fine and works offline; only
|
||||
reach for KaTeX/MathJax if the expressions genuinely need it.
|
||||
- State the **decision rule** alongside the score (e.g. "personal if P ≥ 0.55").
|
||||
- This composes with the model+cost annotation above: the equation says *what* it
|
||||
computes, the model/cost line says *what ran it and for how much*.
|
||||
|
||||
**Example (a logistic head):**
|
||||
> P(personal │ x) = σ(**w**·**e**ₓ + b), σ(z) = 1 / (1 + e⁻ᶻ)
|
||||
> • **e**ₓ — the image's 768-d embedding · **w**, b — weights learned from your labels
|
||||
> · decision: personal if P ≥ 0.55, reference if P ≤ 0.40.
|
||||
|
||||
## Select-all always has a deselect — no dead-end selections
|
||||
|
||||
Any **"Select all"** affordance MUST be paired with a way to **clear the selection** —
|
||||
preferably the *same button*, label-flipped when everything is already selected
|
||||
("Select all" ⇄ "Deselect all"). A select-all with no inverse is a trap: the user
|
||||
over-selects (or hits it by reflex), then has to un-click items one by one, or reload
|
||||
the page, to get back. The cost is silent — it only bites *after* they've committed to
|
||||
the wrong set.
|
||||
|
||||
**How to apply:**
|
||||
- **Toggle the same button** (simplest, fewest controls): when all visible items are
|
||||
selected, the button reads "Deselect all" and clears; otherwise "Select all". One
|
||||
control, no dead end.
|
||||
- Or a **separate Clear/Deselect** shown whenever the selection is non-empty.
|
||||
- The deselect must reach the **same scope** the select-all did (all *shown*, all
|
||||
*filtered*, all *on this page*) — don't let "Select all" grab 500 but "Clear" only
|
||||
drop the 50 on screen.
|
||||
- This generalizes: any reversible bulk toggle (select, expand-all, mute-all,
|
||||
check-all) needs its inverse one tap away. Symmetry of action — see
|
||||
restraint-rule (don't strand the user mid-task).
|
||||
|
||||
## Limitations
|
||||
|
||||
- This skill improves visual direction and review discipline, but it does not replace rendering the actual UI and checking it in target browsers or devices.
|
||||
- Some recommendations assume access to screenshots, browser automation, or vision review; when those are unavailable, treat the guidance as a design checklist rather than proof.
|
||||
- Brand, legal, accessibility, and localization constraints from the product owner override the taste rules here.
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: deterministic-design
|
||||
description: "Render the UI and prove it's balanced + usable: a deterministic layout audit (centroid / optical-center / pixel-oracle balance via explicit math + annotated screenshot) plus a vision-judged Nielsen usability audit by a separate fresh-eyes judge. The measurement layer taste-only design skills lack."
|
||||
risk: safe
|
||||
source: community
|
||||
source_type: community
|
||||
source_repo: connerkward/deterministic-design-skill
|
||||
date_added: "2026-06-16"
|
||||
author: Conner K Ward
|
||||
license: MIT
|
||||
tags:
|
||||
- design
|
||||
- layout
|
||||
- usability
|
||||
- audit
|
||||
- verification
|
||||
- vision
|
||||
tools:
|
||||
- claude-code
|
||||
- antigravity
|
||||
- cursor
|
||||
- gemini-cli
|
||||
- codex-cli
|
||||
---
|
||||
## When to Use
|
||||
|
||||
Use to catch AI-generated UI that "looks off", is misaligned or centered-mush, or fails usability — when you need to PROVE a layout is balanced and usable instead of trusting the model's eye. Compose it with any taste/token design skill before reporting design "done".
|
||||
|
||||
_Source: [connerkward/deterministic-design-skill](https://github.com/connerkward/deterministic-design-skill) (MIT)._
|
||||
|
||||
# deterministic-design
|
||||
|
||||
Thesis: **determinism beats AI randomness.** A model can't trust its own eye on layout — so
|
||||
don't. Render the UI and *measure* it.
|
||||
|
||||
Two sub-skills (load as needed):
|
||||
- **[design-spatial](https://github.com/connerkward/deterministic-design-skill/blob/main/design-spatial/SKILL.md)** — deterministic layout audit: explicit grid
|
||||
+ 8-pt spacing, and `layout-audit.js` computes centroid / optical-center / pixel-oracle
|
||||
balance and draws an annotated screenshot. **Numbers, not vibes.** Plus a render-then-
|
||||
critique vision loop.
|
||||
- **[design-ux](https://github.com/connerkward/deterministic-design-skill/blob/main/design-ux/SKILL.md)** — usability audit: scores the rendered UI against
|
||||
Nielsen's 10 + interaction heuristics via a SEPARATE fresh-eyes judge → prioritized fix list.
|
||||
|
||||
This **improves** existing design skills (including the default Anthropic one) by adding the
|
||||
layer they lack — it doesn't just advise on taste, it renders, measures, and judges the
|
||||
output. Composable with any design skill.
|
||||
|
||||
In central this lives as a subdir of ckw-design; it **publishes separately** as
|
||||
`deterministic-design-skill` (its own distribution) via publish-skill. One of the two
|
||||
flagship narratives — the *determinism* one; its sibling is human-in-the-loop (lookdev).
|
||||
|
||||
## Limitations
|
||||
|
||||
- Layout metrics and vision-judged audits catch many spatial and usability failures, but they are not a substitute for product judgment or user testing.
|
||||
- The workflow requires a rendered UI or screenshot; it cannot validate components that have not been built or captured.
|
||||
- Automated scoring can miss brand nuance, copy tone, accessibility needs, and domain-specific user expectations.
|
||||
+3
-1
@@ -24,7 +24,9 @@ date_added: "2026-02-27"
|
||||
|
||||
## The Quality Loop
|
||||
1. **Write/Edit Code**
|
||||
2. **Run Audit:** `npm run lint && npx tsc --noEmit`
|
||||
2. **Run Audit** for the project's ecosystem:
|
||||
- **Node.js / TypeScript:** `npm run lint && npx tsc --noEmit`
|
||||
- **Python:** `ruff check . --fix && mypy . && bandit -r . -ll`
|
||||
3. **Analyze Report:** Check the "FINAL AUDIT REPORT" section.
|
||||
4. **Fix & Repeat:** Submitting code with "FINAL AUDIT" failures is NOT allowed.
|
||||
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
---
|
||||
name: lookdev-auto
|
||||
description: "Automated visual tuning: a vision or video model rates rendered variants in a loop. Render several labeled variants into one artifact, ask the model to rate them and suggest better values, render the suggestions, ask it to pick the best, repeat until good — the model is the eye, you run the loop."
|
||||
risk: safe
|
||||
source: community
|
||||
source_type: community
|
||||
source_repo: connerkward/lookdev-auto-skill
|
||||
date_added: "2026-06-16"
|
||||
author: Conner K Ward
|
||||
license: MIT
|
||||
tags:
|
||||
- visual-eval
|
||||
- vision-model
|
||||
- tuning
|
||||
- automation
|
||||
- render-loop
|
||||
tools:
|
||||
- claude-code
|
||||
- antigravity
|
||||
- cursor
|
||||
- gemini-cli
|
||||
- codex-cli
|
||||
---
|
||||
## When to Use
|
||||
|
||||
Use whenever "looks/feels right" is the success criterion and there's no cheap numeric metric — animation easing/timing, zoom/camera feel, color grade, layout/spacing, design params, render/encoder settings, prompt params. Use the automated counterpart to lookdev when there's no human to sit the loop.
|
||||
|
||||
_Source: [connerkward/lookdev-auto-skill](https://github.com/connerkward/lookdev-auto-skill) (MIT)._
|
||||
|
||||
# Visual eval loop — let a vision/video model tune what only an eye can judge
|
||||
|
||||
When the target is "does this LOOK/FEEL right" (not a number you can minimize), a
|
||||
vision model (image) or video-understanding model (motion/timing) can be the judge in
|
||||
a tight optimize loop. Worked reference: the `screenstudio-alternative` skill (`iteration.py`)
|
||||
(tuned zoom-animation feel via `fal-ai/video-understanding`).
|
||||
|
||||
## The loop
|
||||
|
||||
1. **Render N labeled variants into ONE artifact.** Vary the parameter(s) across a
|
||||
small spread. **Annotate each variant's params ON the artifact** (burn the label in:
|
||||
"A · 2.2Hz · ζ0.5"). Images → a labeled grid/contact sheet. Video/motion → a
|
||||
labeled *sequence* (label card or burned-in overlay before/over each clip) so the
|
||||
model can compare temporally.
|
||||
2. **One model call, structured output.** Send the single artifact with an explicit
|
||||
rubric (define what "good" means — and what "too much"/"too little" look like).
|
||||
Ask for **per-variant ratings + concrete suggested new values as JSON**:
|
||||
`{"ratings":{"A":n,...},"best_so_far":"X","suggest":[[p1,p2],...]}`.
|
||||
3. **Coarse → fine.** Round 1 = wide spread to locate the region. Round 2 = render the
|
||||
model's suggestions (+ carry the current best) into one artifact; ask it to **pick
|
||||
the single best**. Usually converges in **2 rounds**.
|
||||
4. **Stop when sufficient** — best rates high and suggestions cluster. Apply the winner.
|
||||
|
||||
## Token / quality / step reductions (do these)
|
||||
|
||||
- **One artifact per round, not one call per variant.** The biggest saver — a 6-variant
|
||||
round is 1 upload + 1 inference, not 6. Montage/grid beats a loop of single calls.
|
||||
- **Burn params onto the artifact.** The model sees label+result together → no separate
|
||||
"variant A used X" context to carry → fewer tokens, fewer mistakes.
|
||||
- **Structured JSON out + parse.** No re-asking, no free-text wrangling. Prompt "return
|
||||
ONLY JSON"; regex the first `{...}`.
|
||||
- **Short representative sample.** Tune on a 3-5s clip / one frame / one component, not
|
||||
the whole asset. Cheaper render, smaller upload, faster inference. Apply the found
|
||||
params to the full render once.
|
||||
- **Cap variants at ~5-6.** More doesn't improve the model's discrimination and multiplies
|
||||
render + token cost. Wide-but-sparse round 1, narrow round 2.
|
||||
- **Calibration anchors.** Include one deliberately-bad and one safe-default variant as
|
||||
fixed anchors each round — gives the model a reference scale and exposes when its
|
||||
"best" is worse than the safe default (catch a bad recommendation early).
|
||||
- **Independent rubric, stated up front.** Define "good" concretely in the prompt
|
||||
(smooth, subtle settle, not bouncy, not sluggish). Don't ask "which do you like" —
|
||||
that lets it echo your framing. A held-out criterion keeps the judge honest
|
||||
(see verify-outputs-rule: the check must be independent of what you tuned).
|
||||
- **Reuse renders across rounds.** Carry the round-1 winner's clip into round 2 instead
|
||||
of re-rendering it.
|
||||
- **Early-exit.** If round-1 top ≥9/10 and the three suggestions are within a small delta,
|
||||
skip round 2.
|
||||
- **Cheapest judge that can see the failure.** Frames-through an image VLM can judge
|
||||
spatial things (layout, color, crop); only reach for a true *video* model when the
|
||||
thing being judged is **temporal** (easing, timing, motion smoothness) — those are
|
||||
invisible in stills.
|
||||
|
||||
## When NOT to use it
|
||||
|
||||
- A real numeric metric exists and correlates with quality → optimize that directly;
|
||||
don't pay a model per step.
|
||||
- The judgment is subjective-to-the-user (their taste, brand) → show them the variants
|
||||
and let them pick; a model's "best" isn't their best. (This is why the screen-studio
|
||||
spring auto-tune was dropped — the model's pick didn't match the owner's eye.)
|
||||
- One or two variants → just look yourself.
|
||||
|
||||
## Caveats (learned)
|
||||
|
||||
- The model's pick is an *opinion*, not ground truth — anchor it, and sanity-check the
|
||||
winner against the safe default yourself before committing.
|
||||
- Vision/video models perceive gross differences well, fine ones poorly — keep variant
|
||||
spacing perceptible; near-identical variants get noise-rated.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Model ratings are probabilistic aesthetic judgments, not objective truth; keep a human review step for brand-critical or subjective work.
|
||||
- Automated rounds can become expensive or slow when renders are heavy or many variants are explored.
|
||||
- This skill needs screenshots, frames, or clips that expose the quality difference; it is weak for subtle motion, audio, copy nuance, or user-preference calls.
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
---
|
||||
name: lookdev
|
||||
description: "Human-in-the-loop web studio to tune AI-generated output by eye. Stand up a local interactive studio (sliders, pickers, drag handles) or an inline edit/highlight/comment annotation studio for prose & media, instead of guessing values or shipping a static comparison grid."
|
||||
risk: safe
|
||||
source: community
|
||||
source_type: community
|
||||
source_repo: connerkward/lookdev-studio-skill
|
||||
date_added: "2026-06-16"
|
||||
author: Conner K Ward
|
||||
license: MIT
|
||||
tags:
|
||||
- lookdev
|
||||
- design
|
||||
- ui
|
||||
- tuning
|
||||
- studio
|
||||
- visual-eval
|
||||
- annotation
|
||||
tools:
|
||||
- claude-code
|
||||
- antigravity
|
||||
- cursor
|
||||
- gemini-cli
|
||||
- codex-cli
|
||||
---
|
||||
## When to Use
|
||||
|
||||
Use when the user says "lookdev", or asks to tune / dial in / iterate on the look of something, compare variations by feel, or review / edit / annotate a blog post, doc, copy, or media set. Use whenever "show me, I'll pick" beats asking the user to specify a number, and whenever you'd otherwise hand back a static grid or a wall of prose for review.
|
||||
|
||||
_Source: [connerkward/lookdev-studio-skill](https://github.com/connerkward/lookdev-studio-skill) (MIT)._
|
||||
|
||||
# Lookdev
|
||||
|
||||
When the user says **"lookdev"** — or any of: *tune*, *dial in*, *iterate on the look of*, *compare variations of*, *let me adjust*, *let me edit/annotate/mark up*, *review this post/doc/copy* — they mean **build an interactive in-browser tool the user directly manipulates**. Not a static grid of N variations. Not a Q&A where they specify numbers. Not a wall of prose they're asked to read and reply to in chat. A real-time studio where they act on the artifact and the change is captured.
|
||||
|
||||
**Two studio shapes — pick by what's being tuned:**
|
||||
|
||||
- **Visual-parameter lookdev** — the artifact's *look* is set by numbers/choices (color, type, layout, image treatment, animation, 3D). Controls = sliders, pickers, drag handles. This is the bulk of this skill (below).
|
||||
- **Text & media lookdev** — the artifact is a *document, blog post, copy, or media set* and the user is editing/curating it: rewriting sentences, cutting boring paragraphs, highlighting, leaving margin comments, flagging "diagram goes here" / "wrong image, replace." Controls = **direct inline editing + selection highlight + anchored comments + media annotation**. See the dedicated section below. **A blog post / doc / script review IS this mode — never hand back a long markdown file and ask the user to react in chat. Stand up the annotation studio.**
|
||||
|
||||
## What it covers
|
||||
|
||||
Any visual decision the user picks by feel, not by spec. Expand this list as needed:
|
||||
|
||||
- **Image processing** — dither, halftone, posterize, ASCII, blur, edge, quantize, mosaic, color-grade
|
||||
- **Color** — palette extraction (show coverage %), per-band pickers, saturation / contrast / gamma curves, harmony presets, theme tokens
|
||||
- **Typography** — font selector, size / weight / leading / tracking / measure, live sample text, fallback stack
|
||||
- **Layout, positioning, framing, spacing** — draggable & selectable elements; resize handles; margin / padding rulers; alignment guides; snap-to-grid; aspect-lock toggles
|
||||
- **Crop & framing** — draggable crop rectangle with aspect lock; live cropped preview at production size
|
||||
- **Animation / transitions** — easing curve editor, duration sliders, scrubber, replay
|
||||
- **Component variants** — render hover / focus / disabled / loading / dark side by side on one page
|
||||
- **Iconography** — stroke weight, corner radius, glyph on canvas
|
||||
- **AI-generated content** — prompt input + param sliders + side-by-side regeneration grid
|
||||
- **Anything else where "show me, I'll pick"** beats "ask me to specify a number"
|
||||
|
||||
## Controls must stay reachable while inspecting
|
||||
|
||||
If the studio shows a list, grid, or scroll-long set of variations, **controls must be visible from every scroll position**. The user has to be able to drag a slider while looking at row 14, not scroll back to the top each time.
|
||||
|
||||
Two approaches, pick by layout:
|
||||
|
||||
- **Sticky bar** (`position: sticky; top: 0`) at the top of the scroll container. Keep the bar visually distinct — paper background + blur backdrop + bottom border — so it doesn't muddy the specimens scrolling behind it. Sticky pins relative to the *nearest scrolling ancestor with a defined boundary*; if you nest it inside a sized parent (a `<header>` with `margin-bottom`, a `<div>` with a fixed height), it stops sticking at that parent's bottom edge. Lift it to be a direct child of `<body>` (or the page-wrap) so stickiness spans the whole page.
|
||||
- **Floating overlay** (`position: fixed`) for hotkey-toggled controls — e.g. press `d` to reveal. The portfolio's `.debug-ctl` pattern is this: pinned top-left, transparent until summoned. Use when the controls shouldn't occupy permanent screen real estate (final viewers shouldn't see them; the author can summon on demand).
|
||||
|
||||
Anti-pattern: a top-of-page control panel that the user scrolls past and never sees again. They will tune blindly, give up, or guess. Either keep the controls in view *or* duplicate a compact control bar next to each variation row.
|
||||
|
||||
## Text & media lookdev — direct edit, highlight, comment, annotate
|
||||
|
||||
When the artifact is a **blog post, doc, copy deck, script, or media set**, the user is not turning knobs — they're *marking up the work the way an editor marks a manuscript*. The studio renders the **real artifact WYSIWYG** (the actual rendered blog with its real components/media, not a raw-markdown textarea) and lets the user act on it directly. Building this for a doc review is mandatory: **do not paste a long file into chat and ask "what do you think?" — that's the boring wall of text the user is rejecting.** Stand up the annotation studio and let them edit in place.
|
||||
|
||||
### The four affordances (build all that apply)
|
||||
|
||||
1. **Direct inline editing.** Every text block is editable in place — click a paragraph/heading and type. Use `contentEditable` per block (or click-to-swap-to-`<textarea>`), each block carrying a stable `data-block-id` that maps back to a source location (markdown/MDX line range, JSX node, or content key). Capture the *edited* text per block; the agent applies the diff to source. Don't make them retype in a separate field — they edit the rendered sentence.
|
||||
2. **Selection highlight.** Select text → toolbar (or hotkey) applies a colored highlight (`<mark>`). Multiple colors = a legend the user defines (e.g. yellow "cut this", green "love it", red "wrong/fact-check"). Each highlight stores `{blockId, startOffset, endOffset, color, optional note}`.
|
||||
3. **Anchored comments / margin notes.** Select text or click a media region → attach a comment shown in a **margin rail** (pin in the gutter, expand on hover/click) or as a numbered superscript. Comment = `{anchor, text}` where anchor is a block+range or a media region. This is how the user says "diagram goes here", "too long, cut to two sentences", "needs a real screenshot".
|
||||
4. **Media annotation.** For images/figures: draw a box / drop a pin / arrow on the image and attach a note (`{mediaId, x, y, w, h, note}`); plus a per-media **flag menu** — "replace", "wrong model", "regenerate", "missing — generate one here". Placeholders ("DIAGRAM HERE", "MEDIA?") render as visible drop-zones the user clicks to specify what they want, directly addressing "where are the diagrams / where is the media."
|
||||
|
||||
### Round-trip is MANDATORY (same rule as the settings JSON)
|
||||
|
||||
The studio is worthless if the agent can't read the markup back out. Every edit, highlight, comment, and media-flag must export as **one machine-readable patch** with a single **Copy** button (and persist to `localStorage`/URL so a refresh doesn't lose work — this is human-labeled data; see `human-labeled-data-rule`). Shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"edits": [{ "blockId": "p-12", "text": "new rewritten text" }],
|
||||
"highlights": [{ "blockId": "p-3", "range": [40, 88], "color": "cut", "note": "boring, drop" }],
|
||||
"comments": [{ "anchor": "p-7", "text": "diagram goes here — flow of the save loop" }],
|
||||
"media": [{ "mediaId": "fig-2", "flag": "replace", "note": "use a real screenshot, not ASCII" }]
|
||||
}
|
||||
```
|
||||
|
||||
The agent ingests this and bakes: applies the inline edits to the source file, acts on every comment/flag, swaps/generates the flagged media, resolves the highlights (cut the "cut" spans, etc.). Then re-serve the updated artifact for another pass. **No markup may exist that isn't in the export blob** — otherwise you're back to the user narrating changes by hand.
|
||||
|
||||
### Mechanics
|
||||
|
||||
- **Render the real thing.** MDX/React blog → mount the actual components; static page → render the real HTML/CSS. WYSIWYG per Architecture #5. An annotation layer over a fake-looking preview lies about the result.
|
||||
- **Selection → offsets.** Use the `Selection`/`Range` API; store character offsets relative to the block's text content (not DOM node paths, which break on re-render). Re-apply highlights/comments on load by walking each block's text to the stored offsets.
|
||||
- **Editing toolbar floats with the selection** (a small popover at the selection rect) or a sticky top bar — controls stay reachable (see section above). Hotkeys: highlight on a key (e.g. `h`), comment on `c`.
|
||||
- **Keep edit/annotate modes distinct** so a stray click doesn't garble text while they meant to highlight — a mode toggle (Edit · Highlight · Comment) or modifier key.
|
||||
- Everything else — serve locally on a free port, verify headless, tear down after baking — is identical to the visual-parameter workflow below.
|
||||
|
||||
## Control patterns
|
||||
|
||||
Pick controls by what the decision actually is.
|
||||
|
||||
| Decision type | Control |
|
||||
|---|---|
|
||||
| Continuous value (intensity, size, opacity, k) | `<input type=range>` **paired with an editable `<input type=number>`** (not a static label) — drag OR click-and-type; they two-way sync |
|
||||
| Discrete choice (mode, blend, easing kind) | segmented buttons or radio chips |
|
||||
| Color | `<input type=color>` swatches; pre-extract dominant palette with coverage % when relevant |
|
||||
| Position / size on a canvas | **drag the element itself** — handles, not numeric inputs |
|
||||
| Crop region | draggable rectangle + aspect-lock toggle |
|
||||
| Multiple discrete states | render each in a labeled card on one page |
|
||||
| Font choice | searchable picker + editable sample-text input |
|
||||
|
||||
**Spatial rule:** if the user could point at the thing and drag it, that *is* the control. Don't add an `x:` slider when a drag handle is the obvious affordance.
|
||||
|
||||
**Gesture capture — never make the gesturing hand leave the gesture.** When a control toggles a *live mouse action* the user is performing — recording a cursor path, scrubbing, freehand-drawing, demonstrating a motion — the start/stop must **not** be a button they have to click. Clicking it drags the mouse off the path, pollutes the start/end of the very motion being captured, and forces a round-trip back to where they were. Bind start/stop to the **keyboard (spacebar by default)** — `keydown` on `Space`, `e.preventDefault()` to kill page scroll, toggle the same handler the button would. Keep the button too (discoverability), but the hotkey is the real control. Generalize: any modal capture where one hand is committed to the primary input gets the *other* modality for mode-switching — gesture→key, and conversely a keyboard-heavy capture gets a foot/mouse toggle. The test: if triggering the control would move the thing you're capturing, it's the wrong modality.
|
||||
|
||||
## Coherent control ranges — bounds must propagate
|
||||
|
||||
When one control sets a **bound** on another (a min, a max, a threshold, an allowed set), the bounded control's UI must reflect the new bound the instant you change it. A "scrub" slider whose `min`/`max` attributes drift out of sync with its declared bounds is the most common silent bug — the user moves the bounding slider, nothing visible changes downstream, they assume both are broken.
|
||||
|
||||
Rules:
|
||||
|
||||
- **Single source of truth.** Hold the bound in state once. Every input that *displays* it (its own slider, the dependent control's `min`/`max`, anything else) reads from that state on every update.
|
||||
- **Re-render `min`/`max` on every state change.** Don't rely on browser-cached attribute values; rewrite them via JS each render. `dependent.min = state.lo; dependent.max = state.hi`.
|
||||
- **Clamp the dependent value into the new range immediately.** If the user shrinks the upper bound below the current dependent value, the dependent must snap into range, NOT silently stay outside while the slider shows it pinned to the rail.
|
||||
- **No-op regions are slider bugs.** If dragging a slider past some value has zero downstream effect (because some other control's bound caps it), that's a coherence bug — either narrow this slider's range to where it actually does something, OR change behavior so it does. Sliders with dead zones train the user to think the studio is broken.
|
||||
- **Test by visualisation, not numeric snapshots.** Take a screenshot, change the bounding slider, take another. The two must look meaningfully different — or the slider is decorative. A numeric `snapshot()` showing state changed doesn't prove the pixels did.
|
||||
|
||||
Pattern: every time `applyState()` runs (or its split equivalents), call a `syncBounds()` helper that walks the dependent-input registry and pushes the live bounds into every `min`/`max`/`disabled` attribute. Clamp values into the new bounds in the same pass.
|
||||
|
||||
### Paired controls must not cross
|
||||
|
||||
A common shape is **two sliders that together define an interval** — `min ⟷ max`, `near ⟷ far`, `tightEnd ⟷ wideEnd`, `start ⟷ end`. If the user can drag one past the other, the interval inverts or collapses. Downstream math typically does `(x - lo) / (hi - lo)` which **divides by zero or returns negative `t`** — producing `NaN` coordinates, collapsed views, or inverted lerps. The user sees the studio "break" but no error fires.
|
||||
|
||||
Both ends of the defense:
|
||||
|
||||
- **UI invariant.** Keep the two sliders from crossing. On every `syncBounds()` pass: `lower.max = upper.value - MIN_SPAN` and `upper.min = lower.value + MIN_SPAN` (small epsilon, e.g. 2 units, so they can't even touch). The user can't physically drag past the other anchor.
|
||||
- **Math invariant.** The consuming code (lerp, normalisation, ratio) must guard `denominator > 0` and pick a sane fallback for the degenerate case (e.g. clamp `t = 1` or `t = 0`). UI can race the math — always assume the math could be hit with crossed bounds anyway (URL hash, JSON paste-back, programmatic state mutation).
|
||||
- **Test the boundary explicitly.** When the lookdev exposes both ends of an interval, write a quick check: drag `tightEnd` to the same value as `wideEnd`, verify the scene doesn't break. Drag `tightEnd` past `wideEnd`, verify same. If you can crash the studio with two slider drags, that's a release blocker.
|
||||
|
||||
## Architecture
|
||||
|
||||
1. **Single-page HTML** — `<canvas>` and/or DOM, vanilla JS, a sidebar of controls. No build step, no framework, no deps unless one is genuinely required. Lives in a project-local scratch dir (e.g. `scripts/.lookdev-<name>/` or `scripts/.preview-<name>/`), **gitignored**.
|
||||
2. **Live re-render** on every `input` event. Debounce heavy work via `requestAnimationFrame`. Keep the loop tight enough to feel like a real slider, not a survey.
|
||||
- **Every numeric control is dual-input (MANDATORY): a range slider AND an editable `<input type=number>`, two-way synced.** The drag is for exploring; the typed number is for hitting an exact value (and reading the current one). A static `<span>` readout is not enough — the user must be able to click it and type. Sync rule: on slider `input`, write the number field; on number `input`/`change`, update state and re-render — but **do not overwrite a field while it has focus** (guard with `document.activeElement`), or typing gets clobbered mid-keystroke. Clamp to [min,max] on commit (`change`), not on every keystroke, so intermediate values like "1" before "12" aren't snapped.
|
||||
- **Always include a Reset control** that restores every control to its defaults in one click (keep a `DEFAULTS` object; `Object.assign(state, DEFAULTS)` then re-render). Cheap to add, and essential once the user has wandered far from baseline.
|
||||
- **Always build undo/redo history (MANDATORY).** Dialing-in is iterative and lossy — the user *will* overshoot a good look and need to step back. Bind **Ctrl/Cmd-Z** (undo) and **Ctrl/Cmd-Shift-Z** / **Ctrl-Y** (redo), and surface visible **↶ Undo / ↷ Redo** buttons. Snapshot the *full* serialized state — every control **plus any drawn/spatial state** (polygons, crop rects, dragged handles, palettes), i.e. the same blob as the settings round-trip (#3), not just slider scalars. Debounce so a continuous drag collapses into **one** history step (snapshot ~350 ms after the last `input`, not per event), keep a bounded stack (~100–120 entries), and on a new edit after undo, truncate the redo branch. Restore by re-applying a snapshot through the same `applyState` path the loader uses (so it can't drift). Guard the key handler when focus is in an `<input>`/`<textarea>` so native text-undo still works. A lookdev without undo punishes exploration — the whole point of the tool.
|
||||
3. **Structured settings round-trip (MANDATORY).** Every lookdev MUST expose its full current state as machine-readable, copy-pasteable text — a settings JSON (or equivalent) covering *every* control, with a one-click **Copy** button and a visible live readout. This is non-negotiable: the agent cannot bake by eyeballing a screenshot, and the user shouldn't have to describe what they dialed in. The round-trip is: user drags → studio serializes the exact state → user pastes the blob back (or it persists to URL/localStorage) → agent bakes from those literal values with identical math. No control may be tweakable without appearing in the export blob. Mirror the state into the URL query so a look is shareable by link, too.
|
||||
4. **Reproducible export.** Beyond the settings blob, pick by what gets committed:
|
||||
- **Copy settings JSON** — user pastes back, agent bakes with identical math (port the renderer to Python / build script / etc. and verify the bake matches).
|
||||
- **Download asset** — page renders the final artifact at full resolution and triggers a download (PNG / SVG / WebP / JSON).
|
||||
- **Make exported artifacts re-loadable — sidecar + embedded metadata.** When the download is a *non-JSON* artifact (STL, PNG, GLB, SVG, WebP, video…), the look that produced it shouldn't be strandable. Do BOTH, where the format allows:
|
||||
- **Sidecar:** download a **zip** containing the artifact *and* its `settings.json`, so the exact state ships next to the result.
|
||||
- **Embed the settings inside the file itself**, so the bare artifact alone restores the look — then add a **drag-drop / file-input loader** that reads it back through the same `applyState` path as the JSON paste. Per-format hooks: **binary STL** → append `MAGIC + uint32 len + JSON` after the triangle data (CAM ignores trailing bytes; parse `count` at byte 80, footer at `84 + count*50`) and drop a human note in the 80-byte header; **PNG** → a `tEXt`/`iTXt` chunk; **SVG/XML** → a `<metadata>` element or comment; **JPEG/MP4** → EXIF/XMP `UserComment`; **GLB** → an `extras` field. The payoff: the user drops last week's STL back on the viewport and the studio re-dials itself — no "which settings made this?" archaeology. Verify the round-trip (export → reset → load → assert state matches) and confirm the artifact still opens in its native tool (the trailing/edge metadata must not corrupt it). Skip only when the format has nowhere safe to stash bytes; the sidecar zip always works as the fallback.
|
||||
5. **WYSIWYG.** The preview frame must match the production context — same background color, same fonts loaded, same container max-width, same `object-fit`. A generic centered canvas is not WYSIWYG.
|
||||
6. **Framework-route variant.** When the lookdev is for UI layout inside an existing app, build it as a **temporary route** in the app (`app/dev/...` or equivalent) so the real components, styles, and tokens are in the comparison. **Delete the route once baked.**
|
||||
|
||||
## 3D lookdev — orientation gizmo (MANDATORY when the camera orbits)
|
||||
|
||||
Any lookdev with a **non-fixed camera** (OrbitControls, trackball, free fly — anything where the user can spin/tumble the view) MUST include a **CAD-style ViewCube** in a corner. Free orbit alone disorients: the user loses which way is up, can't get a repeatable canonical view, and can't tell whether they're looking at the front or the back. The cube fixes both problems — it's an orientation *indicator* and a *controller* in one. **Copy the Autodesk/Fusion 360 ViewCube** — that's the interaction users expect; don't invent a different gizmo.
|
||||
|
||||
Required behaviour (this is cheap — ~70 lines of Three.js, no excuse to skip):
|
||||
|
||||
- **Live orientation readout.** A small second scene/renderer in a corner draws a labeled cube (FRONT/BACK/LEFT/RIGHT/TOP/BOTTOM). Each frame, drive the gizmo camera from the *main* camera's view direction (`gizmoCam.position = (mainCam.position − target).normalize() * d; gizmoCam.up = mainCam.up; gizmoCam.lookAt(0,0,0)`) so the cube always mirrors the scene's current orientation.
|
||||
- **Click a face / edge / corner to snap** (the defining Fusion behavior; it's 26 preset views — 6 faces, 12 edges, 8 corners). Raycast the gizmo and snap each component of the local hit point (`|c|>0.55 ? sign(c) : 0`) to derive a view direction. One pickable cube then yields **faces → ortho views, edges → 45° edge views, corners → iso views** from a single mesh — no separate hit zones needed. Animate the main camera to `target + dir*currentDist` with a short lerp (~0.28/frame), not an instant cut — the motion is what keeps the user oriented.
|
||||
- **Drag the cube to orbit freely** (also Fusion, also mandatory — the user WILL try to grab it). Use pointer events with **click-vs-drag discrimination**: on `pointerdown` record the start and `setPointerCapture`; on `pointermove`, once travel exceeds ~4px flip into drag mode and orbit the *main* camera by the pointer delta (convert the camera offset to spherical around the target, `theta -= dx*k; phi = clamp(phi - dy*k, ε, π−ε)`); on `pointerup`, if it never became a drag, treat it as a snap-click. Capture means the drag keeps working when the pointer leaves the little canvas. Cancel any in-flight snap tween when a drag starts.
|
||||
- **Roll arrows = Fusion's "rotate".** Two curved-arrow buttons (⟲ ⟳) beside the cube that **roll the current view 90°** about the view axis (rotate `camera.up` by ±90° around the normalized `position−target` axis). This is the rotate users mean when they say the gizmo "can't rotate" — drag-orbit is *not* a substitute for it. Snap-cleanup the rolled up so near-cardinal components land exactly on 0/±1 (keep genuine diagonals). **Let it roll in ANY view, including iso** — do NOT gate it to face-on views or auto-snap-to-face first. (I tried that "Fusion only rolls in standard views" guard and it backfired: it stops the user rolling an *isometric* view into the exact orientation they want, which is a primary reason they reach for the arrows. Rolling an iso view is a valid, common move.)
|
||||
- **Perspective ⇄ orthographic toggle.** Any 3D lookdev should expose a projection toggle. Perspective for a natural read; **orthographic for CAD/measure/section work** (parallel edges, true elevation, no foreshortening — essential when judging a thickness or aligning a face). Swap by building the other camera, copying `position`/`up`/`target`, and rebuilding controls; size the ortho frustum from the current target distance (`h = 2·dist·tan(fov/2)`) so the switch doesn't jump scale. Handle resize for both (`isPerspectiveCamera` → set `aspect`; ortho → recompute `left/right` from aspect keeping height).
|
||||
- **`camera.up` + OrbitControls is a TRAP — read this.** Three's OrbitControls (r160) captures its orbit-axis quaternion from `camera.up` **once**, at construction. If you mutate `camera.up` afterward (e.g. to "fix" a top view, or to roll) and leave it, the main-viewport drag silently breaks — OrbitControls keeps orbiting around the *old* up while the camera renders with the *new* one. Two consequences for the gizmo: **(a)** Do NOT flip `camera.up` for top/bottom snaps. Leave it `(0,1,0)` and instead nudge the snap *direction* a hair off the pole (`dir = (0,±1,0.0009)`) so `lookAt` with `up=+Y` doesn't gimbal-lock. **(b)** When you DO need a new up (the roll arrows), **dispose and recreate OrbitControls** after setting `camera.up`, copying `target` across, so it re-captures the axis. Snaps and Home should reset to `up=(0,1,0)` and rebuild if currently rolled.
|
||||
- **Home / reset-view button** beside the cube (Fusion's house icon) that re-frames the object, resets `camera.up=(0,1,0)`, and rebuilds controls if rolled.
|
||||
- **Hover highlight the exact zone, not just the face.** Fusion subdivides each face into a 3×3 grid — center cell = face, edge cells = edges, corner cells = corners — and lights the hovered cell *wrapping across the adjacent faces*. Implement with a small pool of up to 3 translucent quads: from the hovered direction `d` (1/2/3 nonzero axes), for each nonzero axis place one quad on that face at the cell offset `(other-axis sign)*⅔`. A corner lights 3 quads (one per adjacent face), an edge 2, a face 1. A plain whole-face tint is wrong — the user can't tell a corner-pick from a face-pick. Also set a `grab`/`grabbing` cursor so the cube reads as draggable.
|
||||
|
||||
**Orient the model so FRONT is the face the user cares about.** The cube's labels are fixed to world axes, so how you place the model decides what "FRONT" shows. For a relief/panel/anything with a hero face, stand it so the hero face points world **+Z** (= FRONT) and image-up points **+Y** — don't lay it flat facing +Y, or FRONT shows a meaningless edge and TOP shows the hero (surprising and "wrong" to the user). Watch the displaced-axis sign too: Three's `PlaneGeometry` pushes `-y`, so vertex row 0 is **+Y (top)** — map image row 0 (top) to it with **no flip**, or your relief comes out upside-down. Verify by snapping FRONT and eyeballing against the source image; don't trust the index math.
|
||||
|
||||
**Build solids, not floating sheets.** A displaced `PlaneGeometry` is a single hollow surface — fine for a quick look, wrong the moment the user inspects it. In X-ray (or any side view) the raised bumps read as hollow domes floating above the base with a gap, and it's not watertight for STL/CAM. If the thing is a real object (relief, terrain block, carved panel), build a **solid heightfield**: displaced top surface + perimeter skirt walls + flat bottom, so it's rooted on its base. The user *will* notice "the back doesn't touch the backplate." Set the material `DoubleSide` so hand-wound walls never render black.
|
||||
|
||||
**Section / X-ray for hidden internal dimensions.** When a control sets something you can't see from outside — wall thickness, a backing/backplate, internal clearance, draft — add an **X-ray/section toggle** so the user can actually see what they're dialing. Cheapest version: ghost the outer shell (`transparent, opacity~0.15, depthWrite:false`) and render the measured solid (the backplate slab, the remaining wall) as an **opaque distinctly-colored mesh** with a bright edge line at the critical boundary; pair it with a side ortho snap so the dimension reads as a clean band. (A true clipping-plane section with caps is the fancier version; usually not worth the stencil work.) Don't make the user infer a hidden thickness from a number alone when one toggle can show it.
|
||||
|
||||
Keep it in world/view space aligned to how the model is *displayed* (account for any root rotation you applied). **Verify by visualization, not math** (these all bit me): click TOP then drag the *main viewport* and screenshot — confirm it still orbits (catches the `camera.up` trap); hover a corner and screenshot the cube — confirm the corner zone lights across faces, not the whole face; click a roll arrow from an iso view and screenshot — confirm it snaps to a face (not a diagonal roll); snap FRONT and confirm the hero face is upright. Genuinely-optional Fusion extras: the adjacent-face triangle arrows (drag-orbit covers them), the N/E/S/W compass ring, and the right-click "set current view as Home" menu — skip unless asked.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Build the studio for the specific question.** Don't make it generic. If the user is choosing a hero crop, the studio shows the actual hero. If they're choosing a font, the studio is reading sample text.
|
||||
2. **Serve locally.** Never hardcode a port — bind a static server to port 0 (the OS hands back a free port) for static HTML, or use the project's dev server for framework routes. Give the user the URL.
|
||||
3. **Verify it works headlessly** before handing it over (headless Playwright). Don't ask the user to debug your scaffolding.
|
||||
4. **User iterates.** They paste back a settings JSON, click a Download button, or say "go with N" / "use this".
|
||||
5. **Bake.** Render the chosen state into committed assets / production code with reproducible math. Verify the baked result matches what they dialed in (a quick screenshot diff is fair).
|
||||
6. **Tear down the scaffolding.** Delete the lookdev dir / dev route — it was decision-time scaffolding, not production code. Commit + deploy.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- **Static N×M comparison grid** — limits the user to your guesses; takes longer than a switcher; doesn't give them the in-between point they actually wanted.
|
||||
- **Numeric prompt before the slider** — "what saturation do you want?" is the wrong question; let them drag.
|
||||
- **Numeric inputs for spatial decisions** — drag the element. Sliders for opacity, drag handles for position.
|
||||
- **Drift between preview math and bake math** — when both JS preview and Python bake exist, port one to match the other and verify on a known input.
|
||||
- **Building inside production routes** — keep scaffolding isolated and trivially deletable. Reach for `app/dev/...` then nuke it.
|
||||
- **Skipping the WYSIWYG details** — preview without the real font / container / background lies to the user.
|
||||
- **No structured way to read the state back out** — a studio with no copy-able settings blob forces the agent to bake from a screenshot and the user to narrate values by hand. Every control must round-trip through a machine-readable export (see Architecture #3).
|
||||
- **Handing back a wall of prose for "review"** — pasting a long doc/blog into chat (or shipping the markdown file) and asking the user to react is NOT lookdev. For any document/copy/media review, build the **text & media annotation studio** (direct edit + highlight + comment + media-flag) so the user marks up the rendered artifact and the markup round-trips back as a patch. A boring text dump the user has to read and reply to in chat is the exact thing this skill exists to replace.
|
||||
|
||||
## Working example
|
||||
|
||||
A worked example — an image-treatment studio:
|
||||
extracts a Lab-k-means dominant palette with coverage %, exposes sliders
|
||||
(resolution, colorize, saturation, gap, glyph, contrast), per-band color
|
||||
pickers, a luminance-vs-nearest mapping toggle, a Copy-settings-JSON
|
||||
button, and a `--bake-json` Python path that renders the chosen state to
|
||||
committed PNG/WebP with math identical to the JS preview. The preview
|
||||
canvases match the production thumb and hero shapes exactly.
|
||||
|
||||
## Related (the studio / narrative family)
|
||||
|
||||
lookdev is one of two flagship narratives — **human-in-the-loop** (you, the human, judge and
|
||||
tune). Its determinism-narrative sibling is deterministic-design (render → *measure* the
|
||||
UI, numbers not vibes). The family it chains with:
|
||||
|
||||
- **deterministic-design** — the other flagship; measure/judge design output deterministically.
|
||||
- **screenstudio-alternative** — human-in-the-loop video/demo polish studio (NLE timeline).
|
||||
- **macos-screen-recorder** — capture a studio session or demo (display + system audio).
|
||||
- **lookdev-auto** — the *automated* counterpart: a vision model judges instead of you.
|
||||
The foil to lookdev's thesis — use when there's no human to sit the loop.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Lookdev is useful only when the user can inspect or mark up rendered variants; it is overkill for small deterministic edits.
|
||||
- A studio must faithfully mirror production fonts, media, containers, and constraints, otherwise the chosen settings can be misleading.
|
||||
- Human preference remains the source of truth, so the workflow cannot guarantee a universally "best" design or media treatment.
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
---
|
||||
name: macos-screen-recorder
|
||||
description: "macOS screen recorder that captures the main display PLUS system audio via ScreenCaptureKit — no BlackHole/loopback driver, no sudo, just the standard Screen Recording permission. CLI-driven; fills the headless-screen-recording-with-system-sound gap QuickTime and `screencapture -v` can't."
|
||||
risk: safe
|
||||
source: community
|
||||
source_type: community
|
||||
source_repo: connerkward/macos-screen-recorder-system-audio
|
||||
date_added: "2026-06-16"
|
||||
author: Conner K Ward
|
||||
license: MIT
|
||||
tags:
|
||||
- macos
|
||||
- screen-recording
|
||||
- system-audio
|
||||
- screencapturekit
|
||||
- cli
|
||||
- swift
|
||||
tools:
|
||||
- claude-code
|
||||
- antigravity
|
||||
- cursor
|
||||
- gemini-cli
|
||||
- codex-cli
|
||||
---
|
||||
## When to Use
|
||||
|
||||
Use when you need to script a screen recording WITH system sound on macOS from the CLI (demos, captures, voice-demo recording) — the case QuickTime and `screencapture -v` can't cover without a virtual audio device.
|
||||
|
||||
_Source: [connerkward/macos-screen-recorder-system-audio](https://github.com/connerkward/macos-screen-recorder-system-audio) (MIT)._
|
||||
|
||||
# macos-screen-recorder (sck-record)
|
||||
|
||||
`sck-record.swift` → compiled `sck-record` (binary gitignored; built by `setup-machine`, or
|
||||
`swiftc -O sck-record.swift -o sck-record`). Records the main display + system audio via
|
||||
ScreenCaptureKit.
|
||||
|
||||
```
|
||||
./sck-record <out.mp4> <seconds>
|
||||
```
|
||||
|
||||
**The one true differentiator:** system audio from the CLI with **zero install** — no
|
||||
BlackHole / loopback virtual device, no sudo; only the standard Screen Recording permission
|
||||
(granted once to whatever app shells out). It is *not* a general "better than OBS/Screen
|
||||
Studio" tool — it fills exactly the headless-CLI-with-system-audio gap.
|
||||
|
||||
`sck-record` is the raw capture primitive — it records, nothing more. To polish a
|
||||
recording afterward (idle speed-up, auto-zoom, keystroke chips, smoothed cursor,
|
||||
vertical export), pair it with
|
||||
[screenstudio-alternative-skill](https://github.com/connerkward/screenstudio-alternative-skill):
|
||||
record with `sck-record --no-cursor <out.mp4> <seconds>`, then run its post-production
|
||||
pass on the resulting mp4. (Auto-zoom and keystroke overlays additionally need an
|
||||
input-event log captured *during* recording, which that skill supplies; `sck-record`'s
|
||||
pixels alone cover idle speed-up, cursor smoothing, and vertical export.)
|
||||
|
||||
## Limitations
|
||||
|
||||
- macOS only; it depends on ScreenCaptureKit and the user's Screen Recording permission.
|
||||
- The recorder captures raw display and system audio but does not provide editing, auto-zoom, captions, or social-format polish by itself.
|
||||
- Input-event overlays require a separate event log captured during recording; pixels alone cannot reconstruct keystrokes or precise click metadata.
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
---
|
||||
name: pr-merge-champion
|
||||
description: "Optimize pull requests for quick approval and merging by ensuring clean diffs, comprehensive self-reviews, and structured documentation."
|
||||
category: workflow
|
||||
risk: safe
|
||||
source: self
|
||||
source_type: self
|
||||
date_added: "2026-06-16"
|
||||
author: himanshu-2l
|
||||
tags: [git, github, pull-request, code-review, workflow]
|
||||
tools: [claude, cursor, gemini, antigravity]
|
||||
---
|
||||
|
||||
# PR Merge Champion
|
||||
|
||||
## Overview
|
||||
|
||||
A systematic playbook for preparing, reviewing, and documenting pull requests to ensure they are high-quality, free of common oversights, and optimized for instant maintainer approval and merging.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Use when preparing to open a new pull request on GitHub or any Git hosting platform.
|
||||
- Use when self-auditing a feature or bug-fix branch for code cleanliness and consistency.
|
||||
- Use when trying to minimize review cycles and speed up the integration of your changes.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Step 1: Pre-Flight Clean Up & Rebase
|
||||
|
||||
Before presenting your code to reviewers, clean up any workspace noise and ensure your branch is up to date:
|
||||
1. Rebase your feature branch on top of the latest target branch (e.g., `main` or `master`) to resolve conflicts early.
|
||||
2. Clean up untracked, temp, or swap files from your repository.
|
||||
3. Run local linters, formatters, and compilers to ensure no stylistic or syntax errors exist.
|
||||
|
||||
### Step 2: Critical Self-Review
|
||||
|
||||
Review your own diff line-by-line as if you were the reviewer. Look out for:
|
||||
1. Leftover debugging statements (e.g., `console.log`, `print`, breakpoints, or custom debug flags).
|
||||
2. Unnecessary changes, white-space only diffs, or commented-out code blocks.
|
||||
3. Incomplete `TODO` comments that should be resolved or turned into tracked issues.
|
||||
4. Correctness of error handling and edge cases.
|
||||
|
||||
### Step 3: Local Verification & Test Suite
|
||||
|
||||
Verify that all changes work as expected:
|
||||
1. Run the project's automated test suite locally to verify no regressions are introduced.
|
||||
2. Check test coverage for any new code blocks you added.
|
||||
3. Manually test the critical paths and edge cases of your feature or bug fix.
|
||||
|
||||
### Step 4: Crafting the Pull Request Description
|
||||
|
||||
Write a high-signal, structured PR description. A great description tells the story of the changes:
|
||||
1. **Summary**: A concise explanation of the changes.
|
||||
2. **Context / Why**: Why this change is necessary and what problem it solves.
|
||||
3. **Verification**: Explicit details on how you tested it (test commands, screenshots, or step-by-step reproduction).
|
||||
4. **Checklist**: Conform to the repository's contributing guidelines and checklist requirements.
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Creating a Clean PR Description
|
||||
|
||||
```markdown
|
||||
# Pull Request: Implement Rate Limiting on Authentication Endpoint
|
||||
|
||||
## Summary
|
||||
Introduces an IP-based rate limiter on the `/api/v1/auth/login` endpoint using Redis to prevent brute-force attacks.
|
||||
|
||||
## Why
|
||||
We identified a high volume of login attempts targeting single accounts. This rate limiting window slows down attackers while keeping the system responsive for genuine users.
|
||||
|
||||
## Verification
|
||||
- Ran unit tests: `npm run test tests/auth.test.js` (all green)
|
||||
- Manually verified using Postman: sending 15 requests in under 60 seconds returns `429 Too Many Requests`.
|
||||
|
||||
## Checklist
|
||||
- [x] Code follows the style guide
|
||||
- [x] Unit tests added/updated
|
||||
- [x] Documentation updated
|
||||
```
|
||||
|
||||
### Example 2: Self-Review Clean Up Commands
|
||||
|
||||
Before committing, run these commands to inspect the diff for accidental additions:
|
||||
|
||||
```bash
|
||||
# Check the names of files changed to ensure no unwanted files are staged
|
||||
git status --porcelain
|
||||
|
||||
# Review the actual diff for any leftover print statements or debuggers
|
||||
git diff | grep -E "(console\.log|debugger|print\(|var_dump|binding\.pry)"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- ✅ **Keep PRs Small and Focused**: A PR with fewer than 200 lines of changes gets reviewed and merged significantly faster than a large one.
|
||||
- ✅ **Perform a Self-Review first**: Finding your own bugs and formatting issues first builds trust with the maintainers.
|
||||
- ✅ **Respect Repository Guidelines**: Check the project's `CONTRIBUTING.md` and pull request templates, and adhere to them strictly.
|
||||
- ❌ **Do Not Bundle Unrelated Changes**: Avoid sneaking refactoring or unrelated bug fixes into a feature PR. Create separate PRs instead.
|
||||
- ❌ **Do Not Ignore CI Failures**: Always fix failing tests, linters, or security scans on your branch before requesting a review.
|
||||
|
||||
## Limitations
|
||||
|
||||
- This skill does not replace project-specific CI/CD validation, automated testing, or domain-expert reviews.
|
||||
- It assumes a standard Git and GitHub-like environment, though the core principles apply to GitLab, Bitbucket, and other platforms.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **Problem:** A PR is left open for a long time due to minor formatting or style comments.
|
||||
**Solution:** Always run the repository's local formatter (e.g., Prettier, ESLint, Black) before committing.
|
||||
- **Problem:** Merge conflicts occur immediately after opening the PR.
|
||||
**Solution:** Pull the latest main branch and rebase or merge it into your branch daily.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `@pr-writer` - For Sentry-specific PR writing guidelines.
|
||||
- `@clean-code` - To ensure code quality before submitting.
|
||||
+11
@@ -113,6 +113,17 @@ A high score indicates _structural suitability_, not guaranteed rankings.
|
||||
|
||||
---
|
||||
|
||||
### Scoring Guidance per Category
|
||||
|
||||
For each of the six scoring categories, allot points within the category's weight band using these anchors:
|
||||
|
||||
- **0–15% of band:** No alignment — the site/topic clearly does not meet the criterion (e.g. fewer than 10 candidate entities for a directory-style PSEO).
|
||||
- **16–40% of band:** Partial alignment — the criterion is partially met, OR met for a small subset of pages only.
|
||||
- **41–80% of band:** Strong alignment — the criterion holds for most of the planned page set.
|
||||
- **81–100% of band:** Exemplary — the criterion holds universally and is reinforced by a structural data source (DB, API, validated CSV).
|
||||
|
||||
Sum the per-category scores to compute the Feasibility Index used in §"Feasibility Bands" below.
|
||||
|
||||
### Feasibility Bands (Required)
|
||||
|
||||
| Score | Verdict | Interpretation |
|
||||
|
||||
+11
@@ -110,6 +110,17 @@ This is a **diagnostic score**, not a promise of rich results.
|
||||
|
||||
---
|
||||
|
||||
### Scoring Guidance per Category
|
||||
|
||||
For each of the six scoring categories, allot points within the category's weight band using these anchors:
|
||||
|
||||
- **0–15% of band:** Schema describes none of the visible content (e.g. you would mark `description` for a `Recipe` page that has no recipe markup yet).
|
||||
- **16–40% of band:** Partial alignment — the schema describes some but not all of the visible content, OR maps to a less-common schema.org type.
|
||||
- **41–80% of band:** Strong alignment — the schema describes the bulk of the visible content with a common schema.org type.
|
||||
- **81–100% of band:** Exemplary — the schema covers all visible content, uses a Google-supported rich-result type, and includes all required properties.
|
||||
|
||||
Sum the per-category scores to compute the Eligibility Index used in §"Eligibility Bands" below.
|
||||
|
||||
### Eligibility Bands (Required)
|
||||
|
||||
| Score | Verdict | Interpretation |
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
---
|
||||
name: screenstudio-alt
|
||||
description: "Open-source headless Screen Studio alternative: auto speed-up of idle, auto-zoom on click clusters, keystroke overlay chips, smoothed synthetic cursor, and 9:16 vertical export that follows the action — post-production for screen recordings from the CLI."
|
||||
risk: safe
|
||||
source: community
|
||||
source_type: community
|
||||
source_repo: connerkward/screenstudio-alternative-skill
|
||||
date_added: "2026-06-16"
|
||||
author: Conner K Ward
|
||||
license: MIT
|
||||
tags:
|
||||
- screen-recording
|
||||
- video
|
||||
- post-production
|
||||
- auto-zoom
|
||||
- vertical-video
|
||||
- ffmpeg
|
||||
tools:
|
||||
- claude-code
|
||||
- antigravity
|
||||
- cursor
|
||||
- gemini-cli
|
||||
- codex-cli
|
||||
---
|
||||
## When to Use
|
||||
|
||||
Use when polishing a screen recording / demo video for sharing, when the user mentions Screen Studio, auto-zoom, idle speed-up, or vertical/social video from a screen capture, and for any social-facing demo (vertical output is the default for those).
|
||||
|
||||
_Source: [connerkward/screenstudio-alternative-skill](https://github.com/connerkward/screenstudio-alternative-skill) (MIT)._
|
||||
|
||||
# screenstudio-alt
|
||||
|
||||
The skill's code lives in this directory (`polish.py`, `render.py`, `studio.py`,
|
||||
`events-log.swift`, test fixtures, etc.). Published publicly as
|
||||
`connerkward/screen-studio-alternative` via the publish-skill skill.
|
||||
|
||||
Two components:
|
||||
|
||||
- `events-log` (Swift) — capture-side input logger (cursor 60Hz, clicks, keys;
|
||||
drops keys during macOS secure input). Runs ONLY while recording. Needs
|
||||
Accessibility/Input Monitoring for the terminal. **Auto-zoom/keys/cursor need
|
||||
this data at capture time — it cannot be recovered from pixels later.**
|
||||
- `polish.py` (Python, ffmpeg + PIL) — the post-production pass:
|
||||
|
||||
```bash
|
||||
python3 src/polish.py in.mp4 --events in.events.jsonl \
|
||||
--speedup # compress idle (input-gap ∩ frozen-pixels; animations stay 1x)
|
||||
--zoom # eased auto-zoom on click clusters (zoompan)
|
||||
--keys # accumulating keystroke chips (PIL overlays, no drawtext dep)
|
||||
--smooth-cursor # synthetic eased cursor (best with sck-record --no-cursor)
|
||||
--vertical # ALSO emit 1080x1920 following the action
|
||||
```
|
||||
|
||||
`--speedup` works WITHOUT events (freezedetect only) — usable on the whole
|
||||
existing dailies corpus.
|
||||
|
||||
- `render.py` — **high-quality non-destructive renderer** (preferred): single-pass
|
||||
spring-physics camera over the original high-res frames, LANCZOS into a smaller
|
||||
target (crisp zoom, ~1.3× sharper than the ffmpeg upscale path), 60fps, H + 9:16 V.
|
||||
Tunable `--freq`/`--zeta` (spring), `--fps`, `--target-w`. Takes explicit
|
||||
`--regions [{t0,t1,z,cx,cy}]`. `polish.py` is the older ffmpeg-filter fallback.
|
||||
- `studio.py [recording.mp4]` — local web UI, **NLE-style fixed-ruler timeline** (bar =
|
||||
source duration, never rescales → upstream always planted): zoom regions are draggable
|
||||
blocks (move / retime edges / click to add / double-click delete); idle spans are
|
||||
**speed blocks with rate-only editing** — source range locked, rate set via inspector
|
||||
slider on select or right-edge **rate-stretch** drag (FCP retime / Premiere Rate
|
||||
Stretch); rate changes ripple downstream only. Tunable cosine-ease ramp, default zoom,
|
||||
aspect, frame styling. Always-smooth synthetic cursor + click ripple + real recorded
|
||||
click sound (CC0 #735771). Export uses render.py. Free port, local. (Keystroke overlay
|
||||
exists in the engine but is off by default.)
|
||||
|
||||
## Easy path
|
||||
|
||||
`screencast.sh --demo` (screencast skill) does the whole chain: starts the event
|
||||
logger, records, then polishes + emits the 9:16 vertical automatically. Vertical
|
||||
is the DEFAULT for social-facing demos.
|
||||
|
||||
## Gotchas (learned the hard way, kept here so they're not relearned)
|
||||
|
||||
- ffmpeg CANNOT do animated `scale=eval=frame` → `crop` (link reinit wedges crop's
|
||||
per-frame exprs). That's why zoom uses `zoompan` (no `t` var there — use `on/FPS`).
|
||||
- This machine's ffmpeg lacks `drawtext`; all text/cursor overlays are PIL-rendered
|
||||
PNGs + `overlay`.
|
||||
- Test rig: `make-fixture.py` synthesizes a fake screen recording + ground-truth
|
||||
events.jsonl — validate any change against it before trusting real footage.
|
||||
|
||||
## Limitations
|
||||
|
||||
- The workflow assumes FFmpeg plus the companion scripts are available locally; it is not a hosted video editor.
|
||||
- Polished cursor, click, and keystroke effects depend on event logs captured during recording; missing logs limit what can be reconstructed.
|
||||
- Auto-zoom and idle speed-up still need human review for pacing, framing, and platform-specific taste.
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
---
|
||||
name: super-code
|
||||
description: "Standing house style to enforce dense, correct, and idiomatic code on all coding tasks. Minimizes code bloat and agent operation overhead."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
|
||||
# Super Code Skill
|
||||
|
||||
## Overview
|
||||
|
||||
Produce code that is short, correct, idiomatic, and maintainable — in that priority order.
|
||||
This skill addresses two distinct inefficiency types that must be fixed independently:
|
||||
|
||||
1. **Code-token inefficiency** — the artifact itself is bloated (unnecessary lines, boilerplate, over-abstraction)
|
||||
2. **Generation-token inefficiency** — how the agent operates during a session (full-file rewrites, unrequested files, prose padding before/after changes)
|
||||
|
||||
Both matter. Fixing only one is not enough.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Apply this skill automatically on EVERY coding task in the IDE.
|
||||
- Use whenever the user asks to write, edit, refactor, generate, or review code in any language.
|
||||
- This is a standing house style, not an on-demand pass — always apply it.
|
||||
|
||||
---
|
||||
|
||||
## Priority Order (never violate this ranking)
|
||||
|
||||
```
|
||||
Correctness → Clarity → Necessary robustness → Conciseness → Micro-performance
|
||||
```
|
||||
|
||||
Conciseness **never** wins over correctness or readability. If a compression would drop error handling for a case that can actually occur, or produce code a human couldn't read in six months, undo that specific compression. Short bad code is worse than long correct code.
|
||||
|
||||
---
|
||||
|
||||
## Workflow — apply to every coding task
|
||||
|
||||
### Step 1: Commit to minimal correct shape BEFORE writing
|
||||
|
||||
Before touching a file, decide:
|
||||
- What is the smallest surface area that correctly solves this problem?
|
||||
- What does the caller actually need from this function/class/module?
|
||||
- Is there a stdlib/framework primitive that already does this?
|
||||
|
||||
Write that down mentally (not in a prose block to the user). This is the target shape.
|
||||
|
||||
### Step 2: Write using language-idiomatic patterns
|
||||
|
||||
Read the relevant reference file for the language in use:
|
||||
- Bash/Shell → `bash/SKILL.md`
|
||||
- C → `c/SKILL.md`
|
||||
- C++ → `cpp/SKILL.md`
|
||||
- C# → `csharp/SKILL.md`
|
||||
- Dart/Flutter → `dart/SKILL.md`
|
||||
- Elixir/Erlang → `elixir/SKILL.md`
|
||||
- Go → `go/SKILL.md`
|
||||
- Java → `java/SKILL.md`
|
||||
- Kotlin/Compose → `kotlin/SKILL.md`
|
||||
- PHP → `php/SKILL.md`
|
||||
- Python → `python/SKILL.md`
|
||||
- Ruby → `ruby/SKILL.md`
|
||||
- Rust → `rust/SKILL.md`
|
||||
- Scala → `scala/SKILL.md`
|
||||
- Swift → `swift/SKILL.md`
|
||||
- TypeScript/JavaScript → `typescript/SKILL.md`
|
||||
|
||||
Apply idiomatic patterns from that file. They replace verbose imperative code with correct, concise equivalents that are still readable.
|
||||
|
||||
### Step 3: Compression pass on your own draft
|
||||
|
||||
Before presenting any code, scan it for:
|
||||
|
||||
| Anti-pattern | Fix |
|
||||
|---|---|
|
||||
| Comment restates what code does | Delete comment, or rewrite to say *why* |
|
||||
| Single-use helper function/class | Inline it |
|
||||
| Stdlib/framework already does this | Replace with the primitive |
|
||||
| Defensive handling for impossible case | Remove |
|
||||
| Verbose loop replaceable by idiomatic expression | Replace |
|
||||
| Logging/print nobody asked for | Remove |
|
||||
| Extra config/files/parameters not requested | Remove |
|
||||
| Unused import or variable | Remove |
|
||||
|
||||
### Step 4: Guardrail check — run this before presenting
|
||||
|
||||
Ask yourself (silently):
|
||||
- [ ] Did I remove handling for a case that **can** actually happen?
|
||||
- [ ] Did I make this harder to read for a human six months from now?
|
||||
- [ ] Did I drop correctness or security to save lines?
|
||||
|
||||
If yes to any: undo that specific compression and keep the rest.
|
||||
|
||||
### Step 5: Present output — generation-token rules
|
||||
|
||||
**Always:**
|
||||
- Edit files via targeted patches/diffs, not full rewrites, unless the file is new or the change touches >70% of lines
|
||||
- Present only what was asked for
|
||||
|
||||
**Never:**
|
||||
- Generate unrequested files (tests, READMEs, configs, types) unless the user asked
|
||||
- Add prose blocks before/after code explaining what you're about to do or just did — just do it
|
||||
- Re-explain the user's own requirement back to them before writing
|
||||
- Restate what changed in a paragraph after showing the diff — the diff is self-evident
|
||||
|
||||
---
|
||||
|
||||
## Universal Anti-Pattern Checklist
|
||||
|
||||
These apply in every language. The language-specific files extend this list.
|
||||
|
||||
### Comments
|
||||
- ❌ `// Loop through the list and add each item` → ❌ delete
|
||||
- ✅ `// Order matters: process refunds before charges` → ✅ keep (explains *why*)
|
||||
- Rule: if the comment could be generated mechanically from reading the code, it adds no value
|
||||
|
||||
### Defensive coding
|
||||
- Only handle error cases that can actually occur given the call site
|
||||
- If the caller guarantees non-null, don't null-check inside the function
|
||||
- If a catch block can only log and rethrow, consider removing the try/catch
|
||||
|
||||
### Abstractions
|
||||
- Don't extract a function for logic used exactly once in one place
|
||||
- Don't create a wrapper class around a primitive used only once
|
||||
- Threshold: abstraction earns its place when it's used 2+ times OR when it has a meaningful name that genuinely clarifies domain logic
|
||||
|
||||
### Scaffolding
|
||||
- No placeholder TODOs unless the user asked for a scaffold
|
||||
- No `// TODO: add error handling` — either add it or don't
|
||||
- No empty catch blocks "just in case"
|
||||
- No parameters the current callers don't use
|
||||
|
||||
---
|
||||
|
||||
## Generation-Token Rules (agentic IDE specific)
|
||||
|
||||
These govern how you operate inside the session, not just what you produce:
|
||||
|
||||
### File edits
|
||||
- Prefer surgical patches: show only the changed lines + minimal context
|
||||
- Full file rewrite is acceptable only for: new files, files shorter than ~30 lines, or changes touching >70% of the file
|
||||
- Never repeat unchanged portions of a file to "show the full context"
|
||||
|
||||
### Unrequested artifacts
|
||||
- Do not create test files, README updates, type definition files, config files, or CI scripts unless explicitly requested
|
||||
- If you believe a test file would be valuable, offer it in one sentence after the main output — don't generate it unasked
|
||||
|
||||
### Prose overhead
|
||||
- No "Here's what I'm going to do:" preambles
|
||||
- No "I've made the following changes:" postambles (the diff shows this)
|
||||
- No "Let me know if you'd like me to..." closers
|
||||
- One-line clarification is acceptable if a requirement is genuinely ambiguous; otherwise, make a reasonable choice and note the assumption in a comment inside the code if it matters
|
||||
|
||||
---
|
||||
|
||||
## Language Reference Files
|
||||
|
||||
| Language / Stack | File |
|
||||
|---|---|
|
||||
| Bash / Shell | `bash/SKILL.md` |
|
||||
| C | `c/SKILL.md` |
|
||||
| C++ | `cpp/SKILL.md` |
|
||||
| C# / .NET | `csharp/SKILL.md` |
|
||||
| Dart / Flutter | `dart/SKILL.md` |
|
||||
| Elixir / Erlang | `elixir/SKILL.md` |
|
||||
| Go | `go/SKILL.md` |
|
||||
| Java | `java/SKILL.md` |
|
||||
| Kotlin + Compose (Android) | `kotlin/SKILL.md` |
|
||||
| PHP | `php/SKILL.md` |
|
||||
| Python | `python/SKILL.md` |
|
||||
| Ruby | `ruby/SKILL.md` |
|
||||
| Rust | `rust/SKILL.md` |
|
||||
| Scala | `scala/SKILL.md` |
|
||||
| Swift (iOS/macOS) | `swift/SKILL.md` |
|
||||
| TypeScript / JavaScript | `typescript/SKILL.md` |
|
||||
|
||||
Read the relevant file at Step 2. If the language isn't listed, apply the universal checklist above and use the language's own idioms for loops, error handling, and data transformation.
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Refactoring a verbose loop
|
||||
```java
|
||||
// Anti-pattern
|
||||
List<String> names = new ArrayList<>();
|
||||
for (User u : users) {
|
||||
if (u.isActive()) {
|
||||
names.add(u.getName());
|
||||
}
|
||||
}
|
||||
// Super-code idiomatic (Java)
|
||||
List<String> names = users.stream().filter(User::isActive).map(User::getName).toList();
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Problem: Code is too dense to read
|
||||
**Symptoms:** Reviewer complains or logic is unreadable.
|
||||
**Solution:** Revert the overly compressed section. Clarity and correctness always win over conciseness.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `@karpathy-guidelines` - For behavioral guidelines on surgical changes and simplicity.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Language Support:** Language-specific idioms require reference files.
|
||||
- **Readability Tradeoffs:** Extreme compression can sometimes harm readability if not careful.
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
---
|
||||
name: bash
|
||||
description: "Language-specific super-code guidelines for bash."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# Bash / Shell: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Quoting & Word Splitting](#quoting)
|
||||
2. [Conditionals & Tests](#conditionals)
|
||||
3. [Loops & Iteration](#loops)
|
||||
4. [Pipes & Process Substitution](#pipes)
|
||||
5. [Functions & Return Values](#functions)
|
||||
6. [Error Handling](#errors)
|
||||
7. [Anti-patterns specific to Bash](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Quoting & Word Splitting {#quoting}
|
||||
|
||||
```bash
|
||||
# ❌ Unquoted variable (word splitting + globbing)
|
||||
for f in $files; do rm $f; done
|
||||
|
||||
# ✅
|
||||
for f in "${files[@]}"; do rm -- "$f"; done
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ Unquoted command substitution
|
||||
path=$(find . -name config)
|
||||
cat $path # breaks on spaces
|
||||
|
||||
# ✅
|
||||
path="$(find . -name config)"
|
||||
cat "$path"
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ Using backticks for command substitution
|
||||
result=`echo hello`
|
||||
|
||||
# ✅ — $() nests cleanly
|
||||
result=$(echo hello)
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ String comparison without quotes
|
||||
if [ $var = "hello" ]; then # breaks if var is empty or has spaces
|
||||
|
||||
# ✅
|
||||
if [[ "$var" = "hello" ]]; then
|
||||
```
|
||||
|
||||
**Rule: double-quote every `$variable` and `$(command)` unless you specifically need splitting.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Conditionals & Tests {#conditionals}
|
||||
|
||||
```bash
|
||||
# ❌ Single bracket test (POSIX but fragile)
|
||||
if [ -f "$file" -a -r "$file" ]; then
|
||||
|
||||
# ✅ — [[ is safer, supports &&/||, no word splitting inside
|
||||
if [[ -f "$file" && -r "$file" ]]; then
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ Testing command exit status with if [ $? -eq 0 ]
|
||||
grep -q pattern file
|
||||
if [ $? -eq 0 ]; then echo "found"; fi
|
||||
|
||||
# ✅ — test command directly
|
||||
if grep -q pattern file; then echo "found"; fi
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ String equality with == outside [[
|
||||
if [ "$a" == "$b" ]; then # == not POSIX in [ ]
|
||||
|
||||
# ✅
|
||||
if [[ "$a" == "$b" ]]; then # Bash
|
||||
# or POSIX:
|
||||
if [ "$a" = "$b" ]; then
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ Arithmetic with [ ] and string comparison
|
||||
if [ "$count" -gt 10 ]; then
|
||||
|
||||
# ✅ — (( )) for arithmetic
|
||||
if (( count > 10 )); then
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Loops & Iteration {#loops}
|
||||
|
||||
```bash
|
||||
# ❌ Parsing ls output
|
||||
for f in $(ls *.txt); do process "$f"; done
|
||||
|
||||
# ✅ — glob directly
|
||||
for f in *.txt; do
|
||||
[[ -e "$f" ]] || continue # handle no-match
|
||||
process "$f"
|
||||
done
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ Reading file line-by-line with for
|
||||
for line in $(cat file.txt); do # splits on words, not lines
|
||||
|
||||
# ✅
|
||||
while IFS= read -r line; do
|
||||
process "$line"
|
||||
done < file.txt
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ Seq for counting
|
||||
for i in $(seq 1 10); do
|
||||
|
||||
# ✅ — brace expansion (Bash)
|
||||
for i in {1..10}; do
|
||||
# or C-style:
|
||||
for (( i = 1; i <= 10; i++ )); do
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ Processing command output line-by-line with pipe (subshell trap)
|
||||
count=0
|
||||
cat file.txt | while read -r line; do
|
||||
(( count++ )) # count resets after loop — subshell
|
||||
done
|
||||
echo "$count" # always 0
|
||||
|
||||
# ✅ — redirect, not pipe
|
||||
count=0
|
||||
while IFS= read -r line; do
|
||||
(( count++ ))
|
||||
done < file.txt
|
||||
echo "$count"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Pipes & Process Substitution {#pipes}
|
||||
|
||||
```bash
|
||||
# ❌ Chained grep | grep for AND
|
||||
grep "error" log.txt | grep "timeout"
|
||||
|
||||
# ✅ — single grep with pattern
|
||||
grep -E "error.*timeout|timeout.*error" log.txt
|
||||
# or awk for complex logic:
|
||||
awk '/error/ && /timeout/' log.txt
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ cat + pipe (UUOC — Useless Use of Cat)
|
||||
cat file.txt | grep pattern
|
||||
|
||||
# ✅
|
||||
grep pattern file.txt
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ Temp file for diff between commands
|
||||
cmd1 > /tmp/a.txt
|
||||
cmd2 > /tmp/b.txt
|
||||
diff /tmp/a.txt /tmp/b.txt
|
||||
rm /tmp/a.txt /tmp/b.txt
|
||||
|
||||
# ✅ — process substitution
|
||||
diff <(cmd1) <(cmd2)
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ Ignoring pipe failures (only last command's exit code)
|
||||
false | true
|
||||
echo $? # 0 — false's failure hidden
|
||||
|
||||
# ✅
|
||||
set -o pipefail
|
||||
false | true
|
||||
echo $? # 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Functions & Return Values {#functions}
|
||||
|
||||
```bash
|
||||
# ❌ Using return for string values
|
||||
get_name() {
|
||||
return "Alice" # return is for exit codes (0-255)
|
||||
}
|
||||
|
||||
# ✅ — echo + capture
|
||||
get_name() {
|
||||
echo "Alice"
|
||||
}
|
||||
name=$(get_name)
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ Global variables modified inside functions
|
||||
result=""
|
||||
compute() { result="done"; }
|
||||
|
||||
# ✅ — use local, return via stdout
|
||||
compute() {
|
||||
local tmp
|
||||
tmp=$(do_work)
|
||||
echo "$tmp"
|
||||
}
|
||||
result=$(compute)
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ Function keyword (not POSIX)
|
||||
function my_func {
|
||||
|
||||
# ✅
|
||||
my_func() {
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Error Handling {#errors}
|
||||
|
||||
```bash
|
||||
# ❌ No error handling — script continues after failure
|
||||
cd /some/dir
|
||||
rm -rf * # if cd fails, deletes from wrong directory
|
||||
|
||||
# ✅
|
||||
set -euo pipefail
|
||||
|
||||
cd /some/dir || { echo "cd failed" >&2; exit 1; }
|
||||
rm -rf ./*
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ No cleanup on exit
|
||||
tmpfile=$(mktemp)
|
||||
# ... script might exit early, leaving tmpfile
|
||||
|
||||
# ✅ — trap for cleanup
|
||||
tmpfile=$(mktemp)
|
||||
trap 'rm -f "$tmpfile"' EXIT
|
||||
```
|
||||
|
||||
```bash
|
||||
# ❌ Silencing errors blindly
|
||||
command 2>/dev/null
|
||||
|
||||
# ✅ — redirect only when you know what you're suppressing
|
||||
command 2>/dev/null || true # explicit: we expect and accept failure
|
||||
```
|
||||
|
||||
**Start every script with `set -euo pipefail`. Remove selectively where needed.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to Bash {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| Parsing `ls` output | glob: `for f in *.txt` |
|
||||
| `cat file \| grep` | `grep pattern file` |
|
||||
| Unquoted `$var` | `"$var"` always |
|
||||
| `[ ]` for complex tests | `[[ ]]` |
|
||||
| Backtick substitution | `$(command)` |
|
||||
| `$?` check after command | `if command; then` |
|
||||
| `echo` for debug | `printf '%s\n'` (portable) |
|
||||
| No `set -euo pipefail` | always set at script top |
|
||||
| Temp files without cleanup | `trap 'rm -f "$tmp"' EXIT` |
|
||||
| `eval` with user input | avoid; use arrays for dynamic commands |
|
||||
| `#!/bin/sh` with Bash features | `#!/usr/bin/env bash` |
|
||||
| String math `expr 1 + 1` | `$(( 1 + 1 ))` |
|
||||
| `test -z` for number comparison | `(( ))` for arithmetic |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
---
|
||||
name: c
|
||||
description: "Language-specific super-code guidelines for c."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# C: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Memory Management](#memory)
|
||||
2. [Pointers & Arrays](#pointers)
|
||||
3. [Error Handling](#errors)
|
||||
4. [Strings](#strings)
|
||||
5. [Structs & Enums](#structs)
|
||||
6. [Preprocessor & Headers](#preprocessor)
|
||||
7. [Anti-patterns specific to C](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Memory Management {#memory}
|
||||
|
||||
```c
|
||||
// ❌ malloc without checking return value
|
||||
char *buf = malloc(size);
|
||||
strcpy(buf, src);
|
||||
|
||||
// ✅
|
||||
char *buf = malloc(size);
|
||||
if (!buf) return -ENOMEM;
|
||||
memcpy(buf, src, size);
|
||||
```
|
||||
|
||||
```c
|
||||
// ❌ Casting malloc result (unnecessary in C, hides missing #include)
|
||||
int *p = (int *)malloc(n * sizeof(int));
|
||||
|
||||
// ✅
|
||||
int *p = malloc(n * sizeof *p);
|
||||
```
|
||||
|
||||
```c
|
||||
// ❌ free without nulling (dangling pointer risk in long-lived scope)
|
||||
free(ptr);
|
||||
// ... later code might use ptr
|
||||
|
||||
// ✅
|
||||
free(ptr);
|
||||
ptr = NULL;
|
||||
```
|
||||
|
||||
```c
|
||||
// ❌ Forgetting to free on early-return paths
|
||||
char *a = malloc(100);
|
||||
char *b = malloc(200);
|
||||
if (!b) return -1; // leaks a
|
||||
|
||||
// ✅ — single cleanup label
|
||||
char *a = NULL, *b = NULL;
|
||||
a = malloc(100);
|
||||
if (!a) goto cleanup;
|
||||
b = malloc(200);
|
||||
if (!b) goto cleanup;
|
||||
// ... use a, b ...
|
||||
cleanup:
|
||||
free(b);
|
||||
free(a);
|
||||
```
|
||||
|
||||
**Use `sizeof *ptr` instead of `sizeof(Type)` — it stays correct when the type changes.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Pointers & Arrays {#pointers}
|
||||
|
||||
```c
|
||||
// ❌ Manual array size tracking
|
||||
void process(int *arr, int len) { ... }
|
||||
process(data, 10);
|
||||
|
||||
// ✅ — pass size alongside pointer, or use a struct
|
||||
typedef struct { int *data; size_t len; } IntSlice;
|
||||
```
|
||||
|
||||
```c
|
||||
// ❌ Pointer arithmetic where array indexing is clearer
|
||||
*(arr + i) = value;
|
||||
|
||||
// ✅
|
||||
arr[i] = value;
|
||||
```
|
||||
|
||||
```c
|
||||
// ❌ VLA in production code (stack overflow risk, optional in C11+)
|
||||
int arr[n];
|
||||
|
||||
// ✅
|
||||
int *arr = malloc(n * sizeof *arr);
|
||||
if (!arr) return -ENOMEM;
|
||||
// ... use arr ...
|
||||
free(arr);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Error Handling {#errors}
|
||||
|
||||
```c
|
||||
// ❌ Using magic numbers for error returns
|
||||
if (do_thing() == -1) { ... }
|
||||
|
||||
// ✅ — define or use named error codes
|
||||
#include <errno.h>
|
||||
if (do_thing() < 0) {
|
||||
perror("do_thing");
|
||||
return errno;
|
||||
}
|
||||
```
|
||||
|
||||
```c
|
||||
// ❌ Deeply nested error checks
|
||||
int r1 = step1();
|
||||
if (r1 == 0) {
|
||||
int r2 = step2();
|
||||
if (r2 == 0) {
|
||||
int r3 = step3();
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ — early return / goto cleanup
|
||||
if (step1() < 0) goto fail;
|
||||
if (step2() < 0) goto fail;
|
||||
if (step3() < 0) goto fail;
|
||||
return 0;
|
||||
fail:
|
||||
cleanup();
|
||||
return -1;
|
||||
```
|
||||
|
||||
**`goto cleanup` is idiomatic C for resource teardown — don't avoid it out of principle.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Strings {#strings}
|
||||
|
||||
```c
|
||||
// ❌ strcpy without bounds checking
|
||||
strcpy(dest, src);
|
||||
|
||||
// ✅
|
||||
strncpy(dest, src, sizeof(dest) - 1);
|
||||
dest[sizeof(dest) - 1] = '\0';
|
||||
// or better: snprintf(dest, sizeof(dest), "%s", src);
|
||||
```
|
||||
|
||||
```c
|
||||
// ❌ strcmp misuse
|
||||
if (str == "hello") { ... } // compares pointers, not content
|
||||
|
||||
// ✅
|
||||
if (strcmp(str, "hello") == 0) { ... }
|
||||
```
|
||||
|
||||
```c
|
||||
// ❌ Building strings with repeated strcat (O(n²))
|
||||
char result[1024] = "";
|
||||
for (int i = 0; i < n; i++) {
|
||||
strcat(result, items[i]);
|
||||
}
|
||||
|
||||
// ✅ — track write position
|
||||
char result[1024];
|
||||
int pos = 0;
|
||||
for (int i = 0; i < n && pos < (int)sizeof(result); i++) {
|
||||
pos += snprintf(result + pos, sizeof(result) - pos, "%s", items[i]);
|
||||
}
|
||||
```
|
||||
|
||||
**Prefer `snprintf` over `sprintf` — always.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Structs & Enums {#structs}
|
||||
|
||||
```c
|
||||
// ❌ Bare struct requiring `struct` keyword everywhere
|
||||
struct point { int x, y; };
|
||||
struct point p = {1, 2};
|
||||
|
||||
// ✅
|
||||
typedef struct { int x, y; } Point;
|
||||
Point p = {1, 2};
|
||||
```
|
||||
|
||||
```c
|
||||
// ❌ Uninitialized struct
|
||||
Point p;
|
||||
use(p.x); // UB
|
||||
|
||||
// ✅
|
||||
Point p = {0};
|
||||
```
|
||||
|
||||
```c
|
||||
// ❌ Magic integer constants
|
||||
if (state == 3) { ... }
|
||||
|
||||
// ✅
|
||||
typedef enum { STATE_IDLE, STATE_RUNNING, STATE_DONE } State;
|
||||
if (state == STATE_DONE) { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Preprocessor & Headers {#preprocessor}
|
||||
|
||||
```c
|
||||
// ❌ Macro where inline function works (no type safety, double eval)
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
MAX(x++, y) // x incremented twice if x > y
|
||||
|
||||
// ✅
|
||||
static inline int max_int(int a, int b) { return a > b ? a : b; }
|
||||
```
|
||||
|
||||
```c
|
||||
// ❌ No include guard
|
||||
// my_header.h
|
||||
struct Foo { int x; };
|
||||
|
||||
// ✅
|
||||
#ifndef MY_HEADER_H
|
||||
#define MY_HEADER_H
|
||||
struct Foo { int x; };
|
||||
#endif
|
||||
// or: #pragma once (widely supported, not standard)
|
||||
```
|
||||
|
||||
**Keep macros for conditional compilation and constants. Use `static inline` for logic.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to C {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `sprintf` | `snprintf` with buffer size |
|
||||
| `gets` | `fgets` (gets is removed in C11) |
|
||||
| Casting `malloc` result | let implicit `void*` conversion work |
|
||||
| `sizeof(Type)` in malloc | `sizeof *ptr` |
|
||||
| VLA for large/runtime arrays | heap allocation |
|
||||
| `void*` callbacks without context param | pass `void *ctx` alongside function pointer |
|
||||
| Global mutable state | pass state through struct pointers |
|
||||
| `assert` for runtime error handling | proper error return codes |
|
||||
| Missing `const` on read-only pointer params | `const char *str` |
|
||||
| Mixing signed/unsigned in comparisons | use consistent types, cast explicitly |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
---
|
||||
name: cpp
|
||||
description: "Language-specific super-code guidelines for cpp."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# C++: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Memory & Ownership](#memory)
|
||||
2. [Modern Types & Containers](#types)
|
||||
3. [Move Semantics & References](#move)
|
||||
4. [Templates & Concepts](#templates)
|
||||
5. [Error Handling](#errors)
|
||||
6. [Concurrency](#concurrency)
|
||||
7. [Anti-patterns specific to C++](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Memory & Ownership {#memory}
|
||||
|
||||
```cpp
|
||||
// ❌ Raw new/delete
|
||||
Widget* w = new Widget();
|
||||
// ... 15 lines later ...
|
||||
delete w;
|
||||
|
||||
// ✅
|
||||
auto w = std::make_unique<Widget>();
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ Shared ownership when unique suffices
|
||||
auto w = std::make_shared<Widget>();
|
||||
transfer(w); // only one owner
|
||||
|
||||
// ✅ — unique_ptr; move when transferring
|
||||
auto w = std::make_unique<Widget>();
|
||||
transfer(std::move(w));
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ new[] for dynamic arrays
|
||||
int* arr = new int[n];
|
||||
// ... use ...
|
||||
delete[] arr;
|
||||
|
||||
// ✅
|
||||
std::vector<int> arr(n);
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ Manual RAII wrapper for file/mutex
|
||||
FILE* f = fopen(path, "r");
|
||||
// ... must remember fclose ...
|
||||
|
||||
// ✅
|
||||
std::ifstream f(path);
|
||||
// closes automatically at scope exit
|
||||
// For non-standard resources: use unique_ptr with custom deleter
|
||||
auto f = std::unique_ptr<FILE, decltype(&fclose)>(fopen(path, "r"), fclose);
|
||||
```
|
||||
|
||||
**Rule: if you type `new`, you almost certainly want `make_unique` or `make_shared`.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Modern Types & Containers {#types}
|
||||
|
||||
```cpp
|
||||
// ❌ C-style string manipulation
|
||||
char buf[256];
|
||||
sprintf(buf, "%s:%d", host, port);
|
||||
|
||||
// ✅
|
||||
auto addr = std::format("{}:{}", host, port); // C++20
|
||||
// or: auto addr = host + ":" + std::to_string(port);
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ out-parameter for multiple returns
|
||||
void compute(int input, int& result, std::string& error);
|
||||
|
||||
// ✅
|
||||
struct ComputeResult { int value; std::string error; };
|
||||
ComputeResult compute(int input);
|
||||
// or: std::pair / std::tuple with structured bindings
|
||||
auto [value, error] = compute(input);
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ Manual loop to find element
|
||||
int idx = -1;
|
||||
for (int i = 0; i < vec.size(); i++) {
|
||||
if (vec[i] == target) { idx = i; break; }
|
||||
}
|
||||
|
||||
// ✅
|
||||
auto it = std::ranges::find(vec, target); // C++20
|
||||
// or: std::find(vec.begin(), vec.end(), target);
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ Checking .find() != .end() then accessing
|
||||
auto it = map.find(key);
|
||||
if (it != map.end()) { use(it->second); }
|
||||
|
||||
// ✅ (C++20)
|
||||
if (map.contains(key)) { use(map[key]); }
|
||||
// or keep iterator version when you need the value without double lookup
|
||||
```
|
||||
|
||||
**Use `std::string_view` for function parameters that don't need ownership.**
|
||||
|
||||
---
|
||||
|
||||
## 3. Move Semantics & References {#move}
|
||||
|
||||
```cpp
|
||||
// ❌ Copying a large container into a function
|
||||
void process(std::vector<Data> items) { ... } // copies on call
|
||||
|
||||
// ✅ — const ref for read, move for sink
|
||||
void process(const std::vector<Data>& items) { ... } // read-only
|
||||
void consume(std::vector<Data> items) { ... } // sink: caller moves in
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ std::move on const object (silently copies)
|
||||
const std::string s = "hello";
|
||||
take(std::move(s)); // still copies
|
||||
|
||||
// ✅ — don't const things you intend to move
|
||||
std::string s = "hello";
|
||||
take(std::move(s));
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ Returning std::move from local (prevents NRVO)
|
||||
std::vector<int> build() {
|
||||
std::vector<int> v;
|
||||
// ... fill ...
|
||||
return std::move(v); // pessimization
|
||||
|
||||
// ✅ — just return the local; compiler applies NRVO or implicit move
|
||||
return v;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Templates & Concepts {#templates}
|
||||
|
||||
```cpp
|
||||
// ❌ SFINAE soup
|
||||
template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
T square(T x) { return x * x; }
|
||||
|
||||
// ✅ (C++20 concepts)
|
||||
template<std::integral T>
|
||||
T square(T x) { return x * x; }
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ Template for one type
|
||||
template<typename T>
|
||||
void log(T msg) { std::cout << msg; }
|
||||
// Only ever called with std::string
|
||||
|
||||
// ✅ — don't templatize unless you need multiple types
|
||||
void log(std::string_view msg) { std::cout << msg; }
|
||||
```
|
||||
|
||||
**Concepts make template errors readable — prefer them over SFINAE and static_assert.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Error Handling {#errors}
|
||||
|
||||
```cpp
|
||||
// ❌ Error codes via int returns (C-style in C++)
|
||||
int parse(const std::string& input, Data& out);
|
||||
|
||||
// ✅ — std::expected (C++23) or exceptions
|
||||
std::expected<Data, ParseError> parse(const std::string& input);
|
||||
// or throw for exceptional conditions
|
||||
Data parse(const std::string& input); // throws ParseError
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ Catching by value (slices derived exceptions)
|
||||
try { ... }
|
||||
catch (std::exception e) { ... }
|
||||
|
||||
// ✅
|
||||
catch (const std::exception& e) { ... }
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ Exception in destructor
|
||||
~MyClass() {
|
||||
if (cleanup() < 0) throw CleanupError(); // terminates
|
||||
|
||||
// ✅ — destructors must be noexcept; log/swallow errors
|
||||
~MyClass() noexcept {
|
||||
if (cleanup() < 0) log_error("cleanup failed");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Concurrency {#concurrency}
|
||||
|
||||
```cpp
|
||||
// ❌ Manual thread + join tracking
|
||||
std::thread t(work);
|
||||
// ... must remember t.join() ...
|
||||
|
||||
// ✅ (C++20)
|
||||
std::jthread t(work); // auto-joins on destruction
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ Lock/unlock manually
|
||||
mtx.lock();
|
||||
data.push_back(item);
|
||||
mtx.unlock(); // missed on exception
|
||||
|
||||
// ✅
|
||||
{
|
||||
std::scoped_lock lock(mtx);
|
||||
data.push_back(item);
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// ❌ Polling a shared bool for completion
|
||||
while (!done.load()) { std::this_thread::sleep_for(10ms); }
|
||||
|
||||
// ✅ — use std::future or condition_variable
|
||||
auto future = std::async(std::launch::async, compute);
|
||||
auto result = future.get();
|
||||
```
|
||||
|
||||
**Use `std::scoped_lock` over `lock_guard` — it handles multiple mutexes and avoids deadlock.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to C++ {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| Raw `new`/`delete` | `make_unique` / `make_shared` |
|
||||
| `(Type)expr` C-style cast | `static_cast<Type>(expr)` |
|
||||
| `#define` constants | `constexpr` variables |
|
||||
| `NULL` | `nullptr` |
|
||||
| `using namespace std;` in headers | explicit `std::` prefix |
|
||||
| Manual loop for transform/filter | `std::ranges` or `<algorithm>` |
|
||||
| `std::endl` | `'\n'` (endl flushes — slow) |
|
||||
| `char*` for string parameters | `std::string_view` |
|
||||
| Exception specification `throw()` | `noexcept` |
|
||||
| Inheriting from `std::` containers | composition, not inheritance |
|
||||
| `volatile` for thread synchronization | `std::atomic` |
|
||||
| Header-only mega-templates | separate declaration/definition where compile time matters |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
---
|
||||
name: csharp
|
||||
description: "Language-specific super-code guidelines for csharp."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# C#: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [LINQ & Collections](#linq)
|
||||
2. [Null Handling](#nulls)
|
||||
3. [Async/Await](#async)
|
||||
4. [Records & Pattern Matching](#records)
|
||||
5. [Error Handling](#errors)
|
||||
6. [Resource Management](#resources)
|
||||
7. [Anti-patterns specific to C#](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. LINQ & Collections {#linq}
|
||||
|
||||
```csharp
|
||||
// ❌ Imperative accumulation
|
||||
var result = new List<string>();
|
||||
foreach (var item in items) {
|
||||
if (item.IsActive) result.Add(item.Name.ToUpper());
|
||||
}
|
||||
|
||||
// ✅
|
||||
var result = items
|
||||
.Where(i => i.IsActive)
|
||||
.Select(i => i.Name.ToUpper())
|
||||
.ToList();
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ Manual grouping
|
||||
var grouped = new Dictionary<string, List<Item>>();
|
||||
foreach (var item in items) {
|
||||
if (!grouped.ContainsKey(item.Category))
|
||||
grouped[item.Category] = new List<Item>();
|
||||
grouped[item.Category].Add(item);
|
||||
}
|
||||
|
||||
// ✅
|
||||
var grouped = items.GroupBy(i => i.Category)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ Checking Any() then First()
|
||||
if (items.Any(i => i.IsValid)) {
|
||||
var first = items.First(i => i.IsValid);
|
||||
}
|
||||
|
||||
// ✅
|
||||
var first = items.FirstOrDefault(i => i.IsValid);
|
||||
if (first is not null) { ... }
|
||||
```
|
||||
|
||||
**Prefer method syntax for chains of 2+ operations. Query syntax is fine for complex joins.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Null Handling {#nulls}
|
||||
|
||||
```csharp
|
||||
// ❌ Nested null checks
|
||||
string city = null;
|
||||
if (user != null && user.Address != null) {
|
||||
city = user.Address.City;
|
||||
}
|
||||
|
||||
// ✅
|
||||
var city = user?.Address?.City;
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ Ternary for null fallback
|
||||
var name = user != null ? user.Name : "Unknown";
|
||||
|
||||
// ✅
|
||||
var name = user?.Name ?? "Unknown";
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ Null check before event invocation
|
||||
if (OnChanged != null) OnChanged(this, args);
|
||||
|
||||
// ✅
|
||||
OnChanged?.Invoke(this, args);
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ Throwing ArgumentNullException manually
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
|
||||
// ✅ (C# 10+)
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
```
|
||||
|
||||
**Enable nullable reference types (`<Nullable>enable</Nullable>`) project-wide.**
|
||||
|
||||
---
|
||||
|
||||
## 3. Async/Await {#async}
|
||||
|
||||
```csharp
|
||||
// ❌ Blocking on async code
|
||||
var result = GetDataAsync().Result; // deadlock risk
|
||||
|
||||
// ✅
|
||||
var result = await GetDataAsync();
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ async void (exceptions are unobservable)
|
||||
async void OnButtonClick() { await DoWork(); }
|
||||
|
||||
// ✅ — async Task; only async void for event handlers that truly require it
|
||||
async Task OnButtonClick() { await DoWork(); }
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ Sequential awaits for independent work
|
||||
var a = await FetchA();
|
||||
var b = await FetchB();
|
||||
|
||||
// ✅
|
||||
var (a, b) = (await Task.WhenAll(FetchA(), FetchB())) switch
|
||||
{
|
||||
var r => (r[0], r[1])
|
||||
};
|
||||
// or cleaner with ValueTuple:
|
||||
var taskA = FetchA();
|
||||
var taskB = FetchB();
|
||||
var a = await taskA;
|
||||
var b = await taskB;
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ Wrapping synchronous code in Task.Run inside a library
|
||||
public Task<int> GetValue() => Task.Run(() => ComputeSync());
|
||||
|
||||
// ✅ — let the caller decide; expose sync method
|
||||
public int GetValue() => ComputeSync();
|
||||
```
|
||||
|
||||
**Add `ConfigureAwait(false)` in library code. Omit in app/UI code.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Records & Pattern Matching {#records}
|
||||
|
||||
```csharp
|
||||
// ❌ Manual equality, ToString, Deconstruct for data types
|
||||
class Point {
|
||||
public int X { get; init; }
|
||||
public int Y { get; init; }
|
||||
// + Equals, GetHashCode, ToString...
|
||||
}
|
||||
|
||||
// ✅ (C# 9+)
|
||||
record Point(int X, int Y);
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ if-else chain for type checking
|
||||
if (shape is Circle) {
|
||||
var c = (Circle)shape;
|
||||
return c.Radius * c.Radius * Math.PI;
|
||||
} else if (shape is Rectangle) { ... }
|
||||
|
||||
// ✅
|
||||
return shape switch {
|
||||
Circle { Radius: var r } => r * r * Math.PI,
|
||||
Rectangle { Width: var w, Height: var h } => w * h,
|
||||
_ => throw new ArgumentException($"Unknown shape: {shape}")
|
||||
};
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ Range checking with &&
|
||||
if (score >= 0 && score <= 100) { ... }
|
||||
|
||||
// ✅ (C# 9+)
|
||||
if (score is >= 0 and <= 100) { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Error Handling {#errors}
|
||||
|
||||
```csharp
|
||||
// ❌ Catching Exception to log and swallow
|
||||
try { Process(); }
|
||||
catch (Exception ex) { logger.LogError(ex, "error"); }
|
||||
|
||||
// ✅ — catch specific, rethrow if you can't handle
|
||||
try { Process(); }
|
||||
catch (HttpRequestException ex) {
|
||||
throw new ServiceException("upstream failure", ex);
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ throw ex (resets stack trace)
|
||||
catch (Exception ex) { throw ex; }
|
||||
|
||||
// ✅
|
||||
catch (Exception ex) { throw; } // preserves stack trace
|
||||
// or wrap: throw new AppException("context", ex);
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ Exceptions for flow control
|
||||
try { return dict[key]; }
|
||||
catch (KeyNotFoundException) { return defaultValue; }
|
||||
|
||||
// ✅
|
||||
return dict.TryGetValue(key, out var value) ? value : defaultValue;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Resource Management {#resources}
|
||||
|
||||
```csharp
|
||||
// ❌ Manual Dispose
|
||||
var conn = new SqlConnection(cs);
|
||||
conn.Open();
|
||||
// ... use conn ...
|
||||
conn.Dispose(); // missed on exception
|
||||
|
||||
// ✅
|
||||
using var conn = new SqlConnection(cs);
|
||||
conn.Open();
|
||||
// disposed at end of scope
|
||||
```
|
||||
|
||||
```csharp
|
||||
// ❌ Verbose using block
|
||||
using (var reader = new StreamReader(path)) {
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
// ✅ (C# 8+)
|
||||
using var reader = new StreamReader(path);
|
||||
return reader.ReadToEnd();
|
||||
// or just: return File.ReadAllText(path);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to C# {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `string.Format("{0}", x)` | `$"{x}"` string interpolation |
|
||||
| `List<T>` as public API return | `IReadOnlyList<T>` or `IEnumerable<T>` |
|
||||
| `async void` | `async Task` |
|
||||
| `.Result` / `.Wait()` on Task | `await` |
|
||||
| `throw ex` | `throw` (preserves stack trace) |
|
||||
| Manual `IEquatable` on data types | `record` |
|
||||
| `object` parameters | generics with constraints |
|
||||
| `DateTime.Now` | `DateTime.UtcNow` or `DateTimeOffset` |
|
||||
| Mutable public fields | properties with `{ get; set; }` or `{ get; init; }` |
|
||||
| `catch (Exception) { }` (swallow all) | catch specific exceptions, rethrow unknown |
|
||||
| `IDisposable` without `using` | `using` declaration |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
---
|
||||
name: dart
|
||||
description: "Language-specific super-code guidelines for dart."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# Dart: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Null Safety](#nulls)
|
||||
2. [Collections & Iteration](#collections)
|
||||
3. [Classes & Records](#classes)
|
||||
4. [Async/Await & Streams](#async)
|
||||
5. [Error Handling](#errors)
|
||||
6. [Flutter-Specific Patterns](#flutter)
|
||||
7. [Anti-patterns specific to Dart](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Null Safety {#nulls}
|
||||
|
||||
```dart
|
||||
// ❌ Manual null check
|
||||
String display;
|
||||
if (user.name != null) {
|
||||
display = user.name!;
|
||||
} else {
|
||||
display = 'Unknown';
|
||||
}
|
||||
|
||||
// ✅
|
||||
final display = user.name ?? 'Unknown';
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ Nested null checks
|
||||
if (user != null && user.address != null && user.address!.city != null) {
|
||||
print(user.address!.city!);
|
||||
}
|
||||
|
||||
// ✅
|
||||
final city = user?.address?.city;
|
||||
if (city != null) print(city);
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ Late field when nullable is correct
|
||||
late String name; // crashes if accessed before assignment
|
||||
|
||||
// ✅ — use late only when you guarantee initialization before access
|
||||
String? name; // honestly nullable
|
||||
// late is fine for: late final _controller = TextEditingController();
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ Bang operator (!) everywhere
|
||||
final name = user.name!;
|
||||
final city = user.address!.city!;
|
||||
|
||||
// ✅ — promote through null checks
|
||||
final name = user.name;
|
||||
if (name == null) return;
|
||||
// name is now non-null (promoted)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Collections & Iteration {#collections}
|
||||
|
||||
```dart
|
||||
// ❌ Imperative accumulation
|
||||
final result = <String>[];
|
||||
for (final item in items) {
|
||||
if (item.isActive) result.add(item.name.toUpperCase());
|
||||
}
|
||||
|
||||
// ✅
|
||||
final result = items
|
||||
.where((i) => i.isActive)
|
||||
.map((i) => i.name.toUpperCase())
|
||||
.toList();
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ Manual map construction
|
||||
final map = <String, List<Item>>{};
|
||||
for (final item in items) {
|
||||
map.putIfAbsent(item.category, () => []).add(item);
|
||||
}
|
||||
|
||||
// ✅ (using collection-if/for in a different way — but groupBy isn't built-in)
|
||||
// The loop above is actually idiomatic Dart. Use package:collection for groupBy:
|
||||
import 'package:collection/collection.dart';
|
||||
final map = groupBy(items, (Item i) => i.category);
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ Building list with add() calls
|
||||
final widgets = <Widget>[];
|
||||
widgets.add(Header());
|
||||
if (showSubtitle) widgets.add(Subtitle());
|
||||
widgets.add(Body());
|
||||
|
||||
// ✅ — collection-if
|
||||
final widgets = [
|
||||
Header(),
|
||||
if (showSubtitle) Subtitle(),
|
||||
Body(),
|
||||
];
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ Spreading manually
|
||||
final all = <int>[];
|
||||
all.addAll(listA);
|
||||
all.addAll(listB);
|
||||
|
||||
// ✅
|
||||
final all = [...listA, ...listB];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Classes & Records {#classes}
|
||||
|
||||
```dart
|
||||
// ❌ Manual data class boilerplate
|
||||
class Point {
|
||||
final int x, y;
|
||||
const Point(this.x, this.y);
|
||||
@override bool operator ==(Object other) => ...
|
||||
@override int get hashCode => ...
|
||||
@override String toString() => 'Point($x, $y)';
|
||||
}
|
||||
|
||||
// ✅ (Dart 3.0+)
|
||||
typedef Point = ({int x, int y});
|
||||
// or for named class semantics:
|
||||
class Point {
|
||||
final int x, y;
|
||||
const Point(this.x, this.y);
|
||||
}
|
||||
// Use package:equatable or Dart records for equality
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ Verbose constructor
|
||||
class User {
|
||||
final String name;
|
||||
final int age;
|
||||
User(String name, int age) : name = name, age = age;
|
||||
}
|
||||
|
||||
// ✅ — initializing formals
|
||||
class User {
|
||||
final String name;
|
||||
final int age;
|
||||
const User(this.name, this.age);
|
||||
}
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ Mutable fields on an immutable object
|
||||
class Config {
|
||||
String host;
|
||||
int port;
|
||||
Config(this.host, this.port);
|
||||
}
|
||||
|
||||
// ✅
|
||||
class Config {
|
||||
final String host;
|
||||
final int port;
|
||||
const Config(this.host, this.port);
|
||||
}
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ Switch on type with if-else chain
|
||||
if (shape is Circle) {
|
||||
return (shape as Circle).radius * pi;
|
||||
} else if (shape is Rectangle) { ... }
|
||||
|
||||
// ✅ (Dart 3.0+)
|
||||
return switch (shape) {
|
||||
Circle(:final radius) => radius * radius * pi,
|
||||
Rectangle(:final width, :final height) => width * height,
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Async/Await & Streams {#async}
|
||||
|
||||
```dart
|
||||
// ❌ .then() chains
|
||||
fetchUser()
|
||||
.then((user) => fetchPosts(user))
|
||||
.then((posts) => display(posts))
|
||||
.catchError((e) => log(e));
|
||||
|
||||
// ✅
|
||||
try {
|
||||
final user = await fetchUser();
|
||||
final posts = await fetchPosts(user);
|
||||
display(posts);
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ Sequential await for independent work
|
||||
final a = await fetchA();
|
||||
final b = await fetchB();
|
||||
|
||||
// ✅
|
||||
final results = await Future.wait([fetchA(), fetchB()]);
|
||||
// or with typed destructuring:
|
||||
final (a, b) = await (fetchA(), fetchB()).wait; // Dart 3.0+ record
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ StreamBuilder doing too much in build
|
||||
StreamBuilder(
|
||||
stream: stream,
|
||||
builder: (ctx, snap) {
|
||||
if (snap.hasError) return Error();
|
||||
if (!snap.hasData) return Loading();
|
||||
final data = snap.data!;
|
||||
// 50 lines of widget tree...
|
||||
},
|
||||
)
|
||||
|
||||
// ✅ — extract widget, or use listen + setState for simple cases
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Error Handling {#errors}
|
||||
|
||||
```dart
|
||||
// ❌ Catching Exception (too broad)
|
||||
try { process(); }
|
||||
on Exception catch (e) { print(e); }
|
||||
|
||||
// ✅ — catch specific types
|
||||
try {
|
||||
process();
|
||||
} on FormatException catch (e) {
|
||||
throw AppException('Invalid format', cause: e);
|
||||
} on HttpException catch (e) {
|
||||
throw AppException('Network error', cause: e);
|
||||
}
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ Returning null for errors
|
||||
Future<User?> fetchUser() async {
|
||||
try { return await api.getUser(); }
|
||||
catch (_) { return null; } // caller doesn't know why
|
||||
}
|
||||
|
||||
// ✅ — let exceptions propagate, or use sealed Result type
|
||||
sealed class Result<T> {}
|
||||
class Success<T> extends Result<T> { final T value; Success(this.value); }
|
||||
class Failure<T> extends Result<T> { final Object error; Failure(this.error); }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Flutter-Specific Patterns {#flutter}
|
||||
|
||||
```dart
|
||||
// ❌ Rebuilding entire tree on state change
|
||||
setState(() {
|
||||
// changes a single value, but the build() method builds 200 widgets
|
||||
});
|
||||
|
||||
// ✅ — extract subtrees into separate widgets or use ValueListenableBuilder
|
||||
ValueListenableBuilder<int>(
|
||||
valueListenable: counter,
|
||||
builder: (_, value, __) => Text('$value'),
|
||||
)
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ const-able widget without const
|
||||
Container(color: Colors.blue)
|
||||
|
||||
// ✅
|
||||
const ColoredBox(color: Colors.blue)
|
||||
// Mark constructors const when possible; use `const` keyword at call site
|
||||
```
|
||||
|
||||
```dart
|
||||
// ❌ Navigator.push with MaterialPageRoute everywhere
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_) => DetailPage()));
|
||||
|
||||
// ✅ — named routes or GoRouter
|
||||
context.go('/detail');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to Dart {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `!` (bang) operator liberally | null checks and promotion |
|
||||
| `dynamic` everywhere | proper types |
|
||||
| `as` cast without check | pattern matching or `is` check |
|
||||
| Mutable fields on value objects | `final` fields |
|
||||
| `print()` for logging | `package:logging` or structured logger |
|
||||
| Manual `==`/`hashCode` | records, equatable, or code generation |
|
||||
| `setState` for complex state | Riverpod / Bloc / Provider |
|
||||
| Deep widget nesting | extract widgets into classes |
|
||||
| String-based routing | typed routing (GoRouter) |
|
||||
| `late` as escape hatch | nullable types or proper initialization |
|
||||
| `Future.delayed` for debounce | `Timer` or proper debounce utility |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
---
|
||||
name: elixir
|
||||
description: "Language-specific super-code guidelines for elixir."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# Elixir / Erlang: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Pattern Matching & Guards](#patterns)
|
||||
2. [Pipe Operator & Transforms](#pipes)
|
||||
3. [Processes & OTP](#otp)
|
||||
4. [Error Handling](#errors)
|
||||
5. [Collections & Enum](#collections)
|
||||
6. [Structs & Protocols](#structs)
|
||||
7. [Anti-patterns specific to Elixir/Erlang](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Pattern Matching & Guards {#patterns}
|
||||
|
||||
```elixir
|
||||
# ❌ Extracting with Map.get then checking
|
||||
value = Map.get(map, :key)
|
||||
if value != nil do
|
||||
process(value)
|
||||
end
|
||||
|
||||
# ✅ — pattern match directly
|
||||
case map do
|
||||
%{key: value} -> process(value)
|
||||
_ -> :noop
|
||||
end
|
||||
# or with if:
|
||||
if value = map[:key], do: process(value)
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Nested case for multiple conditions
|
||||
case fetch_user(id) do
|
||||
{:ok, user} ->
|
||||
case validate(user) do
|
||||
{:ok, valid_user} -> save(valid_user)
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
|
||||
# ✅ — with clause
|
||||
with {:ok, user} <- fetch_user(id),
|
||||
{:ok, valid_user} <- validate(user) do
|
||||
save(valid_user)
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ if/else for known shapes
|
||||
def area(shape) do
|
||||
if shape.type == :circle do
|
||||
:math.pi() * shape.radius * shape.radius
|
||||
else
|
||||
shape.width * shape.height
|
||||
end
|
||||
end
|
||||
|
||||
# ✅ — multi-clause function with pattern match
|
||||
def area(%{type: :circle, radius: r}), do: :math.pi() * r * r
|
||||
def area(%{type: :rect, width: w, height: h}), do: w * h
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Checking type at runtime
|
||||
def process(x) do
|
||||
if is_integer(x) and x > 0 do
|
||||
x * 2
|
||||
end
|
||||
end
|
||||
|
||||
# ✅ — guard clause
|
||||
def process(x) when is_integer(x) and x > 0, do: x * 2
|
||||
def process(_), do: {:error, :invalid_input}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Pipe Operator & Transforms {#pipes}
|
||||
|
||||
```elixir
|
||||
# ❌ Nested function calls
|
||||
String.trim(String.downcase(String.replace(input, ~r/\s+/, " ")))
|
||||
|
||||
# ✅
|
||||
input
|
||||
|> String.replace(~r/\s+/, " ")
|
||||
|> String.downcase()
|
||||
|> String.trim()
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Pipe into anonymous function awkwardly
|
||||
data
|
||||
|> (fn x -> x * 2 end).()
|
||||
|
||||
# ✅ — use then/1 or named function
|
||||
data
|
||||
|> then(&(&1 * 2))
|
||||
# or better: extract a named function
|
||||
data |> double()
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Single-step pipe (no gain in readability)
|
||||
result = list |> Enum.count()
|
||||
|
||||
# ✅ — direct call for single operation
|
||||
result = Enum.count(list)
|
||||
```
|
||||
|
||||
**Pipe when 2+ transforms. Direct call for single operation. First arg flows through pipe.**
|
||||
|
||||
---
|
||||
|
||||
## 3. Processes & OTP {#otp}
|
||||
|
||||
```elixir
|
||||
# ❌ Raw spawn for stateful process
|
||||
pid = spawn(fn -> loop(%{count: 0}) end)
|
||||
send(pid, {:increment})
|
||||
|
||||
# ✅ — GenServer for stateful processes
|
||||
defmodule Counter do
|
||||
use GenServer
|
||||
|
||||
def start_link(init \\ 0), do: GenServer.start_link(__MODULE__, init)
|
||||
def increment(pid), do: GenServer.call(pid, :increment)
|
||||
|
||||
@impl true
|
||||
def init(count), do: {:ok, count}
|
||||
|
||||
@impl true
|
||||
def handle_call(:increment, _from, count), do: {:reply, count + 1, count + 1}
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Spawning without linking (orphan process on crash)
|
||||
spawn(fn -> do_work() end)
|
||||
|
||||
# ✅ — Task for fire-and-forget with supervision
|
||||
Task.start(fn -> do_work() end)
|
||||
# or for awaitable result:
|
||||
task = Task.async(fn -> do_work() end)
|
||||
result = Task.await(task)
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Manual process registry
|
||||
Process.register(self(), :my_worker)
|
||||
|
||||
# ✅ — use Registry or named GenServer
|
||||
{:ok, _} = Registry.start_link(keys: :unique, name: MyRegistry)
|
||||
GenServer.start_link(Worker, arg, name: {:via, Registry, {MyRegistry, :my_worker}})
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ try/catch in GenServer (breaks supervision)
|
||||
def handle_call(:work, _from, state) do
|
||||
try do
|
||||
result = risky_operation()
|
||||
{:reply, result, state}
|
||||
catch
|
||||
_ -> {:reply, :error, state}
|
||||
end
|
||||
end
|
||||
|
||||
# ✅ — let it crash; supervisor restarts
|
||||
def handle_call(:work, _from, state) do
|
||||
result = risky_operation()
|
||||
{:reply, result, state}
|
||||
end
|
||||
```
|
||||
|
||||
**"Let it crash" — supervisors handle recovery. Don't defensively catch inside GenServers.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Error Handling {#errors}
|
||||
|
||||
```elixir
|
||||
# ❌ Raising for expected failures
|
||||
def find_user(id) do
|
||||
case Repo.get(User, id) do
|
||||
nil -> raise "User not found"
|
||||
user -> user
|
||||
end
|
||||
end
|
||||
|
||||
# ✅ — tagged tuples for expected outcomes
|
||||
def find_user(id) do
|
||||
case Repo.get(User, id) do
|
||||
nil -> {:error, :not_found}
|
||||
user -> {:ok, user}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Ignoring error tuple
|
||||
{:ok, result} = might_fail() # crashes on {:error, _}
|
||||
|
||||
# ✅ — handle both cases
|
||||
case might_fail() do
|
||||
{:ok, result} -> process(result)
|
||||
{:error, reason} -> Logger.error("Failed: #{inspect(reason)}")
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ String errors
|
||||
{:error, "something went wrong"}
|
||||
|
||||
# ✅ — atom or struct errors (matchable, cheap)
|
||||
{:error, :timeout}
|
||||
{:error, %ValidationError{field: :email, reason: :invalid_format}}
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Deep nesting of ok/error checks
|
||||
case step1() do
|
||||
{:ok, a} ->
|
||||
case step2(a) do
|
||||
{:ok, b} ->
|
||||
case step3(b) do
|
||||
{:ok, c} -> {:ok, c}
|
||||
error -> error
|
||||
end
|
||||
error -> error
|
||||
end
|
||||
error -> error
|
||||
end
|
||||
|
||||
# ✅
|
||||
with {:ok, a} <- step1(),
|
||||
{:ok, b} <- step2(a),
|
||||
{:ok, c} <- step3(b) do
|
||||
{:ok, c}
|
||||
else
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Collections & Enum {#collections}
|
||||
|
||||
```elixir
|
||||
# ❌ Multiple passes when one suffices
|
||||
items
|
||||
|> Enum.filter(&(&1.active))
|
||||
|> Enum.map(&(&1.name))
|
||||
|
||||
# ✅ — for comprehension when filter + transform
|
||||
for %{active: true, name: name} <- items, do: name
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Enum.count for empty check (traverses whole list)
|
||||
if Enum.count(list) == 0, do: :empty
|
||||
|
||||
# ✅
|
||||
if Enum.empty?(list), do: :empty
|
||||
# or pattern match:
|
||||
case list do
|
||||
[] -> :empty
|
||||
_ -> :has_items
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Building map with Enum.reduce when Map.new works
|
||||
Enum.reduce(users, %{}, fn user, acc -> Map.put(acc, user.id, user) end)
|
||||
|
||||
# ✅
|
||||
Map.new(users, &{&1.id, &1})
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Enum on large dataset (eager — builds intermediate lists)
|
||||
huge_list
|
||||
|> Enum.map(&transform/1)
|
||||
|> Enum.filter(&valid?/1)
|
||||
|> Enum.take(10)
|
||||
|
||||
# ✅ — Stream for lazy evaluation
|
||||
huge_list
|
||||
|> Stream.map(&transform/1)
|
||||
|> Stream.filter(&valid?/1)
|
||||
|> Enum.take(10)
|
||||
```
|
||||
|
||||
**Use `Stream` when chaining transforms on large/infinite collections. `Enum` for small or final step.**
|
||||
|
||||
---
|
||||
|
||||
## 6. Structs & Protocols {#structs}
|
||||
|
||||
```elixir
|
||||
# ❌ Plain map for domain entities
|
||||
user = %{name: "Alice", email: "a@b.com", age: 30}
|
||||
# typo in key goes unnoticed: user.emaail
|
||||
|
||||
# ✅ — struct enforces keys
|
||||
defmodule User do
|
||||
@enforce_keys [:name, :email]
|
||||
defstruct [:name, :email, age: 0]
|
||||
end
|
||||
user = %User{name: "Alice", email: "a@b.com"}
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Protocol with only one implementation (over-abstraction)
|
||||
defprotocol Renderable do
|
||||
def render(data)
|
||||
end
|
||||
defimpl Renderable, for: HtmlPage do ... end
|
||||
|
||||
# ✅ — just a function until you need polymorphism
|
||||
def render(%HtmlPage{} = page), do: ...
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Updating nested struct manually
|
||||
updated = %{user | address: %{user.address | city: "NYC"}}
|
||||
|
||||
# ✅
|
||||
updated = put_in(user.address.city, "NYC")
|
||||
# or Kernel.update_in/3 for transforms
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to Elixir/Erlang {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `spawn` without link/monitor | `Task.start_link` or `GenServer` |
|
||||
| `try/catch` inside GenServer | let it crash; supervisor restarts |
|
||||
| String error reasons | atom or struct errors |
|
||||
| `Enum.count(x) == 0` | `Enum.empty?(x)` or `match?([], x)` |
|
||||
| Mutable-style accumulator | `Enum.reduce` / recursion |
|
||||
| `if/else` chain on data shape | multi-clause function + pattern match |
|
||||
| Nested `case` for ok/error | `with` expression |
|
||||
| `IO.inspect` left in prod | `Logger` with levels |
|
||||
| Single-step pipe | direct function call |
|
||||
| `Enum` on huge/infinite data | `Stream` |
|
||||
| Raw PID passing | named processes / Registry |
|
||||
| Boolean returns for success/fail | `{:ok, val}` / `{:error, reason}` tuples |
|
||||
| `length(list) > 0` (O(n)) | pattern match `[_ | _]` |
|
||||
| Shared mutable state via ETS without wrapper | GenServer or Agent as access layer |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
---
|
||||
name: go
|
||||
description: "Language-specific super-code guidelines for go."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# Go: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Error Handling](#errors)
|
||||
2. [Slices & Maps](#slices)
|
||||
3. [Goroutines & Channels](#concurrency)
|
||||
4. [Structs & Interfaces](#structs)
|
||||
5. [Functions & Closures](#functions)
|
||||
6. [Anti-patterns specific to Go](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Error Handling {#errors}
|
||||
|
||||
```go
|
||||
// ❌ Ignoring errors
|
||||
result, _ := os.Open(path)
|
||||
|
||||
// ✅ — always handle; only use _ when error is provably irrelevant
|
||||
result, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open %s: %w", path, err)
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// ❌ Redundant error variable
|
||||
err := doA()
|
||||
if err != nil { return err }
|
||||
err = doB()
|
||||
if err != nil { return err }
|
||||
|
||||
// ✅ — each :=/: is fine; this is idiomatic Go. Don't try to "fix" it.
|
||||
// What you CAN simplify: collapsing to one-liners where the if body is a single return
|
||||
if err := doA(); err != nil { return err }
|
||||
if err := doB(); err != nil { return err }
|
||||
```
|
||||
|
||||
```go
|
||||
// ❌ Custom error type with no added value
|
||||
type MyError struct{ msg string }
|
||||
func (e MyError) Error() string { return e.msg }
|
||||
|
||||
// ✅ — use errors.New or fmt.Errorf unless callers need to inspect type
|
||||
var ErrNotFound = errors.New("not found")
|
||||
return fmt.Errorf("lookup %q: %w", key, ErrNotFound)
|
||||
```
|
||||
|
||||
**Wrap errors with `%w` (not `%v`) so callers can use `errors.Is` / `errors.As`.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Slices & Maps {#slices}
|
||||
|
||||
```go
|
||||
// ❌ Growing a slice without pre-allocation when size is known
|
||||
var result []string
|
||||
for _, item := range items {
|
||||
result = append(result, item.Name)
|
||||
}
|
||||
|
||||
// ✅
|
||||
result := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
result = append(result, item.Name)
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// ❌ Manual existence check before map write
|
||||
if _, ok := m[key]; !ok {
|
||||
m[key] = []string{}
|
||||
}
|
||||
m[key] = append(m[key], value)
|
||||
|
||||
// ✅ — append to nil slice is valid Go
|
||||
m[key] = append(m[key], value)
|
||||
```
|
||||
|
||||
```go
|
||||
// ❌ Copying a map by assignment (copies reference)
|
||||
copy := original
|
||||
|
||||
// ✅
|
||||
copy := make(map[K]V, len(original))
|
||||
for k, v := range original { copy[k] = v }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Goroutines & Channels {#concurrency}
|
||||
|
||||
```go
|
||||
// ❌ Fire-and-forget goroutine with no lifecycle
|
||||
go doWork()
|
||||
|
||||
// ✅ — use errgroup or WaitGroup to track completion
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
doWork()
|
||||
}()
|
||||
wg.Wait()
|
||||
```
|
||||
|
||||
```go
|
||||
// ❌ Unbuffered channel causing unnecessary goroutine block
|
||||
ch := make(chan Result)
|
||||
go func() { ch <- compute() }()
|
||||
result := <-ch
|
||||
|
||||
// ✅ — for single-result, buffered channel avoids goroutine leak if receiver exits early
|
||||
ch := make(chan Result, 1)
|
||||
go func() { ch <- compute() }()
|
||||
result := <-ch
|
||||
```
|
||||
|
||||
```go
|
||||
// ❌ select with a busy-wait default
|
||||
for {
|
||||
select {
|
||||
case v := <-ch:
|
||||
process(v)
|
||||
default:
|
||||
// spin
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ — blocking select unless you genuinely need non-blocking
|
||||
for v := range ch {
|
||||
process(v)
|
||||
}
|
||||
```
|
||||
|
||||
**Use `golang.org/x/sync/errgroup` for fan-out with error collection.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Structs & Interfaces {#structs}
|
||||
|
||||
```go
|
||||
// ❌ Large interface
|
||||
type Storage interface {
|
||||
Get(key string) ([]byte, error)
|
||||
Set(key string, val []byte) error
|
||||
Delete(key string) error
|
||||
List(prefix string) ([]string, error)
|
||||
// ... 10 more methods
|
||||
}
|
||||
|
||||
// ✅ — small, composable interfaces
|
||||
type Getter interface { Get(key string) ([]byte, error) }
|
||||
type Setter interface { Set(key string, val []byte) error }
|
||||
type Storage interface { Getter; Setter }
|
||||
```
|
||||
|
||||
```go
|
||||
// ❌ Returning concrete struct from constructor (ties callers to implementation)
|
||||
func NewStore() *RedisStore { ... }
|
||||
|
||||
// ✅ — return interface when you have or anticipate multiple implementations
|
||||
func NewStore() Storage { return &RedisStore{...} }
|
||||
```
|
||||
|
||||
```go
|
||||
// ❌ Pointer receiver for tiny value types
|
||||
func (p *Point) X() float64 { return p.x }
|
||||
|
||||
// ✅ — value receiver for small immutable types
|
||||
func (p Point) X() float64 { return p.x }
|
||||
```
|
||||
|
||||
**Rule: pointer receiver when method mutates state OR struct is large (>3 fields of non-trivial size). Value receiver otherwise.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Functions & Closures {#functions}
|
||||
|
||||
```go
|
||||
// ❌ Named return values used just to avoid a variable declaration
|
||||
func divide(a, b float64) (result float64, err error) {
|
||||
result = a / b
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ — named returns are worth it only for deferred mutation or documentation
|
||||
func divide(a, b float64) (float64, error) {
|
||||
if b == 0 { return 0, errors.New("division by zero") }
|
||||
return a / b, nil
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// ❌ Closure capturing loop variable (classic Go bug, fixed in Go 1.22+)
|
||||
// Pre-1.22: each goroutine captures the same i
|
||||
for i := 0; i < n; i++ {
|
||||
go func() { use(i) }()
|
||||
}
|
||||
|
||||
// ✅ (Go <1.22 — pass as parameter)
|
||||
for i := 0; i < n; i++ {
|
||||
go func(i int) { use(i) }(i)
|
||||
}
|
||||
// Go 1.22+: loop variable scoped per iteration, so the original is safe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Anti-patterns specific to Go {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `if err != nil { return err }` repeated 5+ times | acceptable — it's idiomatic Go |
|
||||
| `panic` for expected errors | `return err` |
|
||||
| `init()` with side effects | explicit initialization in `main` or constructors |
|
||||
| `interface{}` / `any` without generics | use generics (Go 1.18+) or typed interfaces |
|
||||
| Mutex field not adjacent to the data it protects | put `mu` directly above the field it guards |
|
||||
| Channel of channels | usually a sign of over-engineering; redesign |
|
||||
| `time.Sleep` in tests | use `testing` hooks or channels for synchronization |
|
||||
| Exported types with unexported fields (when fields are the whole point) | `record`-style structs with all-exported fields |
|
||||
| `log.Fatal` outside `main` | return errors up the stack |
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
---
|
||||
name: java
|
||||
description: "Language-specific super-code guidelines for java."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# Java: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Streams & Collections](#streams)
|
||||
2. [Optional](#optional)
|
||||
3. [Records & Data Classes](#records)
|
||||
4. [Switch Expressions](#switch)
|
||||
5. [Concurrency](#concurrency)
|
||||
6. [Error Handling](#errors)
|
||||
7. [Anti-patterns specific to Java](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Streams & Collections {#streams}
|
||||
|
||||
```java
|
||||
// ❌ Imperative accumulation
|
||||
List<String> result = new ArrayList<>();
|
||||
for (Item item : items) {
|
||||
if (item.isActive()) result.add(item.getName().toUpperCase());
|
||||
}
|
||||
|
||||
// ✅
|
||||
List<String> result = items.stream()
|
||||
.filter(Item::isActive)
|
||||
.map(item -> item.getName().toUpperCase())
|
||||
.toList(); // Java 16+; use .collect(Collectors.toList()) before
|
||||
```
|
||||
|
||||
```java
|
||||
// ❌ Manual grouping
|
||||
Map<String, List<Item>> grouped = new HashMap<>();
|
||||
for (Item item : items) {
|
||||
grouped.computeIfAbsent(item.getCategory(), k -> new ArrayList<>()).add(item);
|
||||
}
|
||||
|
||||
// ✅
|
||||
Map<String, List<Item>> grouped = items.stream()
|
||||
.collect(Collectors.groupingBy(Item::getCategory));
|
||||
```
|
||||
|
||||
```java
|
||||
// ❌ Manual sum
|
||||
int total = 0;
|
||||
for (Order o : orders) total += o.getAmount();
|
||||
|
||||
// ✅
|
||||
int total = orders.stream().mapToInt(Order::getAmount).sum();
|
||||
```
|
||||
|
||||
**Prefer method references (`Item::isActive`) over equivalent lambdas (`item -> item.isActive()`).**
|
||||
|
||||
---
|
||||
|
||||
## 2. Optional {#optional}
|
||||
|
||||
```java
|
||||
// ❌ Null check chain
|
||||
String city = null;
|
||||
if (user != null && user.getAddress() != null) {
|
||||
city = user.getAddress().getCity();
|
||||
}
|
||||
|
||||
// ✅
|
||||
String city = Optional.ofNullable(user)
|
||||
.map(User::getAddress)
|
||||
.map(Address::getCity)
|
||||
.orElse(null);
|
||||
```
|
||||
|
||||
```java
|
||||
// ❌ Optional.get() without isPresent()
|
||||
String name = optional.get(); // throws if empty
|
||||
|
||||
// ✅
|
||||
String name = optional.orElse("default");
|
||||
// or: optional.orElseThrow(() -> new IllegalStateException("name required"));
|
||||
```
|
||||
|
||||
```java
|
||||
// ❌ Optional as a field or parameter (anti-pattern)
|
||||
class User { private Optional<String> nickname; }
|
||||
|
||||
// ✅ — Optional is for return types only
|
||||
class User { private String nickname; } // nullable field
|
||||
public Optional<String> getNickname() { return Optional.ofNullable(nickname); }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Records & Data Classes {#records}
|
||||
|
||||
```java
|
||||
// ❌ Manual POJO
|
||||
class Point {
|
||||
private final int x, y;
|
||||
public Point(int x, int y) { this.x = x; this.y = y; }
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
// + equals, hashCode, toString...
|
||||
}
|
||||
|
||||
// ✅ (Java 16+)
|
||||
record Point(int x, int y) {}
|
||||
```
|
||||
|
||||
```java
|
||||
// ❌ Builder pattern for a 2-field object
|
||||
User user = new User.Builder().name("Alice").age(30).build();
|
||||
|
||||
// ✅ — use record or constructor directly for small objects
|
||||
record User(String name, int age) {}
|
||||
var user = new User("Alice", 30);
|
||||
```
|
||||
|
||||
**Use `record` for any immutable data carrier. Keep builders only for objects with many optional fields.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Switch Expressions {#switch}
|
||||
|
||||
```java
|
||||
// ❌ Switch statement with fall-through and break
|
||||
String label;
|
||||
switch (status) {
|
||||
case ACTIVE: label = "Active"; break;
|
||||
case INACTIVE: label = "Inactive"; break;
|
||||
default: label = "Unknown";
|
||||
}
|
||||
|
||||
// ✅ (Java 14+)
|
||||
String label = switch (status) {
|
||||
case ACTIVE -> "Active";
|
||||
case INACTIVE -> "Inactive";
|
||||
default -> "Unknown";
|
||||
};
|
||||
```
|
||||
|
||||
```java
|
||||
// ❌ instanceof + cast
|
||||
if (shape instanceof Circle) {
|
||||
Circle c = (Circle) shape;
|
||||
return c.radius() * c.radius() * Math.PI;
|
||||
}
|
||||
|
||||
// ✅ Pattern matching (Java 16+)
|
||||
if (shape instanceof Circle c) {
|
||||
return c.radius() * c.radius() * Math.PI;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Concurrency {#concurrency}
|
||||
|
||||
```java
|
||||
// ❌ Raw Thread creation
|
||||
Thread t = new Thread(() -> doWork());
|
||||
t.start();
|
||||
|
||||
// ✅
|
||||
ExecutorService exec = Executors.newVirtualThreadPerTaskExecutor(); // Java 21
|
||||
exec.submit(() -> doWork());
|
||||
```
|
||||
|
||||
```java
|
||||
// ❌ synchronized on this for fine-grained state
|
||||
synchronized(this) { counter++; }
|
||||
|
||||
// ✅
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
counter.incrementAndGet();
|
||||
```
|
||||
|
||||
**Prefer `CompletableFuture.allOf()` over blocking `.get()` chains for parallel async work.**
|
||||
|
||||
---
|
||||
|
||||
## 6. Error Handling {#errors}
|
||||
|
||||
```java
|
||||
// ❌ Catching Exception to log and swallow
|
||||
try {
|
||||
risky();
|
||||
} catch (Exception e) {
|
||||
log.error("error", e);
|
||||
}
|
||||
|
||||
// ✅ — rethrow as unchecked if you can't handle it
|
||||
try {
|
||||
risky();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// ❌ Checked exceptions declared on every method
|
||||
public void process() throws IOException, SQLException, ParseException { ... }
|
||||
|
||||
// ✅ — wrap at the boundary; internal methods throw unchecked
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to Java {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `new ArrayList<String>()` (Java 7+) | `new ArrayList<>()` (diamond) |
|
||||
| `"string".equals(variable)` (Yoda) | `Objects.equals(variable, "string")` |
|
||||
| `for (int i = 0; i < list.size(); i++)` | enhanced for or stream |
|
||||
| `StringBuffer` in single-threaded code | `StringBuilder` |
|
||||
| `e.printStackTrace()` | `log.error("msg", e)` |
|
||||
| `null` return for "not found" | `Optional<T>` return type |
|
||||
| Public fields | private + accessor, or `record` |
|
||||
| Mutable `static` fields | avoid; use dependency injection |
|
||||
| `instanceof` + cast without pattern matching | pattern matching (Java 16+) |
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
---
|
||||
name: kotlin
|
||||
description: "Language-specific super-code guidelines for kotlin."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# Kotlin + Compose: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Collections & Data Transformation](#collections)
|
||||
2. [Null Safety](#null-safety)
|
||||
3. [Functions & Lambdas](#functions)
|
||||
4. [Classes & Objects](#classes)
|
||||
5. [Coroutines & Flow](#coroutines)
|
||||
6. [Compose UI](#compose)
|
||||
7. [Anti-patterns specific to Kotlin](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Collections & Data Transformation {#collections}
|
||||
|
||||
**Prefer scope functions and stdlib transforms over imperative loops.**
|
||||
|
||||
```kotlin
|
||||
// ❌ Verbose
|
||||
val result = mutableListOf<String>()
|
||||
for (item in items) {
|
||||
if (item.isActive) {
|
||||
result.add(item.name.uppercase())
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Idiomatic
|
||||
val result = items.filter { it.isActive }.map { it.name.uppercase() }
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ Manual grouping
|
||||
val map = mutableMapOf<String, MutableList<Item>>()
|
||||
for (item in items) {
|
||||
map.getOrPut(item.category) { mutableListOf() }.add(item)
|
||||
}
|
||||
|
||||
// ✅
|
||||
val map = items.groupBy { it.category }
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ Manual fold
|
||||
var total = 0
|
||||
for (order in orders) total += order.amount
|
||||
|
||||
// ✅
|
||||
val total = orders.sumOf { it.amount }
|
||||
```
|
||||
|
||||
**Use `associate`, `associateBy`, `partition`, `flatMap`, `zip` instead of manual equivalents.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Null Safety {#null-safety}
|
||||
|
||||
```kotlin
|
||||
// ❌ Unnecessary null check when Elvis suffices
|
||||
val name: String
|
||||
if (user?.name != null) {
|
||||
name = user.name
|
||||
} else {
|
||||
name = "Guest"
|
||||
}
|
||||
|
||||
// ✅
|
||||
val name = user?.name ?: "Guest"
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ Double null check
|
||||
if (response != null && response.body != null) {
|
||||
process(response.body!!)
|
||||
}
|
||||
|
||||
// ✅
|
||||
response?.body?.let { process(it) }
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ !! without guard
|
||||
val value = nullable!!.doSomething()
|
||||
|
||||
// ✅ Make the non-null contract explicit at the boundary
|
||||
val value = requireNotNull(nullable) { "nullable must be set before calling X" }.doSomething()
|
||||
// Or return early:
|
||||
val n = nullable ?: return
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Functions & Lambdas {#functions}
|
||||
|
||||
```kotlin
|
||||
// ❌ Single-expression function with unnecessary block body
|
||||
fun double(x: Int): Int {
|
||||
return x * 2
|
||||
}
|
||||
|
||||
// ✅
|
||||
fun double(x: Int) = x * 2
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ Lambda capturing unused parameter
|
||||
items.forEach { item -> doSomething() }
|
||||
|
||||
// ✅
|
||||
items.forEach { doSomething() }
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ Redundant with/apply nesting
|
||||
val builder = Builder()
|
||||
builder.setName("x")
|
||||
builder.setAge(1)
|
||||
val result = builder.build()
|
||||
|
||||
// ✅
|
||||
val result = Builder().apply {
|
||||
setName("x")
|
||||
setAge(1)
|
||||
}.build()
|
||||
```
|
||||
|
||||
**Prefer `let`, `run`, `apply`, `also`, `with` over repeated receiver references — but don't nest more than 2 levels deep.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Classes & Objects {#classes}
|
||||
|
||||
```kotlin
|
||||
// ❌ Mutable class for immutable data
|
||||
class Point {
|
||||
var x: Int = 0
|
||||
var y: Int = 0
|
||||
}
|
||||
|
||||
// ✅
|
||||
data class Point(val x: Int, val y: Int)
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ Companion object just to hold a constant
|
||||
class Foo {
|
||||
companion object {
|
||||
val TAG = "Foo"
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ — top-level if only used in this file
|
||||
private const val TAG = "Foo"
|
||||
class Foo
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ Enum with when that has to be updated in two places
|
||||
enum class Status { ACTIVE, INACTIVE }
|
||||
fun label(s: Status) = when(s) { Status.ACTIVE -> "Active"; Status.INACTIVE -> "Inactive" }
|
||||
|
||||
// ✅ — put display logic on the enum itself
|
||||
enum class Status(val label: String) { ACTIVE("Active"), INACTIVE("Inactive") }
|
||||
```
|
||||
|
||||
**Sealed classes/interfaces over enum when variants carry different data.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Coroutines & Flow {#coroutines}
|
||||
|
||||
```kotlin
|
||||
// ❌ Unnecessary async/await pair when result is used immediately
|
||||
val result = async { fetchData() }.await()
|
||||
|
||||
// ✅
|
||||
val result = fetchData() // just suspend fun, no async needed
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ Collecting in a loop
|
||||
while (true) {
|
||||
val value = channel.receive()
|
||||
process(value)
|
||||
}
|
||||
|
||||
// ✅
|
||||
channel.consumeEach { process(it) }
|
||||
// or for Flow:
|
||||
flow.collect { process(it) }
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ StateFlow + manual emit boilerplate
|
||||
private val _state = MutableStateFlow(initial)
|
||||
val state: StateFlow<State> = _state
|
||||
// ... in many places: _state.value = newValue
|
||||
|
||||
// ✅ — use update{} for atomic mutation
|
||||
_state.update { it.copy(field = newValue) }
|
||||
```
|
||||
|
||||
**Don't launch coroutines in constructors or init blocks. Don't use GlobalScope.**
|
||||
|
||||
---
|
||||
|
||||
## 6. Compose UI {#compose}
|
||||
|
||||
```kotlin
|
||||
// ❌ Unnecessary remember for derived state that's cheap to compute
|
||||
val displayName = remember { user.firstName + " " + user.lastName }
|
||||
|
||||
// ✅ — only remember if computation is expensive or involves object creation
|
||||
val displayName = "${user.firstName} ${user.lastName}"
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ Passing entire state object when composable only needs one field
|
||||
@Composable
|
||||
fun UserBadge(user: User) { Text(user.name) }
|
||||
|
||||
// ✅ — pass only what's needed (stability + minimal recomposition)
|
||||
@Composable
|
||||
fun UserBadge(name: String) { Text(name) }
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ Inline click handler lambda (creates new instance each recomposition)
|
||||
Button(onClick = { viewModel.onSave() }) { ... }
|
||||
|
||||
// ✅
|
||||
val onSave = remember { { viewModel.onSave() } }
|
||||
Button(onClick = onSave) { ... }
|
||||
// Or pass it down as a parameter already
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// ❌ Nested Column/Row just to group children
|
||||
Column {
|
||||
Column {
|
||||
Text("a")
|
||||
Text("b")
|
||||
}
|
||||
}
|
||||
|
||||
// ✅
|
||||
Column {
|
||||
Text("a")
|
||||
Text("b")
|
||||
}
|
||||
```
|
||||
|
||||
**Use `LazyColumn`/`LazyRow` for lists of unknown or large size. Never put a `LazyColumn` inside a `Column` with unbounded height.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to Kotlin {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `if (x == true)` | `if (x)` |
|
||||
| `if (x == null) return else x!!` | `val x = x ?: return` |
|
||||
| `listOf().toMutableList()` for a known-size list | `mutableListOf()` |
|
||||
| `when` with a single branch and else | `if/else` |
|
||||
| `.toString()` on a string | remove it |
|
||||
| Explicit `Unit` return type on functions | omit (inferred) |
|
||||
| `object : Runnable { override fun run() { ... } }` | `Runnable { ... }` (SAM) |
|
||||
| `@JvmStatic` in pure Kotlin code | only needed for Java interop |
|
||||
| Wrapping every function in try/catch to log | handle at the boundary, not inside |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
---
|
||||
name: php
|
||||
description: "Language-specific super-code guidelines for php."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# PHP: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Arrays & Collections](#arrays)
|
||||
2. [Type Safety](#types)
|
||||
3. [Error Handling](#errors)
|
||||
4. [String Handling](#strings)
|
||||
5. [OOP & Modern PHP](#oop)
|
||||
6. [Functions & Closures](#functions)
|
||||
7. [Anti-patterns specific to PHP](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Arrays & Collections {#arrays}
|
||||
|
||||
```php
|
||||
// ❌ Manual accumulation
|
||||
$result = [];
|
||||
foreach ($items as $item) {
|
||||
if ($item->isActive()) {
|
||||
$result[] = strtoupper($item->getName());
|
||||
}
|
||||
}
|
||||
|
||||
// ✅
|
||||
$result = array_map(
|
||||
fn($i) => strtoupper($i->getName()),
|
||||
array_filter($items, fn($i) => $i->isActive())
|
||||
);
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ Manual key-value grouping
|
||||
$grouped = [];
|
||||
foreach ($items as $item) {
|
||||
$grouped[$item->getCategory()][] = $item;
|
||||
}
|
||||
|
||||
// ✅ (PHP 8.1+) — or use the loop above; PHP lacks a built-in groupBy
|
||||
// The foreach is actually idiomatic PHP for grouping. No need to force array_* here.
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ Checking isset then accessing
|
||||
if (isset($data['key'])) {
|
||||
$value = $data['key'];
|
||||
} else {
|
||||
$value = 'default';
|
||||
}
|
||||
|
||||
// ✅
|
||||
$value = $data['key'] ?? 'default';
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ array_push for single element
|
||||
array_push($items, $newItem);
|
||||
|
||||
// ✅
|
||||
$items[] = $newItem;
|
||||
```
|
||||
|
||||
**Use `array_map`/`array_filter` for transforms. The `foreach` loop is fine when array functions would be less readable.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Type Safety {#types}
|
||||
|
||||
```php
|
||||
// ❌ No type declarations
|
||||
function process($items) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
// ✅ (PHP 8.0+)
|
||||
function process(array $items): array {
|
||||
return $items;
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ Union type for nullable
|
||||
function find(string $key): string|null { ... }
|
||||
|
||||
// ✅
|
||||
function find(string $key): ?string { ... }
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ Loose comparison
|
||||
if ($value == '0') { ... } // true for 0, '', false, null
|
||||
|
||||
// ✅
|
||||
if ($value === '0') { ... }
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ Type checking with gettype()
|
||||
if (gettype($x) === 'integer') { ... }
|
||||
|
||||
// ✅
|
||||
if (is_int($x)) { ... }
|
||||
// or with union types, avoid checks entirely
|
||||
```
|
||||
|
||||
**Enable `declare(strict_types=1)` at the top of every file.**
|
||||
|
||||
---
|
||||
|
||||
## 3. Error Handling {#errors}
|
||||
|
||||
```php
|
||||
// ❌ Suppressing errors with @
|
||||
$data = @file_get_contents($path);
|
||||
|
||||
// ✅
|
||||
$data = file_get_contents($path);
|
||||
if ($data === false) {
|
||||
throw new RuntimeException("Failed to read: $path");
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ Catching \Exception and swallowing
|
||||
try { process(); }
|
||||
catch (\Exception $e) { /* silence */ }
|
||||
|
||||
// ✅
|
||||
try {
|
||||
process();
|
||||
} catch (SpecificException $e) {
|
||||
$this->logger->error($e->getMessage(), ['exception' => $e]);
|
||||
throw new AppException('Processing failed', previous: $e);
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ Returning mixed types for error indication
|
||||
function divide(int $a, int $b): int|false {
|
||||
if ($b === 0) return false;
|
||||
return intdiv($a, $b);
|
||||
}
|
||||
|
||||
// ✅ — throw exception for exceptional cases
|
||||
function divide(int $a, int $b): int {
|
||||
if ($b === 0) throw new \DivisionByZeroError();
|
||||
return intdiv($a, $b);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. String Handling {#strings}
|
||||
|
||||
```php
|
||||
// ❌ Concatenation for variable interpolation
|
||||
$msg = 'Hello, ' . $name . '! You have ' . $count . ' messages.';
|
||||
|
||||
// ✅
|
||||
$msg = "Hello, {$name}! You have {$count} messages.";
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ Manual string contains check
|
||||
if (strpos($haystack, $needle) !== false) { ... }
|
||||
|
||||
// ✅ (PHP 8.0+)
|
||||
if (str_contains($haystack, $needle)) { ... }
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ substr for prefix/suffix check
|
||||
if (substr($str, 0, 4) === 'http') { ... }
|
||||
if (substr($str, -4) === '.php') { ... }
|
||||
|
||||
// ✅ (PHP 8.0+)
|
||||
if (str_starts_with($str, 'http')) { ... }
|
||||
if (str_ends_with($str, '.php')) { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. OOP & Modern PHP {#oop}
|
||||
|
||||
```php
|
||||
// ❌ Manual constructor property assignment
|
||||
class User {
|
||||
private string $name;
|
||||
private int $age;
|
||||
public function __construct(string $name, int $age) {
|
||||
$this->name = $name;
|
||||
$this->age = $age;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ (PHP 8.0+)
|
||||
class User {
|
||||
public function __construct(
|
||||
private readonly string $name,
|
||||
private readonly int $age,
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ Constants as class properties
|
||||
class Status {
|
||||
const ACTIVE = 'active';
|
||||
const INACTIVE = 'inactive';
|
||||
}
|
||||
|
||||
// ✅ (PHP 8.1+)
|
||||
enum Status: string {
|
||||
case Active = 'active';
|
||||
case Inactive = 'inactive';
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ instanceof chains
|
||||
if ($shape instanceof Circle) { ... }
|
||||
elseif ($shape instanceof Rectangle) { ... }
|
||||
|
||||
// ✅ (PHP 8.0+)
|
||||
$area = match(true) {
|
||||
$shape instanceof Circle => $shape->radius ** 2 * M_PI,
|
||||
$shape instanceof Rectangle => $shape->width * $shape->height,
|
||||
default => throw new \InvalidArgumentException("Unknown shape"),
|
||||
};
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ Named constructor via static method returning new self()
|
||||
class Money {
|
||||
public static function fromCents(int $cents): self {
|
||||
$m = new self();
|
||||
$m->cents = $cents;
|
||||
return $m;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ (PHP 8.0+) — constructor promotion + named arguments
|
||||
class Money {
|
||||
public function __construct(
|
||||
public readonly int $cents,
|
||||
) {}
|
||||
}
|
||||
$m = new Money(cents: 500);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Functions & Closures {#functions}
|
||||
|
||||
```php
|
||||
// ❌ Verbose closure for simple operation
|
||||
$doubled = array_map(function ($x) { return $x * 2; }, $numbers);
|
||||
|
||||
// ✅ (PHP 7.4+)
|
||||
$doubled = array_map(fn($x) => $x * 2, $numbers);
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ Passing globals or using `global` keyword
|
||||
global $db;
|
||||
function getUser(int $id) {
|
||||
global $db;
|
||||
return $db->find($id);
|
||||
}
|
||||
|
||||
// ✅ — dependency injection
|
||||
function getUser(int $id, PDO $db): ?User {
|
||||
return $db->find($id);
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
// ❌ Named arguments abused for every call
|
||||
str_pad(string: $s, length: 10, pad_string: ' ', pad_type: STR_PAD_LEFT);
|
||||
|
||||
// ✅ — named args are useful for readability on ambiguous params; don't force
|
||||
str_pad($s, 10, ' ', STR_PAD_LEFT);
|
||||
// but named args shine for: new User(name: 'Alice', age: 30)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to PHP {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `==` for comparison | `===` (strict equality) |
|
||||
| `@` error suppression | explicit error handling |
|
||||
| `global` keyword | dependency injection |
|
||||
| `extract()` on user input | access keys explicitly |
|
||||
| `die()` / `exit()` in library code | throw exception |
|
||||
| `strpos !== false` for contains | `str_contains()` (PHP 8.0) |
|
||||
| Manual constructor assignment | constructor promotion (PHP 8.0) |
|
||||
| Class constants for enums | `enum` (PHP 8.1) |
|
||||
| `mixed` return types | specific typed returns |
|
||||
| `array` for everything | typed classes / DTOs |
|
||||
| `var_dump` / `print_r` debugging | proper logging (PSR-3) |
|
||||
| Not using `declare(strict_types=1)` | always enable |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
---
|
||||
name: python
|
||||
description: "Language-specific super-code guidelines for python."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# Python: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Comprehensions & Generators](#comprehensions)
|
||||
2. [Unpacking & Destructuring](#unpacking)
|
||||
3. [Built-ins & stdlib](#builtins)
|
||||
4. [Functions & Defaults](#functions)
|
||||
5. [Classes & Dataclasses](#classes)
|
||||
6. [Error Handling](#errors)
|
||||
7. [Type Hints](#types)
|
||||
8. [Anti-patterns specific to Python](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Comprehensions & Generators {#comprehensions}
|
||||
|
||||
```python
|
||||
# ❌ Imperative accumulation
|
||||
result = []
|
||||
for item in items:
|
||||
if item.active:
|
||||
result.append(item.name.upper())
|
||||
|
||||
# ✅
|
||||
result = [item.name.upper() for item in items if item.active]
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ Dict built in a loop
|
||||
d = {}
|
||||
for k, v in pairs:
|
||||
d[k] = v
|
||||
|
||||
# ✅
|
||||
d = dict(pairs)
|
||||
# or
|
||||
d = {k: v for k, v in pairs}
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ Generator converted to list unnecessarily
|
||||
total = sum(list(x * 2 for x in nums))
|
||||
|
||||
# ✅ — generator expression works directly in sum()
|
||||
total = sum(x * 2 for x in nums)
|
||||
```
|
||||
|
||||
**Use generator expressions (not list comprehensions) when the result is consumed once and not stored.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Unpacking & Destructuring {#unpacking}
|
||||
|
||||
```python
|
||||
# ❌ Index access
|
||||
first = items[0]
|
||||
rest = items[1:]
|
||||
|
||||
# ✅
|
||||
first, *rest = items
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ Temporary variable for swap
|
||||
tmp = a
|
||||
a = b
|
||||
b = tmp
|
||||
|
||||
# ✅
|
||||
a, b = b, a
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ items() with separate indexing
|
||||
for i in range(len(items)):
|
||||
print(i, items[i])
|
||||
|
||||
# ✅
|
||||
for i, item in enumerate(items):
|
||||
print(i, item)
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ zip with separate index
|
||||
for i in range(len(a)):
|
||||
process(a[i], b[i])
|
||||
|
||||
# ✅
|
||||
for x, y in zip(a, b):
|
||||
process(x, y)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Built-ins & stdlib {#builtins}
|
||||
|
||||
```python
|
||||
# ❌ Manual max search
|
||||
max_val = items[0]
|
||||
for item in items[1:]:
|
||||
if item > max_val:
|
||||
max_val = item
|
||||
|
||||
# ✅
|
||||
max_val = max(items)
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ Manual grouping
|
||||
from collections import defaultdict
|
||||
groups = defaultdict(list)
|
||||
for item in items:
|
||||
groups[item.category].append(item)
|
||||
|
||||
# ✅ — same thing, just be explicit about defaultdict; it IS the right tool
|
||||
# (this example is already correct — don't replace defaultdict with a loop)
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ Manual sentinel for dict default
|
||||
if key in d:
|
||||
val = d[key]
|
||||
else:
|
||||
val = default
|
||||
|
||||
# ✅
|
||||
val = d.get(key, default)
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ Rolling your own counter
|
||||
counts = {}
|
||||
for item in items:
|
||||
counts[item] = counts.get(item, 0) + 1
|
||||
|
||||
# ✅
|
||||
from collections import Counter
|
||||
counts = Counter(items)
|
||||
```
|
||||
|
||||
**Use `itertools` (chain, islice, groupby, product) before writing nested loops for combinatorial or streaming logic.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Functions & Defaults {#functions}
|
||||
|
||||
```python
|
||||
# ❌ Mutable default argument (bug, not just style)
|
||||
def append_to(item, lst=[]):
|
||||
lst.append(item)
|
||||
return lst
|
||||
|
||||
# ✅
|
||||
def append_to(item, lst=None):
|
||||
if lst is None:
|
||||
lst = []
|
||||
lst.append(item)
|
||||
return lst
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ Positional args for everything when keyword clarity helps
|
||||
create_user("Alice", True, False, 30)
|
||||
|
||||
# ✅ — use keyword args at call site for boolean/ambiguous params
|
||||
create_user("Alice", is_admin=True, is_active=False, age=30)
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ Long function doing multiple things
|
||||
def process_and_save(data):
|
||||
# 40 lines of transform
|
||||
# 20 lines of DB write
|
||||
...
|
||||
|
||||
# ✅ — split only if each part is reused OR independently testable
|
||||
def _transform(data): ...
|
||||
def _save(record): ...
|
||||
def process_and_save(data): _save(_transform(data))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Classes & Dataclasses {#classes}
|
||||
|
||||
```python
|
||||
# ❌ Manual __init__ for data holders
|
||||
class Point:
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
# ✅
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Point:
|
||||
x: float
|
||||
y: float
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ Class just to hold a namespace of functions
|
||||
class MathUtils:
|
||||
@staticmethod
|
||||
def add(a, b): return a + b
|
||||
|
||||
# ✅ — module-level functions; classes for state + behavior
|
||||
def add(a, b): return a + b
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ __repr__ written manually when dataclass gives it free
|
||||
# (see above — use @dataclass)
|
||||
```
|
||||
|
||||
**Use `@dataclass(frozen=True)` for immutable value objects. Use `NamedTuple` when you need tuple unpacking.**
|
||||
|
||||
---
|
||||
|
||||
## 6. Error Handling {#errors}
|
||||
|
||||
```python
|
||||
# ❌ Bare except
|
||||
try:
|
||||
risky()
|
||||
except:
|
||||
pass
|
||||
|
||||
# ✅ — catch the specific exception; don't swallow silently
|
||||
try:
|
||||
risky()
|
||||
except ValueError as e:
|
||||
logger.warning("Invalid value: %s", e)
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ LBYL (look before you leap) when EAFP is cleaner
|
||||
if os.path.exists(path):
|
||||
with open(path) as f:
|
||||
data = f.read()
|
||||
|
||||
# ✅ (EAFP)
|
||||
try:
|
||||
with open(path) as f:
|
||||
data = f.read()
|
||||
except FileNotFoundError:
|
||||
data = None
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ Re-raising with raise e (loses traceback)
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
# ✅
|
||||
except Exception:
|
||||
raise # bare raise preserves original traceback
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Type Hints {#types}
|
||||
|
||||
```python
|
||||
# ❌ Overly verbose Union syntax (Python <3.10 style in new code)
|
||||
from typing import Optional, Union
|
||||
def f(x: Optional[int]) -> Union[str, None]: ...
|
||||
|
||||
# ✅ (Python 3.10+)
|
||||
def f(x: int | None) -> str | None: ...
|
||||
```
|
||||
|
||||
```python
|
||||
# ❌ Any where a TypeVar or Protocol would be informative
|
||||
from typing import Any
|
||||
def first(lst: list[Any]) -> Any: ...
|
||||
|
||||
# ✅
|
||||
from typing import TypeVar
|
||||
T = TypeVar("T")
|
||||
def first(lst: list[T]) -> T: ...
|
||||
```
|
||||
|
||||
**Don't add type hints to every local variable — annotate function signatures and class fields; leave obvious locals inferred.**
|
||||
|
||||
---
|
||||
|
||||
## 8. Anti-patterns specific to Python {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `len(lst) == 0` | `not lst` |
|
||||
| `if x == True:` | `if x:` |
|
||||
| `if x == None:` | `if x is None:` |
|
||||
| `range(len(lst))` for iteration | `enumerate(lst)` |
|
||||
| String concatenation in a loop | `"".join(parts)` |
|
||||
| `import *` | explicit imports |
|
||||
| Catching `Exception` to log and re-raise | bare `raise` or let it propagate |
|
||||
| `print()` for debug output | `logging.debug()` |
|
||||
| `os.path.join` (Python 3.4+) | `pathlib.Path / "subpath"` |
|
||||
| Manual `__eq__` + `__hash__` on value objects | `@dataclass(eq=True, frozen=True)` |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
---
|
||||
name: ruby
|
||||
description: "Language-specific super-code guidelines for ruby."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# Ruby: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Enumerable & Collections](#enumerable)
|
||||
2. [Blocks, Procs & Lambdas](#blocks)
|
||||
3. [String Handling](#strings)
|
||||
4. [Error Handling](#errors)
|
||||
5. [Classes & Modules](#classes)
|
||||
6. [Ruby Idioms](#idioms)
|
||||
7. [Anti-patterns specific to Ruby](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Enumerable & Collections {#enumerable}
|
||||
|
||||
```ruby
|
||||
# ❌ Manual accumulation
|
||||
result = []
|
||||
items.each do |item|
|
||||
result << item.name.upcase if item.active?
|
||||
end
|
||||
|
||||
# ✅
|
||||
result = items.select(&:active?).map { |i| i.name.upcase }
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ Manual grouping
|
||||
grouped = {}
|
||||
items.each do |item|
|
||||
grouped[item.category] ||= []
|
||||
grouped[item.category] << item
|
||||
end
|
||||
|
||||
# ✅
|
||||
grouped = items.group_by(&:category)
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ Manual sum
|
||||
total = 0
|
||||
orders.each { |o| total += o.amount }
|
||||
|
||||
# ✅
|
||||
total = orders.sum(&:amount)
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ Checking existence then accessing
|
||||
if hash.key?(key)
|
||||
value = hash[key]
|
||||
end
|
||||
|
||||
# ✅
|
||||
value = hash[key] # returns nil if missing
|
||||
# or with default:
|
||||
value = hash.fetch(key, default_value)
|
||||
# or raising on missing:
|
||||
value = hash.fetch(key) # raises KeyError
|
||||
```
|
||||
|
||||
**Prefer `map`/`select`/`reject`/`sum` over manual loops. Use `&:method` for single-method blocks.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Blocks, Procs & Lambdas {#blocks}
|
||||
|
||||
```ruby
|
||||
# ❌ Explicit block-to-proc conversion when unnecessary
|
||||
items.map { |item| item.to_s }
|
||||
|
||||
# ✅
|
||||
items.map(&:to_s)
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ Proc.new when lambda is safer (arity check + return behavior)
|
||||
handler = Proc.new { |x| x * 2 }
|
||||
|
||||
# ✅
|
||||
handler = ->(x) { x * 2 }
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ Multi-line block with { }
|
||||
items.map { |item|
|
||||
result = transform(item)
|
||||
validate(result)
|
||||
result
|
||||
}
|
||||
|
||||
# ✅ — do/end for multi-line, { } for single-line
|
||||
items.map do |item|
|
||||
result = transform(item)
|
||||
validate(result)
|
||||
result
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. String Handling {#strings}
|
||||
|
||||
```ruby
|
||||
# ❌ String concatenation in loop
|
||||
result = ""
|
||||
items.each { |i| result += i.name + ", " }
|
||||
|
||||
# ✅
|
||||
result = items.map(&:name).join(", ")
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ String concatenation for assembly
|
||||
greeting = "Hello, " + name + "! You have " + count.to_s + " messages."
|
||||
|
||||
# ✅
|
||||
greeting = "Hello, #{name}! You have #{count} messages."
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ Mutable string where frozen is fine (Ruby 3+ encourages frozen)
|
||||
SEPARATOR = ", "
|
||||
|
||||
# ✅
|
||||
SEPARATOR = ", ".freeze
|
||||
# or add `# frozen_string_literal: true` at file top
|
||||
```
|
||||
|
||||
**Use heredocs (`<<~HEREDOC`) for multi-line strings. `<<~` strips indentation.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Error Handling {#errors}
|
||||
|
||||
```ruby
|
||||
# ❌ Rescuing Exception (catches EVERYTHING including SignalException, SystemExit)
|
||||
begin
|
||||
risky
|
||||
rescue Exception => e
|
||||
log(e)
|
||||
end
|
||||
|
||||
# ✅ — rescue StandardError (the default)
|
||||
begin
|
||||
risky
|
||||
rescue StandardError => e
|
||||
log(e)
|
||||
raise
|
||||
end
|
||||
# or just: rescue => e (same as StandardError)
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ Using rescue as flow control
|
||||
begin
|
||||
value = hash.fetch(key)
|
||||
rescue KeyError
|
||||
value = default
|
||||
end
|
||||
|
||||
# ✅
|
||||
value = hash.fetch(key, default)
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ Inline rescue hiding errors
|
||||
result = dangerous_operation rescue nil
|
||||
|
||||
# ✅ — inline rescue only for truly trivial fallbacks
|
||||
result = Integer(input) rescue nil # acceptable for parsing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Classes & Modules {#classes}
|
||||
|
||||
```ruby
|
||||
# ❌ Manual accessors
|
||||
class User
|
||||
def name
|
||||
@name
|
||||
end
|
||||
def name=(value)
|
||||
@name = value
|
||||
end
|
||||
end
|
||||
|
||||
# ✅
|
||||
class User
|
||||
attr_accessor :name
|
||||
end
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ Deep inheritance for shared behavior
|
||||
class Animal; end
|
||||
class Pet < Animal; end
|
||||
class Dog < Pet; end
|
||||
|
||||
# ✅ — mixins for shared behavior, inheritance for "is-a"
|
||||
module Trainable
|
||||
def train = puts("Training #{name}")
|
||||
end
|
||||
|
||||
class Dog
|
||||
include Trainable
|
||||
attr_reader :name
|
||||
def initialize(name) = @name = name
|
||||
end
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ Class with only class methods (namespace via class)
|
||||
class MathUtils
|
||||
def self.square(x) = x * x
|
||||
def self.cube(x) = x ** 3
|
||||
end
|
||||
|
||||
# ✅
|
||||
module MathUtils
|
||||
module_function
|
||||
def square(x) = x * x
|
||||
def cube(x) = x ** 3
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Ruby Idioms {#idioms}
|
||||
|
||||
```ruby
|
||||
# ❌ Explicit boolean return
|
||||
def active?
|
||||
if status == :active
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# ✅
|
||||
def active? = status == :active
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ nil check before method call
|
||||
if user && user.name
|
||||
puts user.name
|
||||
end
|
||||
|
||||
# ✅ (Ruby 2.3+)
|
||||
puts user&.name if user&.name
|
||||
# or with safe navigation:
|
||||
user&.name&.then { |n| puts n }
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ Conditional assignment verbosely
|
||||
if @cache.nil?
|
||||
@cache = expensive_compute
|
||||
end
|
||||
|
||||
# ✅
|
||||
@cache ||= expensive_compute
|
||||
```
|
||||
|
||||
```ruby
|
||||
# ❌ Multiple assignment from array manually
|
||||
first = arr[0]
|
||||
second = arr[1]
|
||||
|
||||
# ✅
|
||||
first, second = arr
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to Ruby {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `rescue Exception` | `rescue StandardError` |
|
||||
| `for x in collection` | `collection.each` |
|
||||
| Manual `attr_reader`/`writer` | `attr_accessor` / `attr_reader` |
|
||||
| `class` for pure namespace | `module` |
|
||||
| String concatenation with `+` | string interpolation `#{}` |
|
||||
| `if !condition` | `unless condition` |
|
||||
| `== true` / `== false` | truthy/falsy check directly |
|
||||
| `and`/`or` for control flow | `&&`/`||` (different precedence) |
|
||||
| `return` at end of method | implicit return (last expression) |
|
||||
| Monkey-patching core classes in production | refinements or wrapper |
|
||||
| `eval` / `send` for known methods | direct method call |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
---
|
||||
name: rust
|
||||
description: "Language-specific super-code guidelines for rust."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# Rust: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Ownership & Borrowing](#ownership)
|
||||
2. [Error Handling](#errors)
|
||||
3. [Iterators](#iterators)
|
||||
4. [Pattern Matching](#patterns)
|
||||
5. [Structs & Enums](#structs)
|
||||
6. [Concurrency](#concurrency)
|
||||
7. [Anti-patterns specific to Rust](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Ownership & Borrowing {#ownership}
|
||||
|
||||
```rust
|
||||
// ❌ Cloning to avoid thinking about lifetimes
|
||||
fn get_name(user: &User) -> String {
|
||||
user.name.clone()
|
||||
}
|
||||
|
||||
// ✅ — return a reference when the data lives long enough
|
||||
fn get_name(user: &User) -> &str {
|
||||
&user.name
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
// ❌ Taking ownership when borrowing suffices
|
||||
fn print_name(name: String) {
|
||||
println!("{name}");
|
||||
}
|
||||
|
||||
// ✅
|
||||
fn print_name(name: &str) {
|
||||
println!("{name}");
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
// ❌ Unnecessary .to_string() / .to_owned() in hot paths
|
||||
let key = id.to_string();
|
||||
map.get(&key)
|
||||
|
||||
// ✅ — use Borrow trait; HashMap<String, V> accepts &str as key
|
||||
map.get(id)
|
||||
```
|
||||
|
||||
**Prefer `&str` over `String` in function parameters unless the function needs to own the data.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Error Handling {#errors}
|
||||
|
||||
```rust
|
||||
// ❌ .unwrap() in production code
|
||||
let file = File::open(path).unwrap();
|
||||
|
||||
// ✅
|
||||
let file = File::open(path)
|
||||
.map_err(|e| AppError::Io { path: path.to_owned(), source: e })?;
|
||||
```
|
||||
|
||||
```rust
|
||||
// ❌ Manual match on Result for every call
|
||||
match do_thing() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
// ✅ — the ? operator
|
||||
let v = do_thing()?;
|
||||
```
|
||||
|
||||
```rust
|
||||
// ❌ Box<dyn Error> everywhere (loses type info)
|
||||
fn run() -> Result<(), Box<dyn std::error::Error>> { ... }
|
||||
|
||||
// ✅ — use thiserror for library errors, anyhow for application errors
|
||||
use anyhow::{Context, Result};
|
||||
fn run() -> Result<()> {
|
||||
do_thing().context("failed during run")?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
// ❌ Separate error enum variant for every call site
|
||||
enum Error { FileOpen, FileRead, Parse, Network, ... }
|
||||
|
||||
// ✅ — use thiserror with #[from] for automatic conversion
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
enum Error {
|
||||
#[error("io error")] Io(#[from] std::io::Error),
|
||||
#[error("parse error")] Parse(#[from] serde_json::Error),
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Iterators {#iterators}
|
||||
|
||||
```rust
|
||||
// ❌ Imperative accumulation
|
||||
let mut result = Vec::new();
|
||||
for item in &items {
|
||||
if item.active {
|
||||
result.push(item.name.to_uppercase());
|
||||
}
|
||||
}
|
||||
|
||||
// ✅
|
||||
let result: Vec<_> = items.iter()
|
||||
.filter(|i| i.active)
|
||||
.map(|i| i.name.to_uppercase())
|
||||
.collect();
|
||||
```
|
||||
|
||||
```rust
|
||||
// ❌ Manual sum
|
||||
let mut total = 0;
|
||||
for order in &orders { total += order.amount; }
|
||||
|
||||
// ✅
|
||||
let total: u64 = orders.iter().map(|o| o.amount).sum();
|
||||
```
|
||||
|
||||
```rust
|
||||
// ❌ Index-based loop
|
||||
for i in 0..items.len() {
|
||||
process(&items[i]);
|
||||
}
|
||||
|
||||
// ✅
|
||||
for item in &items {
|
||||
process(item);
|
||||
}
|
||||
// With index:
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
process(i, item);
|
||||
}
|
||||
```
|
||||
|
||||
**Chain iterators lazily; only `.collect()` when you actually need a concrete collection.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Pattern Matching {#patterns}
|
||||
|
||||
```rust
|
||||
// ❌ if-let chain that should be match
|
||||
if let Some(x) = opt {
|
||||
if x > 0 {
|
||||
use(x)
|
||||
}
|
||||
}
|
||||
|
||||
// ✅
|
||||
if let Some(x) = opt.filter(|&x| x > 0) {
|
||||
use(x)
|
||||
}
|
||||
// or match with guard:
|
||||
match opt {
|
||||
Some(x) if x > 0 => use(x),
|
||||
_ => {}
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
// ❌ match with identical arms
|
||||
match status {
|
||||
Status::Active => true,
|
||||
Status::Pending => true,
|
||||
Status::Inactive => false,
|
||||
}
|
||||
|
||||
// ✅
|
||||
matches!(status, Status::Active | Status::Pending)
|
||||
```
|
||||
|
||||
```rust
|
||||
// ❌ Destructuring in body instead of pattern
|
||||
fn area(shape: &Shape) -> f64 {
|
||||
match shape {
|
||||
Shape::Circle(c) => { let r = c.radius; r * r * PI }
|
||||
Shape::Rect(r) => { let w = r.width; let h = r.height; w * h }
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ — destructure in pattern
|
||||
match shape {
|
||||
Shape::Circle(Circle { radius, .. }) => radius * radius * PI,
|
||||
Shape::Rect(Rect { width, height }) => width * height,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Structs & Enums {#structs}
|
||||
|
||||
```rust
|
||||
// ❌ Enum variant carrying bool for binary state
|
||||
enum State { Running(bool) } // true = paused?
|
||||
|
||||
// ✅ — explicit variants
|
||||
enum State { Running, Paused, Stopped }
|
||||
```
|
||||
|
||||
```rust
|
||||
// ❌ Struct with many Option fields (stringly optional)
|
||||
struct Config {
|
||||
timeout: Option<u64>,
|
||||
retries: Option<u32>,
|
||||
base_url: Option<String>,
|
||||
}
|
||||
|
||||
// ✅ — use Default + builder pattern or #[derive(Default)] with sensible defaults
|
||||
#[derive(Default)]
|
||||
struct Config {
|
||||
timeout: u64, // default 0 = no timeout
|
||||
retries: u32, // default 0
|
||||
base_url: String,
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
// ❌ pub fields on a type that needs invariants
|
||||
pub struct Percentage { pub value: f64 }
|
||||
|
||||
// ✅ — private field, constructor enforces invariant
|
||||
pub struct Percentage(f64);
|
||||
impl Percentage {
|
||||
pub fn new(v: f64) -> Option<Self> {
|
||||
(0.0..=100.0).contains(&v).then_some(Self(v))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Concurrency {#concurrency}
|
||||
|
||||
```rust
|
||||
// ❌ Arc<Mutex<T>> for read-heavy data
|
||||
let data = Arc::new(Mutex::new(vec![...]));
|
||||
|
||||
// ✅ — RwLock for read-heavy
|
||||
let data = Arc::new(RwLock::new(vec![...]));
|
||||
```
|
||||
|
||||
```rust
|
||||
// ❌ Spawning OS threads for many small tasks
|
||||
for item in items {
|
||||
std::thread::spawn(|| process(item));
|
||||
}
|
||||
|
||||
// ✅ — use rayon for CPU-bound parallel iteration
|
||||
use rayon::prelude::*;
|
||||
items.par_iter().for_each(|item| process(item));
|
||||
```
|
||||
|
||||
**For async: prefer `tokio::spawn` + `JoinHandle` over manual channels for structured concurrency. Use `tokio::join!` for concurrent awaits.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to Rust {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `.clone()` to appease borrow checker | reconsider lifetime or restructure |
|
||||
| `.unwrap()` in non-test code | `?` operator or explicit handling |
|
||||
| `impl Trait` in return position hiding complex type | name the type or use `Box<dyn Trait>` intentionally |
|
||||
| `String` parameter when `&str` suffices | `&str` for params, `String` for owned storage |
|
||||
| Nested `Option<Option<T>>` | rethink the data model |
|
||||
| `unsafe` block without a safety comment | always document the invariant being upheld |
|
||||
| `Vec<Box<T>>` when `Vec<T>` works | avoid heap allocation inside collections unless T is unsized |
|
||||
| Manual `Drop` for cleanup that `?` handles | let RAII + `?` do it |
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
---
|
||||
name: scala
|
||||
description: "Language-specific super-code guidelines for scala."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# Scala: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Collections & Functional Transforms](#collections)
|
||||
2. [Pattern Matching](#patterns)
|
||||
3. [Case Classes & ADTs](#case-classes)
|
||||
4. [Option & Error Handling](#option)
|
||||
5. [Implicits & Given/Using](#implicits)
|
||||
6. [Concurrency](#concurrency)
|
||||
7. [Anti-patterns specific to Scala](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Collections & Functional Transforms {#collections}
|
||||
|
||||
```scala
|
||||
// ❌ Imperative accumulation
|
||||
val result = new ArrayBuffer[String]()
|
||||
for (item <- items) {
|
||||
if (item.isActive) result += item.name.toUpperCase
|
||||
}
|
||||
|
||||
// ✅
|
||||
val result = items.filter(_.isActive).map(_.name.toUpperCase)
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ Manual grouping
|
||||
val grouped = mutable.Map[String, List[Item]]()
|
||||
for (item <- items) {
|
||||
grouped(item.category) = grouped.getOrElse(item.category, Nil) :+ item
|
||||
}
|
||||
|
||||
// ✅
|
||||
val grouped = items.groupBy(_.category)
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ Manual fold when sum/product works
|
||||
var total = 0
|
||||
for (o <- orders) total += o.amount
|
||||
|
||||
// ✅
|
||||
val total = orders.map(_.amount).sum
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ Using head on potentially empty collection
|
||||
val first = items.head // throws on empty
|
||||
|
||||
// ✅
|
||||
val first = items.headOption // returns Option[T]
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ Chaining filter + head for find
|
||||
val found = items.filter(_.id == targetId).head
|
||||
|
||||
// ✅
|
||||
val found = items.find(_.id == targetId) // returns Option[T]
|
||||
```
|
||||
|
||||
**Use `view` for lazy evaluation on large collections to avoid intermediate allocations.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Pattern Matching {#patterns}
|
||||
|
||||
```scala
|
||||
// ❌ if-else chain for type dispatch
|
||||
if (shape.isInstanceOf[Circle]) {
|
||||
val c = shape.asInstanceOf[Circle]
|
||||
c.radius * c.radius * Math.PI
|
||||
} else if (shape.isInstanceOf[Rect]) { ... }
|
||||
|
||||
// ✅
|
||||
shape match {
|
||||
case Circle(r) => r * r * Math.PI
|
||||
case Rect(w, h) => w * h
|
||||
}
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ Nested match with identical fallthrough
|
||||
x match {
|
||||
case 1 => "low"
|
||||
case 2 => "low"
|
||||
case 3 => "mid"
|
||||
case _ => "high"
|
||||
}
|
||||
|
||||
// ✅
|
||||
x match {
|
||||
case 1 | 2 => "low"
|
||||
case 3 => "mid"
|
||||
case _ => "high"
|
||||
}
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ Match to extract then use
|
||||
val result = opt match {
|
||||
case Some(x) => x.toString
|
||||
case None => "N/A"
|
||||
}
|
||||
|
||||
// ✅
|
||||
val result = opt.map(_.toString).getOrElse("N/A")
|
||||
// or:
|
||||
val result = opt.fold("N/A")(_.toString)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Case Classes & ADTs {#case-classes}
|
||||
|
||||
```scala
|
||||
// ❌ Regular class for data
|
||||
class User(val name: String, val age: Int) {
|
||||
override def equals(obj: Any): Boolean = ...
|
||||
override def hashCode(): Int = ...
|
||||
override def toString: String = ...
|
||||
}
|
||||
|
||||
// ✅
|
||||
case class User(name: String, age: Int)
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ Sealed trait with unrelated case objects
|
||||
sealed trait Result
|
||||
case class Success(value: Int) extends Result
|
||||
case class Failure(error: String) extends Result
|
||||
case object Unknown extends Result // what does "Unknown" mean?
|
||||
|
||||
// ✅ — each variant should carry the data it represents
|
||||
sealed trait Result[+A]
|
||||
case class Success[A] (value: A) extends Result[A]
|
||||
case class Failure(error: Throwable) extends Result[Nothing]
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ (Scala 3) Verbose enum
|
||||
sealed trait Color
|
||||
object Color {
|
||||
case object Red extends Color
|
||||
case object Green extends Color
|
||||
case object Blue extends Color
|
||||
}
|
||||
|
||||
// ✅ (Scala 3)
|
||||
enum Color { case Red, Green, Blue }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Option & Error Handling {#option}
|
||||
|
||||
```scala
|
||||
// ❌ Null checks
|
||||
val name: String = if (user != null) user.name else "Unknown"
|
||||
|
||||
// ✅
|
||||
val name = Option(user).map(_.name).getOrElse("Unknown")
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ .get on Option (defeats the purpose)
|
||||
val name = userOpt.get // throws if None
|
||||
|
||||
// ✅
|
||||
val name = userOpt.getOrElse("default")
|
||||
// or: userOpt.map(process).getOrElse(fallback)
|
||||
// or: userOpt match { case Some(u) => ... case None => ... }
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ Try with .get
|
||||
val result = Try(parse(input)).get // throws on failure
|
||||
|
||||
// ✅
|
||||
val result = Try(parse(input)) match {
|
||||
case Success(v) => v
|
||||
case Failure(e) => handleError(e)
|
||||
}
|
||||
// or: Try(parse(input)).getOrElse(default)
|
||||
// or: Try(parse(input)).toEither
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ Using exceptions for expected failures
|
||||
def findUser(id: String): User = {
|
||||
val user = db.query(id)
|
||||
if (user == null) throw new NotFoundException(id)
|
||||
user
|
||||
}
|
||||
|
||||
// ✅ — Option for absence, Either for expected errors
|
||||
def findUser(id: String): Option[User] = db.query(id)
|
||||
// or:
|
||||
def findUser(id: String): Either[AppError, User]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Implicits & Given/Using {#implicits}
|
||||
|
||||
```scala
|
||||
// ❌ (Scala 2) Implicit conversion that hides bugs
|
||||
implicit def stringToInt(s: String): Int = s.toInt
|
||||
|
||||
// ✅ — extension methods instead of implicit conversions
|
||||
extension (s: String)
|
||||
def toIntSafe: Option[Int] = s.toIntOption
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ (Scala 2) Implicit parameter with broad type
|
||||
def query(sql: String)(implicit conn: Connection): ResultSet
|
||||
|
||||
// ✅ (Scala 3)
|
||||
def query(sql: String)(using conn: Connection): ResultSet
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ Importing implicits from everywhere
|
||||
import com.lib.implicits._
|
||||
|
||||
// ✅ — import only what you need
|
||||
import com.lib.given
|
||||
// or specific: import com.lib.{given ExecutionContext}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Concurrency {#concurrency}
|
||||
|
||||
```scala
|
||||
// ❌ Thread.sleep in production code
|
||||
Thread.sleep(5000)
|
||||
|
||||
// ✅ — use scheduler / timer abstraction
|
||||
import scala.concurrent.duration._
|
||||
system.scheduler.scheduleOnce(5.seconds)(doWork())
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ Blocking inside Future
|
||||
Future {
|
||||
val result = blockingHttpCall() // starves thread pool
|
||||
process(result)
|
||||
}
|
||||
|
||||
// ✅
|
||||
Future {
|
||||
blocking { val result = blockingHttpCall() }
|
||||
// or use a dedicated blocking ExecutionContext
|
||||
}
|
||||
```
|
||||
|
||||
```scala
|
||||
// ❌ Awaiting futures in a loop
|
||||
for (f <- futures) Await.result(f, Duration.Inf)
|
||||
|
||||
// ✅
|
||||
val all = Future.sequence(futures)
|
||||
all.map(results => process(results))
|
||||
```
|
||||
|
||||
**Prefer `Future.sequence`/`Future.traverse` over manual await loops.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to Scala {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `.get` on Option/Try | `.getOrElse` / pattern match |
|
||||
| `null` | `Option` |
|
||||
| `isInstanceOf` + `asInstanceOf` | pattern matching |
|
||||
| Implicit conversions (Scala 2) | extension methods (Scala 3) |
|
||||
| `var` for accumulation | `val` + functional transforms |
|
||||
| `return` keyword | last expression is the return value |
|
||||
| Mutable collections by default | immutable collections |
|
||||
| `Any` / `AnyRef` parameters | generics with type bounds |
|
||||
| Deeply nested `for` comprehensions | break into named values |
|
||||
| Tuple instead of case class | case class for anything with semantic meaning |
|
||||
| `Await.result` in production | compose with `map`/`flatMap` |
|
||||
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
---
|
||||
name: swift
|
||||
description: "Language-specific super-code guidelines for swift."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# Swift: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Optionals](#optionals)
|
||||
2. [Collections & Functional Transforms](#collections)
|
||||
3. [Value vs Reference Types](#value-types)
|
||||
4. [Error Handling](#errors)
|
||||
5. [Concurrency](#concurrency)
|
||||
6. [Protocol-Oriented Design](#protocols)
|
||||
7. [Anti-patterns specific to Swift](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Optionals {#optionals}
|
||||
|
||||
```swift
|
||||
// ❌ Force unwrap
|
||||
let name = user.name!
|
||||
|
||||
// ✅ — guard or if-let
|
||||
guard let name = user.name else { return }
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ Nested if-let pyramid
|
||||
if let user = fetchUser() {
|
||||
if let address = user.address {
|
||||
if let city = address.city {
|
||||
display(city)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ — chained optional binding
|
||||
if let city = fetchUser()?.address?.city {
|
||||
display(city)
|
||||
}
|
||||
// or guard-let for early exit
|
||||
guard let city = fetchUser()?.address?.city else { return }
|
||||
display(city)
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ Ternary for default
|
||||
let name = user.name != nil ? user.name! : "Unknown"
|
||||
|
||||
// ✅
|
||||
let name = user.name ?? "Unknown"
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ Optional map when if-let is clearer for side effects
|
||||
user.name.map { display($0) }
|
||||
|
||||
// ✅ — map for transforms, if-let for side effects
|
||||
let upper = user.name.map { $0.uppercased() }
|
||||
if let name = user.name { display(name) }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Collections & Functional Transforms {#collections}
|
||||
|
||||
```swift
|
||||
// ❌ Imperative filter + map
|
||||
var result: [String] = []
|
||||
for item in items {
|
||||
if item.isActive { result.append(item.name.uppercased()) }
|
||||
}
|
||||
|
||||
// ✅
|
||||
let result = items
|
||||
.filter(\.isActive)
|
||||
.map { $0.name.uppercased() }
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ Manual dictionary construction
|
||||
var dict: [String: User] = [:]
|
||||
for user in users { dict[user.id] = user }
|
||||
|
||||
// ✅
|
||||
let dict = Dictionary(uniqueKeysWithValues: users.map { ($0.id, $0) })
|
||||
// or with possible duplicates:
|
||||
let dict = Dictionary(grouping: users, by: \.department)
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ Checking isEmpty then accessing first
|
||||
if !items.isEmpty { process(items[0]) }
|
||||
|
||||
// ✅
|
||||
if let first = items.first { process(first) }
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ Index-based loop
|
||||
for i in 0..<items.count { process(items[i]) }
|
||||
|
||||
// ✅
|
||||
for item in items { process(item) }
|
||||
// with index:
|
||||
for (i, item) in items.enumerated() { process(i, item) }
|
||||
```
|
||||
|
||||
**Use key paths (`\.isActive`) as closure shorthand where supported.**
|
||||
|
||||
---
|
||||
|
||||
## 3. Value vs Reference Types {#value-types}
|
||||
|
||||
```swift
|
||||
// ❌ Class for plain data (reference semantics where value semantics suffice)
|
||||
class Point {
|
||||
var x: Double
|
||||
var y: Double
|
||||
init(x: Double, y: Double) { self.x = x; self.y = y }
|
||||
}
|
||||
|
||||
// ✅
|
||||
struct Point { var x, y: Double }
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ Large struct copied repeatedly (performance hit)
|
||||
struct HugeData { var buffer: [UInt8] /* thousands of elements */ }
|
||||
func process(_ data: HugeData) { ... } // copies entire buffer
|
||||
|
||||
// ✅ — use class or pass inout for mutation
|
||||
func process(_ data: inout HugeData) { ... }
|
||||
// or use copy-on-write wrapper for large value types
|
||||
```
|
||||
|
||||
**Default to `struct`. Use `class` when you need identity, inheritance, or reference semantics.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Error Handling {#errors}
|
||||
|
||||
```swift
|
||||
// ❌ Using optionals to mask errors
|
||||
func parse(_ input: String) -> Data? { ... } // caller doesn't know why it failed
|
||||
|
||||
// ✅
|
||||
func parse(_ input: String) throws -> Data { ... }
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ try! in production code
|
||||
let data = try! JSONDecoder().decode(User.self, from: jsonData)
|
||||
|
||||
// ✅
|
||||
do {
|
||||
let data = try JSONDecoder().decode(User.self, from: jsonData)
|
||||
} catch {
|
||||
logger.error("decode failed: \(error)")
|
||||
throw AppError.decodingFailed(underlying: error)
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ Generic Error type
|
||||
enum AppError: Error { case generic(String) }
|
||||
|
||||
// ✅ — specific, actionable error cases
|
||||
enum AppError: Error {
|
||||
case networkUnreachable
|
||||
case invalidInput(field: String, reason: String)
|
||||
case unauthorized
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ Catching all errors and ignoring
|
||||
do { try riskyOperation() } catch { }
|
||||
|
||||
// ✅
|
||||
do {
|
||||
try riskyOperation()
|
||||
} catch let error as NetworkError {
|
||||
handleNetworkError(error)
|
||||
} catch {
|
||||
throw error // rethrow unknown
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Concurrency {#concurrency}
|
||||
|
||||
```swift
|
||||
// ❌ Callback-based async (pyramid of doom)
|
||||
fetchUser { user in
|
||||
fetchPosts(for: user) { posts in
|
||||
fetchComments(for: posts.first!) { comments in
|
||||
display(comments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ (Swift 5.5+)
|
||||
let user = try await fetchUser()
|
||||
let posts = try await fetchPosts(for: user)
|
||||
let comments = try await fetchComments(for: posts[0])
|
||||
display(comments)
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ Sequential awaits for independent work
|
||||
let a = try await fetchA()
|
||||
let b = try await fetchB()
|
||||
|
||||
// ✅
|
||||
async let a = fetchA()
|
||||
async let b = fetchB()
|
||||
let (resultA, resultB) = try await (a, b)
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ DispatchQueue.main.async for UI updates in async context
|
||||
DispatchQueue.main.async { label.text = result }
|
||||
|
||||
// ✅
|
||||
await MainActor.run { label.text = result }
|
||||
// or mark the function/class @MainActor
|
||||
```
|
||||
|
||||
**Use `actor` for mutable shared state instead of manual locks/queues.**
|
||||
|
||||
---
|
||||
|
||||
## 6. Protocol-Oriented Design {#protocols}
|
||||
|
||||
```swift
|
||||
// ❌ Deep class inheritance hierarchy
|
||||
class Animal { ... }
|
||||
class Dog: Animal { ... }
|
||||
class GuideDog: Dog { ... }
|
||||
|
||||
// ✅ — protocols + composition
|
||||
protocol Animal { var name: String { get } }
|
||||
protocol Trainable { func train() }
|
||||
struct Dog: Animal, Trainable { ... }
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ Protocol with default implementations for everything
|
||||
protocol Renderable {
|
||||
func render()
|
||||
}
|
||||
extension Renderable {
|
||||
func render() { /* default */ }
|
||||
}
|
||||
// Every conformer uses default — protocol serves no purpose
|
||||
|
||||
// ✅ — only default implementations that provide genuine shared logic
|
||||
```
|
||||
|
||||
```swift
|
||||
// ❌ Associated type when generic parameter suffices
|
||||
protocol Container {
|
||||
associatedtype Element
|
||||
func get() -> Element
|
||||
}
|
||||
|
||||
// ✅ — use `some` or generic parameter for simple cases
|
||||
func process(_ item: some Equatable) { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to Swift {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| Force unwrap `!` in production code | `guard let` / `if let` / `??` |
|
||||
| `try!` outside tests | `do/catch` |
|
||||
| `class` for plain data | `struct` |
|
||||
| Deep inheritance hierarchies | protocol composition |
|
||||
| `@objc` when pure Swift works | native Swift types |
|
||||
| `NSArray` / `NSDictionary` | `Array` / `Dictionary` |
|
||||
| `DispatchQueue` in async/await code | `actor` / `MainActor` |
|
||||
| Implicitly unwrapped optionals as fields | regular optionals or non-optional with init |
|
||||
| `Any` / `AnyObject` everywhere | generics with protocol constraints |
|
||||
| Massive `switch` over string values | enum with raw values |
|
||||
| Singleton pattern (global mutable state) | dependency injection |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
---
|
||||
name: typescript
|
||||
description: "Language-specific super-code guidelines for typescript."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# TypeScript / JavaScript: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Array & Object Operations](#arrays)
|
||||
2. [Destructuring & Spread](#destructuring)
|
||||
3. [Async / Promises](#async)
|
||||
4. [Functions & Closures](#functions)
|
||||
5. [TypeScript Types](#types)
|
||||
6. [React (if applicable)](#react)
|
||||
7. [Anti-patterns specific to TS/JS](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Array & Object Operations {#arrays}
|
||||
|
||||
```ts
|
||||
// ❌ Imperative push loop
|
||||
const result: string[] = []
|
||||
for (const item of items) {
|
||||
if (item.active) result.push(item.name.toUpperCase())
|
||||
}
|
||||
|
||||
// ✅
|
||||
const result = items.filter(i => i.active).map(i => i.name.toUpperCase())
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ Manual reduce for sum
|
||||
let total = 0
|
||||
for (const o of orders) total += o.amount
|
||||
|
||||
// ✅
|
||||
const total = orders.reduce((sum, o) => sum + o.amount, 0)
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ Manual object copy + override
|
||||
const updated = Object.assign({}, user)
|
||||
updated.name = "Alice"
|
||||
|
||||
// ✅
|
||||
const updated = { ...user, name: "Alice" }
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ Existence check before property access
|
||||
const city = user.address ? user.address.city : undefined
|
||||
|
||||
// ✅
|
||||
const city = user.address?.city
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Destructuring & Spread {#destructuring}
|
||||
|
||||
```ts
|
||||
// ❌ Separate variable assignments
|
||||
const name = user.name
|
||||
const age = user.age
|
||||
|
||||
// ✅
|
||||
const { name, age } = user
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ Index access for array elements
|
||||
const first = arr[0]
|
||||
const second = arr[1]
|
||||
|
||||
// ✅
|
||||
const [first, second] = arr
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ Merging arrays with concat
|
||||
const merged = a.concat(b).concat(c)
|
||||
|
||||
// ✅
|
||||
const merged = [...a, ...b, ...c]
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ Omitting a key by delete (mutates)
|
||||
const copy = { ...obj }
|
||||
delete copy.password
|
||||
|
||||
// ✅ — destructure to omit
|
||||
const { password, ...safe } = obj
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Async / Promises {#async}
|
||||
|
||||
```ts
|
||||
// ❌ Promise chain when async/await is cleaner
|
||||
fetchUser(id)
|
||||
.then(user => fetchOrders(user.id))
|
||||
.then(orders => process(orders))
|
||||
.catch(handleError)
|
||||
|
||||
// ✅
|
||||
try {
|
||||
const user = await fetchUser(id)
|
||||
const orders = await fetchOrders(user.id)
|
||||
process(orders)
|
||||
} catch (e) {
|
||||
handleError(e)
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ Sequential awaits for independent operations
|
||||
const user = await fetchUser(id)
|
||||
const config = await fetchConfig()
|
||||
|
||||
// ✅ — run in parallel
|
||||
const [user, config] = await Promise.all([fetchUser(id), fetchConfig()])
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ Wrapping already-async function in new Promise
|
||||
const result = await new Promise((resolve) => {
|
||||
someAsyncFn().then(resolve)
|
||||
})
|
||||
|
||||
// ✅
|
||||
const result = await someAsyncFn()
|
||||
```
|
||||
|
||||
**Don't `await` inside a `.map()` without `Promise.all` — it sequences what should be parallel.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Functions & Closures {#functions}
|
||||
|
||||
```ts
|
||||
// ❌ Arrow function with unnecessary block body
|
||||
const double = (x: number) => { return x * 2 }
|
||||
|
||||
// ✅
|
||||
const double = (x: number) => x * 2
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ Default parameter with if-guard
|
||||
function greet(name?: string) {
|
||||
if (!name) name = "World"
|
||||
return `Hello, ${name}`
|
||||
}
|
||||
|
||||
// ✅
|
||||
function greet(name = "World") {
|
||||
return `Hello, ${name}`
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ IIFE for no reason in module scope
|
||||
;(function() {
|
||||
const x = compute()
|
||||
doSomething(x)
|
||||
})()
|
||||
|
||||
// ✅ — just top-level statements in a module
|
||||
const x = compute()
|
||||
doSomething(x)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. TypeScript Types {#types}
|
||||
|
||||
```ts
|
||||
// ❌ Explicit return type when inference is obvious
|
||||
function add(a: number, b: number): number {
|
||||
return a + b
|
||||
}
|
||||
|
||||
// ✅ — let TS infer simple return types
|
||||
function add(a: number, b: number) {
|
||||
return a + b
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ any
|
||||
function process(data: any) { ... }
|
||||
|
||||
// ✅ — use unknown + type guard, or a proper type/generic
|
||||
function process<T extends Record<string, unknown>>(data: T) { ... }
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ Redundant interface for single-use inline shape
|
||||
interface UserNameProps { name: string }
|
||||
function UserName({ name }: UserNameProps) { ... }
|
||||
|
||||
// ✅ — inline for single-use
|
||||
function UserName({ name }: { name: string }) { ... }
|
||||
// Extract interface when reused in 2+ places
|
||||
```
|
||||
|
||||
```ts
|
||||
// ❌ Type assertion (as) to silence a real type error
|
||||
const el = document.getElementById("app") as HTMLDivElement
|
||||
el.innerText = "hi" // crashes if el is null
|
||||
|
||||
// ✅
|
||||
const el = document.getElementById("app")
|
||||
if (!(el instanceof HTMLDivElement)) throw new Error("Missing #app")
|
||||
el.innerText = "hi"
|
||||
```
|
||||
|
||||
**Prefer `type` for unions/intersections/aliases; `interface` for extensible object shapes.**
|
||||
|
||||
---
|
||||
|
||||
## 6. React (if applicable) {#react}
|
||||
|
||||
```tsx
|
||||
// ❌ Effect for derived state
|
||||
const [doubled, setDoubled] = useState(0)
|
||||
useEffect(() => { setDoubled(count * 2) }, [count])
|
||||
|
||||
// ✅ — compute during render
|
||||
const doubled = count * 2
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ❌ useCallback everywhere by default
|
||||
const handler = useCallback(() => doSomething(id), [id])
|
||||
|
||||
// ✅ — only when passed to memoized child or used as effect dep
|
||||
// Otherwise: const handler = () => doSomething(id)
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ❌ Passing object literal as prop (new reference each render)
|
||||
<Component config={{ debug: true }} />
|
||||
|
||||
// ✅
|
||||
const config = useMemo(() => ({ debug: true }), [])
|
||||
<Component config={config} />
|
||||
// Or if truly static: define outside component
|
||||
const CONFIG = { debug: true }
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ❌ Index as key in list that can reorder/filter
|
||||
items.map((item, i) => <Row key={i} {...item} />)
|
||||
|
||||
// ✅
|
||||
items.map(item => <Row key={item.id} {...item} />)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to TS/JS {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `== null` (loose) | `=== null` or `?? / ?.` |
|
||||
| `typeof x === "undefined"` | `x === undefined` or `x == null` (when both null/undefined ok) |
|
||||
| `!!x` when boolean coercion is implied | `Boolean(x)` for clarity, or just `x` in conditionals |
|
||||
| `var` | `const` by default, `let` when reassigned |
|
||||
| `for...in` on arrays | `for...of` or array methods |
|
||||
| String template literal with no interpolation | plain string `'...'` |
|
||||
| `console.log` left in production code | remove or use a logger |
|
||||
| `Object.keys(obj).forEach(...)` | `for (const [k, v] of Object.entries(obj))` |
|
||||
| Nested ternaries beyond 2 levels | if/else or early return |
|
||||
| `try { ... } catch (e) {}` (silent swallow) | log or rethrow |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
---
|
||||
name: web-media-getter
|
||||
description: "One query across free image / video / GIF APIs (stock + historical/archival + GIF engines), returning normalized, license-tagged results with optional top-K download + attribution sidecar. The retrieval peer to local semantic search and generative media."
|
||||
risk: safe
|
||||
source: community
|
||||
source_type: community
|
||||
source_repo: connerkward/web-media-getter-skill
|
||||
date_added: "2026-06-16"
|
||||
author: Conner K Ward
|
||||
license: MIT
|
||||
tags:
|
||||
- media
|
||||
- images
|
||||
- video
|
||||
- gif
|
||||
- stock
|
||||
- archival
|
||||
- attribution
|
||||
tools:
|
||||
- claude-code
|
||||
- antigravity
|
||||
- cursor
|
||||
- gemini-cli
|
||||
- codex-cli
|
||||
---
|
||||
## When to Use
|
||||
|
||||
Use when a task needs a REAL or ARCHIVAL photo / clip (hero, texture, reference, historical footage) or a reaction / animated GIF, rather than a generated one — fan out across free image/video/GIF sources in one query and download license-tagged results.
|
||||
|
||||
_Source: [connerkward/web-media-getter-skill](https://github.com/connerkward/web-media-getter-skill) (MIT)._
|
||||
|
||||
# web-media
|
||||
|
||||
Query many free image/video sources in one fan-out, get a normalized result list,
|
||||
optionally download top-K with an attribution sidecar. Zero-dep stdlib script.
|
||||
|
||||
**Script:** `webmedia.py` (in this dir). **Keys:** `PEXELS_API_KEY`, `PIXABAY_API_KEY`
|
||||
in `central/.env` (optional — the 5 no-key sources work without them).
|
||||
|
||||
## Sources
|
||||
|
||||
| Source | Key? | Best for | Media |
|
||||
|--------|------|----------|-------|
|
||||
| openverse | none | CC web images (Flickr, museums) | image |
|
||||
| wikimedia | none | factual / historical / landmark photos | image |
|
||||
| internetarchive | none | **historical/archival** images + films | image, video |
|
||||
| loc | none | historical US prints/photos | image |
|
||||
| nasa | none | space imagery + video | image, video |
|
||||
| pexels | free key | modern stock photos + **short video clips** | image, video |
|
||||
| pixabay | free key | modern photos/illustrations + **short clips** | image, video |
|
||||
| klipy | free key | **GIFs** — recommended (free, unlimited, Tenor drop-in) | gif |
|
||||
| giphy | free key | **GIFs** — biggest library (prod key needs approval) | gif |
|
||||
|
||||
GIF sources fire only with `--type gif`. Keys: `KLIPY_API_KEY`, `GIPHY_API_KEY`
|
||||
in `central/.env`. (tenor adapter removed — Google EOL'd the API 2026-06-30.)
|
||||
**klipy** is the one to get (free + unlimited);
|
||||
its adapter is **unverified — assumes Tenor-compatible** request/response;
|
||||
verify against docs.klipy.com when you key it. `webmedia.py "shrug" --type gif --count 6 --json`
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
webmedia.py "1950s street scene" --type image --count 8 --json
|
||||
webmedia.py "rocket launch" --type video --source nasa,internetarchive
|
||||
webmedia.py "car factory 1930s" --source all --download --out /tmp/cars
|
||||
```
|
||||
|
||||
- `--source all` (default) | `nokey` (no-key only) | comma list (`wikimedia,pexels`)
|
||||
- `--type image|video` · `--count N` · `--json` · `--download --out DIR`
|
||||
- `--download` fetches each result's direct media URL and writes `attribution.json`
|
||||
(source, author, license, url, page_url) alongside the files.
|
||||
|
||||
## Record schema
|
||||
|
||||
`{source, title, url, thumb, dl, page_url, author, license, w, h, type}` —
|
||||
`dl` is the directly-downloadable media URL (None when only a page exists).
|
||||
|
||||
## The video caveat (important)
|
||||
|
||||
Archival sources (Internet Archive, Europeana, LoC) host **whole films/documentaries**,
|
||||
not single shots. So:
|
||||
- **Modern single clip** → `pexels` / `pixabay` (born as short clips, direct MP4). Done.
|
||||
- **Historical single shot** → retrieve the IA film here, then extract the shot:
|
||||
- **Twelve Labs** Marengo search (free 600 min) — pass the IA public MP4 URL, get a
|
||||
timestamped moment for "car on assembly line", clip with ffmpeg. Semantic, cheap.
|
||||
- or **PySceneDetect** (free, local) to cut the film into shots, then rank keyframes
|
||||
with CLIP via the `muser` skill. Fully offline.
|
||||
|
||||
## Audio: freesound + audio QA
|
||||
|
||||
`webmedia.py` is image/video. For **sound effects** (real, CC-licensed) and for
|
||||
**judging audio** (since Claude can't hear), two sibling scripts live in
|
||||
`central/scripts/`:
|
||||
|
||||
- **`freesound-fetch.py "<query>" [count] [max_sec] [out_dir]`** — searches freesound.org
|
||||
and downloads short hq-mp3 previews. Prints one JSON line per file with
|
||||
`license`/`user` for attribution. Key: `FREESOUND_API_KEY` in `central/.env`
|
||||
(token-based read; full originals would need OAuth — previews suffice for SFX).
|
||||
- **`audio-judge.py <file> "<target>"`** — sends the clip to OpenAI `gpt-audio`
|
||||
(audio-native) and returns JSON `{heard, score, matches, suggestion}`, enabling a
|
||||
generate/fetch → judge → iterate loop. Auto-sources a real `sk-` `OPENAI_API_KEY`
|
||||
from `.env` (ignores a local `lm-studio` stub env var). Pads sub-2s clips so the
|
||||
speech-tuned model doesn't refuse. **Caveat:** it reliably *describes* audio and
|
||||
filters obvious mismatches, but it is NOT a trustworthy judge of subjective qualities
|
||||
like "grating" — it labels nearly any beep "sharp/high-pitched". Use it to cull, not
|
||||
to make the final aesthetic call; confirm by ear.
|
||||
|
||||
## Where this fits
|
||||
|
||||
This is the **internet-retrieval** capability — peer to `muser` (local semantic search)
|
||||
and `fal` (generate). A future `media` router would fan out across all three and rank
|
||||
candidates by relevance (CLIP), handing aesthetic spreads to `lookdev`. Don't build that
|
||||
router until the model demonstrably mis-routes without it.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Results depend on third-party API availability, quotas, credentials, and license metadata quality.
|
||||
- License tags and attribution fields must still be reviewed before commercial or public use.
|
||||
- Relevance ranking can find plausible assets, but final aesthetic fit, brand safety, and audio suitability require human inspection.
|
||||
+9
-9
@@ -133,11 +133,11 @@ How · Why · What · Best · Full · Real · Free · New · Step-by-Step · Com
|
||||
|
||||
## Tags Strategy
|
||||
|
||||
Use for every mode that includes tags (A-F). Generate 15-20 tags using this mix:
|
||||
Use for long-form modes (A-D) that include tags. Generate 14-19 tags using this mix. For short-form (E-F), see the Shorts section for the 5-8 tag rule.
|
||||
|
||||
| Type | Count | Rule |
|
||||
|---|---|---|
|
||||
| Exact match primary keyword | 2 | Must match Step 1 target keyword exactly |
|
||||
| Exact match primary keyword | 1 | Must match Step 1 target keyword exactly |
|
||||
| Broad topic | 3-4 | 1-2 word umbrella terms |
|
||||
| Long-tail (3-5 words) | 5-6 | Pulled from Step 1 research |
|
||||
| Question-based | 2 | "how to [topic]", "what is [topic]" |
|
||||
@@ -311,7 +311,7 @@ Character count: [N]/70
|
||||
5. [secondary keyword from Step 1 research]
|
||||
|
||||
⑤ TAGS
|
||||
[tag1], [tag2], [tag3] ... [tag15-20 total]
|
||||
[tag1], [tag2], [tag3] ... [tag14-19 total]
|
||||
Total character count: [N]/500
|
||||
|
||||
⑥ HASHTAGS
|
||||
@@ -399,7 +399,7 @@ Chapters in description: Yes / No
|
||||
Links/CTA present: Yes / No
|
||||
|
||||
TAGS ANALYSIS
|
||||
Count: [N] (ideal: 15-20)
|
||||
Count: [N] (ideal: 14-19)
|
||||
Tag type coverage: [which of 7 types are missing]
|
||||
|
||||
HASHTAG ANALYSIS
|
||||
@@ -429,7 +429,7 @@ Character count: [N]/70
|
||||
[Full 3-block description]
|
||||
|
||||
④ REWRITTEN TAGS
|
||||
[15-20 tags across all 7 types]
|
||||
[14-19 tags across all 7 types]
|
||||
|
||||
⑤ REWRITTEN HASHTAGS
|
||||
#Tag1 #Tag2 #Tag3 #Tag4 #Tag5 [#Tag6 #Tag7 optional]
|
||||
@@ -507,7 +507,7 @@ Character count: [N]/70
|
||||
5. [show name + topic]
|
||||
|
||||
⑤ TAGS
|
||||
[tag1], [tag2] ... [tag15-20 — include show + guest name]
|
||||
[tag1], [tag2] ... [tag14-19 — include show + guest name]
|
||||
Total: [N]/500
|
||||
|
||||
⑥ HASHTAGS
|
||||
@@ -605,7 +605,7 @@ Timestamps present: Yes / No
|
||||
Platform links present: Yes / No
|
||||
|
||||
TAGS ANALYSIS
|
||||
Count: [N] (ideal: 15-20)
|
||||
Count: [N] (ideal: 14-19)
|
||||
Show / guest name as tags: Yes / No
|
||||
|
||||
TOPICS / TIMESTAMPS ANALYSIS
|
||||
@@ -628,7 +628,7 @@ Character count: [N]/70
|
||||
[3-block structure, guest bio in Block 2, platform links in Block 3]
|
||||
|
||||
④ REWRITTEN TAGS
|
||||
[15-20 tags including show + guest name]
|
||||
[14-19 tags including show + guest name]
|
||||
|
||||
⑤ REWRITTEN HASHTAGS
|
||||
#Tag1 #Tag2 #Tag3 #Tag4 #Tag5 [#Tag6 #Tag7 optional]
|
||||
@@ -866,7 +866,7 @@ Then run the Quality Checklist below.
|
||||
- [ ] Hashtags on final line only
|
||||
|
||||
### Tags & Hashtags
|
||||
- [ ] 15-20 tags, under 500 chars, all 7 types represented
|
||||
- [ ] 14-19 tags, under 500 chars, all 7 types represented
|
||||
- [ ] No hashtag symbols in the tags field
|
||||
- [ ] 5-8 hashtags (3-5 for short-form), CamelCase, strongest 3 first
|
||||
- [ ] Hashtag set differs from recent uploads
|
||||
|
||||
Reference in New Issue
Block a user