AGENTS.md · git:20261016.14976db · 2026-10-16 · sha256 45763035a28a1356
AGENTS.md git:20261016.14976dbA
Immutable. This exact content never changes and is served at /api/v1/blob/45763035a28a1356.
# simple_english Lint Markdown prose and code comments with the SimpleEnglish Plain-mode rules. Pattern rules run on LanguageTool (`rules/simple-english.xml`). Counting rules run in Ruby. ## Docs - `README.md` is the user guide (lint prose). `docs/DAEMON.md` holds the daemon HTTP API - `docs/RULES.md` lists every rule in prose. Regenerate it with `bin/render-rules` after a rule change. - `docs/DEVELOPMENT.md` is the dev guide (layout, tests, rules). `docs/RELEASING.md` holds the release flow (prepare, merge, publish) ## Branches - Never commit on the default branch. Prefix branch names with your own handle to show ownership. Release prep uses `release/vX.Y.Z`. ## GitHub - Follow the repo's templates. Issues go through the forms in `.github/ISSUE_TEMPLATE/`. Pull requests use `.github/pull_request_template.md`. Fill their sections as written. Do not bypass a template by passing `--body` to `gh`. - `CONTRIBUTING.md` holds the process: false findings get an issue first, not a direct fix. Pull requests are drafts. - A pull request that touches `.github/workflows/` follows the action pinning rules in `.github/AGENTS.md`. - Changelog: run `changie new -k <Kind> -b "BODY"` and commit the fragment under `.changes/unreleased/` only when the tool's behavior changes. Documentation-only changes get no entry. Pass the kind and body as flags: bare `changie new` prompts interactively, which hangs a non-interactive session. Unsure of the kind? Ask the user, do not guess it. Pull requests add fragments, not changelog lines, so they never conflict. The kinds are Breaking, Added, Changed, and Fixed. Only Breaking maps to a major bump in `changie next auto`. Breaking means the change breaks the CLI, the config, or an output format. Write the body as one line, per the rules in `.changes/AGENTS.md`. Preview the entry with `--dry-run` on the same `changie new` command before the pull request. `rake lint` lints the fragment bodies. ## Layout - `lib/simple_english.rb` is the composition root: `lint`. `lint/linter.rb` is the path pipeline the CLI and MCP share. `test/corpus_check.rb` runs the corpus pairs - `lib/simple_english/version.rb` holds the gem version, and a version bump is the only change that belongs in it. `cli.rb` is the Thor CLI - `lib/simple_english/model_context_protocol.rb` is the MCP stdio front door (`SimpleEnglish::ModelContextProtocol`). It is a client of the daemon, not a part of it - The tree groups files by domain: `setup/`, `lint/`, `client/`, `daemon/` - `setup/` resolves the environment once, at the process edge. It holds `languagetool.rb` (pinned engine facts), `install.rb` (bundled executable), `config.rb`, and `fingerprint.rb` - `lint/` is the pipeline domain: `lint_plan.rb`, `markdown.rb`, `extractor.rb`, `annotated_text.rb`, `counts.rb`, `suppressions.rb` - `client/` is the client tier. `language_tool.rb` speaks the LanguageTool wire protocol, used by the daemon's engine. `daemon.rb` probes, trusts, and boots the daemon - `daemon/` holds the server tier: `engine.rb` (lint pipeline), `http.rb` (wire framing), `server.rb` (lifecycle). Server failures raise typed errors (`PortInUse`, `InnerDied`, `InnerTimeout`) - One object definition per file: value objects live in their own files in `lint/` (`finding.rb`, `paragraph.rb`, `span.rb`, `segment.rb`, `result.rb`, `plain_text.rb`) - Internals are `private_class_method` ## Constraints - Runtime dependencies stay minimal. The gemspec adds `tree_sitter_language_pack` for comment extraction and Markdown structure, and `thor` for the CLI. Any further runtime dependency needs a stated reason here first. (Thor was a user-directed refactor, 2026-02-27.) Platform gems bundle a native LanguageTool server. Runtime no longer depends on a JVM (2026-10-01). `rexml` remains dev-only for `bin/render-rules` and `native/languagetool/verify.rb` (2026-10-02). - Dev and test tools are the exception. Pick the best tool for the job even when it adds a dev-only dependency. Record it here with a one-line reason. Keep such tools out of the gemspec runtime list. Current tools, each with its reason: `minitest` for tests. The bundled gem alone is not enough, because `bundle exec` cannot require a bundled gem that the lockfile omits. `minitest-mock` restores `Object#stub` after minitest 6 dropped it. `rake` runs them. Both are dev-only, in the gemspec. `rexml` parses the rule XML in `bin/render-rules` and `verify.rb` extracts rule examples from it for native parity checks. It stopped shipping as a Ruby default gem in 4.x (2026-10-02). `changie` (a brew binary, not a gem) batches change fragments into `CHANGELOG.md` at release time (2026-09-29). `json_schemer` validates the Cursor plugin against the vendored Agent Plugins schema, in the contract tests (2026-10-08). Pull requests add fragments, not changelog lines, so they never conflict. - `Markdown.strip` must keep the line count identical to the source. Findings cite original line numbers, so stripping changes must preserve them. - Ruby 3.3 minimum. CI enforces it. The gemspec declares no floor. ## Changes Default to no comment. A comment earns its place only when it says what the code cannot. It explains why: a non-obvious tradeoff, a workaround for an upstream bug, a perf choice that looks wrong. It warns of a real hazard: ordering, concurrency, a caller invariant. It cites an external source. Never narration of the nearby code, restatement of a good name, or notes about the change itself. Prefer a clearer name or a test over a comment. ## Adding a rule 1. Pattern rule: add to `rules/simple-english.xml` with incorrect and correct examples 2. Counting rule: `lib/simple_english/lint/counts.rb` 3. Add a corpus pair `test/corpus/NN-before.md` / `NN-after.md` (a `.rb` pair for the comment pipeline). The before file must trigger the rule. The after file must stay clean. 4. Close a reported false positive or false negative with a corpus pair, in the same change. The report found a shape the rule author did not imagine, and the pair keeps it covered for good. ## Suppressions A line containing `se: ignore` suppresses findings reported on that line; `se: ignore=RULE1,RULE2` scopes it to rules. Pattern findings cite the line of the match (not the paragraph or comment start), so put the directive on the line the finding reports. Counting findings cite the paragraph's first line. ## Config `.simple-english.yml` in the CWD. Keys: `ignore:` (path globs. `**` crosses directories and `*` does not) and `disabled-rules:` (rule IDs dropped from every file). ## Tests - `rake lint`: self-lint the repo's own prose. CI's `native` job runs it, then `ruby test/examples_check.rb` for the rule examples. The `unit` job runs `rake test` on the supported-platform Ruby matrix. CI adds `standardrb`, `actionlint`, and `hadolint` on top. The `native` job installs the built platform gem, then runs `bin/e2e-story` without an executable override. It lints the corpus and a code comment outside the repo. The corpus has that one CI home. standardrb stays out of the bundle. Reason: rubocop pins `json ~> 2.3`, and Ruby 4.0's default json gem is 3.x. Bundling it breaks `bundle exec` on 4.0 - `rake test`: unit tests only, no LanguageTool needed - `rake lint`: self-lint the repo's own prose, then the changie fragment bodies - `ruby test/examples_check.rb` and `ruby test/corpus_check.rb`: need the native LanguageTool server. Set `SE_LANGUAGETOOL_EXECUTABLE` for a source-tree build See `docs/DEVELOPMENT.md` for the full test strategy.