Skip to content

Commit e41e6cf

Browse files
authored
Merge pull request #14 from nicholsn/feat/lokf-new-scaffold
Phase 3: lokf new — scaffold a publishable Astro knowledge base
2 parents 676061b + 0445102 commit e41e6cf

28 files changed

Lines changed: 1610 additions & 2 deletions

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,21 @@ lokf tables examples/acme-knowledge --format parquet --output build/tables
172172
lokf tables examples/acme-knowledge --format bigquery --location gs://bucket/lokf
173173
```
174174

175+
## Start your own
176+
177+
`lokf new my-kb` scaffolds a complete knowledge-base repo — a starter bundle, a
178+
full Astro site with concept pages and the interactive **graph browser**
179+
(`/graph`, plus `graph.jsonld`), a GitHub Pages workflow, a `justfile`, and the
180+
bundled agent skills — so you (or an AI agent) can go from an idea to a
181+
published, queryable knowledge graph:
182+
183+
```bash
184+
lokf new my-kb --title "My Knowledge Base"
185+
cd my-kb && just setup && just dev
186+
```
187+
188+
See the [scaffold docs](https://lokf.nolan-nichols.com/toolkit/scaffold/).
189+
175190
## Status
176191

177192
LOKF v0.1 is a **draft profile** and is **not affiliated with or endorsed by

pyproject.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ dev = [
6666
"lokf[tables]",
6767
]
6868

69+
[tool.hatch.build.targets.sdist]
70+
# The docs site (web/) — with its node_modules and Astro cache — isn't needed to
71+
# build or use the package, and would push the sdist past PyPI's size limit.
72+
exclude = [
73+
"/web",
74+
"/site",
75+
"**/node_modules",
76+
"**/.astro",
77+
"**/dist",
78+
]
79+
6980
[build-system]
7081
requires = ["hatchling"]
7182
build-backend = "hatchling.build"

src/lokf/cli.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""The ``lokf`` command-line interface (Typer).
22
3+
lokf new my-kb # scaffold a knowledge base
34
lokf convert path/to/concept.md --format ttl # markdown -> RDF
45
lokf query examples/acme-knowledge "SELECT ..." # SPARQL over a bundle
56
lokf serve examples/acme-knowledge # local SPARQL endpoint + viz
@@ -30,6 +31,36 @@ def _err(message: str) -> None:
3031
typer.echo(message, err=True)
3132

3233

34+
# ---------------------------------------------------------------------------
35+
# new
36+
# ---------------------------------------------------------------------------
37+
@app.command()
38+
def new(
39+
name: str = typer.Argument(..., help="Directory name for the new knowledge base."),
40+
path: Path = typer.Option(Path("."), "--path", help="Where to create it."),
41+
title: Optional[str] = typer.Option(
42+
None, "--title", help="Human-readable title (default: derived from name)."
43+
),
44+
base_iri: Optional[str] = typer.Option(
45+
None, "--base-iri",
46+
help="Bundle base IRI (default: https://example.org/<name>/).",
47+
),
48+
) -> None:
49+
"""Scaffold a knowledge-base repo: bundle + Astro site with the graph browser + agent skills."""
50+
from lokf import scaffold
51+
52+
try:
53+
root = scaffold.new(name, path=path, title=title, base_iri=base_iri)
54+
except FileExistsError as exc:
55+
_err(str(exc))
56+
raise typer.Exit(1)
57+
typer.echo(f"created {root}/")
58+
typer.echo(
59+
"next: cd in, `just setup && just dev` to preview (concept pages + /graph), "
60+
"edit knowledge/, then push and enable GitHub Pages (Source: GitHub Actions)."
61+
)
62+
63+
3364
# ---------------------------------------------------------------------------
3465
# convert
3566
# ---------------------------------------------------------------------------

src/lokf/scaffold.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""Scaffold a new LOKF knowledge-base repository.
2+
3+
`lokf new my-kb` writes a self-contained repo from the packaged template
4+
(``lokf/templates/kb``): a starter **bundle** (``knowledge/``), a full **Astro
5+
site** that renders the concepts and ships the interactive **graph browser**
6+
(``/graph``) plus the ``graph.json`` / ``graph.jsonld`` projections, a GitHub
7+
**Pages workflow**, a ``justfile`` that also drives the ``lokf`` toolkit via
8+
``uvx``, and the bundled **agent skills** so an AI agent can author the
9+
knowledge base from a prompt. In the spirit of linkml-cookiecutter: scaffold,
10+
then start authoring (or point Claude at it).
11+
"""
12+
from __future__ import annotations
13+
14+
import pathlib
15+
from importlib import resources
16+
from urllib.parse import urlsplit
17+
18+
from lokf import agentskills
19+
20+
#: Path segments renamed on copy — dotfiles are stored dot-free so packaging
21+
#: tools never skip them.
22+
RENAMES = {"_github": ".github", "_gitignore": ".gitignore"}
23+
24+
25+
def template_dir() -> pathlib.Path:
26+
"""Return the packaged knowledge-base template (``lokf/templates/kb``)."""
27+
return pathlib.Path(str(resources.files("lokf") / "templates" / "kb"))
28+
29+
30+
def _slug_title(name: str) -> str:
31+
return name.replace("-", " ").replace("_", " ").strip().title()
32+
33+
34+
def _tokens(name: str, title: str, base_iri: str) -> dict[str, str]:
35+
"""The substitution map applied to every template file.
36+
37+
``site``/``base`` are split out of the base IRI so the Astro site works
38+
both at a domain root (base "/") and as a GitHub *project* page
39+
(base "/<repo>").
40+
"""
41+
parts = urlsplit(base_iri)
42+
site = f"{parts.scheme}://{parts.netloc}"
43+
base = parts.path.rstrip("/") or "/"
44+
return {
45+
"__KB_NAME__": name,
46+
"__KB_TITLE__": title,
47+
"__KB_BASE_IRI__": base_iri,
48+
"__KB_SITE__": site,
49+
"__KB_BASE__": base,
50+
}
51+
52+
53+
def new(name: str, path: str | pathlib.Path = ".",
54+
title: str | None = None, base_iri: str | None = None) -> pathlib.Path:
55+
"""Create a new knowledge-base repo *name* under *path*; return its root.
56+
57+
Refuses to overwrite an existing non-empty directory.
58+
"""
59+
title = title or _slug_title(name)
60+
base_iri = base_iri or f"https://example.org/{name}/"
61+
if not base_iri.endswith("/"):
62+
base_iri += "/"
63+
64+
root = pathlib.Path(path) / name
65+
if root.exists() and (root.is_file() or any(root.iterdir())):
66+
raise FileExistsError(f"{root} already exists and is not empty")
67+
68+
tdir = template_dir()
69+
if not tdir.is_dir():
70+
raise RuntimeError(f"packaged knowledge-base template not found at {tdir}")
71+
tokens = _tokens(name, title, base_iri)
72+
for src in sorted(tdir.rglob("*")):
73+
if not src.is_file():
74+
continue
75+
rel = tuple(RENAMES.get(p, p) for p in src.relative_to(tdir).parts)
76+
dest = root.joinpath(*rel)
77+
dest.parent.mkdir(parents=True, exist_ok=True)
78+
text = src.read_text(encoding="utf-8")
79+
for token, value in tokens.items():
80+
text = text.replace(token, value)
81+
dest.write_text(text, encoding="utf-8")
82+
83+
# Drop the bundled agent skills in so an AI agent can author the KB.
84+
agentskills.install(root / ".claude" / "skills")
85+
return root
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
name: scaffold-knowledge-base
3+
description: Scaffold and publish a brand-new LOKF knowledge base from an idea — run `lokf new` to create the repo, author the concepts, validate, and publish to GitHub Pages. Use when someone describes a knowledge base they want to exist but there is no repo yet.
4+
---
5+
6+
# Scaffold a knowledge base from an idea
7+
8+
## Purpose
9+
10+
Go from a one-line idea ("a knowledge base of our data metrics", "my research
11+
group's methods and datasets") to a published LOKF site. This skill scaffolds
12+
the repository, then hands off to `author-concept` / `build-knowledge-base` /
13+
`enrich-relations` to fill it in.
14+
15+
## When to use
16+
17+
- There is no repo yet and someone wants a knowledge base to exist.
18+
- Turning a domain, product, team, or personal corpus into a published,
19+
queryable knowledge graph *and* website.
20+
21+
## Steps
22+
23+
1. **Scaffold the repo.** Pick a short kebab-case name and, if known, the URL it
24+
will publish to:
25+
26+
```bash
27+
lokf new my-kb --title "My Knowledge Base" --base-iri https://myorg.github.io/my-kb/
28+
```
29+
30+
This creates `my-kb/` with a starter bundle (`knowledge/`), a full Astro
31+
site (concept pages + the interactive `/graph` browser + `graph.jsonld`),
32+
a GitHub Pages workflow, a `justfile`, and these skills under
33+
`.claude/skills/`. Pass the real published URL as `--base-iri` when known —
34+
it configures both the concept IRIs and the site's base path.
35+
36+
2. **Understand the domain.** Ask the human what concepts matter and how they
37+
relate. Sketch the types (`Metric`, `Dataset`, `Table`, `GlossaryTerm`,
38+
`Service`, `Playbook`, `Document`, `Reference`, `Person`, `Organization`,
39+
`Role`) and the typed relations between them (`measures`, `dependsOn`,
40+
`derivedFrom`, `isPartOf`, `about`, `memberOf`, …).
41+
42+
3. **Author the concepts.** Delete the two examples under `knowledge/` and use
43+
the `author-concept` skill to write one file per concept, then
44+
`enrich-relations` to type the links. Register each concept in
45+
`knowledge/index.md`.
46+
47+
4. **Validate.** Project the bundle to RDF to confirm it parses and the typed
48+
relations bind to the right predicates:
49+
50+
```bash
51+
uvx --from lokf lokf convert knowledge --format ttl | tail
52+
```
53+
54+
Preview the website with `just setup && just dev` (concept pages + the
55+
`/graph` browser); explore with the toolkit via `just serve`; get the
56+
tabular projection with `just tables`.
57+
58+
5. **Publish.** Confirm `base_iri` in `knowledge/index.md` and `site`/`base` in
59+
`astro.config.mjs` match the real URL, run `just setup` once and commit
60+
`package-lock.json`, push to GitHub, and set **Settings → Pages → Source**
61+
to **GitHub Actions**. The `pages` workflow builds and deploys on every
62+
push to `main`.
63+
64+
## Done when
65+
66+
- `lokf convert knowledge --format ttl` emits the intended concepts and edges.
67+
- Every concept is listed in `knowledge/index.md`.
68+
- The repo is pushed and the Pages workflow has published the site.

src/lokf/templates/kb/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# __KB_TITLE__
2+
3+
A [LOKF](https://lokf.nolan-nichols.com) knowledge base. Every markdown file in
4+
`knowledge/` is one concept; together they are a queryable knowledge graph
5+
*and* this website — an [Astro](https://astro.build) site with concept pages
6+
and an interactive **graph browser** at `/graph`.
7+
8+
## Author
9+
10+
Add concept files under `knowledge/` (see the two examples). Each has YAML
11+
frontmatter (a `type` and fields) plus a markdown body; list new concepts in
12+
`knowledge/index.md`. Typed relations between concepts (`measures`,
13+
`dependsOn`, `isPartOf`, …) become edges in the graph and "Knowledge graph"
14+
panels on the pages.
15+
16+
Prefer an AI agent? The bundled skills in `.claude/skills/` drive the whole
17+
workflow — open this repo in [Claude Code](https://claude.com/claude-code) and
18+
ask it to build out the knowledge base from your idea.
19+
20+
## Work with it
21+
22+
```bash
23+
just setup # npm install (once; commit package-lock.json)
24+
just dev # live-preview the site + graph browser
25+
just site # build the static site into dist/
26+
27+
just serve # SPARQL endpoint + graph explorer (lokf toolkit, via uvx)
28+
just rdf # project the bundle to RDF / Turtle
29+
just tables # project the bundle to linked tables (CSV)
30+
```
31+
32+
The `lokf` recipes use [`uvx`](https://docs.astral.sh/uv/), so they only need
33+
`uv` installed — no `pip install`.
34+
35+
## Publish to GitHub Pages
36+
37+
1. Set `base_iri` in `knowledge/index.md` — and `site`/`base` in
38+
`astro.config.mjs` — to the URL you will publish at (already done if you
39+
passed `--base-iri` to `lokf new`).
40+
2. Push to GitHub; in **Settings → Pages**, set the source to **GitHub Actions**.
41+
3. The `pages` workflow builds the site and publishes it on every push to `main`.
42+
43+
## Responsible AI use
44+
45+
This knowledge base may be authored with AI assistance. You own everything you
46+
commit — understand it, verify it, and don't credit an AI as a co-author. See
47+
the [LOKF AI Covenant](https://github.com/nicholsn/lokf/blob/main/AI_COVENANT.md).
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: pages
2+
3+
# Builds the Astro site (which renders the knowledge/ bundle) and publishes it
4+
# to GitHub Pages. In the repo's Settings -> Pages, set Source to
5+
# "GitHub Actions". `npm install` (not ci) so the scaffold works before a
6+
# package-lock.json is committed; commit the lockfile for reproducible builds.
7+
on:
8+
push:
9+
branches: [main]
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
pages: write
15+
id-token: write
16+
17+
concurrency:
18+
group: pages
19+
cancel-in-progress: false
20+
21+
jobs:
22+
build:
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v5
26+
- uses: actions/setup-node@v6
27+
with:
28+
node-version: 24
29+
- run: npm install
30+
- run: npm run build
31+
- uses: actions/upload-pages-artifact@v5
32+
with:
33+
path: dist
34+
deploy:
35+
needs: build
36+
runs-on: ubuntu-latest
37+
environment:
38+
name: github-pages
39+
url: ${{ steps.deployment.outputs.page_url }}
40+
steps:
41+
- id: deployment
42+
uses: actions/deploy-pages@v5

src/lokf/templates/kb/_gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# build output
2+
dist/
3+
.astro/
4+
build/
5+
lokf-tables/
6+
7+
# dependencies
8+
node_modules/
9+
10+
# python / os
11+
__pycache__/
12+
*.pyc
13+
.venv/
14+
.DS_Store
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @ts-check
2+
import { defineConfig } from 'astro/config';
3+
import remarkStripLeadingTitle from './remark-strip-leading-title.mjs';
4+
5+
// `site` + `base` are derived from the bundle's base_iri, so the site works
6+
// both on a custom domain (base "/") and as a GitHub *project* page
7+
// (base "/<repo>"). Internal links use the href() helper in src/lib/lokf.ts,
8+
// which prefixes import.meta.env.BASE_URL. The remark plugin drops a concept
9+
// body's redundant leading `# Title` (the layout renders it from frontmatter).
10+
export default defineConfig({
11+
site: '__KB_SITE__',
12+
base: '__KB_BASE__',
13+
markdown: {
14+
remarkPlugins: [remarkStripLeadingTitle],
15+
},
16+
});

src/lokf/templates/kb/justfile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# List recipes
2+
default:
3+
@just --list
4+
5+
# Install the site's dependencies (once; commit package-lock.json)
6+
setup:
7+
npm install
8+
9+
# Live-preview the site (concept pages + the /graph browser)
10+
dev:
11+
npm run dev
12+
13+
# Build the static site (output: dist/)
14+
site:
15+
npm run build
16+
17+
# Interactive SPARQL endpoint + graph explorer over the bundle
18+
serve:
19+
uvx --from lokf lokf serve knowledge
20+
21+
# Project the bundle to RDF (Turtle) on stdout
22+
rdf:
23+
uvx --from lokf lokf convert knowledge --format ttl
24+
25+
# Project the bundle to linked tables (CSV under build/tables)
26+
tables:
27+
uvx --from 'lokf[tables]' lokf tables knowledge --format csv --output build/tables

0 commit comments

Comments
 (0)