From efbfeb39545e05f6a5170b3dca5e633c984a0bfb Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 2 Sep 2026 22:53:47 +0200 Subject: [PATCH 1/2] 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 --- docs/agent-lifecycle/agent-hierarchy.md | 8 +++----- docs/process/gotchas.md | 2 +- hive-agent/src/term_msg.rs | 14 ++++++-------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/agent-lifecycle/agent-hierarchy.md b/docs/agent-lifecycle/agent-hierarchy.md index 425ef6d0..c1afebcb 100644 --- a/docs/agent-lifecycle/agent-hierarchy.md +++ b/docs/agent-lifecycle/agent-hierarchy.md @@ -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 module docs for `loose_ends.rs`, `stores/broker.rs`, `actions.rs`, and `workers/crash_watch.rs` for the current owner-check logic in - each. (Question/answer routing and its own manager-override cancel - path — formerly `hive-c0re/src/questions.rs` and - `stores/operator_questions.rs` — has been removed entirely; reminder - cancellation is now handled fully in-agent, see the note on - `CancelLooseEndKind::Reminder` in `hive-c0re/src/socket_server/mod.rs`.) + each. (Reminder cancellation is 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 comments as the source of truth for exactly which checks exist today. diff --git a/docs/process/gotchas.md b/docs/process/gotchas.md index 875ccbdb..a3fb9c33 100644 --- a/docs/process/gotchas.md +++ b/docs/process/gotchas.md @@ -472,7 +472,7 @@ Pipeline: - CommonMark from `nixosOptionsDoc.optionsCommonMark` is the only output — the source of truth, emitted as `.md`. - **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 stylesheet (`docs.css`) across `/options/` and the prose `/docs/` tree. Keeping rendering in the website means the theme has a single diff --git a/hive-agent/src/term_msg.rs b/hive-agent/src/term_msg.rs index df4da3d7..193d2d75 100644 --- a/hive-agent/src/term_msg.rs +++ b/hive-agent/src/term_msg.rs @@ -10,14 +10,12 @@ //! `stream-json` line) can expand to several: an `assistant` message with //! both a text block and a `tool_use` block produces two rows. //! -//! Design history: mara's terminal-message redesign (six rounds of -//! negotiation on the forge) collapsed what was an 11-field frontend-side -//! row shape (`StreamRow`, `frontend/packages/agent/src/lib/streamRow.ts`) -//! plus raw claude-JSON passthrough into this 6-field shape, with -//! classification moved server-side so the client-side `classifyEvent.ts` — -//! a large per-tool dispatch table — mostly goes away. `kind`, `unread`, -//! `from`, and `expanded_default` were all considered and dropped along the -//! way; `level` replaces free-text CSS-class styling. +//! Six fields, and classification belongs **here**, not in the client: the +//! web UI renders what it is handed and owns no per-tool dispatch table, so +//! a new tool needs no frontend change. `level` carries styling, so a row +//! never names a CSS class. `kind`, `unread`, `from` and `expanded_default` +//! are deliberately absent — adding one back is a design change, not an +//! oversight. use serde::Serialize; use std::collections::HashMap; From 8bf21b6d0c4c7215a6e8bc1b36542eab8e0cc88a Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 2 Sep 2026 23:04:15 +0200 Subject: [PATCH 2/2] 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 --- scripts/check-doc-refs.sh | 43 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/scripts/check-doc-refs.sh b/scripts/check-doc-refs.sh index 24894438..d6ae0a8f 100755 --- a/scripts/check-doc-refs.sh +++ b/scripts/check-doc-refs.sh @@ -9,12 +9,18 @@ # 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. # -# 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//.md` # rel — a markdown link resolved against the linking file, [x](..//.md) +# path — any repo-relative source path, `/src/.rs`, `nix/.nix` # (spelled with placeholders on purpose: a literal example path here would be # 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 # 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. @@ -98,10 +104,45 @@ done < "$tmp/rel" # 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 # 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 `/` matched mid-path inside `nix//…`, +# 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 echo 'check-doc-refs: root-pointer search matched nothing — pattern or tree changed' >&2 exit 1 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 echo 'check-doc-refs: relative-link search matched nothing — pattern or tree changed' >&2 exit 1