okf 2.0.0 → 2.1.1
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/.okf/capabilities/agent-skill.md +112 -0
- data/.okf/capabilities/bundles-manager.md +133 -0
- data/.okf/capabilities/graph-server.md +678 -0
- data/.okf/capabilities/index.md +26 -0
- data/.okf/capabilities/library-api.md +82 -0
- data/.okf/capabilities/linter.md +83 -0
- data/.okf/capabilities/read-views.md +228 -0
- data/.okf/capabilities/render.md +66 -0
- data/.okf/capabilities/search.md +295 -0
- data/.okf/capabilities/validator.md +60 -0
- data/.okf/cli.md +193 -0
- data/.okf/design/browser-tests.md +211 -0
- data/.okf/design/core-shell-split.md +73 -0
- data/.okf/design/index.md +17 -0
- data/.okf/design/integration-first.md +140 -0
- data/.okf/design/packaging.md +65 -0
- data/.okf/design/ruby-floor.md +53 -0
- data/.okf/design/runtime-dependencies.md +82 -0
- data/.okf/design/search-engines.md +154 -0
- data/.okf/design/server-trust-boundary.md +139 -0
- data/.okf/index.md +40 -0
- data/.okf/log.md +662 -0
- data/.okf/model/bundle.md +47 -0
- data/.okf/model/concept.md +75 -0
- data/.okf/model/graph.md +59 -0
- data/.okf/model/index.md +9 -0
- data/.okf/model/skeleton.md +76 -0
- data/.okf/overview.md +87 -0
- data/.okf/registry.md +265 -0
- data/.okf/structure/format-layer.md +59 -0
- data/.okf/structure/index.md +22 -0
- data/.okf/structure/search.md +53 -0
- data/.okf/structure/the-analysers.md +60 -0
- data/.okf/structure/the-cli.md +99 -0
- data/.okf/structure/the-disk-shell.md +67 -0
- data/.okf/structure/the-model.md +81 -0
- data/.okf/structure/the-server.md +74 -0
- data/.okf/structure/the-skill.md +52 -0
- data/.okf/testing/adding-a-verb.md +76 -0
- data/.okf/testing/index.md +12 -0
- data/.okf/testing/the-harness.md +45 -0
- data/CHANGELOG.md +83 -16
- data/README.md +205 -21
- data/lib/okf/cli.rb +20 -2
- data/lib/okf/skill/SKILL.md +15 -7
- data/lib/okf/skill/playbooks/maintain.md +3 -1
- data/lib/okf/skill/playbooks/produce.md +4 -2
- data/lib/okf/skill/reference/authoring.md +23 -26
- data/lib/okf/skill/reference/cli/checks.md +171 -0
- data/lib/okf/skill/reference/cli/graph.md +49 -0
- data/lib/okf/skill/reference/cli/map.md +98 -0
- data/lib/okf/skill/reference/cli/registry.md +70 -0
- data/lib/okf/skill/reference/cli/search.md +130 -0
- data/lib/okf/skill/reference/cli/serve.md +83 -0
- data/lib/okf/skill/reference/cli/views.md +59 -0
- data/lib/okf/skill/reference/cli.md +33 -603
- data/lib/okf/skill/reference/spec-map.md +32 -0
- data/lib/okf/version.rb +1 -1
- metadata +54 -5
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Component
|
|
3
|
+
title: The Format Layer
|
|
4
|
+
description: "Pure, and the bottom of everything: path normalisation with a root-escape guard, the one YAML door, and the link and citation grammars §5 and §8 are written in."
|
|
5
|
+
tags: [structure, format, pure, yaml, links]
|
|
6
|
+
generated:
|
|
7
|
+
by: human:maintainer
|
|
8
|
+
at: 2026-08-19T12:00:00Z
|
|
9
|
+
resource: lib/okf/path.rb
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# The files
|
|
13
|
+
|
|
14
|
+
| file | what it owns |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `lib/okf.rb` | `OKF::Error`, `OKF.blank?`, `OKF.iso8601`, `OKF.dir_of`, `SPEC_VERSION` |
|
|
17
|
+
| `lib/okf/version.rb` | `OKF::VERSION` |
|
|
18
|
+
| `lib/okf/path.rb` | `Path.normalize_relative!`, `join_under!`, `under?` — and `Path::Error` |
|
|
19
|
+
| `lib/okf/safe_read.rb` | `SafeRead.contained_path!`, `read!` — containment for a caller that already holds a root |
|
|
20
|
+
| `lib/okf/markdown/frontmatter.rb` | parse, dump, `stringify_keys` — **the only** YAML door |
|
|
21
|
+
| `lib/okf/markdown/links.rb` | §6: inline, reference and footnote links, and how a raw target resolves |
|
|
22
|
+
| `lib/okf/markdown/citations.rb` | §8: the `# Citations` section and its entries |
|
|
23
|
+
|
|
24
|
+
# Containment is a primitive here, not a habit
|
|
25
|
+
|
|
26
|
+
`Path.normalize_relative!` rejects any `..` segment outright, which is what makes
|
|
27
|
+
"a concept cannot link out of its own bundle" a property of the format rather
|
|
28
|
+
than a convention. `join_under!` and `under?` are the two questions every writer
|
|
29
|
+
and every reader asks before touching a path, and `SafeRead.read!` is the pair of
|
|
30
|
+
them plus the read, for callers that already hold a root — it resolves symlinks
|
|
31
|
+
and refuses one that leaves.
|
|
32
|
+
|
|
33
|
+
Every layer above reaches for these rather than composing its own check. A second
|
|
34
|
+
containment implementation is the shape this class of bug takes.
|
|
35
|
+
|
|
36
|
+
# One YAML door
|
|
37
|
+
|
|
38
|
+
**All YAML goes through `Markdown::Frontmatter`** — `safe_load`, with `Date` and
|
|
39
|
+
`Time` permitted and aliases off. `PSYCH_KEYWORDS` is the Psych < 3.1
|
|
40
|
+
positional-argument shim, and it lives here precisely so that
|
|
41
|
+
`YAML.safe_load`/`YAML.load` is called in exactly one place in the gem.
|
|
42
|
+
|
|
43
|
+
`stringify_keys` exists so that ActiveSupport does not, which is the same reason
|
|
44
|
+
`OKF.blank?` does.
|
|
45
|
+
|
|
46
|
+
# The link grammar is where the graph comes from
|
|
47
|
+
|
|
48
|
+
`Links.extract` walks prose lines only — `FENCE` and `CODE_SPAN` take fenced
|
|
49
|
+
blocks and code spans out first, because a link inside a code sample is a
|
|
50
|
+
document about a link, not an edge. `INLINE_LINK`, `REFERENCE_LINK` and
|
|
51
|
+
`DEFINITION` cover the three markdown spellings; `FOOTNOTE_REFERENCE` and
|
|
52
|
+
`FOOTNOTE_DEFINITION` are §8's keying. `SCHEME` and `MAILTO` are what keep an
|
|
53
|
+
external URL from being read as a relative path.
|
|
54
|
+
|
|
55
|
+
What is built on top of these is [the-model](/structure/the-model.md).
|
|
56
|
+
|
|
57
|
+
`Links.resolve` is the one that turns a raw target into a bundle-relative path,
|
|
58
|
+
and it is why a link split across a newline produces no edge — the extractor
|
|
59
|
+
reads a line at a time.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Structure
|
|
2
|
+
|
|
3
|
+
Every file under `lib/`, grouped by the layer that owns it. One concept owns
|
|
4
|
+
each file, and `test/unit/bundle_catalog_test.rb` fails if that stops being true
|
|
5
|
+
in either direction — a file no concept names, or a concept naming a file that
|
|
6
|
+
is gone.
|
|
7
|
+
|
|
8
|
+
Fifty files, eight layers, and one rule underneath all of them: **the core is
|
|
9
|
+
pure and the shell does the I/O.** `test/unit/boundary_test.rb` fails if a pure
|
|
10
|
+
file names a shell class or touches `File`, `Dir`, `FileUtils` or stdio. Put new
|
|
11
|
+
logic in the core; put new I/O in the shell.
|
|
12
|
+
|
|
13
|
+
Read it bottom-up — each layer depends only on the ones below it.
|
|
14
|
+
|
|
15
|
+
* [The Format Layer](format-layer.md) - `lib/okf.rb`, `path.rb`, `safe_read.rb`, `version.rb`, `markdown/` — pure: paths, containment, frontmatter, links, citations.
|
|
16
|
+
* [The Model](the-model.md) - `concept.rb`, `bundle.rb`, `bundle/graph.rb`, `references.rb`, `row_filter.rb`, `skeleton.rb` — pure: a bundle in memory, and every derived view of it.
|
|
17
|
+
* [The Analysers](the-analysers.md) - `bundle/validator*.rb`, `bundle/linter*.rb` — pure: §11 conformance, and curation quality, kept deliberately apart.
|
|
18
|
+
* [Search](search.md) - `bundle/search.rb` and its two engines — pure: the facade owns the rows, the engines own the matching.
|
|
19
|
+
* [The Disk Shell](the-disk-shell.md) - `concept/file.rb`, `bundle/reader.rb`, `writer.rb`, `folder.rb`, `registry.rb` — where directories become bundles and back.
|
|
20
|
+
* [The Server and the Page](the-server.md) - `server/app.rb`, `hub.rb`, `hub/not_found.rb`, `runner.rb`, `render/graph.rb` — one ERB template, served or baked.
|
|
21
|
+
* [The CLI](the-cli.md) - `cli.rb`, `cli/command.rb`, and the seventeen verb files — the only layer that parses argv, prints, and exits.
|
|
22
|
+
* [The Skill](the-skill.md) - `skill.rb` — the companion agent skill and its installer.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Component
|
|
3
|
+
title: Search — One Facade, Two Engines
|
|
4
|
+
description: The facade owns the rows, the snippets, the ranking fields and the engine registry; an engine owns only matching, declares its capabilities, and is chosen by what the query needs.
|
|
5
|
+
tags: [structure, search, pure, extension-point]
|
|
6
|
+
generated:
|
|
7
|
+
by: human:maintainer
|
|
8
|
+
at: 2026-08-19T12:00:00Z
|
|
9
|
+
resource: lib/okf/bundle/search.rb
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# The files
|
|
13
|
+
|
|
14
|
+
| file | what it owns |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `lib/okf/bundle/search.rb` | the facade: rows, snippets, weights, the `Corpus`, and the engine registry |
|
|
17
|
+
| `lib/okf/bundle/search/scan.rb` | the default engine — raw-text scan, the only one that does `regexp` |
|
|
18
|
+
| `lib/okf/bundle/search/index.rb` | the minifts engine — BM25+, the only one that does `fuzzy` and `prefix` |
|
|
19
|
+
|
|
20
|
+
# The split
|
|
21
|
+
|
|
22
|
+
The facade decides **what a result is**: `WEIGHTS` (which field counts how much),
|
|
23
|
+
`FIELDS`, `SNIPPET_FIELDS`, `SNIPPET_RADIUS`, and the row shape every caller
|
|
24
|
+
reads. An engine decides only **which documents matched**, and says what it can
|
|
25
|
+
do through `CAPABILITIES`.
|
|
26
|
+
|
|
27
|
+
`Search.engine_for(required)` picks by capability, not by name: `ROUTABLE` is the
|
|
28
|
+
set a query can *demand* (`regexp`, `fuzzy`), `DEFAULT_ENGINE` is `:scan`, and
|
|
29
|
+
`UnsupportedQuery` / `UnknownEngine` are the two honest refusals. `available?`
|
|
30
|
+
lets an engine decline at runtime — the index engine needs `minifts` present.
|
|
31
|
+
|
|
32
|
+
`Corpus` is the cross-bundle form, behind `Search.across`.
|
|
33
|
+
|
|
34
|
+
# Why scan leads
|
|
35
|
+
|
|
36
|
+
The scan is the default because a one-shot CLI cannot amortise an index build:
|
|
37
|
+
3.00 s versus 0.24 s at 1,000 concepts. That is a real argument against the
|
|
38
|
+
`minifts` dependency and it is recorded as such — but `--fuzzy` and parity with
|
|
39
|
+
the graph page's browser-side ranking both still need it, and a cached index
|
|
40
|
+
would restore the case outright.
|
|
41
|
+
|
|
42
|
+
Parity is the subtler half: the Ruby index engine is a bit-for-bit port of the
|
|
43
|
+
browser's MiniSearch, pinned to the same version the page lazy-loads, so an
|
|
44
|
+
`--engine index` result and a search typed into the page rank identically.
|
|
45
|
+
|
|
46
|
+
The verb that drives it is in [the-cli](/structure/the-cli.md).
|
|
47
|
+
|
|
48
|
+
# `Search.register` is an extension point
|
|
49
|
+
|
|
50
|
+
Append-only, idempotent by id, duck-type checked at registration —
|
|
51
|
+
**deliberately the same shape as `CLI.register`**. An engine is a module
|
|
52
|
+
answering `id`, `capabilities`, `available?`, `call`, and optionally `prepare`.
|
|
53
|
+
Adding one is a registration, not an edit to the facade.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Component
|
|
3
|
+
title: The Analysers — Validate and Lint, Kept Apart
|
|
4
|
+
description: Two pure analysers with a boundary that is a spec requirement rather than a preference — conformance may not reject curation problems, and curation may not emit conformance errors.
|
|
5
|
+
tags: [structure, validate, lint, pure, spec]
|
|
6
|
+
generated:
|
|
7
|
+
by: human:maintainer
|
|
8
|
+
at: 2026-08-19T12:00:00Z
|
|
9
|
+
resource: lib/okf/bundle/validator.rb
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# The files
|
|
13
|
+
|
|
14
|
+
| file | what it owns |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `lib/okf/bundle/validator.rb` | §11 conformance: hard errors, plus soft convention warnings |
|
|
17
|
+
| `lib/okf/bundle/validator/result.rb` | the errors/warnings collection and `valid?` |
|
|
18
|
+
| `lib/okf/bundle/linter.rb` | the curation report — every check, and their severities |
|
|
19
|
+
| `lib/okf/bundle/linter/report.rb` | findings by severity, the stats, and `healthy?` |
|
|
20
|
+
|
|
21
|
+
# The boundary is the point
|
|
22
|
+
|
|
23
|
+
**`validate` and `lint` stay separate**, and the line is the spec's, not taste.
|
|
24
|
+
§11 forbids the validator from *rejecting* a broken cross-link or a missing
|
|
25
|
+
optional field — those are warnings at most. Curation findings belong to lint,
|
|
26
|
+
which never emits a conformance error.
|
|
27
|
+
|
|
28
|
+
A new check goes to one side or the other, and getting it wrong changes what
|
|
29
|
+
exit code a user's CI sees: 0 ok, 1 a failing bundle, 2 a usage error.
|
|
30
|
+
|
|
31
|
+
`Validator::CONVENTION_CHECKS` is the soft half — the warnings that are about
|
|
32
|
+
convention rather than conformance, so a caller can tell them apart.
|
|
33
|
+
|
|
34
|
+
# Validator: one method per spec clause
|
|
35
|
+
|
|
36
|
+
The private methods are named for what they check — `validate_families`,
|
|
37
|
+
`validate_generated`, `validate_verified`, `validate_sources`,
|
|
38
|
+
`validate_usage_window`, `validate_lifecycle`, `validate_computation`,
|
|
39
|
+
`validate_parameters`, `validate_contract_mapping`, `validate_okf_version`,
|
|
40
|
+
`validate_index`, `validate_log`, `validate_reserved`, `validate_unparseable`.
|
|
41
|
+
A new §5 family is a new one of these; that is the shape to follow.
|
|
42
|
+
|
|
43
|
+
`validate_unparseable` is the one worth noticing: a file the reader could not
|
|
44
|
+
parse is an error, not an absence. A validator that skipped it would report
|
|
45
|
+
clean over the file most likely to be broken.
|
|
46
|
+
|
|
47
|
+
# Linter: severities are data, and downstream depends on them
|
|
48
|
+
|
|
49
|
+
`SEVERITIES` is the whole check list with each check's level, and `CHECKS` is
|
|
50
|
+
its keys. It is a public fact rather than an implementation detail — okf-pro
|
|
51
|
+
pins a frozen snapshot of it, because a released kernel that reclassified a
|
|
52
|
+
check would change what that gem's gate blocks on.
|
|
53
|
+
|
|
54
|
+
`only:` and `except:` select checks; `stale_before:`/`today:` supply the clock
|
|
55
|
+
that `expired` and `stale` need. **A check that could not run is reported, not
|
|
56
|
+
dropped**: `Report#to_h` carries `skipped_checks`, and `healthy?` over a silent
|
|
57
|
+
skip would be the same lie in a smaller box.
|
|
58
|
+
|
|
59
|
+
`DEFAULT_MIN_BODY` and `HUB_LIMIT` are the two tunables; `ACTOR_FORMS` is the
|
|
60
|
+
`generated.by` grammar, shared with the validator through `Concept`.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Component
|
|
3
|
+
title: The CLI — a Registry, a Base Class, and One File per Verb
|
|
4
|
+
description: The only layer that parses argv, prints and exits; a verb is a class answering four questions about itself and one about a run, and the require order at the bottom of `cli.rb` is the order `okf help` prints.
|
|
5
|
+
tags: [structure, cli, shell, extension-point, plugins]
|
|
6
|
+
generated:
|
|
7
|
+
by: human:maintainer
|
|
8
|
+
at: 2026-08-19T12:00:00Z
|
|
9
|
+
resource: lib/okf/cli.rb
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# The files
|
|
13
|
+
|
|
14
|
+
| file | what it owns |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `lib/okf/cli.rb` | the command registry, the dispatcher, plugin discovery, and `okf help` |
|
|
17
|
+
| `lib/okf/cli/command.rb` | the base every verb inherits: streams, refs, shared flags, printers |
|
|
18
|
+
|
|
19
|
+
And one file per verb, each registering itself at load:
|
|
20
|
+
|
|
21
|
+
| verb file | verb |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `lib/okf/cli/skill.rb` | `skill` |
|
|
24
|
+
| `lib/okf/cli/server.rb` | `server` |
|
|
25
|
+
| `lib/okf/cli/render.rb` | `render` |
|
|
26
|
+
| `lib/okf/cli/registry.rb` | `registry` and its eight subcommands |
|
|
27
|
+
| `lib/okf/cli/lint.rb` | `lint` |
|
|
28
|
+
| `lib/okf/cli/loose.rb` | `loose` |
|
|
29
|
+
| `lib/okf/cli/validate.rb` | `validate` |
|
|
30
|
+
| `lib/okf/cli/search.rb` | `search` |
|
|
31
|
+
| `lib/okf/cli/index.rb` | `index` |
|
|
32
|
+
| `lib/okf/cli/dirs.rb` | `dirs` |
|
|
33
|
+
| `lib/okf/cli/stats.rb` | `stats` |
|
|
34
|
+
| `lib/okf/cli/types.rb` | `types` |
|
|
35
|
+
| `lib/okf/cli/tags.rb` | `tags` |
|
|
36
|
+
| `lib/okf/cli/files.rb` | `files` |
|
|
37
|
+
| `lib/okf/cli/references.rb` | `references` |
|
|
38
|
+
| `lib/okf/cli/catalog.rb` | `catalog` |
|
|
39
|
+
| `lib/okf/cli/graph.rb` | `graph` |
|
|
40
|
+
|
|
41
|
+
**That table's order is the require order at the bottom of `cli.rb`, and the
|
|
42
|
+
require order IS the order `okf help` lists the verbs in.** A test pins the
|
|
43
|
+
result, but the coupling is in the source. What each verb *answers* is the
|
|
44
|
+
[cli](/cli.md)'s group table, pinned against `OKF::CLI.builtins` by
|
|
45
|
+
the same test; the walk a new one owes is
|
|
46
|
+
[adding-a-verb](/testing/adding-a-verb.md).
|
|
47
|
+
|
|
48
|
+
# What a verb is
|
|
49
|
+
|
|
50
|
+
A `CLI::Command` subclass answering four questions about itself — `.id`,
|
|
51
|
+
`.group`, `.help_rows`, `.hidden?` — and one about a run: `#call(argv)`,
|
|
52
|
+
returning the exit status. **Privacy is the boundary**: `#call` is the whole
|
|
53
|
+
public surface, so a helper cannot become a verb by accident. `DUCK_TYPE` is
|
|
54
|
+
that contract, checked at registration.
|
|
55
|
+
|
|
56
|
+
`GROUPS` is the ordering of the sections `okf help` prints; `ROW_FIELDS` is the
|
|
57
|
+
`--fields`/`--except` projection vocabulary.
|
|
58
|
+
|
|
59
|
+
# What the base class already gives you
|
|
60
|
+
|
|
61
|
+
Do not re-implement any of these in a verb:
|
|
62
|
+
|
|
63
|
+
* **refs** — `all_ref?`, and the `@slug` / bare `@` / `@group` resolution every
|
|
64
|
+
verb inherits, so `okf lint @handbook` works without the verb knowing about
|
|
65
|
+
registries.
|
|
66
|
+
* **flags** — `json_flags`, `help_flag`, `projection_flags`, `filter_flags`,
|
|
67
|
+
`depth_flag`. `FILTER_KEYS` is the shared filter vocabulary.
|
|
68
|
+
* **filtering** — `filter_entries`, `dir_scope`, `under_dir?`, and the `--area`
|
|
69
|
+
deprecation shim in `fold_area`.
|
|
70
|
+
* **printing** — `print_inverted_index` is the shared shape behind `types`,
|
|
71
|
+
`tags` and friends.
|
|
72
|
+
* **arity** — `no_extras?` is what makes a second bundle an exit-2 usage error
|
|
73
|
+
for the verbs that take only one. That was a real silent-wrong-answer bug:
|
|
74
|
+
`okf lint a b` once linted `a`, ignored `b`, and exited 0.
|
|
75
|
+
|
|
76
|
+
# `CLI.register` is the extension point
|
|
77
|
+
|
|
78
|
+
Append-only, idempotent by id, duck-type checked — **deliberately the same shape
|
|
79
|
+
as `Search.register`**. Any gem with `okf/plugin.rb` on its load path can
|
|
80
|
+
register a verb and okf finds it, with no edit here and no list of known addons
|
|
81
|
+
(a test greps `cli.rb` to keep it that way).
|
|
82
|
+
|
|
83
|
+
Discovery is **lazy**: a built-in never triggers a scan, so only an unknown verb
|
|
84
|
+
or `okf help` pays for it. A plugin that raises is skipped and reported on
|
|
85
|
+
stderr, never fatal. `declined` and `register_declined` are how a collision is
|
|
86
|
+
reported rather than silently won.
|
|
87
|
+
|
|
88
|
+
`PLUGIN_GEM_PREFIX` is `okf-`: **only gems named `okf-*` are loaded**, the same
|
|
89
|
+
convention Jekyll and Vagrant use. Argue it as a convention if it is revisited —
|
|
90
|
+
the threat it closes is thin, and overselling it invites false confidence.
|
|
91
|
+
|
|
92
|
+
One rule underneath it *is* load-bearing: **naming a gem must never load it.**
|
|
93
|
+
`plugin_gem_name` reads the spec's `full_gem_path` and requires nothing, because
|
|
94
|
+
a refusal that happens after the `require` is not a refusal; a test pins it. A
|
|
95
|
+
path belonging to no gem stays trusted — `ruby -I`, a Gemfile `path:`, a
|
|
96
|
+
checkout — someone put it there.
|
|
97
|
+
|
|
98
|
+
`seal_builtins!` is the line between what ships and what was installed, and it
|
|
99
|
+
is what `extension?` reads to print the "installed extensions" section.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Component
|
|
3
|
+
title: The Disk Shell
|
|
4
|
+
description: Where a directory becomes a Bundle and back — the reader, the atomic writer that validates before it publishes, the folder handle everything above it uses, and the registry.
|
|
5
|
+
tags: [structure, shell, io, registry, atomic]
|
|
6
|
+
generated:
|
|
7
|
+
by: human:maintainer
|
|
8
|
+
at: 2026-08-19T12:00:00Z
|
|
9
|
+
resource: lib/okf/bundle/folder.rb
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# The files
|
|
13
|
+
|
|
14
|
+
| file | what it owns |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `lib/okf/concept/file.rb` | one concept as an on-disk handle: read, save, delete, reload |
|
|
17
|
+
| `lib/okf/bundle/reader.rb` | a directory to a `Bundle`, unparseable files kept rather than dropped |
|
|
18
|
+
| `lib/okf/bundle/writer.rb` | a `Bundle` to a directory — locked, validated, then promoted atomically |
|
|
19
|
+
| `lib/okf/bundle/folder.rb` | the on-disk bundle handle every layer above actually holds |
|
|
20
|
+
| `lib/okf/registry.rb` | which bundles a machine or a project knows, addressed as `@slug` |
|
|
21
|
+
|
|
22
|
+
These are the shell. Everything they call into is pure, and
|
|
23
|
+
`test/unit/boundary_test.rb` keeps the arrow pointing one way.
|
|
24
|
+
|
|
25
|
+
# Folder is the handle, not a convenience
|
|
26
|
+
|
|
27
|
+
`Folder.load(dir)` is what the CLI, the server, the TUI and the MCP shell all
|
|
28
|
+
hold. It delegates `validate`, `lint`, `graph`, `skeleton`, `catalog`, `hubs`,
|
|
29
|
+
`directories`, `directory_index`, `stats`, `tag_groups`, `references` and
|
|
30
|
+
`log_entries` to the pure model, and adds only what needs the disk:
|
|
31
|
+
`concept_source`, `reference_files`, `reload`, `save`, and `Folder.label`.
|
|
32
|
+
|
|
33
|
+
Reach for `Folder`, not `Reader` — the reader is how a folder is built, once.
|
|
34
|
+
|
|
35
|
+
# The writer publishes or it does not
|
|
36
|
+
|
|
37
|
+
`Writer#call` takes a lock, writes the whole tree to a temporary path, runs the
|
|
38
|
+
**validator** against it, and only then promotes it into place. A bundle that
|
|
39
|
+
would not validate is never published, and a crash mid-write leaves the old tree
|
|
40
|
+
intact. `AlreadyExistsError` and `ValidationErrorFromResult` are the two
|
|
41
|
+
refusals; `safe_markdown_path!` is the containment check, borrowed from the
|
|
42
|
+
format layer rather than rewritten.
|
|
43
|
+
|
|
44
|
+
No CLI verb writes a bundle. This is the library API's surface, which is why its
|
|
45
|
+
integration coverage is low *by design*, and why a new verb reaching for it is
|
|
46
|
+
worth a second look — [adding-a-verb](/testing/adding-a-verb.md) says where
|
|
47
|
+
logic belongs instead.
|
|
48
|
+
|
|
49
|
+
# Registry: two files, one answer
|
|
50
|
+
|
|
51
|
+
`Registry` is the `@slug` layer. `HOME_ENV`/`DEFAULT_HOME` is the global
|
|
52
|
+
registry under `$OKF_HOME` (default `~/.okf`); `LOCAL_FILE` is the project-local
|
|
53
|
+
`.okf-registry.json` that `discover` finds by walking up from the working
|
|
54
|
+
directory. **A discovered local registry replaces the global one outright** — it
|
|
55
|
+
does not merge — and `NO_DISCOVERY_ENV` (`OKF_NO_DISCOVERY=1`) is the escape
|
|
56
|
+
hatch that forces the global one, which is what a test that must not see the
|
|
57
|
+
developer's registry sets.
|
|
58
|
+
|
|
59
|
+
`relative_base` is why a local registry's paths stay relative and the file
|
|
60
|
+
travels with the repository. `Registry#reopen` preserves it; `Registry.new(path)`
|
|
61
|
+
does not, and reaching for the latter is how a reload comes to resolve every
|
|
62
|
+
bundle against the wrong base.
|
|
63
|
+
|
|
64
|
+
`Group` is a named, recursive set of bundles. `RESERVED_SLUGS` is `all`, because
|
|
65
|
+
`@all` means every bundle and may not be shadowed. `slugify`, `dedupe`,
|
|
66
|
+
`normalize` and `path_shaped?` are the naming rules a `registry set` goes
|
|
67
|
+
through.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Component
|
|
3
|
+
title: The Model
|
|
4
|
+
description: A bundle and its concepts in memory, with no disk and no stdio anywhere in it — plus the four derived views every other layer reads instead of recomputing.
|
|
5
|
+
tags: [structure, model, pure, graph]
|
|
6
|
+
generated:
|
|
7
|
+
by: human:maintainer
|
|
8
|
+
at: 2026-08-19T12:00:00Z
|
|
9
|
+
resource: lib/okf/bundle.rb
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# The files
|
|
13
|
+
|
|
14
|
+
| file | what it owns |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `lib/okf/concept.rb` | one concept: frontmatter, body, and every §5 question about it |
|
|
17
|
+
| `lib/okf/bundle.rb` | the set: concepts, reserved files, and the rollups |
|
|
18
|
+
| `lib/okf/bundle/graph.rb` | nodes and edges, plus the type and tag indexes |
|
|
19
|
+
| `lib/okf/bundle/references.rb` | the `references/` inventory and who cites what |
|
|
20
|
+
| `lib/okf/bundle/row_filter.rb` | one predicate for `--type/--dir/--tag/--status/--trust` |
|
|
21
|
+
| `lib/okf/bundle/skeleton.rb` | directories, the arcs between them, and the suggested cut |
|
|
22
|
+
|
|
23
|
+
All six are **pure**. No `File`, no `Dir`, no stdio — `test/unit/boundary_test.rb`
|
|
24
|
+
fails the build if that changes.
|
|
25
|
+
|
|
26
|
+
# Concept: the §5 vocabulary lives here
|
|
27
|
+
|
|
28
|
+
`Concept` is where the spec's provenance model is implemented, and the parts
|
|
29
|
+
worth knowing before adding a field:
|
|
30
|
+
|
|
31
|
+
* `RESERVED_FILENAMES` and `reserved?` — `index.md` and `log.md` are not concepts.
|
|
32
|
+
* `STATUSES` / `DEFAULT_STATUS` / `effective_status` — §4.1's lifecycle, with the
|
|
33
|
+
default applied once so no caller has to remember it.
|
|
34
|
+
* `generated`, `generated_at`, `generated_by`, `declared_generated?` — a
|
|
35
|
+
*declared* provenance, never a derived one.
|
|
36
|
+
* `verified`, `fold_tier`, `shows_trust?` — the trust tiers, and the predicate
|
|
37
|
+
that decides whether a bundle even has a trust dimension to show. That last one
|
|
38
|
+
is a single predicate on purpose: a UI that gates a chip one way and a facet
|
|
39
|
+
another promises rows it will not return.
|
|
40
|
+
* `ISO_DATE`, `ISO_CUTOFF`, `ATTESTED_COMPUTATION`, `HUMAN_ACTOR` — the literals
|
|
41
|
+
the validator and the linter both read, rather than each spelling them.
|
|
42
|
+
|
|
43
|
+
`CONCEPT_SCOPED_CHECKS` is the list of lint checks that are about one concept, and
|
|
44
|
+
it is here rather than in the linter because it is a fact about the model.
|
|
45
|
+
|
|
46
|
+
# Bundle: the rollups every other layer reads
|
|
47
|
+
|
|
48
|
+
`Bundle` holds `concepts`, `reserved` and `unparseable` — an unreadable file is
|
|
49
|
+
kept as an `Entry` with its error rather than dropped, because a validator that
|
|
50
|
+
silently skips what it could not parse reports a clean bundle.
|
|
51
|
+
|
|
52
|
+
`catalog`, `stats`, `hubs`, `directories`, `directory_index`, `tag_groups` and
|
|
53
|
+
`paths_by_id` are the derived views. Read one rather than recomputing it: the
|
|
54
|
+
CLI, the server, the TUI and the MCP shell all answer from these, which is what
|
|
55
|
+
keeps four surfaces from disagreeing about the same number.
|
|
56
|
+
|
|
57
|
+
`okf_version` is what the bundle *declares* (§12), never a literal — the health
|
|
58
|
+
of every downstream v0.1-versus-v0.2 decision depends on that distinction.
|
|
59
|
+
`VIRTUAL_ROOT` is the path a rootless in-memory bundle is contained against.
|
|
60
|
+
|
|
61
|
+
The two analysers that read all of this are [the-analysers](/structure/the-analysers.md).
|
|
62
|
+
|
|
63
|
+
# Graph, Skeleton, References, RowFilter
|
|
64
|
+
|
|
65
|
+
`Graph.build` turns concepts into nodes and their links into edges, with
|
|
66
|
+
`type_index` and `tag_index` alongside. `unlinked_ids` is the orphan set.
|
|
67
|
+
`minimal:` and `body:` are how a caller asks for less than the whole thing —
|
|
68
|
+
the payload the graph page embeds is not the payload `okf graph --json` prints.
|
|
69
|
+
|
|
70
|
+
`Skeleton` is the directory-level view: `dirs`, `arcs` between them, and
|
|
71
|
+
`suggested_cut` — the weight at which the arc diagram stops being a hairball.
|
|
72
|
+
`cuts_for` maps that back onto concrete edges.
|
|
73
|
+
|
|
74
|
+
`References` inventories the `references/` folder and the concepts citing each
|
|
75
|
+
file, including the dangling ones.
|
|
76
|
+
|
|
77
|
+
`RowFilter.matches?` is the *single* predicate behind every `--type`, `--dir`,
|
|
78
|
+
`--tag`, `--status` and `--trust` filter in the CLI, the server and the TUI.
|
|
79
|
+
`shows_trust?` is the same gate `Concept` exposes, reachable from a catalog row.
|
|
80
|
+
A second filter implementation is how two views come to disagree about what
|
|
81
|
+
"matching" means.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Component
|
|
3
|
+
title: The Server and the Page
|
|
4
|
+
description: One ERB template that is the whole UI, served by a Rack app or baked to a file by `render` — plus the hub that mounts many bundles and owns the only writes in the server.
|
|
5
|
+
tags: [structure, server, rack, render, xss]
|
|
6
|
+
generated:
|
|
7
|
+
by: human:maintainer
|
|
8
|
+
at: 2026-08-19T12:00:00Z
|
|
9
|
+
resource: lib/okf/render/graph.rb
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# The files
|
|
13
|
+
|
|
14
|
+
| file | what it owns |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `lib/okf/render/graph.rb` | the ERB render — `Graph.static` bakes a file, the same class serves the page |
|
|
17
|
+
| `lib/okf/server/app.rb` | the Rack app for one bundle: the page and its JSON endpoints |
|
|
18
|
+
| `lib/okf/server/hub.rb` | N bundles at `/b/<slug>/`, plus the routes only a set can answer |
|
|
19
|
+
| `lib/okf/server/hub/not_found.rb` | the 404 page: what was asked for, what exists, and the nearest match |
|
|
20
|
+
| `lib/okf/server/runner.rb` | the built-in WEBrick to Rack bridge — no rackup file needed |
|
|
21
|
+
|
|
22
|
+
The template itself is `graph/template.html.erb` beside `render/graph.rb`, and it
|
|
23
|
+
is ~1,300 lines of inline JS and CSS. Both halves open with a section map; the JS
|
|
24
|
+
one also names the three seams that actually couple the sections
|
|
25
|
+
(`applyGraphFilter`, `setView`, the lazy caches). Read it before editing —
|
|
26
|
+
`grep -n '── '` on the template prints the same list with live line numbers.
|
|
27
|
+
|
|
28
|
+
# One template, two modes
|
|
29
|
+
|
|
30
|
+
`Render::Graph.static` bakes a self-contained file with the payload embedded;
|
|
31
|
+
`Server::App` serves the same template and lets the browser `fetch()` bodies on
|
|
32
|
+
demand. **The two modes diverge exactly there** — baked `EMBED` versus fetched
|
|
33
|
+
endpoints — which is why every browser spec runs twice, once against each.
|
|
34
|
+
|
|
35
|
+
`LAYOUTS` is the five Cytoscape layouts; `MIN_SIZE`/`MAX_SIZE` the node scaling.
|
|
36
|
+
|
|
37
|
+
# The page stays self-contained, and two XSS defenses hold the line
|
|
38
|
+
|
|
39
|
+
Only Cytoscape, marked and DOMPurify load from a CDN at boot; Mermaid, Panzoom,
|
|
40
|
+
MiniSearch and the extra layout engines lazy-load on first use. No htmx, no
|
|
41
|
+
bundler, no build step.
|
|
42
|
+
|
|
43
|
+
Two defenses, and a new render path that skips either one reopens the hole:
|
|
44
|
+
|
|
45
|
+
* **`json_for_script`** escapes `<` so inlined data cannot break out of its
|
|
46
|
+
`<script>` — `LT_ESCAPE` is that literal.
|
|
47
|
+
* **`DOMPurify.sanitize(marked.parse(...))`** runs on every fetched body before
|
|
48
|
+
it reaches `innerHTML`.
|
|
49
|
+
|
|
50
|
+
# App: the endpoints
|
|
51
|
+
|
|
52
|
+
`/` is the page; `/node`, `/node/meta`, `/catalog`, `/tags`, `/types`, `/index`
|
|
53
|
+
and `/log` are the JSON it pulls. `SEARCH_ENGINE` is `:index` here — a server
|
|
54
|
+
*can* amortise the build, which is the opposite of the CLI's default — and
|
|
55
|
+
`warm_search` is where that build happens, at boot rather than on the first
|
|
56
|
+
query.
|
|
57
|
+
|
|
58
|
+
The bundles it serves are loaded through [the-disk-shell](/structure/the-disk-shell.md).
|
|
59
|
+
|
|
60
|
+
# Hub: many bundles, and the only writes
|
|
61
|
+
|
|
62
|
+
`Hub` mounts each bundle under `MOUNT` (`/b/<slug>/`) and adds the routes a set
|
|
63
|
+
can answer that one bundle cannot: `GET /search` across all of them, `GET /b/`
|
|
64
|
+
for the listing, and `POST /registry/{default,rename,remove,add}`.
|
|
65
|
+
|
|
66
|
+
**Those four writes are the only writes in the whole server**, they are off
|
|
67
|
+
unless `writable:`, and they are guarded: `authentic?` and `same_origin?` with a
|
|
68
|
+
`token`, because a page that can rename a bundle is a page a hostile site would
|
|
69
|
+
like to submit a form to.
|
|
70
|
+
|
|
71
|
+
`Hub::NotFound` is a real page rather than a status code — it names what was
|
|
72
|
+
asked for, lists what exists, and offers the nearest slug by edit distance. It
|
|
73
|
+
carries its own CSS, mark and script because the 404 must render when the
|
|
74
|
+
bundle it was asked for does not exist.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Component
|
|
3
|
+
title: The Skill and Its Installer
|
|
4
|
+
description: The companion agent skill ships from exactly one tree in this gem, and two generated copies elsewhere in the repository are regenerated rather than edited.
|
|
5
|
+
tags: [structure, skill, generated, single-source]
|
|
6
|
+
generated:
|
|
7
|
+
by: human:maintainer
|
|
8
|
+
at: 2026-08-19T12:00:00Z
|
|
9
|
+
resource: lib/okf/skill.rb
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# The file
|
|
13
|
+
|
|
14
|
+
| file | what it owns |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `lib/okf/skill.rb` | `Skill.install` — the installer, its `ASSETS` tree, and `Skill::Error` |
|
|
17
|
+
|
|
18
|
+
`ASSETS` points at `lib/okf/skill/`, and **that tree is the single canonical
|
|
19
|
+
copy of the skill.** `okf skill <dest>` installs from it, so edit it there and
|
|
20
|
+
nowhere else.
|
|
21
|
+
|
|
22
|
+
`NAME` is `okf`, `SKILLS_DIR` is `skills`, and `nest:` decides whether the
|
|
23
|
+
install lands in a `skills/okf/` subdirectory or directly in the destination —
|
|
24
|
+
`--here` is the flag that turns it off. `force:` overwrites.
|
|
25
|
+
|
|
26
|
+
# Two generated copies exist, and neither is editable
|
|
27
|
+
|
|
28
|
+
`plugin/skills/okf` and `skills/okf` at the repository root are *generated*
|
|
29
|
+
copies — the Claude Code plugin's, and the one a generic skill installer reads.
|
|
30
|
+
`rake skill:sync` writes both from this tree and stamps the plugin manifest's
|
|
31
|
+
version.
|
|
32
|
+
|
|
33
|
+
Two guards fail on drift, both by file list **and** SHA-256 checksum:
|
|
34
|
+
`rake skill:verify`, which `build` depends on, and `test/plugin/sync_test.rb`.
|
|
35
|
+
So a release with a stale copy is impossible rather than a CI failure after the
|
|
36
|
+
fact.
|
|
37
|
+
|
|
38
|
+
# The markers in the skill text
|
|
39
|
+
|
|
40
|
+
Guidance lines in the skill carry stable anchors — `<!-- check:<lint-check-id> -->`
|
|
41
|
+
where a deterministic check enforces the point, `<!-- rule:okf-<slug> -->` for
|
|
42
|
+
pure-judgment craft. They render invisibly and sync verbatim into every copy, so
|
|
43
|
+
keep them on the line they annotate when you edit it. They exist so an eval can
|
|
44
|
+
pin a claim and a concept can cite one.
|
|
45
|
+
|
|
46
|
+
# Loading
|
|
47
|
+
|
|
48
|
+
The other shell that loads on demand is [the-cli](/structure/the-cli.md).
|
|
49
|
+
|
|
50
|
+
`skill.rb` is one of the two argv-facing shells that **do not** load with
|
|
51
|
+
`require "okf"` — the CLI is the other. `exe/okf` requires them, and so must any
|
|
52
|
+
test that drives them. An embedding application never pays for either.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Playbook
|
|
3
|
+
title: Adding a Verb, or a Subcommand
|
|
4
|
+
description: The steps a new command owes, in order — the file it lives in, the base class it must not re-implement, the three folders it earns a test in, and the catalogue entry the pin will demand.
|
|
5
|
+
tags: [testing, playbook, cli]
|
|
6
|
+
generated:
|
|
7
|
+
by: human:maintainer
|
|
8
|
+
at: 2026-08-19T12:00:00Z
|
|
9
|
+
resource: lib/okf/cli/command.rb
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Before adding one, check it is not already there
|
|
13
|
+
|
|
14
|
+
Seventeen commands and eight subcommands already exist, catalogued in
|
|
15
|
+
[cli](/cli.md) and [read-views](/capabilities/read-views.md), and `okf help`
|
|
16
|
+
prints the same list. Most of what a new verb would want is a *flag* on an
|
|
17
|
+
existing view rather than a view of its own: the shared filters and projections
|
|
18
|
+
— `--type/--dir/--tag/--status/--trust` and `--fields/--except` — compose with
|
|
19
|
+
every list already.
|
|
20
|
+
|
|
21
|
+
# The walk
|
|
22
|
+
|
|
23
|
+
1. **Write the failing test first**, in `test/integration/cli/`, in a file named
|
|
24
|
+
for the verb. Run it. It must fail for the reason you predicted — not because
|
|
25
|
+
a fixture is missing or a regex has a typo, which prove nothing about the
|
|
26
|
+
behaviour. A test written *after* the fix certifies only the code it was read
|
|
27
|
+
off.
|
|
28
|
+
2. **One file per verb**, at `lib/okf/cli/<verb>.rb`, registering itself at load.
|
|
29
|
+
A subcommand family stays in its parent's file, but earns its own *test*
|
|
30
|
+
file — `registry set` is a surface a user invokes on its own.
|
|
31
|
+
3. **Subclass `CLI::Command` and add nothing it already has.** Refs, the shared
|
|
32
|
+
flags, `filter_entries`, `print_inverted_index`, `no_extras?` — the list is in
|
|
33
|
+
[the-cli](/structure/the-cli.md). Re-implementing one of these is how two
|
|
34
|
+
verbs come to disagree about what `--dir` means.
|
|
35
|
+
4. **`#call` is the whole public surface.** Everything else is private, so a
|
|
36
|
+
helper cannot become a verb by accident. Answer `.id`, `.group`,
|
|
37
|
+
`.help_rows`, `.hidden?`.
|
|
38
|
+
5. **Add its `require` to the block at the bottom of `cli.rb`**, in the position
|
|
39
|
+
you want it to appear in `okf help` — that order *is* the help order, and a
|
|
40
|
+
test pins the result.
|
|
41
|
+
6. **Decide the arity, explicitly.** If it takes one bundle, `no_extras?` must
|
|
42
|
+
make a second an exit-2 usage error. If it takes several, it belongs in the
|
|
43
|
+
`across_bundles/` group with the ones that do.
|
|
44
|
+
7. **Prove it in every folder it has** — `by_dir/`, `by_registry/`, and
|
|
45
|
+
`across_bundles/` (which for a single-bundle verb means proving the *refusal*).
|
|
46
|
+
Then exercise the whole surface, not the happy path: every flag once, every
|
|
47
|
+
output format it offers, every exit code it can return, and the combinations
|
|
48
|
+
that actually interact.
|
|
49
|
+
8. **Update what describes it.** A new file under `lib/` needs its line in the
|
|
50
|
+
concept in [structure/](/structure/) that owns its layer, and the verb needs
|
|
51
|
+
its cell in [cli](/cli.md)'s group table.
|
|
52
|
+
`test/unit/bundle_catalog_test.rb` fails on either. So does the gem's README,
|
|
53
|
+
which owes a line for every verb; nothing enforces that one.
|
|
54
|
+
9. **Run the same test unedited**, then read the uncovered lines:
|
|
55
|
+
`bundle exec rake test:integration`, then diff
|
|
56
|
+
`coverage/integration/.resultset.json` for the files you changed. Three
|
|
57
|
+
shapes hide there by habit, because a unit test walked them first: the
|
|
58
|
+
*second* output format, an *error* branch and the exit code it carries, and
|
|
59
|
+
*malformed-input* robustness.
|
|
60
|
+
|
|
61
|
+
# Where the logic goes
|
|
62
|
+
|
|
63
|
+
Not in the verb. The CLI parses argv, prints and exits; the question belongs to
|
|
64
|
+
the pure model or to an analyser, so that the server, the TUI and the MCP shell
|
|
65
|
+
get the same answer without asking the CLI. If a verb is computing something,
|
|
66
|
+
it is probably a method on `Bundle` that has not been written yet.
|
|
67
|
+
|
|
68
|
+
New I/O goes in the shell, new logic in the core, and
|
|
69
|
+
`test/unit/boundary_test.rb` fails the build if a pure file forgets.
|
|
70
|
+
|
|
71
|
+
# The exit codes are a contract
|
|
72
|
+
|
|
73
|
+
`0` ok, `1` a failing bundle, `2` a usage error. And the older half of that
|
|
74
|
+
contract: **`validate` and `lint` stay separate** — a conformance check may not
|
|
75
|
+
live in lint and a curation finding may not fail validate. See
|
|
76
|
+
[structure/the-analysers](/structure/the-analysers.md).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
Why the suite is shaped the way it is — integration first, its coverage read as
|
|
4
|
+
a map rather than a score, and the graph page proven in a real browser — is in
|
|
5
|
+
[Design](/design/): [integration-first](/design/integration-first.md) and
|
|
6
|
+
[browser-tests](/design/browser-tests.md).
|
|
7
|
+
|
|
8
|
+
What is here is the procedure: the ordered walk a new surface owes, ending at the
|
|
9
|
+
checks that will refuse it if a step is skipped.
|
|
10
|
+
|
|
11
|
+
* [Adding a Verb](adding-a-verb.md) - The steps a new command or subcommand owes, in order.
|
|
12
|
+
* [The test harness](the-harness.md) - One base class for the whole suite, and why the 2.4 floor binds `test/` exactly as it binds `lib/`.
|