Skip to main content
Glama
README.md
# noobvious

A skill that stops a model from stating the obvious.

Bad prose from a model fails quietly. It frames a claim, pads it, hedges it, then closes with a summary of the paragraph above. Every sentence is grammatical English. The paragraph carries nothing.

`noobvious` teaches an agent to catch that in its own draft. It ships as an Agent Skill, a CLI, and an MCP server. The skill holds the judgement. The CLI holds the patterns.

## The problem, in one block

<!-- noobvious: ignore start -->

> Version.
> qany is at v0.3.0. The CLI and MCP interfaces may change before 1.0.

<!-- noobvious: ignore end -->

Three problems hide in those two lines. "Version." is a label naming the field it sits above. "qany is at v0.3.0" restates the subject before giving the number. The last sentence would sit in any project README, because it names nothing and warns about nothing.

<!-- noobvious: ignore start -->

> Version 0.3.0. Command and tool names can change until 1.0.

<!-- noobvious: ignore end -->

Same facts, one line of them. The skill teaches the agent to find the three problems and cut them.

## Install the skill

```bash
npm install @qexle/noobvious
npx skills experimental_sync -y
```

The sync command crawls `node_modules`, finds the bundled skill, and installs it for Claude Code, OpenCode, Cursor, and the other agents the command covers.

The skill ends its workflow by shelling out to the checker, so the package has to be on the machine:

```bash
npm install -g @qexle/noobvious
```

A public GitHub repo works as well, for anyone who wants to read the source of the skill first: `npx skills add 1QEXLE/noobvious`.

## What the agent learns

Twelve tells, each named so the agent can recognise one in its own draft.

| tell                                           | what it looks like                                              |
| ---------------------------------------------- | --------------------------------------------------------------- |
| A label that names its own field               | "Version." above a version number                               |
| A sentence that would fit in any other project | "Feel free to contribute."                                      |
| A heading restated in prose                    | a "Privacy" section that opens "This section is about privacy." |
| A point made twice on one screen               | the same claim in the intro and again in the body               |
| A question answered with a restatement         | "What does it do? It does the thing it is named for."           |
| A fact wearing a hedge                         | "may" or "can potentially" on a number you measured             |
| A definition that agrees with the noun         | "The parser is a component that parses."                        |
| Padding to look thorough                       | three bullets where one carries the point                       |
| A sign-off that asks for nothing               | "Hope this helps!"                                              |
| A pointer to what the reader can already see   | `As shown above` when the output sits right below               |
| A qualifier with no measurable size            | "very", "quite", "significantly", "essentially"                 |
| False modesty in front of a real result        | "we were pleasantly surprised" and then the numbers             |

Three tests decide the rest:

- The sentence test: what did the reader learn from this sentence?
- The paste test: could this sentence sit in someone else's README?
- The label test: does the label say anything the value does not already say?

A sentence that fails any of them goes. The skill also lists the places where a repeat earns its keep, so the agent keeps the copy a tutorial or a safety notice needs.

The long catalogue with before and after pairs sits in `skills/noobvious/references/tells.md`.

## The mechanical pass

The CLI catches the patterns that have a fixed shape. Thirty-eight rules, milliseconds per file. The checker runs offline, on your machine.

```bash
npx @qexle/noobvious README.md
```

A real run over a file of filler:

```text
sample.md
  3:19    error    frame.important_note          it is important to note that
                                                 Cut the frame and keep the clause.
  7:1     warning  opener.lets_dive              Let's dive into
                                                 Start with the content.
  9:1     error    claim.widely_believed         It is widely believed
                                                 Name the source, or state it as fact if it is one.
  11:1    warning  redundant.in_order_to         In order to
                                                 Replace with "to".
  13:27   error    repeat.adjacent_sentence      The tool checks the text carefully.
                                                 Delete one of the two sentences.
  17:1    error    closing.conclusion_marker     In conclusion
                                                 Delete the marker, or delete the sentence when it repeats.
6 errors, 6 warnings in 1 file (98 words)
```

Seven families of rule:

| family    | what it finds                                            | shape it matches                            |
| --------- | -------------------------------------------------------- | ------------------------------------------- |
| frame     | a wrapper around a clause that already carries the claim | `it is important to note that`              |
| opener    | throat-clearing before the content starts                | `let's dive into`                           |
| closing   | a summary marker that repeats the text above it          | `in conclusion`                             |
| claim     | a claim nobody sourced                                   | `it is widely believed that`                |
| redundant | a word group that says less than the plain version       | `in order to`, `due to the fact that`       |
| hedge     | stacked qualifiers on one verb                           | `could possibly`                            |
| repeat    | two sentences or phrases carrying the same content       | a list item that restates the list above it |

The CLI cannot tell you that a paragraph is boring, that a definition agrees with its noun, or that a label adds nothing. Those are the tells in the skill, and they need a reader.

## The MCP server

```json
{
  "mcp": {
    "servers": {
      "noobvious": {
        "command": "npx",
        "args": ["-y", "@qexle/noobvious", "mcp"]
      }
    }
  }
}
```

Claude Desktop takes the same block under `mcpServers`. The server exposes two tools. `check_prose` takes a draft and returns the issues with a fix for each. `list_rules` returns the catalog.

## Real catches in our own docs

We ran the checker over the READMEs of two packages we ship before publishing them. It found three real problems and two false positives.

- qany: one sentence about fresh dates repeated a sentence from two paragraphs up, with the same words.
- qany: the phrase `the cache and the` showed up twice inside one section.
- seo-mcp: two consecutive sentences opened the same way, so the paragraph carried one point twice.
- The false positives came from markdown tables, where every row repeats the same cell pattern. The engine now leaves tables and headings out of the comparison rules.

I fixed the three real problems before publishing. I changed the engine because of the two false ones.

## Command line

```bash
noobvious docs/            # every markdown file in a folder
noobvious draft.md         # one file
noobvious --stdin < draft.md
noobvious --json draft.md  # machine readable
noobvious --rules          # the whole catalog
noobvious --explain frame.important_note
```

| flag           | effect                       |
| -------------- | ---------------------------- |
| `--json`       | print the findings as JSON   |
| `--strict`     | fail the run on warnings too |
| `--only a,b`   | run only these rules         |
| `--except a,b` | skip these rules             |

Exit code 0 means the file is clean, 1 means the file has an error, 2 means the arguments were wrong.

## The GitHub Action

The action lives in this repository under `action/`, so one tag covers the package, the agent skill and the action.

```yaml
- uses: 1QEXLE/noobvious/action@v1
  with:
    files: README.md,docs
```

The step reads the files you name, prints what it finds, puts the report in the job summary, and stops the build when it finds an error. Four inputs: `files` (which paths to check, `.` by default), `version` (the release of the checker to run), `strict` (`true` also stops the build on a warning), and `summary` (`false` keeps the report out of the job summary).

## What it cannot do

- It cannot check whether a sentence is true. It reads form, not meaning.
- It does not know your audience. Some repetition earns its place in a tutorial or a contract.
- It reads markdown, mdx, and plain text. Code blocks and inline code are skipped.
- The rules are English only.
- Silence a line with `<!-- noobvious: ignore -->` above it, or wrap a block between `<!-- noobvious: ignore start -->` and `<!-- noobvious: ignore end -->`.

## Website

`index.html` in this repository is a one-page version of the same thing, with a marked-up specimen sheet instead of a screenshot.

## Development

```bash
bun install
bun test
bun run typecheck
bun run build
```

The source lives in `src`. `src/rules.ts` holds the catalog, `src/lint.ts` holds the engine, `src/mcp.ts` holds the server, and `skills/noobvious/SKILL.md` holds the skill the agents read.

## License

MIT. Keep the copyright line when you redistribute it.