Skip to content

phpbotscout index sources

Lists every repository discovery found, and the exact clause of the corpus predicate that admitted or rejected it.

Usage

$ phpbotscout index sources
SOURCE                                VERDICT   CLAUSE      REASON
phpboyscout/go/config                 indexed   none        public, in-group, documented
phpboyscout/go/config-toml            indexed   none        public, in-group, listed; no docs site, indexed for readme, code and changelog
phpboyscout/go/cloudevents            rejected  feed        absent from projects.json
phpboyscout/blog                      rejected  visibility  visibility is "private", not public
phpboyscout/iac/landing               rejected  visibility  visibility is "private", not public
phpboyscout/infra                     rejected  excluded    permanently excluded by "phpboyscout/infra"

<n> of <m> sources qualify; feed generated 2026-10-27T11:45:44Z, fetched 2026-10-27T12:00:03Z.

The last line also says which copy of the estate feed decided the listing: a fresh one, as above, or stored copy from <time>; fetch failed when the fetch failed and the last good copy was used.

Why rejections are shown

Every candidate is listed, rejected ones included, because a source listing has to explain an absence. A repository that simply vanished from the output would send an operator hunting for a bug in discovery when the answer is that the predicate did its job.

A predicate nobody can inspect is a curated list with extra steps.

Clauses

Clause Meaning
none Nothing rejected it.
excluded On the permanent exclusion list — infra, sandbox, scoutdm, scoutdm-site. Checked first and absolute; no later clause can readmit it, and an excluded repository is never read from at all.
namespace Outside the phpboyscout group.
visibility The forge did not report it as public. Allowlisted against public rather than denylisted against private, since != private would admit both unknown and internal. This is the clause doing the real work — every exclusion-list entry is also private, so the deny list is a second lock rather than the control.
archived The forge reports it as retired.
feed The estate feed, projects.json, does not list it: absent from projects.json or listed false in projects.json. The feed is canon for which sources may be indexed.
no-docsite No zensical.toml marker, and either outside phpboyscout/go/, phpboyscout/rust/ and phpboyscout/images/ or an empty repository with no default branch.
no-pages A zensical.toml marker but no publicly reachable documentation site, wherever the repository is.
unresolved A fact the predicate needs could not be determined — the forge returned an error that was not "not found". Not a rejection on the merits: the source may well qualify, and the next run asks again.

unresolved exists because "no" and "I could not find out" demand opposite handling. Reading a repository's zensical.toml can fail with a 404 — which genuinely means no marker — or with a 500, which means nothing at all. Treating the second as the first would drop a documented repository out of the corpus silently, and a different one on the next run. The same applies to the Pages lookup, where a 403 is routine because reading Pages settings needs the Maintainer role.

An unresolved check never replaces the verdict a source already has. A source that qualified keeps qualifying, and keeps its indexed pages, while the failure is recorded against it for index status to show. The next decided check clears the failure.

A source the listing no longer contains is retired: renamed, moved out of the group or deleted, it stops qualifying and its pages stop being cited. It does not appear in this listing, which shows only what discovery found, but its row stays in the index with the clause unlisted. Discovery refuses a partial listing, so a failed enumeration never retires anything, and neither does an empty one.

When the forge rate limits discovery, discovery stops asking it. Every source it had not reached is unresolved, with a reason such as not checked: the forge is rate limiting this caller (it asked for 30s), and the next scheduled refresh tries again. Sources the listing alone decides, such as a private repository, are still decided.

archived and feed are evaluated after the screen (exclusion, namespace, visibility) and before anything else is asked of the forge, so a repository the feed does not list costs no further request. An archived repository is a judgement about usefulness, not about whether a source is permitted: its documentation is unmaintained, which for a bot answering questions is worse than absent.

A listed, public project in phpboyscout/go/, phpboyscout/rust/ or phpboyscout/images/ with no docs site is indexed for its README, code and changelog, never for docs, which would have no page to cite. Family members, such as the config adapters and the chat providers, are admitted the same way.

The estate feed

Discovery reads the feed once per run, from index.feed.url. A feed that fetched and parsed is stored in the database, and used whenever a later fetch fails, however old it is; a run using a copy more than 24 hours old logs a warning. The refresh never fails because of the feed.

With no feed at all, fetched or stored, every source past the screen is unresolved. A source that already had a verdict keeps it, and a source seen for the first time stays out until a feed is available.

The feed can only narrow what the forge reports, plus admitting a public, unarchived, non-excluded no-site project in the three groups. It cannot reach a private or excluded repository, because the screen runs first and never consults it, and a path it lists that the forge did not return has no effect.

Where the facts come from

Three forge capabilities, all optional in the contract and all required here:

Capability Answers
Repositories what is in the namespace, with visibility and archived state
Contents whether the repository carries zensical.toml
Sites where its documentation is published

The estate feed adds whether the estate lists it, and its family and role.

A provider implementing none of them is conformant, so their absence is refused at startup rather than tolerated. Without Contents, every repository would be reported as undocumented — which reads as a fact about the repositories when it is really a fact about the provider.

Subgroups and archived repositories are both requested explicitly, because the forge excludes them by default and both defaults fail silently: the entire Go toolkit lives under phpboyscout/go/*, and a filtered listing looks exactly like a complete one.

Why a configured list is refused

Discovery enumerates from the forge API and will not fall back to a list of sources in your config file.

A configuration file asserting a repository is public would replace the check that keeps an infrastructure repository out of a public channel with the operator's memory. Configuration is a request to index and never an authority on visibility — a checkout cannot say whether a repository is still public, still in the group, or even which project it is, and one made private yesterday looks identical on disk.

Flags

sources defines no flags of its own. Global flags (--config, --debug, --output, --ci, --accessible) are inherited from the root command.

Name Description Default
-h, --help Help for sources. false

See also