Skip to content

feat(lint): unpinned-headline rule (Smoke-pin coverage)#6

Merged
hyperpolymath merged 1 commit into
mainfrom
claude/modest-hawking-3fd6it
Jun 18, 2026
Merged

feat(lint): unpinned-headline rule (Smoke-pin coverage)#6
hyperpolymath merged 1 commit into
mainfrom
claude/modest-hawking-3fd6it

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Summary

Implements the spec's unpinned-headline lint (docs/arghda-spec.adoc §Linter rules) — the last remaining v0 Agda lint that was filed under README "Not yet".

A module declares a top-level theorem whose name matches the headline pattern, but the name is not pinned in any Smoke.agda via a using ( … ) clause. This enforces the estate "every headline pinned in Smoke" discipline: a renamed or silently-dropped headline surfaces as a missing pin rather than a quiet regression.

Design

  • Warn severity (per spec — missing-safe-pragma/orphan-module/unjustified-postulate are the hard-blocks; the rest are warns). Not every matching definition is a headline the operator wants pinned, and the default pattern is deliberately broad.
  • Headline detection keys on top-level (column-0) type signatures name : T (and the multi-name a b c : T form). Column-0-only gives the export-only filter for free, since private-block definitions are indented.
  • Pins are the union of every using ( … ) list across the Smoke.agda files among the entry modules; tolerant of multi-line using lists.
  • Self-skips when no Smoke.agda is in scope (e.g. a single-file check), and never lints a Smoke.agda for its own headlines.
  • Operator-configurable regex via --headline-pattern <regex> on scan/dag; default ^[a-z][A-Za-z0-9-]*$ per the spec open question. The pattern lives on the rule instance (UnpinnedHeadline { matcher }), so LintContext and every other rule's test constructor are untouched.

Changes

  • New src/lint/unpinned_headline.rs (+ 12 unit tests).
  • RuleConfig / rules_with_config in src/lint/mod.rs; default_rules() unchanged in behaviour.
  • --headline-pattern flag threaded through scan/dag.
  • Adds the regex dependency.
  • README + CHANGELOG updated; unpinned-headline removed from "Not yet".

Verification

  • cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo build, cargo test all green (lib suite 23 → 35).
  • Dogfooded against the echo-types corpus: fires as a non-blocking warn (432 hits under the broad default), pins read correctly from Smoke.agda + Ordinal/Buchholz/Smoke.agda.

Follow-ups (noted in README)

  • Persist the headline pattern in .arghda/config.toml (currently a CLI flag; config-discovery needs a small design decision since scan/dag aren't workspace-scoped).
  • Populate the DAG schema's per-node headlines array (the extractor now exists).

🤖 Generated with Claude Code

https://claude.ai/code/session_019GiSiEfgZCte35dyykgBHs


Generated by Claude Code

Implements the spec's `unpinned-headline` lint (arghda-spec.adoc §Linter
rules): a module declares a top-level theorem whose name matches the
headline pattern, but the name is not pinned in any `Smoke.agda` via a
`using ( … )` clause. This enforces the estate "every headline pinned in
Smoke" discipline — a renamed or silently-dropped headline surfaces as a
missing pin rather than a quiet regression.

- Warn severity (per spec): not every matching definition is a headline
  the operator wants pinned, and the default pattern is intentionally broad.
- Headline detection keys on top-level (column-0) type signatures
  `name : T` (and `a b c : T`), which yields the "export-only" filter for
  free since `private`-block definitions are indented.
- Pins are the union of every `using ( … )` list across the `Smoke.agda`
  files among the entry modules; tolerant of multi-line `using` lists.
- Self-skips when no `Smoke.agda` is in scope (e.g. single-file `check`),
  and never lints a `Smoke.agda` for its own headlines.
- Operator-configurable regex via `--headline-pattern <regex>` on
  `scan`/`dag`; default `^[a-z][A-Za-z0-9-]*$` per the spec open question.
  The pattern lives on the rule instance, so `LintContext` and the other
  rules' tests are untouched. Persisting it in `.arghda/config.toml`
  remains a follow-up.

Adds the `regex` dependency. 12 unit tests; dogfooded against the
echo-types corpus (fires as a non-blocking warn, pins read correctly).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019GiSiEfgZCte35dyykgBHs
@hyperpolymath hyperpolymath marked this pull request as ready for review June 18, 2026 16:40
@hyperpolymath hyperpolymath merged commit 054d54d into main Jun 18, 2026
1 check passed
@hyperpolymath hyperpolymath deleted the claude/modest-hawking-3fd6it branch June 18, 2026 16:48
hyperpolymath added a commit that referenced this pull request Jun 20, 2026
## Summary

Persists the operator-configurable headline pattern (and any future lint
knobs) in `.arghda/config.toml` — the per-workspace surface the spec's
open question calls for (`docs/arghda-spec.adoc` lines 200–202). Until
now the pattern was only a CLI flag.

## Design

- **`src/config.rs`** — a serde/`toml` schema deserialised from
`.arghda/config.toml`, overlaid on `RuleConfig::default()`:
  ```toml
  [lint]
  headline_pattern = "^[a-z][A-Za-z0-9-]*$"
  ```
`deny_unknown_fields` so a typo (`headline_patten`) **errors** rather
than being silently ignored; a missing file yields defaults.
- **`scan` / `dag`** gain `--config <file>`; discovery defaults to
`<PATH>/.arghda/config.toml`.
- **Precedence (low → high):** built-in default < `config.toml` < CLI
`--headline-pattern`. An explicit `--config` pointing at a missing file
is an error; the default-location lookup is silently optional.
- Reuses the existing `RuleConfig` / `rules_with_config` (from #6) —
**no change to the rule itself**, just where its pattern comes from.

## Verification

- `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`,
`cargo build`, `cargo test` all green (lib suite 41 → 47; 6 new config
unit tests: parse / precedence / unknown-key-rejection / malformed-TOML
/ discovery).
- **Live end-to-end:** empty config → default pattern flags all
headlines; `[lint] headline_pattern = "^thm-.*$"` narrows to just
`thm-key`; `--headline-pattern '^foo-.*$'` overrides the config file;
`--config <missing>` errors cleanly.

Adds the `toml` dependency.

Second of three sequenced engine-polish PRs (3 → 2 → 1): `unused-import`
(#7, merged), **`.arghda/config.toml`** (this), then the DAG `headlines`
field.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_019GiSiEfgZCte35dyykgBHs

---
_Generated by [Claude
Code](https://claude.ai/code/session_019GiSiEfgZCte35dyykgBHs)_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants