docs(project): the server is the standard way in, the skill the supplement

`okf project`'s closing text and the README's first screen now say it in
that order: register `okf mcp --root` once, and it answers from every
project and reaches subagents; the skill beside the bundle is for someone
who would rather register nothing; neither is made again when a bundle is
rebuilt. Two tests hold the order in both places. The README's stale
`<id>-consume` skill path is corrected to `okf-consume-any`.

v1.1 order F, part F3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-21 10:36:52 +02:00
commit 570496470b
5 changed files with 56 additions and 19 deletions

View file

@ -1540,6 +1540,12 @@ R761 **8** (S1-S6 + KP + KN), vegnormal **32** questions / **43**
of them, so over a folder it is REFUSED by name with exit 2 of them, so over a folder it is REFUSED by name with exit 2
(`consume.FOLDER_FLAGS` is the allowlist), never dropped; `--bundle-id` on a (`consume.FOLDER_FLAGS` is the allowlist), never dropped; `--bundle-id` on a
bundle path is refused the same way. A bundle path reads exactly as before. bundle path is refused the same way. A bundle path reads exactly as before.
- **THE SERVER IS THE STANDARD WAY IN AND THE SKILL THE SUPPLEMENT (v1.1 F3,
operator 2026-09-21).** `okf project`'s closing text and README's first
screen say it in that order: register `okf mcp --root` once (every project,
subagents too); the skill beside the bundle is for someone who would rather
register nothing; neither is made again when a bundle is rebuilt. Two tests
hold the ORDER, not just the presence, in both places.
- **`okf card <bundle>` and the generic skill are the one-to-many form.** The card is one bundle's identity, concept count, - **`okf card <bundle>` and the generic skill are the one-to-many form.** The card is one bundle's identity, concept count,
conditional-field counts and whole-bundle cost as JSON, **DERIVED on every run conditional-field counts and whole-bundle cost as JSON, **DERIVED on every run
and never written into the bundle** -- storing it would move the bytes of all and never written into the bundle** -- storing it would move the bytes of all

View file

@ -16,22 +16,23 @@ uv tool install "llm-ingestion-okf[extract] @ git+https://git.fromaitochitta.com
## Use it ## Use it
```sh ```sh
okf project ~/my-documents # folder in: bundle + Claude Code skill, in this directory okf project ~/my-documents # folder in: a bundle under ./.okf, and a skill beside it
claude # start Claude Code here
``` ```
**Or connect every bundle at once.** Register the server once, on user scope, **The standard way in is the server.** Register it once, on user scope,
and every project you open can ask any bundle under that directory — no skill pointing at the directory that holds your projects. Every project you open can
to install per project, and nothing to regenerate when a bundle is rebuilt. then ask any bundle under it, subagents included — they inherit MCP tools and
You run this line; `okf` never starts Claude Code: not skills — and a bundle added or rebuilt is seen by the next call. You run
this line; `okf` never starts Claude Code:
```sh ```sh
claude mcp add --scope user okf -- okf mcp --root ~/okf claude mcp add --scope user okf -- okf mcp --root ~/projects
``` ```
Subagents inherit MCP tools and do not inherit skills, so the server is also **The skill is the supplement**, for when you would rather register nothing:
the only way the same working method reaches an arm running below the main `okf project` writes it beside the bundle, so starting `claude` in that
thread. directory is enough. It reads every bundle under `./.okf` with the same code the
server runs. Neither has to be made again when a bundle is rebuilt.
Then ask in plain language. Three shapes of request work, and the skill states Then ask in plain language. Three shapes of request work, and the skill states
the rules for each: the rules for each:
@ -1236,7 +1237,7 @@ claude
``` ```
`okf project` writes the bundle to `.okf/<id>/` and a skill to `okf project` writes the bundle to `.okf/<id>/` and a skill to
`.claude/skills/<id>-consume/` in the **current directory**, then prints what it `.claude/skills/okf-consume-any/` in the **current directory**, then prints what it
read, what it wrote, and which documents a question cannot reach. Start `claude` read, what it wrote, and which documents a question cannot reach. Start `claude`
in that directory and ask in plain language; the generated skill runs the in that directory and ask in plain language; the generated skill runs the
pre-pass and the contract check itself and marks every claim with its source. pre-pass and the contract check itself and marks every claim with its source.

