Skip to content

Add a consumption test for the compiled wiki - #6

Open
mprokopov wants to merge 2 commits into
lars20070:masterfrom
mprokopov:add-house-style-consumption-test
Open

Add a consumption test for the compiled wiki#6
mprokopov wants to merge 2 commits into
lars20070:masterfrom
mprokopov:add-house-style-consumption-test

Conversation

@mprokopov

@mprokopov mprokopov commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Why

The repo verifies that md2okf produces a well-formed wiki: a book compiles,
okf-lint passes, pages carry the right frontmatter. Nothing verified the wiki
was any use, which is a different claim — and the one OKF's own goals make:

Inform how consumption agents should read and traverse it.
SPEC.md, Goals §2

How it works

An agent is given one badly written paragraph and nothing but the wiki. A new
pi-consume Compose service mounts okf/ read-only and does not mount md/
at all
, so the source documents are out of reach and the answer has to come
from the compiled pages. Each reply ends in a JSON block naming every change and
the page it came from.

Grading is three assertions with no model in the loop — every check is a string
or filesystem operation, so a run is reproducible and a failure is inspectable:

  1. applied — the expected strings appear, the forbidden ones do not
  2. cited — some citation resolves to a page that exists
  3. grounded — that page actually contains the ruling

The third is the point. Without it an agent could produce the right edit from
memory, attach a plausible-looking path, and score full marks having never
opened the wiki. The grader was self-tested against five deliberately bad
inputs — hallucinated citation, unapplied edit, missing JSON block, unresolvable
path, absent transcript — and rejects all five.

Guarding against prior knowledge

A capable model already knows roughly what Economist house style is, so a good
rewrite proves nothing on its own. Cases therefore lean on rulings that cannot
be derived from general knowledge, several of which invert the usual
convention — the wiki requires the % sign where most guides spell out "per
cent", and forbids sentence-initial "Hopefully" while conceding it is not a
grammatical error. Answering from priors gets those wrong, which is what gives
the test the ability to fail.

There is deliberately no A/B control arm. It would quantify the wiki's
contribution but doubles the runs; the counterintuitive rulings already provide
falsifiability. Recorded as a limitation in the spec, not overlooked.

Result

8/8 against a wiki compiled from a 382 KB style guide. The most convincing part
was not the score: on the percentage case the agent made an unprompted extra
change, rewriting "last year" as "in the past year" and citing a ruling that
last year, in 2023, means 2022. That ruling was not in the fixture and not in
the prompt — it was read off a page.

The first run scored 7/8, and the agent was right while the fixture was wrong:
it cited chapter 1, which says outright that to impact should be to have an
impact on
, while the fixture only accepted chapter 9's phrasing of the same
prohibition. grounding now takes a list. That widens what counts as a valid
source without weakening the requirement for one — the synthetic bad inputs were
re-run afterwards and still fail.

Notes for review

  • The skill lives in both runtime config copies, byte-identical, per the
    two-independent-runtimes rule in AGENTS.md.
  • Provider and model are passed as --provider / --model flags rather than
    read from settings.json, so a run states which model it exercised and leaves
    repo config untouched. Override with PI_PROVIDER / PI_MODEL.
  • The driver exports a placeholder OPENROUTER_API_KEY before invoking Compose.
    This is not sloppiness: Compose interpolates every service in the file, so the
    sibling pi service's fail-fast would otherwise abort a consumption run that
    never touches OpenRouter. The real provider key is checked first, and exits 2
    if missing.
  • Exit codes distinguish a genuine result from infrastructure trouble: 0 all
    passed, 1 a case failed, 2 could not run. A missing wiki or absent key is
    never reported as "the wiki is bad".
  • Transcripts land in tests/house-style/out/, which is gitignored.
  • Design rationale, including what this deliberately does not measure, is in
    docs/superpowers/specs/2026-07-31-okf-consumption-test-design.md.

Verification

  • make lint — clean
  • make validateVALID
  • grader self-test — rejects all five bad-input classes
  • every grounding string confirmed present at its expected page before running
  • full run: 8/8, provider litellm, model gemini-3.1-pro-preview

