constant-docs 0.7.0__tar.gz → 0.8.0__tar.gz
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.
- {constant_docs-0.7.0 → constant_docs-0.8.0}/PKG-INFO +64 -55
- {constant_docs-0.7.0 → constant_docs-0.8.0}/README.md +63 -54
- {constant_docs-0.7.0 → constant_docs-0.8.0}/pyproject.toml +1 -1
- {constant_docs-0.7.0 → constant_docs-0.8.0}/pyproject.toml.orig +1 -1
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/api.py +86 -11
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/cli.py +33 -12
- {constant_docs-0.7.0 → constant_docs-0.8.0}/LICENSE +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/__init__.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/__main__.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/auto.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/checks.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/completeness.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/config.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/coverage.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/decisions.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/document.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/fingerprint.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/globs.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/guides/quickstart.md +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/guides/readme.md +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/house-style.md +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/index.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/kinds.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/paths.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/prompts/architecture.md +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/prompts/cli-reference.md +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/prompts/config-reference.md +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/prompts/errors.md +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/prompts/log.md +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/prompts/module.md +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/prompts/spec.md +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/state.py +0 -0
- {constant_docs-0.7.0 → constant_docs-0.8.0}/src/constant_docs/vcs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: constant-docs
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Self-maintaining documentation for agentic codebases
|
|
5
5
|
Keywords: documentation,docs,staleness,drift,ci,codegen,agents,llm
|
|
6
6
|
Author: Ashborn Systems
|
|
@@ -51,11 +51,11 @@ constant_docs:
|
|
|
51
51
|
- src/constant_docs/api.py
|
|
52
52
|
- src/constant_docs/cli.py
|
|
53
53
|
- src/constant_docs/kinds.py
|
|
54
|
-
source_hash: sha256:
|
|
54
|
+
source_hash: sha256:831cdb38a60c83171b114148aff40e507c1feb9c94600e9404de748e293f9b21
|
|
55
55
|
hash_method: sha256-over-sorted-path-and-content
|
|
56
56
|
hash_covers: source_files
|
|
57
|
-
timestamp: '2026-09-
|
|
58
|
-
generator: constant-docs/0.
|
|
57
|
+
timestamp: '2026-09-05T16:46:40Z'
|
|
58
|
+
generator: constant-docs/0.8.0
|
|
59
59
|
generator_spec: https://github.com/ashborn-systems/constant-docs/blob/main/SPEC.md
|
|
60
60
|
-->
|
|
61
61
|
|
|
@@ -94,8 +94,9 @@ Your coding agent writes every word, because it already holds the source and a
|
|
|
94
94
|
model connection. constant-docs names the documents that moved and takes the
|
|
95
95
|
new text back.
|
|
96
96
|
|
|
97
|
-
That one command is also the whole
|
|
98
|
-
change that alters behaviour and leaves its document behind does
|
|
97
|
+
That one command is also the whole continuous-integration check. It exits 1 on
|
|
98
|
+
drift, so a change that alters behaviour and leaves its document behind does
|
|
99
|
+
not merge:
|
|
99
100
|
|
|
100
101
|
```yaml
|
|
101
102
|
- run: constant-docs verify
|
|
@@ -168,12 +169,12 @@ marks say. `verify` hashes everything and exits 1 on drift. Keep `verify` as
|
|
|
168
169
|
the gate.
|
|
169
170
|
|
|
170
171
|
Claude Code gets a plugin. Install it and two hooks run those commands for you:
|
|
171
|
-
`PostToolUse` runs `mark
|
|
172
|
-
turn, and its document is up to date. Any
|
|
173
|
-
write and at turn end does the same job.
|
|
172
|
+
`PostToolUse` runs `mark` after each edit, and `Stop` runs `settle --hook` when
|
|
173
|
+
the turn ends. Edit a file, stop the turn, and its document is up to date. Any
|
|
174
|
+
harness that can run a command on write and at turn end does the same job.
|
|
174
175
|
|
|
175
|
-
Regeneration happens at
|
|
176
|
-
coding loop never write the same file at once. The dirty set lives in
|
|
176
|
+
Regeneration happens at the end of a turn, never mid-edit, so the generator and
|
|
177
|
+
the coding loop never write the same file at once. The dirty set lives in
|
|
177
178
|
`.constant-docs/dirty.json`, which is gitignored, so a crashed session is
|
|
178
179
|
picked up on the next run.
|
|
179
180
|
|
|
@@ -181,9 +182,9 @@ The [harness guide](guides/harness-integration.md) walks through each route.
|
|
|
181
182
|
|
|
182
183
|
## Kinds
|
|
183
184
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
Every document has a kind. The kind decides which sections its body must carry,
|
|
186
|
+
which prompt its generator is handed, and whether a regeneration replaces the
|
|
187
|
+
body or appends an entry.
|
|
187
188
|
|
|
188
189
|
| Kind | For | Mode |
|
|
189
190
|
|---|---|---|
|
|
@@ -191,7 +192,7 @@ replaces the body or appends to it.
|
|
|
191
192
|
| `spec` | What a subsystem promises, and why | replace |
|
|
192
193
|
| `log` | A changelog or build log | **append** |
|
|
193
194
|
| `architecture` | The module map, with a diagram | replace |
|
|
194
|
-
| `errors` | Every message the
|
|
195
|
+
| `errors` | Every message the covered code can raise | replace |
|
|
195
196
|
| `config-reference` | Every configuration key | replace |
|
|
196
197
|
| `cli-reference` | Every command and flag | replace |
|
|
197
198
|
|
|
@@ -212,8 +213,9 @@ An append never rewrites or reorders what is already there. Pass a whole body
|
|
|
212
213
|
to a log, or a single entry to a module document, and the tool refuses it. A
|
|
213
214
|
configuration that has never heard of kinds behaves exactly as it did.
|
|
214
215
|
|
|
215
|
-
One house style governs every
|
|
216
|
-
that all seven built-in prompts receive, so the rules cannot
|
|
216
|
+
One house style governs every document written from a built-in prompt. It lives
|
|
217
|
+
in a single file that all seven built-in prompts receive, so the rules cannot
|
|
218
|
+
drift apart.
|
|
217
219
|
|
|
218
220
|
## Finding what is undocumented
|
|
219
221
|
|
|
@@ -223,17 +225,18 @@ constant-docs coverage # source no module covers, by directory
|
|
|
223
225
|
constant-docs completeness # kinds of document the contents warrant
|
|
224
226
|
```
|
|
225
227
|
|
|
226
|
-
`verify` can only check what has been declared, so a repository
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
justified is one nobody
|
|
228
|
+
`verify` can only check what has been declared, so a repository can drift a
|
|
229
|
+
long way undocumented while every check passes. `coverage` does one thing: a
|
|
230
|
+
set difference. It reports the files no declared glob matches and no exclusion
|
|
231
|
+
names. Declare the directories that should carry no document in the
|
|
232
|
+
configuration, with a reason each. An exclusion nobody justified is one nobody
|
|
233
|
+
decided.
|
|
231
234
|
|
|
232
235
|
Coverage answers whether a file has a document. It cannot answer whether the
|
|
233
|
-
set of documents is complete
|
|
234
|
-
and a
|
|
235
|
-
|
|
236
|
-
|
|
236
|
+
set of documents is complete. A document that was never written cannot drift,
|
|
237
|
+
and a repository with a document per module and no specification passes every
|
|
238
|
+
coverage check. `completeness` proposes the kinds your contents warrant, each
|
|
239
|
+
with the fact behind it:
|
|
237
240
|
|
|
238
241
|
```
|
|
239
242
|
Warranted and not written (2):
|
|
@@ -247,11 +250,11 @@ Warranted and not written (2):
|
|
|
247
250
|
It proposes from evidence in the repository, such as a console script or an
|
|
248
251
|
exception hierarchy. The existence of a kind is not itself evidence. A library
|
|
249
252
|
with no command line is not offered a CLI reference, because a document nobody
|
|
250
|
-
needs still has to be kept true and still
|
|
253
|
+
needs still has to be kept true and still fails the build when it drifts.
|
|
251
254
|
Nothing warrants a changelog: every repository could keep one, so the signal
|
|
252
255
|
fires everywhere and says nothing.
|
|
253
256
|
|
|
254
|
-
Write the document, or decline the kind under `unwarranted:` with a reason and
|
|
257
|
+
Write the document, or decline the kind under `unwarranted:` with a reason, and
|
|
255
258
|
it stops being proposed.
|
|
256
259
|
|
|
257
260
|
`verify --coverage` and `verify --completeness` fold the two into the gate,
|
|
@@ -261,13 +264,14 @@ document, and `prune` deletes it.
|
|
|
261
264
|
## Files it will not touch
|
|
262
265
|
|
|
263
266
|
Pointing the tool at a `docs/` folder the repository already has is safe. Every
|
|
264
|
-
document this tool writes carries a block naming the tool
|
|
265
|
-
|
|
266
|
-
|
|
267
|
+
document this tool writes carries a block naming the tool and, when you declare
|
|
268
|
+
a `project` id, the project that wrote it. A markdown file without that block
|
|
269
|
+
belongs to somebody else, and three commands act on the difference:
|
|
267
270
|
|
|
268
271
|
- `apply` refuses to write over one. It names the file and changes no byte
|
|
269
272
|
- `prune` refuses to delete one, and names every file it left alone
|
|
270
|
-
- `verify` lists them under a heading of their own, and
|
|
273
|
+
- `verify` lists them under a heading of their own, and none of them fails the
|
|
274
|
+
check
|
|
271
275
|
|
|
272
276
|
Take one over when you want it maintained:
|
|
273
277
|
|
|
@@ -288,7 +292,7 @@ rewrite it. Every body your agent generates still carries those headings, and
|
|
|
288
292
|
`apply` checks before it writes a byte.
|
|
289
293
|
|
|
290
294
|
A glob that matches no file gets the same restraint. `verify` names the module
|
|
291
|
-
and exits 1, `apply` refuses to record a
|
|
295
|
+
and exits 1, `apply` refuses to record a hash over nothing, and the document
|
|
292
296
|
stays where it is. A mistyped glob costs you a failing check and nothing else.
|
|
293
297
|
|
|
294
298
|
## Documents outside the repository
|
|
@@ -309,12 +313,14 @@ real directory on this machine: where to open the file.
|
|
|
309
313
|
|
|
310
314
|
The variable must resolve when the configuration loads. Unset, empty, relative,
|
|
311
315
|
or naming a directory that is not there, each refuses the configuration and
|
|
312
|
-
says which. A store landing inside `.ssh`, `Secret/`, a cache or any other
|
|
313
|
-
directory on the deny list is refused too
|
|
314
|
-
directories, so it declines to write documents into them.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
says which. A store landing inside `.ssh`, `Secret/`, a cache, or any other
|
|
317
|
+
directory on the deny list is refused too: the tool declines to read those
|
|
318
|
+
directories, so it declines to write documents into them.
|
|
319
|
+
|
|
320
|
+
Plain `${NAME}` is the whole syntax. A default value would let a machine
|
|
321
|
+
without the vault write documents into the repository and report success. The
|
|
322
|
+
Stop hook blocks on the same failure, so a missing variable cannot switch the
|
|
323
|
+
gate off quietly.
|
|
318
324
|
|
|
319
325
|
Deletion changes as well. A repository's documents ride the branch, and `git
|
|
320
326
|
checkout` brings one back. A store stands still while the checkout moves, so
|
|
@@ -334,12 +340,14 @@ auto:
|
|
|
334
340
|
budget: 5
|
|
335
341
|
```
|
|
336
342
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
`verify` afterwards.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
+
constant-docs itself calls no model. It splits your command with `shlex` and
|
|
344
|
+
runs it directly, so an agent CLI, a script, and a Makefile target all work. No
|
|
345
|
+
credential goes in it. The command's success is checked: `auto` re-runs
|
|
346
|
+
`verify` afterwards.
|
|
347
|
+
|
|
348
|
+
It exits 0 when everything is clean, 1 when something is still stale, and 2
|
|
349
|
+
when the command could not run or exited non-zero. Three outcomes, because a
|
|
350
|
+
scheduler that confuses the last two retries the wrong one.
|
|
343
351
|
|
|
344
352
|
## Three things it checks that a hash cannot
|
|
345
353
|
|
|
@@ -354,9 +362,10 @@ document, with a date.
|
|
|
354
362
|
in those modules' files by name, so it goes stale when their boundary moves.
|
|
355
363
|
|
|
356
364
|
**An error catalogue agrees with the source, both ways.** Every message the
|
|
357
|
-
code raises appears in the catalogue, and every entry still
|
|
358
|
-
|
|
359
|
-
one document here the tool can
|
|
365
|
+
code raises appears in the catalogue, and every entry listed is still raised in
|
|
366
|
+
the code. The check needs no parsing beyond a string scan, so it costs nothing,
|
|
367
|
+
and it is the one document here the tool can guarantee. The rest are only
|
|
368
|
+
fresh.
|
|
360
369
|
|
|
361
370
|
## What a document looks like
|
|
362
371
|
|
|
@@ -381,7 +390,7 @@ constant_docs:
|
|
|
381
390
|
hash_method: sha256-over-sorted-path-and-content
|
|
382
391
|
hash_covers: source_files
|
|
383
392
|
timestamp: '2026-08-19T09:14:00Z'
|
|
384
|
-
generator: constant-docs/0.
|
|
393
|
+
generator: constant-docs/0.8.0
|
|
385
394
|
generator_spec: https://github.com/ashborn-systems/constant-docs/blob/main/SPEC.md
|
|
386
395
|
---
|
|
387
396
|
## Purpose
|
|
@@ -404,7 +413,7 @@ report records that it happened.
|
|
|
404
413
|
|
|
405
414
|
The frontmatter stands alone. An agent that has never run this tool can see
|
|
406
415
|
what the document describes, where its source lives, and how to recompute the
|
|
407
|
-
|
|
416
|
+
hash. `project` answers the question asked before every overwrite and every
|
|
408
417
|
deletion: whose document is this. The tool keeps everything it owns under one
|
|
409
418
|
`constant_docs` key, an Open Knowledge Format producer extension. `type`,
|
|
410
419
|
`title`, `tags` and the two dates stay yours, and unknown keys survive a write.
|
|
@@ -424,11 +433,11 @@ prune() # deletes orphans; under a store, lists them
|
|
|
424
433
|
|
|
425
434
|
## What it does not do
|
|
426
435
|
|
|
427
|
-
A module names a glob, and the tool
|
|
428
|
-
matches. Nothing in that step knows a language, which is what makes
|
|
429
|
-
no model client, no network call, no
|
|
430
|
-
byte moves the hash. Fixing a typo in a comment reports the
|
|
431
|
-
your agent rewrites a document the edit never touched.
|
|
436
|
+
A module names a glob, and the tool hashes the bytes of the files that glob
|
|
437
|
+
matches. Nothing in that step knows a language, which is what makes it work on
|
|
438
|
+
yours: no model client, no network call, no syntax tree, no import graph. The
|
|
439
|
+
cost is that any byte moves the hash. Fixing a typo in a comment reports the
|
|
440
|
+
module stale, and your agent rewrites a document the edit never touched.
|
|
432
441
|
|
|
433
442
|
It watches files and nothing else. A document quoting a queue depth or a
|
|
434
443
|
production URL stays fresh for ever, because nobody edited anything. Hash what
|
|
@@ -22,11 +22,11 @@ constant_docs:
|
|
|
22
22
|
- src/constant_docs/api.py
|
|
23
23
|
- src/constant_docs/cli.py
|
|
24
24
|
- src/constant_docs/kinds.py
|
|
25
|
-
source_hash: sha256:
|
|
25
|
+
source_hash: sha256:831cdb38a60c83171b114148aff40e507c1feb9c94600e9404de748e293f9b21
|
|
26
26
|
hash_method: sha256-over-sorted-path-and-content
|
|
27
27
|
hash_covers: source_files
|
|
28
|
-
timestamp: '2026-09-
|
|
29
|
-
generator: constant-docs/0.
|
|
28
|
+
timestamp: '2026-09-05T16:46:40Z'
|
|
29
|
+
generator: constant-docs/0.8.0
|
|
30
30
|
generator_spec: https://github.com/ashborn-systems/constant-docs/blob/main/SPEC.md
|
|
31
31
|
-->
|
|
32
32
|
|
|
@@ -65,8 +65,9 @@ Your coding agent writes every word, because it already holds the source and a
|
|
|
65
65
|
model connection. constant-docs names the documents that moved and takes the
|
|
66
66
|
new text back.
|
|
67
67
|
|
|
68
|
-
That one command is also the whole
|
|
69
|
-
change that alters behaviour and leaves its document behind does
|
|
68
|
+
That one command is also the whole continuous-integration check. It exits 1 on
|
|
69
|
+
drift, so a change that alters behaviour and leaves its document behind does
|
|
70
|
+
not merge:
|
|
70
71
|
|
|
71
72
|
```yaml
|
|
72
73
|
- run: constant-docs verify
|
|
@@ -139,12 +140,12 @@ marks say. `verify` hashes everything and exits 1 on drift. Keep `verify` as
|
|
|
139
140
|
the gate.
|
|
140
141
|
|
|
141
142
|
Claude Code gets a plugin. Install it and two hooks run those commands for you:
|
|
142
|
-
`PostToolUse` runs `mark
|
|
143
|
-
turn, and its document is up to date. Any
|
|
144
|
-
write and at turn end does the same job.
|
|
143
|
+
`PostToolUse` runs `mark` after each edit, and `Stop` runs `settle --hook` when
|
|
144
|
+
the turn ends. Edit a file, stop the turn, and its document is up to date. Any
|
|
145
|
+
harness that can run a command on write and at turn end does the same job.
|
|
145
146
|
|
|
146
|
-
Regeneration happens at
|
|
147
|
-
coding loop never write the same file at once. The dirty set lives in
|
|
147
|
+
Regeneration happens at the end of a turn, never mid-edit, so the generator and
|
|
148
|
+
the coding loop never write the same file at once. The dirty set lives in
|
|
148
149
|
`.constant-docs/dirty.json`, which is gitignored, so a crashed session is
|
|
149
150
|
picked up on the next run.
|
|
150
151
|
|
|
@@ -152,9 +153,9 @@ The [harness guide](guides/harness-integration.md) walks through each route.
|
|
|
152
153
|
|
|
153
154
|
## Kinds
|
|
154
155
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
Every document has a kind. The kind decides which sections its body must carry,
|
|
157
|
+
which prompt its generator is handed, and whether a regeneration replaces the
|
|
158
|
+
body or appends an entry.
|
|
158
159
|
|
|
159
160
|
| Kind | For | Mode |
|
|
160
161
|
|---|---|---|
|
|
@@ -162,7 +163,7 @@ replaces the body or appends to it.
|
|
|
162
163
|
| `spec` | What a subsystem promises, and why | replace |
|
|
163
164
|
| `log` | A changelog or build log | **append** |
|
|
164
165
|
| `architecture` | The module map, with a diagram | replace |
|
|
165
|
-
| `errors` | Every message the
|
|
166
|
+
| `errors` | Every message the covered code can raise | replace |
|
|
166
167
|
| `config-reference` | Every configuration key | replace |
|
|
167
168
|
| `cli-reference` | Every command and flag | replace |
|
|
168
169
|
|
|
@@ -183,8 +184,9 @@ An append never rewrites or reorders what is already there. Pass a whole body
|
|
|
183
184
|
to a log, or a single entry to a module document, and the tool refuses it. A
|
|
184
185
|
configuration that has never heard of kinds behaves exactly as it did.
|
|
185
186
|
|
|
186
|
-
One house style governs every
|
|
187
|
-
that all seven built-in prompts receive, so the rules cannot
|
|
187
|
+
One house style governs every document written from a built-in prompt. It lives
|
|
188
|
+
in a single file that all seven built-in prompts receive, so the rules cannot
|
|
189
|
+
drift apart.
|
|
188
190
|
|
|
189
191
|
## Finding what is undocumented
|
|
190
192
|
|
|
@@ -194,17 +196,18 @@ constant-docs coverage # source no module covers, by directory
|
|
|
194
196
|
constant-docs completeness # kinds of document the contents warrant
|
|
195
197
|
```
|
|
196
198
|
|
|
197
|
-
`verify` can only check what has been declared, so a repository
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
justified is one nobody
|
|
199
|
+
`verify` can only check what has been declared, so a repository can drift a
|
|
200
|
+
long way undocumented while every check passes. `coverage` does one thing: a
|
|
201
|
+
set difference. It reports the files no declared glob matches and no exclusion
|
|
202
|
+
names. Declare the directories that should carry no document in the
|
|
203
|
+
configuration, with a reason each. An exclusion nobody justified is one nobody
|
|
204
|
+
decided.
|
|
202
205
|
|
|
203
206
|
Coverage answers whether a file has a document. It cannot answer whether the
|
|
204
|
-
set of documents is complete
|
|
205
|
-
and a
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
set of documents is complete. A document that was never written cannot drift,
|
|
208
|
+
and a repository with a document per module and no specification passes every
|
|
209
|
+
coverage check. `completeness` proposes the kinds your contents warrant, each
|
|
210
|
+
with the fact behind it:
|
|
208
211
|
|
|
209
212
|
```
|
|
210
213
|
Warranted and not written (2):
|
|
@@ -218,11 +221,11 @@ Warranted and not written (2):
|
|
|
218
221
|
It proposes from evidence in the repository, such as a console script or an
|
|
219
222
|
exception hierarchy. The existence of a kind is not itself evidence. A library
|
|
220
223
|
with no command line is not offered a CLI reference, because a document nobody
|
|
221
|
-
needs still has to be kept true and still
|
|
224
|
+
needs still has to be kept true and still fails the build when it drifts.
|
|
222
225
|
Nothing warrants a changelog: every repository could keep one, so the signal
|
|
223
226
|
fires everywhere and says nothing.
|
|
224
227
|
|
|
225
|
-
Write the document, or decline the kind under `unwarranted:` with a reason and
|
|
228
|
+
Write the document, or decline the kind under `unwarranted:` with a reason, and
|
|
226
229
|
it stops being proposed.
|
|
227
230
|
|
|
228
231
|
`verify --coverage` and `verify --completeness` fold the two into the gate,
|
|
@@ -232,13 +235,14 @@ document, and `prune` deletes it.
|
|
|
232
235
|
## Files it will not touch
|
|
233
236
|
|
|
234
237
|
Pointing the tool at a `docs/` folder the repository already has is safe. Every
|
|
235
|
-
document this tool writes carries a block naming the tool
|
|
236
|
-
|
|
237
|
-
|
|
238
|
+
document this tool writes carries a block naming the tool and, when you declare
|
|
239
|
+
a `project` id, the project that wrote it. A markdown file without that block
|
|
240
|
+
belongs to somebody else, and three commands act on the difference:
|
|
238
241
|
|
|
239
242
|
- `apply` refuses to write over one. It names the file and changes no byte
|
|
240
243
|
- `prune` refuses to delete one, and names every file it left alone
|
|
241
|
-
- `verify` lists them under a heading of their own, and
|
|
244
|
+
- `verify` lists them under a heading of their own, and none of them fails the
|
|
245
|
+
check
|
|
242
246
|
|
|
243
247
|
Take one over when you want it maintained:
|
|
244
248
|
|
|
@@ -259,7 +263,7 @@ rewrite it. Every body your agent generates still carries those headings, and
|
|
|
259
263
|
`apply` checks before it writes a byte.
|
|
260
264
|
|
|
261
265
|
A glob that matches no file gets the same restraint. `verify` names the module
|
|
262
|
-
and exits 1, `apply` refuses to record a
|
|
266
|
+
and exits 1, `apply` refuses to record a hash over nothing, and the document
|
|
263
267
|
stays where it is. A mistyped glob costs you a failing check and nothing else.
|
|
264
268
|
|
|
265
269
|
## Documents outside the repository
|
|
@@ -280,12 +284,14 @@ real directory on this machine: where to open the file.
|
|
|
280
284
|
|
|
281
285
|
The variable must resolve when the configuration loads. Unset, empty, relative,
|
|
282
286
|
or naming a directory that is not there, each refuses the configuration and
|
|
283
|
-
says which. A store landing inside `.ssh`, `Secret/`, a cache or any other
|
|
284
|
-
directory on the deny list is refused too
|
|
285
|
-
directories, so it declines to write documents into them.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
287
|
+
says which. A store landing inside `.ssh`, `Secret/`, a cache, or any other
|
|
288
|
+
directory on the deny list is refused too: the tool declines to read those
|
|
289
|
+
directories, so it declines to write documents into them.
|
|
290
|
+
|
|
291
|
+
Plain `${NAME}` is the whole syntax. A default value would let a machine
|
|
292
|
+
without the vault write documents into the repository and report success. The
|
|
293
|
+
Stop hook blocks on the same failure, so a missing variable cannot switch the
|
|
294
|
+
gate off quietly.
|
|
289
295
|
|
|
290
296
|
Deletion changes as well. A repository's documents ride the branch, and `git
|
|
291
297
|
checkout` brings one back. A store stands still while the checkout moves, so
|
|
@@ -305,12 +311,14 @@ auto:
|
|
|
305
311
|
budget: 5
|
|
306
312
|
```
|
|
307
313
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
`verify` afterwards.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
+
constant-docs itself calls no model. It splits your command with `shlex` and
|
|
315
|
+
runs it directly, so an agent CLI, a script, and a Makefile target all work. No
|
|
316
|
+
credential goes in it. The command's success is checked: `auto` re-runs
|
|
317
|
+
`verify` afterwards.
|
|
318
|
+
|
|
319
|
+
It exits 0 when everything is clean, 1 when something is still stale, and 2
|
|
320
|
+
when the command could not run or exited non-zero. Three outcomes, because a
|
|
321
|
+
scheduler that confuses the last two retries the wrong one.
|
|
314
322
|
|
|
315
323
|
## Three things it checks that a hash cannot
|
|
316
324
|
|
|
@@ -325,9 +333,10 @@ document, with a date.
|
|
|
325
333
|
in those modules' files by name, so it goes stale when their boundary moves.
|
|
326
334
|
|
|
327
335
|
**An error catalogue agrees with the source, both ways.** Every message the
|
|
328
|
-
code raises appears in the catalogue, and every entry still
|
|
329
|
-
|
|
330
|
-
one document here the tool can
|
|
336
|
+
code raises appears in the catalogue, and every entry listed is still raised in
|
|
337
|
+
the code. The check needs no parsing beyond a string scan, so it costs nothing,
|
|
338
|
+
and it is the one document here the tool can guarantee. The rest are only
|
|
339
|
+
fresh.
|
|
331
340
|
|
|
332
341
|
## What a document looks like
|
|
333
342
|
|
|
@@ -352,7 +361,7 @@ constant_docs:
|
|
|
352
361
|
hash_method: sha256-over-sorted-path-and-content
|
|
353
362
|
hash_covers: source_files
|
|
354
363
|
timestamp: '2026-08-19T09:14:00Z'
|
|
355
|
-
generator: constant-docs/0.
|
|
364
|
+
generator: constant-docs/0.8.0
|
|
356
365
|
generator_spec: https://github.com/ashborn-systems/constant-docs/blob/main/SPEC.md
|
|
357
366
|
---
|
|
358
367
|
## Purpose
|
|
@@ -375,7 +384,7 @@ report records that it happened.
|
|
|
375
384
|
|
|
376
385
|
The frontmatter stands alone. An agent that has never run this tool can see
|
|
377
386
|
what the document describes, where its source lives, and how to recompute the
|
|
378
|
-
|
|
387
|
+
hash. `project` answers the question asked before every overwrite and every
|
|
379
388
|
deletion: whose document is this. The tool keeps everything it owns under one
|
|
380
389
|
`constant_docs` key, an Open Knowledge Format producer extension. `type`,
|
|
381
390
|
`title`, `tags` and the two dates stay yours, and unknown keys survive a write.
|
|
@@ -395,11 +404,11 @@ prune() # deletes orphans; under a store, lists them
|
|
|
395
404
|
|
|
396
405
|
## What it does not do
|
|
397
406
|
|
|
398
|
-
A module names a glob, and the tool
|
|
399
|
-
matches. Nothing in that step knows a language, which is what makes
|
|
400
|
-
no model client, no network call, no
|
|
401
|
-
byte moves the hash. Fixing a typo in a comment reports the
|
|
402
|
-
your agent rewrites a document the edit never touched.
|
|
407
|
+
A module names a glob, and the tool hashes the bytes of the files that glob
|
|
408
|
+
matches. Nothing in that step knows a language, which is what makes it work on
|
|
409
|
+
yours: no model client, no network call, no syntax tree, no import graph. The
|
|
410
|
+
cost is that any byte moves the hash. Fixing a typo in a comment reports the
|
|
411
|
+
module stale, and your agent rewrites a document the edit never touched.
|
|
403
412
|
|
|
404
413
|
It watches files and nothing else. A document quoting a queue depth or a
|
|
405
414
|
production URL stays fresh for ever, because nobody edited anything. Hash what
|
|
@@ -89,7 +89,10 @@ class ModulePlan:
|
|
|
89
89
|
previous_body: str | None
|
|
90
90
|
previous_description: str | None
|
|
91
91
|
required_headings: list[str]
|
|
92
|
-
|
|
92
|
+
# "changed" and "missing" are answers about this repository. "regen" is an
|
|
93
|
+
# answer about the tool: the source has not moved and the document is
|
|
94
|
+
# listed anyway, because a newer prompt writes it differently.
|
|
95
|
+
reason: Literal["changed", "missing", "regen"]
|
|
93
96
|
# A kind decides the shape of the write. `mode` is the one a caller must
|
|
94
97
|
# branch on: "replace" takes a whole body through `apply`, "append" takes
|
|
95
98
|
# a single entry through `append`, and passing one to the other is refused
|
|
@@ -403,6 +406,7 @@ def plan(
|
|
|
403
406
|
config_path: str | Path | None = None,
|
|
404
407
|
changed_paths: list[str] | None = None,
|
|
405
408
|
modules: list[str] | None = None,
|
|
409
|
+
regen: bool = False,
|
|
406
410
|
) -> Plan:
|
|
407
411
|
"""Inspect the repository and return the current state.
|
|
408
412
|
|
|
@@ -412,9 +416,47 @@ def plan(
|
|
|
412
416
|
*changed_paths* of ``None`` means consider every module. *modules* names
|
|
413
417
|
module keys directly, which is what `settle` has and saves it inventing a
|
|
414
418
|
path that resolves back to them.
|
|
419
|
+
|
|
420
|
+
*regen* lists every module as work whether or not its source moved, which
|
|
421
|
+
is the one question a content hash cannot answer: the document is not
|
|
422
|
+
behind its code, it is behind the conventions it was written against. A
|
|
423
|
+
release that changes the prompts leaves every document passing `verify`
|
|
424
|
+
and none of them written the new way.
|
|
425
|
+
|
|
426
|
+
The previous body is still supplied, and the prompt carries an
|
|
427
|
+
instruction to carry its content across. A regeneration that starts from
|
|
428
|
+
an empty page keeps every heading and loses the reasons underneath them,
|
|
429
|
+
which no check here can see.
|
|
415
430
|
"""
|
|
416
431
|
resolved = _resolve_config(config_path)
|
|
417
|
-
return _plan(
|
|
432
|
+
return _plan(
|
|
433
|
+
load_config(resolved), _repo_root(resolved), changed_paths, modules, regen=regen
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# Appended to every prompt a `--regen` plan carries. Without it, a rewrite
|
|
438
|
+
# against an unchanged source loses content: regenerating this repository's
|
|
439
|
+
# own `config` document dropped 22 of its 38 correctness pillars, and the SPEC
|
|
440
|
+
# came back at 10,065 words against 24,502. The same run with this paragraph
|
|
441
|
+
# in front of the generator returned 102% and 98% of the originals.
|
|
442
|
+
#
|
|
443
|
+
# It travels in the plan rather than in the skill, so a harness that reads
|
|
444
|
+
# `settle --json` and follows the `prompt` field gets it without knowing what
|
|
445
|
+
# `--regen` means.
|
|
446
|
+
_REGEN_NOTE = """
|
|
447
|
+
|
|
448
|
+
## This is a regeneration, not a rewrite
|
|
449
|
+
|
|
450
|
+
Nothing in the source moved. This document is being written again because the
|
|
451
|
+
conventions above have changed since it was last written.
|
|
452
|
+
|
|
453
|
+
Carry every rule, every failure mode and every reason the previous body
|
|
454
|
+
records into the new one. Change the wording, the order and the structure as
|
|
455
|
+
the conventions now require. Merge two entries that say the same thing. Drop
|
|
456
|
+
only something the source no longer supports.
|
|
457
|
+
|
|
458
|
+
A shorter document is the failure mode here. If you cannot express a rule in
|
|
459
|
+
the new form, keep the old sentence rather than losing the rule."""
|
|
418
460
|
|
|
419
461
|
|
|
420
462
|
def _plan(
|
|
@@ -423,6 +465,7 @@ def _plan(
|
|
|
423
465
|
changed_paths: list[str] | None = None,
|
|
424
466
|
modules: list[str] | None = None,
|
|
425
467
|
cache: DocumentCache | None = None,
|
|
468
|
+
regen: bool = False,
|
|
426
469
|
) -> Plan:
|
|
427
470
|
"""The body of :func:`plan`, over a configuration already loaded.
|
|
428
471
|
|
|
@@ -431,6 +474,12 @@ def _plan(
|
|
|
431
474
|
the configuration three times and walk the repository three times with it.
|
|
432
475
|
"""
|
|
433
476
|
cache = cache or DocumentCache()
|
|
477
|
+
# *regen* does not appear here. It decides what counts as work, not what
|
|
478
|
+
# is looked at, and the default scope is already every module. A branch
|
|
479
|
+
# widening the scope for it would only fire when a caller passed a
|
|
480
|
+
# narrowing argument *and* asked for a regeneration, and there the
|
|
481
|
+
# narrowing argument is the more specific instruction — the same reason
|
|
482
|
+
# `auto` keeps its budget through one.
|
|
434
483
|
if modules is not None:
|
|
435
484
|
modules_in_scope = {m for m in modules if m in cfg.module_files}
|
|
436
485
|
else:
|
|
@@ -461,7 +510,7 @@ def _plan(
|
|
|
461
510
|
doc_abs = store_path(cfg, repo_root, doc_file)
|
|
462
511
|
previous_body: str | None = None
|
|
463
512
|
previous_description: str | None = None
|
|
464
|
-
reason: Literal["changed", "missing"] = "missing"
|
|
513
|
+
reason: Literal["changed", "missing", "regen"] = "missing"
|
|
465
514
|
|
|
466
515
|
if doc_abs.exists():
|
|
467
516
|
try:
|
|
@@ -472,9 +521,18 @@ def _plan(
|
|
|
472
521
|
previous_body = existing.body
|
|
473
522
|
previous_description = existing.frontmatter.get("description")
|
|
474
523
|
if stored_hash == current_hash:
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
524
|
+
# A log is never regenerated. Its entries are a record of
|
|
525
|
+
# what happened, and the only write it takes is one more
|
|
526
|
+
# entry on the end. Listed under *regen* with nothing to
|
|
527
|
+
# report, a harness writes an entry describing no change.
|
|
528
|
+
# A log whose source did move is still work, and reaches
|
|
529
|
+
# the list by the ordinary route below.
|
|
530
|
+
if not regen or kind.mode == "append":
|
|
531
|
+
fresh.append(mod_key)
|
|
532
|
+
continue
|
|
533
|
+
reason = "regen"
|
|
534
|
+
else:
|
|
535
|
+
reason = "changed"
|
|
478
536
|
except (OSError, DocumentError):
|
|
479
537
|
# Malformed document → treat as missing
|
|
480
538
|
reason = "missing"
|
|
@@ -490,7 +548,7 @@ def _plan(
|
|
|
490
548
|
reason=reason,
|
|
491
549
|
kind=kind.name,
|
|
492
550
|
mode=kind.mode,
|
|
493
|
-
prompt=_prompt_for(kind, repo_root),
|
|
551
|
+
prompt=_prompt_for(kind, repo_root) + (_REGEN_NOTE if regen else ""),
|
|
494
552
|
)
|
|
495
553
|
)
|
|
496
554
|
|
|
@@ -886,12 +944,20 @@ def apply(
|
|
|
886
944
|
|
|
887
945
|
def append(
|
|
888
946
|
module_key: str,
|
|
947
|
+
*,
|
|
889
948
|
entry: str,
|
|
890
949
|
title: str,
|
|
891
950
|
config_path: str | Path | None = None,
|
|
892
951
|
) -> None:
|
|
893
952
|
"""Add one entry to the document for *module_key*.
|
|
894
953
|
|
|
954
|
+
*entry* and *title* are keyword-only, because they are two strings in the
|
|
955
|
+
opposite order to `constant-docs append <module> <title> <entry>` and
|
|
956
|
+
swapping them is silent: the whole entry becomes the heading and the
|
|
957
|
+
title becomes the paragraph under it. That happened to this repository's
|
|
958
|
+
own changelog. A caller that gets it wrong now fails on the call rather
|
|
959
|
+
than in the document.
|
|
960
|
+
|
|
895
961
|
The entry goes under the kind's first section as an H3 whose text is
|
|
896
962
|
Everything already in the document is left exactly as it is: an
|
|
897
963
|
append that can modify history is a replace with extra steps.
|
|
@@ -1228,7 +1294,7 @@ def _unmarked_scope(
|
|
|
1228
1294
|
return _Scope(modules=sorted(keys), evidence=evidence)
|
|
1229
1295
|
|
|
1230
1296
|
|
|
1231
|
-
def settle(config_path: str | Path | None = None) -> Plan:
|
|
1297
|
+
def settle(config_path: str | Path | None = None, regen: bool = False) -> Plan:
|
|
1232
1298
|
"""Plan over what has changed, without clearing the dirty set.
|
|
1233
1299
|
|
|
1234
1300
|
Called at quiescence — the end of a turn — so the generator and the coding
|
|
@@ -1252,10 +1318,17 @@ def settle(config_path: str | Path | None = None) -> Plan:
|
|
|
1252
1318
|
|
|
1253
1319
|
1. A scope declared for this run in the environment, which is how `auto`
|
|
1254
1320
|
holds its budget across whatever loop its command runs
|
|
1255
|
-
2.
|
|
1321
|
+
2. Every module, when *regen* asks for one. It sits below the declared
|
|
1322
|
+
scope so `auto` keeps its cap, and above the rest because the question
|
|
1323
|
+
it asks is not "what moved" at all
|
|
1324
|
+
3. The dirty set, when a hook marked something. What a session touched is
|
|
1256
1325
|
the work that session owes, and planning over more of the repository
|
|
1257
1326
|
would make `auto`'s cap advisory
|
|
1258
|
-
|
|
1327
|
+
4. Otherwise, what git says moved
|
|
1328
|
+
|
|
1329
|
+
A *regen* run records no baseline. A baseline claims every module was
|
|
1330
|
+
fresh at this commit, and a run that listed every module as work has
|
|
1331
|
+
established nothing of the kind.
|
|
1259
1332
|
"""
|
|
1260
1333
|
resolved = _resolve_config(config_path)
|
|
1261
1334
|
repo_root = _repo_root(resolved)
|
|
@@ -1278,6 +1351,8 @@ def settle(config_path: str | Path | None = None) -> Plan:
|
|
|
1278
1351
|
declared = declared_scope()
|
|
1279
1352
|
if declared:
|
|
1280
1353
|
scope = _Scope(modules=declared)
|
|
1354
|
+
elif regen:
|
|
1355
|
+
scope = _Scope(modules=None)
|
|
1281
1356
|
elif dirty.modules:
|
|
1282
1357
|
scope = _Scope(modules=list(dirty.modules))
|
|
1283
1358
|
else:
|
|
@@ -1299,7 +1374,7 @@ def settle(config_path: str | Path | None = None) -> Plan:
|
|
|
1299
1374
|
if scope.modules is None
|
|
1300
1375
|
else [m for m in scope.modules if m in cfg.module_files]
|
|
1301
1376
|
)
|
|
1302
|
-
p = _plan(cfg, repo_root, changed_paths=None, modules=known)
|
|
1377
|
+
p = _plan(cfg, repo_root, changed_paths=None, modules=known, regen=regen)
|
|
1303
1378
|
|
|
1304
1379
|
remaining = dirty.modules
|
|
1305
1380
|
if p.fresh:
|
|
@@ -10,7 +10,7 @@ Usage::
|
|
|
10
10
|
constant-docs prune [<config>]
|
|
11
11
|
constant-docs index [<config>]
|
|
12
12
|
constant-docs mark [<path>...]
|
|
13
|
-
constant-docs settle [--json] [--hook]
|
|
13
|
+
constant-docs settle [--json] [--hook] [--regen]
|
|
14
14
|
constant-docs completeness [--json] [<config>]
|
|
15
15
|
constant-docs prompt [<kind>]
|
|
16
16
|
"""
|
|
@@ -303,10 +303,10 @@ def cmd_verify(
|
|
|
303
303
|
sys.exit(report.exit_code)
|
|
304
304
|
|
|
305
305
|
|
|
306
|
-
def cmd_plan(config_path: str | None, json_output: bool) -> None:
|
|
306
|
+
def cmd_plan(config_path: str | None, json_output: bool, regen: bool = False) -> None:
|
|
307
307
|
"""Inspect and report the current state."""
|
|
308
308
|
try:
|
|
309
|
-
p = plan(_resolve_config(config_path))
|
|
309
|
+
p = plan(_resolve_config(config_path), regen=regen)
|
|
310
310
|
except ConfigError as e:
|
|
311
311
|
_exit_config_error(str(e))
|
|
312
312
|
|
|
@@ -359,7 +359,12 @@ def cmd_append(
|
|
|
359
359
|
) -> None:
|
|
360
360
|
"""Add one entry to an append-mode document."""
|
|
361
361
|
try:
|
|
362
|
-
append(
|
|
362
|
+
append(
|
|
363
|
+
module_key,
|
|
364
|
+
entry=entry,
|
|
365
|
+
title=title,
|
|
366
|
+
config_path=_resolve_config(config_path),
|
|
367
|
+
)
|
|
363
368
|
print(f"Appended to {module_key}.")
|
|
364
369
|
except ConfigError as e:
|
|
365
370
|
_exit_config_error(str(e))
|
|
@@ -907,11 +912,13 @@ def cmd_mark(paths: list[str]) -> int:
|
|
|
907
912
|
return 0
|
|
908
913
|
|
|
909
914
|
|
|
910
|
-
def cmd_settle(
|
|
915
|
+
def cmd_settle(
|
|
916
|
+
config_path: str | None, json_output: bool, hook: bool, regen: bool = False
|
|
917
|
+
) -> int:
|
|
911
918
|
"""Report the work the dirty set implies, without clearing it."""
|
|
912
919
|
try:
|
|
913
920
|
resolved = _resolve_config(config_path)
|
|
914
|
-
p = settle(resolved)
|
|
921
|
+
p = settle(resolved, regen=regen)
|
|
915
922
|
except ConfigError as e:
|
|
916
923
|
# See cmd_mark: silence outside a configured repository. A Stop hook
|
|
917
924
|
# that fails everywhere else is a Stop hook nobody keeps.
|
|
@@ -1142,7 +1149,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1142
1149
|
wants_completeness,
|
|
1143
1150
|
)
|
|
1144
1151
|
elif subcommand == "plan":
|
|
1145
|
-
|
|
1152
|
+
regen = "--regen" in args
|
|
1153
|
+
if regen:
|
|
1154
|
+
args.remove("--regen")
|
|
1155
|
+
cmd_plan(args[0] if args else None, json_output, regen)
|
|
1146
1156
|
elif subcommand == "apply":
|
|
1147
1157
|
try:
|
|
1148
1158
|
retire = _take_repeated(args, "--retire")
|
|
@@ -1214,7 +1224,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1214
1224
|
hook = "--hook" in args
|
|
1215
1225
|
if hook:
|
|
1216
1226
|
args.remove("--hook")
|
|
1217
|
-
|
|
1227
|
+
regen = "--regen" in args
|
|
1228
|
+
if regen:
|
|
1229
|
+
args.remove("--regen")
|
|
1230
|
+
return cmd_settle(args[0] if args else None, json_output, hook, regen)
|
|
1218
1231
|
elif subcommand == "append":
|
|
1219
1232
|
if len(args) < 3:
|
|
1220
1233
|
print(
|
|
@@ -1259,7 +1272,8 @@ def _print_help() -> None:
|
|
|
1259
1272
|
"\n"
|
|
1260
1273
|
"Commands:\n"
|
|
1261
1274
|
" verify [--json] [<config>] Check for stale/missing/orphan documents\n"
|
|
1262
|
-
" plan [--json] [<config>]
|
|
1275
|
+
" plan [--json] [--regen] [<config>]\n"
|
|
1276
|
+
" List module states\n"
|
|
1263
1277
|
" apply <module> <body> [<config>] [--retire <id>]...\n"
|
|
1264
1278
|
" Write a document body\n"
|
|
1265
1279
|
" append <module> <title> <entry> [<config>] Add one log entry\n"
|
|
@@ -1273,7 +1287,8 @@ def _print_help() -> None:
|
|
|
1273
1287
|
" prune [<config>] Delete orphaned documents\n"
|
|
1274
1288
|
" index [<config>] Rebuild the root index from descriptions\n"
|
|
1275
1289
|
" mark [<path>...] Add a path's modules to the dirty set\n"
|
|
1276
|
-
" settle [--json] [--hook]
|
|
1290
|
+
" settle [--json] [--hook] [--regen]\n"
|
|
1291
|
+
" Report what the dirty set implies\n"
|
|
1277
1292
|
" prompt [<name>] Print a kind's conventions, or a guide\n"
|
|
1278
1293
|
"\n"
|
|
1279
1294
|
"Options:\n"
|
|
@@ -1333,7 +1348,13 @@ def _print_subcommand_help(cmd: str) -> None:
|
|
|
1333
1348
|
" --completeness Also fail on a kind the contents warrant and\n"
|
|
1334
1349
|
" nothing provides\n"
|
|
1335
1350
|
),
|
|
1336
|
-
"plan":
|
|
1351
|
+
"plan": (
|
|
1352
|
+
"Usage: constant-docs plan [--json] [--regen] [<config>]\n\n"
|
|
1353
|
+
"List the state of every configured module.\n\n"
|
|
1354
|
+
"Options:\n"
|
|
1355
|
+
" --json Output machine-readable JSON\n"
|
|
1356
|
+
" --regen List every module as work, whether or not its source moved.\n For after an upgrade that changed the conventions: the\n documents are not behind their code, they are behind the\n prompts they were written against, and no hash sees that.\n Each plan still carries the previous body, and the prompt\n it carries says to keep what that body records\n"
|
|
1357
|
+
),
|
|
1337
1358
|
"apply": (
|
|
1338
1359
|
"Usage: constant-docs apply <module> <body> [<config>] "
|
|
1339
1360
|
"[--retire <id>]...\n\n"
|
|
@@ -1378,7 +1399,7 @@ def _print_subcommand_help(cmd: str) -> None:
|
|
|
1378
1399
|
"document to a renamed module.\n"
|
|
1379
1400
|
),
|
|
1380
1401
|
"mark": "Usage: constant-docs mark [<path>...]\n\nAdd every module the given paths belong to, to `.constant-docs/dirty.json`.\nPass paths as arguments from any harness. With no arguments, reads a\nPostToolUse hook payload from stdin, the shape Claude Code sends.\nSilent and exit 0 when a path matches nothing, or outside a configured\nrepository.\n",
|
|
1381
|
-
"settle": "Usage: constant-docs settle [--json] [--hook]\n\nReport the work the dirty set implies. The set is NOT cleared by being\nread; it clears on the apply or append that satisfies each module.\n\nOptions:\n --json Machine-readable plan, the same structure `plan --json` emits\n --hook Stop-hook form: exit 2 with instructions on stderr when work is\n outstanding, exit 0 when it is not\n",
|
|
1402
|
+
"settle": "Usage: constant-docs settle [--json] [--hook] [--regen]\n\nReport the work the dirty set implies. The set is NOT cleared by being\nread; it clears on the apply or append that satisfies each module.\n\nOptions:\n --json Machine-readable plan, the same structure `plan --json` emits\n --hook Stop-hook form: exit 2 with instructions on stderr when work is\n outstanding, exit 0 when it is not\n --regen List every module as work, whether or not its source moved.\n For after an upgrade that changed the conventions: the\n documents are not behind their code, they are behind the\n prompts they were written against, and no hash sees that.\n Each plan still carries the previous body, and the prompt\n it carries says to keep what that body records\n",
|
|
1382
1403
|
"index": "Usage: constant-docs index [<config>]\n\nRebuild <docs_root>/index.md from every document's description.\nLeaves the file byte-identical when nothing changed.\n",
|
|
1383
1404
|
"append": "Usage: constant-docs append <module> <title> <entry> [<config>]\n\nAdd one entry to an append-mode document. The title becomes the entry's\nH2 heading. Nothing already written is rewritten or reordered.\n",
|
|
1384
1405
|
"prompt": (
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|