v0.1.0
Part of the OpenKnowledge changelog.
Minor Changes
feat: CLI colorized output, boxed banner, and NO_COLOR support
- Add colorized CLI output via picocolors with semantic color helpers (error, warning, success, info, dim, accent)
- Render Vite-style boxed startup banner using cli-boxes
- Full NO_COLOR standard compliance: NO_COLOR env var, FORCE_COLOR env var, --no-color/--color CLI flags
- Clickable URLs in startup banner via OSC 8 hyperlinks (iTerm2, modern terminals)
- MCP stdout isolation preserved — diagnostics stay on stderr
Unify wiki → content config, mirrored catalogs
- Config:
wikisection replaced bycontentwithdir,include,excludecontent.dirdefaults to.(project root)content.include/excludeare glob patterns for tracked content files
- MCP tool:
init-wikirenamed toinit-content - Mirrored catalogs: INDEX.md catalogs generated inside
.open-knowledge/catalogs/instead of in-place next to source files
Unify wiki → content config, mirrored catalogs
- Config:
wikisection replaced bycontentwithdir,include,excludecontent.dirdefaults to.(project root)content.include/excludeare glob patterns for tracked content files
- MCP tool:
init-wikirenamed toinit-content - Mirrored catalogs: INDEX.md catalogs generated inside
.open-knowledge/catalogs/instead of in-place next to source files
- Config:
feat: exclude git-ignored files from document system
The file watcher now maintains a filtered in-memory file index, replacing the slow
readdirSyncin the documents API. Filtering uses a unifiedContentFilterthat combines.gitignorerules withconfig.content.excludepatterns. Thecontent.includeandcontent.excludeconfig fields are now wired end-to-end. Response time forGET /api/documentsdropped from ~35s to ~2-5ms.Finish the fullscreen graph surfaces by adding
OrphansandHubsviews insideGraphPanel, with a visible orphan-mode toggle forNo Incoming,No Outgoing, andBoth.The
get_orphansMCP tool and the backing server API now share the same three-mode orphan contract, so agents can query disconnected pages by graph lens instead of only the default fully-disconnected view.feat: add
get_historyandsave_versionMCP tools, fix IPv6 MCP connectivity- Add
get_historyMCP tool wrapping GET /api/history for querying document version history with filtering and pagination - Add
save_versionMCP tool wrapping POST /api/save-version for creating checkpoint commits - Update
rollback_to_versiondescription to referenceget_historyinstead of raw API endpoint - Fix MCP server discovery using
localhostinstead of127.0.0.1to support IPv6-only server bindings
- Add
Image upload + asset resolution: sibling-co-located storage, filter reinterpretation, shortest-path hybrid references, SVG support.
- Storage: Uploaded images land as siblings of the editing
.mdfile (not a flatuploads/dir). Multiple.mdfiles can reference the same image via relative paths. - Config:
content.uploadsDirremoved.content.include/content.excludeschema unchanged — interpretation extended so allowlisted asset extensions (png,jpg,jpeg,gif,webp,svg) in directories containing ≥1 included.mdfile are auto-included.exclude/gitignore continues to supersede. - Serving: Filter-aware
sirvmiddleware overcontentDir(both dev plugin and standalone CLI). Filter-excluded paths return 404.X-Content-Type-Options: nosniffpreserved. - References: Editor inserts bare filename for sibling uploads (
). NewshortestImageRef(assetPath, mdPath)helper returns bare filename when co-located, else root-relative-with-leading-slash. - SVG: Now accepted at upload — consistent with the storage-fidelity precedent. Rendered via
<img src>only; inline<svg>embedding remains unsupported in the editor. - Security: Upload endpoint requires
parentDocNameform field, normalizes it (rejects absolute paths,..segments, NUL), verifies destination isisWithinContentDir, and checksrealpathSyncon the destination directory to defeat symlink escape. Existing magic-bytes MIME check, 10 MB cap, atomicopenSync('wx')write, and numeric-suffix collision retry preserved. - Paste naming: Clipboard pastes without a meaningful filename synthesize
pasted-YYYYMMDD-HHMMSS.<ext>. - Supersedes: #41 (Sarah's original PR — every preserved contribution kept; three load-bearing decisions reworked per the spec).
- Storage: Uploaded images land as siblings of the editing
open-knowledge initnow appends a load-bearing "Open Knowledge" section to rootCLAUDE.mdandAGENTS.md(idempotent via<!-- open-knowledge:begin -->markers; dedups symlinked files viarealpath). The appended section nudges agents towardexec,write_document/edit_document, and[[wiki-links]]. Use--forceto overwrite the block in place.The
execMCP tool now auto-scopes recursivegrep -r/findinvocations with--exclude-dir=/-not -pathfor known non-wiki directories (node_modules,.git,dist,build,.next,.turbo,.nuxt,coverage,.cache,.parcel-cache,.vercel,.open-knowledge). Observed speedup on a real repo: ~210× (56.6s → 0.27s). User-provided--exclude-dir/-not/-prunedisables injection for that stage.feat(mcp): add managed document rename with backlink rewrite
Add the
rename_documentMCP tool and the backing managed rename server flow so page renames update inbound wiki-links plus supported internal inline Markdown links instead of leaving stale references behind.Managed rename now uses a persisted recovery journal for crash-safe rollback, updates already-loaded documents through the live Y.Doc path, and keeps sidebar file rename on the graph-safe endpoint while folder rename stays on the lower-level path rename flow.
feat:
open-knowledge initnow configures MCP for multiple editors- Interactive multi-select prompt asks which tools you use (Claude Code, Cursor, VS Code, Windsurf)
- Writes each editor's MCP config to its expected location and format
--editorflag for non-interactive use (e.g.--editor cursor,vscodeor--editor all)- Falls back to Claude Code only when stdin is not a TTY
Add semantic color bloom to the graph visualization. The
/api/link-graphendpoint now returns frontmatter metadata (cluster,category,tags) on doc nodes. Graph nodes are colored by cluster using a deterministic 16-color palette, with rich HTML tooltips showing metadata on hover and a cluster legend in fullscreen Explore mode.feat: add suggest_links discovery and precision patch targeting
- add a
suggest_linksMCP tool and/api/suggest-linksendpoint for deterministic missing-link discovery - add title-aware and alias-aware mixed live-or-disk scanning that skips already-linked and non-prose regions
- add optional offset-aware
edit_documentpatch targeting so follow-up edits can address an exact mention
- add a
feat: symlink-safe file sync
Symlinks inside the content directory are now fully supported. The file watcher indexes documents by canonical path (
realpath), deduplicating aliases that point to the same file into a single Y.Doc. Persistence writes target the canonical path so atomic rename never breaks symlink chains. Symlinks that escape the content directory are refused, cyclic symlinks are rejected, and broken symlinks fall back to direct writes. The/api/documentsendpoint surfaces alias metadata (isSymlink,canonicalDocName,targetPath), and the file sidebar renders a Link2 badge with a hover tooltip for symlinked entries.feat:
open-knowledge initcommand and MCP workflow tools- Add
open-knowledge initCLI subcommand to scaffold.open-knowledge/and register the MCP server in.mcp.json - Add three MCP workflow tools:
init-wiki,ingest, andresearchwith structured skill-style descriptions (Use when / Triggers on) - MCP server auto-generates INDEX.md catalogs via file watcher on
.open-knowledge/
- Add
feat: Zero-Ceremony Resume — dual-process lifecycle + MCP auto-spawn
Behavior changes operators should know about:
ok mcpauto-spawnsok startby default. Whenok mcpstarts with no liveserver.lock, it detach-spawns the current@inkeep/open-knowledgebinary withstartas a sibling process (re-exec viaprocess.execPath— notnpx, so the sibling is pinned to the same version the MCP client is running). Opt out via theOK_MCP_AUTOSTART=0env var ormcp.autoStart: falseconfig. A pre-existing live lock is always connected regardless of the opt-out (opt-out only suppresses the spawn path).server.portdefault changed from3000to0.ok startnow asks the kernel for a free port by default; the resolved port is written toserver.lockfor MCP discovery. To keep the old behavior, setserver.port: 3000in.open-knowledge/config.ymlor pass--port 3000.- New
ok uicommand. The React editor now runs in its own sibling process (default port 3000; respectsPORTenv /--port).ok startauto-spawns it whenui.lockis absent. AGET /api/configendpoint onok uibootstraps the React app's HocuspocusProvider with the live collab URL read fromserver.lock. - New utility commands:
ok status,ok stop,ok clean. ok initdefaults changed. Non-TTY invocations now write MCP config for every detected editor (Claude, Cursor, VS Code, Windsurf) — previously only Claude. TTY pre-selects all detected editors.--editor <all|claude|...>preserved..claude/launch.jsonscaffolding updated. Entry now launches@inkeep/open-knowledge ui(notstart) withautoPort: true. Existing entries from earlier versions are detected as stale and flagged with a WARN pointing atok init --force.
See
docs/content/internals/lifecycle.mdxanddocs/content/guides/mcp-integration.mdxfor the full lifecycle reference.Zero-config bunx packaging: chokidar as default file watcher with @parcel/watcher as optional native accelerator, React app assets bundled into dist/public/, auto-init on first start
Patch Changes
fix(bridge): close Bug-A (server-side
syncTextToFragmentdestroying concurrent client XmlFragment) and Bug-B (client Observer A's remote-tx baseline refresh absorbing local changes). Server-side agent writes now follow the XmlFragment-authoritative pattern (applyAgentMarkdownWritereplacessyncTextToFragment). Client Observer A uses conditional baseline refresh when a local debounce is pending. ExtractsapplyByPrefixSuffixto@inkeep/open-knowledge-corefor shared use. Hardens the bridge-testing harness (FR-11 invariant watcher, FR-12 origin probe, FR-15 Scheduler DI with clock unification, FR-16 network control, FR-17 multi-client convergence fuzzer with char-granular content oracle).Exclude
dist/**/*.mapfrom the published npm tarball. Source maps ship full TypeScript source viasourcesContent; dropping them from the tarball keeps maps available locally for debugging while the published package is ~46% smaller (3.9 MB → 2.1 MB, 1284 → 660 files).ci: Playwright E2E suite is now deterministic and debuggable on failure
- Event-coupled waits. Removed all 73
page.waitForTimeout(N)magic sleeps and the 1waitUntil: 'networkidle'from the E2E suite. Every wait now couples to a real signal (CRDT propagation, menu render, selection flush, debounce). CI contention no longer causes spurious failures from "200ms should be enough" gone wrong. - Failure observability. On CI, every test failure now uploads the
Playwright HTML report +
test-results/(trace, video, screenshot) with 14-day retention. Configure:retries: 2,failOnFlakyTests: true(retry-success still fails the PR),trace: 'on-first-retry',video: 'retain-on-failure'at 1280×720,screenshot: 'only-on- failure'. Developers canbunx playwright show-traceon the downloaded artifact instead of re-running locally to reproduce. - Named flake fixes. Resolved 4 named flakes from main CI:
sidebar-folder (under user investigation), QA-022 chunked-paste perf
(now baseline-relative —
max(2 × p50Baseline, 32ms)reading fromperf-baseline.json), crdt-stress S6 (root cause:/api/config404 was logged as critical-error by an over-strict filter), docs-open F11 (root cause:Promise.allof clicks didn't preserve array order — sequential awaits restore determinism). - PR #188 absorbed fixes. Cherry-picked from Andrew's PR #188:
Branch C wikiLink parseHTML priority-100,
wrapAsInlineCodemark handler with 9 unit tests, FR-19<pre>regex tightening (/<pre[\s>]/), FR-15 Source empty-selection preventDefault. - DEV-gating.
window.__agentFlashStatewrites wrapped inif (import.meta.env.DEV)so production bundles tree-shake the test hook. STOP rule prevents future ungatedwindow.__*assignments outside the documented allowlist. - STOP rule enforcement. New mechanical test
(
tests/integration/e2e-stop-rules.test.ts) fails CI on any reappearance ofpage.waitForTimeout,waitUntil: 'networkidle', busy-waitPromise+setTimeout,page.pause, webkit-skip ratchet, inner-helper-import (must use barrel), or ungatedwindow.__write. Zero allowlist; per-pattern failure messages listfile:line. - Architectural precedent #20 added to
AGENTS.mddocumenting the E2E test-infra conventions for future contributors.
User-facing impact: faster CI feedback on real regressions, no more "flake or real?" guessing, debuggable failures from CI artifacts alone.
- Event-coupled waits. Removed all 73
fix: file sidebar reveals the active file on navigation
When the active document changes from any entry point (graph click, direct URL, wikilink, rename, browser back/forward), the file sidebar now expands ancestor folders and scrolls the active row into view. Expansion is recomputed per render as
(ancestors ∪ userExpanded) \ userCollapsed, so a user's manual collapse of the active file's folder sticks until they navigate elsewhere. Addsaria-current="page"on the active row and roving tabindex for keyboard access; no focus steal.Improve editor hitbox focus by making TiptapEditor and SourceEditor fill the full height of their containers, so clicking anywhere in the editor area activates focus.
Markdown pipeline engineering health — 21 P0 requirements landing across perf measurement, code refactors, fidelity fixes, test tightening, and CI infrastructure.
Perf measurement: seeded synthetic benchmark corpus + committed harness with pinned methodology (10 warm-ups,
Bun.gc(true),bun@1.3.11); re-measured baseline at 7 block counts; per-stage profile harness + published findings; calibrated perf regression gate (max(2× p99 variance, 10% floor)) + parse-health gate (parseFallback.wholeDoc === 0) in tier-2 CI.Code refactors: R23 guard
O(n·m) → O(n log n)via pre-indexed tag-offset map + binary search (568.88ms → 4.76ms on pathological corpus); processor caching atMarkdownManagerconstruction + idempotency refactor for bothremarkMdxAgnosticandremarkWikiLinkattachers; 2-phase merged post-parse walker (Phase A restoration + Phase B merged dispatcher) gated by one-time byte-for-byte mdast diff validator on 714 fixtures; structural PM↔mdast fix —hydrateMarksoutside-in greedy (library patch),Codemarkexcludes: '_'widened viaCodeMarkFidelity(schema widening per precedent #9), context-aware backslash-before-entity policy.Fidelity: all 6 CommonMark serialization bugs fixed. CommonMark corpus 652/652 idempotent;
KNOWN_CRASH_CEILINGlowered from 50 to 0; all 19 formerly-NORMALIZE sections promoted to byte-identity idempotence assertion.Test tightening: NG1 + NG11 byte-identity pinning; I3's
markdownDocarbitrary parametric blank-line joiner; 6 new PBT invariants (emphasis-cumulation, backslash-idempotence, list-nesting, html-block-edge, link-edge, image-edge) green at 1K samples;parseWithFallbackperf bound (≤5× happy-path) + parametricMAX_SPLIT_DEPTHboundary test.Infrastructure: all markdown fixtures consolidated into
packages/core/src/markdown/fixtures/{commonmark,gfm,mdx,wiki-links,frontmatter,ng-pinned,perf}/with typed loader helpers; all 7 stale@tiptap/markdownreferences removed; three CI tiers (ci.yml/nightly.yml/weekly.yml) calibrated against measured baselines.feat: indicate when an editor doc does not yet exist on disk
- EditorHeader shows a "New file" badge next to the filename when navigating to a non-existent document; disappears after the file is created
- WYSIWYG mode shows contextual placeholder text: "Start writing to create this page…" for new docs, "Start writing…" for empty existing docs
- Source (Markdown) mode shows the same contextual placeholder text via a CodeMirror Compartment
fix(observers): preserve CRDT Item identity through Observer A bridge cycles
Observer A (Y.XmlFragment → Y.Text) now preserves CRDT Items whose content at their position already matches what the sync would write, fixing origin-laundering that broke
Y.UndoManager({ trackedOrigins })consumers — Items written under'agent-write'origin no longer get replaced by Items under'sync-from-tree'origin.Two-path implementation:
- Path A (Y.Text in sync with baseline):
applyIncrementalDiffadds a content-comparison gate before each adjacent REMOVED+ADDED hunk; if Y.Text already has the added value at that offset, bothdeleteandinsertare skipped — preserving CRDT Item identity for any unchanged region. - Path B (Y.Text diverged from baseline):
applyUserDeltais rewritten to use DMPpatch_make+patch_apply(canonical three-way merge) so same-line concurrent edits (user WYSIWYG + agent API write) merge correctly, preserving Item-equal prefix/suffix regions viaapplyByPrefixSuffix. - New optional
ObserverDeps.onMergeFailedcallback +console.warndiagnostic when DMPpatch_applyreports failed patches.
Server-side cleanup: removed the two dead
Y.Map('conflicts')write stanzas instandalone.ts(zero consumers; reconciliation logic,incrementConflict(), and the{ kind: 'conflicts' }return type all preserved).Adds
AGENTS.mdprecedent #9 documenting the three unclaimed bridge-quality patterns and introduces a third invariant (Item-preservation) to the CRDT Bridge Architecture section.Internal change — no public API surface changes.
- Path A (Y.Text in sync with baseline):
Initial publish
Add a dead-link audit surface to the server API and expose it through the MCP tool surface.
fix: eliminate silent data loss on graceful shutdown
createServer().destroy()had two compounding bugs that could silently drop up to 10 seconds of user typing on every Ctrl+C / SIGTERM:hocuspocus.flushPendingStores()is fire-and-forget (voidreturn) — awaiting it awaited nothing- The L2 git-commit flush ran before L1 markdown drain, so it drained an empty queue
The fix adds a
flushAllStoresAndWait()helper that installs a one-shotafterUnloadDocumentextension hook (the same pattern@hocuspocus/server's ownServer.destroy()uses internally), reorders destroy phases correctly (watchers → sessions → L1 drain → L2 git → shadow repo release), and adds a cached-Promise idempotency guard so concurrent shutdown signals (e.g., SIGINT + SIGTERM) share a single teardown. A configurabledestroyTimeoutMs(default 10s) bounds the flush to prevent hangs from misbehavingonStoreDocumenthooks. Structured shutdown logs are emitted on every exit. If the L1 flush hits its timeout ceiling, each still-loaded document's in-memory Y.Doc is dumped to<shadow-gitDir>/rescue/<docName>.md(best-effort per document) so the user can recover edits via the existingGET /api/rescueandGET /api/rescue/:docNameendpoints, even whenonStoreDocumentitself is hung.