Not covered

Navigability. The container ships ripgrep and fd, so the agent searches
rather than traversing index.md files. This test says nothing about whether
the index structure works — a separate test could deny the search tools and
require traversal from the root index.

Summary by CodeRabbit

  • New Features
    • Added an agent capability for applying documented house-style rulings while preserving meaning and factual accuracy.
    • Added isolated, read-only wiki consumption with resource-limited execution.
  • Tests
    • Added eight style-focused test cases covering punctuation, wording, usage, and editorial rulings.
    • Added automated transcript grading for rewrite accuracy, citation validity, and source grounding.
  • Documentation
    • Added an approved design specification for wiki-consumption testing and evaluation.

The repo verifies that md2okf produces a well-formed wiki. Nothing verified
the wiki was any use, which is a different claim and the one OKF's own goals
make ("inform how consumption agents should read and traverse it").

The test gives an agent a badly written paragraph and nothing but the wiki:
a new pi-consume service mounts okf/ read-only and does not mount md/ at all,
so the source documents are out of reach. Each reply must end in a json block
naming its changes and the page each came from.

Grading is three string/filesystem assertions, no model in the loop: the edit
landed, the citation resolves, and the cited page really contains the ruling.
The third is the point. Without it an agent could produce the right edit from
memory, attach a plausible path, and score full marks having never opened the
wiki.

The cases lean on rulings a model cannot guess — the wiki requires the % sign
where most guides spell out "per cent", and forbids sentence-initial
"Hopefully" while conceding it is not a grammatical error. Answering from
priors gets those wrong, which is what gives the test the ability to fail.

Provider and model are passed as flags rather than read from settings.json, so
a run states which model it exercised and leaves repo config untouched.
The first real run failed impact-as-verb, and the agent was right while the
fixture was wrong. It cited chapter 1, which says outright that "to impact"
annoys enough readers that you should write "to have an impact on" — quoted it
verbatim and applied exactly that fix. The fixture only accepted chapter 9's
wording, where the same prohibition appears in a list of nouns not to verb.

So `grounding` now takes a list and any one entry grounds the citation. This
widens what counts as a correct source; it does not weaken the check that a
source is required. Re-running the synthetic bad inputs confirms the
hallucinated-citation case still fails.

Also fixes the driver: compose interpolates every service in the file, so the
sibling `pi` service's OPENROUTER_API_KEY fail-fast aborted a consumption run
that never touches OpenRouter. The driver now supplies a placeholder purely to
satisfy interpolation, after the real provider key check.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an isolated pi-consume runtime, two apply-house-style skills, eight OKF consumption cases, a shell test runner, and deterministic grading for rewrites and citations.

Changes

OKF consumption test

Layer / File(s) Summary
Consumption test contract
docs/superpowers/specs/2026-07-31-okf-consumption-test-design.md
Defines runtime isolation, output JSON, citation checks, eight cases, failure codes, implementation components, and non-goals.
Consumer skill and isolated runtime
pi/container/agent/skills/apply-house-style/SKILL.md, pi/sandbox/files/home/.pi/agent/skills/apply-house-style/SKILL.md, pi/container/compose.yaml
Adds evidence-based rewriting instructions and a read-only pi-consume service with compiled okf access.
Fixture-driven test execution
tests/house-style/cases.json, scripts/test-house-style.sh, .gitignore
Defines house-style fixtures, runs isolated cases, captures transcripts, validates infrastructure, and ignores generated output.
Deterministic transcript grading
tests/house-style/grade.py
Validates JSON responses, rewrite rules, citation paths, citation grounding, case results, and exit statuses.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related PRs

  • lars20070/md2okf#1: Provides the OKF consumption infrastructure extended by this change.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title starts with the imperative verb "Add," names the compiled wiki area, and is 44 characters long.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
tests/house-style/grade.py (1)

42-51: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Constrain resolve() to stay inside the wiki directory.

rel can still contain ../ segments after stripping the leading slash and optional okf/ prefix, so wiki / rel can resolve outside wiki. Since citation originates from model output, a hallucinated or malformed citation could reference a file elsewhere on the host filesystem. is_file() would treat that as a valid, resolved citation, and the "grounded" check would then read that external file's content.

🔒 Proposed fix
 def resolve(wiki, citation):
     """Map a bundle-absolute citation to a file on disk, or None."""
     if not isinstance(citation, str) or not citation.strip():
         return None
     rel = citation.strip().lstrip("/")
     # Tolerate an okf/ prefix even though the skill asks for bundle-absolute.
     if rel.startswith(f"{wiki.name}/"):
         rel = rel[len(wiki.name) + 1:]
-    path = wiki / rel
-    return path if path.is_file() else None
+    wiki_root = wiki.resolve()
+    candidate = (wiki / rel).resolve()
+    if candidate != wiki_root and wiki_root not in candidate.parents:
+        return None
+    return candidate if candidate.is_file() else None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/house-style/grade.py` around lines 42 - 51, Update resolve() to
normalize the candidate path and verify it remains within wiki before accepting
it. After constructing wiki / rel, use the path’s resolved form and require it
to be equal to wiki or have wiki as its parent, then apply the existing
is_file() check; return None for traversal outside the wiki directory.
scripts/test-house-style.sh (1)

47-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use an array instead of unquoted word-splitting; the case block has no default.

Line 73's for id in ${ids}; do relies on unquoted expansion to word-split, which ShellCheck flags (SC2086). Build the id list as an array instead. Separately, the case "${provider}" in ... esac at Lines 47-50 has no default branch: an unrecognized --provider/PI_PROVIDER value silently skips the credential check, so a typo produces a confusing auth failure deep inside the container run instead of a clear fail_infra message.

🔧 Proposed fix
-ids="$(python3 -c "
+mapfile -t ids < <(python3 -c "
 import json,sys
 for c in json.load(open('${cases_file}'))['cases']:
     print(c['id'])
-")"
+")
 ...
-for id in ${ids}; do
+for id in "${ids[@]}"; do
 case "${provider}" in
 litellm) [[ -n "${LITELLM_API_KEY:-}" ]] || fail_infra "LITELLM_API_KEY is not set (provider=litellm)." ;;
 openrouter) [[ -n "${OPENROUTER_API_KEY:-}" ]] || fail_infra "OPENROUTER_API_KEY is not set (provider=openrouter)." ;;
+*) fail_infra "Unrecognized provider: ${provider}" ;;
 esac

As per path instructions, prefer "arrays over constructed command strings" and require "ShellCheck-clean code."

Also applies to: 64-73

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/test-house-style.sh` around lines 47 - 50, Update the id iteration
near the existing `for id in ${ids}` loop to construct and iterate an array,
avoiding unquoted word splitting and keeping the script ShellCheck-clean. Add a
default branch to the `case "${provider}"` validation that calls `fail_infra`
with a clear unsupported-provider message, while preserving the existing
credential checks for `litellm` and `openrouter`.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/specs/2026-07-31-okf-consumption-test-design.md`:
- Around line 120-128: Update the Components table entry for the house-style
fixtures to reference cases.json instead of cases.yaml, leaving the surrounding
component paths and descriptions unchanged.

In `@scripts/test-house-style.sh`:
- Around line 41-42: Update the compiled_pages check and the docker compose
build flow in the test-house-style script to explicitly catch failures and route
them through fail_infra with clear stderr messages, preserving the documented
infrastructure-failure exit code 2. Ensure find errors are not suppressed or
allowed to terminate via set -e before the existing zero-page validation, and
ensure build failures likewise invoke fail_infra instead of propagating Docker’s
exit status.
- Around line 84-90: Remove the unconditional failure suppression from the
docker compose invocation in the per-case loop of scripts/test-house-style.sh.
Ensure failures from the pi-consume run, including container, gateway, network,
or other infrastructure errors, propagate to the script’s infrastructure-failure
handling and produce the designated exit status 2 instead of being passed to
grade.py as malformed output.

---

Nitpick comments:
In `@scripts/test-house-style.sh`:
- Around line 47-50: Update the id iteration near the existing `for id in
${ids}` loop to construct and iterate an array, avoiding unquoted word splitting
and keeping the script ShellCheck-clean. Add a default branch to the `case
"${provider}"` validation that calls `fail_infra` with a clear
unsupported-provider message, while preserving the existing credential checks
for `litellm` and `openrouter`.

In `@tests/house-style/grade.py`:
- Around line 42-51: Update resolve() to normalize the candidate path and verify
it remains within wiki before accepting it. After constructing wiki / rel, use
the path’s resolved form and require it to be equal to wiki or have wiki as its
parent, then apply the existing is_file() check; return None for traversal
outside the wiki directory.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 555324cc-cdb6-4af7-a1ef-85ebdf7d5fff

📥 Commits

Reviewing files that changed from the base of the PR and between 2ca8fc1 and f34f11f.

📒 Files selected for processing (8)
  • .gitignore
  • docs/superpowers/specs/2026-07-31-okf-consumption-test-design.md
  • pi/container/agent/skills/apply-house-style/SKILL.md
  • pi/container/compose.yaml
  • pi/sandbox/files/home/.pi/agent/skills/apply-house-style/SKILL.md
  • scripts/test-house-style.sh
  • tests/house-style/cases.json
  • tests/house-style/grade.py

Comment on lines +120 to +128
## Components

| Path | Purpose |
| --- | --- |
| `pi/{container,sandbox}/…/skills/apply-house-style/SKILL.md` | the consumption task, one copy per runtime, aligned by hand |
| `tests/house-style/cases.yaml` | fixtures: input, assertions, expected page, grounding |
| `tests/house-style/grade.py` | the three assertions, table output, exit code |
| `scripts/test-house-style.sh` | driver: one Pi run per case, then grade |
| `pi/container/compose.yaml` | adds the `pi-consume` service |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix stale fixture-file extension in the Components table.

Line 125 lists tests/house-style/cases.yaml. The actual fixtures file added in this PR is tests/house-style/cases.json. A reader following this table to find or edit fixtures looks for the wrong file.

📝 Proposed fix
-| `tests/house-style/cases.yaml` | fixtures: input, assertions, expected page, grounding |
+| `tests/house-style/cases.json` | fixtures: input, assertions, expected page, grounding |

As per path instructions, "flag commands, paths, options, and examples that this change has made wrong or stale."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Components
| Path | Purpose |
| --- | --- |
| `pi/{container,sandbox}/…/skills/apply-house-style/SKILL.md` | the consumption task, one copy per runtime, aligned by hand |
| `tests/house-style/cases.yaml` | fixtures: input, assertions, expected page, grounding |
| `tests/house-style/grade.py` | the three assertions, table output, exit code |
| `scripts/test-house-style.sh` | driver: one Pi run per case, then grade |
| `pi/container/compose.yaml` | adds the `pi-consume` service |
## Components
| Path | Purpose |
| --- | --- |
| `pi/{container,sandbox}/…/skills/apply-house-style/SKILL.md` | the consumption task, one copy per runtime, aligned by hand |
| `tests/house-style/cases.json` | fixtures: input, assertions, expected page, grounding |
| `tests/house-style/grade.py` | the three assertions, table output, exit code |
| `scripts/test-house-style.sh` | driver: one Pi run per case, then grade |
| `pi/container/compose.yaml` | adds the `pi-consume` service |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-07-31-okf-consumption-test-design.md` around
lines 120 - 128, Update the Components table entry for the house-style fixtures
to reference cases.json instead of cases.yaml, leaving the surrounding component
paths and descriptions unchanged.

Source: Path instructions

Comment on lines +41 to +42
compiled_pages="$(find okf -name '*.md' ! -name '.okflintrc.json' 2>/dev/null | wc -l | tr -d ' ')"
[[ "${compiled_pages}" -gt 0 ]] || fail_infra "No compiled wiki under okf/ — run a compile first."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

find failure can bypass fail_infra and give the wrong exit code.

set -euo pipefail is active. If okf is missing (or find otherwise fails), pipefail makes the pipeline's exit status equal find's non-zero status, and this un-guarded assignment triggers set -e immediately — before Line 42's explicit -gt 0 check runs. The script then exits with find's exit code (typically 1) instead of the documented 2 for infrastructure trouble, and 2>/dev/null on find means no message reaches stderr at all. The docker compose ... build on Line 62 has the same class of gap: a build failure exits the script via set -e with Docker's own exit code, not the documented 2.

🔧 Proposed fix
-compiled_pages="$(find okf -name '*.md' ! -name '.okflintrc.json' 2>/dev/null | wc -l | tr -d ' ')"
-[[ "${compiled_pages}" -gt 0 ]] || fail_infra "No compiled wiki under okf/ — run a compile first."
+[[ -d okf ]] || fail_infra "No compiled wiki under okf/ — run a compile first."
+compiled_pages="$(find okf -name '*.md' ! -name '.okflintrc.json' | wc -l | tr -d ' ')"
+[[ "${compiled_pages}" -gt 0 ]] || fail_infra "No compiled wiki under okf/ — run a compile first."

