pi-okf-wiki
Pi extension that implements the Open Knowledge Format (OKF) as a local knowledge base with /wiki-update and /wiki-query commands.
Package details
Install pi-okf-wiki from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-okf-wiki- Package
pi-okf-wiki- Version
0.2.0- Published
- Jul 21, 2026
- Downloads
- 51/mo · 34/wk
- Author
- panzenbaby
- License
- MIT
- Types
- extension
- Size
- 162.8 KB
- Dependencies
- 5 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-okf-wiki
A Pi extension that turns any project into a local, agent-readable knowledge base using the Open Knowledge Format (OKF).
Drop documents into input/, run /wiki-update, and they become OKF concept
files in wiki/. Then ask /wiki-query <question> and get an answer cited to
the concepts that back it.
Commands
| Command | What it does |
|---|---|
/wiki-update |
Ingest new documents from input/ into the wiki/ bundle, archive the originals, regenerate index.md / log.md, and show a summary. |
/wiki-query <question> |
Answer a question against the wiki, with every claim cited to a wiki/<concept-id>.md source. |
Folder layout
The extension operates on three folders at the project root (ctx.cwd):
.
├── input/ # drop new material here
├── archive/ # originals land here after their concept exists in wiki/
└── wiki/ # the OKF knowledge bundle (concepts + index.md + log.md)
Missing folders are created on the first /wiki-update.
Installation
From npm
pi install npm:pi-okf-wiki
From git
pi install git:github.com/Panzenbaby/pi-okf-wiki
# or, for a specific ref:
pi install git:github.com/Panzenbaby/pi-okf-wiki@v0.1.0
Local / development
Clone the repo, then either load it for a single run:
pi -e /path/to/pi-okf-wiki
…or add it to your project's .pi/settings.json so it auto-loads:
{
"packages": ["./pi-okf-wiki"]
}
After installing, (re)start Pi in your project and the two commands are
available. Reload after upgrading with /reload.
How /wiki-update works
/wiki-update classifies every file in input/ into one of three buckets:
- Conformant — a
.mdfile with parseable YAML frontmatter and a non-emptytypefield. Taken over deterministically (no LLM): the file is written towiki/<relative-path>and only then moved frominput/toarchive/. Its concept ID is the path without.md. - Non-conformant — everything else worth reading: a
.mdlacking frontmatter or a non-emptytypefield; plain text (.txt,.csv,.json) and images (.png,.jpg,.jpeg,.gif,.webp,.bmp) read directly by Pi'sreadtool; and binary/structured documents (.pdf,.docx,.pptx,.xlsx,.odt,.html/.htm,.epub,.rtf) which the extension pre-extracts to plain text into a tempinput/.okf-extract/<relative-dir>/<stem>-extracted.txtand hands that to the agent (see Supported formats). These are handed to the agent, which reads every non-conformant file, clusters those describing the same real-world entity (matched on asserted name / resource / keywords, not on filename), and writes one OKF concept per cluster (frontmatter + structured body, cross-links, citations) towiki/before moving each original toarchive/. The existing wiki structure — directories, types in use, and the full list of existing concept IDs — is passed to the agent so new concepts fit in and duplicate IDs are avoided. This bucket (and every/wiki-query) invokes an agent turn, i.e. uses the LLM; conformant files are ingested deterministically with no LLM call. - Ignored — unsupported file types, reserved filenames
index.md/log.mdplaced ininput/, and documents whose extraction failed (encrypted, no extractable text, or a lib error). Listed in the summary with a stable reason code (unsupported,reserved,encrypted,extraction_failed,empty,io_failed) and left ininput/.
After the agent turn, the extension regenerates index.md, appends a dated
entry to log.md, rewrites /archive/<input-relative-path> placeholder
citation links in the agent-written concepts to the actual (collision-renamed)
archive paths (so a UI can jump straight to the archived original even when it
was renamed during the move), detects any files still left in input/ (the
agent did not finish them), and renders a summary widget:
OKF /wiki-update summary
Conformant imported: 1
Agent-transformed: 3 (of 4)
Ignored: 1
Leftover (failed): 1
Concepts created: 3
Concepts updated: 0
Wiki size: 5 -> 9
Imported concept IDs:
+ tables/orders
Created by agent:
+ playbooks/incident
Ignored:
- notes/old.doc (unsupported file type)
Leftover in input/ (agent did not finish):
! reports/q3.md
Supported formats
| Bucket | Extensions | How they reach the agent |
|---|---|---|
| Conformant (deterministic) | .md with frontmatter type |
Copied to wiki/ directly, no LLM. |
| Plain text (read directly) | .txt, .csv, .json |
Pi's read tool. |
| Images (vision) | .png, .jpg, .jpeg, .gif, .webp, .bmp |
Pi's read tool. |
| Extracted to text | .pdf, .docx, .pptx, .xlsx, .odt, .html/.htm, .epub, .rtf |
Pre-extracted to input/.okf-extract/<rel-dir>/<stem>-extracted.txt; the agent reads that. The original is archived; a copy of the extracted text is archived next to it. |
Extraction libraries (runtime dependencies of the extension):
| Format | Library |
|---|---|
.pdf |
unpdf |
.docx |
mammoth |
.xlsx |
exceljs (rendered as markdown tables) |
.pptx / .odt / .epub |
jszip + an XML-text stripper |
.html |
html-to-text |
.rtf |
dependency-free RTF stripper (control words + destination groups) |
Extraction failures are reported with a stable code and never archive the
original: encrypted (password-protected), empty (no extractable text),
extraction_failed (library error), io_failed (read/write error).
Unsupported types and reserved filenames are unsupported / reserved.
Safety invariant
An original is moved to archive/ only after its content exists as a
concept in wiki/ — per file. If a run is interrupted, the file stays in
input/ and is reprocessed on the next /wiki-update. Archive destinations
are collision-free: if archive/<rel> is already taken, the new copy is
timestamped (orders.2026-07-12-1305.md, then …-1, …-2 within the same
minute) and existing archive files are never overwritten. This keeps every
version of an original while making /wiki-update idempotent and abort-safe.
Finalization (snapshot diff, index.md/log.md regeneration, leftover
detection, summary) runs in Pi's agent_end event, so it always sees the wiki
state after the agent finished writing — not a racy pre-turn snapshot.
Conflict handling & versioning
When several inputs (or a new input and an existing concept) describe the same entity but disagree on a value, the agent does not silently pick one. Instead it records the disagreement inside the single concept body:
- A
# Conflicts(or# Versions) table — one row per source with the differing attribute, its value, citation, and timestamp. - Each source cited under a
# Citationsheading (numbered[1][2]). A citation to an archived original is a markdown link of the form[label](/archive/<input-relative-path>)using the ORIGINAL input relative path; the extension rewrites that placeholder to the actual (collision-renamed) archive path after the originals are moved, so the link stays resolvable when a file was renamed on archiving. Only the concept body is rewritten — theresource:frontmatter field stays a canonical URI (never an archive path). - A canonical value chosen by temporal precedence: the value with the
latest
timestamp/ “latest” marker wins and is stated in thedescriptionand# Schema; older values are marked superseded. If no source is clearly newer, all conflicting values stay in the table labelled unverified and no canonical value is declared.
How /wiki-query works
- Load all concepts from
wiki/. - TF-IDF cosine retrieval picks the top 10 matches for the question; the
root
index.mdand the full wiki tree are always included as context. - The agent answers in the same language as the question, citing each claim
with an inline
[title](wiki/<concept-id>.md)link and a# Sourcessection at the end. It may useread/grepto explore the wiki further. - The retrieved concepts,
index.md, and the wiki tree are injected into the system prompt via abefore_agent_starthook, so the user message stays clean — just the question itself. This keeps the session readable and the question persistable. The agent is also told to open the whole concept body before answering (not just the retrieved snippet), to follow# Related Concepts/# Versionslinks, and to surface any conflicts it finds while exploring.
If wiki/ does not exist or has no concepts, /wiki-query tells you to run
/wiki-update first instead of inventing an answer.
OKF conformance
Concepts are markdown files with YAML frontmatter; type is the only required
field. Recommended fields: title, description, resource, tags,
timestamp. index.md and log.md are reserved filenames maintained by
/wiki-update. See the
OKF spec
for the full format.
The bundle declares its target spec version via okf_version: "0.1" in the
root index.md frontmatter (§11 — the only index.md permitted to carry
frontmatter). Per OKF §6, /wiki-update writes one index.md per qualifying
directory (root + every directory that contains a concept, directly or
transitively) for progressive disclosure; each lists only its own direct
concepts and immediate child subdirectories. Orphan index.md files in
directories that no longer contain any concept are pruned on the next
/wiki-update (best-effort).
wiki/archive/ is a producer-specific OKF extension, not part of the
spec's references/ model: it holds the raw original files (PDFs, DOCX, …)
that back the concepts, so citation links to them stay bundle-relative
(/archive/<rel>) — one of the three §8-sanctioned citation link forms. It is
tolerated by the spec because consumers ignore non-.md files (§9); archived
.md originals carry an outermost .orig suffix so they never count as
concept documents (§3.1 / §9.1). archive/ is never listed in any index.md.
Example concept (wiki/tables/orders.md):
---
type: BigQuery Table
title: Orders
description: One row per completed order.
tags: [sales, orders]
timestamp: 2026-07-03T00:00:00Z
---
# Schema
| Column | Type | Description |
|---------------|--------|-----------------------------------|
| `order_id` | STRING | Unique order identifier. |
| `customer_id` | STRING | FK to [customers](/tables/customers.md). |
Part of the [sales dataset](/datasets/sales.md).
Link styles: inside concept files, links are bundle-relative (
/tables/orders.md— absolute, relative to thewiki/bundle, the §5.1 recommended form). Inindex.mdfiles, links are relative to that directory (orders.mdfromtables/, ortables/orders.mdfrom the root) per the §6 example. These two styles mirror the spec's own conventions for concepts vs. index files, not two competing rules. In/wiki-queryanswers, links are repo-relative (wiki/tables/orders.md) because the answer renders outside the bundle, from the project root — a third rendering context.
Configuration
There is no configuration file yet. The extension always reads from
input/, archive/, and wiki/ relative to ctx.cwd. /wiki-query
answers and /wiki-update summaries follow the language of the question /
transformed content automatically; edit src/prompts.ts to change that behavior.
Architecture
A TypeScript extension, strictly typed with no any. Filesystem, wiki, and
extraction operations return a Result<T> (success/error) and never throw to
callers. Per the project's AGENTS.md, document extraction follows the
Repository pattern: each format family has a DocumentExtractorRepository
that wraps a third-party library, converts the library's native Dto to the
ExtractedText AppModel, and returns Result<ExtractedText>. The Dto never
leaks outside its repository.
| File | Responsibility |
|---|---|
src/index.ts |
Registers the /wiki-update and /wiki-query commands; owns the agent_end finalize hook and the before_agent_start query-context hook (both via the session registries). |
src/types.ts |
Result<T>, AppError, OKF domain models, and the IgnoreReason code union. |
src/session.ts |
Session interface and the generic SessionRegistry<T> that owns the single-slot handoff between a command handler and an event hook. |
src/frontmatter.ts |
Minimal YAML frontmatter parser for the OKF subset. |
src/files.ts |
Filesystem helpers, all returning Result<T> (incl. copyFile, removeDir). |
src/wiki.ts |
Barrel re-exporting the wiki/ modules so the ./wiki.ts import surface stays stable for update.ts, query.ts, classifier.ts, and prompts.ts. |
src/wiki/paths.ts |
wikiPaths, conceptIdFromRelativePath, isConceptFile, relativePosix, WikiPaths. |
src/wiki/concepts.ts |
Concept loading (loadConcept, loadAllConcepts), snapshot/diff (snapshotWiki, diffSnapshots, WikiDiff). |
src/wiki/index-log.ts |
index.md / log.md generation (generateIndexMd, writeIndexMd, appendLogMd, buildLogEntry). |
src/wiki/retrieval.ts |
Structure preview and TF-IDF cosine retrieval. The Retriever interface is the seam injected into /wiki-query; TermFrequencyRetriever is the default implementation (it replaces the former retrieveConcepts free function, which is kept as a thin wrapper). IDF is computed on the fly from the loaded concepts so common terms are downweighted in any language — no hardcoded stopword list. Also exports: tokenize, renderConceptForPrompt, renderWikiTree, displayTitle, buildStructurePreview. |
src/prompts.ts |
Agent prompt builders for ingestion and query. |
src/links.ts |
Pure compileArchiveRewriter / rewriteArchiveCitationLinks — rewrites /archive/<input-relative-path> placeholder citation links in a concept BODY (frontmatter untouched) to the actual (post-rename) archive path. |
src/update.ts |
/wiki-update command logic and the IntakeSession (finalize) that owns the agent-handoff state, including the post-agent citation-link rewrite (rewriteArchiveCitationsInConcepts). |
src/classifier.ts |
InputClassifier that owns the full input→bucket pipeline AND the deterministic conformant intake: tentative dispatch by extension, the extraction pass (staging extracted text), and pass 3 — read + verify frontmatter + write to wiki/ + archive original — for conformant .md files. Emits the three final buckets (conformantImported / forAgent / ignored) once, in input order. |
src/query.ts |
/wiki-query command logic and the QuerySession that owns the pending question. Both buildWikiQueryContext and runQuery take an optional Retriever (default TermFrequencyRetriever) so the scoring strategy is injectable. |
src/extract/types.ts |
ExtractedText AppModel, DocumentExtractorRepository interface, extraction-failure cause codes. |
src/extract/pdf.ts |
PdfRepository (unpdf). |
src/extract/docx.ts |
DocxRepository (mammoth). |
src/extract/sheet.ts |
SheetRepository (exceljs), rendering worksheets as markdown tables. |
src/extract/office-xml.ts |
PptxRepository, OdtRepository, EpubRepository (shared jszip + XML helpers, EPUB spine-order). |
src/extract/html.ts |
HtmlRepository (html-to-text). |
src/extract/rtf.ts |
RtfRepository (dependency-free RTF stripper). |
src/extract/registry.ts |
Format taxonomy + ExtractorRegistry dispatch. |
src/extract/service.ts |
Extraction-to-temp-file orchestration and the .okf-extract/ lifecycle. |
src/extract/util.ts |
Shared Result<T> failure + error-message helpers for repositories. |
Development
npm install # installs peer/dev deps for type-checking
npm run check # tsc --noEmit (strict, noUnusedLocals)
The extension imports @earendil-works/pi-coding-agent only as type-only
imports (erased at runtime), so it has no bundled runtime dependencies.
License
MIT — see LICENSE.