okf-pro 1.0.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 (93) hide show
  1. checksums.yaml +7 -0
  2. data/.okf/contract/containment-directions.md +107 -0
  3. data/.okf/contract/exit-codes.md +42 -0
  4. data/.okf/contract/gates-only-at-the-hook-door.md +39 -0
  5. data/.okf/contract/index.md +10 -0
  6. data/.okf/contract/silent-skips.md +78 -0
  7. data/.okf/contract/telemetry-does-not-lie.md +218 -0
  8. data/.okf/contract/the-contract.md +43 -0
  9. data/.okf/design/a-comment-is-not-an-implementation.md +85 -0
  10. data/.okf/design/a-rule-you-can-walk-past.md +68 -0
  11. data/.okf/design/derivation-that-writes.md +161 -0
  12. data/.okf/design/failure-modes.md +43 -0
  13. data/.okf/design/index.md +18 -0
  14. data/.okf/design/lineage.md +95 -0
  15. data/.okf/design/structure-laws.md +46 -0
  16. data/.okf/design/the-residue.md +44 -0
  17. data/.okf/design/three-laws.md +71 -0
  18. data/.okf/design/three-pillars.md +48 -0
  19. data/.okf/index.md +31 -0
  20. data/.okf/log.md +74 -0
  21. data/.okf/scaffold/collisions-and-refusals.md +76 -0
  22. data/.okf/scaffold/index.md +8 -0
  23. data/.okf/scaffold/no-date-ships.md +41 -0
  24. data/.okf/scaffold/ownership-not-subject.md +63 -0
  25. data/.okf/scaffold/the-adopters-manual.md +114 -0
  26. data/.okf/seam/bundler-scoping.md +45 -0
  27. data/.okf/seam/identity-not-existence.md +67 -0
  28. data/.okf/seam/index.md +8 -0
  29. data/.okf/seam/the-wrapper.md +42 -0
  30. data/.okf/seam/three-fail-opens.md +44 -0
  31. data/.okf/testing/drills-over-units.md +41 -0
  32. data/.okf/testing/fixture-is-a-client.md +35 -0
  33. data/.okf/testing/index.md +4 -0
  34. data/.okf/trust/index.md +7 -0
  35. data/.okf/trust/read-owed-rule.md +45 -0
  36. data/.okf/trust/scalar-verified.md +48 -0
  37. data/CHANGELOG.md +187 -0
  38. data/LICENSE.txt +201 -0
  39. data/NOTICE +10 -0
  40. data/README.md +383 -0
  41. data/lib/okf/plugin.rb +148 -0
  42. data/lib/okf/pro/attestation.rb +62 -0
  43. data/lib/okf/pro/audit.rb +161 -0
  44. data/lib/okf/pro/board/edit.rb +201 -0
  45. data/lib/okf/pro/board.rb +291 -0
  46. data/lib/okf/pro/budget.rb +144 -0
  47. data/lib/okf/pro/bundle_root.rb +241 -0
  48. data/lib/okf/pro/cli.rb +914 -0
  49. data/lib/okf/pro/closing.rb +206 -0
  50. data/lib/okf/pro/conformance.rb +127 -0
  51. data/lib/okf/pro/conserve.rb +94 -0
  52. data/lib/okf/pro/event.rb +95 -0
  53. data/lib/okf/pro/friction.rb +262 -0
  54. data/lib/okf/pro/guards.rb +92 -0
  55. data/lib/okf/pro/log/edit.rb +68 -0
  56. data/lib/okf/pro/log.rb +162 -0
  57. data/lib/okf/pro/pairing.rb +271 -0
  58. data/lib/okf/pro/reconcile.rb +76 -0
  59. data/lib/okf/pro/records.rb +90 -0
  60. data/lib/okf/pro/scaffold.rb +258 -0
  61. data/lib/okf/pro/shell_guard.rb +164 -0
  62. data/lib/okf/pro/snapshot.rb +156 -0
  63. data/lib/okf/pro/state.rb +174 -0
  64. data/lib/okf/pro/target.rb +91 -0
  65. data/lib/okf/pro/template/gem/.claude/hooks/run +119 -0
  66. data/lib/okf/pro/template/gem/.claude/skills/okf-pro/SKILL.md +233 -0
  67. data/lib/okf/pro/template/gem/.claude/skills/okf-pro/guides/attribution.md +46 -0
  68. data/lib/okf/pro/template/gem/.claude/skills/okf-pro/guides/board.md +55 -0
  69. data/lib/okf/pro/template/gem/.claude/skills/okf-pro/guides/closing.md +21 -0
  70. data/lib/okf/pro/template/gem/.claude/skills/okf-pro/guides/frontmatter.md +73 -0
  71. data/lib/okf/pro/template/gem/.claude/skills/okf-pro/guides/okf-rules.md +25 -0
  72. data/lib/okf/pro/template/gem/.githooks/pre-commit +68 -0
  73. data/lib/okf/pro/template/gem/.github/workflows/okf-pro.yml +69 -0
  74. data/lib/okf/pro/template/seed/.claude/settings.json +24 -0
  75. data/lib/okf/pro/template/seed/.okf/areas/corpus.md +73 -0
  76. data/lib/okf/pro/template/seed/.okf/areas/index.md +6 -0
  77. data/lib/okf/pro/template/seed/.okf/board.md +21 -0
  78. data/lib/okf/pro/template/seed/.okf/glossary/index.md +13 -0
  79. data/lib/okf/pro/template/seed/.okf/index.md +30 -0
  80. data/lib/okf/pro/template/seed/.okf/journal/index.md +11 -0
  81. data/lib/okf/pro/template/seed/.okf/learnings/index.md +13 -0
  82. data/lib/okf/pro/template/seed/.okf/log.md +10 -0
  83. data/lib/okf/pro/template/seed/.okf/projects/index.md +23 -0
  84. data/lib/okf/pro/template/seed/.okf/reference/index.md +16 -0
  85. data/lib/okf/pro/template/seed/.okf/roadmap.md +33 -0
  86. data/lib/okf/pro/template/seed/.tmp/.gitkeep +0 -0
  87. data/lib/okf/pro/template/seed/CLAUDE.md +61 -0
  88. data/lib/okf/pro/template/seed/README.md +488 -0
  89. data/lib/okf/pro/template/seed/gitignore +17 -0
  90. data/lib/okf/pro/version.rb +7 -0
  91. data/lib/okf/pro/writes.rb +560 -0
  92. data/lib/okf/pro.rb +198 -0
  93. metadata +169 -0
data/README.md ADDED
@@ -0,0 +1,383 @@
1
+ <h1 align="center">OKF Pro</h1>
2
+ <p align="center"><em>Structure for making things happen!</em></p>
3
+
4
+ **Get better at what you do by structuring how you do it.** Being good at
5
+ something has a shape — you know what you are committed to, you can tell what
6
+ you know from what you have merely read, you do not relearn the same lesson
7
+ twice, you can say where a claim came from. Most people arrive at that shape
8
+ after ten years, by accident. This is the shape written down and held in place.
9
+
10
+ An agent that writes notes accumulates a folder. An agent held to a few
11
+ invariants accumulates a memory. okf-pro is those invariants, run at three doors
12
+ — while the agent edits, when you commit, and again in CI — plus the
13
+ `okf pro setup` that writes the whole arrangement into a repository.
14
+
15
+ It is **a profile of [OKF](https://okfgem.com)**: one opinionated shape a
16
+ knowledge bundle can take, and the gates that hold it in that shape. OKF is an
17
+ open format for durable knowledge — directories of Markdown with YAML
18
+ frontmatter, read by people and agents alike. Any OKF bundle is already a brain:
19
+ authored concepts, a graph, retrievable. That stays general. What this adds is a
20
+ bundle that also refuses to let its own knowledge rot.
21
+
22
+ **What it is for, plainly.** One person's working practice, in **its own
23
+ repository** — not `.okf/` inside your application. It is domain-agnostic by
24
+ construction: none of the rules mentions code, so it works the same for law,
25
+ research, medicine, design or running a team. `setup` writes a whole
26
+ repository's worth of files (`CLAUDE.md`, `README.md`, a workflow), so point it
27
+ at an empty directory. It will complete an existing one — nothing is ever
28
+ overwritten — but that is the recovery path, not the design.
29
+
30
+ **It is not a paid tier** — Apache 2.0, like everything else in the ecosystem.
31
+ The name is an equation, and it is the whole argument in miniature:
32
+
33
+ > **pro(file) + pro(cess) = pro(fessional)**
34
+
35
+ A *profile* is the structure: the term of art for a constrained application of a
36
+ general format, which is exactly what this is, and what holds your *pro(gress)*
37
+ — the board, the journal, the roadmap. A *process* is that structure enforced
38
+ rather than remembered. Neither alone makes anyone better at anything; together
39
+ they are what a professional actually has, and what nobody is handed at the
40
+ start.
41
+
42
+ ```sh
43
+ gem install okf-pro # brings okf with it
44
+ okf skill ~/.claude # the format itself — once per machine
45
+
46
+ mkdir my-brain && cd my-brain # its own repository, not your app's
47
+ git init
48
+ okf pro setup . # bundle + hooks + pre-commit + CI + skill
49
+ git config core.hooksPath .githooks # per clone; hooks do not travel
50
+ ```
51
+
52
+ `okf pro` appears in `okf help` because this gem registers itself through okf's
53
+ plugin seam; there is no second binary.
54
+
55
+ **The second line is a dependency, not a nicety.** Two skills are involved and
56
+ they do different jobs. okf's teaches your agent the *format* — what a concept
57
+ is, what frontmatter it carries, how links resolve, which verb answers which
58
+ question. okf-pro's, installed into the repository by `setup`, teaches the
59
+ *rules on top of it*. Skip the first and you have an agent held to invariants
60
+ about a format nobody taught it, which fails in the least useful way: it knows
61
+ the board is capped at five and not what a concept looks like.
62
+
63
+ Install it into your home directory rather than a project, and every bundle on
64
+ the machine is covered — the ones this gem governs and the ones it does not.
65
+
66
+ **What you need.** Git, Ruby ≥ 2.4, and a coding agent — the agent-time gates
67
+ are written for [Claude Code](https://claude.com/claude-code) and fire at its
68
+ tool boundary. Without one you keep the commit and CI doors and lose the
69
+ attestation gate, which is the most valuable thing here. Worth knowing before
70
+ you invest a week.
71
+
72
+ ## What it enforces
73
+
74
+ Three laws, and the reason each exists.
75
+
76
+ **Rule 1 — writing is reconciliation.** Before a new concept settles, the corpus is
77
+ searched for what it collides with. A contradiction that cannot be settled on
78
+ the spot becomes one dated line on the board — because an unresolved
79
+ contradiction is work, and work that is not on the board is work nobody is
80
+ tracking.
81
+
82
+ **Rule 2 — the day ends with a snapshot.** One mechanical line under the day's heading in
83
+ `log.md`: inbox, oldest, in flight, waiting, backlog, to read, unverified
84
+ briefings, conflicts, deadlines. Its value is the delta. A standing count is
85
+ wallpaper; "+8 inbox, oldest now 6d" is information. The gate computes the line
86
+ and refuses one that disagrees with the board it summarises.
87
+
88
+ **Rule 3 — in flight is a budget.** Five demands, and promoting from the backlog means
89
+ demoting something — or renegotiating the cap, visibly, in the journal. The
90
+ cap's job is not to make five the right number; it is to make overload
91
+ undeniable on the day it happens.
92
+
93
+ And one rule that is not a law but is the reason the rest can be trusted: an
94
+ agent may hold the pen for `verified:`, but the write is routed to you for
95
+ approval, and **the approval is the attestation**. Absent `verified:` on a
96
+ generated concept is the truth about it, not a defect to be tidied away.
97
+
98
+ ## A day with it
99
+
100
+ **You do not run these commands. You talk to your agent, the way you already
101
+ do** — the verbs below are what it reaches for, and the gates hold you both.
102
+ Here is the shape of a day, and the vocabulary the rest of this page uses.
103
+
104
+ **Session start.** Your agent is handed the board state before it does anything,
105
+ by the SessionStart banner, so the first question of a session is answered
106
+ without a call being spent on it. `journal open` creates today's entry.
107
+
108
+ **All day.** *"capture: Ana wants the pricing sheet before Thursday."* Five
109
+ seconds, in your own words, into the board's **Inbox**. No filing, no decision.
110
+
111
+ **Once a day.** You read the Inbox and triage it — the only part that is
112
+ genuinely yours, because it is judgement. Most lines die there. What is real
113
+ work gets promoted into **In flight**, which holds at most five, and a sixth is
114
+ refused until something is demoted. That refusal is the point: it makes you
115
+ compare five commitments and say which is worth less, which is the work of
116
+ prioritisation nobody does voluntarily.
117
+
118
+ **Anything worth keeping.** *"file a briefing on this in reference/."* Your
119
+ agent writes the concept, carrying `generated:`. It may not add `verified:` —
120
+ that write is held and put to you, and your approval is the attestation.
121
+
122
+ **End of day.** The stop gate refuses to close a day you changed until its
123
+ counter line is in `log.md`, and computes the line for you so the fix is a
124
+ paste. Judgement goes in the journal, arithmetic in the log, and they stay apart
125
+ because the moment an opinion leaks into a counter it stops being comparable
126
+ with yesterday's.
127
+
128
+ The return is the delta. "Inbox 14" tells you nothing; "+8 inbox, oldest now six
129
+ days" tells you what your week did to you — and a deadline landing Wednesday
130
+ with nothing in flight against it shows up on Monday, as an arithmetic
131
+ consequence of the board being honest rather than a reminder anyone configured.
132
+
133
+ Call it twenty minutes of *your* time on a day you worked, and nothing at all on
134
+ a day you did not, because a session that changed nothing closes in silence.
135
+ Your agent pays none of it.
136
+
137
+ ## The three doors
138
+
139
+ | Door | When | What it sees |
140
+ |---|---|---|
141
+ | the agent's hooks | the agent's tool boundary | every write into the bundle, before and after |
142
+ | `.githooks/pre-commit` | `git commit` | the **staged** tree, plus the append-only record |
143
+ | `.github/workflows/` | push | everything, on a machine that configured nothing |
144
+
145
+ They exist separately because each sees edits the others cannot. Hooks fire only
146
+ inside the agent; git hooks only on a clone that ran `git config core.hooksPath`;
147
+ CI only on what was pushed. Edit a file in your own editor and the agent hooks
148
+ never see it at all: the commit door catches it if you armed one, and CI catches
149
+ it either way.
150
+
151
+ The first door is six checks, and what each one does when it fires:
152
+
153
+ | Check | Fires on | What it does |
154
+ |---|---|---|
155
+ | `guard-verified` | a write carrying `verified:` | **holds the write** and puts it to you — approve and it lands, deny and it does not |
156
+ | `journal-guard` | a write into `journal/` | **refuses** editing a past day; asks before creating one |
157
+ | `shell-guard` | a Bash command | **holds it** and puts it to you when the command would write into the bundle |
158
+ | `post-edit` | after every write | **reports** — validate, lint, and Rule 1's collisions |
159
+ | `stop-gate` | end of session | **refuses** to close a day you changed without its snapshot |
160
+ | `session-context` | session start | **informs** — hands the agent the board state, costing no call |
161
+
162
+ Two of the six hold a tool call *before* it runs and hand you the decision;
163
+ two refuse outright; one reports after the fact; one only informs. Rule 1 is on
164
+ the reporting side deliberately. It fires when a *new* concept is
165
+ written, names the existing concepts sharing its vocabulary, and tells the agent
166
+ to settle it before continuing — a collision is a judgement, and a gate that
167
+ hard-blocked on shared words would be wrong far more often than right.
168
+
169
+ `settings.json` points all six at one bash wrapper, `.claude/hooks/run`, rather
170
+ than at `okf` directly. That indirection is the contract below being kept: a
171
+ Ruby checker cannot refuse on its own absence, so something already running has
172
+ to.
173
+
174
+ ## The contract
175
+
176
+ > Blocking checks fail **closed**. Feedback checks fail **loud**. No check ever
177
+ > fails **silent**.
178
+
179
+ The third clause is the one that shapes the code. A gate that is sometimes
180
+ absent and does not say so converts "unchecked" into "checked and fine", and
181
+ there is no later moment at which anyone finds out. So a missing checker, an
182
+ unloadable library, a stray `okf` on `PATH`, an exit code the gate did not
183
+ choose — each of them refuses, by name.
184
+
185
+ ## Commands
186
+
187
+ ```
188
+ okf pro setup [DIR] create or complete an agent's brain in DIR (default .)
189
+ okf pro upgrade [DIR] rewrite the gem-owned governance files; stage the rest
190
+ okf pro state [DIR] what is on the board, in one call — --full adds the corpus
191
+ okf pro board [DIR] one row per board line: section, dates, age, links
192
+ okf pro capture TEXT append a dated Inbox line
193
+ okf pro promote SEL Inbox or Backlog to In flight, refusing over the cap
194
+ okf pro demote SEL In flight back to Backlog
195
+ okf pro journal open create today's journal day and index it
196
+ okf pro close SLUG the three mechanical closing moves for a project
197
+ okf pro audit [DIR] every invariant at once — the CI door
198
+ okf pro records [DIR] does the staged commit rewrite a past journal day?
199
+ okf pro snapshot [DIR] compute the day's counter line (prints, never writes)
200
+ okf pro unverified [DIR] concepts still awaiting the owner's read
201
+ okf pro friction [DIR] what was done by hand that a verb could do (--clear resets)
202
+ okf pro skill DEST (re)install the agent skill on its own
203
+ okf pro hook CHECK run one gate against a hook event on stdin
204
+ ```
205
+
206
+ Three exit conventions, and the differences are deliberate:
207
+
208
+ * **Readers** — `audit`, `records`, `state`, `board`, `snapshot`, `unverified`,
209
+ `friction` — follow okf's: `0` clean, `1` findings, `2` could not run.
210
+ * **Writers** — `capture`, `promote`, `demote`, `journal`, `close` — answer `0`
211
+ or `2` and never `1`. Either the edit landed, or nothing was touched: a rule
212
+ refused it, a selector matched nothing or matched twice, or the conservation
213
+ guard caught a delta that was not the declared one. `2` on a writer always
214
+ means the file is exactly as it was.
215
+ * **`hook`** follows the Claude Code hook protocol — `0` passes, `2` blocks. The
216
+ protocol reads `1` as non-blocking, so a gate that exited `1` would let the
217
+ edit through.
218
+
219
+ `state`, `board`, `snapshot`, `unverified` and `friction` take `--json` (and
220
+ `--pretty`), because their consumer is as often an agent as a person. `audit`
221
+ and `records` do not: what they answer with is the exit code, and a pipeline
222
+ reads that. `okf pro --help` lists what each verb takes.
223
+
224
+ Against a bundle a few weeks in — not a fresh one, which reports zeroes:
225
+
226
+ ```console
227
+ $ okf pro state
228
+ Board — in flight 1/5 · backlog 0 · waiting 1 (1 past chase) · inbox 2 (oldest 4d) · to read 1 · deadlines 1 · conflicts open 0
229
+ Deadlines within 7d with nothing in flight against them:
230
+ - 2026-08-19 — insurance renewal lapses
231
+ Log — newest day 2026-08-16 · journal for 2026-08-17 not opened
232
+ Open projects (1): home-move
233
+ As of 2026-08-16, the last logged snapshot — unverified briefings 1 · projects with 0 concepts 1 (`--full` recomputes these live)
234
+ ```
235
+
236
+ ### The writers, and why they refuse
237
+
238
+ Five shapes have exactly one correct form, and reconstructing them from prose
239
+ on every use is what an agent was doing instead: a third of one measured
240
+ session's tool output was this gem's own guides, read to learn where a date
241
+ goes. The verbs know.
242
+
243
+ They are **additive and targeted, never regenerative**. A verb appends a line
244
+ or edits the line it was given, and none of them rewrites a file. That is
245
+ enforced rather than promised: each one computes its new text purely, declares
246
+ the delta it intends, and a conservation guard compares line multisets before
247
+ anything is written. If the actual delta is not the declared one — a line
248
+ dropped alongside the append, a promotion that silently did nothing — it exits
249
+ 2 and the file is untouched.
250
+
251
+ ```console
252
+ $ okf pro capture "the invoice from acme needs checking"
253
+ okf pro capture — one line added to Inbox:
254
+ - 2026-08-17 — the invoice from acme needs checking
255
+
256
+ $ okf pro promote "parking permit"
257
+ okf pro promote — RULE 3: 5 in flight against a cap of 5, so promoting makes 6. Promotion requires demotion (`okf pro demote`), or a visible renegotiation of the cap — which is journal-worthy, and yours to make.
258
+ ```
259
+
260
+ Selectors are keyed, never positional: a `/projects/<slug>` link, a bare slug,
261
+ or a substring only one line carries. Two matches is a refusal listing both,
262
+ not a coin toss — agents rewrite, and "the third line under Backlog" names a
263
+ different commitment after any edit anyone makes.
264
+
265
+ And what stays judgment stays yours. `close` performs the three mechanical
266
+ moves — the marker, the board lines, the log entry — and reports the fourth,
267
+ extracting the durable part to `learnings/`, as owed. No verb writes a concept
268
+ body, and none sets `verified:`.
269
+
270
+ ## What `setup` writes
271
+
272
+ ```
273
+ .okf/ the bundle: board, log, journal, and five zones, all empty
274
+ .claude/hooks/run the fail-closed wrapper the six agent gates point at
275
+ .claude/settings.json the hook table
276
+ .claude/skills/ the okf-pro skill — the operating rules, in full
277
+ .githooks/pre-commit the commit door
278
+ .github/workflows/ the push door
279
+ CLAUDE.md .gitignore .tmp/
280
+ README.md a front page for the adopter, theirs to rewrite
281
+ ```
282
+
283
+ The five zones are `reference/` (what other people produced), `learnings/` (what
284
+ you concluded), `glossary/` (what a word means here), `projects/` (work with a
285
+ definition of done) and `areas/` (a standard held indefinitely). A concept is
286
+ one Markdown file in one of them:
287
+
288
+ ```markdown
289
+ ---
290
+ type: Briefing
291
+ title: Incremental static regeneration
292
+ description: What ISR actually guarantees about staleness, and where that breaks.
293
+ generated: { by: claude/opus-5, at: 2026-08-17T09:12:00Z }
294
+ stale_after: 2026-11-15
295
+ sources:
296
+ - id: nextjs-isr
297
+ resource: https://nextjs.org/docs/app/guides/incremental-static-regeneration
298
+ title: Next.js docs — Incremental Static Regeneration
299
+ ---
300
+
301
+ # What it guarantees
302
+ ...
303
+ ```
304
+
305
+ `generated:` says a machine wrote it and nobody has checked. Adding `verified:`
306
+ is the write that routes to you, and until it does the concept is **unverified**
307
+ — which is a true statement about it, not a defect.
308
+
309
+ **Structure, not content.** Every zone ships with an index describing its room
310
+ and listing nothing; the first knowledge filed is yours. And **no date ships
311
+ anywhere**: a template is cloned an unknowable number of days after it is
312
+ built, and dormancy measures a bundle's age by its oldest journal entry — a
313
+ shipped entry would make a fresh clone read as an old one.
314
+
315
+ Run it again whenever you like. Files you already have are never overwritten:
316
+ the template's version is staged beside yours as `<path>.okf-pro-new` for you
317
+ to merge. `okf pro upgrade` refreshes the four files this gem owns — the hook wrapper,
318
+ the pre-commit hook, the workflow and the okf-pro skill — and leaves everything
319
+ else, including `CLAUDE.md`, `README.md`, `settings.json` and all of `.okf/`,
320
+ exactly as you left it.
321
+
322
+ `journal/` and `log.md` are both dated and easy to confuse: the journal is
323
+ *prose* — one entry per working day, what happened and what it meant — and the
324
+ log is the *record*, one line per change plus the day's counters. Judgement goes
325
+ in one, arithmetic in the other, and keeping them apart is what makes a counter
326
+ comparable with yesterday's.
327
+
328
+ Concepts themselves are written by your agent, not by a verb: you ask for a
329
+ briefing and it files the Markdown. That is the deliberate division — the verbs
330
+ own the shapes that have exactly one correct form, and everything requiring
331
+ judgement stays with you and your agent.
332
+
333
+ The seeded `README.md` is the adopter's manual — the first hour, the refusals,
334
+ the tunable numbers, and how to turn every gate off. This page is the gem's;
335
+ that one is the repository's.
336
+
337
+ ## Prior art
338
+
339
+ If `projects/ areas/ reference/` looks familiar, it should: that is Tiago
340
+ Forte's PARA, and capture-then-triage owes a great deal to GTD. What is not
341
+ borrowed is the enforcement. PARA tells you where things go, and nothing in it
342
+ stops you running eleven active projects or citing a summary you never read.
343
+ The one structural departure is splitting PARA's single Resources room into
344
+ `reference/`, `learnings/` and `glossary/` — three retrieval questions asked at
345
+ three different moments, argued in the seeded `areas/corpus.md`.
346
+
347
+ ## Turning it off
348
+
349
+ A system built out of refusals owes you a way to stop being refused, and every
350
+ lever is a file you edit. One gate: delete its entry from
351
+ `.claude/settings.json`. All the agent-time gates: remove the `hooks` block,
352
+ keeping the commit and CI doors. The commit door: `git config --unset
353
+ core.hooksPath`, or `--no-verify` for one commit. Leaving entirely: delete
354
+ `.claude/`, `.githooks/`, `.github/` and `CLAUDE.md`, and `.okf/` remains a
355
+ valid OKF bundle that `okf validate` passes on its own.
356
+
357
+ There is no environment variable that turns a gate off, because none is read: a
358
+ gate a stray `export` could disable is one you would have no reason to trust.
359
+ The seeded README documents each lever in full.
360
+
361
+ ## What this gem does not do
362
+
363
+ It answers no question okf can answer. Conformance, curation, search, the
364
+ graph, the trust tiers — all of that is the kernel's, and a second
365
+ implementation that disagreed with it by a day or a rule would be worse than
366
+ none. What lives here is the policy on top of those answers, and the machinery
367
+ that makes a gate refuse rather than shrug.
368
+
369
+ It is also **a passport, not a lie detector**: it can prove where a claim came
370
+ from, who summarised it, and whether a human ever checked it, and it cannot tell
371
+ you whether the claim is true. What that buys is a well-sourced mistake staying
372
+ catchable. The collision check, likewise, is only as good as the vocabulary you
373
+ searched with. And every rule assumes **one owner** — your cap, your approval,
374
+ your day; two people sharing one board is not a supported arrangement.
375
+
376
+ Its own knowledge bundle ships inside the gem, at `.okf/` — the non-obvious
377
+ parts, written down: the three ways the plugin seam let an unchecked edit
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.
380
+
381
+ ## Licence
382
+
383
+ Apache-2.0. Built by [Rodrigo Serradura](https://github.com/serradura).
data/lib/okf/plugin.rb ADDED
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The extension point okf discovers: `okf/plugin.rb` anywhere on the load path.
4
+ # Installing this gem is the whole installation — `okf pro` works with no
5
+ # configuration, and `okf help` lists it. This gem ships no executable, so this
6
+ # file is the entry point rather than a second way in.
7
+ #
8
+ # okf loads this file, not the library. Everything expensive stays behind #call:
9
+ # this file is read whenever a verb misses or `okf help` runs, and registering a
10
+ # class costs nothing.
11
+ #
12
+ # THE REASON THIS FILE IS NOT THIN. Every sibling's plugin.rb is a require and a
13
+ # forward. This one holds the contract's last Ruby-side line, because the seam
14
+ # it sits on fails OPEN in three ways the sibling gems can afford and a gate
15
+ # cannot:
16
+ #
17
+ # 1. `okf/exe/okf` is `exit OKF::CLI.start(ARGV)` and CLI#dispatch calls
18
+ # `#call` with no rescue. A LoadError out of the deferred
19
+ # `require "okf/pro"` is a ScriptError — outside every rescue on the
20
+ # path, including discovery's, which wraps only `require path` and catches
21
+ # `LoadError, StandardError`. Measured: process exit 1, and the hook
22
+ # protocol reads 1 as non-blocking, so the edit proceeds unchecked.
23
+ # 2. A check that returns something other than an Integer — or a CLI ported
24
+ # from a standalone binary that calls `exit(true)` — lands on the same
25
+ # hole: `exit(true)` is 0 and `exit(nil)` is 1, and neither is a verdict.
26
+ # 3. A SyntaxError in THIS file is not rescued by discovery either, for the
27
+ # same ScriptError reason, and no code here runs at all. Nothing in Ruby
28
+ # can catch that one; the scaffold's `.claude/hooks/run` is what does, and
29
+ # it is why the wrapper stopped `exec`ing.
30
+ #
31
+ # So the rescue lives here, outside the require it guards, and not inside
32
+ # `Pro::CLI.run` — which cannot catch the LoadError of the require that
33
+ # reaches it, the very fail-open it would exist for.
34
+
35
+ require "okf/cli"
36
+
37
+ # An okf old enough to lack the command registry also lacks the discovery that
38
+ # would load this file, so in practice this is unreachable — it exists so that a
39
+ # hand-written `require "okf/plugin"` against one says what is wrong instead of
40
+ # raising NameError on a constant nobody was looking for. Raised rather than
41
+ # skipped: a plugin that silently declines to register is the failure mode this
42
+ # whole seam is meant to make impossible.
43
+ unless defined?(OKF::CLI::Command)
44
+ raise LoadError, "okf-pro needs an okf with the CLI command registry (OKF::CLI::Command); this okf has none"
45
+ end
46
+
47
+ module OKF
48
+ class CLI
49
+ # `okf pro` — this gem's entry point, and its only one.
50
+ class Pro < Command
51
+ def self.id
52
+ :pro
53
+ end
54
+
55
+ # ONE row, and the first row is the summary.
56
+ #
57
+ # `okf help` prints a map, not a manual: it says a verb exists and who
58
+ # ships it, and `okf pro --help` is where this verb describes itself.
59
+ # Eight rows here made the extensions block the longest section of that
60
+ # map — longer than every built-in group — for a gem the reader may not
61
+ # even have been looking for. The kernel now takes only the first row from
62
+ # an extension, so declaring more would not print them; declaring one is
63
+ # this side of the same decision, and keeps `help_rows` from claiming a
64
+ # surface the map does not show.
65
+ def self.help_rows
66
+ [ [ "pro <command> [DIR]", "scaffold an agent's knowledge repo, and enforce it at three doors" ] ]
67
+ end
68
+
69
+ # The whole public surface, and the whole of the Ruby-side guard.
70
+ #
71
+ # `rescue Exception` is the cop's textbook mistake everywhere else in this
72
+ # repo, and it is the correct call exactly here: the failures this must
73
+ # catch are ScriptErrors, which are not StandardErrors, and catching less
74
+ # than Exception is the same as catching nothing. SystemExit is re-raised
75
+ # rather than swallowed — `okf/pro.rb` refuses an under-floor Ruby with
76
+ # `exit 2`, and a rescue that turned that refusal into an error report
77
+ # would be the fail-open this method exists to close.
78
+ #
79
+ # rubocop:disable Lint/RescueException
80
+ def call(argv)
81
+ verb = argv.first.to_s
82
+
83
+ return refuse_unknown_check(argv) if verb == "hook" && !hook_check?(argv[1])
84
+
85
+ require "okf/pro"
86
+
87
+ status = ::OKF::Pro::CLI.run(argv.dup.tap { |a| a.shift if verb == "hook" },
88
+ stdin: input, stdout: @out, stderr: @err)
89
+
90
+ # A non-Integer status is not a verdict, and `exit` would read it as one:
91
+ # `exit(true)` is 0, `exit(nil)` is 1, and both are a gate saying nothing
92
+ # while the protocol hears "fine".
93
+ status.is_a?(Integer) ? status : blocked("returned #{status.inspect} instead of an exit status")
94
+ rescue ::SystemExit
95
+ raise
96
+ rescue ::Exception => e
97
+ blocked("#{e.class}: #{e.message}")
98
+ end
99
+ # rubocop:enable Lint/RescueException
100
+
101
+ private
102
+
103
+ # Whether the hook door accepts this name.
104
+ #
105
+ # The list it asks is `Pro::CLI::HOOK_NAMES`, deliberately NOT `NAMES`:
106
+ # that one includes the CI verbs, and `Pro::CLI.run` dispatches them off
107
+ # the same first argv element a check name arrives in. An adapter that
108
+ # only stripped `hook` and forwarded would make `okf pro hook audit` run
109
+ # the CI verb — measured status 0, "okf pro audit — clean.", reading no
110
+ # stdin and never blocking. One typo in `settings.json` and the gate is a
111
+ # gate that always says fine.
112
+ #
113
+ # Loading the library to answer this defeats the point of deferring it, so
114
+ # the *check* is deferred instead of the answer: only a `hook` invocation
115
+ # pays for the require, and the whitelist stays the composition table
116
+ # rather than a second copy of it that can drift.
117
+ def hook_check?(name)
118
+ require "okf/pro"
119
+ ::OKF::Pro::CLI::HOOK_NAMES.include?(name.to_s)
120
+ rescue ::SystemExit
121
+ raise
122
+ rescue ::Exception # rubocop:disable Lint/RescueException
123
+ # The library is unloadable, so the name cannot be validated — and an
124
+ # unvalidated name must not be forwarded. Falling through to `#call`'s
125
+ # own require reports the real cause.
126
+ true
127
+ end
128
+
129
+ def refuse_unknown_check(argv)
130
+ given = argv[1].nil? ? "no check name" : "'#{argv[1]}'"
131
+ @err.puts "ENFORCEMENT MISCONFIGURED — `okf pro hook` was given #{given}. The hook door " \
132
+ "accepts only a check name; the CI verbs (audit, records, snapshot, unverified) " \
133
+ "are not gates and would report clean without reading the event at all. " \
134
+ "`okf pro --help` lists the checks."
135
+ 2
136
+ end
137
+
138
+ def blocked(cause)
139
+ @err.puts "ENFORCEMENT DEGRADED — okf pro could not run (#{cause}); nothing was checked, " \
140
+ "so the call is refused. The hook protocol reads every code but 2 as " \
141
+ "non-blocking, which would let this through in silence."
142
+ 2
143
+ end
144
+ end
145
+
146
+ register(Pro)
147
+ end
148
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OKF
4
+ module Pro
5
+ # The attestation surface, listed. This reports and never warns, on
6
+ # purpose: absent `verified:` on a `generated:` concept is the truth, not
7
+ # a defect — a warning that is always present trains its reader to skip
8
+ # it, and the only way to silence it would be to type the one lie the
9
+ # system guards against. The snapshot carries the count as a delta; this
10
+ # carries the names, for an owner deciding what to read next.
11
+ #
12
+ # Corpus-wide, not reference/-scoped like the pairing check: a generated
13
+ # learning or term awaits its read just as much as a briefing does, even
14
+ # though only briefings owe the board a To-read line.
15
+ module Attestation
16
+ module_function
17
+
18
+ # The fourth of the four sites §5.3 governs (see Pairing's comment over
19
+ # `awaiting_read?`). It is here rather than in Pairing because it is
20
+ # corpus-wide and Pairing's is briefing-scoped, but the rule is the same
21
+ # one, asked through the same predicate — and this file used to ask it
22
+ # differently, so a `process:`-verified concept dropped off the list an
23
+ # owner reads to decide what to look at next while still owing the read.
24
+ #
25
+ # The tier is printed rather than merely used: "machine-confirmed" and
26
+ # "unverified" are different states, and an owner deciding what to read
27
+ # next is exactly the reader for whom that difference is the point.
28
+ # `concepts:` is the same courtesy Pairing.failures and Snapshot.counters
29
+ # already extend: a caller holding the parse hands it over. `okf pro
30
+ # state --full` asks this, the pairing invariants and the live
31
+ # unverified count in one breath, and without the seam it parsed the
32
+ # bundle twice to answer one flag.
33
+ def report(root, concepts: nil)
34
+ render(rows(root, concepts: concepts))
35
+ end
36
+
37
+ # The same list, unrendered, for `--json` and for `okf pro state --full`.
38
+ # Split from the rendering rather than parsed back out of it: a consumer
39
+ # that had to take a sentence apart with a regex would break on the next
40
+ # wording change, and the wording is prose written for a person.
41
+ def rows(root, concepts: nil)
42
+ concepts ||= ::OKF::Bundle::Reader.read(root).concepts
43
+ concepts.map do |concept|
44
+ next unless Pairing.awaiting_read?(concept)
45
+
46
+ { "id" => concept.id, "by" => concept.generated_by,
47
+ "at" => concept.generated_at&.to_s,
48
+ "trust" => concept.trust.to_s }
49
+ end.compact
50
+ end
51
+
52
+ # One renderer, so `okf pro unverified` and `okf pro state --full` say
53
+ # the same sentence about the same concept.
54
+ def render(rows)
55
+ rows.map do |row|
56
+ " #{row["id"]}: generated#{" by #{row["by"]}" if row["by"]}#{" on #{row["at"]}" if row["at"]} " \
57
+ "[#{row["trust"]}] — awaiting the owner's read"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end