OpenKnowledge

v0.8.0

Part of the OpenKnowledge changelog.

Minor Changes

  • CLI parity for skill reclaim + symmetric OK_RECLAIM_DISABLE kill switch.

    ok start now refreshes bundled SKILL.md files 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.md already exists, the directory is refreshed from the bundled project skill. 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's cli-hosts entry. 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-skills subcommand for explicit invocation without booting a server.

    OK_RECLAIM_DISABLE=1 now short-circuits the new skill sweep AND the existing repairMcpConfigs + repairLaunchJson sweeps — 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 saw SKILL.md files advance past whatever ok init originally wrote.

  • feat(ok): cross-platform git preflight + ok diagnose health subcommand

    Phase 1 of the git peer-dependency plan. Closes the "Error: spawn git ENOENT" failure surface that simple-git leaks when git is 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.showMessageBox with three buttons: Open Install Page / Retry / Quit. Retry re-probes synchronously; the loop is user-driven (no programmatic cap).
    • ok diagnose health sibling subcommand to ok 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.fail with 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 the git-version-matrix.yml tech-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 on MIN_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).

  • 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 hit npx; 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 — no fs.writeFile fires 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 to mcp-config-migrate and 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 new buildMcpConfigMigrateEvent helper: {event, scope, surface, editorId, configPath, priorCommand, priorArgs}editorId (not editor), configPath always present, priorCommand/priorArgs truncated to 200 chars per entry. Operators with dashboards keyed on mcp-config-reclaim must repoint to mcp-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-worktree typed 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's is already checked out at and newer git's is 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 new POST /api/local-op/ok-init endpoint. 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 /var vs /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 commondir pointer) instead of the per-worktree git dir, which lacks them. Previously a worktree project reported no-remote and fell back to the Publish-to-GitHub modal even when origin was configured.
    • Fix share-link receive for linked-worktree projects: readCanonicalGitHubRemoteUrl (which backfills RecentProject.gitRemoteUrl on 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's gitRemoteUrl was 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 (the branch-match-ok arm's repo-identity check, and the Q2 "I have it locally" validator) read origin config from the worktree gitdir, which has none, and returned not-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's commondir pointer for the config read.
  • 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 ToggleHeadings PM extension + its tests, the sharedExtensions registration, the TiptapEditor docName-threaded configure call, three CSS scopes (.heading-fold-chevron / .is-heading-foldable / .is-heading-folded[-content] including the print + reduced-motion overrides), the chunk-wrapper-decoration heading carve-out (headings now participate in ok-chunk-wrapper CV:auto wrapping like every other top-level block), and the showcase/18-toggle-headings.md walkthrough.

    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. Existing ok:fold:v1:* entries remain in browser storage until quota pressure forces eviction or the user clears site data; their footprint is negligible (a JSON-stringified Set<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 ok CLI, plus a one-command bug-report bundle generator for easy diagnostic capture.

    New surfaces:

    • ok bug-report CLI command — gathers logs + sysinfo + lockDir contents into a redacted zip at ~/.ok/bug-reports/, reveals in Finder via open -R; --no-reveal flag 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, BundleRedaction types 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 from e.message and e.stack.
    • Pino redact on authorization, password, token, apiKey, secret fields (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) bans JSON.stringify inside logger calls + raw process.argv/process.env reads in scoped files.

    Verbosity:

    • Default info; OK_LOG_LEVEL env var honored at logger init (capped at debug).
    • 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.md Research: 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 fourth ResolveStrategy variant (alongside the existing 'mine' / 'theirs' / 'content'), runs git rm to honor deletion intent, and threads a stage-presence discriminator (kind: 'both-modified' | 'delete-modify' | 'modify-delete') through GET /api/sync/conflict-content so 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 changes for delete-modify (DU), and Keep my version / Accept their deletion for modify-delete (UD). The classical both-modified flow is unchanged.

    MCP: resolve_conflict({strategy: 'delete'}) runs git rm then commits. Agents inspect kind on get_conflict_content to pick the right strategy.

    Wire (backward-compatible): SyncConflictContentSuccessSchema gains kind; SyncResolveConflictRequestSchema admits 'delete' and now rejects empty strings for strategy: '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/. Bare import 'pino' / import 'yazl' in that dist failed to resolve at runtime because Node's module resolver from inside app.asar.unpacked/ walks the real filesystem only and can't cross into the sibling app.asar/ for sibling node_modules. Result: opening a project crashed with ERR_MODULE_NOT_FOUND: Cannot find package 'pino' and the server never booted. Added both packages to tsdown's alwaysBundle and 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-server is also installed into the packaged app's node_modules and would hit the same ERR_MODULE_NOT_FOUND if 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.

View v0.8.0 on GitHub