extant 0.12.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,769 @@
1
+ Metadata-Version: 2.4
2
+ Name: extant
3
+ Version: 0.12.2
4
+ Summary: Check that the claims in your documentation are still true, against git and the filesystem
5
+ Author: scooter-sensei
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/scooter-sensei/extant
8
+ Project-URL: Changelog, https://github.com/scooter-sensei/extant/blob/main/CHANGELOG.md
9
+ Keywords: documentation,validation,git,pre-commit,ai-agents
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Documentation
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: tomli>=2; python_version < "3.11"
24
+ Dynamic: license-file
25
+
26
+ <div align="center">
27
+
28
+ # extant
29
+
30
+ **Your documentation makes claims. This checks whether they are still true.**
31
+
32
+ [![tests](https://github.com/scooter-sensei/extant/actions/workflows/tests.yml/badge.svg)](https://github.com/scooter-sensei/extant/actions/workflows/tests.yml)
33
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
34
+ [![python: 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)](https://www.python.org)
35
+ [![dependencies: none on 3.11+](https://img.shields.io/badge/dependencies-none%20on%203.11%2B-success)](#requirements)
36
+
37
+ *extant (adj.) - still in existence; surviving.*
38
+
39
+ </div>
40
+
41
+ ---
42
+
43
+ ## The problem
44
+
45
+ Your README says the project needs Node 18. Your `package.json` says 20. Your
46
+ CONTRIBUTING file links to a script deleted in March. An architecture note says
47
+ a rewrite "landed in `8f2a91c`", and that commit no longer exists.
48
+
49
+ Every one of those sentences was true the day it was written.
50
+
51
+ Nothing complains, because documentation is just writing. Tests check code. Type
52
+ checkers check types. Nothing checks whether your prose is still accurate, so it
53
+ drifts quietly until somebody follows it and loses an afternoon.
54
+
55
+ This matters more than it used to. AI assistants read these files and treat them
56
+ as fact. An assistant cannot tell that a line expired, so it plans around
57
+ something untrue and hands back confidently wrong work.
58
+
59
+ ## What it looks like
60
+
61
+ An ordinary project. A README, a `package.json`, a CONTRIBUTING file. No status
62
+ document, no new habits.
63
+
64
+ ```console
65
+ $ python tools/extant_collect.py --verify
66
+
67
+ line 5: [dead-sha] `deadbeef1234567` does not resolve in this repo
68
+ line 3: [dead-md-link] links to `docs/setup.md`, which does not exist
69
+ line 1: [inconsistent-artifact] `node_version` disagrees across files:
70
+ `18` in README.md; `20` in package.json
71
+
72
+ checked README.md: dead-sha 1, dead-md-link 1, inconsistent-artifact 2
73
+ CONTRIBUTING.md: line 3: [dead-md-link] links to `scripts/gone.sh`
74
+ ```
75
+
76
+ Four false statements in the docs you already have, with line numbers, in under
77
+ a second.
78
+
79
+ That `checked` line counts what it **looked at**, not what it found. It matters
80
+ as much as the findings, and [there is a section about why](#every-check-reports-its-denominator).
81
+
82
+ ---
83
+
84
+ ## What it covers
85
+
86
+ Twelve rules. Every one answers a question git or the filesystem can settle.
87
+
88
+ | Rule | Catches |
89
+ |:---|:---|
90
+ | `dead-sha` | "released in commit `abc1234`" when that commit does not exist |
91
+ | `false-merge-claim` | "merged into `develop` at `abc1234`" when that commit is not on `develop` |
92
+ | `stale-live-claim` | "not merged yet" about something merged last week |
93
+ | `unknown-branch` | "work is on branch X" when git has never seen that name |
94
+ | `dead-release-tag` | "released in v2.1" when no such tag exists, or it never shipped |
95
+ | `dead-path-pointer` | "see the file at this path" when the file moved |
96
+ | `dead-md-link` | `[a link](to/a/file.md)` whose target is gone |
97
+ | `dead-md-anchor` | a `#jump-to-section` link with no such heading |
98
+ | `inconsistent-artifact` | two files in your project stating different values for the same thing |
99
+ | `dead-pinned-ref` | an install snippet pinning a version of your project that does not exist |
100
+ | `raw-lfs-blob` | an asset your `.gitattributes` says is in Git LFS, committed into git as a real binary instead |
101
+ | `possible-secret` | a password or key pasted in by accident |
102
+
103
+ Five details that are easy to miss:
104
+
105
+ **More than one integration branch works.** A merge claim names the branch it
106
+ means, so "merged to `develop` at `abc1234`" is checked against `develop` and
107
+ "merged to `main` at ..." against `main`. Nothing to configure. Gitflow teams
108
+ write both kinds, and against a single configured trunk half of those claims
109
+ were never examined at all - not judged and found true, simply not read.
110
+
111
+ **Case matters, and only on some machines.** Windows and macOS open
112
+ `docs/PLAN.md` happily when the file is `docs/plan.md`. Linux does not. Without
113
+ the check a document passes on your laptop and fails on the server, or worse,
114
+ passes everywhere while misleading every Linux reader. Wrong-case paths are
115
+ reported on every platform.
116
+
117
+ **Examples are left alone.** Claims inside fenced code blocks are not read as
118
+ promises, so a README showing what a claim looks like is not accused of making
119
+ one. A password inside a fence is still reported, because that one is about what
120
+ the file contains rather than what it promises.
121
+
122
+ **Install snippets are the exception.** `dead-pinned-ref` is the one rule that
123
+ reads *inside* code blocks, because an install snippet is the opposite of an
124
+ example: it is the block a reader copies verbatim, and a version that does not
125
+ exist fails for them on first use. It only checks pins whose `repo:` names your
126
+ repository, so documenting somebody else's hook is never flagged.
127
+
128
+ **Renames are followed.** When a file has moved, you get told where:
129
+
130
+ ```
131
+ line 5: [dead-md-link] links to `docs/old-name.md`, which does not exist;
132
+ git shows it renamed to `docs/new-name.md`
133
+ ```
134
+
135
+ ### What it deliberately ignores
136
+
137
+ It never judges whether a number or a date is *right*. "We had 2238 tests in
138
+ March" was true in March. It is not wrong now, only old, and there is nothing to
139
+ check it against.
140
+
141
+ That restraint is the whole design. A tool that cries wolf gets ignored, and an
142
+ ignored tool is worse than none. This one reports only what it can prove.
143
+
144
+ The single exception proves the rule: it will compare **two files against each
145
+ other**, because that has a definite answer. See
146
+ [files that contradict each other](#files-that-contradict-each-other).
147
+
148
+ ---
149
+
150
+ ## Is this for you?
151
+
152
+ | | |
153
+ |:---|:---|
154
+ | **Probably yes** | Your project uses git and has documentation: a README, a CONTRIBUTING file, docs, architecture notes. That is enough. It matters more if an AI assistant reads those files, because it cannot tell an expired line from a current one. |
155
+ | **Probably not** | Your project does not use git, or your documentation is one paragraph that never mentions a file, a commit, or a version. |
156
+
157
+ Note what is **not** required: a status file, a changelog, a particular
158
+ workflow, or any change to how you work.
159
+
160
+ ---
161
+
162
+ ## Requirements
163
+
164
+ Git, and Python 3.9 or newer. Check both:
165
+
166
+ ```console
167
+ $ git --version
168
+ $ python --version
169
+ ```
170
+
171
+ | If missing | Get it |
172
+ |:---|:---|
173
+ | Git | https://git-scm.com |
174
+ | Python 3.9+ | https://www.python.org |
175
+
176
+ On macOS and Linux you may need `python3` rather than `python`.
177
+
178
+ **The tool itself has no third-party dependencies.** Standard library and git,
179
+ nothing else.
180
+
181
+ 3.9 and 3.10 are supported for the enterprise distributions that ship them:
182
+ RHEL 9 and Debian 11 are on 3.9, Ubuntu 22.04 LTS on 3.10. The one thing
183
+ newer than 3.9 anywhere in the tool is `tomllib`, which joined the standard
184
+ library in 3.11. Below that the tool runs on its defaults with no parser at
185
+ all, and reading a `.extant.toml` wants `pip install tomli` - the same
186
+ parser under its original name, so a config file is read identically on
187
+ every version.
188
+
189
+ ---
190
+
191
+ ## Install
192
+
193
+ Four ways in. Pick one.
194
+
195
+ ### Option A: pre-commit
196
+
197
+ If you already use [pre-commit](https://pre-commit.com), this is the whole
198
+ setup. Add to `.pre-commit-config.yaml`:
199
+
200
+ ```yaml
201
+ repos:
202
+ - repo: https://github.com/scooter-sensei/extant
203
+ rev: v0.12.2
204
+ hooks:
205
+ - id: extant
206
+ ```
207
+
208
+ Then `pre-commit install`, and create a `.extant.toml` naming the document to
209
+ check:
210
+
211
+ ```toml
212
+ primary_doc = "README.md"
213
+ extra_docs = ["CONTRIBUTING.md"]
214
+ ```
215
+
216
+ It runs on every commit whether or not you touched documentation. That is
217
+ deliberate: merging a branch can make a sentence false without editing a line of
218
+ prose, and that is the case this exists for.
219
+
220
+ A second hook id, `extant-annotate`, emits GitHub Actions annotations instead of
221
+ plain text.
222
+
223
+ ### Option B: pip, pipx or uv
224
+
225
+ Installs the validator as a command. Nothing is written into your project, so
226
+ this is the quickest way to see what the tool says before deciding anything:
227
+
228
+ ```console
229
+ $ uvx extant --repo . --verify
230
+ ```
231
+
232
+ `uvx` downloads and runs it without installing. To keep it around:
233
+
234
+ ```console
235
+ $ pipx install extant
236
+ $ extant --repo . --verify
237
+ ```
238
+
239
+ `pip install extant` works too, though a tool you run against many projects is
240
+ usually happier in its own environment.
241
+
242
+ This is the VALIDATOR only. The git hooks and the `/extant` command have to
243
+ live inside the repository they check, so use Option C or D to install those.
244
+ Point it at a document with `--validate`, or create a `.extant.toml` naming
245
+ one, exactly as in Option A.
246
+
247
+ ### Option C: Claude Code
248
+
249
+ Two lines:
250
+
251
+ ```
252
+ /plugin marketplace add scooter-sensei/extant
253
+ /plugin install extant@extant
254
+ ```
255
+
256
+ Then open the project you want to protect and ask:
257
+
258
+ > Set up extant in this project.
259
+
260
+ It inspects your repository, works out the settings, and reports what it found
261
+ and how confident it is about each value.
262
+
263
+ ### Option D: by hand
264
+
265
+ Works with or without Claude Code.
266
+
267
+ **1. Get the files.** Download the ZIP from
268
+ [the repository](https://github.com/scooter-sensei/extant) (green **Code**
269
+ button, then **Download ZIP**), or clone it:
270
+
271
+ ```console
272
+ $ git clone https://github.com/scooter-sensei/extant
273
+ ```
274
+
275
+ **2. Preview.** This changes nothing:
276
+
277
+ ```console
278
+ $ python extant/plugin/skills/extant/install.py --repo /path/to/your/project --dry-run
279
+ ```
280
+
281
+ Read what it prints. If it looks wrong, stop, and nothing has happened.
282
+
283
+ **3. Do it for real.** The same command without `--dry-run`, plus a preset:
284
+
285
+ ```console
286
+ $ python extant/plugin/skills/extant/install.py --repo /path/to/your/project --preset readme
287
+ ```
288
+
289
+ **4. Turn on the automatic checks.**
290
+
291
+ ```console
292
+ $ cd /path/to/your/project
293
+ $ sh tools/hooks/install
294
+ ```
295
+
296
+ They run after a commit is already recorded, print what they found, and never
297
+ stop you doing anything.
298
+
299
+ ### Presets
300
+
301
+ A preset picks the documents and the shape, so you are not deriving
302
+ configuration before you have seen the tool work once.
303
+
304
+ | Preset | For |
305
+ |:---|:---|
306
+ | `readme` | any project. Your README and CONTRIBUTING. Nothing else needed. |
307
+ | `node` | the same, plus `package.json` and `CHANGELOG.md` version agreement |
308
+ | `python` | the same, with `pyproject.toml` |
309
+ | `rust` | the same, with `Cargo.toml` |
310
+ | `enterprise` | long-lived projects. Also `SECURITY.md`, `SUPPORT.md`, `UPGRADING.md`, `MIGRATION.md` |
311
+ | `ml` | data and model projects. Also `MODEL_CARD.md` and `DATA_CARD.md`, and that `pyproject.toml` and `environment.yml` pin the same Python |
312
+ | `legacy-web` | older web apps. Also `INSTALL.md`, `DEPLOY.md`, `UPGRADING.md`, and that `.nvmrc` and `package.json` agree on Node |
313
+ | `go` | a Go module. Also `SECURITY.md`, and that `go.mod` and your `Dockerfile` build with the same Go |
314
+ | `jvm` | Gradle or Maven. Also `UPGRADING.md`, `MIGRATION.md`, and `gradle.properties` against the changelog |
315
+ | `k8s` | Helm charts. Also `RUNBOOK.md`, and `Chart.yaml`'s chart version against the changelog |
316
+ | `terraform` | Terraform modules. Also `UPGRADING.md` and `MIGRATION.md`, beside the terraform-docs README |
317
+ | `docker` | images and compose. Also `DEPLOY.md`, `RUNBOOK.md`, `OPERATIONS.md` |
318
+ | `monorepo` | a workspace root. Also `ARCHITECTURE.md`, `docs/README.md`, and the root version |
319
+ | `mobile` | iOS and Android. Also `RELEASE_NOTES.md`, `PRIVACY.md`, and one marketing version across both stores |
320
+ | `unity` | a Unity project. Checks the editor-version badge against `ProjectSettings/ProjectVersion.txt` |
321
+ | `godot` | a Godot project. Checks the version in `doc/setup_instructions.md` against `project.godot` |
322
+ | `agent` | files AI agents read as fact: `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `copilot-instructions.md` |
323
+ | `status` | a running status file with dated entries |
324
+
325
+ **`agent` is worth a note.** [AGENTS.md](https://agents.md) is the cross-tool
326
+ standard for instructing AI coding agents, and `CLAUDE.md` and `GEMINI.md` are
327
+ the vendor-native equivalents. Those files are mostly paths and commands, and an
328
+ agent cannot tell an expired line from a current one. A dead path in a README
329
+ wastes somebody's afternoon; a dead path in an `AGENTS.md` becomes confidently
330
+ wrong work.
331
+
332
+ **Where two files look like they should agree and do not, no check is written.**
333
+ Helm documents `appVersion` as unrelated to the chart `version`. Terraform's
334
+ `required_version` is a constraint (`">= 1.5.0"`) while `.terraform-version` is
335
+ an exact pin. Go's `go` directive is a minimum and `toolchain` is exact. A mobile
336
+ `versionCode` is a build counter, not the version anyone sees. Every one of those
337
+ pairs would report a correct repository, so `docker` and `terraform` carry no
338
+ cross-check at all, and the rest compare only what genuinely must match.
339
+
340
+ The last three exist because long-lived documentation rots in a particular
341
+ place. An enterprise project rarely has a stale README. It has a `MIGRATION.md`
342
+ last edited in 2021 pointing at three files that have since moved.
343
+
344
+ Two rules govern every preset:
345
+
346
+ **A preset never overrides something measured.** Detection reads your repository
347
+ for the trunk name, branch naming and commit conventions. A template would be
348
+ guessing at those, and a preset that quietly replaced your real branch name
349
+ would be the copied-configuration problem this tool was built around. Choosing
350
+ the *document* is your call, which is why passing a preset settles that one.
351
+
352
+ **Checks whose files are absent are skipped and reported.** A preset never opens
353
+ by complaining about a file you do not have. A tool whose first act is a false
354
+ positive has taught a lesson that is very hard to unteach.
355
+
356
+ ### Works with any coding agent
357
+
358
+ The checker is Python, git hooks and a pre-commit entry, so it never depended on
359
+ a particular assistant. What did was one line deciding where the agent-facing
360
+ instructions went.
361
+
362
+ Setup now writes them to `.agents/skills/extant/SKILL.md`, the location the
363
+ [Agent Skills](https://agentskills.io) open standard defines. One file, read by
364
+ **OpenAI Codex, Gemini CLI, GitHub Copilot, Cursor, Kimi Code, Claude Code** and
365
+ the twenty-odd other tools that adopted it.
366
+
367
+ It is rendered for your repository rather than copied, so it names your document
368
+ and your paths, and it carries the discipline that matters: read the
369
+ denominator, never make a document pass by deleting the claim.
370
+
371
+ Claude Code additionally gets `/extant`, a slash command for the end-to-end
372
+ workflow. Both are rendered from the same observations, so they cannot end up
373
+ describing different documents.
374
+
375
+ ### What lands in your project
376
+
377
+ | | |
378
+ |:---|:---|
379
+ | `tools/` | the checker |
380
+ | `.extant.toml` | settings, written by reading **your** project |
381
+ | git hooks | the automatic checks. They report, they never block |
382
+ | `.agents/skills/extant/SKILL.md` | instructions any agent reads, rendered for your project |
383
+ | `/extant` command | Claude Code only, for the full workflow |
384
+
385
+ ---
386
+
387
+ ## Features
388
+
389
+ ### Every check reports its denominator
390
+
391
+ The summary line counts what was **examined**, not what was found:
392
+
393
+ ```console
394
+ checked STATUS.md: dead-sha 36, stale-live-claim 1, false-merge-claim 2,
395
+ dead-path-pointer 5 (907 lines scanned for secrets)
396
+ ```
397
+
398
+ If one of those reads `0`, that check found nothing to look at, which usually
399
+ means a setting is wrong rather than that your file is spotless. Any rule that
400
+ examined nothing is named on a `NOTE:` line.
401
+
402
+ This is the single most important line in the output. "Found no problems" and
403
+ "did not look" print identically otherwise, and only one of them is good news.
404
+
405
+ ### It can prove its own checks fire
406
+
407
+ The worry above deserves more than a warning, so there is a command:
408
+
409
+ ```console
410
+ $ python tools/extant_collect.py --selftest
411
+ ```
412
+
413
+ It takes a real claim from your document, breaks it on purpose, confirms the
414
+ matching rule notices, and puts everything back. Nothing is written.
415
+
416
+ ```console
417
+ dead-sha FIRED
418
+ stale-live-claim FIRED
419
+ dead-path-pointer FIRED
420
+ dead-release-tag NO PROBE nothing to corrupt
421
+ possible-secret FIRED
422
+
423
+ 4 fired, 1 had nothing to corrupt, 0 stayed silent
424
+ ```
425
+
426
+ **A check that stays silent after you break something it should catch is
427
+ broken.** This runs in CI here on every change, so the tool is not merely
428
+ tested, it is watched failing.
429
+
430
+ ### Findings inside pull requests
431
+
432
+ ```console
433
+ $ python tools/extant_collect.py --verify --format=github
434
+ $ python tools/extant_collect.py --verify --format=sarif
435
+ ```
436
+
437
+ **`github`** emits GitHub Actions annotations, so each problem is highlighted on
438
+ its own line in the pull request diff rather than buried in a log nobody opens.
439
+ Add the flag to your existing step. Nothing else is needed, and it requires no
440
+ extra permissions.
441
+
442
+ **`sarif`** emits the standard format code-scanning tools exchange, as pure JSON
443
+ on stdout, so it pipes straight to a file. To get results into GitHub's Security
444
+ tab:
445
+
446
+ ```yaml
447
+ - name: Check the documentation
448
+ run: python tools/extant_collect.py --verify --format=sarif > extant.sarif
449
+ continue-on-error: true
450
+
451
+ - uses: github/codeql-action/upload-sarif@v3
452
+ with:
453
+ sarif_file: extant.sarif
454
+ ```
455
+
456
+ That upload needs `permissions: security-events: write`. This repository uses
457
+ the annotation route instead, so treat the SARIF upload as a starting point
458
+ rather than something proven here.
459
+
460
+ ### Files that contradict each other
461
+
462
+ The tool never asks whether a number is *correct*. It will ask whether two files
463
+ disagree, which has a definite answer needing only the filesystem.
464
+
465
+ This project shipped the bug that motivated it: three manifests said the version
466
+ was 0.1.0 while the changelog said 0.3.0, and everyone installing was told they
467
+ were getting the first release.
468
+
469
+ ```toml
470
+ [extant.consistency.version]
471
+ "package.json" = '"version":\s*"([^"]+)"'
472
+ "CHANGELOG.md" = '^## (\d+\.\d+\.\d+)'
473
+ ```
474
+
475
+ Each line names a file and how to find the value inside it. Disagree, and you
476
+ are told which file says what.
477
+
478
+ Four shapes are refused when the settings load, because each would produce a
479
+ check that can never fail: a check listing one file, a pattern with no capture
480
+ group, a pattern with more than one, and the same file named twice under
481
+ different spellings. A pattern that matches nothing is reported rather than
482
+ passing quietly.
483
+
484
+ ### Corrections as a patch, never an edit
485
+
486
+ When a file has been renamed, the tool already tells you where it went. It can
487
+ also write the correction:
488
+
489
+ ```console
490
+ $ python tools/extant_collect.py --verify --suggest-fixes
491
+ ```
492
+
493
+ This prints a **patch** and changes nothing. Read it, and apply it if you agree:
494
+
495
+ ```console
496
+ $ python tools/extant_collect.py --verify --suggest-fixes | git apply -
497
+ ```
498
+
499
+ It only offers corrections for files git actually recorded as renamed. If a file
500
+ is simply gone it says nothing, because guessing where it went means writing
501
+ something that might not be true, and that is the one thing this refuses to do.
502
+ Its authority rests on checking claims and never authoring them.
503
+
504
+ ### Search across the archive
505
+
506
+ If you keep a status document, old entries get archived so the live file stays
507
+ short. That helps until you need to remember why a decision was made.
508
+
509
+ ```console
510
+ $ python tools/extant_collect.py --search "checkout"
511
+ ```
512
+
513
+ It searches the live document and the archive together and returns whole
514
+ **entries** rather than matching lines. That is the only reason it beats grep: a
515
+ decision lives in a dated entry with its reasoning, and a line from the middle
516
+ tells you a phrase exists rather than what was decided.
517
+
518
+ ### Every other document you keep
519
+
520
+ Your primary document is not the only one that rots. A `CLAUDE.md`, an
521
+ `AGENTS.md`, a `CONTRIBUTING.md` all make the same kinds of claim:
522
+
523
+ ```toml
524
+ extra_docs = ["CLAUDE.md", "AGENTS.md", "CONTRIBUTING.md"]
525
+ ```
526
+
527
+ They get every rule that does not depend on dated entries, which is most of
528
+ them.
529
+
530
+ ### Adopting on a project that already has years of prose
531
+
532
+ Point this at a ten-year-old repository and the first run reports everything at
533
+ once. That is accurate and useless: CI goes red, nobody has a week for
534
+ decade-old documentation, and the tool comes back out.
535
+
536
+ Record what is already there, once:
537
+
538
+ ```console
539
+ $ python tools/extant_collect.py --verify --write-baseline
540
+ recorded 47 finding(s) in .extant-baseline.json
541
+ ```
542
+
543
+ Then every run checks **new** claims and ignores the recorded ones:
544
+
545
+ ```console
546
+ $ python tools/extant_collect.py --verify --baseline
547
+ 1 new finding(s), 47 suppressed by .extant-baseline.json
548
+ ```
549
+
550
+ New documentation is held to the standard from day one; the backlog waits.
551
+
552
+ **It always says how much it is hiding.** "No findings" and "no new findings, 47
553
+ suppressed" are different facts, and a baseline that concealed its own size
554
+ would be exactly the failure this tool exists to surface, reintroduced by one of
555
+ its own features.
556
+
557
+ **Nothing is ever recorded implicitly.** `--write-baseline` is a separate,
558
+ deliberate command. A baseline that rewrote itself on every run would forgive
559
+ whatever it had just found, and the check would decay to nothing while still
560
+ reporting success.
561
+
562
+ **An amnesty must not outlive its finding:**
563
+
564
+ ```console
565
+ $ python tools/extant_collect.py --verify --baseline-check
566
+ baseline: 47 entr(y/ies), 45 still occur, 2 do not
567
+ STALE docs/setup.md: [dead-md-link] links to `scripts/old.sh`, ...
568
+ ```
569
+
570
+ Those two were fixed. Their entries now forgive something that is not there, so
571
+ they are reported and should be deleted. A baseline nobody prunes becomes a
572
+ permanent exemption, and it is itself a stale claim.
573
+
574
+ The file is JSON, and deliberately readable: each entry carries the path, the
575
+ rule and the message alongside its fingerprint. It is a list of things your
576
+ project has agreed to leave broken for now, so it belongs in review like any
577
+ other change.
578
+
579
+ ### The optional wrong-branch guard
580
+
581
+ One check is **off unless you ask for it**, because unlike everything else here
582
+ it can refuse to save your work:
583
+
584
+ ```console
585
+ $ sh tools/hooks/install --with-trunk-guard
586
+ ```
587
+
588
+ **What it solves.** Git lets you keep several versions of a project going at
589
+ once. If you lose track of which one you are on, you can commit to the wrong
590
+ branch. The work is not lost, but it is filed in the wrong place, and finding
591
+ out later is unpleasant.
592
+
593
+ **Why it is off by default.** It has nothing to do with your documentation. You
594
+ came for a tool that checks whether your writing is still true, and a tool that
595
+ suddenly refuses to save your work for an unrelated reason is a tool people
596
+ uninstall.
597
+
598
+ **Should you?** If you work on one branch, no. If you juggle several, or have
599
+ ever pushed work and found it on the wrong branch, yes. It is also worth it if
600
+ AI assistants commit in your project, because they are particularly good at
601
+ losing track of which branch they are on.
602
+
603
+ Remove it by deleting `pre-commit` from your project's `.git/hooks`, or bypass
604
+ it once with `git commit --no-verify`.
605
+
606
+ ### Any language
607
+
608
+ The checking does not care what your project is written in. Only the optional
609
+ "run the tests" step does, and you name the command:
610
+
611
+ ```toml
612
+ suite_command = ["npm", "test"]
613
+ suite_command = ["cargo", "test"]
614
+ ```
615
+
616
+ ### Fast enough to leave on
617
+
618
+ A 16,000-line document validates in under a second. A 100,000-line document in
619
+ about four. The rules that query git batch their questions, so a document
620
+ naming two thousand distinct commits asks git once per commit rather than twice
621
+ per claim.
622
+
623
+ ---
624
+
625
+ ## Read the settings it writes
626
+
627
+ Setup works out your project's habits by reading it, and prints each value with
628
+ how confident it is:
629
+
630
+ ```console
631
+ trunk [derived ] origin/HEAD -> main
632
+ branch_token [derived ] 128 branches sampled
633
+ entry_prefix [guessed ] highest-scoring header '## Release'
634
+ merge_claim [unknown ] no matching phrasing found
635
+ ```
636
+
637
+ Anything it could not work out is left **switched off** rather than guessed.
638
+
639
+ > **This is the part that matters.** If a setting is wrong, that rule quietly
640
+ > does nothing and you get a tool reporting "all clear" forever without looking
641
+ > at anything. It is the one way this fails badly. Read what setup prints, and
642
+ > see [porting.md](plugin/skills/extant/references/porting.md) to fill gaps.
643
+
644
+ ---
645
+
646
+ ## What it cannot do
647
+
648
+ - Only catches sentences that can be **proven** wrong. "Nearly finished" is
649
+ beyond it, on purpose.
650
+ - Cannot judge whether a summary is a *good* summary.
651
+ - Expects consistent headings for dated entries. A heading that does not match
652
+ is skipped.
653
+ - Assumes one main branch. Gitflow-style release branches are not modelled.
654
+ - **Checks links to your own files, not to the web.** Nothing here touches the
655
+ network. Checking external links would make a passing run depend on someone
656
+ else's uptime and rate limits, turning a definite answer into a coin flip.
657
+ Issue and pull request links go unchecked for the same reason.
658
+ - **Does not complain about a branch merged and then deleted.** That is normal
659
+ tidying, and the branch is still named in the merge commit. Only a name git
660
+ has never seen is reported.
661
+ - **Settings load next to the tool, not next to the folder you point at.**
662
+ Correct once installed in your project, wrong if you run it from elsewhere. It
663
+ says so on stderr rather than quietly using the wrong ones.
664
+ - **It cannot tell a corrected claim from a deleted one.** Removing the sentence
665
+ it complained about makes a document pass. The `/extant` workflow works around
666
+ this by reporting first-run findings even after fixing them, so a deletion is
667
+ visible in both the report and the diff. The checker alone cannot know.
668
+ - **A pattern you write yourself can hang it.** Settings take regular
669
+ expressions, and a badly shaped one can take effectively forever on some text.
670
+ The fix is to simplify the pattern, but the failure looks like a freeze rather
671
+ than a complaint.
672
+
673
+ ---
674
+
675
+ <details>
676
+ <summary><b>If something goes wrong</b></summary>
677
+
678
+ <br>
679
+
680
+ **"python is not recognised"** on Windows, or **"command not found"** on macOS
681
+ and Linux: try `python3`, or reinstall Python with the "add to PATH" box ticked.
682
+
683
+ **The checks never run.** Confirm you did step 4, then run `--verify` by hand.
684
+
685
+ **It flags something you think is fine.** Do not silence it by deleting the
686
+ sentence. Either that sentence really is out of date, or a setting needs
687
+ adjusting. Both are worth knowing.
688
+
689
+ **Everything reports 0.** A setting is almost certainly wrong. See
690
+ [Read the settings it writes](#read-the-settings-it-writes).
691
+
692
+ </details>
693
+
694
+ <details>
695
+ <summary><b>Deeper documentation</b></summary>
696
+
697
+ <br>
698
+
699
+ All under `plugin/skills/extant/`:
700
+
701
+ | File | What is in it |
702
+ |:---|:---|
703
+ | `references/porting.md` | Getting the settings right for your project. Read first. |
704
+ | `references/config.md` | Every setting explained. |
705
+ | `references/design.md` | Why each rule works as it does, and the real mistake behind each decision. |
706
+ | `SKILL.md` | What Claude reads. |
707
+
708
+ </details>
709
+
710
+ <details>
711
+ <summary><b>What is in this repository</b></summary>
712
+
713
+ <br>
714
+
715
+ ```
716
+ .claude-plugin/marketplace.json lets Claude Code install this
717
+ .pre-commit-hooks.yaml lets pre-commit install this
718
+ plugin/
719
+ .claude-plugin/plugin.json plugin details
720
+ skills/extant/
721
+ SKILL.md what Claude reads
722
+ install.py, detect.py the setup program
723
+ payload/ what gets copied into your project
724
+ references/ the deeper documentation
725
+ tests/ 219 tests
726
+ tests/harnesses/ five slow audits, run by hand
727
+ NEXT_SESSION.md this project's own status document
728
+ ```
729
+
730
+ That last one is not decoration. This project runs its own tool on its own
731
+ document, on every change, in CI. If it stopped working, this repository would
732
+ be the first to find out.
733
+
734
+ `tests/harnesses/` holds the audits pytest cannot perform: one that breaks the
735
+ code on purpose to see whether any test notices (57 mutations, all caught), one
736
+ that installs into twenty unlike projects (20 scenarios, 87 assertions), one
737
+ that tries to abuse the tool (18 adversarial probes), and two that measure speed
738
+ and load. Between them they found every defect fixed in 0.3.0, and the stale
739
+ assertion caught in the audit before 0.6.0. The unit suite found none of them,
740
+ because the unit suite was the thing being audited.
741
+
742
+ The bug fixed in 0.6.1 was found by none of them. It took installing the
743
+ published release and using it as a stranger would, which is its own lesson.
744
+
745
+ </details>
746
+
747
+ <details>
748
+ <summary><b>The one idea worth taking elsewhere</b></summary>
749
+
750
+ <br>
751
+
752
+ When you write a rule that checks something, **look at your real data first and
753
+ shape the rule to fit it**, rather than writing what the rule ought to look like
754
+ and hoping. Done the second way, the file-path rule produced 23 false alarms on
755
+ the first project it ran against. Done the first way it produced none, and still
756
+ caught the real problem.
757
+
758
+ And always report how many things you looked at. "Found no problems" and "did
759
+ not look" print exactly the same otherwise.
760
+
761
+ </details>
762
+
763
+ ---
764
+
765
+ <div align="center">
766
+
767
+ **MIT licensed.** Use it freely, including commercially. See [LICENSE](LICENSE).
768
+
769
+ </div>