carlos / platform Public

plan: four pre-flight corrections before execution

Scanning the plan for cross-task conflicts before dispatching found four,
all mine:

The gate tests skipped on `docs/site` existing, but Task 4 creates
`docs/site/anchors.json` — so the skips would stop firing four tasks
early and every gate would fail on a corpus that does not arrive until
Task 8. They test `nav.json` now.

`fencedCarlosVerbs` could not produce `adopt` from
`/opt/carlos/carlos adopt`, which the plan's own test asserted it does.
It now matches a field ending in `/carlos` too — the on-box runbooks
invoke the binary by absolute path, and those examples go stale the same
way bare ones do.

A test named for nesting that checked fence languages is renamed to what
it checks, and Task 8's orientation step now runs a command that exists.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 7385c63556142133c7709eaa8a2d0078ec1eea15 parent 37969ea
1 file changed, +29 −22
  • docs/superpowers/plans/2026-08-25-docs-pipeline-and-gates.md +29 −22
diff --git a/docs/superpowers/plans/2026-08-25-docs-pipeline-and-gates.md b/docs/superpowers/plans/2026-08-25-docs-pipeline-and-gates.md
index 57d3be9e1..fa9425d55 100644
--- a/docs/superpowers/plans/2026-08-25-docs-pipeline-and-gates.md
+++ b/docs/superpowers/plans/2026-08-25-docs-pipeline-and-gates.md
@@ -441,13 +441,15 @@ Expected: `TestNavAndFilesAgreeBothWays` FAILS with `load: read nav.json: ... do
Add to the top of `TestNavAndFilesAgreeBothWays` only:
```go
- if _, err := os.Stat(root); os.IsNotExist(err) {
- t.Skip("no corpus yet — Task 8 creates docs/site")
+ if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) {
+ t.Skip("no corpus yet — Task 8 creates docs/site/nav.json")
}
```
Add the same three lines to every gate test that reads `root` in Tasks 3–7. Task 8 deletes all of them in one commit. Import `"os"`.
+The condition tests `nav.json`, not the directory. Task 4 creates `docs/site/anchors.json`, so a bare directory test would stop skipping one task early and every gate would then fail on a corpus that does not exist until Task 8.
+
- [ ] **Step 6: Run to verify all pass**
Same command. Expected: PASS (one skipped).
@@ -495,8 +497,8 @@ import (
// exists, the link still looks right in review, and the reader lands at
// the top of a long page wondering what they missed.
func TestInternalLinksResolve(t *testing.T) {
- if _, err := os.Stat(root); os.IsNotExist(err) {
- t.Skip("no corpus yet — Task 8 creates docs/site")
+ if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) {
+ t.Skip("no corpus yet — Task 8 creates docs/site/nav.json")
}
site, err := Load(root)
if err != nil {
@@ -553,8 +555,8 @@ func TestLinkCheckCatchesADeadFragment(t *testing.T) {
// the second one. The link then goes to the wrong section rather than
// nowhere, which is harder to notice.
func TestAnchorsAreUnique(t *testing.T) {
- if _, err := os.Stat(root); os.IsNotExist(err) {
- t.Skip("no corpus yet — Task 8 creates docs/site")
+ if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) {
+ t.Skip("no corpus yet — Task 8 creates docs/site/nav.json")
}
site, err := Load(root)
if err != nil {
@@ -660,7 +662,7 @@ func TestAnchorMatchesFixture(t *testing.T) {
Add `"encoding/json"` and `"path/filepath"` to the imports.
-Note this test does **not** skip on a missing corpus: `anchors.json` is created by this task, so from here on `docs/site/` exists. Tasks 2, 3 and 5–7 keep their skips until Task 8, because they need the `.md` pages, not just the fixture.
+Note this test does **not** skip: `anchors.json` is created by this task, so it is always there to read. The other gates keep skipping until Task 8 because they need the `.md` pages — which is why their condition tests `nav.json` rather than the `docs/site` directory this task brings into existence.
- [ ] **Step 3: Run to verify it passes**
@@ -718,8 +720,8 @@ var knownFenceLangs = map[string]bool{
}
func TestFenceLanguagesAreDeclared(t *testing.T) {
- if _, err := os.Stat(root); os.IsNotExist(err) {
- t.Skip("no corpus yet — Task 8 creates docs/site")
+ if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) {
+ t.Skip("no corpus yet — Task 8 creates docs/site/nav.json")
}
site, err := Load(root)
if err != nil {
@@ -739,10 +741,10 @@ func TestFenceLanguagesAreDeclared(t *testing.T) {
}
}
-// TestFenceParsingHandlesNesting proves the parser does not close an
-// outer fence on an inner one, which would split one block into two and
-// hide the second half from every gate.
-func TestFenceParsingHandlesNesting(t *testing.T) {
+// TestTestdataFencesDeclareLanguages runs the same rule against the
+// testdata corpus, so the gate above is proven to work on a corpus that
+// exists before docs/site does.
+func TestTestdataFencesDeclareLanguages(t *testing.T) {
site, err := Load(testCorpus)
if err != nil {
t.Fatalf("load: %v", err)
@@ -855,8 +857,8 @@ func contains(xs []string, s string) bool {
// someone can forget to update, so gating against it would only prove
// the docs match a second copy of the docs. The switch is the CLI.
func TestCLICommandsAreDocumented(t *testing.T) {
- if _, err := os.Stat(root); os.IsNotExist(err) {
- t.Skip("no corpus yet — Task 8 creates docs/site")
+ if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) {
+ t.Skip("no corpus yet — Task 8 creates docs/site/nav.json")
}
site, err := Load(root)
if err != nil {
@@ -1026,8 +1028,8 @@ Append to `internal/docsite/fences_test.go`:
// prose. A renamed verb leaves every fence containing it copy-pasteable
// and wrong, and nothing else in this package would notice.
func TestFencedCarlosCommandsAreReal(t *testing.T) {
- if _, err := os.Stat(root); os.IsNotExist(err) {
- t.Skip("no corpus yet — Task 8 creates docs/site")
+ if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) {
+ t.Skip("no corpus yet — Task 8 creates docs/site/nav.json")
}
site, err := Load(root)
if err != nil {
@@ -1067,7 +1069,13 @@ func fencedCarlosVerbs(body string) []string {
}
fields := strings.Fields(line)
for i, f := range fields {
- if f != "carlos" || i+1 >= len(fields) {
+ // Bare "carlos", or the binary called by an absolute path —
+ // runbooks on a box invoke /opt/carlos/carlos, and those
+ // examples go stale the same way.
+ if f != "carlos" && !strings.HasSuffix(f, "/carlos") {
+ continue
+ }
+ if i+1 >= len(fields) {
continue
}
next := fields[i+1]
@@ -1116,7 +1124,7 @@ GOCACHE=/tmp/claude-1001/gocache GOFLAGS=-mod=mod GOPROXY=off go test ./internal
Expected: `TestFencedCarlosCommandsAreReal` SKIP, `TestFencedCarlosVerbsIgnoresNoise` PASS.
-The `/opt/carlos/carlos adopt` case yields `adopt` — the path segment is one field, so `carlos` matches only the binary name. That is the behaviour we want, and the test pins it.
+The `/opt/carlos/carlos adopt` case yields `adopt`: a field ending in `/carlos` is the binary called by absolute path, which is how the on-box runbooks invoke it, and those examples go stale the same way bare ones do. `echo carlos` yields nothing because no word follows, and `carlos -h` yields nothing because a flag is not a verb. The test pins all three.
- [ ] **Step 3: Commit**
@@ -1149,11 +1157,10 @@ Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>"
- [ ] **Step 1: Get the command list**
```bash
-GOCACHE=/tmp/claude-1001/gocache GOFLAGS=-mod=mod GOPROXY=off go run ./scripts/... 2>/dev/null || \
- go doc ./internal/docsite TopLevelCommands
+grep -o 'case "[a-z-]*"' main.go | sort -u
```
-Simpler: read `main.go:138-204`, the `usage` function. It already groups every command by role — your apps, your deployment, on the host — with a one-line description each. That grouping is the CLI page's structure. Do not copy its wording; it is terse to fit a terminal, and the page has room to say what a command is for.
+That is the list the gate will hold you to. Then read `main.go:138-204`, the `usage` function. It already groups every command by role — your apps, your deployment, on the host — with a one-line description each. That grouping is the CLI page's structure. Do not copy its wording; it is terse to fit a terminal, and the page has room to say what a command is for.
- [ ] **Step 2: Write `docs/site/nav.json`**