feat(readme,skill,cli): the first screen an agent reads, three modes, and one flag that made two builds

`okf project` built a bundle two rules behind `okf build`. `cli.build`'s
signature defaulted `keep_table_heading` and `sheet_section_rows` to `False`
while argparse defaulted both to `True`, and `project.create` calls `build()`
as a function. Measured on a five-document folder: 15 concepts / 30 files
where `okf build` wrote 26 / 52, the whole difference in the priced sheet --
the document a question about price has to reach. The invariant test could not
see it: it compared `project.create` against the same function, and its two
fixture documents had neither a table nor a sheet. Both gaps are tests now,
and the two paths are byte-equal on that folder (`diff -rq`, 0 differences).

README opens with what / one install line / two commands / the three shapes of
request; the phase-status paragraph moved down, nothing deleted. One tag is
pinned everywhere: README pinned v0.4.0 on its install lines and v0.6.0 below,
llms.txt pinned v0.4.0, so an agent reading from the top installed a tag
without `okf project`.

The skill states three modes -- question, hypothesis (per premise, `confirmed`
/ `refuted` / `undecidable-from-bundle`), and a task producing a document
(source per claim in the artefact, an ungrounded paragraph written and marked
rather than dropped, the cut declared inside the document). The five markings
are untouched.

Generated skills state relative paths in the project layout: `okf consume
.okf/<id>` and `okf check --skill .claude/skills/<id>-consume/SKILL.md`,
runnable from where `okf project` tells the reader to start `claude`. Two
absolute paths to zero, measured with a query shown capable of finding first --
O5's published "4 -> 0" used `grep -c "^/"` against paths indented by two
spaces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-09 18:12:02 +02:00
commit 1260fac154
12 changed files with 532 additions and 43 deletions

View file

@ -11,6 +11,7 @@ from __future__ import annotations
import json
import re
import shutil
import subprocess
import sys
from pathlib import Path
@ -252,3 +253,107 @@ def test_the_generated_skill_carries_a_payload_its_own_bundle_produced(tmp_path:
assert payload["bundle"]["bundle_id"] == okf_consume.root_bundle_id_of(PROVENANCE)
assert payload["bundle"]["ref"] == okf_consume.bundle_ref(PROVENANCE)
assert okf_contract_check.check(written.read_text(encoding="utf-8"), payload).findings == ()
# --- The three modes, and the paths a reader can actually run (O6) -------------
#: A path is absolute here if it starts a line or follows whitespace. The
#: measurement O5 published used `grep -c "^/"`, which cannot match a path
#: indented by two spaces -- which is the form the generator writes. Every
#: assertion below runs this pattern against a known-positive first.
ABSOLUTE = re.compile(r"(?:^|[ \t])(/[A-Za-z])", re.MULTILINE)
def _project_layout(tmp_path: Path, bundle: Path) -> tuple[Path, Path, str]:
"""Generate into the layout `okf project` writes, and return the pieces."""
root = tmp_path / "prosjekt"
identity = okf_consume.root_bundle_id_of(bundle)
inside = root / ".okf" / identity
inside.parent.mkdir(parents=True)
shutil.copytree(bundle, inside)
out = root / ".claude" / "skills" / f"{identity}-consume"
written = _generate(inside, out)
return root, written, identity
def test_the_pattern_that_looks_for_absolute_paths_can_find_one(tmp_path: Path) -> None:
"""Face 4 first: a query is shown capable of finding before a zero is read.
O5's own published figure ("4 absolute paths -> 0") was measured with
`grep -c "^/"` against a file whose paths are indented by two spaces, so the
query could not have found one either way. This is that control.
"""
known_positive = "prose with no path\n /Users/x/bundle\nokf consume /Users/x/other\n"
assert len(ABSOLUTE.findall(known_positive)) == 2
assert len(re.findall(r"^/", known_positive, re.MULTILINE)) == 0
@pytest.mark.parametrize("bundle", BUNDLES, ids=lambda path: path.name)
def test_a_project_skill_names_its_bundle_and_itself_relative_to_the_project_root(
bundle: Path, tmp_path: Path
) -> None:
"""The commands have to be runnable where `claude` is started: the project root.
`okf project`'s own closing line tells the reader to start `claude` in the
project root, so the skill's commands are read from there. An absolute path
makes the skill unmovable, unshareable, and wrong for anyone whose clone
lives elsewhere.
"""
root, written, identity = _project_layout(tmp_path, bundle)
text = written.read_text(encoding="utf-8")
assert f".okf/{identity}" in text
assert f".claude/skills/{identity}-consume/SKILL.md" in text
assert str(root) not in text
# Every absolute path left is a scratch write target, not a path into the
# machine the skill was generated on. `/tmp/payload.json` is where the
# pre-pass puts its payload; naming it relative would litter the project.
leftover = [line for line in text.splitlines() if ABSOLUTE.search(line) and "/tmp/" not in line]
assert leftover == []
@pytest.mark.parametrize("bundle", BUNDLES, ids=lambda path: path.name)
def test_the_generated_skill_declares_the_hypothesis_mode(bundle: Path, tmp_path: Path) -> None:
"""A hypothesis is answered per premise, not as one verdict over the whole.
The three verdicts are literals, like the five markings: a reader that
invents a fourth ("partly confirmed") has left the contract.
"""
_, written, _ = _project_layout(tmp_path, bundle)
text = written.read_text(encoding="utf-8")
assert "## Modes" in text
for literal in ("`confirmed`", "`refuted`", "`undecidable-from-bundle`"):
assert literal in text
assert "per premise" in text.lower()
assert "[sourced-not-sufficient]" in text
@pytest.mark.parametrize("bundle", BUNDLES, ids=lambda path: path.name)
def test_the_generated_skill_declares_the_document_task_mode(bundle: Path, tmp_path: Path) -> None:
"""A task whose answer is a document carries the cut INTO the document.
Dropping an ungrounded paragraph silently is the denominator failure with
a nicer surface: the reader cannot see what the bundle did not cover.
"""
_, written, _ = _project_layout(tmp_path, bundle)
text = written.read_text(encoding="utf-8")
assert "## Modes" in text
assert "Task" in text
for token in ("considered", "withheld", "delivered"):
assert token in text
def test_the_five_markings_are_untouched_by_the_modes(tmp_path: Path) -> None:
"""The modes add no sixth marking. § 4.3 makes an undeclared extension the defect."""
_, written, _ = _project_layout(tmp_path, GOLDEN)
text = written.read_text(encoding="utf-8")
for literal in okf_contract_check.REQUIRED_MARKINGS:
assert literal in text
def test_a_project_skill_still_passes_the_contract_checker(tmp_path: Path) -> None:
"""Relative paths and a new section must not cost conformance."""
_, written, _ = _project_layout(tmp_path, GOLDEN)
payload = okf_consume.build_payload(GOLDEN, question="hva er kravet til pris?")
report = okf_contract_check.check(written.read_text(encoding="utf-8"), payload)
assert report.findings == ()