Repository navigation
Make the content commands do what the guide said they did - #118
Merged
Merged
Conversation
The content signals guide documented a command that does not exist. It showed "content quality -" reading a JSONL stream, and there was no stream form. It listed six output fields, lang, lang_confidence, text_length, link_density, boilerplate_ratio and readability_score, and the command emits none of them. It documented a --warc flag on all three subcommands and a --lang flag on quality, neither of which was ever registered. Every pipeline on the page failed. Two of those were worth fixing rather than deleting. Scoring one URL at a time is a spot check, and filtering a corpus is the reason the signals exist, so all four commands now take "-" and read a list from stdin. A line is either a bare URL or a JSON object with a url field, which is what search, columnar and crawl fetch already write with -o jsonl, so a query feeds a content command with no glue in between. A URL that cannot be fetched is named on stderr and the rest of the list carries on, because any real list of seeds has a few dead hosts in it. A stream where every URL failed is an error rather than an empty result, the same distinction that keeps a search that could not read the index off exit 3, and empty stdin is exit 3, which is what dropping Single from these ops buys. content outlinks now carries the source URL on every row. One page at a time it was redundant; over a stream the rows of a hundred pages land in one file and an edge with no tail is not an edge. "-" is refused under serve, api and mcp. The same handler serves all three surfaces and stdin there is the JSON-RPC session or the operator's terminal, never the client asking for the URL. The rest is the docs matching the binary. Every field table is now the fields the command emits, with what each one actually measures: spam_score is sixteen English sales phrases at a tenth each, is_parked is a regex on a page under 150 words, has_main_content is fifty words. Every pipeline on the page was run before it was written down. The requirements page said crawl fetch reads robots.txt first, which is only true with --robots, and now says which commands check it and which do not, the content commands being the ones that do not. Tests stand two pages up on localhost and drive the commands against them, so the stream form, the skip, the all-failed error, the empty result and the resolved relative link are all covered without touching the network. One test runs a real search against the fake Common Crawl and feeds every row to the stdin parser, because the documented pipeline rests on a contract between two commands rather than on either of them. cli coverage 50.7 to 52.3. Closes #110
This was referenced Aug 12, 2026
tamnd
added a commit
that referenced
this pull request
Aug 12, 2026
docs-drift reads the requirements page with a hardcoded list of the commands it covers, because the page is organised by dependency and its headings name no command to scope against. #118 added a paragraph about robots.txt and neither crawl fetch nor crawl run was on the list, so --robots and --no-robots had nothing to match and the check went red on main. Closes #122
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #110.
The content signals guide documented a command that does not exist.
content quality -was shown reading a JSONL stream and there was no stream form. The output table listedlang,lang_confidence,text_length,link_density,boilerplate_ratioandreadability_score, andQualityReportemits none of those. A--warcflag was documented on three subcommands and a--langflag onquality, and neither was ever registered. Every pipeline on the page failed.The done-when list asked whether the streaming form is worth having. Two of these are: scoring one URL at a time is a spot check, and filtering a corpus is the reason the signals exist at all. So the form exists now rather than being deleted from the page.
What changed in the binary
All four content commands take
-in place of the URL and read a list from stdin. A line is either a bare URL or a JSON object with aurlfield, which is whatsearch,columnarandcrawl fetchalready write with-o jsonl, so a query feeds a content command with no glue in between.A URL that cannot be fetched is named on stderr and the rest of the list carries on, because any real list of seeds has a few dead hosts in it and losing the other nine hundred to them helps nobody:
The exit codes follow the rule the last release settled on. A run that scored at least one page exits 0. A run where every URL failed exits 1, because nothing was learned about those pages, they were never seen. Empty stdin exits 3, which is what dropping
Singlefrom these ops buys. A single URL that fails still exits 1.content outlinksnow carries the source URL on every row. One page at a time it was redundant; over a stream the rows of a hundred pages land in one file and an edge with no tail is not an edge.-is refused underserve,apiandmcp. The same handler serves all three surfaces, and stdin there is the JSON-RPC session or the operator's terminal, never the client asking for the URL.What changed in the docs
The guide is rewritten against the binary. Every field table is the fields the command emits, with what each one measures rather than what its name suggests:
spam_scoreis sixteen English sales phrases at a tenth each and capped at 1,is_parkedis a regex on a page under 150 words,has_main_contentis fifty words. It says outright that these are blunt, thatspam_scorereads English only, and that a page scoring 0 is not thereby clean.Every pipeline on the page was run before it was written down. The stored-WARC section now points at
ccrawl parse, since the content commands fetch and do not read archives, and says plainly that scoring a stored capture means refetching the page as it is today, which is a different thing.The CLI reference had
content outlinksdown as emitting anchor text, which it has never done. Fixed, along with thequalityfield list, and the-form is documented there too.While checking what these commands need, the requirements page turned out to claim
crawl fetchreadsrobots.txtfirst. It only does with--robots;crawl runis the one that checks by default. That page now says which commands check it, which do not, and that a long list on stdin is whatcrawl runis for.Tests
cli/content_cmd_test.gostands two pages up on localhost and drives the commands against them, so the single URL path, the stream, the skipped dead URL, the all-failed error, the empty result, the JSON line with a url field and the resolved relative link are covered without touching the network. One test runs a realsearchagainst the fake Common Crawl and feeds every row through the stdin parser, because the documented pipeline rests on a contract between two commands rather than on either of them.clicoverage 50.7 to 52.3 percent, floor raised to 52.scripts/docs-drift.shpasses.Done when