As per path instructions, "validate prerequisites early and exit non-zero with a clear message on stderr" and avoid "silently masked failures."

Also applies to: 62-62

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/test-house-style.sh` around lines 41 - 42, Update the compiled_pages
check and the docker compose build flow in the test-house-style script to
explicitly catch failures and route them through fail_infra with clear stderr
messages, preserving the documented infrastructure-failure exit code 2. Ensure
find errors are not suppressed or allowed to terminate via set -e before the
existing zero-page validation, and ensure build failures likewise invoke
fail_infra instead of propagating Docker’s exit status.

Source: Path instructions

Comment on lines +84 to +90
docker compose -f "${compose_file}" run --rm -T pi-consume \
-xt bash \
--provider "${provider}" \
--model "${model}" \
-p "Load the apply-house-style skill: read /home/node/.pi/agent/skills/apply-house-style/SKILL.md, then follow it to apply house style to this paragraph: ${paragraph}" \
</dev/null >"${outdir}/${id}.txt" 2>&1 || true
done

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Per-case run failures are masked, so infra trouble can look like a grading failure.

The || true on Line 89 means any failure of the docker compose run for a single case (container startup failure, gateway timeout, network error) is swallowed. grade.py then reads whatever partial/garbled output landed in the transcript, most likely reports "no parseable json block", and the case counts as a normal grading failure contributing to exit 1. The design doc explicitly states a gateway error belongs to the 2 (infrastructure trouble) category, and that "Infrastructure failure must never read as 'the wiki is bad'." As written, an intermittent gateway error during one case's run reads exactly as a bad wiki.

🔧 Proposed direction
+	run_status=0
 	docker compose -f "${compose_file}" run --rm -T pi-consume \
 		-xt bash \
 		--provider "${provider}" \
 		--model "${model}" \
 		-p "Load the apply-house-style skill: read /home/node/.pi/agent/skills/apply-house-style/SKILL.md, then follow it to apply house style to this paragraph: ${paragraph}" \
-		</dev/null >"${outdir}/${id}.txt" 2>&1 || true
+		</dev/null >"${outdir}/${id}.txt" 2>&1 || run_status=$?
+	[[ ${run_status} -eq 0 ]] || fail_infra "pi-consume run for case '${id}' exited ${run_status} — see ${outdir}/${id}.txt"
 done

As per path instructions, avoid "silently masked failures."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/test-house-style.sh` around lines 84 - 90, Remove the unconditional
failure suppression from the docker compose invocation in the per-case loop of
scripts/test-house-style.sh. Ensure failures from the pi-consume run, including
container, gateway, network, or other infrastructure errors, propagate to the
script’s infrastructure-failure handling and produce the designated exit status
2 instead of being passed to grade.py as malformed output.

Source: Path instructions


## Procedure

1. **Find the rulings.** Search `okf/` for the words and constructions used in

@lars20070 lars20070 Aug 3, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much of style guides are rules for specific words. In these cases, the CLI tools vale (active development, 5.7k stars) or qmd (active development, 28.5k stars) in addition to okf might help the Pi agent.

The harder problem are soft, less specific rules. Even if you can load thousands of lines of md into context, it will most likely lead to context rot i.e. the rules are overlooked and not applied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants