Install the "glint" agent skill from https://github.com/speakeasy-api/gram/tree/main/.agents/skills/glint into .claude/skills/glint/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "glint", then confirm the skill loads.
Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
Type this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
skills CLI
$ npx skills add speakeasy-api/gram --skill glint -a codex
Project install goes to .agents/skills/; add -g for ~/.codex/skills/.
Install the "glint" agent skill from https://github.com/speakeasy-api/gram/tree/main/.agents/skills/glint into .agents/skills/glint/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "glint", then confirm the skill loads.
Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
skills CLI
$ npx skills add speakeasy-api/gram --skill glint -a cursor
Project install goes to .agents/skills/; add -g for ~/.cursor/skills/.
Install the "glint" agent skill from https://github.com/speakeasy-api/gram/tree/main/.agents/skills/glint into .cursor/skills/glint/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "glint", then confirm the skill loads.
Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
skills CLI
$ npx skills add speakeasy-api/gram --skill glint -a gemini-cli
Project install goes to .agents/skills/; add -g for ~/.gemini/skills/.
Install the "glint" agent skill from https://github.com/speakeasy-api/gram/tree/main/.agents/skills/glint into .gemini/skills/glint/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "glint", then confirm the skill loads.
Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
GitHub CLI
$ gh skill install speakeasy-api/gram glint
Installs for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
skills CLI
$ npx skills add speakeasy-api/gram --skill glint -a github-copilot
Project install goes to .agents/skills/; add -g for ~/.copilot/skills/.
Install the "glint" agent skill from https://github.com/speakeasy-api/gram/tree/main/.agents/skills/glint into .github/skills/glint/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "glint", then confirm the skill loads.
GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
skills CLI
$ npx skills add speakeasy-api/gram --skill glint -a opencode
OpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
Install the "glint" agent skill from https://github.com/speakeasy-api/gram/tree/main/.agents/skills/glint into .opencode/skills/glint/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "glint", then confirm the skill loads.
OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
Facts
Skill name
glint
GitHub stars
272
Token cost
~6.4k tokens
SKILL.md length
2,340 words
Files
1
Skills in repo
39
Repo updated
First seen
Licence
AGPL-3.0
At a glance
Conventions for authoring or editing analyzers in the glint/ Go static-analysis package — Gram's custom golangci-lint plugin built on go/analysis.
Works in 6 steps: Pick a kebab-case rule key and a… → Define Settings with Disabled bool. Add… → Implement detection in newAnalyzer(rule… → …
Tasks that involve Linting and formatting
SKILL.md covers Quick start, Detection methodology, Settings and ignore mechanisms and Diagnostic message style, plus 3 more sections
Calls mise and go
What it does
Glint is an agent skill from speakeasy-api/gram. Conventions for authoring or editing analyzers in the glint/ Go static-analysis package — Gram's custom golangci-lint plugin built on go/analysis. Activate this skill whenever the task involves adding, modifying, or testing a glint analyzer (new rule key, new diagnostic, settings struct, fixture under glint/testdata/, wiring in BuildAnalyzers), even if the user does not say "glint" explicitly — phrases like "add a lint rule", "write a custom analyzer", "go/analysis", or "enforce X via golangci-lint" should…
Its SKILL.md is about 6.4k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Development, covering Linting and formatting and Static analysis and SAST. It works with Go. The repository describes itself as: Securely scale AI usage across your organization. A single stack to Connect, Secure, Observe and Distribute agents, MCPs, and Skills within your company. The licence is AGPL-3.0.
When your agent uses it
Tasks that involve Linting and formatting
Tasks that involve Static analysis and SAST
Example prompts
“explicitly — phrases like”
“write a custom analyzer”
“go/analysis”
“/glint”
Workflow steps
6 steps, taken from the first numbered list in SKILL.md.
1Pick a kebab-case rule key and a matching snake_case.go file name in glint/. See Naming conventions.
2Define Settings with Disabled bool. Add nothing else unless there's a concrete user-facing reason. See Settings and ignore mechanisms.
3Implement detection in newAnalyzer(rule Settings) *analysis.Analyzer. Prefer type-based checks over AST shape, and AST shape over string…
4Write the diagnostic with imperative tone and the offending identifier inlined via %q. See Diagnostic message style.
5Wire the rule into glint/plugin.go: add a ruleSettings field with the kebab-case JSON tag, then an if !p.settings.Rules..Disabled block in…
6Add _test.go with analysistest.Run(...) and a fixture under glint/testdata/src//. Update disabledAllRulesPlugin() and the count in…
What it can do on your machine
Read from SKILL.md and the folder at commit ad78247. It shows what the files ask for, not the result of running them.
Tool permissions
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Runs code
Shell commands in SKILL.md call:
mise
go
From the folder's file list and the shell code blocks in SKILL.md.
Network
Links to these hosts (documentation or services it may open):
github.com
From URLs in SKILL.md, links to its own repository left out.
Credentials
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Context cost
Glint loads about 6.4k tokens when it runs. Until then it costs about 135 tokens; SKILL.md has 2,340 words of instructions outside code blocks.
Always· name and description, kept in context so the agent knows when to use it
~135
When it runs· the whole SKILL.md, loaded when a task matches
~6.4k
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
Safety
Auto-check passed
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
Download SKILL.mdSave it as .claude/skills/glint/SKILL.md (or your agent's skills folder).
name
glint
description
Conventions for authoring or editing analyzers in the `glint/` Go static-analysis package — Gram's custom golangci-lint plugin built on `go/analysis`. Activate this skill whenever the task involves adding, modifying, or testing a `glint` analyzer (new rule key, new diagnostic, settings struct, fixture under `glint/testdata/`, wiring in `BuildAnalyzers`), even if the user does not say "glint" explicitly — phrases like "add a lint rule", "write a custom analyzer", "go/analysis", or "enforce X via golangci-lint" should trigger it.
metadata.relevant_files
glint/**/*.go
glint is Gram's package of custom go/analysis analyzers, and gcl is the golangci-lint custom-build configuration that loads glint as a plugin. Together they automate enforcement of project coding conventions and bug-prevention rules so the same feedback isn't re-litigated in PR review.
Plugin entry point: glint/plugin.go — registers the plugin via register.Plugin("glint", New), defines the settings/ruleSettings structs, and lists every analyzer in BuildAnalyzers.
Second plugin: glint/nolint_plugin.go registers glintnolint, which polices //nolint:glint directives. It is a separate linter name because a //nolint:glint directive would otherwise suppress the diagnostic that reports it. It is enabled in server/.golangci.yaml next to glint, and derives the valid analyzer names from BuildAnalyzers, so new analyzers need no extra wiring there.
gcl wiring: server/.custom-gcl.yml — declares github.com/speakeasy-api/gram/glint as the imported plugin module for the custom golangci-lint binary. Its version must match the golangci-lint pinned in mise.toml; mise run lint:server refuses to run otherwise.
glint is its own Go module (glint/go.mod), separate from the root module, so that the custom build only depends on golang.org/x/tools and plugin-module-register at the versions golangci-lint itself uses, and so that server dependency bumps never rebuild gcl. Keep it that way: never import a root-module package from glint. The one shared type, the annotations.Service marker embedded by services, lives in the root module at server/internal/annotations and analyzers match it by import path.
Tests run with mise run test:glint (CI runs them in the glint-test job).
The current set of analyzers and their rule keys is the source of truth in BuildAnalyzers. Read that function before adding a new one.
Quick start
When adding a new analyzer:
Pick a kebab-case rule key and a matching snake_case.go file name in glint/. See Naming conventions.
Define <rule>Settings with Disabled bool. Add nothing else unless there's a concrete user-facing reason. See Settings and ignore mechanisms.
Implement detection in new<Rule>Analyzer(rule <rule>Settings) *analysis.Analyzer. Prefer type-based checks over AST shape, and AST shape over string matching. See Detection methodology.
Write the diagnostic with imperative tone and the offending identifier inlined via %q. See Diagnostic message style.
Wire the rule into glint/plugin.go: add a ruleSettings field with the kebab-case JSON tag, then an if !p.settings.Rules.<X>.Disabled block in BuildAnalyzers.
Add <rule>_test.go with analysistest.Run(...) and a fixture under glint/testdata/src/<fixture>/. Update disabledAllRulesPlugin() and the count in TestBuildAnalyzersAllEnabled in glint/plugin_test.go. See Testing.
Detection methodology
Strongly prefer type-based detection over AST-shape matching, and AST-shape matching over source-string matching. Type-based checks are robust against renames, aliases, vendored packages, and import-path quirks; AST shape is robust against most refactors but blind to type identity; raw string matching is fragile and should only be reached for when the signal genuinely lives in source text rather than the type system.
Prefer types via pass.TypesInfo
glint/no_repo_fields_in_service.go walks struct fields, resolves each field's type via pass.TypesInfo.TypeOf, then narrows through *types.Pointer → *types.Named to identify a sqlc-generated repo handle:
go
fieldType := pass.TypesInfo.TypeOf(field.Type)
if fieldType == nil {
continue
}
ptr, ok := fieldType.(*types.Pointer)
if !ok {
continue
}
named, ok := ptr.Elem().(*types.Named)
if !ok {
continue
}
if !isSqlcGenerated(pass.Fset, named.Obj(), cache) {
continue
}
Reach for type assertions on types.Type (*types.Named, *types.Pointer, *types.Slice, *types.Map, *types.Interface); use pass.TypesInfo.Uses[ident] / pass.TypesInfo.Defs[ident] to resolve identifiers to their declared types.Object; inspect method receivers via Recv() when checking method-set rules. The plugin advertises register.LoadModeTypesInfo from GetLoadMode in glint/plugin.go, so type information is guaranteed to be loaded.
AST shape is acceptable when types are not load-bearing
When the rule is fundamentally about the shape of code rather than its types, AST matching is fine. glint/no_anonymous_defer.go walks *ast.DeferStmt and asserts the call target is *ast.FuncLit:
go
ins := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
ins.Preorder([]ast.Node{(*ast.DeferStmt)(nil)}, func(node ast.Node) {
deferStmt := node.(*ast.DeferStmt)
if _, ok := deferStmt.Call.Fun.(*ast.FuncLit); !ok {
return
}
pass.ReportRangef(deferStmt, "%s", message)
})
There's no *types.Type that captures "anonymous deferred function" — the property only exists at the AST level — so AST matching is the right tool.
Traversal: depend on inspect.Analyzer for deep walks
When a rule needs to find nodes anywhere in the tree (calls, defers, type specs nested in functions, etc.), declare a dependency on the shared inspector rather than hand-rolling for _, file := range pass.Files { ast.Inspect(file, ...) }. inspect.Analyzer parses each file once and shares the resulting *inspector.Inspector across every dependent analyzer, so the package's analyzers walk each file once collectively instead of once per analyzer. Preorder also filters by node type up front, replacing the node.(*ast.T) type-switch-and-return true boilerplate:
The callback returns nothing, so there is no return false to prune a subtree the way ast.Inspect allows. If a rule genuinely depends on pruning descent (matching a node and deliberately skipping its children, for example), keep a manual ast.Inspect and say why in a comment.
Preorder walks every file in the package with no file boundary, so any per-file scoping has to happen per node instead of around a pass.Files loop. When a rule applies only to certain files, filter inside the callback via pass.Fset.File(n.Pos()).Name(). For example, glint/no_testing_raw_sql.go checks for the _test.go suffix there because the test-only rule no longer has a file loop to gate.
Pulling the inspector from pass.ResultOf only works if the analyzer declared inspect.Analyzer in its Requires; otherwise the result is nil and the type assertion panics. This bites helpers shared across analyzers, since every caller must carry the dependency. For example, findAnnotatedStructs reads the inspector, so all four analyzers that call it list inspect.Analyzer in their Requires.
Two situations where the manual walk is still the right call, and the inspector earns nothing:
Per-file stateful detection. When a rule accumulates state across a file and emits fixes scoped to that one *ast.File (an occurrence list, a "is this import used elsewhere" flag, import add/remove SuggestedFixes), the natural unit of work is the file, not the node. The shared inspector deliberately flattens that boundary, so adopting it would force re-bucketing nodes back by file to rebuild the same state, with no traversal saved. Keep the manual per-file ast.Inspect and leave a comment explaining the exemption. For example, glint/no_sql_err_no_rows.go collects each file's ErrNoRows occurrences alongside an otherSqlUsage flag before deciding which imports to rewrite.
Top-level-declaration scans. When a rule only cares about package-level declarations, iterating file.Decls directly is already a shallow single pass and expresses the intent precisely. Reaching for Preorder on *ast.GenDecl would additionally visit function-local declarations, widening the rule's scope, so the inspector is both unnecessary and subtly wrong here. For example, the audit-* analyzers (e.g. glint/audit_event_urn_naming.go) walk file.Decls to inspect only package-level *ast.GenDecl/*ast.TypeSpec.
Source-string matching is a last resort
Raw strings.Contains / regex over file content is fragile and almost never warranted. The only string-matching helper currently in the package is isSqlcGenerated in glint/no_repo_fields_in_service.go, and only because the "sqlc-generated" signal lives in a generated-file header comment that has no type-system representation:
go
f, err := parser.ParseFile(token.NewFileSet(), pos.Filename, nil, parser.ParseComments|parser.PackageClauseOnly)
if err == nil && ast.IsGenerated(f) {
for _, cg := range f.Comments {
if strings.Contains(cg.Text(), "sqlc") {
result = true
break
}
}
}
Note the helper caches results by filename, parses with parser.PackageClauseOnly to skip body parsing, and uses ast.IsGenerated to confirm the standard generated-code header before substring-matching. If you find yourself reaching for string matching, justify in a comment why type-based detection won't work — future readers will assume it was a last resort.
Settings and ignore mechanisms
Each analyzer accepts exactly one common setting: Disabled bool. Don't add allowlist fields (Ignored []string, regex includes/excludes, package globs, etc.) — they're a maintenance burden, they obscure the rule's contract, and golangci-lint already understands //nolint directives as the standard opt-out path.
go
type noRepoFieldsInServiceSettings struct {
Disabled bool `json:"disabled"`
}
The opt-out for end users is the standard //nolint:glint directive, which golangci-lint applies without any analyzer-side wiring. golangci-lint only knows the linter name, so the directive silences every glint analyzer on that line. glintnolint therefore requires it to sit on the offending line (not above package or a top-level declaration) with an explanation that starts with the suppressed analyzer name: //nolint:glint // notestingrawsql: <why this violation is intentional>.
The only setting-shape exception today is narrow message customization: two analyzers (no-anonymous-defer, enforce-o11y-conventions) expose a Message string that gets appended to the default diagnostic when set. From glint/no_anonymous_defer.go:
Add a Message field only if there's a concrete reason for end-user customization. Anything more elaborate than a single appended-suffix string warrants a discussion before being added.
Diagnostic message style
Diagnostics tell the reader what to do, not just what's wrong. Imperative present-tense, action-oriented, with the offending identifier inlined where it aids resolution. No trailing punctuation, no leading capital, match the tone of the existing analyzers.
<bad-example>
go
pass.ReportRangef(field, "Repo field detected in service struct.")
Past-tense observation, capitalized, trailing period — describes the symptom but doesn't tell the reader what to change.
</bad-example>
<good-example>
go
pass.ReportRangef(field, "field %q in %s has type %s which is sqlc-generated; services should use *pgxpool.Pool and create repo instances in methods",
field.Names[0].Name, s.name, fieldType)
Imperative ("avoid"), short, no trailing punctuation.
</good-example>
When the diagnostic has a mechanical fix, attach a SuggestedFix so editor quick-fix actions and golangci-lint --fix can apply it. See Suggested fixes.
Show full SKILL.md (960 more words)Show less
Analyzer.Doc
Set the Doc field on every *analysis.Analyzer. It's what go vet -<rule> and IDE tooling show users when surfacing the rule. Keep it short and descriptive, and where the diagnostic has a single canonical default message, reuse the same constant for both so they stay in sync. From glint/no_sql_err_no_rows.go:
When the diagnostic has a mechanical fix that's always safe to apply, attach SuggestedFixes to the analysis.Diagnostic so editor quick-fix actions and golangci-lint --fix can apply it. glint/no_sql_err_no_rows.go is the worked example:
Things to keep in mind when assembling TextEdits (upstream rules):
Each TextEdit applies to a single file; End must not be earlier in the file than Pos; for a pure insertion, set End equal to Pos (or token.NoPos). Edits within the same diagnostic must not overlap.
Build replacement text from AST nodes via go/format.Node rather than hand-concatenating strings, so the output stays gofmt-clean. For trivial selector or identifier replacements, raw byte literals are fine.
If the fix involves cross-occurrence work (e.g. removing an import once all call sites have been rewritten), attach the cross-cutting edits to every diagnostic's SuggestedFix. analysistest's three-way merge dedupes identical edits, but splitting them risks an editor quick-fix branch removing the import while sibling occurrences remain unfixed. See the explanatory comment in glint/no_sql_err_no_rows.go for the reasoning.
Share editing helpers in subpackages
Common edit logic belongs in a shared subpackage under glint/, not duplicated across analyzers. Today's example is glint/imports/, which exposes reusable helpers for emitting analysis.TextEdit values that add or remove imports:
LocalName(file, path, defaultName) (string, bool) — resolves the local identifier (alias or default) for an imported package, or reports that the import is absent.
Add(file, path) (analysis.TextEdit, bool) — emits an edit that inserts a new import into the file's first grouped import block.
Remove(fset, file, path) (analysis.TextEdit, bool) — emits an edit that deletes the import line for path.
When you find yourself writing AST-rewriting helpers that another analyzer would plausibly need, add them under glint/<helper>/ with a package-level docstring describing the intended use in SuggestedFixes, and call them from your analyzer's Run function. Future analyzers should compose the helper rather than reimplement it.
Testing fixes
Test fix application with analysistest.RunWithSuggestedFixes instead of analysistest.Run. The harness applies the suggested fixes to the input file and compares the result against a <fixture>.go.golden file in the same fixture directory:
There are three parallel namings to keep aligned for each analyzer: the rule key (user-facing, in YAML/JSON settings; //nolint:glint explanations use the analyzer name, which is the rule key without dashes), the Go identifiers (constant, settings struct, constructor), and the file name.
Rule key (kebab-case, user-facing)
Prohibition rules use a no- prefix: no-anonymous-defer, no-repo-fields-in-service, no-sql-err-no-rows, no-direct-chat-message-insert.
Other rules use a descriptive predicate that reads naturally as an assertion about correct code: enforce-o11y-conventions, service-has-attach-func, service-has-service-assertion, audit-event-urn-naming, audit-event-typed-snapshot. Use a verb-led key when the rule is fundamentally about an action (enforce-...); use a <subject>-has-<property> or <subject>-<aspect> shape when the rule is an invariant check.
Go identifiers (lowerCamel mirroring the rule key)
The analyzer name constant, settings struct, and constructor mirror the rule key in lowerCamel form, with Go-style initialisms (SQL, URN, O11y):
Rule key
Constant
Settings
Constructor
no-anonymous-defer
noAnonymousDeferAnalyzer
noAnonymousDeferSettings
newNoAnonymousDeferAnalyzer
no-repo-fields-in-service
noRepoFieldsInServiceAnalyzer
noRepoFieldsInServiceSettings
newNoRepoFieldsInServiceAnalyzer
no-sql-err-no-rows
noSqlErrNoRowsAnalyzer
noSqlErrNoRowsSettings
newNoSqlErrNoRowsAnalyzer
enforce-o11y-conventions
enforceO11yConventionsAnalyzer
enforceO11yConventionsSettings
newEnforceO11yConventionsAnalyzer
service-has-attach-func
serviceHasAttachFuncAnalyzer
serviceHasAttachFuncSettings
newServiceHasAttachFuncAnalyzer
audit-event-urn-naming
auditEventURNNamingAnalyzer
auditEventURNNamingSettings
newAuditEventURNNamingAnalyzer
The analyzer-name string constant is also the Name field of the returned *analysis.Analyzer, and it's the directory name analysistest looks for under glint/testdata/src/. Keep them identical so the test harness, the diagnostic source, and the rule key all line up.
File names (snake_case mirroring the rule key)
Each analyzer lives in glint/<rule-key-with-underscores>.go with a sibling _test.go:
The JSON tag is what end users write under linters-settings.glint.rules.<key>.disabled in .golangci.yml, so it must match the rule key exactly.
Testing
Every analyzer ships with an analysistest-based test plus a fixture directory.
Default-settings test
Use golang.org/x/tools/go/analysis/analysistest and pass the constructor with zero-value settings. The fourth argument is the directory name analysistest looks up under glint/testdata/src/:
Fixtures live at glint/testdata/src/<fixture-name>/<file>.go. The glint module cannot import the root module, so an analyzer that matches a root-module type does it by import path and type name (see annotationsPkgPath in glint/annotated_service.go), and its fixtures compile against a stand-in copy of that type under testdata/src/ at the real import path: glint/testdata/src/github.com/speakeasy-api/gram/server/internal/annotations/structs.go mirrors server/internal/annotations/structs.go and must be kept identical to it. Because that path is internal to server/, fixtures that import it live at glint/testdata/src/github.com/speakeasy-api/gram/server/internal/<fixture-name>/ and the test passes that full import path to analysistest.Run. Mark expected diagnostics with // want "<regex>" comments on the offending line:
For diagnostics whose text contains regex metacharacters (*, (, ., etc.), escape them in the // want directive and use backticks instead of double quotes when the message itself contains quotes:
go
repo *repo.Queries // want `field "repo" in Service has type \*github.com/speakeasy-api/gram/server/internal/serviceannotationrepofield/repo.Queries which is sqlc-generated; services should use \*pgxpool.Pool and create repo instances in methods`
Non-default settings
For each non-default setting variant (e.g. a custom Message), add a separate test function and a separate fixture directory whose // want text reflects the customized output:
The disabled-rule test surface is centralized in glint/plugin_test.go, which exposes a disabledAllRulesPlugin() helper returning a *plugin with every rule's Disabled: true. Two assertions live there:
TestBuildAnalyzersAllDisabled — BuildAnalyzers returns an empty slice when everything is disabled.
TestBuildAnalyzersAllEnabled — asserts the expected analyzer count for a zero-value *plugin{}. Bump this number whenever a new analyzer is added.
Each analyzer's own test file then adds a per-rule registration test: start from disabledAllRulesPlugin(), flip only the rule under test back on, and assert BuildAnalyzers returns exactly that one analyzer with the expected name. From glint/no_repo_fields_in_service_test.go:
When adding a new analyzer, extend disabledAllRulesPlugin() with the new Disabled: true field, increment the expected count in TestBuildAnalyzersAllEnabled, and add the per-rule registration test alongside the analyzer's own unit tests.
Glint next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
Explains what each aru doctor architecture-check finding means in the Arandu Go framework, why it is never suppressed, and how to fix the line it points to.
A skill your agent uses when adding, changing, restyling, reviewing, validating, or previewing a Gram/Speakeasy transactional email, in Go or in LMX/MJML — a template<name.go, a TemplateKey…
A skill your agent uses when adding, changing, or styling UI in client/admin (the Gram admin dashboard) that touches shadcn/ui — a button, dialog, table, sidebar, badge, select, tabs, tooltip, card…
A skill your agent uses when adding, editing, reviewing, testing, or locating a reviewed skill distributed with the Platform MCP plugin; triggers include "Platform MCP skill", "platformmcpskills"…
A skill your agent uses when gating a feature behind a flag, dogfooding or gradually rolling out a change, choosing between productfeatures and PostHog feature flags, adding or checking a product…
Conventions for authoring or editing analyzers in the glint/ Go static-analysis package — Gram's custom golangci-lint plugin built on go/analysis. Glint is an agent skill from speakeasy-api/gram. Conventions for authoring or editing analyzers in the glint/ Go static-analysis package — Gram's custom golangci-lint plugin built on go/analysis.
When should I use Glint?
Glint fits situations like: tasks that involve Linting and formatting; tasks that involve Static analysis and SAST.
How do I install Glint in Claude Code?
Run `npx skills add speakeasy-api/gram --skill glint -a claude-code`. Or copy the skill folder (.agents/skills/glint in speakeasy-api/gram) into .claude/skills/glint in your project. Claude Code loads it when a task matches its description.
How do I install Glint in Codex?
Run `npx skills add speakeasy-api/gram --skill glint -a codex`. Or copy the skill folder (.agents/skills/glint in speakeasy-api/gram) into .agents/skills/glint in your project. Codex loads it when a task matches its description.
Can I use Glint in Cursor, Gemini CLI or GitHub Copilot?
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add speakeasy-api/gram --skill glint -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/glint, .gemini/skills/glint, .github/skills/glint and .opencode/skills/glint in your project.
What does Glint need to run?
Going by SKILL.md and its folder, Glint needs the command-line tools its instructions call (mise and go).
Does Glint access the network?
SKILL.md names 1 domain. As links in the text: github.com. This is read from the text; nothing was executed.
Is Glint safe to install?
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
What licence does Glint use?
Glint is published under the AGPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
How many tokens does Glint use?
About 6.4k tokens (SKILL.md is roughly 25k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
What are the alternatives to Glint?
Skills that share tags, products or a category with Glint: Golang Continuous Integration (samber/cc-skills-golang, 3.4k stars), Golang Continuous Integration (context-labs/whip, 1.1k stars), Arandu Doctor Findings (arandu-io/arandu, 281 stars) and Fix (BUZZARDGTA/Session-Sniffer, 104 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
Who maintains Glint?
speakeasy-api (a GitHub organization) maintains it in speakeasy-api/gram, which has 272 GitHub stars. The repository holds 39 skills in this directory. The repository was last updated on October 8, 2026.
Source: speakeasy-api/gram on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.