Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
Author SHA1 Message Date
atlas
8bf21b6d0c ci: check every repo-relative path resolves, not just doc pointers
mara on #3923: "any gate we add should not have false positives". This is
the gate, and the reason it can exist without a skip-list is the prose fix
in the previous commit -- every false positive this arm would have raised
was a sentence naming where something used to live.

A third arm on the existing doc-pointer lint rather than a fourth CI job:
it inherits the job that already runs on every PR and in pre-push, the
`lint:allow` hatch the sibling arms honour, and the ::error annotation
format. A new job is a thing that can ship without ever running.

Scope is any repo-relative source path, not only `docs/**.md`. A comment
naming a moved .rs or .nix file misleads exactly as much as a dead doc
link, and nothing was checking it: 1123 such paths resolve on this tree
and none were verified before now.

Both ends of the pattern are anchored, each because its absence invented a
path -- an unanchored extension matched the prefix of a longer one, and an
unanchored prefix matched mid-path inside a nested directory, conjuring a
top-level twin of a real file. Those failures now surface as dead paths and
fail the build rather than passing silently, so they need no separate
control.

Written with placeholders, because the first draft named the two phantom
paths literally and the new arm immediately reported the script to itself.
The header already warned about exactly that, two lines above where I put
them.

Refs #3923
2026-09-07 15:06:12 +02:00
atlas
efbfeb3954 docs: say what these things are, not where they used to be
mara on #3923: "past tense is itself a smell for docs, we dont need any
'used to be somewhere else'" and "any gate we add should not have false
positives". Those are one instruction: the entire false-positive set of a
repo-path checker was prose naming files that are deliberately gone, so
removing the prose is what lets the gate be strict instead of carrying a
skip-list nobody maintains.

Three sites, and only two of them are past tense:

`agent-hierarchy.md` named two removed modules to explain that a code path
is gone. A reader cannot act on where it used to live; they can act on
where cancellation happens now, which is the half the sentence buried.

`term_msg.rs` carried a "Design history" paragraph whose live content was
three rules -- classification is server-side, `level` carries styling, four
named fields are deliberately absent -- wrapped in narration about a
redesign and a pointer to a deleted frontend file. Restated as the rules.

`gotchas.md` is NOT past tense: it correctly names a file in the website
repo. Qualifying it as `hyperhive/website:nix/options.nix` did not work --
the extractor still matches `nix/options.nix` as a substring, so the fix
has to be prose that contains no repo-relative path at all. Naming the
file alone does that and reads better.

Verified against this tree rather than the default checkout: the audit
script takes a directory argument and defaults elsewhere, so its first
(unchanged) DEAD:3 was a true statement about a different branch. On this
one: DEAD 3 -> 0, 141 distinct paths enumerated, and an injected dead path
is still caught.

Refs #3923
2026-09-07 15:06:12 +02:00
4 changed files with 52 additions and 15 deletions

View file

@ -155,11 +155,9 @@ Manager}` switch picks the MCP tool allow-list claude sees. Both are
ancestor/descendant check instead of a manager-name check — see the ancestor/descendant check instead of a manager-name check — see the
module docs for `loose_ends.rs`, `stores/broker.rs`, `actions.rs`, module docs for `loose_ends.rs`, `stores/broker.rs`, `actions.rs`,
and `workers/crash_watch.rs` for the current owner-check logic in and `workers/crash_watch.rs` for the current owner-check logic in
each. (Question/answer routing and its own manager-override cancel each. (Reminder cancellation is handled fully in-agent — see the note
path — formerly `hive-c0re/src/questions.rs` and on `CancelLooseEndKind::Reminder` in
`stores/operator_questions.rs` — has been removed entirely; reminder `hive-c0re/src/socket_server/mod.rs`.)
cancellation is now handled fully in-agent, see the note on
`CancelLooseEndKind::Reminder` in `hive-c0re/src/socket_server/mod.rs`.)
None of the above is a stable interface — treat the module doc None of the above is a stable interface — treat the module doc
comments as the source of truth for exactly which checks exist today. comments as the source of truth for exactly which checks exist today.

View file

@ -472,7 +472,7 @@ Pipeline:
- CommonMark from `nixosOptionsDoc.optionsCommonMark` is the only - CommonMark from `nixosOptionsDoc.optionsCommonMark` is the only
output — the source of truth, emitted as `.md`. output — the source of truth, emitted as `.md`.
- **HTML + CSS is rendered downstream by the website repo** - **HTML + CSS is rendered downstream by the website repo**
(`nix/options.nix` there), which consumes this bundle's `host.md` / (its own `options.nix`), which consumes this bundle's `host.md` /
`agent.md`, renders them with `cmark-gfm`, and shares one `agent.md`, renders them with `cmark-gfm`, and shares one
stylesheet (`docs.css`) across `/options/` and the prose `/docs/` stylesheet (`docs.css`) across `/options/` and the prose `/docs/`
tree. Keeping rendering in the website means the theme has a single tree. Keeping rendering in the website means the theme has a single

View file

@ -10,14 +10,12 @@
//! `stream-json` line) can expand to several: an `assistant` message with //! `stream-json` line) can expand to several: an `assistant` message with
//! both a text block and a `tool_use` block produces two rows. //! both a text block and a `tool_use` block produces two rows.
//! //!
//! Design history: mara's terminal-message redesign (six rounds of //! Six fields, and classification belongs **here**, not in the client: the
//! negotiation on the forge) collapsed what was an 11-field frontend-side //! web UI renders what it is handed and owns no per-tool dispatch table, so
//! row shape (`StreamRow`, `frontend/packages/agent/src/lib/streamRow.ts`) //! a new tool needs no frontend change. `level` carries styling, so a row
//! plus raw claude-JSON passthrough into this 6-field shape, with //! never names a CSS class. `kind`, `unread`, `from` and `expanded_default`
//! classification moved server-side so the client-side `classifyEvent.ts` — //! are deliberately absent — adding one back is a design change, not an
//! a large per-tool dispatch table — mostly goes away. `kind`, `unread`, //! oversight.
//! `from`, and `expanded_default` were all considered and dropped along the
//! way; `level` replaces free-text CSS-class styling.
use serde::Serialize; use serde::Serialize;
use std::collections::HashMap; use std::collections::HashMap;

View file

@ -9,12 +9,18 @@
# Emits a CI error annotation per hit and exits 1 if any pointer is dead, 0 # Emits a CI error annotation per hit and exits 1 if any pointer is dead, 0
# otherwise. Runs as its own job; seconds, pure git+grep. # otherwise. Runs as its own job; seconds, pure git+grep.
# #
# Two arms, because a pointer is written two ways: # Three arms, because a pointer is written three ways:
# root — a repo-root-relative path in prose or a comment, `docs/<dir>/<f>.md` # root — a repo-root-relative path in prose or a comment, `docs/<dir>/<f>.md`
# rel — a markdown link resolved against the linking file, [x](../<dir>/<f>.md) # rel — a markdown link resolved against the linking file, [x](../<dir>/<f>.md)
# path — any repo-relative source path, `<crate>/src/<f>.rs`, `nix/<f>.nix`
# (spelled with placeholders on purpose: a literal example path here would be # (spelled with placeholders on purpose: a literal example path here would be
# a dead pointer this script then reports against itself) # a dead pointer this script then reports against itself)
# #
# The third arm carries no skip-list, and could only be built after the prose
# it would have flagged was fixed: every false positive was a sentence naming
# where something *used to* live. Tolerating those needs a past-tense
# heuristic — the permanent carve-out that teaches readers to ignore a gate.
#
# Scope is every tracked file, deliberately unrestricted by type: prose # Scope is every tracked file, deliberately unrestricted by type: prose
# describes a path in words, and a stale path in a config file (an ignore # describes a path in words, and a stale path in a config file (an ignore
# rule, a build input) is a live defect rather than a stale comment. # rule, a build input) is a live defect rather than a stale comment.
@ -98,10 +104,45 @@ done < "$tmp/rel"
# something that resolves before a clean run means anything: if a rename, a # something that resolves before a clean run means anything: if a rename, a
# pattern edit or a tree move silences the search, that is a failure of this # pattern edit or a tree move silences the search, that is a failure of this
# script, not a property of the tree. # script, not a property of the tree.
# Third arm: repo-relative paths to any tracked file, not just `docs/**.md`.
# A comment naming a moved `.rs` or `.nix` file misleads exactly as much as a
# dead doc link, and nothing was checking it.
#
# Both ends are anchored, and each anchor is there because its absence
# invented a path: unanchored, a `.js` extension matched the prefix of a
# `.json` one, and a bare `<dir>/` matched mid-path inside `nix/<dir>/…`,
# conjuring a top-level twin of a real nested file. (Placeholders, per the
# header — a literal here would be a dead path this arm reports against
# itself.) `grep -o` has no lookaround, so the boundary char is stripped below.
prefix_pattern='(nix|scripts|docs|frontend|branding|hive-[a-z0-9-]+/src|hivectl/src|swarmctl/src|swarm-controller/src)'
ext_pattern='(nix|sh|rs|md|ts|tsx|js|css|toml)'
path_pattern="(^|[^A-Za-z0-9._/-])${prefix_pattern}/[A-Za-z0-9._/-]+\\.${ext_pattern}($|[^A-Za-z0-9])"
git ls-files -z | xargs -0 -r grep -nEo "$path_pattern" /dev/null 2>/dev/null \
> "$tmp/paths0" || true
sed -E 's/^([^:]*:[0-9]+:)[^A-Za-z0-9]?/\1/; s/[^A-Za-z0-9]$//' "$tmp/paths0" \
> "$tmp/paths" || true
while IFS=: read -r file lineno ref; do
[ -n "${ref:-}" ] || continue
exempt "$file" "$lineno" && continue
if [ -f "$ref" ]; then
alive=$((alive + 1))
else
dead=$((dead + 1))
printf '::error file=%s,line=%s::repo path does not resolve: %s\n' \
"$file" "$lineno" "$ref"
fi
done < "$tmp/paths"
if [ "$(wc -l < "$tmp/root")" -eq 0 ]; then if [ "$(wc -l < "$tmp/root")" -eq 0 ]; then
echo 'check-doc-refs: root-pointer search matched nothing — pattern or tree changed' >&2 echo 'check-doc-refs: root-pointer search matched nothing — pattern or tree changed' >&2
exit 1 exit 1
fi fi
if [ "$(wc -l < "$tmp/paths")" -eq 0 ]; then
echo 'check-doc-refs: repo-path search matched nothing — pattern or tree changed' >&2
exit 1
fi
if [ "$(wc -l < "$tmp/rel")" -eq 0 ]; then if [ "$(wc -l < "$tmp/rel")" -eq 0 ]; then
echo 'check-doc-refs: relative-link search matched nothing — pattern or tree changed' >&2 echo 'check-doc-refs: relative-link search matched nothing — pattern or tree changed' >&2
exit 1 exit 1