plankc/.claude/skills/frontend-diagnostics/SKILL.md
Mandatory use when adding, modifying, or reviewing diagnostic emission in any frontend pipeline stage. Use when touching emit_ methods, Session::emit_diagnostic, or the Diagnostic/Cause/Claim builder API.
npx skillsauth add plankevm/plank-monorepo frontend-diagnosticsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Every frontend pipeline stage emits diagnostics through dedicated emit_
methods that take minimal domain inputs, build a Diagnostic value, and forward
it to Session. The pipeline never constructs diagnostics inline at the call
site.
Call site (business logic)
| calls emit_foo(index, span, ...)
v
emit_ method (on pipeline struct)
| resolves names/spans via self
| builds Diagnostic via builder API
v
Session::emit_diagnostic(diagnostic)
Diagnostic construction, no format!.Diagnostic.Session::emit_diagnostic.| Method | Purpose |
|--------|---------|
| Diagnostic::error(title) | Start an error diagnostic |
| Diagnostic::warning(title) | Start a warning diagnostic |
| .primary(source_id, span, label) | Convenience: pushes a lone Cause with a single primary annotation |
| .element(impl Into<Element>) | Push any element (Cause, Origin, Message) to primary group |
| .note(message) | Push a note message element |
| .help(message) | Push a help message element |
| .add_claim(claim) | Push a secondary group (renders as separate titled section) |
Groups annotations for a single source file. Converts Into<Element>.
| Method | Purpose |
|--------|---------|
| Cause::new(source_id) | Start a cause for a source file |
| .primary(span, label) | Add a primary annotation (AnnotationKind::Primary) |
| .secondary(span, label) | Add a context annotation (AnnotationKind::Context) |
| .span(span, kind) | Add an unlabeled annotation with explicit AnnotationKind |
Creates a secondary group rendered with secondary_title.
| Method | Purpose |
|--------|---------|
| Claim::new(level, title) | Start a claim (e.g. Level::Note, "entry file") |
| .element(impl Into<Element>) | Push element (Cause, Origin, etc.) |
| .primary(source_id, span, label) | Convenience: lone Cause with primary annotation |
| .note(message) / .help(message) | Push message elements |
| Variant | Purpose |
|---------|---------|
| Element::Cause { source, annotations } | Annotated source snippet (built via Cause) |
| Element::Origin { path: SourceId } | Bare file reference (::: path) |
| Element::Message { level, text } | Footer-style message |
| Element::Suggestion { source, patches } | Patch-based code suggestion |
// BAD: formatting scattered in business logic
let name = self.resolve_name(ident_idx);
self.session.borrow_mut().emit_diagnostic(
Diagnostic::error(format!("cannot find `{name}` in this scope"))
.primary(self.source_id, self.span_of(ident_idx), "not found in this scope")
);
// Call site: passes only what it naturally has
self.emit_unresolved_ident(ident_idx);
// In a diagnostics impl block (typically diagnostics.rs):
impl BlockLowerer<'_> {
fn emit_unresolved_ident(&self, ident: IdentIdx) {
let name = self.resolve_name(ident);
let span = self.span_of(ident);
self.emit_diagnostic(
Diagnostic::error(format!("cannot find `{name}` in this scope"))
.primary(self.source_id, span, "not found in this scope")
);
}
fn emit_diagnostic(&self, diagnostic: Diagnostic) {
self.session.borrow_mut().emit_diagnostic(diagnostic);
}
}
Rules:
emit_ methods live on the pipeline's main struct, grouped in a dedicated
diagnostics.rs file or impl blockself to resolve anything it needsemit_diagnostic helper method forwards to the sessionEach .primary() on Diagnostic creates a separate Cause. To group
multiple annotations into one snippet, build a Cause explicitly:
// Two annotations in ONE snippet (same source):
Diagnostic::error("variable not declared mutable")
.element(
Cause::new(self.source_id)
.primary(assign_span, "assignment to immutable variable")
.secondary(decl_span, "declared here")
)
.help("consider declaring it with `let mut`")
// Two annotations in SEPARATE snippets (different sources):
Diagnostic::error("unresolved import")
.primary(self.source_id, span, "not found in target module")
.add_claim(
Claim::new(Level::Note, "target module")
.element(Element::Origin { path: target_source })
)
When two SourceIds might or might not be the same, branch explicitly:
if self.source_id == prev_source_id {
diagnostic = diagnostic.element(
Cause::new(self.source_id)
.primary(span, "conflicting import")
.secondary(prev_span, prev_label),
);
} else {
diagnostic = diagnostic
.primary(self.source_id, span, "conflicting import")
.element(Cause::new(prev_source_id).secondary(prev_span, prev_label));
}
Default — &mut Session directly: when emit_ methods can take &mut self.
struct Pipeline {
session: Session,
}
impl Pipeline {
fn emit_some_error(&mut self, span: SourceSpan) {
self.session.emit_diagnostic(
Diagnostic::error("...").primary(self.source_id, span, "...")
);
}
}
RefCell — when emit_ needs &self: when business logic holds shared
borrows into the struct during emission (common in tree-walking lowerers):
struct BlockLowerer<'a> {
session: RefCell<&'a mut Session>,
}
impl BlockLowerer<'_> {
fn emit_diagnostic(&self, diagnostic: Diagnostic) {
self.session.borrow_mut().emit_diagnostic(diagnostic);
}
}
Decision rule: methods take &self and read multiple fields while emitting?
Use RefCell. Otherwise &mut Session.
| Mistake | Why it's wrong | Fix |
|---------|---------------|-----|
| Building Diagnostic at the call site | Scatters formatting, noisy call sites, inconsistent messages | Dedicated emit_ method |
| Passing String args to emit_ methods | Forces caller to resolve names, duplicates lookup logic | Pass indices/spans, let emit_ resolve via self |
| panic!/todo! instead of emitting | Kills compilation on first error, no useful output | Emit diagnostic and return a poison/sentinel value |
| Bare Element::Origin without context | Lone file path with no explanation | Wrap in a Claim with descriptive title |
| Same-source annotations in separate Causes | Renders as two disconnected snippet blocks | Group in one Cause with .primary() + .secondary() |
development
Mandatory use when editing or defining data structures in the compiler.
development
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.
development
Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.
development
Run, watch, debug, and extend OpenClaw QA testing with qa-lab and qa-channel. Use when Codex needs to execute the repo-backed QA suite, inspect live QA artifacts, debug failing scenarios, add new QA scenarios, or explain the OpenClaw QA workflow. Prefer the live OpenAI lane with regular openai/gpt-5.4 in fast mode; do not use gpt-5.4-pro or gpt-5.4-mini unless the user explicitly overrides that policy.