Help us shape the future of Go! Take the 2026 Go Developer Survey

unswell

package module
v0.1.0-alpha.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 18 Imported by: 1

README

Unswell

A linter for lean English, built to reduce AI-sounding prose.

Unswell's main motivation is to keep formulaic AI-style wording from leaking into source code and documentation. AI-assisted work often leaves behind conversational preambles, generic claims of importance, inflated modifiers and repeated setup. Unswell finds these patterns in prose and explains what to revise before the text reaches a commit or a published document.

The checks apply to text regardless of its author. They enforce a chosen editorial policy through concrete findings and explainable scores. Unswell runs locally as a Go library and CLI, without sending source text to an AI service.

Development alpha. The current implementation targets stages 0–1 of the technical roadmap. Rules and defaults are experimental. Revision probability is unavailable until a labeled corpus and compatible calibration model meet the later acceptance criteria.

Quick start

Build with the minimum Go version declared in go.mod:

go build -o bin/unswell ./cmd/unswell
bin/unswell config init --profile technical
bin/unswell check README.md docs/

The public Homebrew tap supports brew install --HEAD stokaro/unswell/unswell while the first release is being prepared. A separate GitHub Action runs pinned release archives and writes JSON/SARIF reports. See installation and release verification for availability and platform checks.

For example, this introduction:

It is important to note that the client retries a request only after a connection failure.

produces filler.announced-importance. A direct version preserves the condition:

The client retries a request only after a connection failure.

Use the strict profile to make the introductory phrase a hard policy violation:

printf '%s\n' 'It is important to note that the client may retry.' |
  bin/unswell check --stdin --filename draft.md --profile strict

Reports and gate

One analysis can write all five reports:

bin/unswell check README.md docs/ \
  --report text:- \
  --report json:artifacts/unswell.json \
  --report sarif:artifacts/unswell.sarif \
  --report html:artifacts/unswell.html \
  --report markdown:artifacts/unswell.md

Add --include-source to retain snippets and highlighted paragraphs in saved reports. Source text is omitted by default. Reports can contain internal prose; choose artifact access accordingly.

bin/unswell report artifacts/unswell.json --format html --output artifacts/review.html
bin/unswell explain README.md --at 3:1
bin/unswell rules list
bin/unswell rules show repetition.near-sentence
bin/unswell rules test
bin/unswell config validate
bin/unswell config explain --file README.md
bin/unswell doctor

Exit codes: 0 for a complete pass, 1 for a complete policy failure, 2 for operational errors or incomplete analysis, and 130 for cancellation. An empty scan fails unless explicitly allowed. --no-gate permits advisory checks while preserving operational errors. Display filtering never changes the gate.

The index uses points from 0 to 100. Every local score includes its contributing rules, activations, deduplication decisions and caps. A clean paragraph cannot reduce another paragraph's score. The tool makes no authorship or factuality claim.

Input and configuration

Inputs include plain text, Markdown/GFM, and comments and string literals in Go, JavaScript, TypeScript/TSX, Python, Rust, Java, C/C++, C#, YAML, Bash/POSIX sh, Zsh's common shell syntax, Fish and PowerShell. Markdown uses block and inline grammars. See formats and extraction limits for extensions and dialect details. For stdin, provide --filename or a supported --format. Coordinates use original UTF-8 byte ranges and one-based Unicode code point columns.

Recursive Git scans select tracked files. Explicit files may be untracked and bypass recursive include/exclude patterns. Outside Git, directories use a bounded filesystem walk. Inputs must remain within the current project root. Symlinks are not followed during discovery. Unsupported explicit formats fail.

The CLI reads .unswell.yaml in its current project root, or the exact file named by --config. It reads no home configuration. The alpha accepts one builtin profile and explicit rule overrides; local inheritance and file overrides belong to stage 2. Select checked contexts globally or per language with the extraction policy. Use reasoned exceptions for intentional examples, dictionaries and other text that should remain outside an editorial check. Exceptions can select paths, languages, comments or strings, and named string owners.

AI assistants can call the same engine through the separate MCP server. Its tools check supplied drafts and expose the fixed policy. make dogfood-mcp verifies repository checks through a real MCP client and subprocess. Separate CLI and MCP containers support Linux AMD64 and ARM64, including repository checks through both interfaces in CI.

version: 1
extends: [builtin:technical-v1]
language: en
calibration:
  model: none
rules:
  filler.announced-importance:
    enabled: true
    severity: error
    gate: forbid
  policy.banned-phrases:
    parameters:
      phrases: ["leverage synergies"]
      positions: [any]

Available profiles are technical, strict, minimal, business, reference and custom; each also accepts a -v1 suffix. Unknown settings, duplicate keys, unsupported rule parameters and unavailable model names fail before analysis.

Go library

engine, err := unswell.New(unswell.Options{})
if err != nil {
    return err
}
result, err := engine.Analyze(ctx, document.Source{
    Name: "draft.md",
    Format: document.Markdown,
    Bytes: draft,
})
if err != nil {
    return err
}
return report.Write(writer, "json", result, report.Options{})

The external consumer implements a custom rule using only public packages. See the API policy for supported imports and ownership contracts. Go rules are trusted code and must support concurrent calls.

Development and limits

Unswell checks its own README, documentation, Go code and Bash scripts with the committed strict repository policy. Run make dogfood, or make check for all checks. CI runs the same gate and uploads all five report formats. A negative CLI probe must fail with exit code 1, proving the policy is active. Third-party licenses and test-data documents are outside this editorial policy; inline code and fenced examples use the normal extractor's protected boundaries. The policy excludes deliberate fixture strings and catalog data with recorded reasons. Comments and runtime strings remain checked. Bash also passes syntax, ShellCheck and shfmt checks, including negative probes for each gate.

The roadmap preserves the remaining requirements: the custom rule DSL, full suppressions, baseline, committed changed-unit checks, trusted policy comparison, Go analysis integration and calibrated revision probabilities. The alpha does not silently claim those capabilities.

Code, URLs, front matter, directives and quoted Markdown blocks are excluded from ordinary prose checks. POS-based chunks are surface candidates, not grammatical dependencies. Lexical overlap is not a claim of semantic equivalence. Thresholds are initial editorial policy, not measured precision or confidence.

Code is MIT licensed. Runtime dependencies and the embedded English model have their own notices. No scan downloads rules, sends prose to a service or requires Python, Node.js, a server, an API key or cgo.

Documentation

Overview

Package unswell analyzes English prose offline with an extensible rule engine. It never discovers files, reads environment variables, prints, exits, or uses the network. Style findings are results; operational failures are Go errors.

Index

Constants

View Source
const SchemaVersion = "1.0.0-alpha.1"

SchemaVersion versions the saved result contract independently of CLI flags.

View Source
const Version = "0.1.0-alpha.1"

Version is the release identity used in canonical results.

Variables

View Source
var BuildCommit = "development"

BuildCommit identifies release builds. Set it with -ldflags at build time. Applications should not change this value while engines are running.

Functions

This section is empty.

Types

type Assessment

type Assessment struct {
	Path               string         `json:"path"`
	Scope              string         `json:"scope"`
	UnitID             int            `json:"unit_id"`
	Span               document.Span  `json:"span"`
	Words              int            `json:"words"`
	SlopScore          float64        `json:"slop_score"`
	EffectiveSlopScore float64        `json:"effective_slop_score"`
	SlopProbability    *float64       `json:"slop_probability"`
	ProbabilityStatus  string         `json:"probability_status"`
	Status             string         `json:"status"`
	Contributions      []Contribution `json:"contributions"`
}

Assessment is a local, nondilutable index. Probability is unavailable in alpha.

type Contribution

type Contribution struct {
	FindingID  string  `json:"finding_id"`
	RuleID     string  `json:"rule_id"`
	Group      string  `json:"group"`
	Weight     int     `json:"weight"`
	Activation int     `json:"activation"`
	Raw        float64 `json:"raw"`
	Effective  float64 `json:"effective"`
	RuleCap    int     `json:"rule_cap"`
	GroupCap   int     `json:"group_cap"`
	Reason     string  `json:"reason"`
}

Contribution explains fixed-point scoring and the effects of caps and deduplication.

type DocumentResult

type DocumentResult struct {
	Name            string               `json:"name"`
	Format          document.Format      `json:"format"`
	SourceHash      string               `json:"source_hash"`
	Bytes           int                  `json:"bytes"`
	ProseWords      int                  `json:"prose_words"`
	Blocks          int                  `json:"blocks"`
	Sentences       int                  `json:"sentences"`
	Excluded        []document.Exclusion `json:"excluded"`
	Source          string               `json:"source,omitempty"`
	Maximum         float64              `json:"paragraph_maximum"`
	Median          float64              `json:"paragraph_median"`
	P90             float64              `json:"paragraph_p90"`
	FlaggedFraction float64              `json:"eligible_paragraph_flagged_fraction"`
}

DocumentResult summarizes one source without including its prose by default.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine is immutable after construction and supports concurrent calls. Custom rule and NLP implementations must uphold their documented concurrency contract.

func New

func New(options Options) (*Engine, error)

New validates the entire policy and required capabilities before reading prose.

func (*Engine) Analyze

func (e *Engine) Analyze(ctx context.Context, src document.Source) (Result, error)

Analyze runs the same pipeline used by AnalyzeAll on one in-memory source.

func (*Engine) AnalyzeAll

func (e *Engine) AnalyzeAll(ctx context.Context, sources []document.Source) (RunResult, error)

AnalyzeAll returns a deterministic, path-sorted result regardless of worker count or source order. It returns partial evidence and an error on incompleteness.

type Finding

type Finding struct {
	ID            string        `json:"id"`
	RuleID        string        `json:"rule_id"`
	RuleVersion   string        `json:"rule_version"`
	Severity      string        `json:"severity"`
	Gate          string        `json:"gate"`
	Group         string        `json:"group"`
	Scope         string        `json:"scope"`
	Message       string        `json:"message"`
	Primary       Location      `json:"primary"`
	Related       []Location    `json:"related"`
	Evidence      rule.Evidence `json:"evidence"`
	Fingerprint   string        `json:"fingerprint"`
	Suppressed    bool          `json:"suppressed"`
	BaselineState string        `json:"baseline_state"`
	Derived       bool          `json:"derived"`
}

