okf-pro 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.okf/capabilities/checks.md +52 -0
  3. data/.okf/capabilities/index.md +12 -0
  4. data/.okf/capabilities/verbs.md +70 -0
  5. data/.okf/contract/containment-directions.md +3 -0
  6. data/.okf/contract/exit-codes.md +5 -2
  7. data/.okf/contract/gates-only-at-the-hook-door.md +3 -0
  8. data/.okf/contract/silent-skips.md +3 -0
  9. data/.okf/contract/telemetry-does-not-lie.md +3 -0
  10. data/.okf/contract/the-contract.md +3 -0
  11. data/.okf/design/a-comment-is-not-an-implementation.md +3 -0
  12. data/.okf/design/a-rule-you-can-walk-past.md +3 -0
  13. data/.okf/design/derivation-that-writes.md +3 -0
  14. data/.okf/design/failure-modes.md +3 -0
  15. data/.okf/design/lineage.md +3 -0
  16. data/.okf/design/structure-laws.md +3 -0
  17. data/.okf/design/the-residue.md +3 -0
  18. data/.okf/design/three-laws.md +3 -0
  19. data/.okf/design/three-pillars.md +3 -0
  20. data/.okf/index.md +23 -14
  21. data/.okf/log.md +20 -0
  22. data/.okf/scaffold/collisions-and-refusals.md +3 -0
  23. data/.okf/scaffold/no-date-ships.md +3 -0
  24. data/.okf/scaffold/ownership-not-subject.md +3 -0
  25. data/.okf/scaffold/the-adopters-manual.md +3 -0
  26. data/.okf/seam/bundler-scoping.md +4 -1
  27. data/.okf/seam/identity-not-existence.md +3 -0
  28. data/.okf/seam/the-wrapper.md +3 -0
  29. data/.okf/seam/three-fail-opens.md +3 -0
  30. data/.okf/structure/doors.md +80 -0
  31. data/.okf/structure/index.md +18 -0
  32. data/.okf/structure/reading-the-world.md +65 -0
  33. data/.okf/structure/the-gates.md +64 -0
  34. data/.okf/structure/the-guards.md +43 -0
  35. data/.okf/structure/the-recorder.md +49 -0
  36. data/.okf/structure/the-scaffold.md +42 -0
  37. data/.okf/structure/the-writers.md +60 -0
  38. data/.okf/testing/adding-a-verb.md +63 -0
  39. data/.okf/testing/drills-over-units.md +3 -0
  40. data/.okf/testing/fixture-is-a-client.md +3 -0
  41. data/.okf/testing/index.md +1 -0
  42. data/.okf/trust/read-owed-rule.md +3 -0
  43. data/.okf/trust/scalar-verified.md +3 -0
  44. data/CHANGELOG.md +68 -1
  45. data/README.md +5 -1
  46. data/lib/okf/pro/cli.rb +1 -1
  47. data/lib/okf/pro/version.rb +1 -1
  48. metadata +19 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89c3d1c35f2504c135c9875a57050aac3288610a5bb812b067faf1284905ab5b
4
- data.tar.gz: 8d25d56ce3601cc0fd16e72b6ad43c358b69ee859baac16d0abcb855171cbc64
3
+ metadata.gz: a908d8394cc7ea78e25a744f080813d34eb656b77274e3bb8db4397d222c3950
4
+ data.tar.gz: 687919c65db7bf0d42bc48f475cd88f636c01aaf87f49443d92c6de0fd434fb9
5
5
  SHA512:
6
- metadata.gz: 4f17f3f09f2aac6ceb618bcfb0e871c51cf55cc62b701b55b57154ff10b27c02fa84bdc09cc6b38759113f6536afd7246c74e2a3b4233c202c69b59df6a563b1
7
- data.tar.gz: d9487c17f613c163b49283b6a626a3e74f0133eafcf1231bd9266d21237573d0d8f799fa4a637da6bda9379e2013d00454979d4be4acea908dd57555072fd774
6
+ metadata.gz: '0589ad1bd8edb04b767044692d20b271614fe37d60f598fe1b51ba436384142049da27d94d8281df54b0a485b658751e7ef2fe3d4c84a292b4593a61502d6c87'
7
+ data.tar.gz: 7afe1c08143bf0524f9deb5ce47e827276b59aa805f62cb65b2be5e5414bb542b1208a4b2a59826443ab1fde4e5680066fd047e6236007afc8b3e1b0e04e7ae3
@@ -0,0 +1,52 @@
1
+ ---
2
+ type: Capability
3
+ title: The checks — nine hook names, and the event each one rides
4
+ description: What `okf pro hook <name>` accepts, which Claude Code event the scaffold wires it to, what it asks, and what it does when it cannot answer.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-19
8
+ ---
9
+
10
+ # The catalogue
11
+
12
+ `OKF::Pro::CLI::HOOK_NAMES` is the source of truth, and it is strictly narrower
13
+ than `NAMES`: the hook door accepts these nine and nothing else, because `run`
14
+ dispatches the CI verbs off the same first argv element and a `settings.json`
15
+ typo spelling `hook audit` would otherwise install a gate that reads no stdin,
16
+ never blocks, and reports "clean." That failure is [gates-only-at-the-hook-door](/contract/gates-only-at-the-hook-door.md).
17
+
18
+ | name | event | asks |
19
+ |---|---|---|
20
+ | `guard-verified` | `PreToolUse` on `Edit\|Write\|MultiEdit` | is this edit setting `verified:` on something the owner has not read? |
21
+ | `journal-guard` | `PreToolUse` on `Edit\|Write\|MultiEdit` | is this rewriting a past journal day? |
22
+ | `shell-guard` | `PreToolUse` on `Bash` | is this command about to write into the bundle behind the other two? |
23
+ | `post-edit` | `PostToolUse` on `Edit\|Write\|MultiEdit` | the three below, in one process, sharing one bundle read |
24
+ | `check-okf` | (composed into `post-edit`) | does the bundle still validate and lint? |
25
+ | `cap-check` | (composed into `post-edit`) | is In flight over the cap? |
26
+ | `reconcile-search` | (composed into `post-edit`) | what already says this? (**Law 1**) |
27
+ | `stop-gate` | `Stop` | is the day's snapshot line written, and does it agree with the board? (**Law 2**) |
28
+ | `session-context` | `SessionStart` | the banner: what is on the board, as the session opens |
29
+
30
+ `check-okf`, `cap-check` and `reconcile-search` remain addressable on their own
31
+ — they are in `CHECKS` and the door accepts them — but the scaffold wires
32
+ `post-edit` instead, because the bundle read is the expensive part and three
33
+ separate invocations paid for it three times over.
34
+
35
+ # What they do when they cannot answer
36
+
37
+ This is the half that matters, and it is the contract rather than a courtesy.
38
+
39
+ `hook` reads **0 as pass and 2 as block, and every other code — including 1 —
40
+ as non-blocking**: the tool call proceeds. So `hook` never returns 1. A gate
41
+ that crashes therefore *passes*, from the protocol's side, which is why the
42
+ reader scrubs rather than raises, why `CLI.run` refuses anything that still
43
+ raises, and why the shell wrapper exists at all — a Ruby checker structurally
44
+ cannot refuse on its own absence.
45
+
46
+ A check that does not apply is not a failure: `Target.for` returns `nil` for an
47
+ edit outside any bundle, and the gate passes. A check that could not run says
48
+ so, in the same channel it would have used to refuse.
49
+
50
+ The full table, and the argument, is [exit-codes](/contract/exit-codes.md); the
51
+ three ways the seam let an unchecked edit through are
52
+ [three-fail-opens](/seam/three-fail-opens.md).
@@ -0,0 +1,12 @@
1
+ # Capabilities
2
+
3
+ What this gem can already answer. Read it before writing a check or a verb —
4
+ the point of a catalogue is that the fifteenth thing is not the eighth thing
5
+ under a new name.
6
+
7
+ Both lists are pinned: `test/unit/bundle_catalog_test.rb` compares them against
8
+ `OKF::Pro::CLI`'s own tables, so a verb added to the code and not to the
9
+ catalogue is a red suite rather than a stale document.
10
+
11
+ * [verbs](verbs.md) - The sixteen `okf pro` verbs: what each answers, which module implements it, and which flags it takes.
12
+ * [checks](checks.md) - The nine hook names, the event each rides, and what it does when it cannot answer.
@@ -0,0 +1,70 @@
1
+ ---
2
+ type: Capability
3
+ title: The verbs — what `okf pro` already answers
4
+ description: Sixteen verbs in four families — scaffold, readers, writers, and the hook adapter — each with the module that implements it and the flags it declares.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-19
8
+ ---
9
+
10
+ # The catalogue
11
+
12
+ `OKF::Pro::CLI::USAGE` is the source of truth for this table, and a test holds
13
+ the two together.
14
+
15
+ | verb | argument | answers | implemented in |
16
+ |---|---|---|---|
17
+ | `setup` | `[DIR]` | create or complete an agent's brain in DIR (default `.`) | `scaffold.rb` |
18
+ | `upgrade` | `[DIR]` | rewrite the gem-owned governance files; stage the rest | `scaffold.rb` |
19
+ | `skill` | `DEST` | (re)install okf-pro's agent skill on its own | `scaffold.rb` |
20
+ | `state` | `[DIR]` | what is on the board, in one call — add `--full` for the corpus | `state.rb` |
21
+ | `board` | `[DIR]` | one row per board line: section, dates, age, links | `board.rb` |
22
+ | `audit` | `[DIR]` | every invariant at once — the CI door | `audit.rb` |
23
+ | `records` | `[DIR]` | does the staged commit rewrite a past journal day? | `records.rb` |
24
+ | `snapshot` | `[DIR]` | compute the day's counter line (prints, never writes) | `snapshot.rb` |
25
+ | `unverified` | `[DIR]` | generated concepts still awaiting the owner's read | `attestation.rb` |
26
+ | `friction` | `[DIR]` | what was done by hand that a verb could do | `friction.rb` |
27
+ | `capture` | `TEXT` | append a dated Inbox line | `writes.rb` |
28
+ | `promote` | `SEL` | Inbox or Backlog to In flight, refusing over the cap | `writes.rb` |
29
+ | `demote` | `SEL` | In flight back to Backlog | `writes.rb` |
30
+ | `journal` | `open` | create today's journal day and index it | `writes.rb` |
31
+ | `close` | `SLUG` | the three mechanical closing moves for a project | `writes.rb` |
32
+ | `hook` | `CHECK` | run one gate against a hook event on stdin | see [checks](checks.md) |
33
+
34
+ # The four families, and why they differ
35
+
36
+ **Scaffold** (`setup`, `upgrade`, `skill`) takes a *destination*, not a bundle.
37
+ It does not go through `dir_argument`: `setup` into an empty directory is the
38
+ whole point, and refusing one that holds no bundle would refuse every first run.
39
+
40
+ **Readers** (`audit`, `records`, `snapshot`, `unverified`, `state`, `board`,
41
+ `friction`) answer questions. Every one of them routes through `parse_flags`,
42
+ listed in `FLAGS` or not — because absence from that table means "accepts none",
43
+ and a verb that skips the parser hands its undeclared flag to `BundleRoot` as a
44
+ directory and reports "holds no OKF bundle" as a *finding*: a pipeline's own
45
+ typo, spelled as a broken bundle. `test/integration/cli_test.rb` pins it over
46
+ `READERS`.
47
+
48
+ **Writers** (`capture`, `promote`, `demote`, `journal`, `close`) are the shapes
49
+ with exactly one correct form. Each is additive, targeted, and refused by
50
+ `Conserve` rather than careful — see [the-writers](/structure/the-writers.md).
51
+ They go through `writer_flags` instead, because their first argument is content
52
+ and a flag there is data.
53
+
54
+ **The hook adapter** is `hook`, and it is the odd one: its exit codes are the
55
+ protocol's, not this repo's.
56
+
57
+ # Flags
58
+
59
+ Declared per verb in `CLI::FLAGS`, and the help text is read *from* that table
60
+ rather than typed beside it:
61
+
62
+ | verb | flags |
63
+ |---|---|
64
+ | `state` | `--json`, `--pretty`, `--full` |
65
+ | `board` | `--json`, `--pretty`, `--section NAME` |
66
+ | `snapshot` | `--json`, `--pretty` |
67
+ | `unverified` | `--json`, `--pretty` |
68
+ | `friction` | `--json`, `--pretty`, `--issue`, `--clear` |
69
+
70
+ Everything else accepts none, and says so.
@@ -2,6 +2,9 @@
2
2
  type: Decision
3
3
  title: Containment fails in four different directions, on purpose
4
4
  description: A path that escapes the bundle is refused, or ignored, or read as "not closed" — and which one is right is decided per call site by what a wrong answer costs, at the write door as well as the read one.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The rule
@@ -2,6 +2,9 @@
2
2
  type: Decision
3
3
  title: Exit codes
4
4
  description: The hook protocol reads 0 and 2 and treats every other code as non-blocking, which inverts the repo's usual 0/1/2 convention for `hook` alone.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # What the protocol actually reads
@@ -17,8 +20,8 @@ that lets the edit through.
17
20
 
18
21
  # The deviation, and where it stops
19
22
 
20
- `AGENTS.md` states the repo's convention as 0 ok / 1 failing bundle / 2 usage
21
- error. `okf pro hook` cannot keep it: 1 is a code the protocol ignores, so it
23
+ The kernel's guide states the repo's convention as 0 ok / 1 failing bundle / 2
24
+ usage error (`@okf capabilities/validator` carries it too). `okf pro hook` cannot keep it: 1 is a code the protocol ignores, so it
22
25
  is never returned. Every other verb keeps the convention.
23
26
 
24
27
  | Verb | 0 | 1 | 2 |
@@ -2,6 +2,9 @@
2
2
  type: Decision
3
3
  title: Only gates answer at the hook door
4
4
  description: The CI verbs are not checks, and accepting one as a check name would report clean without ever reading the event.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The whitelist
@@ -2,6 +2,9 @@
2
2
  type: Finding
3
3
  title: The checker broke its own third clause
4
4
  description: A default Linter.call skips two clock-gated checks and still reports healthy, so the gate reported clean over checks it never ran.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # What was measured
@@ -2,6 +2,9 @@
2
2
  type: Learning
3
3
  title: The third clause, applied to something that is not a check
4
4
  description: The friction recorder neither refuses nor blocks, and still may not report a zero it did not count — because a measurement that quietly degrades to "nothing happened" retires itself.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # What it is
@@ -2,6 +2,9 @@
2
2
  type: Decision
3
3
  title: The contract
4
4
  description: Blocking checks fail closed, feedback checks fail loud, and no check ever fails silent — the three clauses every other decision in this gem is derived from.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The three clauses
@@ -2,6 +2,9 @@
2
2
  type: Learning
3
3
  title: A comment is not an implementation
4
4
  description: A regex that claimed to anchor `>` to a redirection position never did, and the guard it powered fired on reads for its whole life — which is the shape of noise that retires a gate. The same class reaches comments, docstrings and the bundle's own concepts.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # What happened
@@ -2,6 +2,9 @@
2
2
  type: Learning
3
3
  title: A rule you can walk past is a preference
4
4
  description: Why the gates block rather than warn, why three doors are not redundant, and why the day's ritual must stay silent on a day nobody worked.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The claim
@@ -2,6 +2,9 @@
2
2
  type: Decision
3
3
  title: Derivation may write, under a conservation guard
