agentscamp 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -19
- package/content/manifest.json +212 -2
- package/content/skills/circular-dependency-breaker.md +48 -0
- package/content/skills/commit-splitter.md +54 -0
- package/content/skills/dashboard-designer.md +38 -0
- package/content/skills/deadlock-diagnoser.md +45 -0
- package/content/skills/feature-flag-retirer.md +44 -0
- package/content/skills/flamegraph-analyzer.md +35 -0
- package/content/skills/git-blame-investigator.md +34 -0
- package/content/skills/graphql-schema-designer.md +49 -0
- package/content/skills/hallucination-evaluator.md +40 -0
- package/content/skills/integration-test-designer.md +81 -0
- package/content/skills/model-router-designer.md +39 -0
- package/content/skills/onboarding-guide-writer.md +84 -0
- package/content/skills/rbac-designer.md +82 -0
- package/content/skills/release-notes-writer.md +78 -0
- package/content/skills/web-vitals-optimizer.md +34 -0
- package/dist/index.js +400 -43
- package/package.json +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "rbac-designer"
|
|
3
|
+
description: "Design the authorization model itself — fine-grained permissions on resources composed into roles, with the right amount of resource/tenant scoping — instead of scattering role-name checks through handlers. Use when building multi-user or multi-tenant authorization, when `if user.isAdmin` checks are sprawling across the codebase, or when 'who can do what' needs a real model rather than ad-hoc gates."
|
|
4
|
+
allowed-tools: "Read, Grep, Glob"
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Design the authorization model — the permission system itself — rather than reviewing one that exists. The job is to decide *what capabilities exist*, *how they compose into roles*, *how far each check is scoped*, and *where enforcement lives* — so that application code asks one question, **"can this actor perform this action on this resource?"**, instead of the brittle `if (user.isAdmin)` checks that breed across handlers and rot the moment requirements change. The skill reads the codebase to find the resources, actions, and existing role checks, then produces a concrete permission/role model, a single central enforcement design, and explicit decisions on hierarchy, default-deny, tenant isolation, and audit.
|
|
9
|
+
|
|
10
|
+
## When to use this skill
|
|
11
|
+
|
|
12
|
+
- Building authorization for a multi-user or multi-tenant (SaaS) product, where access depends on both *who* the actor is and *which org/project/resource* they are touching.
|
|
13
|
+
- When ad-hoc role checks — `if (user.role === 'admin')`, `user.isManager`, `@RequireRole("OWNER")` — are sprawling through controllers and every new rule means a code hunt.
|
|
14
|
+
- When "who can do what" is tribal knowledge with no single model, or a customer/security review asks you to document the permission matrix.
|
|
15
|
+
- Before adding roles, a permissions UI, custom roles, or an admin-impersonation feature on top of a system that hardcodes role names.
|
|
16
|
+
|
|
17
|
+
> [!WARNING]
|
|
18
|
+
> Scattering role-name checks (`isAdmin`, `role === "manager"`) through the codebase instead of checking granular permissions makes every permission change a risky code hunt and guarantees missed spots — the endpoint you forget is the privilege-escalation bug. Model permissions, compose them into roles, and enforce in one place so a grant change is one edit and coverage is greppable.
|
|
19
|
+
|
|
20
|
+
## Instructions
|
|
21
|
+
|
|
22
|
+
1. **Inventory resources and actions before inventing roles.** Glob the routers, controllers, and data models (`**/routes/**`, `**/*controller*`, `**/models/**`, `**/entities/**`) and list every *resource* (invoice, project, user, billing-account) and every *action* on it (read, create, update, delete, approve, export, invite). Permissions are these `resource:action` pairs — `invoice:read`, `invoice:approve`, `member:invite`. Name them after the capability, not the role, so the same permission can be granted to many roles. This list is the vocabulary; everything else composes it.
|
|
23
|
+
2. **Compose permissions into roles — never the reverse.** Define roles as *named sets of permissions* (`viewer = {invoice:read, project:read}`, `approver = viewer ∪ {invoice:approve}`). Code checks `can(actor, "invoice:approve", invoice)`, never `actor.role === "approver"`. This is the whole point: when product says "approvers can now export", you edit one role→permission map, not every handler. Grep the codebase for existing `role ===`, `isAdmin`, `hasRole`, `@Role`, `@PreAuthorize` sites and list each as a call site to migrate to a permission check.
|
|
24
|
+
3. **Pick the granularity you actually need — and stop there.** Choose explicitly among three:
|
|
25
|
+
- **Pure RBAC** (roles → permissions, global) — fine for single-tenant internal tools where a role means the same thing everywhere.
|
|
26
|
+
- **Scoped RBAC** (role *within* an org/project/workspace) — the default for SaaS: a user is `admin` of org A and `viewer` of org B, and every check is scoped to the resource's tenant. Model the assignment as `(actor, role, scope)`.
|
|
27
|
+
- **ReBAC / ABAC** (permission depends on the specific object's relationship or attributes — "owner of THIS document", "assignee of THIS ticket") — reach for this *only* for the per-object rules; let scoped RBAC carry the rest. Do **not** stand up a full policy engine if scoped RBAC suffices.
|
|
28
|
+
State the choice and the reason; mixing scoped RBAC for the 90% with a handful of ReBAC ownership rules is usually correct.
|
|
29
|
+
4. **Centralize enforcement in one authorization layer.** Design a single policy function/middleware — `authorize(actor, action, resource)` (or a guard/policy class) — that every entry point routes through: HTTP handlers, GraphQL resolvers, queue/cron jobs, and admin scripts. No handler should make its own role decision. Specify *where* it sits (e.g. middleware that resolves the resource, computes the actor's permissions in that scope, and allows/denies) so coverage is provable by reading one module, not auditing hundreds.
|
|
30
|
+
5. **Default-deny, explicitly.** The policy layer returns deny unless a rule grants. A new route with no policy attached must fail closed (no access), never fall through to allowed. Specify how an un-annotated/un-checked endpoint is detected and rejected (e.g. a route-level assertion that a policy was declared) so "forgot to add a check" becomes a *deny*, not a hole.
|
|
31
|
+
6. **Decide role hierarchy and inheritance deliberately.** If `admin` should imply everything `editor` can do, model it as *permission inheritance* (admin's permission set ⊇ editor's) computed when permissions are resolved — not as a chain of `if role >= X` comparisons, which reintroduce role-name logic. Keep the hierarchy shallow and flatten to an effective permission set at check time; document the partial order so "what can role X do" is answerable from the model alone.
|
|
32
|
+
7. **Scope every check to the resource — at the API *and* data layer.** A valid role on tenant A must never act on tenant B's data. The permission check answers "may this actor approve invoices?"; the *data* layer must additionally bind the query to the resource's owner/tenant (`WHERE org_id = :actorOrg`, a tenant filter, or row-level security), so changing an id in the URL cannot reach another tenant's row. Specify both: the policy check *and* the scoped query. Skipping the data-layer scope is the classic IDOR — the permission passed, but the object belonged to someone else.
|
|
33
|
+
8. **Make it auditable.** Design the model so authorization decisions are explainable and logged: who has which role in which scope (queryable), what permissions a role grants (the map), and a decision log for sensitive actions (actor, action, resource, allow/deny, why). A model nobody can answer "who can approve invoices in org X?" about is not finished.
|
|
34
|
+
|
|
35
|
+
> [!NOTE]
|
|
36
|
+
> RBAC without per-tenant/resource scoping is the most common real failure: a legitimate `admin` of org A passes the `invoice:approve` permission check and then approves org B's invoice because the query fetched by id alone. The permission says *what* the actor may do; the scope says *to which objects*. Both are required — design them together, not as an afterthought.
|
|
37
|
+
|
|
38
|
+
## Output
|
|
39
|
+
|
|
40
|
+
A concrete authorization design with four parts:
|
|
41
|
+
|
|
42
|
+
1. **The permission/role model** — the resource×action permission list, the role→permission map (with inheritance), and the assignment shape (`(actor, role)` for pure RBAC or `(actor, role, scope)` for scoped/multi-tenant).
|
|
43
|
+
2. **The central enforcement design** — the single `authorize(actor, action, resource)` entry point, where it sits, what it resolves, and the list of existing scattered role checks to migrate into it.
|
|
44
|
+
3. **Granularity decision** — pure RBAC vs scoped RBAC vs ReBAC/ABAC, stated with the reason, including which specific rules (if any) need per-object relationship checks.
|
|
45
|
+
4. **The hardening decisions** — default-deny mechanism, role hierarchy/partial order, the API-and-data-layer scoping rule per resource, and the audit/decision-log plan.
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
Authorization model — scope: src/routes/**, src/models/** (multi-tenant SaaS)
|
|
49
|
+
Granularity: SCOPED RBAC (role within org) + ReBAC for document ownership
|
|
50
|
+
|
|
51
|
+
PERMISSIONS (resource:action)
|
|
52
|
+
invoice: read, create, update, delete, approve, export
|
|
53
|
+
member: read, invite, remove
|
|
54
|
+
doc: read, edit (edit also gated by ownership — see ReBAC)
|
|
55
|
+
|
|
56
|
+
ROLES → PERMISSIONS (within an org)
|
|
57
|
+
viewer = {invoice:read, member:read, doc:read}
|
|
58
|
+
editor = viewer ∪ {invoice:create, invoice:update, doc:edit}
|
|
59
|
+
approver = editor ∪ {invoice:approve, invoice:export}
|
|
60
|
+
admin = approver ∪ {member:invite, member:remove} # inherits all above
|
|
61
|
+
|
|
62
|
+
ASSIGNMENT: (user_id, role, org_id) # scoped — same user differs per org
|
|
63
|
+
|
|
64
|
+
ENFORCEMENT (one layer)
|
|
65
|
+
authorize(actor, action, resource):
|
|
66
|
+
1. resolve actor's role in resource.org_id -> effective permission set
|
|
67
|
+
2. deny if action ∉ permissions # DEFAULT-DENY
|
|
68
|
+
3. ReBAC rule: doc:edit also requires resource.owner_id == actor.id
|
|
69
|
+
Every route/resolver/job calls authorize(); routes with no policy → fail closed.
|
|
70
|
+
|
|
71
|
+
MIGRATE these scattered checks into authorize():
|
|
72
|
+
- src/routes/invoices.ts:41 if (user.isAdmin) -> can(..,"invoice:approve",inv)
|
|
73
|
+
- src/routes/members.ts:88 user.role === "owner" -> can(..,"member:invite",org)
|
|
74
|
+
|
|
75
|
+
DATA-LAYER SCOPING (prevents IDOR — required alongside the permission check)
|
|
76
|
+
invoices: WHERE id = :id AND org_id = :actorOrg # not findById(id) alone
|
|
77
|
+
docs: WHERE id = :id AND org_id = :actorOrg # + ReBAC owner check above
|
|
78
|
+
|
|
79
|
+
AUDIT
|
|
80
|
+
- role assignments queryable: "who can invoice:approve in org X?"
|
|
81
|
+
- decision log on approve/export/remove: actor, action, resource, allow/deny
|
|
82
|
+
```
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "release-notes-writer"
|
|
3
|
+
description: "Write user-facing release notes — the curated 'what's new and what it means for you' — by starting from the real changes (git log / merged PRs / the changelog since the last release) and translating developer-speak into user impact, grouped by what the user cares about with breaking changes and required actions surfaced first. Use when shipping a release to users or customers and the raw commit log isn't something a user should read, when you need a published GitHub-release / blog / in-app announcement, or when a breaking change must be made unmissable so upgrades don't break."
|
|
4
|
+
allowed-tools: "Read, Grep, Glob, Bash, Write"
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
A changelog records *what changed*; release notes explain *what it means for the person upgrading*. Pasting raw conventional-commit lines into a release fails users twice: it buries the two things they actually need under twenty refactors and dependency bumps, and it hides the one breaking change that will take down their integration on upgrade. This skill reads the real changes since the last release, throws away the churn users don't care about, translates the rest into impact-and-action language grouped the way a user thinks (New / Improved / Fixed), and puts breaking changes and required steps at the top where they cannot be missed.
|
|
9
|
+
|
|
10
|
+
## When to use this skill
|
|
11
|
+
|
|
12
|
+
- You are shipping a release to end users or API consumers and the commit log / changelog is not something they should read.
|
|
13
|
+
- You need a GitHub release body, a "what's new" blog post, or an in-app changelog entry — not an internal diff.
|
|
14
|
+
- A release contains a breaking change or a required migration and you need it surfaced first, with the exact action spelled out.
|
|
15
|
+
- You have a draft changelog (e.g. from `changelog-from-prs`) and need to convert it into something audience-appropriate and benefit-led.
|
|
16
|
+
|
|
17
|
+
## Instructions
|
|
18
|
+
|
|
19
|
+
1. **Start from the real changes, not memory.** Establish the range from the last released tag and pull the actual shipped work — never invent items or summarize from what you "think" landed.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
LAST_TAG=$(git describe --tags --abbrev=0)
|
|
23
|
+
git log "$LAST_TAG"..HEAD --no-merges --pretty='%s'
|
|
24
|
+
gh pr list --state merged --search "merged:>$(git log -1 --format=%cI "$LAST_TAG")" \
|
|
25
|
+
--json number,title,labels,body --limit 200
|
|
26
|
+
```
|
|
27
|
+
If a `CHANGELOG.md` already covers this range, read it as the source of record instead of re-deriving from commits.
|
|
28
|
+
|
|
29
|
+
2. **Identify the audience and pin the voice.** End users, API consumers, and self-hosting operators need different notes. Look at where this publishes (`README`, app store text, GitHub release, developer docs) and at past release notes for tone. API/SDK consumers need exact symbol/endpoint names and code; end users need plain-language benefit and a screenshot-level description, not the function that changed.
|
|
30
|
+
|
|
31
|
+
3. **Drop the churn.** Remove everything a user cannot observe: internal refactors, test-only changes, CI/build config, dependency bumps with no behavior change, lint/format, doc-internal edits. A 60-commit release is often 5 user-facing notes. Keep a dependency bump *only* if it fixes a user-visible bug or a known CVE the user is exposed to — and say which.
|
|
32
|
+
|
|
33
|
+
4. **Extract breaking changes and required actions first — this is the part that breaks systems if you get it wrong.** Scan PR bodies/commits for `BREAKING`, `!` in conventional-commit type, removed/renamed exports, flags, endpoints, config keys, changed defaults, and tightened validation. For each, write: what changed, who it affects, and the **exact action** the user must take to upgrade safely (the command, the renamed field, the config edit), with a link to a migration guide if one exists. Cross-check against the SemVer bump — a major bump with zero listed breaking changes means you missed one.
|
|
34
|
+
|
|
35
|
+
5. **Group the rest by what the user cares about, in benefit language.** Use **New** (capabilities they didn't have), **Improved** (things that got faster/better/clearer), **Fixed** (bugs that affected them). Rewrite each from implementation to impact: not "refactor `ExportService` to stream rows" but "Exports of large datasets no longer time out." For notable new features add a one-line *how to use it* (the flag, the menu, the endpoint). Order within each group by how many users it affects, not by PR number.
|
|
36
|
+
|
|
37
|
+
6. **Append upgrade instructions and links.** Give the concrete upgrade step for this project (`npm i pkg@2.0.0`, the container tag, the migration command) and link the full changelog, the migration guide, and relevant docs for new features. Keep PR/issue references only where a user might want the detail — don't litter end-user notes with `(#1423)`.
|
|
38
|
+
|
|
39
|
+
7. **Lead with a one-line summary and write the header.** Open with a single sentence a user can skim ("v2.0 adds scheduled exports and a JSON API; one breaking change to the auth header"). Then breaking/action-required, then New / Improved / Fixed, then upgrade steps. Emit it as Markdown ready to paste — publish nothing yourself.
|
|
40
|
+
|
|
41
|
+
> [!WARNING]
|
|
42
|
+
> Release notes are not a commit dump. Pasting raw conventional-commit lines (`feat:`, `chore(deps):`, `refactor:`) buries the few items users need under noise they cannot act on, and makes the notes look auto-generated and untrustworthy. Translate to impact and delete the rest.
|
|
43
|
+
|
|
44
|
+
> [!CAUTION]
|
|
45
|
+
> A breaking change hidden mid-list — or omitted because it "looked small" — is how you break your users' systems on upgrade. Every removed/renamed flag, changed default, tightened validation, or altered response shape goes in a **Breaking changes / action required** block at the very top, with the exact migration step. If the SemVer bump is major but you wrote no breaking items, stop and re-scan; you missed one.
|
|
46
|
+
|
|
47
|
+
## Output
|
|
48
|
+
|
|
49
|
+
Publishable release notes — breaking-first, benefit-led — ready to paste into a GitHub release, blog post, or in-app changelog:
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
# v2.0.0 — 2026-06-17
|
|
53
|
+
|
|
54
|
+
Scheduled exports and a new JSON API. **One breaking change:** the API auth header was renamed — update integrations before upgrading.
|
|
55
|
+
|
|
56
|
+
## ⚠️ Breaking changes — action required
|
|
57
|
+
- **Auth header renamed `X-Token` → `Authorization: Bearer <key>`.** Requests using `X-Token` now return `401`. Update your client before upgrading. See the [migration guide](https://docs.example.com/migrate/v2).
|
|
58
|
+
- **`export` config key `format: csv` is no longer the default** — it now defaults to `json`. Add `format: csv` explicitly to keep the old behavior.
|
|
59
|
+
|
|
60
|
+
## New
|
|
61
|
+
- **Scheduled exports.** Set a cron in Settings → Exports to deliver reports automatically — no more manual runs.
|
|
62
|
+
- **JSON API for reports.** Pull report data programmatically via `GET /api/v2/reports`. See the [API docs](https://docs.example.com/api).
|
|
63
|
+
|
|
64
|
+
## Improved
|
|
65
|
+
- Exports of large datasets no longer time out — they now stream and complete in seconds.
|
|
66
|
+
- Faster dashboard load on accounts with many projects.
|
|
67
|
+
|
|
68
|
+
## Fixed
|
|
69
|
+
- Fixed a crash when a saved filter referenced a deleted field.
|
|
70
|
+
- Times now display in the account's timezone instead of UTC.
|
|
71
|
+
|
|
72
|
+
## Upgrade
|
|
73
|
+
1. Update auth headers per the breaking change above.
|
|
74
|
+
2. `npm i your-pkg@2.0.0` (or pull image tag `:2.0.0`).
|
|
75
|
+
3. Run `your-cli migrate` to apply the config default change.
|
|
76
|
+
|
|
77
|
+
[Full changelog](https://github.com/org/repo/compare/v1.6.0...v2.0.0)
|
|
78
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "web-vitals-optimizer"
|
|
3
|
+
description: "Diagnose and fix Core Web Vitals — LCP, CLS, and INP — by treating real-user field data at p75 as the source of truth, using Lighthouse/WebPageTest only to find the at-fault element, script, or shift, then applying the one targeted fix per metric and re-measuring. Use when a page feels slow, scores poorly on PageSpeed/Lighthouse, or fails CWV in CrUX/RUM field data."
|
|
4
|
+
allowed-tools: "Read, Grep, Glob, Edit, Bash"
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
A page can score 98 in Lighthouse and still fail Core Web Vitals for real users — because Lighthouse measures one throttled load on your machine, while Google ranks you on p75 of *field* data from real devices and networks. This skill refuses to optimize the lab number. It pulls the field metrics first, uses lab tools only to find the specific element, script, or shift at fault, applies the one fix that addresses *that* cause, and re-measures against the field target — not the audit.
|
|
9
|
+
|
|
10
|
+
## When to use this skill
|
|
11
|
+
|
|
12
|
+
- A page is flagged "Needs improvement" or "Poor" for LCP, CLS, or INP in Search Console / CrUX / your RUM, even if Lighthouse looks fine.
|
|
13
|
+
- The hero or main content visibly pops in late, or the page jumps as images, ads, fonts, or banners load.
|
|
14
|
+
- Tapping a button, opening a menu, or typing feels laggy after the page looks ready.
|
|
15
|
+
- You're about to "fix performance" by chasing a higher Lighthouse score and want to target what real users actually feel.
|
|
16
|
+
|
|
17
|
+
## Instructions
|
|
18
|
+
|
|
19
|
+
1. **Get the field data first — it is the only source of truth.** Pull p75 LCP, CLS, and INP from CrUX (PageSpeed Insights field section, the CrUX API, or BigQuery) for the specific URL or origin, segmented by phone vs. desktop. If you have RUM (`web-vitals` library, your analytics), prefer it — it's per-page and current. Thresholds: LCP ≤ 2.5s, CLS ≤ 0.1, INP ≤ 200ms, all at **p75**. Write down the failing metric(s) and the gap to target before opening a single file.
|
|
20
|
+
2. **Use lab tools only to find the culprit, never as the goal.** Run Lighthouse / WebPageTest / a local trace to *locate* what's at fault — the LCP element, the layout-shift sources, the long tasks blocking interaction. The lab gives you the "what and where"; the field data decides whether you've actually won. A green lab score does not close a failing field metric.
|
|
21
|
+
3. **LCP — find the LCP element, then speed its delivery.** Read the Lighthouse "Largest Contentful Paint element" (usually the hero image or a large heading/text block). If it's an image: ensure it is **not** `loading="lazy"`, add `fetchpriority="high"`, `<link rel="preload" as="image">` it (with `imagesrcset`), serve a right-sized AVIF/WebP at the displayed dimensions, and host it on a fast/CDN origin. If it's blocked by render-blocking CSS/JS, inline critical CSS and `defer`/async the rest. If TTFB itself is slow (>800ms), fix the server/cache before touching the front end — you can't paint what hasn't arrived.
|
|
22
|
+
4. **CLS — reserve space and stop late insertions.** For every image/video/iframe/ad/embed, set explicit `width`/`height` or `aspect-ratio` so the browser reserves the box before content loads. Never inject content *above* existing content after load (cookie/consent banners, late-arriving ads, "you have a new message" bars) — reserve their slot or render them in a fixed overlay. For font swap, `<link rel="preload">` the font and use `font-display: optional` or a `size-adjust`/`ascent-override` `@font-face` to match fallback metrics so the swap doesn't reflow text.
|
|
23
|
+
5. **INP — shorten the work between tap and paint.** Find the slow interaction in a performance trace and read the long tasks (>50ms) on the main thread. Break long JS into chunks and `yield` to the main thread (`await scheduler.yield()` or `setTimeout(0)`) so input can be handled; defer or remove unnecessary hydration and heavy third-party scripts (analytics, chat, A/B tools) that monopolize the thread; keep event handlers cheap — do the visual update first, then debounce/queue the expensive work. Don't run layout-thrashing reads/writes inside the handler.
|
|
24
|
+
6. **Change one thing, then re-measure against the field metric.** After each fix, re-run the lab trace to confirm the mechanism (LCP element now preloaded, shift gone, long task split). But only the **p75 field metric** trending back under threshold confirms a real win — and field data lags 28 days in CrUX, so verify with RUM for fast feedback. If the field metric doesn't move, you fixed the wrong cause; go back to the trace.
|
|
25
|
+
|
|
26
|
+
> [!WARNING]
|
|
27
|
+
> Optimizing the Lighthouse lab score while p75 field data still fails is optimizing the wrong number. Lighthouse is one throttled synthetic load; CrUX is the 75th percentile of real devices and networks, and that is what ranks. Ship for the field metric — a 100 lab score with "Poor" field LCP is still a failing page.
|
|
28
|
+
|
|
29
|
+
> [!NOTE]
|
|
30
|
+
> A blanket `loading="lazy"` on every image directly regresses LCP when it lands on the hero/above-the-fold image — the browser delays the very request that defines your LCP. Lazy-load only below-the-fold media; the LCP image must be eager and, ideally, preloaded with `fetchpriority="high"`.
|
|
31
|
+
|
|
32
|
+
## Output
|
|
33
|
+
|
|
34
|
+
Per failing metric: the **specific culprit** (the named LCP element, the elements/sources causing each shift, or the long-task script/handler), the **single targeted fix** as an `Edit` diff (preload tag, `width/height`, `defer`, yield, etc.), and the **p75 field target** to confirm against (LCP ≤ 2.5s / CLS ≤ 0.1 / INP ≤ 200ms) with a note on how to verify it (RUM now, CrUX after the 28-day window). End with the lab mechanism check plus the field metric as the real pass/fail gate.
|