From 2298e016c70a54e8286b9adba281d5d074497520 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sat, 31 Oct 2026 22:17:26 +0100 Subject: [PATCH] wired: match the invocation, not the prose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `tools/wired` is the guard over the other four: its test fails when a tool under `tools/` is invoked by no workflow. It exists because the MkDocs-to-Hugo move (#28) rewrote the workflows and dropped two documentation guards while leaving them in the tree — a guard nothing calls is indistinguishable from a guard that passes, since both are silent. It matched the tool's **bare name** anywhere in the concatenated workflow text. Which means it was already partly answering on prose: .github/workflows/docs.yml:106: go run ./tools/docexample --rbgo /tmp/rbgo --root content/en .github/workflows/docs.yml:108: # docexample judges the examples that claim an answer -- 11 of them. The ## Measured, by planting it Replaced line 106 with an `echo`, leaving the comment, and counted both rules against that state rather than reasoning about them: $ grep -rno "docexample" .github/workflows .github/workflows/docs.yml:108:docexample <- the OLD rule: still 1 match → "wired" $ grep -rno "tools/docexample" .github/workflows (nothing) <- the NEW rule: 0 matches → unwired and the gate itself, under the new rule: --- FAIL: TestEveryToolIsWiredIntoAWorkflow no workflow invokes [docexample]. So the one defect this package exists to catch was invisible for the one tool whose step carries a comment about it. Line 106 is restored byte-for-byte. ## Still deliberately crude The existing comment argues the crudeness is a choice — *"it looks for the directory's name in the YAML, not for a correctly formed step … a crude test that would have failed #28 beats a precise one nobody wrote"* — and that argument is kept. This does not start parsing YAML or validating steps. It only stops treating *"appears in a sentence"* and *"appears in a command"* as the same claim, and `tools/` is what every spelling of an invocation has in common: go run ./tools/upstreamlinks --root content/en go run github.com/go-embedded-ruby/docs/tools/conformancefigure@v0.6.0 cd tools/docexample && go run . while a prose comment does not. All three are test cases. A workflow that invoked a tool some other way — through a variable, a matrix — now reads as unwired. That fails loudly rather than quietly, which is the direction this particular guard should be wrong in. ## Tests `TestAMentionInProseIsNotAnInvocation` asserts both halves together — the comment alone must not count, and each real invocation must still count — so a rule tightened into matching nothing cannot pass it. The repository's live state is unchanged: 4 tools under `tools/`, all four invoked. The failure message said "no workflow **mentions**", which is now the wrong word for what is checked; it says "invokes" and names the pattern, because the message is what the next person acts on. README: `tools/wired` was not described at all — a reader could not tell the wiring was asserted. One paragraph, with the reason and this defect. Co-Authored-By: Claude Opus 5 --- README.md | 10 ++++++++++ tools/wired/wired.go | 27 +++++++++++++++++++++++-- tools/wired/wired_test.go | 42 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8c421c8..9ceb4c2 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,16 @@ go run ./tools/numericsurface --rbgo /tmp/rbgo --root content/en go run ./tools/upstreamlinks --root content/en ``` +A fifth directory, `tools/wired/`, is not a tool but the guard over the four +above: its test fails when a tool under `tools/` is invoked by no workflow. +It exists because the MkDocs-to-Hugo move ([#28](https://github.com/go-embedded-ruby/docs/pull/28)) +rewrote the workflows and dropped two of these guards while leaving them in the +tree — **a guard nothing calls is indistinguishable from a guard that passes, +since both are silent**, and the pages could have said anything for two +releases. It looks for the path `tools/`, not the bare name: `docs.yml` +mentions `docexample` in a comment as well as in its step, so against the bare +name, deleting that step would have left the tool reported as wired. + The authority column is the point. None of these holds the right answer itself: a table of right answers kept in **this** repository would repeat the page's mistake and agree with it. `upstreamlinks` asks `go.mod` precisely because a diff --git a/tools/wired/wired.go b/tools/wired/wired.go index b2738b1..4b4edb3 100644 --- a/tools/wired/wired.go +++ b/tools/wired/wired.go @@ -86,11 +86,34 @@ func WorkflowText(dir string) (string, error) { return b.String(), nil } -// Unwired returns the commands no workflow mentions. +// Unwired returns the commands no workflow invokes. +// +// ⛔ It looks for `tools/`, the PATH, not for the bare name. +// +// Measured 2026-10-31: `.github/workflows/docs.yml` names `docexample` twice — +// once on the `run:` line that invokes it, and once in a comment on line 108 +// explaining what it judges. Against the bare name, deleting the invocation and +// leaving the comment would have kept that tool reported as wired. The one +// defect this package exists to catch would have been invisible for the one +// tool whose step carries prose about it. +// +// This stays deliberately crude — it still does not check for a well-formed +// step, because the failure it guards is not subtle (a tool nobody calls) and a +// crude test that would have failed #28 beats a precise one nobody wrote. But +// "appears in a sentence" and "appears in a command" are not the same claim, and +// `tools/` is what every spelling of the invocation has in common: +// +// go run ./tools/upstreamlinks --root content/en +// go run github.com/go-embedded-ruby/docs/tools/conformancefigure@v0.6.0 +// +// while a prose comment does not. A workflow that invoked a tool some other way +// — through a variable, a matrix — would now read as unwired, which fails loudly +// rather than quietly, and that is the right direction for this guard to be +// wrong in. func Unwired(commands []string, workflows string) []string { var out []string for _, c := range commands { - if !strings.Contains(workflows, c) { + if !strings.Contains(workflows, "tools/"+c) { out = append(out, c) } } diff --git a/tools/wired/wired_test.go b/tools/wired/wired_test.go index 9688343..2e73848 100644 --- a/tools/wired/wired_test.go +++ b/tools/wired/wired_test.go @@ -38,14 +38,15 @@ func TestEveryToolIsWiredIntoAWorkflow(t *testing.T) { bad := wired.Unwired(cmds, wf) if len(bad) > 0 { - t.Errorf("no workflow mentions %v.\n"+ + t.Errorf("no workflow invokes %v (looked for `tools/`, not the bare name:\n"+ + "a tool named in a comment is documented, not called).\n"+ "A tool nothing calls is indistinguishable from a tool that passes: both are "+ "silent. Either wire it into .github/workflows or delete it; leaving it is the "+ "state that let the MkDocs-to-Hugo move (#28) drop both documentation guards "+ "without a red build.", bad) return } - t.Logf("%d tool(s) under tools/, all named by a workflow: %v", len(cmds), cmds) + t.Logf("%d tool(s) under tools/, all invoked by a workflow: %v", len(cmds), cmds) } // TestUnwiredNamesOnlyWhatIsMissing checks the rule itself against literals, so @@ -64,6 +65,43 @@ func TestUnwiredNamesOnlyWhatIsMissing(t *testing.T) { } } +// ⛔ A NAME IN A SENTENCE IS NOT AN INVOCATION, and this repository already +// contains the sentence: docs.yml:108 reads +// +// # docexample judges the examples that claim an answer -- 11 of them. The +// +// so against the bare name, deleting the `go run ./tools/docexample` step and +// leaving that comment kept the tool reported as wired — the exact blindness +// this package exists to remove, for the one tool whose step is documented. +// +// The two halves are asserted together: the comment alone must NOT count, and +// the invocation must still count, so a rule tightened into matching nothing +// cannot pass this. +func TestAMentionInProseIsNotAnInvocation(t *testing.T) { + const comment = " # docexample judges the examples that claim an answer -- 11 of them. The\n" + if got := wired.Unwired([]string{"docexample"}, comment); len(got) != 1 { + t.Errorf("a tool named only in a comment was reported as wired (Unwired = %v)", got) + } + + // The real spellings of an invocation, each of which must count. + for _, yaml := range []string{ + " run: go run ./tools/docexample --rbgo /tmp/rbgo --root content/en\n", + " run: go run github.com/go-embedded-ruby/docs/tools/docexample@v0.6.0\n", + " run: cd tools/docexample && go run .\n", + } { + if got := wired.Unwired([]string{"docexample"}, yaml); len(got) != 0 { + t.Errorf("an invoked tool was reported as unwired (%v) for:\n%s", got, yaml) + } + } + + // And the comment beside a real invocation is the repository's actual state: + // wired because of the step, not because of the prose. + both := " run: go run ./tools/docexample --root content/en\n" + comment + if got := wired.Unwired([]string{"docexample"}, both); len(got) != 0 { + t.Errorf("Unwired = %v, want the step to count", got) + } +} + // repoRoot walks up to the directory holding go.mod, so the test runs from // wherever `go test ./...` places it. func repoRoot(t *testing.T) string {