Skip to content

okf: generalize into a reusable OKF knowledge-bundle CLI - #27

Merged
kriswill merged 13 commits into
mainfrom
okf-generalization
Jul 5, 2026
Merged

okf: generalize into a reusable OKF knowledge-bundle CLI#27
kriswill merged 13 commits into
mainfrom
okf-generalization

Conversation

@kriswill

@kriswill kriswill commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Turns okf (flakes/okf/) from a dotfiles-specific tool into a generic OKF knowledge-bundle CLI any project can adopt — any language or domain, no Nix required, git optional (or no version control at all). Finished extraction-ready in-tree: splitting okf to its own repository remains a one-line input-URL swap.

Eight steps, one commit each (plus the merge-date fix), every step leaving the repo fully green:

  1. okf.toml (renamed from okf-viz.toml) — one strict loader (config-cli.ts) for all commands; bundle.dir honored everywhere (validate/index/scaffold previously hardcoded knowledge/). Pages CI trigger updated in the same commit.
  2. [profile] — validation policy (required/recommended fields, reserved files, rooted/repo link rules) moves from code to config; defaults reproduce prior behavior exactly.
  3. VcsProvider adapters (vcs/) — git's batched one-spawn implementations moved verbatim behind an interface; lib.ts is now pure text helpers. Forge-agnostic revision links via [vcs] commit-url-template ({url}/commit/{hash}; GitLab /-/commit/); remote detection accepts any https/scp/ssh origin.
  4. none provider + config-first discovery — nearest okf.toml at/above cwd defines the workspace (monorepo sub-bundles work), else the git toplevel; the filesystem provider (fs walk + ignore globs + mtime) runs with git absent from PATH. auto picks git only at a git toplevel; explicit git on a nested root fails loudly instead of silently mtime-dating.
  5. Facet classify providers — the Nix optionalAttrs parser stays built-in; new provider = "command" runs any argv printing a JSON name→value map (failures fail the build). Legacy nix-packages spelling still normalizes.
  6. Scaffold hooks — the 566-line dotfiles scaffolder moved out of the flake to scripts/okf-scaffold.ts, driven by an injected ScaffoldContext API (type-only okf imports — vendored/cloned/store okf all work); plus declarative [[scaffold.collect]] glob+template entries and a non-JS command escape hatch.
  7. Config-aware help + okf init — help derives paths from the workspace config via a never-throwing loader; init bootstraps a starter okf.toml + bundle skeleton, never overwrites.
  8. Extraction sweep — flake source free of dotfiles assumptions, test fixtures neutralized, legacy config fallback removed, generic README with a no-Nix adoption path.

Also includes the --diff-merges=c fix: files introduced by merge conflict resolution now get their real commit date, making scaffold --force fully deterministic (evil-merge fixture test added).

Decision records: okf-toml-unified-config, okf-vcs-provider, okf-facet-classify, okf-scaffold-hook, closing umbrella okf-generalization (all under knowledge/decisions/).

Verification

  • This repo's behavior is unchanged at every step: okf validate 0 errors/0 warnings throughout; okf scaffold = 0 written/100 skipped; viz commit links 33/42 and the platform facet map (5 entries) byte-stable; 286 bun tests + svelte-check clean; nix build/nix run store copy verified per step (incl. the git-less sandbox test check).
  • Scaffold move gold check: pre/post worktree scaffold --force output diffed byte-identical.
  • Acceptance — three-way second-repo smoke on a fresh non-Nix Python repo: (A) git provider with real commit timestamps → init/scaffold/index/validate/viz green; (B) .git deleted → identical on the none provider; (C) okf copied standalone + bun install → green with no Nix and no git anywhere.
  • Two scaffold --force runs now byte-identical (merge-date fix).

Note for merge: the Pages workflow deploys the public graph (kris.net/dotfiles) on merge to main — the trigger path moved from okf-viz.toml to okf.toml in this PR; worth watching the first run.

kriswill added 13 commits July 4, 2026 18:27
All four commands now consume a single strict loader (loadContext():
root + normalized config + bundle path) instead of viz privately reading
okf-viz.toml while validate/index/scaffold hardcoded knowledge/. Fixes
the latent validate.ts repo-link misresolution and index-gen's literal
'# knowledge' root heading under a non-default bundle.dir. Malformed
config now fails every command, matching viz's strict philosophy.

Step 1a of the okf generalization arc (okf.toml rename is 1b).
The file is no longer viz settings — it is the okf workspace config.
Legacy okf-viz.toml still loads with a deprecation warning (dropped when
the generalization arc completes). Pages CI trigger path updated in the
same commit so config edits keep redeploying the published graph.

Decision record: knowledge/decisions/okf-toml-unified-config.md (also
documents the loadContext() singleton chosen over the planned run(ctx)
dispatch and why). Step 1b of the okf generalization arc.

Verified: validate 156/0/0, scaffold 0-written, viz delta exactly the
new decision doc (146 nodes/493 edges/288 files, commit links 33/42
unchanged), 244 bun tests, store-built okf identical.
required-fields ('type' always enforced), recommended-fields,
reserved-files, rooted-links (error|allow), repo-links
(check|ignore|forbid) — previously the hardcoded RESERVED /
PROFILE_FIELDS constants in lib.ts and inline rules in validate.ts.
Defaults reproduce prior behavior exactly; this repo's okf.toml sets
nothing. splitCliSections() in config-cli.ts consumes CLI-only sections
before the viewer normalizer sees the raw TOML (which strict-rejects
unknown keys), keeping them out of the #data embed.

New pure tests: flakes/okf/test/config-cli.test.ts (11 cases).
Battery: validate 156/0/0, viz counts unchanged, 255 bun tests,
store-copy identical; scratch-bundle spot check confirms overrides
react (rooted-links=allow, recommended-fields=[], unknown-key failure).

Step 2 of the okf generalization arc.
All version-control access moves behind vcs/{types,git,index}.ts —
lib.ts is now pure text helpers (frontmatter/links/walk/slug/ANSI).
Git provider = the batched one-spawn implementations moved verbatim.

Forge-agnostic outbound links: [vcs] commit-url-template (default
'{url}/commit/{hash}'; [repo] stays as deprecated alias of [vcs]),
remote detection normalizes any https/scp/ssh origin, repoNameFromUrl
derives header names from any forge (subgroup chains kept whole). The
viewer fills {hash} from an embedded pre-substituted template and no
longer knows what GitHub is.

Decision record: knowledge/decisions/okf-vcs-provider.md.
Battery: validate 157/0/0, commit links 33/42 unchanged (the step's
oracle), 264 bun tests + svelte-check clean, store-built okf identical.

Step 3 of the okf generalization arc; the 'none' provider and [vcs]
provider selection land next.
okf now runs with no version control at all. [vcs] provider =
auto|git|none (auto = git only when the root is a git toplevel — the
batched git paths are toplevel-relative, and silent mtime fallback
would let a later scaffold --force rewrite every timestamp; explicit
git on a non-toplevel root fails loudly instead). The none provider
walks the filesystem (junk names + [vcs] ignore globs + the generated
viz output skipped), stamps mtime dates with git-matching
newest-under-prefix dir semantics, and has no citations/remote.

Workspace root is discovered config-first: nearest okf.toml at or
above cwd (resolves the config<->root circularity, enables monorepo
sub-bundles), else the git toplevel as before.

Verified: no-git scratch bundle validates + builds viz with git
removed from PATH; 275 bun tests incl. tempdir fixtures (git-dependent
cases skip in the git-less nix sandbox — checks.test green); full
battery on this repo unchanged (git auto-selected, commit links 33/42).

Step 4 of the okf generalization arc.
[facet.<n>.nix-packages] generalizes to a [facet.<n>.classify] tagged
union: provider 'nix-optional-attrs' (existing optionalAttrs parser,
still built-in — a Nix battery costs non-Nix users nothing) or
'command' (any argv run at the workspace root printing a JSON
name->value map; non-zero exit / bad JSON / out-of-values FAIL the viz
build). Shared keys: types + key = basename|id. Legacy nix-packages
spelling normalizes to the union (idempotent, tested); this repo's
okf.toml moved to the canonical spelling same commit.

Decision record: knowledge/decisions/okf-facet-classify.md.
Verified: platform facet map byte-identical to baseline (5 entries);
command provider smoke-tested (success/failure/out-of-range) in a
scratch bundle; viz boots to interactive in headless Chrome (--perf);
278 bun tests + svelte-check clean; store-built okf identical.

Step 5 of the okf generalization arc.
okf scaffold is now a generic driver. The 566-line dotfiles metadata
pass moved out of the flake to scripts/okf-scaffold.ts (mechanical
port), dynamically imported per okf.toml [scaffold] script with an
injected ScaffoldContext (flakes/okf/scaffold-api.ts: emit owning
idempotence/--force/counters, VCS timestamps, configurable-marker
comment extraction, text helpers). Scripts use type-only okf imports +
builtins, so vendored/cloned/store okf all work and the script survives
a future okf repo split. [scaffold] command is the non-JS escape hatch
(OKF_* env); [[scaffold.collect]] adds a declarative glob+template tier
(placeholders validated at config load) for simple repos.

Gold check: pre/post worktree scaffold --force diffed BYTE-IDENTICAL
modulo the pre-existing now()-fallback on 4 merge-added packages
(git log --name-only omits merges; tracked as a follow-up task).
Battery: 0-written/100-skipped unchanged; store-built okf runs the
repo script; collect tier smoke-tested in a scratch workspace
(comment extraction, templates, idempotence, validate-clean output);
285 bun tests + svelte-check clean.

Step 6 of the okf generalization arc.
Help text derives the bundle dir, viz output path, and the profile-doc
footer pointer from the workspace's okf.toml via quietBundleInfo() — a
never-throwing loader, so a broken config can't break 'okf help'. The
repo-specific docs footer (.claude skill path) is gone; OKF_PROG
mechanism unchanged (dev.nix untouched).

New 'okf init [--dir=<d>]': commented starter okf.toml + bundle
skeleton (<d>/index.md with okf_version, <d>/log.md) at cwd, which
becomes the workspace root. Never overwrites; re-run is a no-op.
Flag dispatcher now accepts declared '--flag=<value>' spellings.

Smoke: init -> validate 0/0 -> index -> viz all green in a fresh dir;
--dir=kb variant green; re-init no-op; battery on this repo unchanged
(159/0/0, 0-written, commit links 33/42); store-built okf help OK.

Step 7 of the okf generalization arc.
- flakes/okf source carries no dotfiles assumptions: header comments
  genericized, okf-viz.toml legacy fallback removed, package.nix
  homepage TODO'd for the eventual repo split (maintainer stays).
- Test fixtures neutralized (Darwin/NixOS Module -> Alpha/Beta Module,
  nvim->wiki, nebula->europa, kriswill/dotfiles -> acme/widgets, incl.
  URL-encoded hash forms); 'knowledge' remains only as okf's generic
  default. 285 tests green, git-less nix sandbox check green.
- README: generic tool doc with a no-Nix adoption section (copy/vendor
  + bun install + init).
- Acceptance: three-way second-repo smoke on a fresh non-Nix Python
  repo — (A) git provider with real commit timestamps, (B) .git
  deleted -> none provider, (C) okf copied standalone + bun install,
  no Nix and no git anywhere. init/scaffold/index/validate/viz green
  in all three.

Closing decision record: knowledge/decisions/okf-generalization.md.
This repo's behavior unchanged end to end (validate 160/0/0, scaffold
0-written, commit links 33/42, platform map 5 entries).

Step 8 of 8 — okf is now reusable by any project; the repo split
remains a one-line input-URL swap.
Files created during merge conflict resolution (in neither parent) never
appeared in the batched git log --name-only pass, so lastModified returned
null and scaffold --force stamped them with the current time. Combined
diff (-c) lists exactly what a merge changed vs all parents; clean merges
list nothing, so no other dates move. first-parent was rejected: it would
restamp entire PRs with their merge date.
# Conflicts:
#	knowledge/log.md
The --diff-merges=c fix landed (okf-vcs-provider.md); the closing arc
record no longer lists it as a remaining gap.
…fields, port stripping

Three confirmed bugs from the xhigh code review:
- scaffold collect tier: output templates expanded before {repo}/
  {description}/{description-sentence} existed, silently emitting
  filenames like 'widget-{description}.md' or 'widget-.md'. Fixed at
  the right altitude: OUTPUT_PLACEHOLDERS restricts output to the
  path-safe set ({path} {name} {Title} {dir} {timestamp}) at config
  load, with a message naming the allowed set ({repo} is derived FROM
  the output path — circular by construction).
- validate: '!v || v === ""' never flags an empty array ([] is
  truthy), so a user-configured array-typed required field passed
  empty. Now: empty covers "" and [].
- repoNameFromUrl: explicit ports leaked into the display name
  ('8080/team/repo') for manual [vcs] url overrides, which never pass
  through normalizeRemoteUrl's port stripping. Regex now drops ports.

Also from the review: git-missing vs not-a-repo error messages
distinguished again (Bun.which hint in loadContext + createProvider);
[vcs] non-table self-validated like [profile]/[scaffold]; isObj +
fieldIn deduped (exported from viz-app/config.ts, one copy);
PLACEHOLDER_RE shared between validation and expansion (can't drift);
none-provider directory dates memoized; dead exports trimmed
(findConfigUp un-exported + names param dropped, ConceptDoc and
FacetClassify un-exported); ctxCache singleton invariant documented.

3 new regression tests (289 pass); svelte-check clean; all three bug
repros re-run and confirmed fixed; battery unchanged (validate
160/0/0, scaffold 0-written, viz 33/42 commit links); store-built okf
identical.
@kriswill
kriswill merged commit d749014 into main Jul 5, 2026
@kriswill
kriswill deleted the okf-generalization branch July 5, 2026 03:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant