Custom Rules Guide

June 16, 2026 · View on GitHub

Telescope supports multiple ways to extend its linting capabilities: built-in Go rules via the RuleBuilder API (for Telescope contributors), Spectral-compatible YAML rulesets, declarative YAML in .telescope.yaml, and Bun sidecar execution for TypeScript/JavaScript rules.

Built-in Rule Development

RuleBuilder API

Use rules.Define() to create a rule with metadata, then chain visitor methods and call Register():

package analyzers

import (
	"github.com/LukasParke/gossip"
	ctypes "github.com/sailpoint-oss/telescope/server/core/types"
	"github.com/sailpoint-oss/telescope/server/openapi"
	"github.com/sailpoint-oss/telescope/server/rules"
)

func registerOperationSummary(s *gossip.Server) {
	rules.Define("operation-summary-required", rules.RuleMeta{
		Description: "Operations must have a summary",
		Severity:    ctypes.SeverityWarning,
		Category:    rules.CategoryDocumentation,
		Recommended: true,
	}).Operations(func(path, method string, op *openapi.Operation, r *rules.Reporter) {
		if op.Summary == "" {
			r.At(op.Loc, "%s %s is missing summary", method, path)
		}
	}).Register(s)
}

Then add registerOperationSummary(s) to RegisterAll() in rules/analyzers/register.go.

Visitor Methods

MethodCallback SignatureWhen Called
Documentfunc(doc *Document, r *Reporter)Full document (top-level checks)
Infofunc(info *Info, r *Reporter)Document info object
Pathsfunc(path string, item *PathItem, r *Reporter)Each path item
Operationsfunc(path, method string, op *Operation, r *Reporter)Each operation
Schemasfunc(name string, schema *Schema, pointer string, r *Reporter)Top-level schemas
RecursiveSchemasfunc(name string, schema *Schema, pointer string, r *Reporter)All schemas (nested)
Parametersfunc(param *Parameter, r *Reporter)Each parameter
Responsesfunc(code string, resp *Response, r *Reporter)Each response
Tagsfunc(tag *Tag, r *Reporter)Each tag
Serversfunc(server *Server, r *Reporter)Each server
RequestBodiesfunc(path, method string, rb *RequestBody, r *Reporter)Request bodies
SecuritySchemesfunc(name string, ss *SecurityScheme, r *Reporter)Security schemes
Examplesfunc(name string, ex *Example, r *Reporter)Examples
Customfunc(idx *Index, r *Reporter)Full index access

Reporter

Use r.At(loc, format, args...) to emit diagnostics:

r.At(op.Loc, "missing operationId")
r.At(op.Loc, "%s %s needs a summary", method, path)

Testing Rules

Use rulestest.Run() with exact diagnostic assertions:

package myrules

import (
	"testing"

	"github.com/LukasParke/gossip/treesitter"
	ctypes "github.com/sailpoint-oss/telescope/server/core/types"
	"github.com/sailpoint-oss/telescope/server/openapi"
	"github.com/sailpoint-oss/telescope/server/rules"
	"github.com/sailpoint-oss/telescope/server/rules/testing/rulestest"
)

func TestOperationSummaryRequired(t *testing.T) {
	_, analyzer := rules.Define("operation-summary-required", rules.RuleMeta{
		Description: "Operations must have a summary",
		Severity:    ctypes.SeverityWarning,
	}).Operations(func(path, method string, op *openapi.Operation, r *rules.Reporter) {
		if op.Summary == "" {
			r.At(op.Loc, "missing summary")
		}
	}).Build()

	rulestest.Run(t, analyzer,
		rulestest.Case{
			Name: "flags missing summary",
			Spec: `openapi: "3.1.0"
info:
  title: Test
  version: "1.0"
paths:
  /users:
    get:
      operationId: listUsers
      responses:
        "200":
          description: OK`,
			Expect: []rulestest.Diag{
				{Line: 7, Code: "operation-summary-required", Severity: rulestest.Warn},
			},
		},
		rulestest.Case{
			Name: "passes when summary present",
			Spec: `openapi: "3.1.0"
info:
  title: Test
  version: "1.0"
paths:
  /users:
    get:
      operationId: listUsers
      summary: List users
      responses:
        "200":
          description: OK`,
			Expect: nil,
		},
	)
}

rulestest.Diag fields: Line (0-based), Col (0 = any), Code, Severity, Message (substring match).

Spectral YAML Rulesets

Telescope supports Spectral-compatible YAML rulesets. No JavaScript execution — JSONPath expressions and built-in functions only.

Example Ruleset

# custom-rules.yaml
extends: telescope:recommended
rules:
  my-custom-rule:
    description: "Paths must use kebab-case"
    severity: warn
    given: "$.paths"
    then:
      field: "@key"
      function: pattern
      functionOptions:
        match: "^/[a-z0-9]+(-[a-z0-9]+)*$"

Configuration

Reference in .telescope.yaml:

extends: telescope:recommended
spectralRulesets:
  - ./rulesets/custom-rules.yaml
rules:
  my-custom-rule: error  # Override severity
  no-trailing-slash: off # Disable built-in rule

Bun Sidecar (TypeScript/JavaScript Rules)

TypeScript/JavaScript rules run in a Bun subprocess managed by lsp/bun/Manager. Telescope ships a bundled sidecar script (runner.js) and launches it with the system bun executable when custom rules or Spectral rulesets are configured. If Bun is missing, Telescope keeps core validation/LSP features available and simply disables the sidecar-backed rule paths.

Runtime Requirements

  • Bun must be installed and available on PATH.
  • Source-checkout workflows should bundle the sidecar once with bash server/lsp/bun/runner/build.sh.
  • VS Code packaging uses pnpm --filter ./client run build:sidecar to copy the bundled script into the extension package.

IPC Protocol

Messages use newline-delimited JSON with an Envelope wrapper:

MessageDirectionPurpose
loadRules / loadResponseClient → Sidecar → ClientLoad TypeScript rules from file paths
runRules / ruleResultClient → Sidecar → ClientRun loaded rules on a document
runSpectral / spectralResultClient → Sidecar → ClientRun Spectral rulesets
ping / pongClient → Sidecar → ClientHealth check
shutdownClient → SidecarGraceful shutdown

Runner Auto-Detection

config.ResolveRunner() determines whether a custom rule uses the Bun sidecar ("bun") or native Go execution based on file extension (.ts, .js → bun) or explicit runner field.

JSON Schema Validation

For schema validation, Telescope uses the Go validator path. Configure JSON Schema files under additionalValidation.schemas:

# .telescope/config.yaml
additionalValidation:
  my-group:
    patterns:
      - "custom/**/*.yaml"
    schemas:
      - schema: my-schema.json

Configuration Reference

.telescope.yaml

extends: telescope:recommended   # or telescope:all, telescope:owasp, telescope:strict
rules:
  rule-id: error | warn | info | hint | off
spectralRulesets:
  - ./path/to/ruleset.yaml
include:
  - "**/*.yaml"
  - "**/*.json"
exclude:
  - "node_modules/**"

Built-in Rulesets

RulesetDescription
telescope:recommendedCurated rules for most projects. See RULES.md for counts.
telescope:allAll non-OWASP rules
telescope:owasp~32 OWASP security rules
telescope:strictrecommended + OWASP