ADR-0045: Project wiki — markdown knowledge base as a DSL surface

  • Status: Accepted
  • Date: 2026-06-15
  • Deciders: @karasu, with colleagues
  • Supersedes:
  • Superseded by:
  • Relates to: ADR-0006 (DSL format), ADR-0015 (federated config, project:/ and config:/ URIs), ADR-0031 (markdown rendering), ADR-0032 (Monaco editor), ADR-0020 (issues), ADR-0034 (session binding)

Context

Operators working with kaged-managed projects accumulate project knowledge — setup guides, architecture notes, runbooks, decision logs — that doesn't belong in code comments, issue descriptions, or agent prompts. Today there is no structured place for this inside kaged. Operators either maintain a separate wiki tool (Otterwiki, Notion, Confluence) or scatter .md files in the repo and hope someone finds them.

The building blocks already exist:

  • Markdown rendering. The UI has a Markdown component (react-markdown + remark-gfm) with custom code block rendering via CodeBlock.
  • File editing. Monaco-based FileEditor already handles .md files with the markdown language mode.
  • File browsing. The daemon already serves directory listings for project:/ and config:/ scoped paths. The UI already has a file browser that can navigate to any scoped path.
  • Scoped paths. The PrefixedPathSchema in @kaged/dsl validates project:/ and config:/ URIs. The path system is mature.
  • Entity references. Sessions are identified by ULIDs, issues by sequential integer numbers. Both have existing badge components (SessionBadge, IssueBadge, StatusBadge).

What's missing is a thin DSL surface to declare "this project has a wiki, it lives here," a link syntax for cross-referencing wiki pages and kaged entities, and mermaid diagram rendering.

The motivating use case: an operator has a git submodule containing wiki data managed by Otterwiki. The submodule lives at project:/wiki/data. The markdown files are editable from Otterwiki's web UI, from a code editor, and now from kaged's UI — all pointing at the same files. Because the wiki is a submodule with its own git history, auto-commit and pull-before-edit are necessary to keep state consistent across editing surfaces and prevent data loss.

A second use case: a project with a project:/docs directory of markdown files that the operator wants surfaced as a browsable, linked wiki within the kaged UI. No git sync needed — it's just files in the project tree.

Decision

kaged adds an optional wiki block to the project DSL. The wiki is a directory of markdown files at a scoped path, rendered in the UI as a browsable knowledge base with a file-tree sidebar. Cross-references between wiki pages and kaged entities (issues, sessions) use [[double-bracket]] link syntax, resolved by a remark plugin. Mermaid diagrams are supported in fenced code blocks. An optional git sub-block enables auto-commit, auto-push, and pull-before-edit for submodule-backed wikis.

1. DSL surface

A new optional wiki block on ProjectDslSchema:

# project.yaml
version: 1
project: my-project
wiki:
  path: "project:/wiki"
  home: "readme.md"
  git:
    auto_commit: true
    auto_push: false
    pull_before_edit: true
primary:
  # ...

wiki.path (string, required)

The scoped path to the wiki root directory. Accepts project:/ or config:/ URIs per ADR-0015. The directory must exist at project load time. All wiki operations are scoped to this directory — no path traversal outside it.

Examples:

  • project:/wiki — a wiki/ directory in the project root.
  • project:/wiki/data — a subdirectory (e.g. a submodule mount point).
  • config:/wiki — operator-level wiki content, shared across projects.

wiki.home (string, optional)

The filename to use as the wiki homepage. If set, this file is displayed when the operator navigates to the wiki root.

If omitted, the runtime resolves the homepage by trying these filenames in order: home.md, readme.md, README.md. The first match wins. If none exist, the wiki opens to the file browser with no page selected.

This is a filename, not a path — it is resolved relative to wiki.path. Subdirectory paths are not accepted.

wiki.git (object, optional)

Git sync configuration for wikis backed by a separate git history (typically submodules). If the git block is absent, wiki editing is plain file I/O — no commits, no pushes, no pulls. The wiki directory may or may not be inside a git repository; kaged does not care.

When present, the daemon performs git operations scoped to the wiki directory (not the project root). This is specifically designed for submodule-backed wikis where the wiki directory has its own .git or is a submodule with its own commit history.

Field Type Default Description
auto_commit boolean false Commit changes on save. The commit is scoped to the wiki directory's git context. Commit message is generated from the filename and action (create/edit/delete).
auto_push boolean false Push to the tracking remote after a successful auto-commit. Requires auto_commit: true (validated at parse time). Defaults to false because auto-push is a footgun for most workflows.
pull_before_edit boolean false Pull from the tracking remote before opening a file for editing. Prevents edit conflicts when the wiki is edited from multiple surfaces (Otterwiki, code editor, kaged UI). Without this, an operator can unknowingly edit a stale file and force-overwrite newer changes on the next push.

Git operations are fire-and-forget from the UI's perspective — the daemon runs them and reports errors. A failed pull does not block editing (the operator sees a warning). A failed push is retried once and then surfaced as a notification.

2. Wiki link syntax — [[double-bracket]]

Wiki pages and kaged entities are cross-referenced using double-bracket syntax, the de facto wiki convention (MediaWiki, Obsidian, Notion). This syntax does not collide with standard markdown.

Syntax forms

Syntax Resolves to Rendered as
[[page-name]] ./page-name.md (wiki-internal link) Text link with page title
[[subdir/page]] ./subdir/page.md (wiki-internal, relative) Text link with page title
[[issue:42]] Issue #42 in the current project IssueBadge with issue title, linked to issue detail
[[session:01HXAA...]] Session by ULID SessionBadge with session title, linked to session detail

Resolution rules

  • Unscoped ([[blah]]) — always a wiki-internal link. Resolved relative to the current page's directory. The .md extension is appended if not present.
  • issue: scope — the number after the colon is the issue's sequential integer number (the user-facing identifier, consistent with the #N convention in the UI).
  • session: scope — the value after the colon is the session ULID (26 characters).
  • Unknown scopes — rendered as plain text with a warning indicator. Future scopes (e.g., workflow:, task:) can be added without syntax changes.

Display text override

A pipe separator provides custom display text, following wiki convention:

See [[setup-guide|the setup guide]] for details.
Fixed in [[issue:42|the auth bug]].

Implementation

A custom remark plugin transforms [[...]] tokens during markdown rendering. For entity links (issue:, session:), the plugin emits React components that fetch the entity title and status asynchronously, rendering a skeleton badge until loaded. The existing IssueBadge and SessionBadge components are reused.

Wiki-internal links are resolved to the wiki path and rendered as standard <a> tags with client-side navigation (no full page reload).

Links to nonexistent wiki pages are rendered with a distinct style (red or dashed, following wiki convention) to signal missing content.

3. Mermaid diagram support

Fenced code blocks with the mermaid language identifier are rendered as diagrams:

```mermaid
graph TD
    A[Start] --> B{Decision}
    B -->|Yes| C[Do thing]
    B -->|No| D[Do other thing]
```

Implementation: the existing Markdown component's custom code block renderer already checks the language class (language-*). A language-mermaid case delegates to a lazy-loaded mermaid renderer (client-side, via the mermaid npm package). The diagram is rendered as an inline SVG.

Mermaid rendering is available everywhere the Markdown component is used, not just in the wiki. Issue descriptions, session transcripts, and agent messages all gain mermaid support.

4. UI surface

The wiki UI is minimal and built from existing components:

  • Sidebar: the existing file browser component, scoped to wiki.path. Displays the directory tree. Clicking a .md file renders it in the content area. Non-markdown files are handled by the existing file viewer.
  • Content area: the existing Markdown component, extended with the [[...]] remark plugin and mermaid support.
  • Edit mode: a toggle button switches from rendered view to the Monaco editor (existing FileEditor). A save button writes the file (and triggers git operations if configured). Split-pane or live preview editing is deferred to a future iteration.
  • Feature gate: if the wiki block is absent from the project DSL, the wiki navigation entry is not shown. The feature is entirely opt-in.

5. Frontmatter

Wiki markdown files may contain YAML frontmatter. In v1, frontmatter is preserved but not interpreted — the Markdown component strips it before rendering (as react-markdown already does with remark-frontmatter). Future iterations may use frontmatter for page metadata (title override, tags, sidebar ordering, access control).

Consequences

What this commits us to

  • wiki block on ProjectDslSchema. A new optional top-level field with WikiSchema (path, home, git sub-object). Zod schema in @kaged/dsl, strict as always.
  • [[double-bracket]] remark plugin. A custom remark plugin for the Markdown component. Handles wiki links, issue references, session references.
  • Mermaid rendering. A new npm dependency (mermaid) loaded lazily in the Markdown component's code block handler.
  • Git operations in the daemon. When wiki.git is configured, the daemon runs git pull, git add, git commit, git push scoped to the wiki directory. This is the first time the daemon performs git operations — it uses Bun.spawn (per ADR-0004, no child_process).
  • Wiki route in the UI. A new route under the project scope for the wiki view (file browser + content + edit toggle).

