okf 2.0.0 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43476118366ed4610628f4692f1c1432e4babe4a9d5749f06eb69fc41738c4e5
4
- data.tar.gz: 218144bd22483995d933cf9d89f44e9602c2c95255d9e1c95626700a4f5b2f2e
3
+ metadata.gz: 047d30628ab36abdfe517a2722753dc8f4a6eb60cea9a41d6015508d64784af6
4
+ data.tar.gz: 637ce26fbb8375b9c4d0daeab6ef6bdc8b8cee0258838e73151944b772445c47
5
5
  SHA512:
6
- metadata.gz: 40ccd9ca9a9fe8580856825a972753b0f58ff2158fec6262db84d0b9d504e24c071a78e5b05939c0c5648ac7da67083621085417547ae194a0343ae1de34694b
7
- data.tar.gz: 506f9d1feb6c6428c9db8845b4880207e904901636b35411b8b85f37dc909abb08b96d82246f06e410c97f6991ccfd947a559eed1d9c5b8d8e429b4790bd5929
6
+ metadata.gz: e30d74c10e269546cd81a433ff91b8f4f662540a81de9010de03c5e245d59eae3f7fff2743519379bbc2de53319501b2e517a6d7c613bf24d7e21a9ab447dd5d
7
+ data.tar.gz: 862d1454d20f6ff1f51e0f17829676becdf1503df42f2d6ca5bf9b0004437ba43e65f6db381bba14b0126f2f40bb3704a6f29c8e12bafc237d405612604610a8
data/CHANGELOG.md CHANGED
@@ -5,6 +5,33 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.1.0] - 2026-08-17
9
+
10
+ ### Changed
11
+
12
+ - **The skill's CLI reference is an index and seven leaves.** `reference/cli.md`
13
+ answered every question about every verb in one 46,234-byte file, so an agent
14
+ asking about `search` paid for `serve`, `registry` and the graph too. The
15
+ shared contract — refs, exit codes, `--json`, the filters — stays in `cli.md`
16
+ and routes to one file per surface under `reference/cli/`. Measured on the
17
+ worst question rather than the mean: 9,084 bytes for a routing question,
18
+ 19,533 for the heaviest leaf. `reference/spec-map.md` is the other half, a
19
+ pointer from a spec clause to the file that answers it, so the vendored
20
+ `SPEC.md` keeps its index beside it instead of cut into it.
21
+ - **Cross-file citations in the skill name a `rule:` marker**, not a section
22
+ anchor: the key travels with the paragraph the next time anything moves.
23
+ - **`okf help` prints one row per extension**, plus a pointer to
24
+ `okf <verb> --help` for the rest. An addon with an umbrella verb and several
25
+ subcommands used to dwarf the built-ins the map exists to teach.
26
+
27
+ ### Added
28
+
29
+ - **The skill teaches what a log records.** `log.md` carries durable knowledge
30
+ and shipped behavior, never the process that produced them —
31
+ `rule:okf-log-durable-only` in `reference/authoring.md`, cited by the maintain
32
+ playbook and the Closeout gate. It was the okf-gem repository's own contract
33
+ before this, which an agent maintaining any other bundle never reads.
34
+
8
35
  ## [2.0.0] - 2026-08-14
9
36
 
10
37
  > Major, not minor. The Breaking entries below change public shapes a shipped
data/README.md CHANGED
@@ -158,9 +158,12 @@ without ever failing it.
158
158
  Publish a gem named `okf-*` carrying an `okf/plugin.rb` and installing it is the
159
159
  whole installation: your verb answers to `okf` and behaves like a built-in.
160
160
  Nothing an addon registers can displace one, and a broken addon is skipped rather
161
- than taking the CLI down. [`okf-mcp`](https://rubygems.org/gems/okf-mcp) is the
162
- first one install it and `okf mcp` serves your bundles over the Model Context
163
- Protocol, with nothing in this gem naming it.
161
+ than taking the CLI down. Three ship alongside this one, with nothing in this
162
+ gem naming any of them: [`okf-mcp`](https://rubygems.org/gems/okf-mcp) serves
163
+ your bundles over the Model Context Protocol,
164
+ [`okf-tui`](https://rubygems.org/gems/okf-tui) browses them full-screen in a
165
+ terminal, and [`okf-pro`](https://rubygems.org/gems/okf-pro) writes an
166
+ agent's knowledge repository and enforces it at three doors.
164
167
 
165
168
  The graph page treats a bundle as untrusted content: inlined data is escaped and
166
169
  every concept body is sanitized before it reaches the DOM, so a script hidden in
@@ -171,8 +174,9 @@ do not know.
171
174
  ## More
172
175
 
173
176
  The [project README](https://github.com/serradura/okf-gem#readme) carries the
174
- diagrams, the comparison with `CLAUDE.md`, agent auto-memory and wikis, and the
175
- Claude Code plugin. The [docs](https://okfgem.com/docs/) are the manual. And the
177
+ diagrams, the comparison with `CLAUDE.md`, agent auto-memory and wikis, the
178
+ Claude Code plugin, and the way to install the skill into any agent without this
179
+ gem (`npx skills add serradura/okf-gem`). The [docs](https://okfgem.com/docs/) are the manual. And the
176
180
  repo documents *itself* in OKF — clone it and run `okf server .okf` to read this
177
181
  gem's own knowledge as a graph.
178
182
 
data/lib/okf/cli.rb CHANGED
@@ -92,7 +92,7 @@ module OKF
92
92
  [ :judge, nil ],
93
93
  [ :read, nil ],
94
94
  [ :graph, nil ],
95
- [ :extension, " installed extensions:" ]
95
+ [ :extension, " installed extensions (`okf <verb> --help` for each):" ]
96
96
  ].freeze
97
97
 
98
98
  # Everything the map's grammar column cannot say for itself. A test finds the
@@ -476,13 +476,31 @@ module OKF
476
476
  end
477
477
 
478
478
  def print_group(io, group, heading)
479
- rows = self.class.commands.reject(&:hidden?).select { |command| command.group == group }.flat_map(&:help_rows)
479
+ commands = self.class.commands.reject(&:hidden?).select { |command| command.group == group }
480
+ rows = group == :extension ? extension_rows(commands) : commands.flat_map(&:help_rows)
480
481
  return if rows.empty?
481
482
 
482
483
  io.puts heading if heading
483
484
  rows.each { |left, desc| io.puts " #{left.to_s.ljust(56)}#{desc}" }
484
485
  io.puts
485
486
  end
487
+
488
+ # One line per extension, whatever it declares — its FIRST row, which is why
489
+ # an addon's first row should be its summary.
490
+ #
491
+ # Enforced here rather than asked of each addon, because a rule an addon has
492
+ # to remember is a rule this map cannot rely on. An umbrella verb is a shape
493
+ # an addon legitimately has — the built-in `registry` has it too — and one
494
+ # arriving with eight subcommands made this block the longest section on the
495
+ # page, dwarfing the built-ins above it and burying the single-line addons
496
+ # beside it.
497
+ #
498
+ # A built-in keeps every row: those are this gem's own surface, they are what
499
+ # the map exists to teach, and there is no second place to read them. An
500
+ # extension has one — `okf <verb> --help`, which the heading points at.
501
+ def extension_rows(commands)
502
+ commands.map { |command| command.help_rows.first }.compact
503
+ end
486
504
  end
487
505
  end
488
506
 
@@ -96,11 +96,18 @@ The one trap worth carrying in your head: **the age cutoff is off by default**
96
96
  a plain `okf lint` reports concepts past their own declared `stale_after` (the
97
97
  `expired` check reads the clock the CLI supplies), but never judges *age*; pass
98
98
  `--stale-after <90d|12w|ISO-date>` when you want anything not touched since then
99
- flagged too. <!-- check:stale -->
100
-
101
- Read [cli.md](reference/cli.md) before *interpreting* a verb's output in depth:
102
- what `validate` may and may not reject, lint's categories and check ids, the JSON
103
- shapes, the tag-curation views, the server's trust boundary.
99
+ flagged too the two mechanisms share a spelling and nothing else
100
+ ([cli/checks.md](reference/cli/checks.md), rule `okf-two-clocks`).
101
+ <!-- check:stale -->
102
+
103
+ Read [cli.md](reference/cli.md) before *interpreting* a verb's output in depth.
104
+ It is the index and the shared contract — refs, exit codes, `--json`, the
105
+ filters — and its table routes to the one file the verb lives in:
106
+ [checks](reference/cli/checks.md) (what `validate` may and may not reject, lint's
107
+ categories and check ids), [search](reference/cli/search.md),
108
+ [map](reference/cli/map.md) (`index`/`dirs`), [views](reference/cli/views.md)
109
+ (the tag-curation views), [serve](reference/cli/serve.md) (the trust boundary),
110
+ [registry](reference/cli/registry.md), [graph](reference/cli/graph.md).
104
111
 
105
112
  ## Orient before you touch anything
106
113
 
@@ -123,8 +130,9 @@ playbooks (the Commands table below; no `okf` installed? read the root
123
130
  `consume` (use it as context) carry the judgment the executable can't — this is
124
131
  where the skill earns its keep. Each has a playbook (the Commands table below);
125
132
  read the modelling craft in [authoring.md](reference/authoring.md) before
126
- producing or maintaining, and the verbatim spec [SPEC.md](reference/SPEC.md)
127
- when you need chapter and verse.
133
+ producing or maintaining. When you need chapter and verse, go through
134
+ [spec-map.md](reference/spec-map.md) which § settles which question — into the
135
+ verbatim [SPEC.md](reference/SPEC.md).
128
136
 
129
137
  **No subcommand?** Infer intent: "document this / capture X" → `produce`;
130
138
  "convert / migrate / OKFy these existing docs into a bundle" → `migrate`; "the
@@ -25,7 +25,9 @@ lives in [authoring.md](../reference/authoring.md).
25
25
  silently deleting the context that explains them.
26
26
  4. **Update every enumeration that names what you changed — including `index.md`
27
27
  bodies**, not just the concept files: a new, renamed, or removed concept changes
28
- its directory's index listing too. Append a dated `log.md` entry. Step 1's map
28
+ its directory's index listing too. Append a dated `log.md` entry what changed and why it
29
+ matters, never the rounds it took (rule `okf-log-durable-only` in
30
+ [authoring.md](../reference/authoring.md)). Step 1's map
29
31
  is how you verify this — re-run `okf index` and confirm each listing matches
30
32
  reality.
31
33
  5. Run `validate`, then `lint` to catch the curation drift the change introduced —
@@ -4,7 +4,8 @@ The craft that makes these steps land well — granularity, choosing `type`, tag
4
4
  vocabulary, topology, links, sources — lives in
5
5
  [authoring.md](../reference/authoring.md). Read it before a non-trivial produce.
6
6
 
7
- 1. Read [SPEC.md](../reference/SPEC.md) if you are unsure of any rule.
7
+ 1. Unsure of a rule? [spec-map.md](../reference/spec-map.md) names the § that
8
+ settles it; read that section of [SPEC.md](../reference/SPEC.md).
8
9
  2. Pick the source(s): **code** (derive concepts from source, READMEs, docstrings,
9
10
  config), **docs/wiki** (distill pages into concepts; record the originals in
10
11
  `sources:` and key claims with `[^id]` footnotes), **manual** (decisions,
@@ -27,5 +28,6 @@ vocabulary, topology, links, sources — lives in
27
28
  `okf_version: "0.2"`. Append a dated entry to `log.md`.
28
29
  6. **Close out** — walk the
29
30
  [Closeout gate](../reference/authoring.md#closeout--the-finishing-gate)
30
- (`validate` + `lint` are part of it, see [cli.md](../reference/cli.md))
31
+ (`validate` + `lint` are part of it, see
32
+ [cli/checks.md](../reference/cli/checks.md))
31
33
  before finishing.
@@ -1,33 +1,13 @@
1
1
  # Authoring OKF well — the craft
2
2
 
3
- The spec ([SPEC.md](SPEC.md)) tells you what is *legal*. This file is what is
4
- *good* the modelling judgment that turns a pile of conformant files into
3
+ The spec ([SPEC.md](SPEC.md)) tells you what is *legal*, and
4
+ [spec-map.md](spec-map.md) says which § settles which question. This file is
5
+ what is *good* — the modelling judgment that turns a pile of conformant files into
5
6
  knowledge worth consuming. Read it before `produce` or `maintain`, and keep the
6
7
  §11 conformance rules in mind (parseable frontmatter, a non-empty `type`, and
7
8
  well-formed reserved files — the hard rules in [SKILL.md](../SKILL.md));
8
9
  everything else is guidance a consumer must tolerate.
9
10
 
10
- ## What each SPEC section governs
11
-
12
- Consult the right section on demand instead of re-reading all of [SPEC.md](SPEC.md):
13
-
14
- | § | Governs | Reach for it when |
15
- |---|---------|-------------------|
16
- | §3 | bundle structure, reserved filenames | laying out directories |
17
- | §4 | concept documents & frontmatter | writing or validating a concept |
18
- | §5.1 | provenance — `sources` and its credibility signals | any external or empirical claim |
19
- | §5.2 / §5.3 | trust — `generated`, `verified`, and the derived tiers | recording who wrote or confirmed content |
20
- | §5.4 / §5.5 | lifecycle — `status`, `stale_after` | marking drafts, deprecations, expiries |
21
- | §6 / §6.1 | cross-links; **broken links are tolerated** | linking; judging a "broken" link |
22
- | §6.2 / §6.3 | path-valued fields; the `references/` convention | pointing at non-concept assets |
23
- | §7 | the actor convention | filling any `by` |
24
- | §8 | index files & progressive disclosure | orienting; writing or synthesizing an index |
25
- | §9 | log files | recording history |
26
- | §10 | attested computations | a concept that *is* a sanctioned computation |
27
- | §11 | conformance — the hard gate | what `validate` may and may not reject |
28
- | §12 | versioning (`okf_version`) | the root index's one allowed field |
29
- | §13 | changes from v0.1 | migrating a bundle; reading an unmigrated one |
30
-
31
11
  ## Modelling principles
32
12
 
33
13
  These are the decisions that make or break a bundle. None are enforced by the
@@ -177,7 +157,8 @@ declare it only when it says something. `stale_after` is an absolute
177
157
  the day itself. Use it for knowledge with a known shelf life (a quota, a
178
158
  migration window); `lint`'s `expired` check reports the ones whose date has
179
159
  passed. It is a declared expiry, distinct from the `--stale-after` *flag*,
180
- which is a reader-supplied age cutoff — see [cli.md](cli.md).
160
+ which is a reader-supplied age cutoff — see [cli/checks.md](cli/checks.md),
161
+ rule `okf-two-clocks`.
181
162
 
182
163
  ### Capture the non-obvious — not what code already says <!-- rule:okf-non-obvious -->
183
164
  A bundle that restates function signatures or config keys goes stale the moment
@@ -217,6 +198,21 @@ concepts. Templates: [concept](../templates/concept.md),
217
198
  nested [index](../templates/index.md), bundle-root
218
199
  [root-index](../templates/root-index.md), [log](../templates/log.md).
219
200
 
201
+ ### The log records what shipped, not how it shipped <!-- rule:okf-log-durable-only -->
202
+ `log.md` carries durable knowledge and shipped behavior — never the process that
203
+ produced them. A bug fixed in a release is an entry; the review rounds that found
204
+ it are not. A capability that shipped is an entry; the iterations it took to
205
+ stabilize it before it shipped are not. When a change taught a lesson, the lesson
206
+ belongs in the concept it is about, stated as a principle, and the log entry
207
+ *points* at that concept instead of re-narrating the rounds — a reader finds it
208
+ where the subject lives, not by reading history.
209
+
210
+ The bar is what a reader six months out needs: *what changed and why it matters*,
211
+ never *how many passes it took to get there*. Watch for the shape this invites —
212
+ the newest entries sit at the top where every reader lands, so a stretch of work
213
+ stabilized by iteration accretes "round N found M defects" exactly where a durable
214
+ summary belongs.
215
+
220
216
  ## Migrating from v0.1 (§13)
221
217
 
222
218
  A v0.1 bundle is consumable forever — §13.1 sanctions reading `timestamp` as
@@ -237,7 +233,7 @@ verb (search, produce, migrate, maintain, consume, curate, doctor), routed by th
237
233
  Commands table in [SKILL.md](../SKILL.md). The Closeout below is their shared
238
234
  finishing gate.
239
235
 
240
- ## Closeout — the finishing gate
236
+ ## Closeout — the finishing gate <!-- rule:okf-closeout-gate -->
241
237
 
242
238
  `produce` step 6 and `maintain` steps 4–7 both land here: before calling an
243
239
  authoring task done, walk this once. It is the repo's "turn every task into a check
@@ -247,7 +243,8 @@ grep can't:
247
243
  - **Index enumerations** — every `index.md` that lists what you added, renamed, or
248
244
  removed is updated; re-run `okf index` and eyeball each listing against reality.
249
245
  Easy to skip, expensive to miss — this is the check that was missing.
250
- - **`log.md`** — a dated entry, newest first.
246
+ - **`log.md`** — a dated entry, newest first, and durable only (rule
247
+ `okf-log-durable-only` above).
251
248
  - **`generated.at`** bumped on the concepts you touched (and `generated.by` says
252
249
  who touched them — you, in §7's spelling).
253
250
  - **`validate`** — zero §11 errors.
@@ -0,0 +1,171 @@
1
+ # Checking a bundle — `validate`, `lint`, `loose`, `references`
2
+
3
+ Kind: reference. Answers: what makes a bundle non-conformant, what each lint
4
+ check means and the severity it is pinned at, which of the two clocks a flag
5
+ reads, and which dangling pointers each surface can and cannot see.
6
+
7
+ The shared contract — `@slug` refs, exit codes, `--json`, the filters — is in
8
+ [cli.md](../cli.md) and is not repeated here.
9
+
10
+ ## validate — the hard gate (§11)
11
+
12
+ Implements the spec's §11 conformance definition exactly (its words are in
13
+ [SPEC.md](../SPEC.md), routed by [spec-map.md](../spec-map.md)):
14
+
15
+ - **§11 cond. 1** every non-reserved file has a parseable YAML frontmatter block;
16
+ - **§11 cond. 2** every such block has a non-empty `type`;
17
+ - **§11 cond. 3** any `index.md`/`log.md` present follows §8/§9 (a nested
18
+ `index.md` has no frontmatter, a root `index.md` carries only `okf_version`,
19
+ `log.md` date headings are ISO `YYYY-MM-DD`).
20
+
21
+ `ERROR`s are the three conditions above; the bundle is non-conformant until every
22
+ one is fixed. `warn`s are soft — missing recommended fields, non-list tags, an
23
+ unparseable timestamp, **broken cross-links, which §6.1 explicitly tolerates**,
24
+ the shape of every §5/§10 family (`generated` not a mapping, a non-integer
25
+ `usage_count`, a `stale_after` that is not `YYYY-MM-DD`, a missing `runtime` on
26
+ an Attested Computation, …), and an `okf_version` the gem does not know (read
27
+ best-effort under §12; an absent one never warns). Absence of an optional family
28
+ is never a fault — a pure v0.1 bundle validates with zero warnings.
29
+
30
+ In `--json`, every warning carries `check` (a stable id) and `source` — `spec`
31
+ when the SPEC's own words state the rule, `convention` for a shape this gem asks
32
+ for beyond them (`verified[].by` presence, integer `usage_count`, a per-entry
33
+ `usage_window` mapping, `parameters[].name`, `executor`/`attester` `resource`).
34
+ Gate on `source` when you want only the spec-normative set; errors keep their
35
+ two-key `{ path, message }` shape. Fix warnings when cheap; never block on them.
36
+ Use `--json` in CI.
37
+
38
+ ## lint — curation quality (advisory)
39
+
40
+ Asks the complementary question to `validate`: not "is this legal OKF?" but "is
41
+ this well-curated, navigable, trustworthy?" — precisely over the things §11
42
+ forbids `validate` from rejecting. It has its own report, never emits
43
+ conformance errors, and **exits `0` even with findings** unless you pass
44
+ `--fail-on warn` (exit 1 on any `warn` finding) or `--fail-on info` (exit 1 on
45
+ any finding at all).
46
+
47
+ **Severity is API.** Every check has a pinned level — `warn` or `info` — and
48
+ machine consumers gate on it, so the levels below are stable, not advisory. A
49
+ finding you want to gate on that is `info` gets `--fail-on info` (usually with
50
+ `--only`), never a hope that its severity changes. <!-- rule:okf-severity-is-api -->
51
+
52
+ Eight categories, each backed by individual checks (severity in brackets):
53
+
54
+ - **Reachability** — `orphan` [warn], `not_in_index` [warn],
55
+ `disconnected_component` [info], `unlinked` [info]
56
+ - **Backlog** — `missing_concept` [info], `broken_index_entry` [warn]
57
+ - **Completeness** — `stub` [info], `missing_title` [info],
58
+ `missing_description` [info], `missing_generated` [info] (quiet on either
59
+ spelling — a legacy `timestamp` still counts as a recorded change)
60
+ - **Freshness** — `expired` [info] (§5.5: past the concept's own declared
61
+ `stale_after`, on the day itself), `stale` [warn] (older than the
62
+ reader-supplied `--stale-after` cutoff, keyed on `generated_at`)
63
+ - **Provenance** — `uncited_external` [info] (external body links and no
64
+ sources, in either spelling), `broken_source` [warn] (an in-bundle `.md`
65
+ source target that names no concept; URLs and scope descriptors are out of
66
+ scope, and a non-`.md` asset is out of reach — the reader models concepts,
67
+ so lint never sees the file; `okf references` is the view that checks those
68
+ pointers), `unattributed_claim` [warn] (a footnote
69
+ no `sources[].id` answers — it *misattributes* a claim, which is why it
70
+ outranks its join-twin), `unused_source` [info] (a keyed source no footnote
71
+ cites — slack, not a defect), `unprefixed_actor` [info] (a `verified[].by`
72
+ outside §7's three forms reads as machine-confirmed; a `generated.by`
73
+ outside them feeds no tier but leaves a reader unable to tell a person
74
+ from a process; info so it informs, never blocks). A missing `generated.by` is the *validator's* warning —
75
+ REQUIRED-within is shape, not curation — so lint never double-reports it
76
+ - **Attestation** — `incomplete_computation` [warn] (an Attested Computation
77
+ providing its computation neither way, or both ways — §10.3 says a
78
+ `computation:` path is used *instead of* the body fence),
79
+ `broken_attestation_ref` [warn] (on an `Attested Computation`, a
80
+ `computation`, `executor.resource` or `attester.resource` naming an
81
+ in-bundle `.md` that is not there — a contract no consumer can follow; the
82
+ keys are read only on that type, since §4.1 lets any other concept use them
83
+ for its own purpose). Its reach is exactly the `.md` files: URLs are out
84
+ of scope, and a `.sql` or `.py` target is invisible to *every* check here,
85
+ because the linter reads the concept model and the model carries only
86
+ markdown — `okf references` is the surface that sees those files and reports
87
+ a pointer that misses, whatever the extension. Remember §6.2 reads a bare
88
+ `references/…` as relative to the concept, so from a nested concept it wants
89
+ the leading `/`
90
+ - **Migration** — `legacy_timestamp` [info], `legacy_citations` [info]: one
91
+ finding per bundle naming the files still in a retired v0.1 spelling, with
92
+ the rewrite instructions in the message. Info on purpose — §13 says a v0.1
93
+ bundle is consumable forever, so `--fail-on warn` must not turn red on one.
94
+ A migration campaign gates explicitly:
95
+ `okf lint <dir> --only legacy_timestamp,legacy_citations --fail-on info`,
96
+ exit 1 until clean.
97
+ - **Hygiene** — `duplicate_title` [info], `unused_reference_def` [info],
98
+ `undefined_reference` [warn], `self_link` [info], `log_order` [info] (§9
99
+ reads a log newest-first; disorder is slack, never a §11 error)
100
+
101
+ `--only` / `--except` filter by the **individual check names above**, not the
102
+ category labels — `okf lint <dir> --only orphan,stub` works; `--only reachability`
103
+ is an error. Two knobs tune specific checks: `--min-body N` sets the `stub` body
104
+ threshold in characters (default 50), and `--stale-after DUR` sets the `stale`
105
+ cutoff — a duration like `90d` or `12w`, or an ISO date like `2026-01-01` (a bare
106
+ number is rejected).
107
+
108
+ **Two different clocks, one unlucky name.** The `--stale-after` *flag* and the
109
+ `stale_after:` *frontmatter field* are different mechanisms that happen to share
110
+ a spelling. The flag is the **reader's** age cutoff: "flag anything not touched
111
+ since DUR", keyed on `generated_at`, feeding the `stale` check. The field is the
112
+ **author's** declared expiry: "do not trust this past DATE", feeding the
113
+ `expired` check. Never read one as the other, and never show them adjacent
114
+ without the distinction. <!-- rule:okf-two-clocks -->
115
+
116
+ **The clock is explicit.** `expired` compares against a day the CLI supplies —
117
+ today by default, or `--today YYYY-MM-DD` for a reproducible report (CI wants
118
+ this). The pure library runs no clock check unless handed `today:`, and every
119
+ clock-gated check that was selected but could not run is *named* in
120
+ `stats.skipped_checks` (the human report prints one `skipped:` line) — a gate
121
+ that is sometimes absent and does not confess converts "unchecked" into
122
+ "checked and fine".
123
+
124
+ The report's stats carry the bundle's posture too: `trust` (the §5.3 tier
125
+ distribution, in the hyphenated wire spelling) and `status` (effective-status
126
+ frequency).
127
+
128
+ `lint --json` is the structured substrate you consume to reason about the two
129
+ things lint deliberately does **not** compute — contradictions and *semantic*
130
+ staleness — which need understanding of meaning.
131
+
132
+ ## loose — files with no graph connections (by folder)
133
+
134
+ Lists the **loose** files — concepts with graph **degree 0**: no cross-links in
135
+ *or* out — grouped by folder. It is a focused, folder-organized view over `lint`'s
136
+ `unlinked` check (`okf loose <dir>` ≈ `okf lint <dir> --only unlinked`, regrouped),
137
+ for the "which files float in the graph?" question. Advisory: **exits `0`**; `--json`
138
+ emits `{ bundle, count, loose: [{ id, title, dir }] }`.
139
+
140
+ **Loose ≠ orphan** — the trap. `lint`'s `orphan` is about *reachability*, and an
141
+ `index.md` listing makes a file reachable, so an indexed file is never an orphan.
142
+ But an index listing is **not a graph edge**: a file can be listed in an index yet
143
+ have no cross-links, so it floats in the graph while `lint` reports it as reachable.
144
+ `loose`/`unlinked` catch exactly that gap. A loose file is not automatically a
145
+ defect — a terminal leaf (a backlog item, a spec reference) can be loose by design;
146
+ `loose` surfaces the set so you can judge intent (see the
147
+ [maintain playbook](../../playbooks/maintain.md)).
148
+ <!-- rule:okf-loose-not-orphan -->
149
+
150
+ ## references — the `references/` inventory (§6.3)
151
+
152
+ Lists every file under `references/` — including the non-markdown ones no other
153
+ verb can see, since the concept model carries only markdown — with which
154
+ concepts cite each file through the §6.2 path-valued fields (`resource`,
155
+ `sources[].resource`, `computation`, `executor.resource`, `attester.resource`),
156
+ plus every pointer into `references/` that resolves to nothing. Advisory:
157
+ **exits `0`** even with dangling pointers — the findings are the output. JSON:
158
+ `{ bundle, dangling, count, references: [{ path, dir, kind, referenced_by }] }`,
159
+ with `--fields`/`--except` projecting the rows. A file that is itself a concept
160
+ (§6.3 allows both) is marked `kind: "concept"`; body links are the graph's
161
+ business and are not counted here.
162
+
163
+ **The dangling list is where §6.2's bare-path trap surfaces.** A bare
164
+ `references/attesters/rev.py` written from `metrics/` resolves relative to the
165
+ concept — `metrics/references/attesters/rev.py`, nothing — and when the
166
+ leading-slash spelling would have hit, the entry says so:
167
+ `/references/attesters/rev.py exists — missing leading slash?`. Reach is any
168
+ extension, which is exactly what `broken_source` and `broken_attestation_ref`
169
+ cannot offer (their exemptions above), so run it wherever a bundle carries
170
+ attester code or computation files.
171
+
@@ -0,0 +1,49 @@
1
+ # `graph` — the raw structure
2
+
3
+ Kind: reference. Answers: what a full dump costs and how to plan a traversal
4
+ without paying it, plus the two rankings that give `refine` its evidence —
5
+ `--hubs` by concept, `--traffic` by directory.
6
+
7
+ The shared contract — `@slug` refs, exit codes, `--json` — is in
8
+ [cli.md](../cli.md) and is not repeated here.
9
+
10
+ Prints the node/edge graph. `--json` emits a machine-readable dump — the
11
+ `bundle`/`slug` head every view carries, then `nodes` (with
12
+ `id`/`type`/`title`/`description`/`tags` **and, by default, every `body`** — the
13
+ part that dominates the bytes on a real bundle) plus `edges` — you can pipe into
14
+ other analysis. A concept with a missing *or blank* `type` indexes under
15
+ `Untyped`: §11 condition 2 rejects both identically, so both land in one bucket. To *plan* a traversal, structure is all you need: `--no-body`
16
+ drops each node's body, and `--minimal` ships only `id`/`title` plus the type/tag
17
+ indexes — the lean shape the `server` page boots from. Reach for the full dump
18
+ only when the task truly consumes every body; for one question, the
19
+ [search verb](search.md) is orders cheaper.
20
+
21
+ `--hubs` swaps the dump for the **inbound ranking**: every concept with at
22
+ least one inbound link, ranked by inbound degree, each with its links grouped
23
+ by *source top-level dir* (`core/status ×3 flows 2, billing 1`) — the evidence for
24
+ [refine](../../playbooks/refine.md)'s hub origin test ("is this hub well-homed?").
25
+ A source at the bundle root counts under `(root)`. JSON: `{ bundle, count,
26
+ hubs: [{ id, top_dir, inbound, by_top_dir: { <top_dir>: n } }] }`. Advisory read, exit 0;
27
+ `--minimal`/`--no-body` shape node payloads and change nothing here.
28
+
29
+ `--traffic` asks the same question one grain coarser: **directories**, not
30
+ concepts. Every concept collapses into the directory it lives in and every link
31
+ between two directories collapses into one weighted arc, so a bundle's wiring
32
+ becomes a table you can read — measured on one 47-concept bundle, 227 links
33
+ collapsed into 50 arcs, of which the fitted cut draws 22. Each row carries the
34
+ directory's traffic split three ways
35
+ (`internal` / `out` / `in`) plus **cohesion**, its internal share of the total:
36
+ the evidence for [refine](../../playbooks/refine.md)'s container test, where
37
+ `--hubs` only ever answered about concepts. Rows lead with the lowest cohesion,
38
+ so the directories with a case to answer come first, and a directory with no
39
+ traffic at all prints `—` rather than a `0%` it did not earn.
40
+
41
+ `--cut N` is the least arc weight drawn. It defaults to a value **fitted to the
42
+ bundle** — enough arcs for roughly 1.5 per directory, floored at 8 — because a
43
+ fixed weight cannot serve both ends: measured at weight 3 across ten bundles it
44
+ left 2 arcs on one and 136 on another. The JSON says which you got. Cohesion is
45
+ computed over *every* arc and never the drawn ones, so tightening the cut
46
+ changes the picture and never the evidence. JSON: `{ bundle, cut, fitted, dirs:
47
+ [{ dir, parent, count, subtree, internal, out, in, cohesion }], arcs: [{ source,
48
+ target, weight }], total_arcs }` — a fraction of the full dump (2.6 KB against
49
+ 27 KB on that bundle), and the shape rather than the contents. Advisory, exit 0.
@@ -0,0 +1,98 @@
1
+ # Orienting — `index` and `dirs`
2
+
3
+ Kind: reference. Answers: which view to run first on a bundle you do not know,
4
+ how `--dir`, `--depth` and the ancestor chain compose, what a synthesized
5
+ listing means, and how to keep the map from paging the whole bundle.
6
+
7
+ The shared contract — `@slug` refs, exit codes, `--json`, the filters — is in
8
+ [cli.md](../cli.md) and is not repeated here.
9
+
10
+ ## index — the progressive-disclosure map (§8)
11
+
12
+ The "orient before you read" view, and the read verb that sees the layer the
13
+ concept views can't: `index.md` files are reserved/structural, so
14
+ `catalog`/`files`/… never show them (in the browser, the Indexes tab and
15
+ folder clicks render this same map). `okf index <dir>` prints one entry per directory
16
+ that holds concepts or carries an `index.md`, root first — the authored index body
17
+ (frontmatter stripped), a `type`/`tag` rollup over the concepts that live directly
18
+ there, its child directories, and the concept listing. Run it first when picking up
19
+ an existing bundle: it is the cheapest high-signal orientation, and it surfaces
20
+ enumeration drift a grep can't (you can't grep for a listing entry that is *missing*).
21
+
22
+ `--dir PATH` narrows to a directory **and everything below it**, and is
23
+ **repeatable** — `--dir model --dir format` shows both; `root` (or `.`) names the
24
+ bundle root, unless the bundle really has a `root/` directory, which owns the
25
+ word. A `--dir` also brings the **chain from the root down to it**, so a branch is
26
+ never shown adrift of the authored context that says what it is — the root
27
+ `index.md`'s prose first among it. Those rows print with a leading `↑` and carry
28
+ `ancestor: true`; `--no-ancestors` drops them. Ascent and descent are separate
29
+ axes, so `--depth` never bounds the chain: `--dir X --depth 0` is X alone, plus
30
+ how you get to X. A `--dir` that names nothing gains no chain — a lone root row
31
+ would read as a partial answer to a query that matched nothing.
32
+
33
+ `--depth N` bounds how far below the starting point the map reaches
34
+ (the `--dir` when one is given, else the bundle root), counted **relatively**:
35
+ `--depth 1` is the top of the tree, `--dir X --depth 1` is one branch of it, and
36
+ the pair walks down a level at a time.
37
+
38
+ **On a bundle of any size the map is unreadable whole** — every directory is a
39
+ section, and even `--no-body` keeps one listing row per *concept* — so narrow
40
+ rather than paging it: `okf dirs` is the orientation, `--dir <branch> --depth 1`
41
+ is the step down into it,
42
+ and `--except body,listing` on top of either is the lean JSON skeleton. Full
43
+ `index` output on a few hundred concepts runs to hundreds of KB; the same map at
44
+ `--depth 1` is a couple of KB.
45
+
46
+ `--no-body` drops the prose to a
47
+ skeleton (headers, rollups, child pointers). For a directory that has concepts but
48
+ **no `index.md`**, the listing is **synthesized** from the concepts' descriptions
49
+ and tagged `(no index.md)` — §8 explicitly permits synthesizing a map on the fly.
50
+
51
+ It is a **read view**: advisory, always exit 0. A synthesized directory is a
52
+ *signal* (a map worth writing), never a defect — `index` emits no lint findings and
53
+ never fails a bundle. JSON: `{ bundle, count, directories: [{ dir, index_path,
54
+ present, synthesized, count, types, tags, subdirs, body, listing: [{ id, title,
55
+ description, type, tags }] }] }` — `ancestor` marks a row that is there to place
56
+ the branch rather than to answer about it.
57
+
58
+ ## dirs — the bundle's clusters and their sizes
59
+
60
+ `okf dirs <dir>` lists every directory the bundle has — the ones holding
61
+ concepts, the ones carrying an `index.md`, and the empty intermediates that only
62
+ exist to connect the tree — with the number of concepts living **directly** in
63
+ each and the number in its **subtree**. A cluster *is* a directory here, so this
64
+ is the view that tells you what `--dir` can be pointed at and how much sits
65
+ behind each choice.
66
+
67
+ Two numbers, because one cannot answer the question. `count` is direct, so the
68
+ column sums to the bundle's concept total and a dir holding only sub-directories
69
+ reads `0` rather than a hidden rollup. `subtree` is defined as *exactly what
70
+ `--dir <that row>` returns*, so the row and the flag can never disagree — which
71
+ is also why the root's subtree is its own direct count (`.` is a prefix of
72
+ nothing). Without it a truncated listing is all zeroes at the top of a deep tree,
73
+ which is where you most need to know where the mass is. The human table shows the
74
+ second column only where some dir actually nests.
75
+
76
+ `--dir PATH` (repeatable) narrows to a directory and its subtree, and brings the
77
+ **chain up to the root** with it so the branch is placed rather than shown
78
+ adrift — those rows are marked `↑`, carry `ancestor: true`, and stay out of
79
+ `total` (`--no-ancestors` drops them). `--depth N` keeps only N levels below the
80
+ starting point — the `--dir` when one is given,
81
+ the bundle root otherwise. Relative, not absolute, so `--dir a/b --depth 1`
82
+ reads "a/b and one level under it" without your first working out how deep `a/b`
83
+ is. `--depth 0` is the starting point alone. A `--depth` that is not a whole
84
+ number is a usage error (exit 2).
85
+
86
+ **This is the first command to run on a bundle you do not know** — the same first
87
+ move [SKILL.md](../../SKILL.md) prescribes. `okf dirs <dir>` is one row per
88
+ directory, so its size tracks the tree rather than the concept count: it tells
89
+ you the shape and where the weight sits, `--depth 1` trims it further on a deep
90
+ bundle, and you then descend with `okf index --dir`, one level at a time.
91
+
92
+ The root prints `(root)` and stores `.` — the split every grouped view keeps, so
93
+ a table and its `--json` never disagree about which spelling is the data. JSON:
94
+ `{ bundle, total, count, dirs: [{ dir, ancestor, count, subtree, subdirs }] }`,
95
+ root first. `count` is rows printed, chain included; `total` sums the direct
96
+ counts of the rows you actually asked for, which is what keeps a row's `subtree`
97
+ equal to the `total` that `--dir` on that row returns.
98
+