Documentation
¶
Overview ¶
Package lint checks a skill against the rules of the format — the ones the engine cannot enforce at execution time without having already burned the turn.
Why it lives with the format. Every rule here was paid for by a broken turn in production, and each is about a defect that stays QUIET: a loop that collects into a variable nobody writes runs all its iterations and gathers nothing, a typo in a variable name resolves to an empty string, a required field the instruction allows to be empty sends the model into whitespace up to the token ceiling. None of it raises an error; all of it produces an answer that looks fine. Left in the embedding application, these rules are rewritten from scratch by the next embedder, along with the failures that taught them.
The split with Flow.Validate is deliberate: Validate refuses what CANNOT run, the linter reports what runs badly. Moving advice into Validate would start breaking skills that are already written and already working.
Degradation is built in: every fact about the installation is optional, a missing one skips its rules and records the skip in Report.Skipped AND as an info finding — a partial check must never look like a full one. A linter that falls over because a dependency is unavailable is a linter people stop running.
Index ¶
Constants ¶
const DefaultPlaybookBudget = 12288
DefaultPlaybookBudget — the playbook size above which S5 warns. Not a limit but a reminder: a playbook is the weight of the context of every single run.
const SkipRule = "X1"
SkipRule — the rule id carrying "a rule did not run". It is a FINDING and not only an entry in Report.Skipped, because a caller that reads findings and nothing else would otherwise see a partial check as a clean one.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AssetVocabulary ¶
type AssetVocabulary struct {
// CodeKinds — kinds whose content is a program: it belongs PAST the model,
// passed by reference into a call's arguments.
CodeKinds []string
// ReferenceKinds — kinds whose content is knowledge for the model: it
// belongs IN the instruction's text, or the model never sees it.
ReferenceKinds []string
// KnownParams — the param keys the host's resolver actually reads.
KnownParams []string
// LangParam — the key naming a code asset's language, if the host has one.
LangParam string
}
AssetVocabulary — the names the embedder uses for asset kinds and params.
The format deliberately does not close these lists (kinds are an application's vocabulary — that is why `params` is an open map), so the rules about them cannot be grounded without the host saying what it calls things. Left empty, the rules that need it skip with a reason. The vocabulary used by the shipped examples is returned by SchemaVocabulary.
func SchemaVocabulary ¶
func SchemaVocabulary() AssetVocabulary
SchemaVocabulary returns the asset vocabulary the shipped examples use. A starting point, not a default: an embedder with its own kinds passes its own.
type Envelope ¶
type Envelope struct {
// Server — whose results are wrapped.
Server string
// Fields — the wrapper's fields, named in the finding so the author
// recognises what they are looking at.
Fields []string
// Payload — the field holding what the call actually produced.
Payload string
}
Envelope describes a server whose call result WRAPS the payload instead of being it — an exit code, stdout and stderr around what the script printed.
The engine cannot know which server does that, and the difference is invisible: substituting the whole envelope where the payload is expected breaks nothing loudly. A loop honestly makes one iteration over the wrapper, a renderer honestly reports zero findings — because there was nothing to find.
type Facts ¶
type Facts struct {
// ServerNames — the registered MCP servers. W2, E1.
ServerNames func() []string
// AllTools — server name → the tools it carries. W3, W12, E2.
AllTools func() map[string][]string
// ToolSchemas — "server:tool" → the tool's input JSON Schema. W5 checks a
// call step's arguments against `required` in it.
ToolSchemas func() map[string][]byte
// BuiltinTools — the built-in tools the application actually has. W15, E5.
BuiltinTools func() []string
// WriteServers — the servers that CHANGE something. E3 uses it to spot a
// skill that calls itself read-only and reaches for one anyway.
WriteServers func() []string
// SkillNames — the catalogue a `delegate` step can name. E4.
SkillNames func() []string
}
Facts — what the embedder knows about ITS installation, and the engine cannot know: which servers are up, which tools they carry, which built-in tools exist. The rule knows WHAT to compare, the host says WHAT WITH.
Every field is optional. nil (or an empty answer) skips the rules that need it and records the reason — never an error, and never silence.
type Finding ¶
type Finding struct {
Rule string
Severity Severity
Skill string
Path string
Line int // 1-based; 0 = the finding is about the whole file
Message string
}
Finding — one rule's result on one skill.
type Options ¶
type Options struct {
// Unmarshal — how to parse YAML. Required, for the same reason the engine
// takes it as a parameter: a library that picks a YAML implementation picks
// it for everyone who embeds it.
Unmarshal skillengine.Unmarshal
// PlaybookBudget — the size of a playbook, in bytes, above which S5 warns.
// 0 = DefaultPlaybookBudget; negative = the rule is off.
PlaybookBudget int
// CallProtocol — the name of the tool an MCP tool is called THROUGH, if the
// application has one (a step is handed that one tool, not a function per
// tool name). Empty → W12 and E2 do not run.
CallProtocol string
// Envelopes — servers whose result wraps the payload. Empty → W8 does not run.
Envelopes []Envelope
// Assets — what the host calls its asset kinds and params. See AssetVocabulary.
Assets AssetVocabulary
// StaleAPIs — constructs the host has removed. Empty → S3 does not run.
StaleAPIs []StaleAPI
// ReadOnlyRoles — the role names that promise to change nothing. Empty →
// E3 does not run.
ReadOnlyRoles []string
// ImplicitBuiltins — built-in tools handed out WITHOUT being declared. A
// skill is not required to declare them, and E5 does not ask it to.
ImplicitBuiltins []string
// HostVars — variables the embedding application puts into the flow before
// the first step (the request, the history). A skill does not declare them
// and is free to read them; W14 would otherwise report every one of them as
// a typo. Leaving this empty is safe but noisy — the rule cannot tell an
// injected variable from a misspelled one.
HostVars []string
}
Options — the knobs and the host's vocabulary. The zero value is valid: the rules that need a name they were not given skip with a reason.
type Report ¶
type Report struct {
Findings []Finding
// Skipped — rules that did not run, with the reason. Read it before
// concluding a skill is clean.
Skipped []string
}
Report — the findings of one run plus a summary of what was skipped.
func Lint ¶
Lint checks one skill. An error means the check itself could not run (a missing parser, a broken option) — "the skill is bad" is findings in the report, never an error.
type Rule ¶
type Rule struct {
// ID — the rule's number, e.g. "W14". The numbers are owned by this
// package: while rules were being added on both sides of the boundary they
// collided, and a number that means two things in two places is worse than
// no number.
ID string
// Title — what it catches, in one line.
Title string
// Emits — the severities the rule can produce.
Emits []Severity
// Needs — the Facts and Options fields the rule cannot run without. Empty
// means it only needs the skill itself.
Needs []string
}
Rule — one entry of the catalogue: what a rule catches and what it needs to run at all.
The catalogue exists so an embedder can show the list without reading the source, and so that "which rules did not run, and why" has an answer before the run rather than after it. A test keeps it honest in both directions: a rule the code emits and the catalogue does not list is a rule nobody can look up, and a catalogued rule that fires nowhere is a rule that was renamed or quietly lost its call site.
type Severity ¶
type Severity string
Severity ranks a finding. What BLOCKS is decided by the caller: the library says "this is a defect of the format", the embedder decides whether that stops a save or fails a build. Without that split every embedder would need its own severity scale to express its own policy.
type StaleAPI ¶
type StaleAPI struct {
// Pattern — a regular expression matched against the playbook.
Pattern string
// What / Instead — how the finding reads: "<what> is no longer supported —
// use <instead>".
What string
Instead string
}
StaleAPI — a construct the embedder has REMOVED, with what replaced it.
The vocabulary belongs to the host: the engine knows nothing of the tools an application used to have. Empty list → rule S3 does not run.