Skip to content

docs(cfcli): document the contracts the signatures do not carry - #16

Merged
exadmin merged 1 commit into
exadmin:mainfrom
vlsi:docs/cfcli-doc-comments
Aug 13, 2026
Merged

docs(cfcli): document the contracts the signatures do not carry#16
exadmin merged 1 commit into
exadmin:mainfrom
vlsi:docs/cfcli-doc-comments

Conversation

@vlsi

@vlsi vlsi commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Why

cfcli carried 2 comment lines across 3764 lines of Go — 0.1%. Every fact the type system does not express had to be re-derived from the code on each reading.

The obvious objection is that this codebase is machine-written and machine-read, so it does not need prose: whoever reads it next can re-read the code. The published evidence does not support the strong version of that objection, but it does not support the strong version of the opposite claim either, so it is worth stating what the argument here actually rests on.

What the evidence supports

Comments help comprehension, modestly, and the effect shrinks as models get stronger. Impact of Comments on LLM Comprehension of Legacy Code measured multiple-choice comprehension on an assembler module: 84% with no comments against 96% with all of them on the basic quiz, 84% against 90% on the advanced one. Llama 3 went from 80% to 100% and Mixtral from 60% to 85%, but GPT-4 barely moved. Two caveats belong with those numbers: the models scored 75% with no code at all, so a good part of the quiz was answerable from priors, and the corpus is a single module, which the authors themselves call preliminary.

Wrong comments cost far more than right ones gain, and that cost does not shrink with model strength. CodeCrash (NeurIPS 2025) stress-tested 17 models: misleading comments degraded output prediction by 24.3%, and plausible-but-wrong hints at a function signature cost 12.4% on simple tasks and 33.0% on complex ones — the harder the code, the more the model leans on the prose instead of tracing the logic. Chain-of-thought reduced the damage but left 13.8%. The paper's central finding is the one that matters here: models treat natural language as higher-priority evidence than executable logic. A stale comment does not merely fail to help; it overrides the code.

Two corollaries from the same work are worth knowing before anyone proposes a cheap mitigation: instructing a model to ignore comments gives only partial relief, and a refactoring agent told to remove misleading comments did worse, not better. "The next agent will notice the comment is wrong" is not a strategy the measurements back.

The comprehension study reports the same asymmetry from the other side: minor inaccuracies were tolerated, while wholesale inaccuracy dropped scores from 73% to 61%.

What the evidence does not support

The natural argument for documenting a machine-read codebase is that an agent starts every session cold, so a written contract is a cache that saves re-derivation. That argument is plausible and it is not established. The closest direct test, Evaluating AGENTS.md (SRI Lab, ETH Zürich), found that repository-level context files "do not generally improve task success rates, while increasing inference cost by over 20% on average", across several models and agents, for both generated and human-written files. Its internal split is the useful part: instructions were followed; repository overviews were not helpful.

Declaration-level doc comments are not repository overviews, so the result does not transfer directly. It does rule out the lazy version of the argument, and this PR does not make it.

What this PR rests on

The asymmetry, not the cache. A right comment buys a little and buys less every model generation; a wrong one costs a lot and keeps costing. So the question a comment has to answer is not "is this helpful?" but "can this be kept true?", and the two design rules that follow are the ones applied here: write only facts that change when behavior changes, and prefer encoding a fact in the type system over stating it in prose.

The sweep that produced this PR is itself a demonstration of both halves. A model that had just read app.go end to end wrote, of runWithDependencies:

scan output and warnings go to stdout

That is wrong. refresh(ctx, errorOutput) and loadDictionary(plaintext, errorOutput) write their warnings to stderr; the only warning on stdout is the one for --print=details. Establishing that takes three files: the call site, the argument it passes, and dictionary_cache.go:97, where the stream has to be read as errorOutput rather than output.

So the fact was worth writing down — and the first attempt to write it down got it backwards. An adversarial judge checking each claim against the code caught it before it shipped. That check is not overhead around the change; on the numbers above, it is the part that decides whether the change is worth making at all.

What

Doc comments on the declarations whose contract is not evident from the signature, and inline comments where a line is surprising. The facts that cost the most to re-derive, and are now stated once:

Fact Why it is not in the signature
appDependencies.getenv and .now tolerate nil the field types are func(string) string and func() time.Time
replaceFile requires one filesystem inherited from os.Rename, which has no copy fallback; it is why the caller creates its temp file in filepath.Dir(destination)
runWithDependencies returns four distinct exit codes the return type is int
encryptDictionaryForTest duplicates decryptDictionary's format salt, iteration count, and IV are shared across two files with nothing linking them
dictionary.go's parser accepts a strict subset of Java properties no ! comments, no : separator, no line continuation

Test comments state the rule each test guards, since a test is read in exactly one situation: it just went red.

Deliberately not written: narration of what the next line does, any account of how the code got this way, and comments on unexported helpers whose single call site is the answer. Those are the categories that drift fastest and pay for nothing when they do — which, under the asymmetry above, makes them net negative rather than merely useless.

How to verify

The change is comment-only, and that claim is mechanical rather than asserted — each file parses to an identical AST with comments dropped:

cd cfcli
gofmt -l .        # prints nothing
go vet ./...      # clean
go test ./...     # passes

Nothing in Go validates a doc link: an unresolved [Foo] renders as literal text and neither go vet nor a stock golangci-lint reports it. All 33 doc links introduced here were checked to resolve against declarations in the package.

Follow-up

#17 replaces runWithDependencies' bare int exit codes with named constants, deleting a four-row table from a doc comment in the process. Given the evidence above it is arguably the more valuable of the two changes: a table in a comment is exactly the artifact that becomes expensive when it drifts, and a constant cannot drift from its own value. It is stacked on this branch, because both changes edit the same comment block — merge this one first.

🤖 Generated with Claude Code

cfcli carried 2 comment lines across 3764 lines of Go, so every fact not
expressed by a type had to be re-derived from the code on each reading.
This adds doc comments to the declarations whose contract is not evident
from the signature, and inline comments where a line is surprising.

The facts recovered here are the ones that cost the most to re-derive:
nil-tolerance of appDependencies.getenv and .now, the same-filesystem
constraint replaceFile inherits from os.Rename, the four exit codes
behind runWithDependencies' bare int return, and the format coupling
between encryptDictionaryForTest and decryptDictionary.

Comments only: the packages parse to identical ASTs with comments
dropped, and gofmt, go vet, and go test are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@exadmin
exadmin merged commit e91a148 into exadmin:main Aug 13, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants