okf 1.13.0 → 2.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 +4 -4
- data/CHANGELOG.md +195 -0
- data/README.md +25 -6
- data/lib/okf/bundle/folder.rb +27 -1
- data/lib/okf/bundle/graph.rb +12 -3
- data/lib/okf/bundle/linter.rb +470 -47
- data/lib/okf/bundle/reader.rb +10 -4
- data/lib/okf/bundle/references.rb +111 -0
- data/lib/okf/bundle/row_filter.rb +53 -0
- data/lib/okf/bundle/search.rb +20 -2
- data/lib/okf/bundle/validator/result.rb +6 -3
- data/lib/okf/bundle/validator.rb +267 -26
- data/lib/okf/bundle/writer.rb +1 -1
- data/lib/okf/bundle.rb +105 -4
- data/lib/okf/cli/catalog.rb +1 -1
- data/lib/okf/cli/command.rb +27 -8
- data/lib/okf/cli/files.rb +1 -1
- data/lib/okf/cli/index.rb +1 -1
- data/lib/okf/cli/lint.rb +70 -12
- data/lib/okf/cli/references.rb +97 -0
- data/lib/okf/cli/search.rb +2 -1
- data/lib/okf/cli/stats.rb +3 -39
- data/lib/okf/cli/tags.rb +6 -43
- data/lib/okf/cli/types.rb +1 -1
- data/lib/okf/cli/validate.rb +3 -3
- data/lib/okf/cli.rb +4 -1
- data/lib/okf/concept.rb +362 -10
- data/lib/okf/markdown/citations.rb +41 -4
- data/lib/okf/markdown/frontmatter.rb +1 -1
- data/lib/okf/markdown/links.rb +67 -7
- data/lib/okf/render/graph/template.html.erb +173 -41
- data/lib/okf/render/graph.rb +11 -3
- data/lib/okf/server/app.rb +47 -15
- data/lib/okf/server/hub.rb +1 -1
- data/lib/okf/skill/SKILL.md +14 -12
- data/lib/okf/skill/playbooks/curate.md +8 -3
- data/lib/okf/skill/playbooks/doctor.md +3 -1
- data/lib/okf/skill/playbooks/maintain.md +7 -6
- data/lib/okf/skill/playbooks/menu.md +5 -4
- data/lib/okf/skill/playbooks/migrate.md +31 -8
- data/lib/okf/skill/playbooks/produce.md +16 -9
- data/lib/okf/skill/playbooks/search.md +2 -2
- data/lib/okf/skill/reference/SPEC.md +739 -187
- data/lib/okf/skill/reference/authoring.md +154 -35
- data/lib/okf/skill/reference/cli.md +155 -42
- data/lib/okf/skill/templates/attested-computation.md +41 -0
- data/lib/okf/skill/templates/concept.md +13 -6
- data/lib/okf/skill/templates/root-index.md +1 -1
- data/lib/okf/version.rb +1 -1
- data/lib/okf.rb +22 -2
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43476118366ed4610628f4692f1c1432e4babe4a9d5749f06eb69fc41738c4e5
|
|
4
|
+
data.tar.gz: 218144bd22483995d933cf9d89f44e9602c2c95255d9e1c95626700a4f5b2f2e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 40ccd9ca9a9fe8580856825a972753b0f58ff2158fec6262db84d0b9d504e24c071a78e5b05939c0c5648ac7da67083621085417547ae194a0343ae1de34694b
|
|
7
|
+
data.tar.gz: 506f9d1feb6c6428c9db8845b4880207e904901636b35411b8b85f37dc909abb08b96d82246f06e410c97f6991ccfd947a559eed1d9c5b8d8e429b4790bd5929
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,200 @@ 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.0.0] - 2026-08-14
|
|
9
|
+
|
|
10
|
+
> Major, not minor. The Breaking entries below change public shapes a shipped
|
|
11
|
+
> consumer already reads: two lint check ids are renamed, the `stale` finding's
|
|
12
|
+
> JSON metric moves from `timestamp` to `generated_at`, and the `timestamp`
|
|
13
|
+
> catalog column is gone. A v0.1 *bundle* still validates with zero warnings —
|
|
14
|
+
> §13.1's fallbacks hold — but spec compatibility and API compatibility are
|
|
15
|
+
> different promises, and semver tracks the second. okf-mcp's open-ended
|
|
16
|
+
> `okf >= 1.13` is closed to `>= 2.0, < 3` in the same release.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`okf references` — the §6.3 inventory.** Lists every file under
|
|
21
|
+
`references/`, the non-markdown ones included (the concept model carries
|
|
22
|
+
only markdown, so no other verb can see a `.py` attester or a `.sql`
|
|
23
|
+
computation), with which concepts cite each file through the §6.2
|
|
24
|
+
path-valued fields — plus every pointer into `references/` that resolves to
|
|
25
|
+
nothing. A bare `references/…` written from a subdirectory resolves relative
|
|
26
|
+
to the concept (the §6.2 trap the SPEC's own examples invite), and when the
|
|
27
|
+
leading-slash spelling would have hit, the dangling entry says so. Advisory
|
|
28
|
+
(exit 0); `--json` with `--fields`/`--except` like the other read views.
|
|
29
|
+
Backed by `OKF::Bundle::References` (pure) and
|
|
30
|
+
`OKF::Bundle::Folder#references` (the disk manifest) in the library, and by
|
|
31
|
+
`Markdown::Links.resolve_path` — `#resolve`'s path arithmetic without its
|
|
32
|
+
`.md` gate, since §6.2 fields accept any file.
|
|
33
|
+
- **The §5/§10 families across every surface.** The validator warns on their
|
|
34
|
+
shapes (raw keys only, warnings only — §11's three hard conditions are
|
|
35
|
+
untouched) and its warnings become machine-readable: each carries `check:`
|
|
36
|
+
(a stable id) and `source:` (`spec` | `convention`), with errors keeping
|
|
37
|
+
their exact two-key shape. A root `index.md`'s `okf_version` is actually
|
|
38
|
+
read now — an unknown version warns under §12 and the bundle is consumed
|
|
39
|
+
anyway; unquoted `okf_version: 0.2` (a Psych Float) is accepted.
|
|
40
|
+
- **Eight lint categories with pinned severities** (`Linter::SEVERITIES`, a
|
|
41
|
+
tested constant): Freshness gains `expired` (info, clock-gated — the CLI
|
|
42
|
+
passes today, `--today YYYY-MM-DD` pins it for reproducible CI reports, and
|
|
43
|
+
the pure library runs no clock check unless handed `today:`, confessing via
|
|
44
|
+
`stats[:skipped_checks]`); Provenance gains `broken_source`,
|
|
45
|
+
`unattributed_claim` (warn — only once a concept adopts keyed attribution,
|
|
46
|
+
with the label↔id join case-folded the way GFM resolves footnotes),
|
|
47
|
+
`unused_source`, and `unprefixed_actor` (info — a `verified[].by` outside
|
|
48
|
+
§7's three forms reads as machine-confirmed); a missing `generated.by` is
|
|
49
|
+
the validator's warning alone, never double-reported by lint. Attestation
|
|
50
|
+
gains `incomplete_computation` (warn, on
|
|
51
|
+
neither-or-both computation shapes) and `broken_attestation_ref` (warn — a
|
|
52
|
+
`computation`, `executor.resource` or `attester.resource` on an
|
|
53
|
+
`Attested Computation` naming an in-bundle `.md` that is not there; a contract whose parts are named but
|
|
54
|
+
absent is one no consumer can follow, so it warns rather than informs, with
|
|
55
|
+
the same URL/non-`.md` exemption `broken_source` carries); Migration's `legacy_timestamp` and
|
|
56
|
+
`legacy_citations` (info) name what a v0.1 bundle would change without ever
|
|
57
|
+
failing it. Reports gain `trust` and `status` distributions.
|
|
58
|
+
- **`--fail-on info`** joins `never | warn` — gateability without a severity
|
|
59
|
+
promotion; a migration campaign is
|
|
60
|
+
`okf lint <dir> --only legacy_timestamp,legacy_citations --fail-on info`.
|
|
61
|
+
- **`Linter.call(only:/except:)` refuses an unknown check id** with an
|
|
62
|
+
ArgumentError naming it, instead of silently intersecting to an empty run
|
|
63
|
+
that reports healthy — the library-side twin of the CLI's exit 2, and what
|
|
64
|
+
keeps a caller pinned to a renamed id from reading "checked and fine" over
|
|
65
|
+
a run that ran nothing.
|
|
66
|
+
- **`--status` and `--trust` filters** on catalog, files, search, tags and
|
|
67
|
+
types — `--status` matches the effective value (absent reads `stable`),
|
|
68
|
+
`--trust` folds either tier spelling.
|
|
69
|
+
- **Sources are searchable text** in both Ruby engines (weight 1 — the weight
|
|
70
|
+
the body text carried in v0.1) and snippet-eligible, so a migrated bundle
|
|
71
|
+
keeps its recall and a source-only hit keeps a snippet; the static page
|
|
72
|
+
bakes and indexes the same source text.
|
|
73
|
+
- **Trust as the graph page's third channel**: tier chips, status badges
|
|
74
|
+
(declared non-default only), a generated line, a client-clock stale marker,
|
|
75
|
+
and status/trust filter groups counted off the catalog.
|
|
76
|
+
- The skill teaches v0.2: the vendored SPEC is the published v0.2, a new
|
|
77
|
+
attested-computation template carries §10.3's MUST NOT, and the migrate
|
|
78
|
+
playbook walks the v0.1→v0.2 rewrite.
|
|
79
|
+
- **`Concept#shows_trust?` and `Bundle::RowFilter.shows_trust?` — §5.3's
|
|
80
|
+
display half, in one place.** The tier is always derivable; whether a surface
|
|
81
|
+
should *claim* it is a different question, and the answer is no for a concept
|
|
82
|
+
that declared no §5 family, which is every concept of every v0.1 bundle.
|
|
83
|
+
Claiming it there would paint a provenance verdict onto documents that never
|
|
84
|
+
made one — the false claim the trust system exists to prevent. The rule was
|
|
85
|
+
spelled twice, inline in `/node/meta` and in the page's `showsTrust`, and a
|
|
86
|
+
third consumer (okf-tui, which needs it for a chip, a facet gate, the facet
|
|
87
|
+
counts and the narrowing) is what a hand-copied predicate does not survive:
|
|
88
|
+
a gate disagreeing with the counts beside it reads "unverified 3" over two
|
|
89
|
+
chipped cards. One class method takes the two wire values so a Concept and a
|
|
90
|
+
catalog row ask the same rule; the server now routes through it, and the
|
|
91
|
+
page's client-side twin — unavoidable, since it runs over baked rows with no
|
|
92
|
+
Ruby to call — is pinned equal to it by `parity_test.rb`, truth table and
|
|
93
|
+
source literal both.
|
|
94
|
+
- **`Bundle#okf_version` — the spec version the root index declares (§12)**,
|
|
95
|
+
as the producer wrote it, or nil when it declares none, which §12 permits.
|
|
96
|
+
Stringified and stripped for the reason the validator compares that way: an
|
|
97
|
+
unquoted `okf_version: 0.2` is a Psych Float, and a consumer switching on it
|
|
98
|
+
must not be handed 0.2 the number. Public because the alternative is a
|
|
99
|
+
literal — a consumer naming the version on screen had no way to ask, which is
|
|
100
|
+
how a reader gets told "v0.1" about a bundle declaring 0.2. Unparseable
|
|
101
|
+
frontmatter stays the validator's error to report rather than this reader's
|
|
102
|
+
to raise.
|
|
103
|
+
- **`log_order` [info], the first log-side lint check.** §9 describes the log
|
|
104
|
+
as date-grouped entries, newest first — prose, not an RFC keyword, so
|
|
105
|
+
disorder is curation slack rather than a conformance error: exactly lint's
|
|
106
|
+
side of the split. Only shape-valid headings are compared; a malformed date
|
|
107
|
+
stays the validator's error, reported once.
|
|
108
|
+
- **`unprefixed_actor` now covers `generated.by` too.** §7 gives the actor
|
|
109
|
+
convention to both identity fields; only `verified[].by` was checked, on the
|
|
110
|
+
argument that §5.3 derives trust from nothing else — true, but a
|
|
111
|
+
`generated.by` no form can classify leaves a provenance reader unable to
|
|
112
|
+
tell a person from a process, so it earns the same info finding with its own
|
|
113
|
+
consequence. A *missing* `generated.by` stays the validator's warning.
|
|
114
|
+
|
|
115
|
+
- **`Bundle#stats` and `Bundle#tag_groups` join the library API** —
|
|
116
|
+
extracted from the CLI's `stats` and `tags --by` so the MCP shell reads
|
|
117
|
+
the same rollups instead of hand-copying them: the `by_dir` zero-keeping
|
|
118
|
+
subtlety and the within-group-beside-total tag counting each have one
|
|
119
|
+
home now (`Folder` delegates both). The CLI verbs are pure consumers of
|
|
120
|
+
the extraction; their output is unchanged.
|
|
121
|
+
- **Two long-standing behaviors are documented contract now, each with a
|
|
122
|
+
pinning test.** Hidden files are outside the bundle: the reader excludes
|
|
123
|
+
dot-prefixed files and everything under a dot-prefixed directory — the
|
|
124
|
+
Unix convention, kept so reading a project root cannot pull an installed
|
|
125
|
+
skill or `.github/` templates in as concepts. And a frontmatter `id:`
|
|
126
|
+
renames the concept, not its home — an extension beyond §2's path-derived
|
|
127
|
+
identity, with the recorded split stated where authors read it: the
|
|
128
|
+
identity views (catalog, hubs, `--dir`, search) follow the id, the
|
|
129
|
+
physical views (`index`, `dirs`, stats' `by_dir`) keep the file where it
|
|
130
|
+
lives. Both in the skill's authoring guide.
|
|
131
|
+
|
|
132
|
+
### Changed
|
|
133
|
+
|
|
134
|
+
- **`validate` warns on a declared-but-blank `status`.** `status: ""` is a
|
|
135
|
+
producer typo, not an absence — §5.4's default belongs to a concept that
|
|
136
|
+
never declared the key — and reading the blank through the default turned it
|
|
137
|
+
into `stable` before the vocabulary check ran, so the one value §5.4 names
|
|
138
|
+
nowhere was the one that never warned.
|
|
139
|
+
- **`lint --stale-after` refuses the ISO spellings it used to reinterpret.**
|
|
140
|
+
It took whatever `Date.iso8601` parsed, so the basic (`20260101`) and week
|
|
141
|
+
(`2026-W01-1`) forms silently became a cutoff nobody asked for — `20260101`
|
|
142
|
+
read as the year 2026 day 01 of month 01 only by luck of the parser, and
|
|
143
|
+
`2026-W01-1` as 2025-12-29. Both now exit 2 naming the accepted shapes. A
|
|
144
|
+
`YYYY-MM-DD` date and a full `2026-01-01T09:00:00Z` timestamp (what a
|
|
145
|
+
concept's own `generated.at` looks like) are both still accepted, the
|
|
146
|
+
timestamp reduced to its date as before. The new `--today` takes the
|
|
147
|
+
narrower grammar — it names a calendar day, not a moment.
|
|
148
|
+
- **The gem targets OKF v0.2** and keeps reading v0.1 under §13.1's two
|
|
149
|
+
sanctioned fallbacks: a legacy `timestamp` reads as `generated.at` (per-key,
|
|
150
|
+
so a half-migrated `generated: { by: … }` beside a `timestamp` keeps its
|
|
151
|
+
date, and no actor is ever invented), and a body `# Citations` list reads as
|
|
152
|
+
`sources` whenever the native key yields zero mappings. One `OKF::Concept`
|
|
153
|
+
class, no version hierarchy; a pure v0.1 bundle still validates with zero
|
|
154
|
+
warnings.
|
|
155
|
+
- **Breaking: lint check ids renamed** — `missing_timestamp` is
|
|
156
|
+
`missing_generated`, `broken_citation` is `broken_source` — so `--only` /
|
|
157
|
+
`--except` lists naming the old ids exit 2. `uncited_external` is redefined
|
|
158
|
+
over `#sources` (a v0.1 `# Citations` still silences it — a prose-only
|
|
159
|
+
section included; so does a migrated `sources:` block). The `stale`
|
|
160
|
+
finding's JSON metric renames `timestamp:` to `generated_at:` (and its
|
|
161
|
+
message says "last updated <generated_at>") — a consumer reading
|
|
162
|
+
`finding.metric.timestamp` gets `null` and must move with it.
|
|
163
|
+
- **Breaking: the `timestamp` catalog column is removed** — `--fields
|
|
164
|
+
timestamp` exits 2 loudly, naming the valid fields. The `concepts` row gains
|
|
165
|
+
`generated_at`, `generated_by`, `generated` (the raw declared-key boolean
|
|
166
|
+
that tells hand-written apart from v0.1-with-timestamp), `trust` (the
|
|
167
|
+
hyphenated wire literals `unverified` | `machine-confirmed` |
|
|
168
|
+
`human-reviewed`), `stale_after`, and a `sources` count. `status` stays the
|
|
169
|
+
raw declared value, `null` when absent. Temporal values render ISO 8601.
|
|
170
|
+
- **Breaking: `Concept#citations` and `Markdown::Citations.targets` are
|
|
171
|
+
removed**, both subsumed by `Concept#sources` / `Citations.entries`.
|
|
172
|
+
- **The staleness boundary is `today >= stale_after`** — a concept is stale
|
|
173
|
+
**on** the day itself, per §5.5. A consumer replacing its own `<`-based
|
|
174
|
+
check shifts by one day and should know it.
|
|
175
|
+
- `GET /node/meta` returns JSON (`{ description, trust: { tier, generated_by,
|
|
176
|
+
generated_at, status, stale_after } }`, null-stripped) instead of an escaped
|
|
177
|
+
HTML fragment; the graph page composes the trust line client-side for served
|
|
178
|
+
and baked pages alike, and computes expiry against the viewer's own clock.
|
|
179
|
+
- The demo Open Graph card URL the graph template points at moved to
|
|
180
|
+
`og-demo-v5.png` (the site's card-art version bump); the old URL keeps
|
|
181
|
+
serving the current art, so nothing breaks in between.
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
### Fixed
|
|
185
|
+
|
|
186
|
+
- **A calendar-invalid log heading is now a §11 error.** §9's MUST is ISO
|
|
187
|
+
8601, and `## 2026-02-30` matched the digit shape while naming a day that
|
|
188
|
+
never existed — the validator now asks `Date.iso8601`, so a log.md is
|
|
189
|
+
conformant only around real dates.
|
|
190
|
+
- **`MAILTO:` is excluded case-insensitively**, like every other scheme (RFC
|
|
191
|
+
3986). The guard sat inline and case-sensitive beside the case-insensitive
|
|
192
|
+
`SCHEME` regex in three places, so an uppercase mailto whose address ends in
|
|
193
|
+
`.md` resolved as a relative path — a link outside the bundle reported as a
|
|
194
|
+
file inside it. One `Links::MAILTO` now, beside `SCHEME`, for the same
|
|
195
|
+
reason `SCHEME` moved there.
|
|
196
|
+
- **`incomplete_computation` requires the fence, not the heading.** §10.3's
|
|
197
|
+
inline form is a fenced code block under `# Computation`; a heading over
|
|
198
|
+
prose used to count as provided, and a contract with nothing an executor
|
|
199
|
+
could run lint'd clean.
|
|
200
|
+
|
|
201
|
+
|
|
8
202
|
## [1.13.0] - 2026-08-07
|
|
9
203
|
|
|
10
204
|
### Added
|
|
@@ -1322,6 +1516,7 @@ Initial release.
|
|
|
1322
1516
|
|
|
1323
1517
|
- Runs on Ruby >= 2.4 with two runtime dependencies: rack and webrick.
|
|
1324
1518
|
|
|
1519
|
+
[2.0.0]: https://github.com/serradura/okf-gem/compare/v1.13.0...v2.0.0
|
|
1325
1520
|
[1.13.0]: https://github.com/serradura/okf-gem/compare/v1.12.0...v1.13.0
|
|
1326
1521
|
[1.12.0]: https://github.com/serradura/okf-gem/compare/v1.11.0...v1.12.0
|
|
1327
1522
|
[1.11.0]: https://github.com/serradura/okf-gem/compare/v1.10.0...v1.11.0
|
data/README.md
CHANGED
|
@@ -67,9 +67,10 @@ okf validate <dir|@slug> # is this legal OKF?
|
|
|
67
67
|
okf lint <dir|@slug> [--fail-on warn] # is it navigable, complete, fresh?
|
|
68
68
|
okf loose <dir|@slug> # concepts with no links in or out
|
|
69
69
|
okf search <dir|@slug…|@all> <term…> # ranked retrieval; @all spans every bundle
|
|
70
|
-
okf index <dir|@slug> [--dir D] [--depth N] # the §
|
|
70
|
+
okf index <dir|@slug> [--dir D] [--depth N] # the §8 map: index bodies, rollups, listings
|
|
71
71
|
okf dirs <dir|@slug> [--dir D] [--depth N] # the shape: every directory and what it holds
|
|
72
72
|
okf catalog | files | tags | types | stats <dir|@slug> # the browser views, on the CLI
|
|
73
|
+
okf references <dir|@slug> # the references/ inventory: files, citers, dangling pointers
|
|
73
74
|
okf graph <dir|@slug> [--hubs] [--traffic] # the raw graph; --hubs ranks concepts, --traffic dirs
|
|
74
75
|
okf server [DIR|@slug…] [-p PORT] [--bind ADDR] # the live graph: one bundle, or all of them
|
|
75
76
|
okf render <dir|@slug> [-o FILE] # the same page as one static, self-contained file
|
|
@@ -89,7 +90,7 @@ behind one hub. `okf registry init` scopes one to a single project instead, and
|
|
|
89
90
|
committed `.okf-registry.json` travels with the repo.
|
|
90
91
|
|
|
91
92
|
**A big bundle is read a level at a time.** `okf index --depth 1 --except
|
|
92
|
-
body,listing` is the map an agent orients on — on a
|
|
93
|
+
body,listing` is the map an agent orients on — on a 414-concept bundle, 2.8 KB
|
|
93
94
|
against the full 313 KB — and `--dir` then opens one branch, bringing the
|
|
94
95
|
ancestors that say what it is.
|
|
95
96
|
|
|
@@ -105,7 +106,7 @@ require "okf"
|
|
|
105
106
|
|
|
106
107
|
folder = OKF::Bundle::Folder.load("docs")
|
|
107
108
|
folder.concepts # => [OKF::Concept]
|
|
108
|
-
folder.validate # => §
|
|
109
|
+
folder.validate # => §11 conformance result
|
|
109
110
|
folder.lint # => curation report
|
|
110
111
|
folder.graph # => nodes, edges, indexes
|
|
111
112
|
|
|
@@ -122,18 +123,36 @@ writer, and the lower-level pieces.
|
|
|
122
123
|
## validate and lint are two different questions
|
|
123
124
|
|
|
124
125
|
`validate` asks *"is this legal OKF?"* and implements the spec's
|
|
125
|
-
[§
|
|
126
|
+
[§11](lib/okf/skill/reference/SPEC.md#11-conformance) exactly — which means it is *forbidden* to
|
|
126
127
|
reject a bundle for a broken link or a missing optional field.
|
|
127
128
|
|
|
128
129
|
`lint` asks the complementary question, *"is this well-curated, navigable,
|
|
129
130
|
trustworthy?"*, over exactly those tolerated things: reachability, backlog,
|
|
130
|
-
completeness, freshness, provenance, hygiene. It is
|
|
131
|
-
with findings unless you pass `--fail-on warn
|
|
131
|
+
completeness, freshness, provenance, attestation, migration, hygiene. It is
|
|
132
|
+
advisory and exits `0` even with findings unless you pass `--fail-on warn`, or
|
|
133
|
+
`--only legacy_timestamp,legacy_citations --fail-on info` to gate a migration
|
|
134
|
+
campaign on the two findings that name a bundle's leftover v0.1 spellings and
|
|
135
|
+
nothing else.
|
|
132
136
|
|
|
133
137
|
Keeping them apart is what lets you gate CI on conformance without gating it on
|
|
134
138
|
taste. `lint --json` is also the structured input an agent reads to reason about
|
|
135
139
|
the two things no checker can compute — contradictions, and *semantic* staleness.
|
|
136
140
|
|
|
141
|
+
## Trust is data, so you can filter on it
|
|
142
|
+
|
|
143
|
+
OKF v0.2 lets a bundle say where each concept came from and how far to trust
|
|
144
|
+
it — `generated` (who or what wrote it), `verified` (who confirmed it),
|
|
145
|
+
`status` (its lifecycle), `stale_after` (a declared expiry) — and this gem
|
|
146
|
+
reads the families everywhere: `--status` and `--trust` narrow `catalog`,
|
|
147
|
+
`files`, `search`, `tags` and `types`; the graph page shows each concept's
|
|
148
|
+
tier beside its type; `lint` reports what expired, against a clock you can
|
|
149
|
+
pin (`--today`) for a reproducible report. `okf references` closes the loop
|
|
150
|
+
for §10's attested computations, inventorying the `references/` files —
|
|
151
|
+
attester code, computation files — that back them, with every pointer that
|
|
152
|
+
resolves to nothing named. A v0.1 bundle needs none of this and stays
|
|
153
|
+
readable forever (§13); the two Migration findings tell it what to modernize
|
|
154
|
+
without ever failing it.
|
|
155
|
+
|
|
137
156
|
## Extending it
|
|
138
157
|
|
|
139
158
|
Publish a gem named `okf-*` carrying an `okf/plugin.rb` and installing it is the
|
data/lib/okf/bundle/folder.rb
CHANGED
|
@@ -62,6 +62,22 @@ module OKF
|
|
|
62
62
|
@bundle.directory_index
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
def stats
|
|
66
|
+
@bundle.stats
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def tag_groups(by:, entries: nil)
|
|
70
|
+
@bundle.tag_groups(by: by, entries: entries)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The §6.3 inventory — this handle's half is the disk: the reader models
|
|
74
|
+
# only markdown, so listing `references/` here is how a .py attester or a
|
|
75
|
+
# .sql computation becomes visible at all. The pure model gets the
|
|
76
|
+
# manifest and answers the rest (citers, dangling pointers).
|
|
77
|
+
def references
|
|
78
|
+
Bundle::References.build(@bundle, files: reference_files)
|
|
79
|
+
end
|
|
80
|
+
|
|
65
81
|
# Every log.md with its content, root scope first — read live from disk so a
|
|
66
82
|
# just-appended entry shows without a reload; the reserved snapshot is the
|
|
67
83
|
# fallback if the file has since vanished. Shared by `okf render`'s bake
|
|
@@ -119,7 +135,7 @@ module OKF
|
|
|
119
135
|
Concept::File.new(root: @root, path: path).read
|
|
120
136
|
end
|
|
121
137
|
|
|
122
|
-
# Materialize the in-memory bundle to disk (Writer validates §
|
|
138
|
+
# Materialize the in-memory bundle to disk (Writer validates §11 before
|
|
123
139
|
# publishing, so a malformed bundle is never written).
|
|
124
140
|
def save(overwrite: false)
|
|
125
141
|
Writer.call(
|
|
@@ -139,6 +155,16 @@ module OKF
|
|
|
139
155
|
|
|
140
156
|
private
|
|
141
157
|
|
|
158
|
+
# Bundle-relative paths of every file under references/, sorted. A glob
|
|
159
|
+
# on a directory that does not exist is simply empty — no references/ and
|
|
160
|
+
# an empty references/ are the same inventory.
|
|
161
|
+
def reference_files
|
|
162
|
+
Dir.glob(File.join(@root, "references", "**", "*"))
|
|
163
|
+
.select { |path| File.file?(path) }
|
|
164
|
+
.map { |path| Pathname.new(path).relative_path_from(Pathname.new(@root)).to_s }
|
|
165
|
+
.sort
|
|
166
|
+
end
|
|
167
|
+
|
|
142
168
|
def reserved_hash(basename)
|
|
143
169
|
@bundle.reserved
|
|
144
170
|
.select { |entry| File.basename(entry.path) == basename }
|
data/lib/okf/bundle/graph.rb
CHANGED
|
@@ -20,7 +20,7 @@ module OKF
|
|
|
20
20
|
attr_reader :nodes, :edges, :type_index, :tag_index
|
|
21
21
|
|
|
22
22
|
def self.build(bundle, minimal: false, body: true)
|
|
23
|
-
# Best-effort (§
|
|
23
|
+
# Best-effort (§11): a malformed concept never reaches here — the reader keeps
|
|
24
24
|
# it in bundle.unparseable — so the rest of the bundle still renders. Inspect
|
|
25
25
|
# bundle.unparseable to detect skips.
|
|
26
26
|
concepts = bundle.concepts
|
|
@@ -51,10 +51,19 @@ module OKF
|
|
|
51
51
|
# Edges resolve by *path* — a markdown link is a file path — then map that path
|
|
52
52
|
# to the concept living there and use its id, so a frontmatter `id` that differs
|
|
53
53
|
# from the path still lands the edge on the right node.
|
|
54
|
+
# Body links and sources[].resource entries feed the same resolver: §5.1
|
|
55
|
+
# says a `resource` naming another concept is a derivation edge that
|
|
56
|
+
# "already exists in the bundle graph", and this is what makes that true —
|
|
57
|
+
# it is also what keeps a migrated bundle's graph equal to its v0.1 twin's,
|
|
58
|
+
# since a `# Citations` in-bundle link stops being a body link the moment
|
|
59
|
+
# it is lifted into frontmatter. URLs and scope descriptors resolve to
|
|
60
|
+
# nothing; an unresolvable path is broken_source's to report.
|
|
54
61
|
def self.edges_for(concepts, id_by_path, root)
|
|
55
62
|
seen = Set.new
|
|
56
63
|
concepts.each_with_object([]) do |concept, edges|
|
|
57
|
-
Markdown::Links.extract(concept.body)
|
|
64
|
+
targets = Markdown::Links.extract(concept.body) +
|
|
65
|
+
concept.sources.map { |source| source["resource"] }.compact
|
|
66
|
+
targets.each do |raw|
|
|
58
67
|
resolved = Markdown::Links.resolve(raw, from: concept.path, bundle: root)
|
|
59
68
|
next if resolved.nil?
|
|
60
69
|
|
|
@@ -86,7 +95,7 @@ module OKF
|
|
|
86
95
|
concept.tags.is_a?(Array) ? concept.tags : []
|
|
87
96
|
end
|
|
88
97
|
|
|
89
|
-
# Blank, not just nil: §
|
|
98
|
+
# Blank, not just nil: §11 condition 2 makes a whitespace-only `type` as non-conformant
|
|
90
99
|
# as a missing one (the validator says so with the same OKF.blank?), so the
|
|
91
100
|
# index must not sort them into different buckets. Otherwise `type: " "`
|
|
92
101
|
# earns its own row, labelled with spaces, next to Untyped.
|