v0.8.0
Part of the OpenKnowledge changelog.
Minor Changes
CLI parity for skill reclaim + symmetric
OK_RECLAIM_DISABLEkill switch.ok startnow refreshes bundledSKILL.mdfiles on every boot, matching the Desktop's launch-time + project-open reclaim posture. Two sweeps:- Project-local — for every editor with a
projectSkillPath(Claude Code, Cursor, Codex), if<projectDir>/.<host>/skills/open-knowledge/SKILL.mdalready exists, the directory is refreshed from the bundledprojectskill. No-create — greenfield projects are untouched (mirrors the namespace-ownership rule the Desktop's project-mcp-reclaim already uses). - User-global — version-gated against
~/.ok/skill-state.yml'scli-hostsentry. When the recorded version doesn't match the bundled CLI version, the central store at~/.agents/skills/open-knowledge-discovery/plus per-host copies under~/.{claude,cursor}/skills/are force-rewritten and the recorded version advances.
Also exposed as a standalone
ok repair-skillssubcommand for explicit invocation without booting a server.OK_RECLAIM_DISABLE=1now short-circuits the new skill sweep AND the existingrepairMcpConfigs+repairLaunchJsonsweeps — three-way symmetry with Desktop's reclaim kill switch.Closes the clone-and-go gap where a teammate using only
@inkeep/open-knowledge(no Desktop install) never sawSKILL.mdfiles advance past whateverok initoriginally wrote.- Project-local — for every editor with a
feat(ok): cross-platform git preflight +
ok diagnose healthsubcommandPhase 1 of the git peer-dependency plan. Closes the "Error: spawn git ENOENT" failure surface that simple-git leaks when
gitis missing or unreachable from OK's process. Three surfaces share a single detection primitive (assertGitAvailable()):- CLI auto-notice.
bootServer()runs the preflight after config + logger init and before any simple-git work. On failure, the typed error is logged structured, install guidance is written to stderr, and the process exits 78 (EX_CONFIG, the stable scriptable signal). - Electron recoverable dialog. First launch shows a
dialog.showMessageBoxwith three buttons: Open Install Page / Retry / Quit. Retry re-probes synchronously; the loop is user-driven (no programmatic cap). ok diagnose healthsibling subcommand took diagnose process <pid>. Runs seven environment checks (git, Bun, config, content dir, server lock, shadow repo, macOS code-sig). Emits human-readable or NDJSON via--json; filter via--check <name>. Each check is timeout-bounded (5s default) and crash-contained — doctor never hangs or propagates check-internal errors.
The detection primitive uses a two-stage probe — current PATH first, then platform-specific fallback paths (
/opt/homebrew/bin/git,/Library/Developer/CommandLineTools/usr/bin/git,C:\Program Files\Git\cmd\git.exe, etc.) — to close the documented Cursor-class bug where GUI-launched Electron processes inherit a minimal launchctl PATH that doesn't see brew's bin. PATH enrichment at the Electron server-child spawn site keeps the common case on the fast path.Failure-only OTel telemetry (
ok.preflight.git.failwith bounded enum attributes) instruments missing-git incidence to inform a future Phase 2 (bundling) sizing decision.The minimum git version is currently
2.31.0(provisional). The real floor will land via thegit-version-matrix.ymltech-probe workflow included in this PR — an operator runs the workflow, reads the per-cell result matrix, and commits the empirical value. Documented as deferred work in spec D22 + an inline comment onMIN_GIT_VERSION.New published API on
@inkeep/open-knowledge-server:detectGit,assertGitAvailable,GitNotAvailableError,GitTooOldError,GitDetected,InstallGuidance,InstallOption,MIN_GIT_VERSION,compareSemver,emitPreflightFailureSpan,GIT_PREFLIGHT_FAIL_SPAN_NAME.Cross-platform CI coverage runs the preflight test surface on Linux + macOS. Windows is currently advisory while a separate upstream snapshot file rename is sorted out (see PR comment + deferred-scope list).
- CLI auto-notice.
MCP resilient chain — single shape across Desktop and CLI surfaces.
All managed MCP entries (Desktop + CLI, user-scope + project-scope) now write the byte-identical resilient chain:
{ "command": "/bin/sh", "args": [ "-l", "-c", "# ok-mcp-v1\nBUNDLE=…\n[ -f \"$BUNDLE\" ] && [ -x \"$BUNDLE\" ] && exec \"$BUNDLE\" mcp\n…" ] }The chain resolves to whichever runtime is locally available at MCP-host spawn time — user-local bundle (
~/Applications/...) → system bundle (/Applications/...) →npx(via login-shell PATH) → an explicit glob across nvm/fnm/asdf/brew/installer/local/volta. DMG-only users (no Node) hit the bundle branch; npm-installed CLI users hitnpx; teammates with neither see a structured stderr message and exit 127.Reclaim sweeps (
ok start+ Desktop boot + Desktop project-open) now share a single sentinel-based classifier (isEntryUpToDate). An entry already on the current chain is left untouched — nofs.writeFilefires on a second sweep (verified by mtime). Legacy bare-npx, the rejected bundle-direct per-surface shape, and arbitrary foreign customizations all migrate forward to the chain on next sweep.Observability — event rename + ordering change. The structured event previously emitted as
mcp-config-reclaim(after a successful write) is renamed tomcp-config-migrateand now fires before the rewrite so the "intent to migrate" signal is captured even when the write fails with EACCES/EROFS. Field shape is unified across all three emission surfaces (cli-repair,desktop-startup,desktop-project-open) via the newbuildMcpConfigMigrateEventhelper:{event, scope, surface, editorId, configPath, priorCommand, priorArgs}—editorId(noteditor),configPathalways present,priorCommand/priorArgstruncated to 200 chars per entry. Operators with dashboards keyed onmcp-config-reclaimmust repoint tomcp-config-migrate.Removes the per-surface canonical-shape split (and its cross-rewrite repair sweep) proposed in the 2026-05-26 amendment. One shape, one classifier, one write path — across Desktop, CLI, user-scope, and project-scope.
Spec:
specs/2026-05-27-mcp-resilient-chain/SPEC.md.Multi-worktree share-link receive: route share links to the worktree whose branch matches the share
Share links now treat the share's branch as the primary candidate-selection signal across both Recents and
git worktree list-enumerated checkouts, instead of dispatching to whichever clone was opened most recently. This fixes three multi-worktree failure modes: wrong-worktree dispatch, switch-fail with a confusing toast, and CLI-managed worktrees being invisible to selection.- Branch-match-wins candidate selection across all enumerated worktrees; the no-match fallback prefers a main checkout (safe to
git checkout) over an arbitrary worktree. - New
branch-in-other-worktreetyped checkout outcome that surfaces the holding worktree's path and pivots the dialog to open it. Its git-stderr detection matches both phrasings — older git'sis already checked out atand newer git'sis already used by worktree at— so the pivot fires regardless of the host's git version. - One-shot consent dialog ("Initialize and open") for branch-matching worktrees that lack
.ok/config.yml, backed by a newPOST /api/local-op/ok-initendpoint. The endpoint enforces the same home-directory containment guard (isSafeLocalPath) as the sibling local-op endpoints. - Realpath-based candidate identity so symlinked worktree paths (e.g. macOS
/varvs/private/var) collapse to one candidate. - Dispatched-window toast naming the chosen worktree, suppressed for single-clone receivers.
- Fix share-link creation from a linked worktree: origin config and remote-tracking refs are now read from the shared common git dir (via the worktree's
commondirpointer) instead of the per-worktree git dir, which lacks them. Previously a worktree project reportedno-remoteand fell back to the Publish-to-GitHub modal even when origin was configured. - Fix share-link receive for linked-worktree projects:
readCanonicalGitHubRemoteUrl(which backfillsRecentProject.gitRemoteUrlon project open) now resolves the common git dir for the config read, so a worktree project gets a non-null remote URL and is matched by the Q1 lookup. Previously a worktree project'sgitRemoteUrlwas null, so an open worktree on the share's branch silently missed Q1 and fell back to the clone/locate dialog. - Fix the worktree share-receive dispatch gate:
validateLocalFolderForShare(thebranch-match-okarm's repo-identity check, and the Q2 "I have it locally" validator) read origin config from the worktree gitdir, which has none, and returnednot-git— so even when selection correctly picked an open worktree on the share's branch, the dispatch was rejected and the receiver got the clone/locate dialog. It now resolves the shared common dir via the worktree'scommondirpointer for the config read.
- Branch-match-wins candidate selection across all enumerated worktrees; the no-match fallback prefers a main checkout (safe to
revert(editor): drop ToggleHeadings (collapsible headings) extension
The fold-state chevron sat inline at the trailing edge of each foldable heading and rode the caret on every keystroke when typing at end-of-heading —
Decoration.widget(..., { side: -1 })rendered the chevron before the heading's last position, so the caret consistently landed after the chevron rather than after the typed character. Pulling the feature is cheaper than redesigning under a shipped surface; a future re-introduction starts from a blank canvas.Removed surfaces: the
ToggleHeadingsPM extension + its tests, thesharedExtensionsregistration, theTiptapEditordocName-threaded configure call, three CSS scopes (.heading-fold-chevron/.is-heading-foldable/.is-heading-folded[-content]including the print + reduced-motion overrides), thechunk-wrapper-decorationheading carve-out (headings now participate inok-chunk-wrapperCV:auto wrapping like every other top-level block), and theshowcase/18-toggle-headings.mdwalkthrough.Local-only fold state lived in
localStorage(ok:fold:v1:<docName>) and never touched the markdown source or the Y.Doc, so this is a pure behavior removal — no migration, no CRDT cleanup, no data loss. Existingok:fold:v1:*entries remain in browser storage until quota pressure forces eviction or the user clears site data; their footprint is negligible (a JSON-stringifiedSet<string>of slugs per docName).feat(open-knowledge): structured file logging + ok bug-report bundling
Adds persistent structured logging across the OK Electron desktop and
okCLI, plus a one-command bug-report bundle generator for easy diagnostic capture.New surfaces:
ok bug-reportCLI command — gathers logs + sysinfo + lockDir contents into a redacted zip at~/.ok/bug-reports/, reveals in Finder viaopen -R;--no-revealflag for agent/CI use.- OK-managed MCP skill at
packages/server/assets/skills/bug-report/SKILL.md— agent-discoverable across any MCP host with OK wired. createFileLogger()+serializeError()exports from@inkeep/open-knowledge-server.Loggable,SerializedError,ClassifiedPath,BundleManifest,BundleRedactiontypes from@inkeep/open-knowledge-core.
Log file behavior:
- Files at
~/.ok/logs/{cli,desktop}.<YYYY-MM-DD>.log(one per runtime per day). - NDJSON via pino; every record carries
pid,runtime,project,subsystem. - Per-file rotation at 5 MB with 2 archives (~15 MB per logger).
- Age-based pruning (delete files >7 days) + aggregate cap (45 MB total).
- Cleanup runs async (
setTimeout) — does not block startup or CLI execution.
Secret avoidance (systematic discipline):
serializeError()scrubs/Users/<name>/paths frome.messageande.stack.- Pino
redactonauthorization,password,token,apiKey,secretfields (top-level + one-deep nested). - Bundle-time auto-redaction for residual home paths, GitHub PATs, AWS/Anthropic/OpenAI keys, Bearer headers. Every scrub recorded in
MANIFEST.json.redactions[]. - CI-blocking grep test (
tests/integration/logger-discipline.test.ts) bansJSON.stringifyinside logger calls + rawprocess.argv/process.envreads in scoped files.
Verbosity:
- Default
info;OK_LOG_LEVELenv var honored at logger init (capped atdebug). - No CLI flag, no settings toggle — env var is the sole runtime override.
Desktop log sites:
- Boot: version, isPackaged, electron/node versions, platform, arch.
- Project open: projectName, entryPoint, deep-link presence.
- Navigator: open + focus-existing.
- Auto-updater: update-available, update-not-available, update-downloaded.
- IPC errors: dual-emit (existing structured
console.warn+ new file log).
Spec:
public/open-knowledge/specs/2026-05-26-electron-app-and-cli-structured-logs/SPEC.mdResearch:reports/electron-app-logging-bug-bundles/REPORT.md
Patch Changes
feat(open-knowledge): honest resolution path for delete-vs-modify conflicts
Adds
'delete'as a fourthResolveStrategyvariant (alongside the existing'mine'/'theirs'/'content'), runsgit rmto honor deletion intent, and threads a stage-presence discriminator (kind: 'both-modified' | 'delete-modify' | 'modify-delete') throughGET /api/sync/conflict-contentso the UI and MCP agents can branch on which conflict shape they have.User-visible: the DiffViewBoundary now renders shape-aware affordances —
Keep file deleted/Restore with remote changesfor delete-modify (DU), andKeep my version/Accept their deletionfor modify-delete (UD). The classical both-modified flow is unchanged.MCP:
resolve_conflict({strategy: 'delete'})runsgit rmthen commits. Agents inspectkindonget_conflict_contentto pick the right strategy.Wire (backward-compatible):
SyncConflictContentSuccessSchemagainskind;SyncResolveConflictRequestSchemaadmits'delete'and now rejects empty strings forstrategy: 'content'at the Zod trust boundary with a clear field-specific message (previously bubbled up as a misleading 500 with'requires content parameter'detail).Closes the gap in
specs/2026-05-19-conflict-aware-write-surfaces(see the post-ship corrigendum breadcrumbs added in that spec for the in-doc trail).fix(open-knowledge): inline pino + yazl into the cli bundle so packaged DMG can open projects
The Electron app's packaged CLI dist landed in
app.asar.unpacked/. Bareimport 'pino'/import 'yazl'in that dist failed to resolve at runtime because Node's module resolver from insideapp.asar.unpacked/walks the real filesystem only and can't cross into the siblingapp.asar/for siblingnode_modules. Result: opening a project crashed withERR_MODULE_NOT_FOUND: Cannot find package 'pino'and the server never booted. Added both packages to tsdown'salwaysBundleand a regression test that asserts every cli runtime dep is bundle-covered.fix(open-knowledge): inline pino + pino-pretty in the server bundle to pre-empt the cli packaging bug class
Follow-up to #1389. The cli bundle inlined its logger deps after the packaged Electron app placed the cli's dist in
app.asar.unpacked/— Node's module resolver from there can't reach sibling node_modules inside the asar archive.@inkeep/open-knowledge-serveris also installed into the packaged app's node_modules and would hit the sameERR_MODULE_NOT_FOUNDif any future native dep makes electron-builder relocate the server package the same way. Pre-empt by inlining pino + pino-pretty here too. Scope is intentionally narrow (logger deps only); OTel + Hocuspocus + Tiptap + Yjs bundling is non-trivial and they aren't implicated in the bug pattern. Added a regression test analogous to the cli one.