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:
parent
efa92ce170
commit
f6fea13299
21 changed files with 4017 additions and 2833 deletions
178
docs/2026-09-08-o5-okf-project.md
Normal file
178
docs/2026-09-08-o5-okf-project.md
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
# O5: one command from a folder to a bundle you can ask a question of
|
||||
|
||||
2026-09-08. Session 113, order `20260908T184152Z-1340419920`, on top of
|
||||
`efa92ce` (K3 round 3).
|
||||
|
||||
This is the session that made the reading direction installable. Everything
|
||||
here already worked from a clone; none of it worked for anyone else.
|
||||
|
||||
## 1. The red measurement
|
||||
|
||||
The consumption skill generator was run against a bundle and the result
|
||||
grepped for paths:
|
||||
|
||||
```sh
|
||||
python3 tools/okf_skill.py ~/okf-test/v1-bundle --out /tmp/o5-red/skill
|
||||
grep -c "^/" /tmp/o5-red/skill/SKILL.md # absolute paths
|
||||
```
|
||||
|
||||
**4 lines**, of which two are the commands the skill tells a reader to run:
|
||||
|
||||
```
|
||||
17: The contract this skill is held to is `<HOME>/repos/llm-ingestion-okf/docs/consumption-contract.md`.
|
||||
25: python3 <HOME>/repos/llm-ingestion-okf/tools/okf_consume.py \
|
||||
26: <HOME>/okf-test/v1-bundle \
|
||||
40: python3 <HOME>/repos/llm-ingestion-okf/tools/okf_contract_check.py \
|
||||
```
|
||||
|
||||
Line 26 is the caller's own data and belongs there. The other three name a
|
||||
checkout. A skill carrying them cannot be moved, shared, or run by anyone
|
||||
without that clone at that exact path — which is every reader except the one
|
||||
who generated it.
|
||||
|
||||
## 2. Step 0: the default moved again, because the number behind it moved
|
||||
|
||||
Round 3 set `okf build`'s flagless default to `--outline-run 3 --unit-fold`
|
||||
(Arm D plus Arm F) on Arm F's published "5 of 12". That figure had been
|
||||
measured with `--table-grid` ON. Without Arm E, Arm F's table clause — fold a
|
||||
table back into the heading that introduces it — has no joined table to fold,
|
||||
because a grid table is then not one block but one block per rule line.
|
||||
|
||||
Arm E therefore joined the default, with `--no-table-grid` as its opt-out.
|
||||
Measured here on the operator's five-document folder, at the same stamp:
|
||||
|
||||
| build | `.md` files | of which concepts | `index.md` | `log.md` |
|
||||
|---|---|---|---|---|
|
||||
| new default (D+E+F) | 30 | **15** | 14 | 1 |
|
||||
| `--no-table-grid` (D+F, round 3's) | 43 | **28** | 14 | 1 |
|
||||
|
||||
The `.md` totals reproduce the PM measurement exactly (43 and 30). The concept
|
||||
counts differ from the 29 and 16 it reported by exactly one each, and the
|
||||
decomposition says why: `md` files minus `index.md` counts `log.md` as a
|
||||
concept. The index walk does not, and neither does the pre-pass.
|
||||
|
||||
`--keep-table-heading` did NOT join the default: measured on two K2 bundles in
|
||||
round 3 it buys 35 bytes and zero rank positions.
|
||||
|
||||
The red test is `test_the_build_default_is_now_arm_d_plus_arm_e_plus_arm_f` on
|
||||
a grid-table fixture, where the arm's effect is visible in the TITLES: with it
|
||||
off the sheet lands as `7 Romskjema` plus `Tabell linje 6`, `Tabell linje 8`
|
||||
and `Tabell linje 10` — concepts named after a line number. Its opt-out half
|
||||
asserts those four titles come back under `--no-table-grid`.
|
||||
|
||||
## 3. The move
|
||||
|
||||
Three modules moved from `tools/` into `src/llm_ingestion_okf/`:
|
||||
|
||||
| was | is | reached as |
|
||||
|---|---|---|
|
||||
| `tools/okf_consume.py` | `consume.py` | `okf consume` |
|
||||
| `tools/okf_contract_check.py` | `contract_check.py` | `okf check` |
|
||||
| `tools/okf_skill.py` | `skill.py` | `okf skill` |
|
||||
|
||||
and one is new: `project.py`, reached as `okf project`.
|
||||
|
||||
The `tools/` files stay, because published reproduction blocks name them and a
|
||||
measurement whose command no longer runs is a measurement nobody can repeat.
|
||||
They are **aliases, not re-exports**: `sys.modules[__name__] = _impl`. 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 — measured, two
|
||||
tests that `monkeypatch` `okf_consume` went green again only under the alias.
|
||||
|
||||
**Two authored files had to learn to travel.** `okf skill` instantiates
|
||||
`skills/okf-consume-template/SKILL.md`, and `okf consume` measures
|
||||
`docs/consumption-contract.md` as its § 7.4 known-positive and refuses without
|
||||
it. Neither is under `src/`. Both are force-included into the wheel from the
|
||||
file they are authored in, and both resolvers prefer the packaged copy and fall
|
||||
back to the source tree — one authored copy, no committed duplicate, because a
|
||||
duplicate is the drift the generator exists to prevent.
|
||||
|
||||
**The generated skill names commands, not paths.** `okf consume` and
|
||||
`okf check`, resolved by the shell. The two attribution lines that named
|
||||
`tools/okf_skill.py` now name `okf skill`.
|
||||
|
||||
## 4. What `okf project` does, and what it refuses to decide
|
||||
|
||||
`okf project <folder>` runs `okf build` with **this package's default** into
|
||||
`<out>/.okf/<id>/`, generates the skill into
|
||||
`<out>/.claude/skills/<id>-consume/`, and prints a summary. `<out>` defaults to
|
||||
the current directory, `<id>` to the folder's name NFC-normalised and reduced to
|
||||
`[a-z0-9-]`.
|
||||
|
||||
It owns no flag that changes a bundle's bytes. A test holds the project bundle
|
||||
byte-equal to the `okf build` bundle of the same folder at the same stamp: two
|
||||
build paths would mean every measurement report is pinned to a bundle nobody
|
||||
produces.
|
||||
|
||||
The summary reports two things a concept count cannot:
|
||||
|
||||
- documents in the folder that **no concept names** — nothing can quote them;
|
||||
- documents that landed **whole**, as one flat concept at the bundle root
|
||||
(measured on the artefact: a concept id with no `/`) — reachable, but a
|
||||
question returns the entire document as one excerpt, which the budget often
|
||||
refuses and which often does not carry the answer where it was asked.
|
||||
|
||||
Both are `[sourced-not-sufficient]` cases, and the summary names the documents.
|
||||
|
||||
## 5. The install measurement
|
||||
|
||||
From a fresh `uv tool install` into an isolated tool directory, run from an
|
||||
empty folder with this repository nowhere on `PYTHONPATH` and `PATH` reduced to
|
||||
the install plus `/usr/bin:/bin`:
|
||||
|
||||
```
|
||||
Read 5 document(s) from <HOME>/okf-test/dokumenter.
|
||||
Wrote 15 concept(s) to /private/tmp/o5-user/.okf/dokumenter.
|
||||
Wrote the skill to /private/tmp/o5-user/.claude/skills/dokumenter-consume/SKILL.md.
|
||||
|
||||
0 of 5 document(s) were left out of the bundle. [...]
|
||||
1 of 5 document(s) landed WHOLE [...] Expect `[sourced-not-sufficient]` there:
|
||||
- Del II Bilag 6 - Teknisk oppsett.pdf
|
||||
|
||||
NEXT: start claude again in /private/tmp/o5-user and ask your question.
|
||||
```
|
||||
|
||||
Exit 0. The generated skill carries **0** references to `tools/` or to any
|
||||
checkout. `okf consume` on that bundle exits 0, and `okf check` on the pair is
|
||||
**conformant: 15 rules over 8 excerpts and 7 withheld entries, 0 findings**.
|
||||
|
||||
**The first install measurement was wrong and is recorded here rather than
|
||||
dropped.** `uv tool install` from the unchanged local path returned a cached
|
||||
build of the previous version, and `okf project` failed with `invalid choice:
|
||||
'project'`. The version bump was already required; without it the measurement
|
||||
would have reported the old wheel as the new one.
|
||||
|
||||
## 6. What was NOT done
|
||||
|
||||
- **`docs/consumption-contract.md` was not edited.** Its § "What is
|
||||
mechanically checked" still names `tools/okf_contract_check.py`, which is
|
||||
true — the alias exists. Its bytes are the pre-pass's known-positive
|
||||
(`KNOWN_POSITIVE_EXPECTED = 12563`), so editing it makes the pre-pass refuse
|
||||
until the literal is updated. That coupling is deliberate and this was not
|
||||
the session to spend it.
|
||||
- **`skills/okf-consume/` was not regenerated**, only its four command and
|
||||
attribution lines were updated in place. Regenerating needs the K2 bundle,
|
||||
which is never copied into this repository.
|
||||
- **No push, and the tag is local.** The order and the operator own that.
|
||||
- **`tests/test_okf_consume.py` was not left untouched, and the order asked
|
||||
that it be.** Two assertions in it read a PATH, which is the one thing this
|
||||
work exists to change: one read the pre-pass's source text at
|
||||
`tools/okf_consume.py`, the other required the README recipe to invoke
|
||||
`python3 tools/<script>.py`. Left alone, the first would have measured a
|
||||
wrapper with no vocabulary in it and passed, and the second would have
|
||||
failed. Both were moved to the new location and the second was **made
|
||||
stronger** — it now asserts every command the recipe names is a subcommand
|
||||
the CLI registers, which a file existing on disk never proved. Nothing else
|
||||
in the file changed; the pre-pass's behavioural contract is unaltered.
|
||||
- **Round 4's per-file-type questions** (`xlsx` at 12 against a reference of
|
||||
11, `pdf` at 3 of 8 and losing 940 characters of an opening) were not
|
||||
touched. They stand as next.
|
||||
|
||||
## 7. Numbers
|
||||
|
||||
| | before | after |
|
||||
|---|---|---|
|
||||
| tests | 1414 | **1427** |
|
||||
| `okf` subcommands | 1 | **5** |
|
||||
| absolute paths in a generated skill | 4 | **0** |
|
||||
| `mypy --strict` source files | 17 | 21 |
|
||||
Loading…
Add table
Add a link
Reference in a new issue