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.
- checksums.yaml +7 -0
- data/.okf/contract/containment-directions.md +107 -0
- data/.okf/contract/exit-codes.md +42 -0
- data/.okf/contract/gates-only-at-the-hook-door.md +39 -0
- data/.okf/contract/index.md +10 -0
- data/.okf/contract/silent-skips.md +78 -0
- data/.okf/contract/telemetry-does-not-lie.md +218 -0
- data/.okf/contract/the-contract.md +43 -0
- data/.okf/design/a-comment-is-not-an-implementation.md +85 -0
- data/.okf/design/a-rule-you-can-walk-past.md +68 -0
- data/.okf/design/derivation-that-writes.md +161 -0
- data/.okf/design/failure-modes.md +43 -0
- data/.okf/design/index.md +18 -0
- data/.okf/design/lineage.md +95 -0
- data/.okf/design/structure-laws.md +46 -0
- data/.okf/design/the-residue.md +44 -0
- data/.okf/design/three-laws.md +71 -0
- data/.okf/design/three-pillars.md +48 -0
- data/.okf/index.md +31 -0
- data/.okf/log.md +74 -0
- data/.okf/scaffold/collisions-and-refusals.md +76 -0
- data/.okf/scaffold/index.md +8 -0
- data/.okf/scaffold/no-date-ships.md +41 -0
- data/.okf/scaffold/ownership-not-subject.md +63 -0
- data/.okf/scaffold/the-adopters-manual.md +114 -0
- data/.okf/seam/bundler-scoping.md +45 -0
- data/.okf/seam/identity-not-existence.md +67 -0
- data/.okf/seam/index.md +8 -0
- data/.okf/seam/the-wrapper.md +42 -0
- data/.okf/seam/three-fail-opens.md +44 -0
- data/.okf/testing/drills-over-units.md +41 -0
- data/.okf/testing/fixture-is-a-client.md +35 -0
- data/.okf/testing/index.md +4 -0
- data/.okf/trust/index.md +7 -0
- data/.okf/trust/read-owed-rule.md +45 -0
- data/.okf/trust/scalar-verified.md +48 -0
- data/CHANGELOG.md +187 -0
- data/LICENSE.txt +201 -0
- data/NOTICE +10 -0
- data/README.md +383 -0
- data/lib/okf/plugin.rb +148 -0
- data/lib/okf/pro/attestation.rb +62 -0
- data/lib/okf/pro/audit.rb +161 -0
- data/lib/okf/pro/board/edit.rb +201 -0
- data/lib/okf/pro/board.rb +291 -0
- data/lib/okf/pro/budget.rb +144 -0
- data/lib/okf/pro/bundle_root.rb +241 -0
- data/lib/okf/pro/cli.rb +914 -0
- data/lib/okf/pro/closing.rb +206 -0
- data/lib/okf/pro/conformance.rb +127 -0
- data/lib/okf/pro/conserve.rb +94 -0
- data/lib/okf/pro/event.rb +95 -0
- data/lib/okf/pro/friction.rb +262 -0
- data/lib/okf/pro/guards.rb +92 -0
- data/lib/okf/pro/log/edit.rb +68 -0
- data/lib/okf/pro/log.rb +162 -0
- data/lib/okf/pro/pairing.rb +271 -0
- data/lib/okf/pro/reconcile.rb +76 -0
- data/lib/okf/pro/records.rb +90 -0
- data/lib/okf/pro/scaffold.rb +258 -0
- data/lib/okf/pro/shell_guard.rb +164 -0
- data/lib/okf/pro/snapshot.rb +156 -0
- data/lib/okf/pro/state.rb +174 -0
- data/lib/okf/pro/target.rb +91 -0
- data/lib/okf/pro/template/gem/.claude/hooks/run +119 -0
- data/lib/okf/pro/template/gem/.claude/skills/okf-pro/SKILL.md +233 -0
- data/lib/okf/pro/template/gem/.claude/skills/okf-pro/guides/attribution.md +46 -0
- data/lib/okf/pro/template/gem/.claude/skills/okf-pro/guides/board.md +55 -0
- data/lib/okf/pro/template/gem/.claude/skills/okf-pro/guides/closing.md +21 -0
- data/lib/okf/pro/template/gem/.claude/skills/okf-pro/guides/frontmatter.md +73 -0
- data/lib/okf/pro/template/gem/.claude/skills/okf-pro/guides/okf-rules.md +25 -0
- data/lib/okf/pro/template/gem/.githooks/pre-commit +68 -0
- data/lib/okf/pro/template/gem/.github/workflows/okf-pro.yml +69 -0
- data/lib/okf/pro/template/seed/.claude/settings.json +24 -0
- data/lib/okf/pro/template/seed/.okf/areas/corpus.md +73 -0
- data/lib/okf/pro/template/seed/.okf/areas/index.md +6 -0
- data/lib/okf/pro/template/seed/.okf/board.md +21 -0
- data/lib/okf/pro/template/seed/.okf/glossary/index.md +13 -0
- data/lib/okf/pro/template/seed/.okf/index.md +30 -0
- data/lib/okf/pro/template/seed/.okf/journal/index.md +11 -0
- data/lib/okf/pro/template/seed/.okf/learnings/index.md +13 -0
- data/lib/okf/pro/template/seed/.okf/log.md +10 -0
- data/lib/okf/pro/template/seed/.okf/projects/index.md +23 -0
- data/lib/okf/pro/template/seed/.okf/reference/index.md +16 -0
- data/lib/okf/pro/template/seed/.okf/roadmap.md +33 -0
- data/lib/okf/pro/template/seed/.tmp/.gitkeep +0 -0
- data/lib/okf/pro/template/seed/CLAUDE.md +61 -0
- data/lib/okf/pro/template/seed/README.md +488 -0
- data/lib/okf/pro/template/seed/gitignore +17 -0
- data/lib/okf/pro/version.rb +7 -0
- data/lib/okf/pro/writes.rb +560 -0
- data/lib/okf/pro.rb +198 -0
- 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
|