View file

@ -178,16 +178,19 @@ def summarise(
lines.extend( lines.extend(
[ [
"", "",
f"NEXT: start claude again in {out} and ask your question.", "NEXT -- the standard way in is the server. Register it ONCE; you run",
"", f"this line, {CLI_ID} never starts claude:",
"To reach this bundle -- and every other bundle under one directory --",
"from ANY project, register the server once instead. You run this line;",
f"{CLI_ID} never starts claude:",
"", "",
f" claude mcp add --scope user okf -- okf mcp --root {out.parent}", f" claude mcp add --scope user okf -- okf mcp --root {out.parent}",
"", "",
"Then no skill has to be installed per project, and nothing has to be", "It then answers from every project, reaches subagents too, and sees",
"regenerated when a bundle is rebuilt.", "every bundle under that directory -- one added or rebuilt later included.",
"",
"The skill written here is the supplement, for when you would rather",
f"register nothing: start claude in {out} and ask. It reads every",
f"bundle under {out / BUNDLE_DIR} with the same code.",
"",
"Neither has to be made again when a bundle is rebuilt.",
] ]
) )
return "\n".join(lines) return "\n".join(lines)

View file

@ -134,6 +134,33 @@ def test_the_project_summary_says_what_to_do_next_with_the_server(tmp_path: Path
assert "claude mcp add --scope user okf -- okf mcp --root" in summary assert "claude mcp add --scope user okf -- okf mcp --root" in summary
def test_the_project_summary_puts_the_server_first_and_the_skill_second(tmp_path: Path) -> None:
"""v1.1 F3: the server is the standard way in, the skill the supplement."""
folder = tmp_path / "Dokumenter"
folder.mkdir()
(folder / "krav.md").write_text(
"## 4 Grunnforhold\n\nGrunnen er morene over berg.\n", encoding="utf-8", newline=""
)
out = tmp_path / "project"
_, _, summary = project.create(folder, out=out)
server = summary.index("claude mcp add --scope user okf -- okf mcp --root")
skill = summary.index(f"start claude in {out}")
assert server < skill
assert "standard" in summary[:server]
assert "supplement" in summary[server:]
assert "Neither has to be made again when a bundle is rebuilt" in summary
def test_the_readme_first_screen_puts_the_server_first_and_the_skill_second() -> None:
readme = (PROJECT_ROOT / "README.md").read_text(encoding="utf-8")
first_screen = readme.split("## Known limitations", 1)[0]
server = first_screen.index("claude mcp add --scope user okf -- okf mcp --root")
skill = first_screen.index("The skill is the supplement")
assert server < skill
assert "standard way in" in first_screen[:server]
assert "Neither has to be made again" in first_screen
def test_a_project_bundle_is_where_a_root_server_finds_it(tmp_path: Path) -> None: def test_a_project_bundle_is_where_a_root_server_finds_it(tmp_path: Path) -> None:
"""`--root <the directory holding projects>` must reach `<project>/.okf/<id>`. """`--root <the directory holding projects>` must reach `<project>/.okf/<id>`.

View file

@ -127,7 +127,7 @@ def test_the_summary_names_the_documents_that_landed_whole(folder: Path, tmp_pat
assert "notat.md" in summary assert "notat.md" in summary
assert "krav.md" not in summary assert "krav.md" not in summary
assert "[sourced-not-sufficient]" in summary assert "[sourced-not-sufficient]" in summary
assert f"NEXT: start claude again in {out}" in summary assert f"start claude in {out}" in summary
def test_a_document_that_is_in_the_bundle_is_not_reported_as_missing( def test_a_document_that_is_in_the_bundle_is_not_reported_as_missing(