feat(cli): okf project/consume/check/skill, and a generated skill with no path into a checkout

The reading direction existed only for someone standing in a clone. `consume`,
`contract_check` and `skill` moved from `tools/` into the package and are
reachable as `okf consume`, `okf check` and `okf skill`; `okf project` is new
and does the whole thing in one command.

The red measurement: a consumption skill generated from a checkout carried 4
lines naming that checkout by absolute path, 2 of them the commands the skill
tells a reader to run. It now names `okf consume` and `okf check`, and a test
asserts this repository appears in it nowhere, with a known-positive so the
zero is a measurement rather than a search that could not find.

The `tools/` files stay as ALIASES, not re-exports: a re-export binds copies of
the names into a second module object, so a caller patching one patches a
binding the implementation never reads. Two tests that monkeypatch okf_consume
went green again only under the alias. Every published reproduction block runs
unchanged.

The template and docs/consumption-contract.md (the section 7.4 known-positive)
are force-included into the wheel from the file they are authored in, so both
travel with the commands that cannot run without them and there is still one
authored copy of each.

Step 0, before any of it: okf build's default gained Arm E (--table-grid),
with --no-table-grid as its opt-out. The default moved to D plus F earlier the
same day on Arm F's published 5 of 12 -- a figure measured with Arm E ON.
Without it the fold has no joined table to fold, and the shipped default scored
2 of 12 with docx 0 of 3. Measured on the operator's folder: 30 md / 15
concepts on the new default against 43 / 28 without Arm E.

Install measurement from a fresh uv tool install, empty folder, this repository
nowhere on PYTHONPATH: 5 documents in, 15 concepts out, 0 references to tools/
in the generated skill, okf check conformant (15 rules, 0 findings).

Deviation stated rather than hidden: the order asked that
tests/test_okf_consume.py be left untouched. Two assertions in it read a PATH,
which is the one thing this work changes. Both were moved and the second made
stronger -- it now asserts every command the README recipe names is a
subcommand the CLI registers, which a file existing on disk never proved.

Suite 1414 -> 1427. ruff clean, mypy --strict clean over 21 files.
Record: docs/2026-09-08-o5-okf-project.md

Co-Authored-By: Claude <claude-opus-5>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-08 21:47:38 +02:00
commit f6fea13299
21 changed files with 4017 additions and 2833 deletions

View file

@ -89,17 +89,47 @@ def test_a_skill_generated_for_one_bundle_does_not_name_the_other(tmp_path: Path
@pytest.mark.parametrize("bundle", BUNDLES, ids=lambda path: path.name)
def test_the_generated_commands_are_absolute_so_a_caller_outside_this_repo_can_run_them(
bundle: Path, tmp_path: Path
) -> None:
# The skill is copied into someone else's `.claude/skills/`, where a
# relative `tools/okf_consume.py` resolves to nothing.
def test_the_generated_commands_name_this_repository_nowhere(bundle: Path, tmp_path: Path) -> None:
"""O5's red measurement, made a test: no path into a checkout, anywhere.
**This assertion REPLACES its own opposite, and the replacement is the
point.** The test here until 2026-09-08 required the emitted commands to be
ABSOLUTE, on the reasoning that a skill copied into someone else's
`.claude/skills/` cannot resolve a relative `tools/okf_consume.py`. Both
halves of that were true and the conclusion was still wrong: an absolute
path into THIS clone is not portable either, it is merely portable-looking.
Measured before the move, a skill generated from a checkout carried four
lines naming this checkout by absolute path, two of them the commands a
reader is told to run -- so the skill could not be moved, shared, or run by
anyone without that clone at that exact path.
It also caught nothing by then: the regex it looped over matched zero lines
once the commands stopped being `python3 <file>.py`, so it was green over
an empty set. The assertion below has a denominator that cannot go to zero.
"""
written = _generate(bundle, tmp_path / bundle.name)
text = written.read_text(encoding="utf-8")
for command in re.findall(r"^\S*python3? (\S+\.py)", text, flags=re.MULTILINE):
assert Path(command).is_absolute(), command
assert Path(command).is_file(), command
# The BUNDLE root is the one absolute path that belongs here: it points at
# the caller's data. These fixtures happen to live inside this repository,
# so it is removed before the search -- otherwise the search would find the
# repository root inside the one path allowed to carry it.
assert str(bundle.resolve()) in text
rest = text.replace(str(bundle.resolve()), "<BUNDLE>")
# The known-positive for the search: the string it hunts for DOES occur in
# the environment running it, so the zero below means the generator kept it
# out rather than the search being unable to find it.
assert str(PROJECT_ROOT) in str(Path(__file__).resolve())
assert str(PROJECT_ROOT) not in rest
# No file under `tools/` at all, and not only the two commands: the
# attribution lines named the generator by path too, which is a file the
# reader does not have either.
assert "tools/" not in rest
# And what it names instead: commands resolved by PATH after an install.
assert "\nokf consume \\\n" in text
assert "\nokf check \\\n" in text
@pytest.mark.parametrize("bundle", BUNDLES, ids=lambda path: path.name)