Skip to content

docs(cmd): pilot the README-prose -> Long migration on app listing - #274

Open
ZacxDev wants to merge 8 commits into
mainfrom
zach/help-long-pilot
Open

docs(cmd): pilot the README-prose -> Long migration on app listing#274
ZacxDev wants to merge 8 commits into
mainfrom
zach/help-long-pilot

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Pilot for the "move the README's command-reference prose into the cobra command
definitions" migration, scoped to one group before touching ~50 command files.
Also records what the scoping measured — including two findings that reverse the
plan's own premise.

This PR has been through an adversarial pre-merge audit. The audit falsified
three of my own claims; the corrections are commits abbdf26, 4429c8b, 953f1d9
and the numbers below are the post-fix, re-measured ones. See "What the audit
caught" at the bottom.

What the measurement found (whole tree, 53 nodes)

Fact Measured
Nodes carrying an Annotations map 0 of 53
Nodes with NO Long at all 8
Nodes with a Long under 400 chars 16 more
README table cells, 16 matched rows 6,255 chars
Long on those same 16 nodes 19,017 chars

Long is already ~3x richer than the README table for the App-authoring
commands, so that half of the migration was largely already done. The real gap is
the public read-API group (models/images/collections/…), which owns 19 of the
24 empty-or-thin nodes and has no table row at all.

🔴 Annotations cannot reach the docs site — measured, not inferred

The docs generator captures exactly two channels per node (--help and
__complete) and parses the text. Cobra renders Annotations in neither. Probe on
workflows get: the value appears 1x in the binary (strings), 0x in
--help, 0x in __complete; positive control (a word that IS in that help)
1x. Recommendation: drop the Annotations half.

🔴 The blocker — now fixed upstream

gen-appblocks-cli.mjs:781 was description: short || parseLongDescription(help).split('\n')[0] || ''
— it parsed the whole Long and published only the Short. Measured: this PR's
new Long moved cli.json by +926 chars, all of it examples.
Fixed in civitai-developer-docs#49, which publishes longDescription on 52/52
commands (+76.9% artifact). Merge order does not matter — this PR stands alone for
terminal users, and gains a consumer once #49 lands.

What this PR changes — civitai app listing only

  • set-icon / set-cover / add-screenshot gained a Long and an Example;
    they had neither, so --help printed a single Short line and the generator
    published a single line too.
  • status / rm-screenshot / reorder gained an Example; reorder also states
    the positional-ordering rule.
  • The accepted formats and per-kind byte caps appeared nowhere in the help
    surface — only in the README. They are now stated, and computed from
    maxIconBytes/maxCoverBytes/maxScreenshotBytes through the same humanBytes
    the refusal message uses, so --help predicts the error text.
  • Three pre-existing body lines running past 80 columns are rewrapped.

--help cost, measured against current origin/main over all 54 captured nodes

47 nodes byte-identical, 7 changed. Tree 100,781 → 104,401 bytes (+3.6%),
1,990 → 2,068 lines (+3.9%).

Attribution was checked rather than assumed: an earlier capture showed 9 nodes
moving, and app metrics + root turned out to have moved under #268 (merged in),
not under this PR.

Guards

Four, none subsuming the others: caps-vs-constants in context (the group's
three caps are asserted next to the kind each applies to); kind-naming (each body
names its own asset and not a differently-capped sibling); formats-vs-decoder (a
real PNG/JPEG/WebP header through appapi.DecodeImageInfo, with a GIF negative
control); completeness (Long+Short+Example on every node, walking the real tree
with a count floor); and a budget (1400 chars / 80 columns, counting runes).

Mutation matrix — 13 killed, 1 declared-equivalent, null mutant survived.
Checksum-gated so a no-op edit reports NOT-APPLIED rather than surviving.

The one that does not die is the kindScreenshotkindIcon swap, and it is an
equivalent mutant, proven: both render "2.0 MB", so the mutation produces a
different binary and byte-identical help (cmp, with the differing binaries as the
negative control). It becomes observable the moment the caps diverge — measured,
setting maxScreenshotBytes to 3 MiB under that mutation reddens with "does not
quote the cap it enforces (3.0 MB)". It is invisible exactly while it is harmless.

make ci green on the merged tree: 18/18 packages, 0 FAIL.

What the audit caught (all fixed here)

  1. A false mechanism claim in shipped help. set-cover said its larger cap was
    "because a cover is stored at full resolution rather than inlined". runSetMedia
    sends cover and screenshot down the identical IngestAssetFullRes path while
    the screenshot keeps the icon's 2 MiB cap. Deleted.
  2. A third copy of the exit-code contract. The group body hand-typed "(exit 2)",
    which exitcodes_doc.go exists to prevent, and dropped the exception both
    governed surfaces carry (an unreadable file exits 1 — AGENTS item 24). Verified
    on the binary: oversize/empty/non-image/directory/missing → 2, mode-000 PNG → 1.
    The sentence now names the checks and states no code.
  3. Two guard holes, both measured surviving a green suite. humanBytes(icon) and
    humanBytes(screenshot) are the same string, so the group enforced only 2 of 3
    caps; and listingImageFormats was asserted against a Long built by
    interpolating that same constant — a constant compared with itself, which can
    never fail (narrowing it to "png or jpeg" and widening it to claim avif both
    survived). Both closed, both mutants now killed.
  4. "Four pre-existing" was three. Corrected in the PR body, the handoff, and the
    guard's own comment, whose provenance claim was also wrong.

One defect found while fixing, not by the audit: the drift check's first GIF control
used gif.Encode, and importing image/gif registers the decoder package-wide,
which changed how generate --image refuses a GIF and reddened three unrelated
tests. The repo's own lockstep guard caught it. The control is hand-built bytes now.

🤖 Generated with Claude Code

ZacxDev and others added 8 commits August 7, 2026 16:36
… the caps

Pilot for the "move the README's command-reference prose into the command
definitions" migration, scoped to ONE group before touching ~50 files.

Measured on the tree at aeceb6b, across all 53 command nodes:

  - `Annotations` is set on ZERO nodes today.
  - 8 nodes have NO `Long` at all, 16 more are under 400 chars.
  - Three of the eight are in this group (set-icon, set-cover,
    add-screenshot): `--help` printed a single Short line, and the docs
    generator publishes exactly what `--help` prints.

What changed, all in `app listing`:

  - set-icon / set-cover / add-screenshot get a Long and an Example (they had
    neither); status / rm-screenshot / reorder get an Example and, for reorder,
    the positional-ordering rule.
  - The accepted formats and the per-kind byte caps — which appeared NOWHERE in
    the help surface, only in the README — are now stated, and are COMPUTED from
    maxIconBytes/maxCoverBytes/maxScreenshotBytes through the same humanBytes the
    refusal message uses. `--help` now predicts the error text rather than
    quoting a separately-maintained figure.
  - Four pre-existing body lines that ran past 80 columns are rewrapped.

`--help` cost, measured base vs HEAD over all 54 captured nodes: 7 nodes change
and 47 are byte-identical; the tree grows 100,451 -> 104,190 bytes (+3.7%) and
1,987 -> 2,065 lines (+3.9%).

Three guards, none subsuming the others: the caps guard pins help against the
enforced constants (and cross-kind, since icon and screenshot share a cap); the
completeness guard requires a Long, Short and Example on every node in the
group, walking the real tree with a count floor; the budget guard caps a body at
1400 chars and 80 columns, counting RUNES — a byte count reported four
79-column em-dash lines as 81 and failed prose that was fine.

Mutation-measured, checksum-gated so a no-op edit reports NOT-APPLIED rather
than surviving: 9/9 mutants killed by the intended guard (stale literal + moved
constant, dropped cap sentence, cross-kind copy-paste, group losing a cap,
deleted Example, deleted Long, unregistered subcommand, over-wide line, blown
budget); a comment-only null mutant survived.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… assumption

Records what the pilot measured, including two findings that reverse the
handoff's own next-step 1:

  - `Long` is already 3x RICHER than the README table for the App-authoring
    commands (19,017 vs 6,255 chars over the 16 matched rows), so "migrate the
    README's prose into Long" was largely already done there. The real gap is
    the public read-API group, which has no table row at all.
  - `Annotations` cannot reach the docs site. The generator captures only
    `--help` and `__complete`, and cobra renders Annotations in neither —
    probed with a positive control. Recommendation: drop that half.
  - The blocker: `gen-appblocks-cli.mjs:781` parses the whole `Long` and then
    publishes only `short`. Measured end-to-end, the pilot's ~4,000 new chars of
    `Long` moved `cli.json` by +926 chars, all of it `examples`.

Also marks #253 fixed (#264), re-verified with a positive control, and replaces
the verify block's bare zero with the control that makes it mean something.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Added `longDescription: parseLongDescription(help)` to a scratch copy of
gen-appblocks-cli.mjs and ran it against the pilot binary: 52/52 commands gain a
longDescription and cli.json goes 42,784 -> 88,747 chars (+107%), publishing
43,460 chars of prose the CLI already carries. The one-line generator change is
worth more than the migration it unblocks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…audit found

An adversarial pre-merge audit of #274 falsified three things I had asserted.

FALSE PROSE, now removed:

  - `set-cover` said its larger cap was "because a cover is stored at full
    resolution rather than inlined". `runSetMedia` sends cover AND screenshot
    down the identical IngestAssetFullRes path while the screenshot keeps the
    icon's 2 MiB cap, so the storage path does not explain the difference. An
    author reasoning from the stated rule would prepare a 3 MiB screenshot and
    be refused. The clause is deleted rather than reworded — the caps are the
    server's and do not follow from the path.
  - The group body hand-typed "(exit 2)", making a THIRD publishing surface for
    a contract `exitcodes_doc.go` exists to keep in one place, and dropping the
    exception both governed surfaces carry (an UNREADABLE file exits 1, not 2 —
    AGENTS.md item 24). Measured on the HEAD binary: oversize/empty/non-image/
    directory/missing all exit 2, a mode-000 valid PNG exits 1. The sentence now
    names the checks and states no code.
  - `add-screenshot` promoted a hand-typed server constant ("up to 8") into two
    emphatic statements. It is now attributed to the server and dated.

GUARD HOLES, now closed. Both were measured surviving a green suite:

  - `humanBytes(maxIconBytes)` and `humanBytes(maxScreenshotBytes)` are the SAME
    STRING ("2.0 MB"), so the group's three bare Contains checks enforced only
    two of three caps, and a per-kind body asserted to contain "2.0 MB" was
    satisfied by the other 2 MiB kind's sentence. Deleting the icon cap from the
    group, deleting the screenshot cap from it, and swapping add-screenshot's
    rule to kindIcon ALL survived. The group assertion is now cap-in-context and
    TestListingHelpNamesTheKindItDescribes requires each body to name its own
    asset and not a differently-capped sibling.
  - `listingImageFormats` was asserted against a Long built by interpolating the
    same constant — a constant compared with itself, which can never fail.
    Narrowing it to "png or jpeg" (understates) and widening it to claim avif
    (lies) both survived. TestListingImageFormatsMatchesTheDecoder now decodes a
    real PNG/JPEG/WebP header through appapi.DecodeImageInfo and carries a GIF
    negative control, so the mirror has a drift check.

Also corrects the runes-vs-bytes comment's provenance: it said the byte-based
first draft reddened "four PRE-EXISTING bodies", but three of those four bodies
had no Long at all before this work. The genuinely pre-existing over-80-COLUMN
lines number 3, not 4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…he proof

The audit reported it as a surviving hole. It is not: `humanBytes(maxIconBytes)`
and `humanBytes(maxScreenshotBytes)` are both "2.0 MB", so the swap produces a
DIFFERENT BINARY and BYTE-IDENTICAL rendered help — measured with `cmp` on the
two `--help` outputs, with the differing binaries as the negative control. There
is nothing for an assertion to observe, and asserting against a token that cannot
vary would be a guard that cannot fail.

What DOES protect it was measured separately: under that mutation, moving
maxScreenshotBytes to 3 MiB reddens TestListingHelpQuotesTheEnforcedCaps with
"does not quote the cap it enforces (3.0 MB)". The swap is invisible exactly
while it is harmless.

(The first attempt to test this said DIFFERS — because the baseline binary got
built from the base clone rather than the worktree. The negative control that
the two binaries are distinct builds is what makes the corrected answer evidence.)

Also fixes the handoff: the next-steps list read 5,6,5 after an insertion, the
retracted #253 heading still asserted "still ships", and step 1 now records that
the generator fix shipped as docs PR #49 with its measured numbers and its one
unguarded residual.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ted the package

The drift check's negative control used `gif.Encode`, which means importing
`image/gif`. Its init() REGISTERS the GIF decoder process-wide, so a GIF stopped
failing `civitai generate --image` via `image.ErrFormat` and started failing via
the allowlist branch instead. That reddened three unrelated tests, one of which
(TestSupportedImageFormats_DecoderRegistrationIsInLockstep) exists precisely to
catch it: "the unreachability argument for the allowlist branch depends on this
being the ErrFormat arm".

A test fixture that mutates global decoder state changes the thing it measures.
The control is now 13 hand-written GIF87a bytes (magic + logical screen
descriptor) with the hazard documented at the fixture, and the three tests are
green again.

Verified after: `make ci` 18/18 packages ok, 0 FAIL; the GIF control still
REFUSES (it is not passing vacuously); 29 subtests pass across the listing
guards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant