
Nostics
- 27 installs
- 191 repo stars
- Updated July 17, 2026
- vercel-labs/nostics
nostics provides diagnostic workflows for agents and apps.
About
The nostics skill from Vercel Labs supports diagnostic workflows for agents and applications, including structured troubleshooting patterns and integration with nostics tooling for runtime inspection and health checks.
- Agent and app diagnostic workflows.
- Nostics tooling integration patterns.
- Structured troubleshooting guidance.
Nostics by the numbers
- 27 all-time installs (skills.sh)
- +2 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #9,601 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
nostics capabilities & compatibility
- Capabilities
- nostics skill
- Works with
- vercel
- Use cases
- debugging
What nostics says it does
nostics
npx skills add https://github.com/vercel-labs/nostics --skill nosticsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 27 |
|---|---|
| repo stars | ★ 191 |
| Last updated | July 17, 2026 |
| Repository | vercel-labs/nostics ↗ |
How do I diagnose agent runtime issues with nostics?
Nostics diagnostics toolkit for agent and application observability workflows.
Who is it for?
Developers debugging agent or app runtime behavior.
Skip if: Skip without nostics tooling context.
When should I use this skill?
nostics diagnostics or agent troubleshooting.
What you get
Diagnostic findings from nostics-guided inspection.
Files
nostics
Every error condition becomes a typed Diagnostic (extends Error) with a stable code, docs URL, and actionable fix. Serializable via toJSON().
Diagnostic: name (the code), message/why (interpolated text), fix?, docs?, sources? ('file:line:column'), cause?, toJSON(). Throw it, catch it with instanceof Diagnostic, send toJSON() across process boundaries.
defineDiagnostics
Returns one callable handle per code. Calling a handle builds a fresh Diagnostic, fires every reporter in order, and returns it. throw the return value to raise (reporters still run, so a thrown diagnostic also reports).
import { createConsoleReporter, defineDiagnostics } from 'nostics'
const diagnostics = /*#__PURE__*/ defineDiagnostics({
docsBase: (code) => `https://nuxt.com/e/${code.replace('NUXT_', '').toLowerCase()}`,
reporters: [/*#__PURE__*/ createConsoleReporter()],
codes: {
NUXT_B1001: {
why: 'Could not compile template.',
fix: 'Check the template for syntax errors.',
},
NUXT_B2011: {
why: (p: { src: string }) => `Invalid plugin "${p.src}". src option is required.`,
fix: 'Pass a string path or an object with a `src` to `addPlugin()`.',
},
NUXT_W9001: { why: 'message', docs: false }, // per-code: string overrides docsBase, false opts out
},
})- `docsBase`
string | (code) => string | undefined: string appends/${code.toLowerCase()}; function returns the full URL (orundefinedto omit). - `codes`: each definition needs
why(string | (params) => string, the only required field, becomesError.message); optionalfix(string | (params) => string) anddocs(string | false). - `reporters`: fired on every call; optional. Their
optionstypes are intersected; required reporter options become required at the call site. Omit it (or pass[]) for a catalog whose codes are only everthrown: the thrownDiagnosticalready carries the message, so a console reporter would print it once and surface it again from the uncaught error, a visible duplicate. Keep report-only warnings and fatal throws in separate catalogs when one needs a reporter and the other does not. - Param inference: params from
whyandfixare intersected and required at the call site. Ifwhyneeds{ src }andfixneeds{ date }, the call requires{ src, date }.
Call sites
diagnostics.NUXT_B1001() // no params: report only
diagnostics.NUXT_B2011({ src: '/plugins/bad.ts' }) // params first
diagnostics.NUXT_B2011({
src,
cause: originalError,
sources: ['nuxt.config.ts:42:3'],
}) // runtime fields merge in
diagnostics.NUXT_B2011({ src }, { method: 'error' }) // reporter options second
throw diagnostics.NUXT_B2011({ src }) // raisecause/sources go in the params object; sources matters most for build/config diagnostics where the JS stack points inside the library. Catch with if (err instanceof Diagnostic) then read .name, .message, .fix, .docs.
Reporters
(diagnostic: Diagnostic, options?: Opts) => void. Declaring a required options type makes the second call-site argument required and typed.
| Reporter | Import | Description |
|---|---|---|
createConsoleReporter(options?) | nostics | console[method](formatter(d)). method defaults 'warn' (`'log'\ |
createFetchReporter(url) | nostics/reporters/fetch | POSTs diagnostic JSON to the URL; failures swallowed. |
createFileReporter(options?) | nostics/reporters/node | Appends NDJSON to a local file (default .nostics.log). |
createDevReporter() | nostics/reporters/dev | Sends toJSON() to the Vite dev server via import.meta.hot.send(). |
import type { DiagnosticReporter } from 'nostics'
const sentryReporter: DiagnosticReporter = (d) =>
sentry.captureMessage(d.message, { tags: { code: d.name } })
const audited: DiagnosticReporter<{ priority: number }> = (d, o) =>
audit.log({ name: d.name, priority: o.priority })
// → audited makes diagnostics.X({...}, { priority: 1 }) required and type-checked.Formatters
| Formatter | Import | Description |
|---|---|---|
formatDiagnostic | nostics | Plain unicode-decorated string (built-in reporters use it). |
ansiFormatter(colors) | nostics/formatters/ansi | Colorized; accepts a Colors interface (red/yellow/cyan/gray/bold/dim, each (s) => string). |
jsonFormatter | nostics/formatters/json | JSON.stringify(diagnostic) via toJSON(). |
formatDiagnostic output, detail order fixed fix → sources → see, missing fields omitted:
[NUXT_B2011] Invalid plugin `/plugins/bad.ts`. src option is required.
├▶ fix: Pass a string path or an object with a `src` to `addPlugin()`.
├▶ sources: nuxt.config.ts:42:3
╰▶ see: https://nuxt.com/e/b2011Vite plugins (@nostics/unplugin, dev dependency)
@nostics/unplugin/strip-transform (library authors, build optimization) and @nostics/unplugin/dev-server-collector (app developers, dev-time collection). Both unplugin-based: .vite(), .webpack(), .rollup(), etc.
- `nosticsStrip` marks
defineDiagnostics()/*#__PURE__*/and wraps bare diagnostic expression statements with aNODE_ENVguard so they tree-shake out of production. OptionpackageName?(default'nostics'). Throws/returns/assignments stay (they are behavior). For tracking: relative imports, export the catalog directly, no factory wrappers or deep barrels. - The plugin is optional. The same production output happens with no build transform if the catalog is annotated by hand: put
/*#__PURE__*/beforedefineDiagnostics(and before each reporter factory call inside it (as in every example here), and dev-guard each report-only call site (process.env.NODE_ENV !== 'production' && diagnostics.CODE(p)). Always write the annotations in source; reach for the plugin when report-only call sites are unguarded and you want stripping without touching them. - `nosticsCollector` listens for
createDevReporter()diagnostics over the Vite WebSocket and writes them as NDJSON viacreateFileReporter. Vite-only. OptionslogFile?(default.nostics.log),debug?(default!!process.env.DEBUG).
// vite.config.ts
import { nosticsStrip } from '@nostics/unplugin/strip-transform'
import { nosticsCollector } from '@nostics/unplugin/dev-server-collector'
export default defineConfig({
plugins: [nosticsStrip.vite(), nosticsCollector.vite()],
})
// src/diagnostics.ts — pair the collector with createDevReporter()
import { createConsoleReporter, defineDiagnostics } from 'nostics'
import { createDevReporter } from 'nostics/reporters/dev'
export const diagnostics = /*#__PURE__*/ defineDiagnostics({
reporters: [/*#__PURE__*/ createConsoleReporter(), /*#__PURE__*/ createDevReporter()],
codes: {
/* ... */
},
})Production builds
- Report-only diagnostics (bare
diagnostics.X()) should disappear:nosticsStripor hand annotations drop them, then the unused catalog tree-shakes. - Surviving diagnostics (
throw/return/assigned/argument) stay, and each keeps the _whole_ catalog reachable, so everywhy/fixships. Not every library throws in production: if yours only reports, stripping is enough, stop here.
When a library _does_ throw in production, pick defineProdDiagnostics at definition time with a NODE_ENV ternary, so a consumer bundler drops the dev branch (all catalog text):
import { defineDiagnostics, defineProdDiagnostics } from 'nostics'
export const diagnostics =
process.env.NODE_ENV === 'production'
? /*#__PURE__*/ defineProdDiagnostics({ docsBase })
: /*#__PURE__*/ defineDiagnostics({
docsBase,
reporters: [
/* ... */
],
codes: {
/* text */
},
})The accessed code becomes the message, docs still derives from docsBase, no why/fix text ships. No reporters by default (so a surviving throw doesn't also log and then resurface as the uncaught error); pass reporters to keep prod telemetry. nosticsStrip tracks this ternary like a direct catalog export.
Conventions
- Codes are stable, fully-qualified
PREFIX_XNNNN(Bbuild,Rruntime,Cconfig,Ddeprecation). Never reuse or reassign a published code. - Always provide
why; providefixwhenever the solution is known (the most actionable field for humans and agents). Use parameterized templates for runtime values, not string concatenation outside the factory. - `why` is the diagnosis, `fix` is the remedy — split them, don't overlap them.
whystates only what is wrong;fixstates only what to do. The reporter prints both, so any wording that appears in both is dead weight. When a single source sentence carries both ("A hash must start with '#'. Prefix it with '#'."), cut it in two — diagnosis towhy, remedy tofix— rather than pasting the whole thing intowhyand echoing it infix.fixaccepts a param function too ((p) => ...), so move value-bearing remedies (use "#${p.hash}") into it instead of leaving them inwhy. - Pass
causewhen re-raising; passsourceswhen the JS stack doesn't reflect the user's source. - Split large catalogs by domain (
diagnostics/build.ts,runtime.ts,config.ts, re-exported fromindex.ts), eachdefineDiagnostics()sharingdocsBasewith its own code range.
References
- Migrating an existing library to nostics (replacing
console.warn/console.error/warn()/thrownErrors with diagnostic codes, without changing runtime behavior): followreferences/migration.mdstart to finish. - Building the error-code documentation site (page template, deployment, agent optimization):
references/documentation-site.md.
Documentation Site and Error Code Registry
Every published code needs a stable, public documentation page forever. It serves three audiences: developers clicking the see: URL from their terminal, AI agents fetching the page to help when a user pastes an error, and search engines indexing NUXT_B2011 so it's findable. Plan the URL structure to match docsBase (string form → ${docsBase}/${code.toLowerCase()}; function form → full control).
Page template
Each code page (https://nuxt.com/e/b2011) follows this structure. Keep it human-readable and agent-parseable: consistent ## headings, actionable content early, no critical info hidden in tabs/collapsed sections/JS-rendered content.
# {CODE}: {Short title}
Code: `{CODE}`
Level: {error|warn|suggestion|deprecation}
## What this error means
{Plain-language explanation, no assumed context: what the system expected vs received. 1-3 sentences. Agents rely on this to explain the error.}
## Why this happens
{Bulleted list of the concrete scenarios that trigger this diagnostic.}
## How to fix it
{The most important section: copy-pasteable code showing the wrong pattern and the corrected version.}
## Additional context
{Optional: links to related docs, changelog, or related codes.}
## Example output
{Optional: the formatted terminal output, so users confirm they're on the right page.}Deployment
- Host on a public URL matching
docsBase: a static site generator (VitePress, Nuxt Content) with a catch-all/e/[code]route, or a dedicated/errorsroute in existing docs. - Return 200 for valid codes, 404 for unknown ones, so agents and crawlers distinguish them.
- Add frontmatter/
<meta>structured data (code, level, title). Keep pages lightweight; avoid SPAs that block fetch-based agents.
Keep docs in sync with code
- Store the markdown alongside the diagnostic definitions or in
docs/errors/, and add the page in the same PR as a new code. - Generate an index page listing all codes with messages and levels.
- In CI, validate that every code in
defineDiagnostics()has a corresponding page; fail the build if one is missing.
Migrate errors and warnings to nostics
Follow this start to finish whenever the task is migrating a library's existing user-facing errors, warnings, and logs (console.warn/console.error, warn() helpers, thrown Errors) to nostics diagnostics. Turn ad hoc reporting into a catalog of stable diagnostic codes without changing runtime behavior. Full API: the nostics skill's SKILL.md (this reference lives beside it).
What to migrate
Inventory console.warn, console.error, warn(...) helpers, throw new Error(...), Promise.reject(new Error(...)). Skip tests, fixtures, snapshots, and generated output. Plain debug console.logs are usually not user-facing; leave them.
Migrate:
- dev warnings that report and keep going
- warnings followed by recovery or a fallback (replace only the report, keep the recovery)
- plain user-facing thrown or rejected
Errors (the diagnostic becomes the thrown/rejected value) - deprecation notices
- build/config errors caused by a user's file: always pass both the original error as
causeand the file assources, because the JS stack points inside the library and is useless to the user
Do not migrate:
- structured errors other code inspects (type fields, private symbols,
isXxxError()guards,instanceofchecks): they are control flow, not reporting. Leave them unchanged. Only if such an error is also deliberately user-facing, add a separate dev-only report with the error ascause; never replace the error object itself. - catch blocks that only log a native/platform error and fall back when the library cannot name a likely cause or a concrete fix: the native error is the best available information, keep the plain log. This exception covers platform APIs failing (e.g.
history.pushState, storage quota), not errors caused by the user's own files: a caught parse or config error on a user file should become a diagnostic carrying the original error ascauseand the file assources. - anything where the diagnostic would only restate "an operation failed". A diagnostic earns its place by naming a likely user-code problem or a concrete fix.
Preserve behavior exactly
A project's dev guard may be process.env.NODE_ENV !== 'production' or its own build-time constant (libraries often define a flag for this; recognize whatever the codebase uses). Written as DEV below; treat all forms the same:
- Keep existing dev guards exactly as they are. nostics stripping is additive and does not replace them. If a throw or reject only happened in dev, it must still only happen in dev.
- Never add a guard the original did not have. A throw or report that fired in production keeps firing in production builds that do not use stripping; note that migrating an unguarded report-only call makes it strippable, so once
nosticsStripruns in the build it disappears from production bundles. That is usually the goal of the migration, but if the library deliberately reports in production, surface that decision instead of changing it silently. - Keep throw vs reject, timing, recovery code, and returned fallbacks.
- Keep structured error shapes (fields, symbols). Migrating a throw replaces the thrown message with the diagnostic's
why: if tests assert the exact old text, update them deliberately as part of the migration, never weaken the message to dodge a test.
Catalog shape
One catalog file per area of the library (a single src/diagnostics.ts is fine for small ones), exported directly. No factory wrappers and no deep barrel re-exports: the strip plugin tracks the export across one relative import. nostics is a runtime import: add it to dependencies, not devDependencies (library bundlers refuse or inline it otherwise).
import { createConsoleReporter, defineDiagnostics } from 'nostics'
export const diagnostics = /*#__PURE__*/ defineDiagnostics({
docsBase: (code) => `https://example.com/e/${code.toLowerCase()}`,
reporters: [/*#__PURE__*/ createConsoleReporter()],
codes: {
LIB_R0001: {
why: (p: { hook: string }) => `${p.hook}() must be called at the top of a setup function.`,
fix: 'Move the call into setup() or a composable called by setup().',
},
},
})- Codes are
PREFIX_XNNNN. Pick the category letter by area, not severity:Bbuild,Rruntime,Cconfig,Ddeprecation. A runtime warning isR; reserveDfor deprecations. Published codes are permanent: never rename or reuse one. whysays what happened with runtime values interpolated through typed param functions (bothwhyandfixaccept them; their params are merged and required at the call site).fixis the concrete next action, never a restatement of the problem.- Split the original warning; never copy it whole into `why`. Most existing warnings bundle the diagnosis and the remedy in one string (
"A hash must start with '#'. Prefix it with '#'."). The reporter printswhyandfix, so pasting the full sentence intowhyand then writing afixduplicates the remedy on screen. Cut the sentence in two: diagnosis towhy, remedy tofix. If the remedy needs the offending value, makefixa param function — its params merge withwhy's.
// before: warn(`A \`hash\` should start with "#". Replace "${hash}" with "#${hash}".`)
// ❌ remedy lives in why and is echoed by fix
{ why: (p) => `A \`hash\` should start with "#". Replace "${p.hash}" with "#${p.hash}".`,
fix: 'Prefix the hash with "#".' }
// ✅ diagnosis in why, remedy in fix (a function, because it needs the value)
{ why: (p) => `A \`hash\` should start with "#" but received "${p.hash}".`,
fix: (p) => `Prepend "#": use "#${p.hash}".` }- Extra
console.warn/console.errorarguments must not be lost: an error value becomescause; data values are interpolated intowhy(e.g.JSON.stringify(p.value)). causeandsources('file:line:column'strings pointing at user code) go inside the params object (the first argument), merged with the message params. The second argument is reporter options only, e.g.{ method: 'error' }.docsBaseis optional. If the project has no documented error-page URL scheme, propose one and surface it to the maintainer rather than inventing pages that do not exist. When pointing per-codedocsat existing documentation with#hashanchors, verify each anchor against the built or published HTML (grep theid=) instead of guessing it. A custom slugify can keep heading casing and leave a trailing hyphen, so the real anchor may look like#Using-the-store-outside-of-setup-.
Call-site patterns
| Before | After |
|---|---|
DEV && warn(msg) | DEV && diagnostics.LIB_R0001(params) (same guard) |
| warn, then recover/fallback | diagnostic, then the same recovery |
warn, then throw new Error(...) | throw diagnostics.LIB_R0002(params) |
warn, then Promise.reject(new Error(...)) | return Promise.reject(diagnostics.LIB_R0003(params)) |
console.error(...) level | diagnostics.LIB_B0001(params, { method: 'error' }) |
| caught error tied to a user file | diagnostics.LIB_B0002({ ...params, cause: err, sources: ['src/file.ts:10:5'] }, { method: 'error' }) |
| structured/internal error | leave unchanged |
Calling a handle always runs the reporters, so throw diagnostics.CODE(params) reports and throws. For a warn-then-throw site that is the same double output it already had. For a bare throw new Error(...) it adds a console report before the throw, which duplicates the message the uncaught error already shows. When a catalog's codes are only ever thrown (config validators, fatal asserts), give that catalog no reporters (reporters is optional) so the throw is the only output, and keep warnings in a separate reporter-backed catalog. Dropping the reporter also keeps a strict test harness happy when its afterEach fails on any unasserted console.warn/console.error.
Report-only calls must stay bare expression statements (DEV && diagnostics.LIB_R0001(p) included) so nosticsStrip can remove them in production. throw/return/assigned diagnostics are behavior and stay.
Dropping diagnostics from production builds takes two pieces: /*#__PURE__*/ annotations on the catalog (defineDiagnostics(...) and each reporter factory call inside it) so an unused catalog tree-shakes away, and a DEV guard on every report-only call site. Both can be written manually in source, or the nosticsStrip build plugin adds them at build time (import { nosticsStrip } from '@nostics/unplugin/strip-transform', then the matching unplugin adapter: nosticsStrip.rolldown(), .vite(), .rollup(), ...). Decide from context: when every report-only site is already dev-guarded, manual annotations in the catalog file are enough and avoid a build transform; reach for the plugin when call sites are unguarded and stripping is wanted. Either way the behavior rule holds: if the library deliberately reports unguarded in production, do not silence it with a guard or the plugin; ask the maintainer.
Verify
- Tests for warnings, throws, guards, and error shapes still pass; tests asserting exact message text are updated consciously, not accidentally.
- Watch substring assertions when splitting a warning.
toHaveBeenWarned('...')/toContainpin a fragment, not the whole message, and a fragment may sit in the remedy half you just moved tofix. Before splitting, grep the tests for substrings of each warning: keep pinned diagnosis fragments inwhy; when a test pins a remedy fragment, update that assertion to the survivingwhytext. The same warning is often pinned by a shared constant duplicated across several spec files — fix every copy. - Dev-only gates are still present everywhere the source had them, and no new gates were added.
- Report-only diagnostics remain strippable expression statements. Thrown/returned diagnostics keep their message text in production by design: they are behavior, not reports.
Related skills
FAQ
What does nostics do?
nostics provides diagnostic workflows for agents and apps.
When should I use nostics?
nostics diagnostics or agent troubleshooting.
Is this skill safe to install?
Review the Security Audits panel on this page before installing in production.