Finding is a rule activation enriched with policy, identity, and source locations.

type GateDecision

type GateDecision struct {
	Passed  bool         `json:"passed"`
	Reasons []GateReason `json:"reasons"`
}

GateDecision reports policy separately from operational completion.

type GateReason

type GateReason struct {
	Code      string `json:"code"`
	Path      string `json:"path"`
	FindingID string `json:"finding_id"`
	Message   string `json:"message"`
}

GateReason identifies the exact local condition behind a policy failure.

type Location

type Location struct {
	Path     string            `json:"path"`
	Span     document.Span     `json:"span"`
	Start    document.Position `json:"start"`
	End      document.Position `json:"end"`
	Segments []document.Span   `json:"segments"`
	Snippet  string            `json:"snippet,omitempty"`
}

Location contains original byte spans and Unicode code point positions.

type Manifest

type Manifest struct {
	ToolVersion     string            `json:"tool_version"`
	ToolCommit      string            `json:"tool_commit"`
	ConfigHash      string            `json:"config_hash"`
	RulesetHash     string            `json:"ruleset_hash"`
	ScoringProfile  string            `json:"scoring_profile"`
	FeatureContract string            `json:"feature_contract"`
	NLP             nlp.Identity      `json:"nlp"`
	Rules           []rule.Descriptor `json:"rules"`
	SelectionMode   string            `json:"selection_mode"`
	Complete        bool              `json:"complete"`
	NoGate          bool              `json:"no_gate"`
	IncludeSource   bool              `json:"include_source"`
	SkippedRules    []string          `json:"skipped_rules"`
}

Manifest records the exact execution policy and feature versions, without time.

type Options

type Options struct {
	Config        []byte
	Rules         []rule.Rule
	NLP           nlp.Provider
	Jobs          int
	IncludeSource bool
	AllowEmpty    bool
	NoGate        bool
}

Options injects all inputs. A nil Rules slice selects the builtin catalog; a nonnil slice is the complete registry. Config is YAML bytes, never a path.

type Result

type Result = RunResult

Result is the same report contract restricted to one source by Analyze.

type RunError

type RunError struct {
	Path    string `json:"path"`
	Message string `json:"message"`
}

RunError records an operational failure in a partial result.

type RunResult

type RunResult struct {
	SchemaVersion string           `json:"schema_version"`
	Status        string           `json:"status"`
	Manifest      Manifest         `json:"manifest"`
	Documents     []DocumentResult `json:"documents"`
	Findings      []Finding        `json:"findings"`
	Assessments   []Assessment     `json:"assessments"`
	Gate          GateDecision     `json:"gate"`
	Errors        []RunError       `json:"errors"`
}

RunResult is the immutable input shared by every reporter. Returned slices are owned by the caller and do not alias the engine.

Directories

Path Synopsis
Package builtin constructs the versioned alpha rule catalog without init hooks.
Package builtin constructs the versioned alpha rule catalog without init hooks.
cmd
genschema command
Command genschema derives the saved-result JSON schema from the public types.
Command genschema derives the saved-result JSON schema from the public types.
repocheck command
Command repocheck validates the repository's source and CI contracts.
Command repocheck validates the repository's source and CI contracts.
unswell command
Command unswell checks English prose against a local editorial policy.
Command unswell checks English prose against a local editorial policy.
Package config compiles strict, offline YAML policy against an explicit catalog.
Package config compiles strict, offline YAML policy against an explicit catalog.
Package document defines source coordinates and the neutral prose model.
Package document defines source coordinates and the neutral prose model.
Package extract provides source-preserving prose extraction from text and syntax trees.
Package extract provides source-preserving prose extraction from text and syntax trees.
goanalysis module
internal
cli
Package cli owns command parsing, filesystem discovery, streams, and exit codes.
Package cli owns command parsing, filesystem discovery, streams, and exit codes.
mapping
Package mapping builds source-preserving normalized prose.
Package mapping builds source-preserving normalized prose.
pathglob
Package pathglob compiles bounded project-relative path patterns.
Package pathglob compiles bounded project-relative path patterns.
repopolicy
Package repopolicy checks repository contracts against the actual source tree.
Package repopolicy checks repository contracts against the actual source tree.
nlp
Package nlp defines the backend contract without exposing vendor types.
Package nlp defines the backend contract without exposing vendor types.
english
Package english adapts Prose's tokenizer, Punkt segmenter, and POS tagger.
Package english adapts Prose's tokenizer, Punkt segmenter, and POS tagger.
Package report renders a completed result without analyzing or reading sources.
Package report renders a completed result without analyzing or reading sources.
Package rule defines explicit rule registration, evidence, and configuration.
Package rule defines explicit rule registration, evidence, and configuration.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL