Answer your first question from the docs¶
By the end of this you'll have phpbotscout installed, a local index of every documented phpboyscout repository, and a real answer with a citation you can click. You'll also have seen it refuse to answer something, which is the behaviour worth understanding early.
Allow about twenty minutes. Most of that is two downloads: the Go dependency tree the first time you build, and the corpus itself, which is a shallow clone of fifty-odd repositories.
What you need first¶
Go 1.27.2 or newer, to build from source. A release works just as well if you would rather not build.
A GitLab personal access token with the Maintainer role on the phpboyscout group. This is the
step that bites, so it's worth being blunt about it: without one, you will get a corpus of nothing.
Discovery reads each repository's Pages settings to find its documentation site, and GitLab gates
that endpoint behind Maintainer. With a weaker token, or none, every candidate comes back
unresolved and zero sources qualify. The clone itself needs no token; discovery does.
A way to reach a model, for the last step only. Everything up to and including retrieval works
with no model at all. Composing an answer needs either an ANTHROPIC_API_KEY, or a locally
installed and authenticated claude binary.
Install phpbotscout¶
You need Linux, Go 1.27.2 or newer, and a C toolchain, because the SQLite driver is the native one,
under cgo. From a checkout, just build is the shortest path. Otherwise:
$ go install -tags sqlite_fts5,discord_novoice gitlab.com/phpboyscout/phpbotscout/cmd/phpbotscout@latest
Both tags are required, and leaving either off stops the build with an error naming it. Getting started says what each one does.
Check it landed:
You'll see fifteen commands. Four of them (serve, ask, index and review) are phpbotscout's;
the rest come from the framework it's built on.
Create a configuration file¶
phpbotscout refuses to run without one:
ERRO failed to load configuration: no config file found hints="Run 'phpbotscout init' to create a configuration."
For a throwaway run, a project-local file is quickest. Make a working directory (the index lands in it) and drop a config beside it:
$ mkdir ~/phpbotscout-demo && cd ~/phpbotscout-demo
$ cat > .phpbotscout.yaml <<'YAML'
gitlab:
auth:
env: GITLAB_TOKEN
ai:
provider: claude
YAML
auth.env is the name of an environment variable, not the token. That's the whole point of it:
the secret never enters a file that might get committed.
Now export the token itself:
Where the config actually came from
phpbotscout config path prints the files being read. Worth running once, because --config
replaces the default paths rather than adding to them, and a project-local file is suppressed
when you pass it.
See what qualifies before indexing anything¶
Every candidate is listed, rejected ones included, with the clause that decided it:
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/blog rejected visibility visibility is "private", not public
phpboyscout/infra rejected excluded permanently excluded by "phpboyscout/infra"
<n> of <m> sources qualify; feed generated <time>, fetched <time>.
If that last line says 0 of … qualify, look at the clause column. A wall of unresolved with
403 Forbidden in the reason means the token problem from the prerequisites. It is not a bug, and
not something a retry will fix. A wall of unresolved reading no estate feed available this run
means the estate feed could not be fetched and no earlier copy is stored, which on a first run
usually means no network to phpboyscout.uk.
Build the index¶
$ phpbotscout index build
53 sources: 53 indexed, 0 unchanged, 0 failed
1786 documents, 11928 chunks
That took a little over a minute here. Each qualifying repository is shallow-cloned into memory, never to disk, and only the files the corpus indexes are read at all.
The database lands at phpbotscout.db in the working directory. That default is relative, so
running these commands from a different directory silently works on a different index. Worth
knowing before you wonder where your corpus went.
Check what you got¶
$ phpbotscout index status
SOURCE DOCS CHUNKS COMMIT AGE
phpboyscout/afmpeg 59 382 8f2d6078 1m
phpboyscout/cicd 57 410 1759234a 1m
phpboyscout/go-tool-base 435 3085 c688fc4b 1m
AGE is how long since that source last indexed successfully. status reads the local database
only, so it keeps working when the forge does not, which is exactly when you want it.
Look at retrieval on its own¶
Before spending anything on a model, see what the question actually finds:
$ phpbotscout ask --retrieval-only --limit 5 "what byte limit is used for oversized chunks"
21.38 package corpus › Chunk
https://gitlab.com/phpboyscout/phpbotscout/-/blob/3eb6711…/pkg/corpus/chunk.go#L35
18.59 package corpus › DefaultMaxChunkBytes
https://gitlab.com/phpboyscout/phpbotscout/-/blob/3eb6711…/pkg/corpus/chunk.go#L15
No model is called, so this costs nothing. Higher scores are better: the raw BM25 numbers are negative, and get flipped once at the boundary so nobody downstream has to remember that.
This is the tool to reach for when an answer disappoints you. If the right passage isn't in this list, the problem is retrieval; if it's here and the answer is still wrong, the problem is composition.
Ask a real question¶
$ export ANTHROPIC_API_KEY='sk-ant-…'
$ phpbotscout ask "How do I stop a gtb tool checking for updates?"
To stop a gtb tool from checking for updates, set the `update.policy` configuration key to
`disabled`. This will log only without checking for updates.
Sources:
[1] Configuration Keys › Self-update
https://go-tool-base-ff2fdf.gitlab.io/reference/config/#self-update
Follow the link. It goes to a real page, at the section the answer came from. Every substantive claim has to be traceable to a passage that was actually retrieved, and a citation naming a passage outside that set gets the whole answer thrown away.
No API key? Use the default provider
Drop the ai block from the config above and phpbotscout uses its default, claude-local,
which drives a locally installed and already signed-in claude binary instead of the API. It
needs no key.
Now watch it refuse¶
Ask something the documentation genuinely does not cover:
$ phpbotscout ask "What is the airspeed velocity of an unladen swallow?"
No answer from the indexed documentation.
nothing in the indexed documentation matched this question
This is the behaviour to get comfortable with. A decline is not a failure to try hard enough. It is a documentation gap with a timestamp on it, and the stream of them is meant to become a prioritised list of things to write. An error means something broke; a decline means nobody wrote it down.
Try one more, this time on a subject the corpus definitely covers but with a question it does not answer. You may well get a decline, and when you do it'll show you the closest passages it found, with scores, so you can tell "nothing matched" apart from "the right page was there and didn't say it".
Keep it current¶
build re-indexes everything. Day to day, use:
Run straight after a build and nothing has moved, so nothing is re-indexed. Only sources whose commit has changed get re-read. Every source is still fetched either way, so what you save is chunking and writing rather than network traffic, and a refresh takes about as long as a build.
Reach for build again after anything that changes how content is chunked or ranked.
What to read next¶
- Run the bot: the daemon, its health endpoints, and what it reads versus what it keeps.
- Indexing reference: which files are indexed, where chunks are cut, and the numbers behind them.
- How an answer is produced: what happens between your question and the citation.
- What phpbotscout does not do, before you plan around a feature that isn't there.