4
4
  description: Failure mode 07 forbids an LLM regenerating a view; it does not forbid a Ruby function performing a declared, line-conserving edit — and the status quo it was written against was the failure mode itself.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The verdict, read precisely
@@ -2,6 +2,9 @@
2
2
  type: Finding
3
3
  title: The eight failure modes
4
4
  description: The named ways a personal knowledge system dies — three by lying and five by telling the truth badly — and the mechanism that answers each.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The catalogue
@@ -11,6 +11,9 @@ sources:
11
11
  resource: https://gettingthingsdone.com/
12
12
  title: Getting Things Done
13
13
  author: David Allen
14
+ generated:
15
+ by: human:maintainer
16
+ at: 2026-08-17
14
17
  ---
15
18
 
16
19
  # Nothing here is invented from nothing
@@ -2,6 +2,9 @@
2
2
  type: Decision
3
3
  title: Structure laws
4
4
  description: The seven rules that decide whether a room, a field or a file may exist — a directory is a retrieval question, and state lives in exactly one place.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The rules
@@ -2,6 +2,9 @@
2
2
  type: Learning
3
3
  title: The residue
4
4
  description: Trust measures process, never truth — a misheard number, sourced and verified, reaches maximum trust while being false, and no law closes it.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The limit
@@ -2,6 +2,9 @@
2
2
  type: Decision
3
3
  title: Three laws
4
4
  description: Nothing is written without confronting what is already there; every view confesses its blind spots; attention is a budget, renegotiable but never silently exceeded.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The laws
@@ -2,6 +2,9 @@
2
2
  type: Overview
3
3
  title: Three pillars
4
4
  description: Know, Work and State — three questions no one of them can answer alone, and why the zones of the bundle are the pillars themselves.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The three questions
data/.okf/index.md CHANGED
@@ -4,25 +4,34 @@ okf_version: "0.2"
4
4
 
5
5
  # okf-pro knowledge bundle
6
6
 
7
- The non-obvious knowledge behind **okf-pro** — the gem that turns an
8
- [okf](@okf) bundle into a working memory an agent is held to. The `README`
9
- documents the verbs and the scaffold, and `AGENTS.md` carries the contracts a
10
- change has to keep; this bundle deliberately restates neither.
7
+ The knowledge behind **okf-pro** — the gem that turns an okf bundle (`@okf`)
8
+ into a working memory an agent is held to. It is written to be read *before*
9
+ opening `lib/`, so an agent about to add a check, a verb or a test does not
10
+ re-derive the layering and does not rebuild something this gem already answers.
11
11
 
12
- It captures two things. The first is the argument the gem serves three laws,
13
- the failures each one closes, and the limit each one admits because the rules
14
- are borrowed and reasoned rather than obvious, and a rule whose reasoning is
15
- lost gets re-argued from scratch every time someone questions it.
12
+ `AGENTS.md` beside it carries the contract, the constraints and the commands,
13
+ and routes here for everything else; the `README` is the adopter's. What used
14
+ to be a hand-maintained Map of `lib/**` in `AGENTS.md` now lives in
15
+ [Structure](structure/), pinned by `test/unit/bundle_catalog_test.rb` the
16
+ code is the truth and this bundle is the claim, so the two cannot drift
17
+ quietly.
16
18
 
17
- The second is the thing this gem is unusual for: **every defect here fails
18
- in the direction of silence.** A gate that cannot run, a check that was skipped,
19
- a shim on PATH, a status code the protocol reads as "proceed" — each of them
20
- produces an unchecked bundle that is indistinguishable, from the outside, from a
21
- clean one. So the knowledge worth recording is not what the checks assert; it is
22
- where the machinery around them can stop asserting anything without saying so.
19
+ It also captures the argument the gem serves three laws, the failures each
20
+ one closes, and the limit each one admits because the rules are borrowed and
21
+ reasoned rather than obvious, and a rule whose reasoning is lost gets re-argued
22
+ from scratch every time someone questions it.
23
+
24
+ And the thing this gem is unusual for: **every defect here fails in the
25
+ direction of silence.** A gate that cannot run, a check that was skipped, a shim
26
+ on `PATH`, a status code the protocol reads as "proceed" — each of them produces
27
+ an unchecked bundle that is indistinguishable, from the outside, from a clean
28
+ one. So the knowledge worth recording is not what the checks assert; it is where
29
+ the machinery around them can stop asserting anything without saying so.
23
30
 
24
31
  # Areas
25
32
 
33
+ * [Structure](structure/) - Every file under `lib/`, grouped by the layer that owns it: the doors, the readers, the gates, the guards, the writers, the scaffold, the recorder.
34
+ * [Capabilities](capabilities/) - The catalogue: sixteen verbs and nine checks — what each answers, before you write a seventeenth.
26
35
  * [Design](design/) - Why the rules exist: three pillars, three laws, the eight failures they close, and what was borrowed.
27
36
  * [Contract](contract/) - Fail closed, fail loud, never fail silent — and the exit codes that make it true.
28
37
  * [Seam](seam/) - The plugin entry point, the three ways it fails open, and the wrapper that closes them.
data/.okf/log.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Update Log
2
2
 
3
+ ## 2026-08-19
4
+
5
+ * **Every concept declares `generated:`.** The dates are read from git with
6
+ `--follow`, so a rename is not mistaken for authorship — the first attempt
7
+ stamped all 37 with the day the files *moved*, which is provenance invented
8
+ rather than recorded, and was thrown away.
9
+
10
+ * **The structure and the catalogue moved into this bundle, and a test holds
11
+ them to the code.** `AGENTS.md` carried a hand-maintained Map of `lib/**` that
12
+ nothing checked, so a file could arrive, move or leave and the Map would keep
13
+ reading plausibly. [Structure](/structure/) now owns it — one concept per
14
+ layer, naming every file — and [Capabilities](/capabilities/) owns the
15
+ catalogue of what the sixteen verbs and nine checks already answer, which is
16
+ the list an agent has to read before writing a seventeenth.
17
+ `test/unit/bundle_catalog_test.rb` is the pin, and it bites in both
18
+ directions: a file no concept names, a concept naming a file that is gone, or
19
+ a catalogue that disagrees with `CLI::USAGE` or `CLI::HOOK_NAMES`.
20
+ [testing/adding-a-verb](/testing/adding-a-verb.md) is the walk a new surface
21
+ owes, ending at the catalogue entry the pin will demand.
22
+
3
23
  ## 2026-08-17
4
24
 
5
25
  * **okf-pro 1.0.0.** The prototype checker — carried inside a template
@@ -2,6 +2,9 @@
2
2
  type: Decision
3
3
  title: Setup writes what it can, and refuses exactly one thing
4
4
  description: A collision is staged beside the adopter's file rather than refused; a root-level bundle is refused outright, because writing there manufactures an ambiguous layout.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # Never refuse wholesale
@@ -2,6 +2,9 @@
2
2
  type: Decision
3
3
  title: No date ships in the generated bundle
4
4
  description: A template is cloned an unknowable number of days after it is built, and every shipped date ages into somebody else's calendar.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The rule
@@ -2,6 +2,9 @@
2
2
  type: Decision
3
3
  title: The scaffold splits by ownership, not by subject
4
4
  description: Knowledge-versus-machinery is the obvious cut and the wrong one — it makes `upgrade` destroy exactly the hand-merge that adopting the gem asked for.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The two classes
@@ -2,6 +2,9 @@
2
2
  type: Learning
3
3
  title: The seeded README is the only document written for the owner
4
4
  description: Every other seeded file addresses the agent, so the adopter's README carries the exit, the limits and the price — and a fresh reader can measure whether it does.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # What the seeded README is for
@@ -2,6 +2,9 @@
2
2
  type: Finding
3
3
  title: Bundler scoping switches the gates off, and the fix locks out the in-bundle adopter
4
4
  description: Gem discovery is bundle-scoped once bundler's variables are exported, so `okf pro` is an unknown command inside a bundle that does not name okf-pro.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # Both directions, both measured
@@ -10,7 +13,7 @@ okf finds its extensions with `Gem.find_latest_files`, which is bundle-scoped
10
13
  once bundler's variables are in the environment.
11
14
 
12
15
  ```
13
- BUNDLE_GEMFILE=okf/Gemfile bundle exec okf tui --help
16
+ BUNDLE_GEMFILE=gems/okf/Gemfile bundle exec okf tui --help
14
17
  → unknown command, exit 2
15
18
  ```
16
19
 
@@ -2,6 +2,9 @@
2
2
  type: Learning
3
3
  title: Existence is not identity
4
4
  description: A stray `okf` on PATH that exits 0 passes every gate, and no exit-code normalisation can tell it from a clean check.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The claim that was wrong
@@ -2,6 +2,9 @@
2
2
  type: Decision
3
3
  title: Why the wrapper is a separate shell script
4
4
  description: A Ruby checker structurally cannot refuse on its own absence, so something already running has to — and it must not exec.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The argument
@@ -2,6 +2,9 @@
2
2
  type: Finding
3
3
  title: Three fail-opens in the plugin seam
4
4
  description: Moving the checker behind okf's plugin seam introduced three paths where a failure exits 1 — which the hook protocol reads as "proceed".
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The measurements
@@ -0,0 +1,80 @@
1
+ ---
2
+ type: Component
3
+ title: The doors, and the constants the contract is written in
4
+ description: Three entry points — the plugin verb, the hook adapter, the CI verbs — all through one dispatcher, and the exit codes every one of them answers in.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-19
8
+ ---
9
+
10
+ # The files
11
+
12
+ | file | what it owns |
13
+ |---|---|
14
+ | `lib/okf/pro.rb` | the contract's constants, the two reads, `NO_RECONCILE`, and the require list that loads everything |
15
+ | `lib/okf/plugin.rb` | `OKF::CLI::Pro` — the okf extension seam, and the last Ruby-side line of the contract |
16
+ | `lib/okf/pro/cli.rb` | `OKF::Pro::CLI` — dispatch, the verb tables, the flag parser, and the identity marker |
17
+ | `lib/okf/pro/version.rb` | `OKF::Pro::VERSION` |
18
+
19
+ # `pro.rb` is the vocabulary, not a namespace
20
+
21
+ Three constants, and every exit this gem takes is one of them:
22
+
23
+ * `PASS = 0`, `BLOCK = 2` — the only two codes the hook protocol reads as
24
+ meaning anything.
25
+ * `FAIL = 1` — `audit` only. It is *findings*, which is CI's vocabulary and not
26
+ the hook's; the hook door never returns it, because the protocol reads it as
27
+ proceed.
28
+
29
+ Then three things every layer above uses. `read_text` is the raw read, as
30
+ bytes, forced to UTF-8 and scrubbed — it failed open twice before it looked
31
+ like this, once on an unset `LANG` making `Encoding.default_external` US-ASCII
32
+ and once on a single invalid byte raising out of `match?`. `read_contained` is
33
+ the same read through the kernel's `SafeRead.read!`, for the callers that
34
+ already hold a root and must refuse a symlink leaving it. `newline_terminated`
35
+ guarantees the final newline a splice depends on.
36
+
37
+ `require "okf/pro"` loads **everything**, deliberately: this gem is not an
38
+ embedding library, it is a checker, and every module is on the path of some
39
+ gate. It also refuses at the top — a `LoadError` on `okf` prints
40
+ `ENFORCEMENT DEGRADED` and exits 2, rather than letting the edit through.
41
+
42
+ # `plugin.rb` is where the contract runs out of Ruby
43
+
44
+ It registers `pro` with the kernel's command registry, and holds a
45
+ `rescue Exception` that re-raises `SystemExit`, cop disabled, reason beside it.
46
+ The failures it catches are `ScriptError`s, which are not `StandardError`s, and
47
+ it must sit **outside** the `require "okf/pro"` it guards — inside the CLI it
48
+ could not catch the `LoadError` of the require that reaches it.
49
+
50
+ That is the first of [three fail-opens](/seam/three-fail-opens.md) this seam had.
51
+
52
+ A `SyntaxError` here is unreachable from Ruby altogether: discovery rescues
53
+ `LoadError, StandardError`, a `SyntaxError` is neither, and no gem code runs at
54
+ all. Only the scaffold's shell wrapper can catch that, which is why the wrapper
55
+ no longer `exec`s.
56
+
57
+ # `cli.rb` is one dispatcher and four tables
58
+
59
+ The verb tables are the catalogue, and they are read rather than copied
60
+ wherever a second copy would drift:
61
+
62
+ | table | what it holds |
63
+ |---|---|
64
+ | `CHECKS` | the eight hook checks, name to lambda |
65
+ | `HOOK_NAMES` | `CHECKS.keys` plus `session-context` — the **only** names the hook door accepts |
66
+ | `READERS` | the seven question-answering verbs |
67
+ | `WRITERS` | the five verbs that change a file |
68
+ | `SCAFFOLD` | `setup`, `upgrade`, `skill` — they take a destination, not a bundle |
69
+ | `USAGE` | the printed command list, joined to `help_rows` by a test rather than by a second literal |
70
+ | `FLAGS` | what each verb accepts, declared; absence means "accepts none" |
71
+
72
+ `HOOK_NAMES` exists because `run` dispatches the CI verbs off the same first
73
+ argv element a check name arrives in. Without the whitelist a `settings.json`
74
+ typo spelling `hook audit` installs a gate that reads no stdin, cannot block,
75
+ and reports "clean."
76
+
77
+ `MARKER` — `okf-pro-enforcer v1` on stderr immediately before a check runs — is
78
+ the whole of the wrapper's identity proof, and it is emitted *here* rather than
79
+ at the door so that its presence means the check was reached, not merely that
80
+ the plugin loaded.
@@ -0,0 +1,18 @@
1
+ # Structure
2
+
3
+ Every file under `lib/`, grouped by the layer that owns it. One concept owns
4
+ each file, and `test/unit/bundle_catalog_test.rb` fails if that stops being true
5
+ in either direction — a file no concept names, or a concept naming a file that
6
+ is gone.
7
+
8
+ Read it as a pipeline. An event arrives at a door, the world is read into data,
9
+ a gate or a guard asks one question of that data, and — only for the write
10
+ verbs — a transform is planned, conserved and committed.
11
+
12
+ * [doors](doors.md) - `lib/okf/pro.rb`, `lib/okf/plugin.rb`, `lib/okf/pro/cli.rb`, `lib/okf/pro/version.rb` — the seam, the contract's constants, and the one dispatcher.
13
+ * [reading-the-world](reading-the-world.md) - `bundle_root.rb`, `event.rb`, `target.rb`, `board.rb`, `log.rb`, `records.rb` — where the bundle is, what arrived, and the two documents as data.
14
+ * [the-gates](the-gates.md) - `reconcile.rb`, `budget.rb`, `closing.rb`, `snapshot.rb`, `attestation.rb`, `pairing.rb`, `conformance.rb`, `state.rb`, `audit.rb` — one question each, and the two doors that ask them all.
15
+ * [the-guards](the-guards.md) - `guards.rb`, `shell_guard.rb` — the trust rules, and the same rules at the door a shell command comes through.
16
+ * [the-writers](the-writers.md) - `conserve.rb`, `board/edit.rb`, `log/edit.rb`, `writes.rb` — pure transforms, a declared delta, and the refusal that enforces it.
17
+ * [the-scaffold](the-scaffold.md) - `scaffold.rb` and the template tree it writes.
18
+ * [the-recorder](the-recorder.md) - `friction.rb` — telemetry about the tooling, which refuses nothing and blocks nothing.
@@ -0,0 +1,65 @@
1
+ ---
2
+ type: Component
3
+ title: Reading the world — the root, the event, and the two documents as data
4
+ description: Six files that answer where the bundle is, what arrived on stdin, and what `board.md` and `log.md` say — before any gate has an opinion.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-19
8
+ ---
9
+
10
+ # The files
11
+
12
+ | file | pure? | what it owns |
13
+ |---|---|---|
14
+ | `lib/okf/pro/bundle_root.rb` | pure | where the bundle is, given where the agent is |
15
+ | `lib/okf/pro/event.rb` | pure | the hook event — the only place untrusted input is parsed |
16
+ | `lib/okf/pro/target.rb` | shell | bundle root + edited path, or `nil` when a check cannot apply |
17
+ | `lib/okf/pro/board.rb` | pure | `board.md` as data: sections, budget header, links, dates |
18
+ | `lib/okf/pro/log.rb` | pure | `log.md` as data: the snapshot line, the newest day |
19
+ | `lib/okf/pro/records.rb` | shell | the append-only record, asked of git at the commit door |
20
+
21
+ # Where the root is
22
+
23
+ `BundleRoot.resolve` walks from a starting directory to the bundle it belongs
24
+ to. `DIR` is `.okf`, and `root_kind` distinguishes the repository root from a
25
+ level root from a plain directory, because "the bundle" is a different question
26
+ in a monorepo than in a repository holding one. It is the one reader that does
27
+ **not** go through `read_contained`: it is deciding where the root *is*, so it
28
+ has none to contain against — a raise there would be a permanent lockout and a
29
+ rescue would mis-root the bundle and disarm the journal guard.
30
+
31
+ # What arrived
32
+
33
+ `Event` is a class rather than a module, and it is the only place untrusted
34
+ input is parsed. It answers `tool_name`, `tool_input`, `file_path`, `command`,
35
+ `cwd`, `added_text`, `stop_hook_active?` — and `parse_error?`, which is the
36
+ honest answer to malformed JSON rather than an exception thrown at a gate that
37
+ would then not run.
38
+
39
+ `Target.for(event)` turns an event into a root plus a relative path, or `nil`
40
+ when the check simply does not apply — an edit outside any bundle is not a
41
+ failure, and a gate that treated it as one would refuse every edit in the
42
+ repository. `Target#read` and `#exist?` are how a gate reaches a file inside
43
+ its own root.
44
+
45
+ # The two documents
46
+
47
+ `Board` and `Log` are the parsers, and they are pure — no disk, no stdio.
48
+
49
+ `Board` strips HTML comments first (`strip_comments`, then `visible`), because
50
+ a commented-out row is not on the board and every count downstream depends on
51
+ that being true. It answers `rows`, `count`, `budget`, `targets`, `line_date`,
52
+ `chase_date`, and the three grammar checks — `missing_sections`,
53
+ `date_findings`, `stray_bullets` — against the six `SECTIONS` it knows.
54
+
55
+ `Log` answers the questions the closing gate asks of `log.md`:
56
+ `snapshot_under?` for a given day, `days`, `newest_day`, `latest_snapshot_entry`,
57
+ and `malformed_days`. `DAY_HEADING` and the deliberately looser `DAYISH_HEADING`
58
+ exist as a pair — the second is how a heading that *looks* like a day but is not
59
+ one gets reported instead of silently skipped.
60
+
61
+ The gates that consume all six are [the-gates](/structure/the-gates.md).
62
+
63
+ `Records` is the git question: `staged_violations` asks the index, not the
64
+ working tree, whether a commit rewrites or deletes a past journal day. It is at
65
+ the commit door because that is the only door where the staged tree is the truth.
@@ -0,0 +1,64 @@
1
+ ---
2
+ type: Component
3
+ title: The gates — one question each, and the two doors that ask them all
4
+ description: Nine files, each answering one invariant, plus `audit` (the CI door) and `state` (the readers' payload) which ask the same questions from a different side.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-19
8
+ ---
9
+
10
+ # The files
11
+
12
+ | file | the law or the question |
13
+ |---|---|
14
+ | `lib/okf/pro/reconcile.rb` | **Law 1** — what already says this, while the write is still hot |
15
+ | `lib/okf/pro/budget.rb` | **Law 3** — the cap, and the dormancy question |
16
+ | `lib/okf/pro/closing.rb` | **Law 2** — the stop gate, and the session banner |
17
+ | `lib/okf/pro/snapshot.rb` | Law 2's counters, derived — a checker, never a generator |
18
+ | `lib/okf/pro/attestation.rb` | what still awaits the owner's read |
19
+ | `lib/okf/pro/pairing.rb` | the board↔work invariants, both directions |
20
+ | `lib/okf/pro/conformance.rb` | `okf validate` + `okf lint`, in process |
21
+ | `lib/okf/pro/audit.rb` | the CI door: the same invariants minus the tool event |
22
+ | `lib/okf/pro/state.rb` | the readers' payload — cheap by contract |
23
+
24
+ The laws themselves are [design/three-laws](/design/three-laws.md); this concept
25
+ is where each one is implemented.
26
+
27
+ # Snapshot is a checker, not a generator
28
+
29
+ `Snapshot.counters` recomputes every number from the board and the bundle;
30
+ `Snapshot.line` renders it; `Snapshot.parse` reads a line already written; and
31
+ `Snapshot.verify` compares the two and reports the difference. There is no
32
+ `--write`, and there deliberately never will be: a writer and a checker sharing
33
+ a code path agree trivially, which is exactly what Law 2's confession must not
34
+ be able to do.
35
+
36
+ # Conformance is the kernel's answer, not a second one
37
+
38
+ `Conformance.check` runs the kernel's validator and linter in process against
39
+ the target's bundle. Every conformance and curation question is okf's to answer
40
+ — this gem owns the policy on top. The one rule it adds is the contract's third
41
+ clause: `Linter.call` with no options skips `expired` and `stale` and still
42
+ reports `healthy?`, so `confession` surfaces what was not run rather than
43
+ reporting clean over a silent skip.
44
+
45
+ # Two doors, one set of invariants
46
+
47
+ `Audit.call` is the CI door. It asks `structure`, `conformance`, `curation`,
48
+ `snapshot` and `pairing` of a tree with no tool event in sight, and it reserves
49
+ exit 1 for *findings* — spelling "the checker broke" as 2, because a pipeline
50
+ that cannot tell those apart learns to ignore both. `ambiguous_layout` is its
51
+ refusal when it cannot tell which bundle it was pointed at.
52
+
53
+ `State.call` is the other side: what is on the board, in one call, cheap by
54
+ contract. `full: true` is the one parse it will pay for — the readers exist
55
+ because an agent working in a seeded bundle rediscovered the board by reading
56
+ raw markdown while the stop gate was already computing it.
57
+
58
+ # Pairing holds the one git shell-out
59
+
60
+ `Pairing.failures` asks both directions at once: a project with no board line,
61
+ a board line with no project, a target that does not resolve, a link to a
62
+ closed project, a read line gone stale. `MARKER` and `NEGATED` are a pair — the
63
+ second is why "never closed" is not read as a closure marker. `dirty_markdown?`
64
+ is the git shell-out, and it is the only one outside `records.rb`.
@@ -0,0 +1,43 @@
1
+ ---
2
+ type: Component
3
+ title: The guards — the trust rules, and the same rules at the shell door
4
+ description: Two files asking the same questions at two doors, because a `cat > concept.md` is seen by neither the Edit tool nor the file-path checks.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-19
8
+ ---
9
+
10
+ # The files
11
+
12
+ | file | the door |
13
+ |---|---|
14
+ | `lib/okf/pro/guards.rb` | an `Edit` or `Write` tool event: the trust rules on the text about to land |
15
+ | `lib/okf/pro/shell_guard.rb` | a `Bash` tool event: the same rules, read out of a command line |
16
+
17
+ # What they ask
18
+
19
+ `Guards.attests?` is the trust question — does this text set `verified:`?
20
+ `ATTESTATION` matches the block form, `FLOW_ATTESTATION` the inline-mapping
21
+ form, and both exist because a rule that only knew one spelling waved the other
22
+ through. `guard_verified` is the gate that uses them; `journal_guard` is the
23
+ append-only rule for `journal/YYYY-MM-DD.md`, which `JOURNAL_ENTRY` recognises.
24
+
25
+ `ShellGuard.check` reads the same rules out of a command. `MUTATORS` is the
26
+ list of commands that can write, `SEPARATORS` splits a compound command so a
27
+ mutator after `&&` is not missed, `MARKDOWN` finds the paths, and `own_write?`
28
+ is what keeps `okf pro` verbs from being refused by the guard that exists to
29
+ make people use them.
30
+
31
+ # Why there are two
32
+
33
+ A gate reads a tool event. A shell redirect is not one — a `cat > board.md`
34
+ carries no `file_path`, so `guard_verified` never sees it and `Target.for`
35
+ returns nothing to check. Without the shell door the whole trust layer is one
36
+ `bash -c` away from being off, which is the shape every defect in this gem has
37
+ had: not a wrong answer, a *no* answer wearing the costume of a clean one.
38
+
39
+ The write verbs the guards are protecting are [the-writers](/structure/the-writers.md).
40
+
41
+ That is also why the write verbs refuse text spanning lines rather than
42
+ escaping it — a verb invoked through Bash is seen by neither guard, so the
43
+ safety there is by construction rather than by inspection.
@@ -0,0 +1,49 @@
1
+ ---
2
+ type: Component
3
+ title: The recorder — telemetry that refuses nothing and still may not lie
4
+ description: "`friction` counts what was done by hand that a verb could have done, at paths that already run; it blocks nothing, and it may not report a zero it did not count."
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-19
8
+ ---
9
+
10
+ # The file
11
+
12
+ | file | what it owns |
13
+ |---|---|
14
+ | `lib/okf/pro/friction.rb` | the friction log: record, report, classify, clear |
15
+
16
+ # What it is, and what it is not
17
+
18
+ It is telemetry about the *tooling*, not about the knowledge base. It refuses
19
+ nothing and blocks nothing, and it is **not** one of `okf pro state`'s sources —
20
+ mixing it in would make a reader's payload depend on how the last session was
21
+ typed.
22
+
23
+ Nor does it register a new hook event. `.claude/settings.json` is a seeded file,
24
+ so a registration added there would never reach an existing adopter through
25
+ `upgrade`. The recorder rides the paths that already run.
26
+
27
+ # It still answers to the third clause
28
+
29
+ A recorder that fails quietly reports a clean session it never observed, which
30
+ is the contract's third clause broken by something that is not even a check.
31
+ So:
32
+
33
+ * a failed write leaves `MARKER` (`okf-pro-friction.unavailable`) behind, and
34
+ `available?` is asked directly rather than inferred from an empty log;
35
+ * an unwritable `.tmp/` is confessed, not shrugged at;
36
+ * an unparseable line is reported rather than skipped.
37
+
38
+ The argument is [telemetry-does-not-lie](/contract/telemetry-does-not-lie.md).
39
+
40
+ # And it may not ask for a verb that is not missing
41
+
42
+ `own_command?` recognises this gem's own invocations — including through
43
+ `WRAPPERS` (`bundle exec`, `env`, `sudo` and the rest) — so `okf pro capture`
44
+ is not recorded as friction against `okf pro capture`. `covered_by` and
45
+ `verb_covered?` decide whether a row names something a verb would actually
46
+ answer: a shell redirect's honest answer is `SHELL_ANSWER` — Edit or Write,
47
+ because the trust guards read a tool event and a redirect is none. The session
48
+ banner counts only the rows a verb would answer, and `--issue` prints nothing
49
+ when nothing was recorded.
@@ -0,0 +1,42 @@
1
+ ---
2
+ type: Component
3
+ title: The scaffold — what `setup` writes, and who owns it afterwards
4
+ description: "One generator, two template trees, and a split by ownership rather than by subject: `upgrade` rewrites the gem's files and never touches a seeded one."
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-19
8
+ ---
9
+
10
+ # The file
11
+
12
+ | file | what it owns |
13
+ |---|---|
14
+ | `lib/okf/pro/scaffold.rb` | `setup`, `upgrade`, `skill` — the generator and its refusals |
15
+
16
+ Plus `lib/okf/pro/template/`, which is not code: it is the tree `setup` writes.
17
+ It ships, and the gemspec says why — a gem without it installs a verb that
18
+ cannot do its job.
19
+
20
+ # Two trees, and the split is ownership
21
+
22
+ `GEM_DIR` (`template/gem/`) holds the machinery: `upgrade` rewrites those files
23
+ in place, every time, without asking. `SEED_DIR` (`template/seed/`) holds the
24
+ adopter's: written once by `setup`, never touched again.
25
+
26
+ Reclassifying `CLAUDE.md`, `.gitignore` or `settings.json` as machinery would
27
+ make `upgrade` destroy exactly the hand-merge `setup` told the adopter to
28
+ perform. The argument is [ownership-not-subject](/scaffold/ownership-not-subject.md).
29
+
30
+ `DOTFILES` maps `gitignore` to `.gitignore` — stored without the dot, because a
31
+ dotfile inside the template is a file `git ls-files` and every glob treat
32
+ differently. `EXECUTABLE` is the two files that must land with the bit set.
33
+ `SUFFIX` (`.okf-pro-new`) is what a collision is written as instead of an
34
+ overwrite; `flat_layout_refusal` is what happens when the destination is not a
35
+ shape this gem can complete.
36
+
37
+ # What must not appear
38
+
39
+ No date ships in the generated bundle, outside a code span — and the exemption
40
+ *is* required rather than cosmetic, because the closure marker has to be taught
41
+ and `Pairing::MARKER` requires a date. Both directions of that coupling are
42
+ pinned by `test/unit/closure_grammar_test.rb`. See [no-date-ships](/scaffold/no-date-ships.md).
@@ -0,0 +1,60 @@
1
+ ---
2
+ type: Component
3
+ title: The writers — pure transforms, a declared delta, and the refusal that enforces it
4
+ description: Every write verb computes its new text without touching the disk, declares the lines it means to add, remove and move, and is refused if the actual delta differs.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-19
8
+ ---
9
+
10
+ # The files
11
+
12
+ | file | pure? | what it owns |
13
+ |---|---|---|
14
+ | `lib/okf/pro/conserve.rb` | pure | the write contract, enforced: line multisets in, refusals out |
15
+ | `lib/okf/pro/board/edit.rb` | pure | the board's text transforms, and the keyed selectors |
16
+ | `lib/okf/pro/log/edit.rb` | pure | a dated line, under its day, newest-first |
17
+ | `lib/okf/pro/writes.rb` | shell | the mechanical writers: read, transform, guard, rename |
18
+
19
+ # The shape every verb has
20
+
21
+ Read the file. Compute the new text with a **pure** transform — `Board::Edit` and
22
+ `Log::Edit` cannot touch the disk, which is what makes the transform testable
23
+ without a fixture and unable to half-write. Declare the delta: which lines were
24
+ added, removed, moved. Then `Conserve.check` compares the declared delta against
25
+ the actual one and refuses with exit 2 — **nothing written** — if they differ in
26
+ either direction.
27
+
28
+ That is the enforcement of [derivation-that-writes](/design/derivation-that-writes.md):
29
+ a write verb is additive and targeted, never regenerative. `Conserve.normalize`
30
+ chomps, so a missing final newline is not a delta; `counts` and `subtract` do the
31
+ multiset arithmetic, so a line moved between sections is a move rather than a
32
+ delete plus an add.
33
+
34
+ # What the verbs are
35
+
36
+ `Writes` holds `capture`, `promote`, `demote`, `journal_open` and `close`, and
37
+ each one is a plan-then-commit pair: `plan` computes and conserves, `commit`
38
+ writes atomically. `PROMOTE_FROM` is the two sections a promotion may come from.
39
+ `dormancy_note` is what a demotion says.
40
+
41
+ `Board::Edit`'s selectors are the part worth reading before touching:
42
+ `select` tries `by_target` then `by_substring`, and reports `no_match` or
43
+ `ambiguous` rather than guessing. `BUDGET_LINE` is the header regex
44
+ `set_declared` rewrites, and `NAMES_NOTHING` is the pair of selectors that
45
+ address nothing at all — an empty string and a bare `/projects`.
46
+
47
+ # Three refusals that are not politeness
48
+
49
+ * **A missing file is refused, never created.** `capture` will not create
50
+ `board.md`; `journal open` will not create `journal/index.md`. An index
51
+ rebuilt from the one line a verb knows is regeneration wearing an append's
52
+ clothes.
53
+ * **Text spanning lines is refused, not escaped.** Agent text reaches a board
54
+ line body or a journal entry body and nowhere else.
55
+ * **A caller-supplied name is contained twice** — one directory segment, *and*
56
+ resolving inside the bundle, because `projects/<slug>` can be a symlink out.
57
+ `contained?`, `escaping` and `escape_refusal` are that check; the directions
58
+ are argued in [containment-directions](/contract/containment-directions.md).
59
+
60
+ No verb writes a concept body or sets `verified:`.
@@ -0,0 +1,63 @@
1
+ ---
2
+ type: Playbook
3
+ title: Adding a verb, or a check
4
+ description: The nine steps a new `okf pro` surface owes, in order — where the logic goes, which table it joins, which test file it earns, and which catalogue entry the pin will demand.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-19
8
+ ---
9
+
10
+ # Decide the family first
11
+
12
+ Four families, and they differ in how they are dispatched, what they may accept
13
+ and what they may do. [capabilities/verbs](/capabilities/verbs.md) has the table.
14
+
15
+ * a **check** rides a hook event, reads stdin, and answers in the protocol's
16
+ exit codes — 0 or 2, never 1;
17
+ * a **reader** answers a question about a bundle and takes flags;
18
+ * a **writer** changes a file, and is refused by `Conserve` unless its declared
19
+ delta is exactly what happened;
20
+ * a **scaffold** verb takes a destination, not a bundle.
21
+
22
+ Getting this wrong is not stylistic. A reader that skips `parse_flags` reports
23
+ its caller's typo as a broken bundle; a check that returns 1 is read as
24
+ *proceed*.
25
+
26
+ # The walk
27
+
28
+ 1. **Write the failing test first**, in `test/integration/`, in a file named for
29
+ the verb — `cli_<verb>_test.rb`, one file per verb and subcommand. Run it.
30
+ It must fail for the reason you predicted, not because a fixture is missing.
31
+ 2. **Put the logic in a module, not in the CLI.** `cli.rb` dispatches, parses
32
+ flags and prints; the question itself belongs in the module that owns that
33
+ part of the world. [structure/](/structure/) says which one, and a genuinely
34
+ new layer earns a new file *and* a line in the concept that owns it.
35
+ 3. **Join the right table** in `cli.rb`: `CHECKS` (which puts it in
36
+ `HOOK_NAMES` automatically), or `READERS`, `WRITERS`, `SCAFFOLD`.
37
+ 4. **Add its `USAGE` row**, and the matching row in `OKF::CLI::Pro.help_rows`.
38
+ The two are separate literals on purpose — reading `USAGE` from the plugin
39
+ would make every `okf help` load the whole library — and they are held
40
+ together by a test rather than by discipline.
41
+ 5. **Declare its flags** in `FLAGS` if it takes any. Absence means "accepts
42
+ none", not "is exempt": every reader routes through `parse_flags` either way.
43
+ 6. **A writer owes a pure transform.** Compute the new text in `board/edit.rb`
44
+ or `log/edit.rb` — they cannot touch the disk — declare the added, removed
45
+ and moved lines, and let `Conserve` refuse the mismatch. See
46
+ [the-writers](/structure/the-writers.md).
47
+ 7. **A check owes a drill** in `test/integration/wrapper_test.rb` if it adds a
48
+ way for enforcement to be absent. Half the drills there assert a *pass*, and
49
+ yours should too: a wrapper that refused everything would satisfy every
50
+ refusal drill in the file. See [drills-over-units](drills-over-units.md).
51
+ 8. **Update the catalogue** — [capabilities/verbs](/capabilities/verbs.md) or
52
+ [capabilities/checks](/capabilities/checks.md). This is not documentation
53
+ etiquette: `test/unit/bundle_catalog_test.rb` compares those tables against
54
+ `CLI::USAGE` and `CLI::HOOK_NAMES`, so the suite is red until you do.
55
+ 9. **Run the same test unedited.** If it needed editing to pass, it was written
56
+ after the code and certifies only what the code happens to do.
57
+
58
+ # What the fixture will do to you
59
+
60
+ `BundleFixture` is a **client of the code under test** — `write_log` computes
61
+ its snapshot line by calling `Snapshot.line` — so "the suite is green, therefore
62
+ nothing changed" holds only for changes that do not touch what the fixture
63
+ calls. [fixture-is-a-client](fixture-is-a-client.md) is the full argument.
@@ -2,6 +2,9 @@
2
2
  type: Learning
3
3
  title: Drills, not unit tests, at the enforcement seam
4
4
  description: Every defect found in the entry point was a gate that passed while unable to check, which only a real process with a real exit status can catch.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # What a drill is here
@@ -2,6 +2,9 @@
2
2
  type: Learning
3
3
  title: The bundle fixture is a client of the code under test
4
4
  description: The bundle fixture computes its own snapshot line by calling the code under test, so a change to the counters reaches every fixture carrying a log day — which bounds what a green ported suite proves.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # Why it calls the code
@@ -2,3 +2,4 @@
2
2
 
3
3
  * [drills-over-units](drills-over-units.md) - Every bug in this layer was a gate that passed when it could not do its job.
4
4
  * [fixture-is-a-client](fixture-is-a-client.md) - The bundle fixture calls the code under test, so "the ported suite is unchanged proof" has a limit.
5
+ * [adding-a-verb](adding-a-verb.md) - The nine steps a new verb or check owes, in order.
@@ -2,6 +2,9 @@
2
2
  type: Decision
3
3
  title: A briefing is owed a read until a human has verified it
4
4
  description: §5.3's trust tiers replaced a truthiness test on `verified`, and the four call sites that read the rule have to move together or a briefing falls through all of them.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The rule
@@ -2,6 +2,9 @@
2
2
  type: Finding
3
3
  title: A scalar `verified:` fails in the worst available direction
4
4
  description: The guard fires on the word, the owner approves, and then the reader drops the malformed value — so the concept stays unverified and the To-read line is demanded forever.
5
+ generated:
6
+ by: human:maintainer
7
+ at: 2026-08-17
5
8
  ---
6
9
 
7
10
  # The sequence
data/CHANGELOG.md CHANGED
@@ -4,6 +4,72 @@ All notable changes to okf-pro are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this gem uses
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.1.0] - 2026-08-22
8
+
9
+ ### Changed
10
+
11
+ - **The `okf` floor moves to 2.2.0**, with no change to this gem's own code. The
12
+ floor tracks the kernel each sibling develops against and may never lag it, so
13
+ the PR that bumps okf moves every sibling's line — `test/unit/gemspec_test.rb`
14
+ fails until it does. Nothing here reads a 2.2.0 surface yet; the rule is
15
+ defensive, and this is the gem where it matters most, since a missing okf
16
+ method raises inside a check whose crash the hook protocol reads as
17
+ *non-blocking* — a floor that lags does not error here, it stops gating.
18
+
19
+ A floor is part of the published contract, so moving it is a release even when
20
+ no code changed. Hence a version of its own rather than a line riding along
21
+ with the kernel's.
22
+
23
+ ## [1.0.1] - 2026-08-20
24
+
25
+ ### Fixed
26
+
27
+ - **The package metadata follows the `gems/` move and the repository rename.**
28
+ Two URLs on the package page were about to be wrong at once. `changelog_uri`
29
+ named `blob/main/okf-pro/CHANGELOG.md`, a path that stopped existing when the
30
+ four gems moved under `gems/`; `homepage` named `serradura/okf-gem`, and the
31
+ repository is now **`serradura/okf`** — the name the command has had all
32
+ along. rubygems.org serves whatever the last release published, so neither
33
+ corrects itself: only a release republishes metadata, and this is that
34
+ release. Both land in one round rather than two, which is the whole reason
35
+ the rename waited for the paths to stop moving.
36
+
37
+ `homepage_uri`, `source_code_uri` and `changelog_uri` are all derived from
38
+ `homepage`, so one line moves four pieces of this gem's metadata. GitHub
39
+ redirects the old URLs permanently, so anything already published keeps
40
+ resolving.
41
+
42
+ ### Changed
43
+
44
+ - **Every concept in the gem's bundle now declares `generated:`.** The actor is
45
+ the maintainer and the date is read from git with `--follow`, so a rename is
46
+ not mistaken for authorship — 27 concepts date to the 1.0.0 release and 10 to
47
+ the structural work after it. `okf lint` on the bundle is clean.
48
+
49
+ ### Added
50
+
51
+ - **The bundle carries the structure and the catalogue, and a test pins them.**
52
+ `.okf/structure/` names every file under `lib/`, grouped by the layer that
53
+ owns it; `.okf/capabilities/` is the catalogue of the sixteen verbs and nine
54
+ checks this gem already answers; `.okf/testing/adding-a-verb.md` is the walk a
55
+ new one owes. `test/unit/bundle_catalog_test.rb` fails when a file under
56
+ `lib/` is named by no concept, when a concept names a file that is gone, or
57
+ when either catalogue disagrees with `CLI::USAGE` or `CLI::HOOK_NAMES`.
58
+ `AGENTS.md` kept the contract and the commands and now routes here for the
59
+ rest — its hand-maintained Map of `lib/**` was the thing nothing checked.
60
+
61
+ ### Fixed
62
+
63
+ - **`AGENTS.md`'s 2.4 floor command pointed at a directory that no longer
64
+ exists** — `cd /build/okf-pro`, from before the gems moved under `gems/`.
65
+
66
+ ### Changed
67
+
68
+ - **The okf floor moves to `>= 2.1.1, < 3`** — the kernel this gem develops
69
+ against released 2.1.1, and the floor tracks what the suite proves against
70
+ (the gemspec drill enforces equality as the normal state). Nothing here calls
71
+ a 2.1-only surface; the ceiling is unchanged.
72
+
7
73
  ## [1.0.0] - 2026-08-17
8
74
 
9
75
  First release. okf-pro begins as a working prototype — a checker carried inside
@@ -184,4 +250,5 @@ every use. This surface answers both.
184
250
  - **The `.bin/okf_pro` binary is gone.** `okf pro` is the only door, which is
185
251
  what lets the wrapper refuse anything that is not it.
186
252
 
187
- [1.0.0]: https://github.com/serradura/okf-gem/releases/tag/okf-pro%2Fv1.0.0
253
+ [1.0.1]: https://github.com/serradura/okf/compare/okf-pro/v1.0.0...okf-pro/v1.0.1
254
+ [1.0.0]: https://github.com/serradura/okf/releases/tag/okf-pro%2Fv1.0.0
data/README.md CHANGED
@@ -376,7 +376,11 @@ your day; two people sharing one board is not a supported arrangement.
376
376
  Its own knowledge bundle ships inside the gem, at `.okf/` — the non-obvious
377
377
  parts, written down: the three ways the plugin seam let an unchecked edit
378
378
  through, the check the gate skipped in silence, and why a stray binary on
379
- `PATH` cannot be caught by reading an exit code.
379
+ `PATH` cannot be caught by reading an exit code. It is also this gem's
380
+ structural documentation and its catalogue of what the verbs and checks already
381
+ answer, so `okf server .okf` inside an installed copy is a working map of the
382
+ code. Which is the format arguing for itself: the bundle you are being handed
383
+ is one the gem reads with its own tools.
380
384
 
381
385
  ## Licence
382
386
 
data/lib/okf/pro/cli.rb CHANGED
@@ -129,7 +129,7 @@ module OKF
129
129
  # Where a friction report goes. It is about okf-pro, not about the
130
130
  # adopter's knowledge base, so it is this repository regardless of whose
131
131
  # tree the bundle lives in. Nothing is ever filed automatically.
132
- ISSUE_REPO = "serradura/okf-gem"
132
+ ISSUE_REPO = "serradura/okf"
133
133
 
134
134
  HELP = %w[--help -h help].freeze
135
135
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OKF
4
4
  module Pro
5
- VERSION = "1.0.0"
5
+ VERSION = "1.1.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: okf-pro
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura
@@ -15,7 +15,7 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '2.0'
18
+ version: 2.2.0
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
21
  version: '3'
@@ -25,7 +25,7 @@ dependencies:
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: '2.0'
28
+ version: 2.2.0
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
31
  version: '3'
@@ -48,6 +48,9 @@ executables: []
48
48
  extensions: []
49
49
  extra_rdoc_files: []
50
50
  files:
51
+ - ".okf/capabilities/checks.md"
52
+ - ".okf/capabilities/index.md"
53
+ - ".okf/capabilities/verbs.md"
51
54
  - ".okf/contract/containment-directions.md"
52
55
  - ".okf/contract/exit-codes.md"
53
56
  - ".okf/contract/gates-only-at-the-hook-door.md"
@@ -77,6 +80,15 @@ files:
77
80
  - ".okf/seam/index.md"
78
81
  - ".okf/seam/the-wrapper.md"
79
82
  - ".okf/seam/three-fail-opens.md"
83
+ - ".okf/structure/doors.md"
84
+ - ".okf/structure/index.md"
85
+ - ".okf/structure/reading-the-world.md"
86
+ - ".okf/structure/the-gates.md"
87
+ - ".okf/structure/the-guards.md"
88
+ - ".okf/structure/the-recorder.md"
89
+ - ".okf/structure/the-scaffold.md"
90
+ - ".okf/structure/the-writers.md"
91
+ - ".okf/testing/adding-a-verb.md"
80
92
  - ".okf/testing/drills-over-units.md"
81
93
  - ".okf/testing/fixture-is-a-client.md"
82
94
  - ".okf/testing/index.md"
@@ -139,14 +151,14 @@ files:
139
151
  - lib/okf/pro/template/seed/gitignore
140
152
  - lib/okf/pro/version.rb
141
153
  - lib/okf/pro/writes.rb
142
- homepage: https://github.com/serradura/okf-gem
154
+ homepage: https://github.com/serradura/okf
143
155
  licenses:
144
156
  - Apache-2.0
145
157
  metadata:
146
158
  allowed_push_host: https://rubygems.org
147
- homepage_uri: https://github.com/serradura/okf-gem
148
- source_code_uri: https://github.com/serradura/okf-gem
149
- changelog_uri: https://github.com/serradura/okf-gem/blob/main/okf-pro/CHANGELOG.md
159
+ homepage_uri: https://github.com/serradura/okf
160
+ source_code_uri: https://github.com/serradura/okf
161
+ changelog_uri: https://github.com/serradura/okf/blob/main/gems/okf-pro/CHANGELOG.md
150
162
  rubygems_mfa_required: 'true'
151
163
  rdoc_options: []
152
164
  require_paths: