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

@ -1182,20 +1182,39 @@ def test_the_readme_consume_section_states_the_rule_count_the_code_emits() -> No
def test_the_readme_recipe_names_only_commands_this_repository_ships() -> None:
# Every command in the "Consume in Claude Code" section was run in the
# session that wrote it. This test cannot re-run them; what it can hold is
# that each script the recipe invokes still exists under the path it names.
"""Every command the recipe invokes must exist. What "exist" means MOVED.
Until 2026-09-08 (O5) the recipe told a reader to run `python3
tools/<script>.py`, and this test held that each of those three files was
on disk. The recipe now names `okf` subcommands, because a reader who
installed this library has no `tools/` directory at all -- so the check
that means the same thing is that each subcommand is one the CLI
registers. That is a stronger claim than a file existing: a script can be
present and unreachable from the installed command, which is exactly the
defect O5 fixed.
The section's own heading is the fixed point, not the form of the commands
inside it.
"""
readme = (PROJECT_ROOT / "README.md").read_text(encoding="utf-8")
recipe = readme.split("## Consume in Claude Code", 1)[1].split("\n## ", 1)[0]
scripts = set(re.findall(r"python3 (tools/\S+\.py)", recipe))
assert scripts == {
"tools/okf_skill.py",
"tools/okf_consume.py",
"tools/okf_contract_check.py",
}, scripts
for script in scripts:
assert (PROJECT_ROOT / script).is_file(), script
assert "okf build " in recipe
# No route back to the old form: a `python3 tools/...` line in the recipe
# is a line a reader without this repository cannot run.
assert re.findall(r"python3 (tools/\S+\.py)", recipe) == []
invoked = set(re.findall(r"^okf ([a-z]+)", recipe, flags=re.MULTILINE))
assert invoked == {"build", "consume", "check", "skill", "project"}, invoked
listed = subprocess.run(
[sys.executable, "-m", "llm_ingestion_okf.cli", "--help"],
capture_output=True,
text=True,
cwd=PROJECT_ROOT,
)
assert listed.returncode == 0
for command in invoked:
assert command in listed.stdout, command
# --- Step 11: the measurement scorer -----------------------------------------
@ -1340,7 +1359,11 @@ def test_every_vocabulary_member_is_long_enough_to_ever_match() -> None:
def test_the_vocabulary_is_one_list_and_names_no_corpus_document() -> None:
source = (PROJECT_ROOT / "tools" / "okf_consume.py").read_text(encoding="utf-8")
# The PATH moved on 2026-09-08 (O5) and nothing else in this test did. The
# pre-pass is `src/llm_ingestion_okf/consume.py` now; `tools/okf_consume.py`
# is an alias to it, so reading the old path would have measured a wrapper
# and passed on a file with no vocabulary in it at all.
source = (PROJECT_ROOT / "src" / "llm_ingestion_okf" / "consume.py").read_text(encoding="utf-8")
assert source.count("COST_VOCABULARY = (") == 1
leak = re.compile(r"del-ii-bilag|del-i-vedlegg|prisskjema|prissammenstilling|stange", re.I)
assert leak.findall(source) == []