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:
parent
956714594d
commit
1260fac154
12 changed files with 532 additions and 43 deletions
|
|
@ -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 == ()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue