nostics

작성자: vercel

Structured diagnostic code library for JavaScript/TypeScript. Turns errors and other conditions into typed, machine-readable `Diagnostic` instances with stable…

npx skills add https://github.com/vercel-labs/nostics --skill nostics

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 (or undefined to omit).
  • codes: each definition needs why (string | (params) => string, the only required field, becomes Error.message); optional fix (string | (params) => string), docs (string | false), and data (object | (params) => object). Each code's resolved data is typed and included in toJSON().
  • reporters: fired on every call; optional. Their options types are intersected; required reporter options become required at the call site. Omit it (or pass []) for a catalog whose codes are only ever thrown: the thrown Diagnostic already 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 why, fix, and data are intersected and required at the call site. If why needs { src } and fix needs { 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 }) // raise

cause/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, and .data.

Definition data is not present in defineProdDiagnostics(). Its .data is undefined. A dev/prod ternary therefore types .data as the code's data type or undefined.

Reporters

(diagnostic: Diagnostic, options?: Opts) => void. Declaring a required options type makes the second call-site argument required and typed.

ReporterImportDescription
createConsoleReporter(options?)nosticsconsole[method](formatter(d)). method defaults 'warn' ('log'|'warn'|'error'), formatter defaults formatDiagnostic; both via options, method also overridable per call.
createFetchReporter(url)nostics/reporters/fetchPOSTs diagnostic JSON to the URL; failures swallowed.
createFileReporter(options?)nostics/reporters/nodeAppends NDJSON to a local file (default .nostics.log).
createDevReporter()nostics/reporters/devSends 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

FormatterImportDescription
formatDiagnosticnosticsPlain unicode-decorated string (built-in reporters use it).
ansiFormatter(colors)nostics/formatters/ansiColorized; accepts a Colors interface (red/yellow/cyan/gray/bold/dim, each (s) => string).
jsonFormatternostics/formatters/jsonJSON.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/b2011

Vite 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 a NODE_ENV guard so they tree-shake out of production. Option packageName? (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__*/ before defineDiagnostics( 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 via createFileReporter. Vite-only. Options logFile? (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: nosticsStrip or 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 every why/fix ships. 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 instance name, docs still derives from docsBase, why points to the docs URL when one exists (empty otherwise), 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 (B build, R runtime, C config, D deprecation). Never reuse or reassign a published code.
  • In tests, assert the diagnostic code, not its message or formatted text. Check .name on a thrown Diagnostic or [CODE] in reporter output.
  • Always provide why; provide fix whenever 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. why states only what is wrong; fix states 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 to why, remedy to fix — rather than pasting the whole thing into why and echoing it in fix. fix accepts a param function too ((p) => ...), so move value-bearing remedies (use "#${p.hash}") into it instead of leaving them in why.
  • Pass cause when re-raising; pass sources when the JS stack doesn't reflect the user's source.
  • Split large catalogs by domain (diagnostics/build.ts, runtime.ts, config.ts, re-exported from index.ts), each defineDiagnostics() sharing docsBase with its own code range.

References

  • Migrating an existing library to nostics (replacing console.warn/console.error/warn()/thrown Errors with diagnostic codes, without changing runtime behavior): follow references/migration.md start to finish.
  • Building the error-code documentation site (page template, deployment, agent optimization): references/documentation-site.md.

vercel의 다른 스킬

vercel
vercel
로컬 개발 및 테스트를 위한 Vercel REST API 에뮬레이션입니다. 사용자가 로컬에서 Vercel API 엔드포인트와 상호작용하거나 Vercel 통합을 테스트해야 할 때 사용합니다.
cron-jobs
vercel
Vercel Cron Jobs 구성 및 모범 사례. vercel.json에서 예약된 작업을 추가, 편집 또는 디버깅할 때 사용합니다.
codegen
vercel
json-render을 위한 코드 생성 유틸리티입니다. UI 명세서에서 코드를 생성하거나, 사용자 정의 코드 내보내기를 구축하거나, 명세서를 탐색하거나, props를 직렬화할 때 사용합니다.
enable-shopify-menus
vercel
하드코딩된 내비게이션 및 푸터 메뉴를 Shopify 기반 메뉴로 교체합니다.
next-best-practice
vercel
Next.js 모범 사례 - 파일 규칙, RSC 경계, 데이터 패턴, 비동기 API, 메타데이터, 오류 처리, 라우트 핸들러, 이미지/폰트 최적화,…
benchmark-sandbox
vercel
Vercel Sandbox에서 vercel-plugin eval 시나리오를 로컬 WezTerm 패널 대신 실행합니다. Claude Code와 플러그인이 사전 설치된 임시 마이크로VM을 프로비저닝합니다.
write-guide
vercel
점진적인 예제를 통해 실제 사용 사례를 가르치는 기술 가이드를 제작합니다. 개념은 독자가 필요로 할 때만 소개됩니다.
benchmark-testing
vercel
벤치마크 테스트 프로젝트를 생성하고 실행하여 실제 시나리오에서 vercel-plugin 스킬 인젝션을 테스트합니다. 격리된 디렉토리를 설정하고, 설치하며…