What this forecloses

  • Wiki as a database. The wiki is files on disk, not a database-backed content system. No full-text search index, no revision history beyond git, no concurrent editing conflict resolution beyond pull-before-edit. This is intentional — the wiki is a file browser with markdown rendering, not a CMS.
  • Wiki permissions separate from project permissions. Wiki access follows project access. There is no per-page or per-directory ACL. The cage system does not apply to wiki content (wiki files are not agent-accessible unless the cage's fs mounts include the wiki path).
  • Rich text editing. The editor is Monaco (plain text markdown). No WYSIWYG, no block editor. The preview is the rendered markdown view. This matches the operator audience — these are people who write markdown.

What becomes easier

  • Project knowledge has a home. Setup guides, architecture notes, runbooks live inside the project and are browsable from the kaged UI without leaving the tool.
  • Cross-referencing. Wiki pages link to issues and sessions with live badges. An operator writing a postmortem can reference [[issue:42]] and [[session:01HXAA...]] inline.
  • Submodule-backed wikis. Operators using Otterwiki or similar tools can edit wiki content from kaged's UI with git sync keeping everything consistent.
  • Mermaid everywhere. Diagrams in wiki pages, issue descriptions, and agent transcripts. One dependency, broad utility.

What becomes harder

  • Git error handling. The daemon now runs git operations that can fail (network, auth, merge conflicts). The error surfaces are new and need thought — a failed pull-before-edit should warn, not block; a merge conflict on auto-commit is a real edge case that v1 may punt on (surface the error, let the operator resolve manually).
  • Mermaid bundle size. mermaid is a large package. Lazy loading mitigates this, but it still adds to the UI's total downloadable size. Worth watching.

Alternatives considered

Alternative A — No DSL surface; just render markdown from any directory

Why tempting: Less schema surface. The UI could just render .md files wherever the file browser navigates.

Why rejected: Without a declared wiki path, there's no place to hang the git sync configuration, no way to scope the sidebar, and no way to disable the feature for projects that don't want it. The DSL field is cheap and makes the feature explicit.

Alternative B — Database-backed wiki with revision history

Why tempting: Proper versioning, search, concurrent editing support.

Why rejected: Massively more complex. The files-on-disk model means the wiki works with any text editor, any git tool, and any existing wiki system that operates on markdown files. It's composable. A database-backed wiki is a silo.

Alternative C — Use standard markdown link syntax instead of [[double-bracket]]

Why tempting: No custom syntax; standard markdown links work everywhere.

Why rejected: Standard [text](url) links require the author to know the exact relative path and don't support entity references without URL scheme hacks. [[page-name]] is terser, more discoverable, and the established convention for wikis. The remark plugin handles the translation transparently.

Alternative D — Defer mermaid support

Why tempting: Smaller scope for the wiki ADR.

Why rejected: Mermaid is a no-brainer addition to the existing Markdown component. It's one code block handler case and a lazy-loaded dependency. Deferring it to a separate ADR is overhead that exceeds the implementation cost.

Phasing

Phase 1 (MVP)

  • wiki block on ProjectDslSchema (path, home).
  • Wiki route in the UI: file browser sidebar + markdown rendering + edit toggle.
  • [[page-name]] wiki-internal links (remark plugin).
  • [[issue:N]] and [[session:ULID]] entity links with badge rendering.
  • Mermaid code block rendering (lazy-loaded).
  • Display text override ([[target|display text]]).

Phase 2

  • wiki.git block (auto_commit, auto_push, pull_before_edit).
  • Git operations in the daemon scoped to the wiki directory.
  • Error handling for git failures (warnings, notifications).
  • Broken-link styling for nonexistent wiki pages.

Phase 3

  • Frontmatter interpretation (title, tags, sidebar ordering).
  • Split-pane or live preview editing.
  • Additional entity scopes ([[workflow:name]], [[task:name]]).
  • Wiki search (full-text search within wiki files).

Spec amendments required

# File Change
1 docs/specs/project-dsl.md Add wiki block to project-level schema. Document WikiSchema (path, home, git). Add to Appendix A JSON Schema.
2 docs/specs/ui/README.md Document wiki route, file browser scoping, edit toggle, wiki link rendering.
3 docs/specs/daemon.md Document wiki git operations (pull, commit, push) and error handling.

Existing ADRs amended

  • ADR-0031 — The Markdown component gains mermaid code block rendering and the [[...]] remark plugin. These apply globally (transcripts, issues, wiki), not only to the wiki surface.

References

  • ADR-0006 — DSL format (YAML + Zod)
  • ADR-0015project:/ and config:/ URI prefixes
  • ADR-0020 — issue model (sequential numbers, status)
  • ADR-0031 — markdown rendering in the UI
  • ADR-0032 — Monaco as the code editor
  • ADR-0034 — session-issue binding, issue badge rendering
  • Otterwiki — https://otterwiki.com/ — the external wiki tool motivating the git sync use case
  • Original discussion: design conversation with colleagues, 2026-06-15