code2okf 0.1.0__py3-none-any.whl
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.
- code2okf/SPEC.md +1006 -0
- code2okf/__init__.py +8 -0
- code2okf/cli.py +234 -0
- code2okf/clis/inspectmd/pyproject.toml +40 -0
- code2okf/clis/inspectmd/src/inspectmd/__init__.py +8 -0
- code2okf/clis/inspectmd/src/inspectmd/__main__.py +5 -0
- code2okf/clis/inspectmd/src/inspectmd/cli.py +159 -0
- code2okf/clis/inspectmd/src/inspectmd/parse.py +212 -0
- code2okf/clis/inspectokf/pyproject.toml +40 -0
- code2okf/clis/inspectokf/src/inspectokf/__init__.py +8 -0
- code2okf/clis/inspectokf/src/inspectokf/__main__.py +5 -0
- code2okf/clis/inspectokf/src/inspectokf/cli.py +104 -0
- code2okf/clis/merkleokf/pyproject.toml +40 -0
- code2okf/clis/merkleokf/src/merkleokf/__init__.py +8 -0
- code2okf/clis/merkleokf/src/merkleokf/__main__.py +5 -0
- code2okf/clis/merkleokf/src/merkleokf/cli.py +121 -0
- code2okf/clis/merkleokf/src/merkleokf/merkle.py +145 -0
- code2okf/clis/sizeokf/pyproject.toml +40 -0
- code2okf/clis/sizeokf/src/sizeokf/__init__.py +8 -0
- code2okf/clis/sizeokf/src/sizeokf/__main__.py +5 -0
- code2okf/clis/sizeokf/src/sizeokf/cli.py +93 -0
- code2okf/clis/sizeokf/src/sizeokf/sizes.py +155 -0
- code2okf/compile.py +267 -0
- code2okf/events.py +86 -0
- code2okf/kit/README.md +128 -0
- code2okf/kit/files/home/.local/lib/code2okf/mount-state.sh +48 -0
- code2okf/kit/files/home/.pi/agent/AGENTS.md +185 -0
- code2okf/kit/files/home/.pi/agent/models.json +84 -0
- code2okf/kit/files/home/.pi/agent/settings.json +7 -0
- code2okf/kit/files/home/.pi/agent/skills/compile-okf/SKILL.md +142 -0
- code2okf/kit/files/home/.pi/agent/skills/compile-okf/scripts/check-okf.sh +155 -0
- code2okf/kit/files/home/.pi/agent/skills/compile-okf/scripts/frontmatter-guard.py +289 -0
- code2okf/kit/files/home/.pi/agent/skills/curate-okf/SKILL.md +68 -0
- code2okf/kit/files/home/.pi/agent/skills/inspect-md/SKILL.md +52 -0
- code2okf/kit/files/home/.pi/agent/skills/inspect-okf/SKILL.md +47 -0
- code2okf/kit/files/home/.pi/agent/skills/merkle-okf/SKILL.md +59 -0
- code2okf/kit/files/home/.pi/agent/skills/size-okf/SKILL.md +52 -0
- code2okf/kit/spec.yaml +312 -0
- code2okf/resources.py +74 -0
- code2okf/sandbox.py +266 -0
- code2okf/workbench.py +572 -0
- code2okf-0.1.0.dist-info/METADATA +391 -0
- code2okf-0.1.0.dist-info/RECORD +48 -0
- code2okf-0.1.0.dist-info/WHEEL +4 -0
- code2okf-0.1.0.dist-info/entry_points.txt +2 -0
- code2okf-0.1.0.dist-info/licenses/LICENSE +21 -0
- code2okf-0.1.0.dist-info/licenses/LICENSE-OKF-SPEC.txt +203 -0
- code2okf-0.1.0.dist-info/licenses/NOTICE-OKF-SPEC.md +37 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Check the wiki conventions that the OKF spec floor deliberately leaves open.
|
|
3
|
+
|
|
4
|
+
`okfctl validate` enforces the floor: every concept node carries a non-empty
|
|
5
|
+
`type` (OKF v0.2 §11). Everything beyond that is a producer's choice, so the
|
|
6
|
+
conventions this wiki settled on -- a title, a description, tags, provenance,
|
|
7
|
+
and a well-formed update log -- need their own check. This is that check.
|
|
8
|
+
|
|
9
|
+
Usage: frontmatter-guard.py [bundle]
|
|
10
|
+
bundle wiki root to check (default: ., the workspace, which IS the wiki root)
|
|
11
|
+
|
|
12
|
+
Exit codes:
|
|
13
|
+
0 clean
|
|
14
|
+
1 findings (one per line on stdout)
|
|
15
|
+
2 usage or runtime error (bad path, or SPEC.md could not be found)
|
|
16
|
+
|
|
17
|
+
SPEC.md is read to learn which okf_version the root index must declare, so the
|
|
18
|
+
check moves with the spec instead of hard-coding a number. It is looked up as
|
|
19
|
+
the sibling of the bundle directory, which is one rule that covers both layouts:
|
|
20
|
+
on the host the bundle is `./okf` and the spec is `./SPEC.md`; in the sandbox the
|
|
21
|
+
workspace IS the bundle and the spec is the read-only `../SPEC.md` mount. Set
|
|
22
|
+
SPEC_MD to override.
|
|
23
|
+
|
|
24
|
+
Deliberately dependency-free: the sandbox installs a bare python3, so PyYAML is
|
|
25
|
+
not available and the frontmatter is parsed directly. The parser understands the
|
|
26
|
+
small YAML subset this wiki uses and reports anything it cannot read, rather than
|
|
27
|
+
passing it silently.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import itertools
|
|
33
|
+
import os
|
|
34
|
+
import re
|
|
35
|
+
import sys
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
RESERVED = {"index.md", "log.md"}
|
|
39
|
+
|
|
40
|
+
# §5: "Every timestamp-valued key in OKF is an ISO 8601 datetime with an explicit
|
|
41
|
+
# UTC offset". A bare date is what the okfctl migrate bug produces, so rejecting
|
|
42
|
+
# it here is load-bearing, not pedantry.
|
|
43
|
+
ISO_UTC = re.compile(
|
|
44
|
+
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# §7 actor convention: <producer>/<version>, human:<id>, or process:<id>.
|
|
48
|
+
ACTOR = re.compile(r"^([^\s:/]+/[^\s:/]+|human:[^\s]+|process:[^\s]+)$")
|
|
49
|
+
|
|
50
|
+
# The spec states its own revision as, e.g., "**Version 0.2**".
|
|
51
|
+
SPEC_VERSION = re.compile(r"^\*\*Version\s+(\d+\.\d+)\*\*\s*$", re.MULTILINE)
|
|
52
|
+
|
|
53
|
+
DATE_HEADING = re.compile(r"^##\s+(\d{4}-\d{2}-\d{2})\s*$", re.MULTILINE)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def strip_quotes(value: str) -> str:
|
|
57
|
+
"""Unwrap a quoted scalar, honouring the \\" escape the wiki's titles use."""
|
|
58
|
+
value = value.strip()
|
|
59
|
+
if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":
|
|
60
|
+
inner = value[1:-1]
|
|
61
|
+
return inner.replace('\\"', '"') if value[0] == '"' else inner
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def split_flow(body: str) -> list[str]:
|
|
66
|
+
"""Split a flow collection's body on commas that are not inside quotes."""
|
|
67
|
+
items, current, quote = [], [], ""
|
|
68
|
+
for char in body:
|
|
69
|
+
if quote:
|
|
70
|
+
if char == quote:
|
|
71
|
+
quote = ""
|
|
72
|
+
current.append(char)
|
|
73
|
+
elif char in "\"'":
|
|
74
|
+
quote = char
|
|
75
|
+
current.append(char)
|
|
76
|
+
elif char == ",":
|
|
77
|
+
items.append("".join(current))
|
|
78
|
+
current = []
|
|
79
|
+
else:
|
|
80
|
+
current.append(char)
|
|
81
|
+
items.append("".join(current))
|
|
82
|
+
return [item for item in (i.strip() for i in items) if item]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def parse_scalar(value: str) -> object:
|
|
86
|
+
"""Parse one YAML value: a flow sequence, a flow mapping, or a scalar."""
|
|
87
|
+
value = value.strip()
|
|
88
|
+
if value.startswith("[") and value.endswith("]"):
|
|
89
|
+
return [strip_quotes(item) for item in split_flow(value[1:-1])]
|
|
90
|
+
if value.startswith("{") and value.endswith("}"):
|
|
91
|
+
mapping = {}
|
|
92
|
+
for item in split_flow(value[1:-1]):
|
|
93
|
+
key, sep, val = item.partition(":")
|
|
94
|
+
if sep:
|
|
95
|
+
mapping[key.strip()] = strip_quotes(val)
|
|
96
|
+
return mapping
|
|
97
|
+
return strip_quotes(value)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def parse_frontmatter(text: str) -> dict | None:
|
|
101
|
+
"""Return the frontmatter block as a dict, or None when there is no block.
|
|
102
|
+
|
|
103
|
+
Handles the shapes this wiki uses: `key: value`, flow sequences and mappings,
|
|
104
|
+
a one-level block mapping (`generated:` with indented `by:`/`at:`), and a
|
|
105
|
+
block sequence of plain strings. A value is always split on the FIRST colon,
|
|
106
|
+
so a URL or a timestamp keeps its colons.
|
|
107
|
+
"""
|
|
108
|
+
if not text.startswith("---\n"):
|
|
109
|
+
return None
|
|
110
|
+
end = text.find("\n---", 3)
|
|
111
|
+
if end == -1:
|
|
112
|
+
return None
|
|
113
|
+
block = text[4 : end + 1]
|
|
114
|
+
|
|
115
|
+
data: dict[str, object] = {}
|
|
116
|
+
key: str | None = None
|
|
117
|
+
for raw in block.splitlines():
|
|
118
|
+
if not raw.strip() or raw.lstrip().startswith("#"):
|
|
119
|
+
continue
|
|
120
|
+
if raw[0] not in " \t-":
|
|
121
|
+
name, sep, value = raw.partition(":")
|
|
122
|
+
if not sep:
|
|
123
|
+
continue
|
|
124
|
+
key = name.strip()
|
|
125
|
+
data[key] = parse_scalar(value) if value.strip() else {}
|
|
126
|
+
elif key is not None:
|
|
127
|
+
item = raw.strip()
|
|
128
|
+
if item.startswith("- "):
|
|
129
|
+
existing = data.get(key)
|
|
130
|
+
if not isinstance(existing, list):
|
|
131
|
+
existing = []
|
|
132
|
+
data[key] = existing
|
|
133
|
+
existing.append(strip_quotes(item[2:]))
|
|
134
|
+
else:
|
|
135
|
+
name, sep, value = item.partition(":")
|
|
136
|
+
if sep and isinstance(data.get(key), dict):
|
|
137
|
+
data[key][name.strip()] = strip_quotes(value) # type: ignore[index]
|
|
138
|
+
return data
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def spec_version(bundle: Path) -> str:
|
|
142
|
+
"""The okf_version the root index must declare, read from SPEC.md."""
|
|
143
|
+
override = os.environ.get("SPEC_MD")
|
|
144
|
+
spec = Path(override) if override else bundle.resolve().parent / "SPEC.md"
|
|
145
|
+
if not spec.is_file():
|
|
146
|
+
sys.exit(
|
|
147
|
+
f"Error: SPEC.md not found at {spec}. It is looked up as the sibling "
|
|
148
|
+
"of the bundle; set SPEC_MD to override."
|
|
149
|
+
)
|
|
150
|
+
match = SPEC_VERSION.search(spec.read_text(encoding="utf-8"))
|
|
151
|
+
if not match:
|
|
152
|
+
sys.exit(f"Error: no '**Version X.Y**' line in {spec}.")
|
|
153
|
+
return match.group(1)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def check_node(path: Path, rel: str, findings: list[str]) -> None:
|
|
157
|
+
"""Check one concept node's frontmatter."""
|
|
158
|
+
data = parse_frontmatter(path.read_text(encoding="utf-8"))
|
|
159
|
+
if data is None:
|
|
160
|
+
findings.append(f"frontmatter: {rel} has no parseable YAML frontmatter block")
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
for field in ("title", "description"):
|
|
164
|
+
value = data.get(field)
|
|
165
|
+
if not isinstance(value, str) or not value.strip():
|
|
166
|
+
findings.append(f"{field}: {rel} has no non-empty {field}")
|
|
167
|
+
|
|
168
|
+
tags = data.get("tags")
|
|
169
|
+
if not isinstance(tags, list) or not tags:
|
|
170
|
+
findings.append(f"tags: {rel} has no tags list (a non-empty YAML list)")
|
|
171
|
+
elif not all(isinstance(tag, str) and tag.strip() for tag in tags):
|
|
172
|
+
findings.append(f"tags: {rel} has a tags list with a non-string entry")
|
|
173
|
+
|
|
174
|
+
check_provenance(rel, data, findings)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def check_provenance(rel: str, data: dict, findings: list[str]) -> None:
|
|
178
|
+
"""Check §5.2 `generated: { by, at }`.
|
|
179
|
+
|
|
180
|
+
The wiki was migrated to v0.2, so the legacy v0.1 `timestamp` key is a
|
|
181
|
+
finding rather than an accepted fallback: §13.1 lets a *consumer* fall back
|
|
182
|
+
to it, but a producer writing new v0.2 pages has no reason to emit one, and
|
|
183
|
+
silently accepting it would let the corpus drift back.
|
|
184
|
+
"""
|
|
185
|
+
generated = data.get("generated")
|
|
186
|
+
if isinstance(generated, dict) and generated:
|
|
187
|
+
by = str(generated.get("by", "")).strip()
|
|
188
|
+
at = str(generated.get("at", "")).strip()
|
|
189
|
+
if not ACTOR.match(by):
|
|
190
|
+
findings.append(
|
|
191
|
+
f"generated.by: {rel} has {by!r}, not a §7 actor "
|
|
192
|
+
"(<producer>/<version>, human:<id>, or process:<id>)"
|
|
193
|
+
)
|
|
194
|
+
if not ISO_UTC.match(at):
|
|
195
|
+
findings.append(
|
|
196
|
+
f"generated.at: {rel} has {at!r}, not an ISO 8601 datetime with "
|
|
197
|
+
"an explicit UTC offset (§5)"
|
|
198
|
+
)
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
if data.get("timestamp"):
|
|
202
|
+
findings.append(
|
|
203
|
+
f"generated: {rel} carries a legacy v0.1 `timestamp`; v0.2 §13.1 "
|
|
204
|
+
"supersedes it with `generated: { by, at }` (§5.2)"
|
|
205
|
+
)
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
findings.append(
|
|
209
|
+
f"generated: {rel} records no provenance (§5.2 `generated: {{ by, at }}`)"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def check_root_index(bundle: Path, findings: list[str]) -> None:
|
|
214
|
+
"""§12: the bundle-root index declares okf_version, and nothing else."""
|
|
215
|
+
index = bundle / "index.md"
|
|
216
|
+
if not index.is_file():
|
|
217
|
+
findings.append("index: the bundle root has no index.md")
|
|
218
|
+
return
|
|
219
|
+
data = parse_frontmatter(index.read_text(encoding="utf-8"))
|
|
220
|
+
if data is None:
|
|
221
|
+
findings.append(
|
|
222
|
+
"okf_version: index.md carries no frontmatter block; the bundle-root "
|
|
223
|
+
"index declares the spec version (§12)"
|
|
224
|
+
)
|
|
225
|
+
return
|
|
226
|
+
want = spec_version(bundle)
|
|
227
|
+
got = str(data.get("okf_version", "")).strip()
|
|
228
|
+
if got != want:
|
|
229
|
+
findings.append(
|
|
230
|
+
f"okf_version: index.md declares {got!r} but SPEC.md is version {want!r}"
|
|
231
|
+
)
|
|
232
|
+
extra = sorted(key for key in data if key != "okf_version")
|
|
233
|
+
if extra:
|
|
234
|
+
findings.append(
|
|
235
|
+
"okf_version: index.md frontmatter may contain only okf_version (§12); "
|
|
236
|
+
f"found {', '.join(extra)}"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def check_log(bundle: Path, findings: list[str]) -> None:
|
|
241
|
+
"""§9: log.md exists, and its date headings are ISO 8601, newest first."""
|
|
242
|
+
log = bundle / "log.md"
|
|
243
|
+
if not log.is_file():
|
|
244
|
+
findings.append("log: the bundle root has no log.md (§9)")
|
|
245
|
+
return
|
|
246
|
+
dates = DATE_HEADING.findall(log.read_text(encoding="utf-8"))
|
|
247
|
+
if not dates:
|
|
248
|
+
findings.append("log: log.md has no '## YYYY-MM-DD' date headings (§9)")
|
|
249
|
+
return
|
|
250
|
+
for earlier, later in itertools.pairwise(dates):
|
|
251
|
+
if earlier < later:
|
|
252
|
+
findings.append(
|
|
253
|
+
f"log: log.md heading {later} follows {earlier}; dates run "
|
|
254
|
+
"newest first (§9)"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def main(argv: list[str]) -> int:
|
|
259
|
+
bundle = Path(argv[1] if len(argv) > 1 else ".")
|
|
260
|
+
if not bundle.is_dir():
|
|
261
|
+
sys.exit(f"Error: wiki bundle not found: {bundle}")
|
|
262
|
+
|
|
263
|
+
findings: list[str] = []
|
|
264
|
+
for path in sorted(bundle.rglob("*.md")):
|
|
265
|
+
if path.name in RESERVED:
|
|
266
|
+
continue
|
|
267
|
+
check_node(path, str(path.relative_to(bundle)), findings)
|
|
268
|
+
check_root_index(bundle, findings)
|
|
269
|
+
check_log(bundle, findings)
|
|
270
|
+
|
|
271
|
+
if not findings:
|
|
272
|
+
print("OK: frontmatter, provenance and log conventions hold")
|
|
273
|
+
return 0
|
|
274
|
+
for finding in findings:
|
|
275
|
+
print(finding)
|
|
276
|
+
print(f"{len(findings)} guard finding(s)")
|
|
277
|
+
return 1
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
if __name__ == "__main__":
|
|
281
|
+
try:
|
|
282
|
+
sys.exit(main(sys.argv))
|
|
283
|
+
except SystemExit as exc:
|
|
284
|
+
# sys.exit(str) prints the message and exits 1; this guard's contract
|
|
285
|
+
# reserves 1 for findings and 2 for "could not run", so remap.
|
|
286
|
+
if isinstance(exc.code, str):
|
|
287
|
+
print(exc.code, file=sys.stderr)
|
|
288
|
+
sys.exit(2)
|
|
289
|
+
raise
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: curate-okf
|
|
3
|
+
description: Check the wiki against the OKF spec and its curation health, and maintain nodes and indexes without breaking links. Use before finishing a run, and whenever adding, moving or removing a page.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Curate the wiki with `okfctl`
|
|
7
|
+
|
|
8
|
+
Use the `okfctl` CLI (on `PATH`) to check the wiki and to maintain its nodes and
|
|
9
|
+
reserved `index.md` files. The skill name is `curate-okf`; the binary is
|
|
10
|
+
`okfctl` — never shell the skill id.
|
|
11
|
+
|
|
12
|
+
## Invocation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
okfctl index build "$PWD" # after adding, moving or removing a page
|
|
16
|
+
okfctl analyze "$PWD" # where the wiki is weak — advice, never a gate
|
|
17
|
+
okfctl node mv old.md new.md --bundle "$PWD" # rename, rewriting inbound links
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- **Always name the wiki as `"$PWD"`.** You are already in the wiki root, and
|
|
21
|
+
okfctl's own default is the current directory, but the explicit path keeps a
|
|
22
|
+
command copied into a skill or a log unambiguous.
|
|
23
|
+
- `index build` **owns every `index.md`** — never hand-edit one. It regenerates
|
|
24
|
+
the link lists from what is on disk, so an index can never promise a page that
|
|
25
|
+
does not exist.
|
|
26
|
+
- `node mv` rewrites every inbound link and preserves each link's existing form,
|
|
27
|
+
so bundle-absolute prose links stay absolute.
|
|
28
|
+
- Allowed beyond the above: `validate`, `lint`, `bundle info`, `index check`,
|
|
29
|
+
`node list|show|new|rm`, `search`, `graph export`, `template list|show`,
|
|
30
|
+
`version`.
|
|
31
|
+
- Exit codes: `0` ok, `1` findings, `2` usage or runtime error.
|
|
32
|
+
|
|
33
|
+
## Workflow
|
|
34
|
+
|
|
35
|
+
Write the pages first, then `okfctl index build`, then the gate —
|
|
36
|
+
`~/.pi/agent/skills/compile-okf/scripts/check-okf.sh`, which runs `validate`,
|
|
37
|
+
the frontmatter guard, `lint`, the dangling-link check and `index check` in one
|
|
38
|
+
pass. Rebuild the index *before* the gate: `index check` fails closed, so a page
|
|
39
|
+
added without a rebuild is reported as both a stale index and an orphan.
|
|
40
|
+
|
|
41
|
+
`okfctl node new` writes `created`/`modified`, **not** the `generated: { by, at }`
|
|
42
|
+
this wiki uses — fill the frontmatter in yourself afterwards, as `AGENTS.md`
|
|
43
|
+
describes. The guard catches it if you forget.
|
|
44
|
+
|
|
45
|
+
## Reading the output
|
|
46
|
+
|
|
47
|
+
`lint` findings come in two classes, and the gate treats them differently:
|
|
48
|
+
|
|
49
|
+
| Class | Checks | What to do |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| Defect | `broken-link`, `orphan`, `type-hygiene`, `status-lifecycle`, `spec-version` | **Blocks the run.** Each has one correct fix — repair the path, link or rebuild the index. |
|
|
52
|
+
| Judgment | `missing-xref`, `coverage-gap` | Printed, never blocking. Act on it when the wiki genuinely reads better for it. |
|
|
53
|
+
|
|
54
|
+
`analyze` is a report, not a gate: thin pages, uncited pages and tag clusters are
|
|
55
|
+
prompts for judgment. It exits `0` however much it finds.
|
|
56
|
+
|
|
57
|
+
## Limits
|
|
58
|
+
|
|
59
|
+
- **Never run `log append` or `log show`.** They write a second `# Change Log`
|
|
60
|
+
heading in a flat format that contradicts the spec's dated `##` headings. Write
|
|
61
|
+
`log.md` by hand, as `AGENTS.md` describes.
|
|
62
|
+
- **Never run `migrate`, `eval`, `serve`, `registry`, `connect`, or anything
|
|
63
|
+
semantic** (`okfctl-search`, `lint --semantic`). `migrate` is a one-off the
|
|
64
|
+
maintainers have already run; the rest need a network or a model this sandbox
|
|
65
|
+
does not have.
|
|
66
|
+
- A clean gate means the wiki is well-formed and well-linked. It says nothing
|
|
67
|
+
about whether the prose is faithful to the source — that is `compile-okf`'s
|
|
68
|
+
fidelity rule, and it outranks a clean report.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: inspect-md
|
|
3
|
+
description: Map headings in a long Markdown source under ../md/ before reading it in ranges. Use when a source is too large to pull into one call.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Map a long Markdown source with `inspectmd`
|
|
7
|
+
|
|
8
|
+
Use the `inspectmd` CLI (on `PATH`) to plan ranged reads of a file under
|
|
9
|
+
`../md/`.
|
|
10
|
+
The skill name is `inspect-md`; the binary is `inspectmd` — never shell the
|
|
11
|
+
skill id.
|
|
12
|
+
|
|
13
|
+
## Invocation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
inspectmd ../md/<document>.md
|
|
17
|
+
inspectmd -L 2 ../md/<document>.md
|
|
18
|
+
inspectmd --section N ../md/<document>.md
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- Requires a Markdown **file** path (not a directory).
|
|
22
|
+
- `-L`/`--level N` caps the map at heading level `N` (`N` ≥ 1). Omit for every
|
|
23
|
+
heading.
|
|
24
|
+
- `--section N` prints only `start:end N words` for a ranged read.
|
|
25
|
+
- Exit codes: `0` ok, `2` usage or runtime error.
|
|
26
|
+
|
|
27
|
+
## Workflow
|
|
28
|
+
|
|
29
|
+
Map → cut → read. Do **not** treat `-L` as a directory depth (that is the wiki
|
|
30
|
+
tools).
|
|
31
|
+
|
|
32
|
+
1. Run `inspectmd -L 2 ../md/<document>.md` (or without `-L` if you need deeper
|
|
33
|
+
headings).
|
|
34
|
+
2. Pick a section `Index`, then `inspectmd --section N ../md/<document>.md`.
|
|
35
|
+
3. Ranged-read that line span — never pull a whole book into one call.
|
|
36
|
+
|
|
37
|
+
## Reading the output
|
|
38
|
+
|
|
39
|
+
| Column | Meaning |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| `Index` | Section number in document order (`0` = preamble when present). Pass this to `--section`. |
|
|
42
|
+
| `Level` | Heading depth: `0` preamble, `1` = `#`, …, `6` = `######`. |
|
|
43
|
+
| `Lines` | 1-based inclusive line range (`start-end`) for that section. |
|
|
44
|
+
| `Words` | Whitespace-split word count of that range. |
|
|
45
|
+
| `Slug` | Kebab-case slug from the heading title (same style as OKF file names). |
|
|
46
|
+
| `Title` | Heading text as written (or `(preamble)` / `(empty)`). |
|
|
47
|
+
|
|
48
|
+
## Limits
|
|
49
|
+
|
|
50
|
+
- Maps **ATX headings only** (`#` … `######`).
|
|
51
|
+
- The map is a plan for cuts and reads — **not** permission to paraphrase source
|
|
52
|
+
prose. The fidelity rule in `compile-okf` still governs.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: inspect-okf
|
|
3
|
+
description: Survey what the OKF wiki already contains before writing. Use when choosing where to place or update wiki pages.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Survey the wiki with `inspectokf`
|
|
7
|
+
|
|
8
|
+
Use the `inspectokf` CLI (on `PATH`) to see what the wiki already holds. The
|
|
9
|
+
skill name is `inspect-okf`; the binary is `inspectokf` — never shell the
|
|
10
|
+
skill id.
|
|
11
|
+
|
|
12
|
+
## Invocation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
inspectokf -L 1 "$PWD" # top level only: the categories — start here
|
|
16
|
+
inspectokf "$PWD/<topic>" # then descend into the one category you need
|
|
17
|
+
inspectokf "$PWD" # every page: hundreds of lines — avoid opening with this
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- **Always name the wiki as `"$PWD"`**. You are already in the wiki root; the
|
|
21
|
+
CLI's own default (`okf/`) is for host-side use and would select an invalid
|
|
22
|
+
nested `okf/` directory here. Any existing directory works, typically a wiki
|
|
23
|
+
subfolder.
|
|
24
|
+
- `-L`/`--level N` descends at most `N` directory levels (`N` ≥ 1). Default:
|
|
25
|
+
unlimited.
|
|
26
|
+
- Output is the `tree` listing of that path. An empty or dotfile-only directory
|
|
27
|
+
exits `0` with `0 directories, 0 files` and does not require `tree`.
|
|
28
|
+
- Exit codes: `0` ok, `2` usage or runtime error.
|
|
29
|
+
|
|
30
|
+
## Workflow
|
|
31
|
+
|
|
32
|
+
Shallow first, then descend into the one path that matters. Do not open with the
|
|
33
|
+
full unlimited tree.
|
|
34
|
+
|
|
35
|
+
## Reading the output
|
|
36
|
+
|
|
37
|
+
Each line is a path that exists on disk. Folders and files appear as `tree`
|
|
38
|
+
renders them. Use this to find pages already covering a topic before writing.
|
|
39
|
+
|
|
40
|
+
## Limits
|
|
41
|
+
|
|
42
|
+
- Hides **dotfiles**, so anything starting with `.` is invisible here.
|
|
43
|
+
- Slugs are lossy (`1981.md`, `exams.md` say nothing about content) — open the
|
|
44
|
+
page when you need substance.
|
|
45
|
+
- A page listed here may still be **unreachable in the wiki** if no `index.md`
|
|
46
|
+
links it. The gate does catch that — `okfctl lint` reports it as an `orphan`,
|
|
47
|
+
which usually means the indexes need `okfctl index build`.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: merkle-okf
|
|
3
|
+
description: Confirm which wiki pages a run actually changed via Merkle hashes. Use before and after writing to the wiki.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Confirm what changed with `merkleokf`
|
|
7
|
+
|
|
8
|
+
Use the `merkleokf` CLI (on `PATH`) to localise edits in the wiki. The skill
|
|
9
|
+
name is `merkle-okf`; the binary is `merkleokf` — never shell the skill id.
|
|
10
|
+
|
|
11
|
+
## Invocation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
merkleokf -L 1 "$PWD" # categories — capture before and after
|
|
15
|
+
merkleokf "$PWD/<topic>" # descend where a hash moved
|
|
16
|
+
merkleokf "$PWD/<topic>/<page>.md" # a single file
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- **Always name the wiki as `"$PWD"`**. You are already in the wiki root; the
|
|
20
|
+
CLI's own default (`okf/`) is for host-side use and would select an invalid
|
|
21
|
+
nested `okf/` directory here. The absolute path also preserves the root name
|
|
22
|
+
that `--nolog` relies on.
|
|
23
|
+
- Pass a directory or a single Markdown file.
|
|
24
|
+
- `-L`/`--level N` lists entries at most `N` directory levels deep (`N` ≥ 0;
|
|
25
|
+
`0` = walk root only; ignored for a file). Default: unlimited. Digests always
|
|
26
|
+
cover the full subtree.
|
|
27
|
+
- `--nolog` omits the wiki's root `log.md` from the listing and digests
|
|
28
|
+
(nested `log.md` still hashed; ignored for a single file).
|
|
29
|
+
- Exit codes: `0` ok, `2` usage or runtime error.
|
|
30
|
+
|
|
31
|
+
## Workflow
|
|
32
|
+
|
|
33
|
+
Needs a **before** and an **after** listing — one post-write run alone cannot
|
|
34
|
+
localise anything.
|
|
35
|
+
|
|
36
|
+
1. Before writing: `merkleokf -L 1 "$PWD"` and keep the listing.
|
|
37
|
+
2. After writing: run it again and diff against the baseline.
|
|
38
|
+
3. Descend only into folders whose hash moved; folders whose hash is unchanged
|
|
39
|
+
are provably untouched.
|
|
40
|
+
|
|
41
|
+
## Reading the output
|
|
42
|
+
|
|
43
|
+
A table (no summary line). The walk root is always the first path listed when
|
|
44
|
+
sorted alphabetically:
|
|
45
|
+
|
|
46
|
+
| Column | Meaning |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `Hash` | First 12 hex characters of the SHA-256 digest (Merkle digest for folders). |
|
|
49
|
+
| `Files` | Markdown files covered. Always `1` for a file. |
|
|
50
|
+
| `Path` | Rooted at the hashed directory's name (e.g. `okf/…`). Folders end in `/`. |
|
|
51
|
+
|
|
52
|
+
Rows are sorted alphabetically so two runs diff line by line.
|
|
53
|
+
|
|
54
|
+
## Limits
|
|
55
|
+
|
|
56
|
+
- Hashes **raw bytes — frontmatter included**. A timestamp or tag edit counts as
|
|
57
|
+
a change (unlike `sizeokf`).
|
|
58
|
+
- A moved hash proves *that* something changed, never that the change is
|
|
59
|
+
correct. Lint still gates conformance.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: size-okf
|
|
3
|
+
description: Measure how much Markdown prose a page or category holds, excluding YAML frontmatter. Use when judging whether a page is thin or a category is unbalanced.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Measure wiki content with `sizeokf`
|
|
7
|
+
|
|
8
|
+
Use the `sizeokf` CLI (on `PATH`) to count content words in the wiki. The
|
|
9
|
+
skill name is `size-okf`; the binary is `sizeokf` — never shell the skill id.
|
|
10
|
+
|
|
11
|
+
## Invocation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
sizeokf -L 1 "$PWD" # words per category — start here
|
|
15
|
+
sizeokf "$PWD/<topic>" # then per page within one category
|
|
16
|
+
sizeokf "$PWD" # every file and folder (large)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- **Always name the wiki as `"$PWD"`**. You are already in the wiki root; the
|
|
20
|
+
CLI's own default (`okf/`) is for host-side use and would select an invalid
|
|
21
|
+
nested `okf/` directory here. The absolute path also preserves the root name
|
|
22
|
+
that `--nolog` relies on. Any existing directory works.
|
|
23
|
+
- `-L`/`--level N` lists entries at most `N` directory levels deep (`N` ≥ 0;
|
|
24
|
+
`0` = walk root only). Default: unlimited. Folder **totals** are always
|
|
25
|
+
recursive.
|
|
26
|
+
- `--nolog` omits the wiki's root `log.md` from the listing and totals (nested
|
|
27
|
+
`log.md` still counted).
|
|
28
|
+
- Exit codes: `0` ok, `2` usage or runtime error.
|
|
29
|
+
|
|
30
|
+
## Workflow
|
|
31
|
+
|
|
32
|
+
Shallow first (`sizeokf -L 1`), then descend into the one category you care
|
|
33
|
+
about. Same `-L` pattern as `inspectokf`, different question: how much is
|
|
34
|
+
written, not what exists.
|
|
35
|
+
|
|
36
|
+
## Reading the output
|
|
37
|
+
|
|
38
|
+
A table (no summary line). The walk root is always listed:
|
|
39
|
+
|
|
40
|
+
| Column | Meaning |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `Words` | Whitespace-split words of Markdown **content**, frontmatter excluded. Recursive for folders. |
|
|
43
|
+
| `Files` | Markdown files counted. Always `1` for a file. |
|
|
44
|
+
| `Path` | Rooted at the measured directory's name (e.g. `okf/…`). Folders end in `/`. |
|
|
45
|
+
|
|
46
|
+
Rows are sorted largest first.
|
|
47
|
+
|
|
48
|
+
## Limits
|
|
49
|
+
|
|
50
|
+
- Counts **content only** — says nothing about frontmatter bulk.
|
|
51
|
+
- Never use it to reason about frontmatter itself; use `merkleokf` when asking
|
|
52
|
+
whether bytes changed.
|