From 9cdbce548a553138da8b446854ede7dd1d6001f9 Mon Sep 17 00:00:00 2001 From: claude code agent 227 Date: Wed, 24 Jun 2026 13:07:27 +0300 Subject: [PATCH] docs(git-sync): restore README/CHANGELOG/AGENTS deleted by a stray commit (#119 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stray commit 0bff612c "rm all 7" deleted README.md, README.ru.md, CHANGELOG.md and AGENTS.md (956 lines) on this branch only — AGENTS.md is the canonical contributor guide the whole workflow relies on. Restored all four from develop's canonical versions so the merge cannot wipe them. Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 307 +++++++++++++++++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 232 ++++++++++++++++++++++++++++++++++++++ README.md | 225 +++++++++++++++++++++++++++++++++++++ README.ru.md | 212 +++++++++++++++++++++++++++++++++++ 4 files changed, 976 insertions(+) create mode 100644 AGENTS.md create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 README.ru.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..48a282ad --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,307 @@ +# AGENTS.md + +This file guides AI agents (Claude Code, opencode, …) working in this +repository. It has two layers: **how to run a task end-to-end** (the +sections below), and **how the codebase is built** (the technical sections +further down, formerly in `CLAUDE.md`). + +## Task lifecycle + +### 1. Start: sync with develop + +Before starting **any** work, update your local `develop` and branch off it: + +```bash +git checkout develop +git fetch gitea +git pull --ff-only gitea develop +git checkout -b +``` + +Never build a feature directly on `develop`, and never branch off a stale +`develop` — otherwise the PR will carry extra commits or conflict. + +### 2. Implementation + +Run the task through the workflow from the system prompt (Phase 1 analysis → +Phase 3 implementation → Phase 4 review → Phase 5 verification → Phase 6 +report). Delegate large changes to a general subagent; review via the review +subagent. + +**Create worktrees only inside the `.claude` folder** (e.g. +`.claude/worktrees/`). Creating a git worktree anywhere else — the repo +root, sibling directories, or temp folders — is forbidden. + +### 3. Commit — ONLY to Gitea and ONLY as `claude_code` + +This rule has no exceptions: + +- **Where:** the only remote for commits/pushes is **`gitea`** + (`gitea.vvzvlad.xyz`). **Never** push to `origin` (the GitHub mirror), and + especially not to `upstream` (the original Docmost). The GitHub mirror is + updated by the owner's CI process, not by the agent. +- **Who:** commit **only** as the agent identity. Any commit whose author or + committer is `vvzvlad` is an error and must be rewritten. + - **name:** `claude_code` + - **email:** `claude_code@vvzvlad.xyz` + +Use `--reset-author` when amending, otherwise git keeps the original author +(the default config on this machine is `vvzvlad`, so check after every commit): + +```bash +GIT_AUTHOR_NAME="claude_code" \ +GIT_AUTHOR_EMAIL="claude_code@vvzvlad.xyz" \ +GIT_COMMITTER_NAME="claude_code" \ +GIT_COMMITTER_EMAIL="claude_code@vvzvlad.xyz" \ +git commit --amend --no-edit --reset-author +``` + +For a regular new commit, set the branch-local config once and commit normally: + +```bash +git config user.name "claude_code" +git config user.email "claude_code@vvzvlad.xyz" +``` + +Check before push: + +```bash +git log -1 --format='Author: %an <%ae>%nCommitter: %cn <%ce>' +# both lines must show claude_code +``` + +### 4. Push and PR to develop + +PRs always target `develop`. The `claude_code` password lives in the macOS +keychain as a **generic password** under service `gitea-claude-code` (do not +duplicate it as an internet-password for `gitea.vvzvlad.xyz` — that creates a +conflict with the owner's account in the git credential helper): + +```bash +AGENT_PASS=$(security find-generic-password -s gitea-claude-code -w) +``` + +Push by temporarily injecting the credentials into the remote URL, then always +restore the URL to its clean form (the password must not linger in git +config / reflog): + +```bash +ORIG_URL=$(git remote get-url gitea) +SAFE_PASS=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$AGENT_PASS") +git remote set-url gitea "https://claude_code:${SAFE_PASS}@gitea.vvzvlad.xyz/vvzvlad/gitmost.git" +git push -u gitea +git remote set-url gitea "$ORIG_URL" +unset AGENT_PASS SAFE_PASS +``` + +The PR is created via the Gitea REST API (Basic Auth as `claude_code`): + +```bash +curl -s -X POST \ + -u "claude_code:$(security find-generic-password -s gitea-claude-code -w)" \ + -H "Content-Type: application/json" \ + -d @pr_body.json \ + "https://gitea.vvzvlad.xyz/api/v1/repos/vvzvlad/gitmost/pulls" +``` + +`base: develop`, `head: `. In the PR body: what was done, what is out +of scope, verification results (tsc/lint/tests). + +> If push fails with `User permission denied for writing`, then `claude_code` +> lacks collaborator rights on the repo. Ask the owner to add them (once, via +> the Gitea UI or `PUT /api/v1/repos/vvzvlad/gitmost/collaborators/claude_code` +> with `{"permission":"write"}` from their account). + +### 5. Merge and cleanup + +- **The user merges the PR into develop** (not the agent). The agent does not + press the merge button. +- **After implementing a task, delete its plan from `docs/backlog/.md`** — + this is part of closing the task, not the user's work. Files in + `docs/backlog/` are the work queue; completed items get cleaned out of it. + Do this in a separate commit from the same `claude_code` on the same branch + (or ask the user to delete it if the PR is already open and you don't want to + repush it). +- Any junk left uncommitted in the working tree? Check `git status` before the + final report. + +## Release cycle: staging a new version + +When enough changes have accumulated on `develop` for a release, a **final +review by three orchestrator skills** runs before the merge/tag: + +1. **test-orchestrator** (the `code-review-orchestrator` skill focused on test + coverage) — verifies new code is covered by tests and there are no + regressions in existing ones. +2. **review-orchestrator** (the `code-review-orchestrator` skill) — + multi-aspect code review: security, stability, convention conformance, + regressions, over-complexity. +3. **red-team-orchestrator** (the red-team skill) — adversarial analysis of + attack scenarios against the affected components. + +Order: the orchestrators return finding lists → the agent fixes everything they +found (via a subagent or itself, per the delegation rules) → re-runs the review +on the affected areas → cuts the tag per the "Cutting a release" procedure +below. + +## Accounts & endpoints cheat sheet + +| Item | Value | +| --- | --- | +| Only remote for commits | `gitea` → `https://vvzvlad@gitea.vvzvlad.xyz/vvzvlad/gitmost.git` | +| Agent user (Gitea/git) | `claude_code` | +| Agent email | `claude_code@vvzvlad.xyz` | +| Keychain password | `security find-generic-password -s gitea-claude-code -w` | +| PR API | `https://gitea.vvzvlad.xyz/api/v1/repos/vvzvlad/gitmost/pulls` (here `gitmost` is the repo's real slug on the server) | +| Base branch | `develop` | +| `origin` | GitHub mirror `vvzvlad/gitmost` — **do not push**, updated by the owner's CI | +| `upstream` | The original Docmost — **never push** | + +--- + +# Architecture and codebase + +## What this is + +**Gitmost** is a community fork of [Docmost](https://github.com/docmost/docmost) — an open-source collaborative wiki / documentation app. The fork's defining constraint: **100% open, AGPL-only, with no Enterprise-Edition (EE) code**. The upstream `apps/server/src/ee`, `apps/client/src/ee` and `packages/ee` directories were deleted; there is no license gating or feature-flag wall. Features that upstream hides behind the enterprise license (comment resolution, the embedded `/mcp` server, the AI agent chat) are **re-implemented from scratch** on the community codebase. + +**Naming gotcha:** only the *product* is rebranded. Internal identifiers are still `docmost` everywhere — npm package names (`docmost`, `@docmost/mcp`, `@docmost/editor-ext`), the default DB name, env-var prefixes (`MCP_DOCMOST_*`), and the TS path aliases (`@docmost/db/*`, `@docmost/transactional/*`). Do not "fix" these to `gitmost`; they are load-bearing for Docmost data/image compatibility (the DB schema is a strict superset of Docmost's, so an existing instance migrates by swapping images). + +## Monorepo layout + +pnpm workspace (`pnpm@10.4.0`) orchestrated by **Nx**. Four workspace packages: + +| Path | Name | Stack | Role | +| --- | --- | --- | --- | +| `apps/server` | `server` | NestJS 11 + Fastify, Kysely (Postgres), Redis | Backend API, collaboration, AI | +| `apps/client` | `client` | React 18 + Vite + Mantine 8 + TanStack Query + Jotai | SPA frontend | +| `packages/editor-ext` | `@docmost/editor-ext` | Tiptap/ProseMirror | Shared Tiptap node/mark extensions, imported by both the client and the server | +| `packages/mcp` | `@docmost/mcp` | MCP SDK, Tiptap, Yjs | Standalone MCP server, also bundled into the server at `/mcp`. Does **not** import `editor-ext` — it keeps its own vendored mirror of the schema in `packages/mcp/src/lib/` | + +`build` targets are Nx-cached and dependency-ordered (`dependsOn: ["^build"]`), so `editor-ext` builds before the apps. `nx.json` sets `affected.defaultBase: main`. + +## Commands + +Run from the repo root unless noted. The dev workflow needs **Postgres (with the `pgvector` extension) and Redis** reachable per `.env` (copy `.env.example` → `.env`). + +```bash +pnpm install # install all workspaces (uses pnpm patches; see package.json `pnpm.patchedDependencies`) +pnpm dev # client (Vite) + server (Nest watch) concurrently — primary dev loop +pnpm client:dev # frontend only (Vite proxies /api to APP_URL) +pnpm server:dev # backend only (nest start --watch) +pnpm build # nx run-many -t build (all packages) +pnpm collab:dev # run the collaboration server process standalone (see "Two server processes") +``` + +**Lint** (per package — there is no root lint script): +```bash +pnpm --filter server lint # eslint --fix on server .ts +pnpm --filter client lint # eslint on client +``` + +**Tests** (per package — no root test script): +```bash +pnpm --filter server test # Jest, matches *.spec.ts under src +pnpm --filter server test -- ai-chat.service # single file by name pattern +pnpm --filter server test -- -t "resolves a comment" # single test by name +pnpm --filter client test # Vitest (vitest run) +pnpm --filter client test -- message-list # single Vitest file by name +pnpm --filter @docmost/mcp test # node --test (unit + mock) +pnpm --filter @docmost/mcp test:e2e # MCP end-to-end against a live instance +``` + +**Database migrations** (Kysely, run from `apps/server`). **Where they auto-apply:** in **production** (the built image / `start:prod`) pending migrations run automatically on server boot. In **local dev** (the `pnpm dev` stand / `nest start --watch`) they do **NOT** auto-run — after you pull or switch branches you must apply them yourself with `pnpm --filter server migration:latest`, or any endpoint touching a new column/table 500s (e.g. a freshly-added `ai_chats.page_id` blanket-500s all of AI chat until migrated). +```bash +pnpm --filter server migration:create --name=my_change # new empty migration +pnpm --filter server migration:latest # apply all pending +pnpm --filter server migration:down # revert last +pnpm --filter server migration:codegen # regenerate src/database/types/db.d.ts from the live DB +``` +Migration files live in `apps/server/src/database/migrations/` and are named `YYYYMMDDThhmmss-description.ts`. Fork-specific migrations only **add** tables (`page_embeddings`, `ai_chats`, `ai_chat_messages`, `ai_provider_credentials`, `ai_mcp_servers`, `page_template_references`) and columns (e.g. `pages.is_template`, a `NOT NULL DEFAULT false` boolean) — never drop/rewrite Docmost data. + +**Migration ordering — always check when merging branches/features.** Kysely runs migrations in **alphabetical (= timestamp) order** and refuses to start if a *new* migration sorts **before** one already applied to the DB (`corrupted migrations: ... must always have a name that comes alphabetically after the last executed migration`). When you merge a branch or land a feature, verify your migration's timestamp still sorts **after every migration that may already be applied on the target** (`/bin/ls -1 apps/server/src/database/migrations | sort | tail`). Branches developed in parallel routinely break this: a feature branch adds `…T130000-…`, `main` meanwhile ships and deploys `…T150000-…`, and after the merge the older-timestamped file is rejected at boot. **Fix = rename your migration to a timestamp after the latest one already in the target** (content unchanged — the filename is the ordering key), then rebuild so the compiled `dist/database/migrations/` picks up the new name. + +## Architecture — the big picture + +### Two server processes +`apps/server` builds one codebase but runs as **two distinct entrypoints**, both required in production: +- **API server** — `dist/main` (`apps/server/src/main.ts`), the Fastify HTTP app (`AppModule`). +- **Collaboration server** — `dist/collaboration/server/collab-main` (`pnpm collab`), a Hocuspocus/Yjs WebSocket server (`apps/server/src/collaboration/`) handling real-time document editing, persistence, and page-history snapshots. It listens on `COLLAB_PORT` (default `3001`), separate from the API server's `PORT` (default `3000`), and shares state with the API server through Redis. + +The API server is a Fastify app with a global `/api` prefix (`main.ts` excludes `robots.txt`, public share pages, and `mcp` from the prefix). A `preHandler` hook enforces that a resolved `workspaceId` exists for most `/api` routes (multi-tenant by hostname/subdomain via `DomainMiddleware`). Auth is JWT (cookie + bearer); authorization is **CASL** (`core/casl`) — every data access is scoped to the user's abilities. + +### Module structure (server) +`AppModule` wires integration modules (`integrations/*`: storage [local/S3/Azure], mail, queue [BullMQ on Redis], security, telemetry, throttle, `mcp`, `ai`) plus `CoreModule`, `DatabaseModule`, and `CollaborationModule`. `CoreModule` (`core/*`) holds the domain modules: `page`, `space`, `comment`, `workspace`, `user`, `auth`, `group`, `attachment`, `search`, `share`, `ai-chat`, etc. Each domain module follows NestJS controller → service → repo layering; DB repos live under `database/repos` and are injected app-wide from the global `DatabaseModule`. + +**EE removal artifact:** `app.module.ts` still contains a `try/require('./ee/ee.module')` stub. That path no longer exists, so the require fails and is swallowed (it only hard-exits when `CLOUD === 'true'`). Treat EE as gone — do not add code that depends on it. + +### Persistence +- **Postgres via Kysely** (`nestjs-kysely`), typed by the generated `src/database/types/db.d.ts`. Use the camelCase Kysely query builder, not an ORM. After schema changes, write a migration *and* regenerate the DB types. +- **pgvector is mandatory** — the RAG feature stores embeddings in `page_embeddings`. `docker-compose.yml` uses `pgvector/pgvector:pg18` for this reason; the stock `postgres` image will fail the `CREATE EXTENSION vector` migration. +- **Redis** backs caching, the BullMQ queues, the WebSocket Socket.IO adapter, and collaboration sync. + +### The two AI subsystems (the main fork additions) +1. **Embedded MCP server** (`integrations/mcp/` + `packages/mcp`). The standalone `@docmost/mcp` server (38 agent-native tools: per-block patch/insert/delete by id, scripted `(doc)=>doc` transforms with dry-run diff, table editing, version diff/restore, comments, images, shares) is bundled and served over HTTP at `/mcp`. It writes through Docmost's real-time-collaboration layer so concurrent human edits aren't clobbered. Each request authenticates **per-user** via the `Authorization` header — either HTTP Basic (`base64(email:password)`, the user's own Docmost login, validated through `AuthService`) or a Bearer access JWT (the user's `authToken`) — and the session acts under that user's permissions. `MCP_DOCMOST_EMAIL` / `MCP_DOCMOST_PASSWORD` are an **optional service-account fallback**, used only when a request carries neither Basic nor Bearer credentials (back-compat for CI/scripts). An admin enables MCP with a workspace toggle (Workspace settings → AI). Optionally protected by a shared `MCP_TOKEN`: when set, every `/mcp` request must carry a matching `X-MCP-Token` header (its own header, separate from `Authorization`, which now carries the per-user Basic/Bearer credentials). Note: this changed from the older `Authorization: Bearer ` scheme — see `.env.example` and the CHANGELOG Breaking Changes entry. +2. **AI agent chat** (`core/ai-chat/` server + `apps/client/src/features/ai-chat/` client). A built-in agent over the wiki using the Vercel **AI SDK** (`ai`, `@ai-sdk/*`) against any OpenAI-compatible provider configured per workspace (`integrations/ai/` — credentials encrypted at rest via `integrations/crypto`, stored in `ai_provider_credentials`). Key pieces: + - `core/ai-chat/tools/` — the agent's ~40 read+write tools. Every tool runs under the **calling user's** CASL permissions via a per-user loopback access token (`docmost-client.loader.ts`), so the agent can never exceed what the user could do. Only **reversible** operations are exposed (page history + trash; no permanent delete). Agent edits get an "AI agent" provenance badge in page history (`20260616T130000-agent-provenance` migration). + - `core/ai-chat/embedding/` — RAG indexer + a BullMQ consumer on `AI_QUEUE` that embeds pages into `page_embeddings` (vector search), complementing Postgres full-text search. Pages are (re)indexed on edit; `AI_EMBEDDING_TIMEOUT_MS` bounds a hung embeddings endpoint. + - `core/ai-chat/external-mcp/` — admins can attach external MCP servers (e.g. Tavily) to give the agent web access. **`ssrf-guard.ts` validates outbound MCP URLs against SSRF** — keep that guard in the path when touching external-MCP connection logic. + +### Client structure +Vite SPA. Code is organized by feature under `apps/client/src/features/*` (mirrors the server domains: `page`, `space`, `comment`, `ai-chat`, `editor`, …). Conventions: +- **TanStack Query** for server state (one `queries/` file per feature), **Jotai** atoms for local/shared UI state, **Mantine 8** + CSS modules (`*.module.css`) + `postcss-preset-mantine` for UI. +- The editor is Tiptap; shared node/mark extensions live in `packages/editor-ext` and are imported by **both the client and the server** (collaboration, import/export) — editor schema changes often need to be made in `editor-ext`, not just the client. Note `packages/mcp` does *not* depend on `editor-ext`; it carries its own mirrored copy of the schema, so keep the two in sync manually when the document schema changes. +- API access goes through `apps/client/src/lib/api-client.ts` (axios). The `@` alias maps to `apps/client/src`. +- Runtime config is injected at build time by `vite.config.ts` via `define` (`APP_URL`, `COLLAB_URL`, `APP_VERSION`, …) — these come from the root `.env`, not from `import.meta.env`. + +## Conventions + +- **Code comments must be in English.** +- **Errors must never be swallowed or shown as generic messages.** Every caught error MUST (1) be logged in full to the console/logger — error name, message, stack, `cause`, and (for HTTP/provider failures) the status code and response body — and (2) be surfaced to the user with a *specific, human-readable explanation of what actually went wrong*, never a bare generic string like "Something went wrong" / "Could not start recording" / "Transcription failed". Include the real reason (the underlying error/provider message) in the user-facing text. On the server, wrap third-party/provider failures with `describeProviderError` (or equivalent) and rethrow as a meaningful HTTP status + message — never let them collapse into an opaque 500. On the client, `console.error(, err)` the raw error AND show the extracted reason (e.g. `err.response?.data?.message`, or the error `name: message`) in the notification. +- The version string shown in the UI comes from `APP_VERSION` (CI/Docker) or `git describe --tags --always` (local), resolved in `vite.config.ts` — not from `package.json`. +- Server TS config is permissive (`noImplicitAny: false`, `strictNullChecks: false`, `no-explicit-any` lint disabled). Follow the existing relaxed style rather than tightening types broadly. +- Dependency versions are heavily pinned via `pnpm.overrides` and `pnpm.patchedDependencies` (`scimmy`, `yjs`) in the root `package.json`. Don't bump pinned/patched deps casually; the patches and overrides exist for compatibility/security reasons. + +## CI / release + +- `.github/workflows/develop.yml` — on push to `develop`, builds and pushes `ghcr.io/vvzvlad/gitmost:develop`. +- `.github/workflows/release.yml` — on `v*` tags (or manual dispatch), builds multi-arch (amd64 + arm64) images, pushes a manifest list to GHCR (`latest` + semver tags), and creates a draft GitHub Release with image tarballs. Uses the built-in `GITHUB_TOKEN` (not Docker Hub). +- The `Dockerfile` is a multi-stage pnpm build; `APP_VERSION` is passed as a build arg because `.git` isn't in the build context. + +### Cutting a release + +The git tag is the source of truth for the displayed version (UI reads `git describe --tags`); the `package.json` bump is metadata only. Steps: + +1. Make sure `main` is clean and pushed (`git status`, `git push`). +2. Pick `vX.Y.Z` (SemVer): **minor** bump for a batch of features, **patch** for fixes only. Review what landed with `git log ..HEAD --no-merges`. +3. Bump `"version"` to `X.Y.Z` in the **root** `package.json`, `apps/client/package.json`, and `apps/server/package.json` (keep all three in sync). Leave `packages/mcp` alone — it is versioned independently. Commit with the bare version as the subject, e.g. `0.91.0` (matches past bump commits). +4. Update `CHANGELOG.md` (Keep a Changelog format): add a `## [X.Y.Z] - YYYY-MM-DD` section summarising `git log vPREV..HEAD --no-merges` grouped by type (Breaking / Added / Changed / Fixed / Removed), and add the `compare/vPREV...vX.Y.Z` link at the bottom. Fold the bump + changelog into the release commit. +5. Tag the release commit with a **lightweight** tag (existing release tags are lightweight): `git tag vX.Y.Z`. +6. Push commit and tag: `git push origin main && git push origin vX.Y.Z`. Pushing the `v*` tag triggers `release.yml` (multi-arch GHCR images + a draft GitHub Release). +7. **Back-merge the release into `develop`** so develop builds report the new version: `git checkout develop && git merge --no-ff main && git push origin develop` (push to Gitea as well if that is the canonical remote). + +#### Why develop keeps showing the *previous* version (and why step 7 matters) + +The UI version is `git describe --tags --always` (see `vite.config.ts`), which walks **backwards from the current commit** and picks the **nearest tag reachable in that commit's ancestry**, then appends `--g`. + +The release tag (`vX.Y.Z`) is created on **`main`'s release merge commit**, and that commit is **not** in `develop`'s history. So until the release is back-merged, `git describe` on `develop` cannot see the new tag and falls back to the *previous* reachable tag. Result: every develop build — and the `ghcr.io/vvzvlad/gitmost:develop` image — keeps reporting e.g. `v0.91.0-NNN-g` even though `main` is already tagged `v0.93.0`. This is the classic git-flow pitfall: the version on `develop` does **not** advance just because a release was tagged on `main`. + +Back-merging `main → develop` (step 7) pulls the tagged release commit into `develop`'s ancestry, after which develop builds correctly show `vX.Y.Z-NNN-g`. If `develop` already drifted (release tagged but never back-merged), just run step 7 now — no new tag is needed. + +##### The tag must also exist on the remote that CI builds from (multi-remote gotcha) + +`git describe` names a tag **ref**, not just a commit — so the back-merge is *necessary but not sufficient*. The develop image is built by GitHub Actions (`develop.yml`, `actions/checkout` with `fetch-depth: 0`, then `git describe --tags --always`), so the version it prints depends on which tags exist **on the `github` remote**, not on your local clone or on `gitea`. + +This repo has two writable remotes — `gitea` (canonical, where commits land) and `github` (where the `:develop` and release images are built) — plus `upstream` (docmost, never push). **`git push ` does NOT push tags**; tags must be pushed explicitly and *to each remote separately*. A release tag that only lives on `gitea` is invisible to the GitHub Actions build: even with the tagged commit fully in `develop`'s history (step 7 done), `git describe` on the GitHub runner falls back to the previous tag it *does* have, so the develop image keeps showing e.g. `v0.91.0-NNN` while `git describe` locally already says `v0.93.0-NN`. + +Fix / checklist when develop still shows the old version after a back-merge: + +1. Confirm the tag is missing on github: `git ls-remote --tags github` (compare with `gitea`). +2. Push it there: `git push github vX.Y.Z` (and `git push gitea vX.Y.Z` if it is missing on gitea too). Note: pushing a `v*` tag to `github` also triggers `release.yml` (multi-arch GHCR images + draft Release) — expected, but be aware. +3. Re-run the develop build (`gh workflow run Develop`, or push any commit to `develop`) so `git describe` re-resolves with the tag now present. + +(The `git push origin ...` in steps 6–7 above is shorthand — there is no `origin` remote here; substitute `gitea` **and** `github` as appropriate, and always push release tags to both.) + +## Planning docs + +`docs/*.md` hold design plans for in-progress / planned features (mobile app, offline sync, RAG improvements, voice dictation). Arbitrary HTML embed has **shipped** — it renders inside a sandboxed iframe and, when the `htmlEmbed` workspace toggle is on, is insertable by any member (no longer admin-only); turning the toggle off hides/stops serving existing embeds on public share pages. `docs/backlog/*.md` track known issues / follow-ups (e.g. AI-chat review follow-ups). Consult the relevant plan before working on one of those areas. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..efb96a72 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,232 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +> Releases prior to `0.91.0` predate this changelog; see the +> [git tags](https://github.com/vvzvlad/gitmost/tags) for earlier history. + +## [Unreleased] + +### Added + +- **AI-agent attribution for MCP writes.** Comments (and pages) created through + the MCP endpoint by a dedicated agent account are now badged as "AI", with + unspoofable provenance derived from a per-user `is_agent` flag (not from the + request body). **Operator setup:** use a *dedicated* service account for the + MCP fallback and set the flag with SQL — + `UPDATE users SET is_agent = true WHERE email = ''`. Never flag a + human or shared account, or its normal edits get mis-attributed as AI. See the + AI-agent block in `.env.example`. (#143) + +### Changed + +- **Public share AI: default per-workspace hourly assistant cap lowered + 300 → 100.** The limiter falls back to this default whenever + `SHARE_AI_WORKSPACE_MAX_PER_HOUR` is unset, so a `0.93.0` deployment that + never set the env var has its anonymous public-share assistant hourly cap + cut from 300 to 100 on upgrade. Set `SHARE_AI_WORKSPACE_MAX_PER_HOUR` to + keep the previous limit. (#62) + +### Fixed + +- **Editor: caret/selection landed on the wrong line when clicking inside code + blocks and footnotes.** The affected NodeViews rendered their non-editable + chrome (language menu, footnotes heading, footnote number marker) before the + editable content, so the browser's click hit-testing missed the contentDOM and + snapped the caret to a previous node. Content now renders first in the DOM + (chrome is lifted back into place via CSS flex `order`), and scroll containers + are nudged after a paste to refresh stale hit-testing geometry. The caret + symptom is macOS-specific and was confirmed manually on macOS; the automated + guard pins the DOM-order invariant, not the caret behavior itself. (#146, #147) + +## [0.93.0] - 2026-06-21 + +This release builds on the 0.91.0 AI foundation: admin-defined AI agent roles, +an anonymous AI assistant on public shares, server-side voice dictation, an +editor footnotes model, live page-template embeds, and sandboxed arbitrary-HTML +embeds — plus a large batch of security hardening and test coverage. + +### Breaking Changes + +- **MCP shared-token auth moved to its own header.** The `/mcp` shared guard + no longer reads `Authorization: Bearer `; it now reads only the + `X-MCP-Token` header. The `Authorization` header is now reserved for per-user + HTTP Basic / Bearer access-JWT credentials, so each `/mcp` request + authenticates as a specific user (the `MCP_DOCMOST_*` service account is only + a fallback). Existing MCP clients (e.g. Claude Desktop) configured with + `Authorization: Bearer ` must be reconfigured to send + `X-MCP-Token: ` instead. See `MCP_TOKEN` in `.env.example`. As a + one-time aid, the server logs a single migration warning when it sees the + old-style header. + +### Added + +- **AI agent roles**: admin-defined assistant personas with an optional + per-role model override, selectable in chat. +- **Anonymous AI assistant on public shares**: public-share visitors can chat + with a selectable agent-role identity that reuses the internal chat + presentation, with per-request output-token caps and a fail-closed Redis + limiter. +- **Voice dictation (STT)**: server-side speech-to-text with a mic button in + the chat and the editor, OpenRouter STT support, an endpoint test, and real + provider-error surfacing. +- **Footnotes**: an editor footnotes model (inline references + a definitions + list). +- **Page templates**: live whole-page embed (MVP) with a template-marker icon + in the page tree and a working Refresh action. +- **Arbitrary HTML/CSS/JS embeds**: a sandboxed-iframe embed block gated by a + per-workspace toggle (default OFF); insertable by any member when the toggle + is on. +- Admin-only **"Analytics / tracker"** workspace setting: a raw HTML/JS snippet + injected into the `` of public share pages only (for analytics such as + Google Analytics or Yandex.Metrika), kept separate from the member-facing + HTML-embed feature. +- **MCP**: a hierarchical tree mode for `list_pages`, and per-user auth for the + embedded `/mcp` endpoint. +- **Page tree**: Expand all / Collapse all for the space tree, and + server-authoritative realtime tree updates. +- **AI chat UX**: a `get_current_page` tool for proxy-robust page context, a + current-context-size readout, an agent step cap raised 8→20 with a forced + final text answer, and auto-collapse of the chat window on page focus. +- **AI settings**: a Clear control inside the API-key field and an endpoint + status dot bound to "configured × enabled". +- **Client**: an always-visible space grid replacing the space-switcher popover, + removal of the sidebar Overview item, tighter comments-panel density, and no + auto-open of the comments panel when adding a comment. + +### Changed + +- HTML embed blocks now render inside a sandboxed iframe (separate origin) and, + when the workspace HTML-embed toggle is on, can be inserted by any member + (previously admin-only). Turning the toggle off hides existing embeds and + stops serving them on public share pages. +- Remove the server-side role-based stripping of HTML-embed blocks from the + write paths (collab/REST/MCP, page create/duplicate, import, transclusion + unsync); sandboxing makes per-write gating unnecessary. The only remaining + server-side strip is the public-share read path, which still honors the + workspace HTML-embed toggle. + +### Fixed + +- AI chat: preserve scroll position during streaming, record chats that fail on + their first turn, and resolve the current page for agent context behind + proxies. +- AI roles: guard `update()` against concurrent soft-delete; harden the model + override, role-name uniqueness, and id validation; sandwich the safety + framework around the role persona. +- Auth: handle null-password (SSO/LDAP-only) accounts without a bcrypt throw. +- Footnotes: survive duplicate-id definitions without collab divergence. +- HTML embed: fix stale iframe height and damp the resize loop; strip embeds at + serve time on authenticated read paths and the plain page-create path. +- Page templates: import `ThrottleModule` so collab boots, never strand an + in-flight page-embed id, and add defense-in-depth workspace checks. +- Pages: `movePage` cycle guard with no phantom `PAGE_MOVED` event. +- Import: surface the real error cause from `/pages/import` instead of a generic + 400. + +### Security + +- MCP: close an SSO/MFA bypass on Basic auth and stop minting non-init sessions; + close a brute-force limiter check-then-act race. +- Public share: block restricted descendants in the anonymous assistant, cap + per-request output, fail closed when Redis is unavailable, and reject non-text + message parts to close a size-cap bypass. +- Make `trustProxy` env-configurable with a safe default. + +### Internal + +- CI: gate the `develop` and release image builds on the test suite, run the + suites on push/PR, and build the `:develop` image on push to `develop`. +- Docs: replace `CLAUDE.md` with `AGENTS.md` codifying the agent workflow and + the release procedure, add migration-ordering guidance, and prune implemented + plans. +- A large batch of new server/client test coverage. + +## [0.91.0] - 2026-06-18 + +Gitmost is a community-focused fork of Docmost. This release drops the +Enterprise-Edition code paths and introduces the in-app AI agent chat, a RAG +knowledge layer, an embedded MCP server, and the Gitmost rebrand. + +### Breaking Changes + +- Remove all frontend Enterprise-Edition code — the project now builds as a pure + community edition. +- AI agent: drop the `updateComment` tool from the agent toolset. + +### Added + +- **AI agent chat**: per-user in-app AI agent with a floating chat window. + Includes live streaming responses, open-page context awareness, a typing + indicator, a Stop control, and copy/export of a conversation as Markdown. +- **AI agent write tools & provenance**: reversible write tools (page + create/update/move/soft-delete, comment reply/resolve) enforced by Docmost + CASL, plus non-spoofable agent provenance signed into access/collab tokens and + recorded on pages and comments. No permanent/force delete. +- **RAG knowledge retrieval**: workspace bulk reindex with a manual "Reindex + now" action, hybrid RRF retrieval with heading-breadcrumb chunks and a merged + search tool, dimension-agnostic embeddings, and RAG indexing coverage shown in + AI settings. +- **MCP**: embedded community MCP server served at `/mcp`; an admin UI to + list/add/edit/delete external MCP servers with per-server enable toggle, Test, + write-only auth headers, a tool allowlist, and a Tavily preset; `insert_image`/ + `replace_image` can now fetch sources from web URLs. +- **AI configuration**: dedicated AI provider settings with separate base URL and + API key for the chat vs. embedding model, and per-endpoint test buttons. +- **Branding**: Gitmost logo, favicon, and app name. +- **Collaboration**: comment resolution for the community build; agent edits are + separated from human edits in page history. +- **Editor / client**: page-tree open/closed state is persisted per + workspace+user; the brand logo shows the current `git describe` version. + +### Changed + +- Move AI settings to a dedicated `/settings/ai` page and redesign it with + per-endpoint test buttons. +- `edit_page_text` now returns verifiable mutation results and refuses + formatting-only edits; the agent tolerates Markdown in + `edit_page_text`/`insert_node` locators. +- Compact large tool outputs before persisting them. +- Reduce the chat window corner radius, shrink the chat message font size, and + shrink the default page-tree indentation from 16px to 8px. + +### Fixed + +- AI chat: stable streaming store id so optimistic and streamed messages render + immediately; provider errors stay visible and surface the real provider + status/message; the composer draft survives the new-chat id-adoption remount; + the workspace AI-chat enable toggle is restored for self-hosted. +- AI providers: use OpenAI Chat Completions for multi-turn requests; self-heal + the stored provider settings JSON; drop the hard output-token cap that + truncated complex tool calls. +- RAG: make the indexer observable and bound hung embedding calls; stop the + coverage bar from sticking below 100% on empty pages. +- Collaboration: use `-` instead of `:` in the agent page-history job id. +- Accessibility fixes (#2275) and table jitter on the edit/read toggle (#2252). + +### Removed + +- Non-functional DOCX / PDF / Confluence import buttons. + +### Documentation + +- README: rebrand to the Gitmost fork with EE-free positioning, an MCP + comparison, a grouped roadmap, a Russian translation, a "Migration from + Docmost" section, and AI agent chat documentation. +- Add plans for mobile app, voice dictation, arbitrary HTML/CSS/JS embeds, and + offline sync & PWA. + +### Internal + +- Add `.claude/worktrees/` to `.gitignore`. +- CI: add a `develop` workflow with `workflow_dispatch`; ignore cache errors in + the develop and release builds. +- Build: drop the private EE submodule, retarget CI to GHCR, and update the + Docker image to the GHCR registry. + +[Unreleased]: https://github.com/vvzvlad/gitmost/compare/v0.93.0...HEAD +[0.93.0]: https://github.com/vvzvlad/gitmost/compare/v0.91.0...v0.93.0 +[0.91.0]: https://github.com/vvzvlad/gitmost/compare/v0.90.1...v0.91.0 diff --git a/README.md b/README.md new file mode 100644 index 00000000..b63b76f5 --- /dev/null +++ b/README.md @@ -0,0 +1,225 @@ +
+

Gitmost

+

+ Open-source collaborative wiki and documentation software. +
+ A fully-open community fork of Docmost. +

+
+
+ +**English** · [Русский](README.ru.md) + +## About this fork + +**Gitmost** is a community fork of [Docmost](https://github.com/docmost/docmost), an open-source +collaborative wiki and documentation app. + +The goal of the fork is a **100% open, AGPL-only build with no Enterprise-Edition (EE) code**: + +- **No EE code at all.** All proprietary Enterprise-Edition sources were removed — the private + `apps/server/src/ee` submodule, the `apps/client/src/ee` directory (201 files) and the + `packages/ee` package are gone. There is no license gating: every feature is available to everyone. +- **Replacements are written from scratch.** Features that previously lived behind the enterprise + license (e.g. comment resolution, the AI agent chat, the `/mcp` server) were re-implemented from + scratch on top of the community codebase. No EE code is reused, and there is no + entitlement/feature-flag wall. +- **No upsell.** There are no "buy a license" / "upgrade to Enterprise" banners, trial nags, or + locked-feature placeholders anywhere in the UI. +- Authentication is plain email + password (no SSO/LDAP/cloud/billing flows). + +## What's different from Docmost + +| Change | Details | +| --- | --- | +| **EE code removed** | Stripped all client and server Enterprise-Edition code; ships as a clean community/AGPL build with no license checks. | +| **Comment resolution** | Re-implemented from scratch as a community feature (resolve / re-open with Open/Resolved tabs). No EE code reused, available to anyone who can comment. | +| **Embedded MCP server** | A community MCP server (`@docmost/mcp`, 38 tools) is served over HTTP at `/mcp` — no enterprise license required. Replaces the removed license-gated EE MCP. | +| **AI agent chat** | Built-in AI agent chat over your wiki, written from scratch as a community feature — no enterprise license. The agent reads and edits pages on your behalf (scoped to your permissions), with full-text + vector (RAG) search and optional web access via external MCP servers. | +| **Rebranding** | App logo / name changed from *Docmost* to *Gitmost*. | +| **Compact page tree** | Default page-tree indentation reduced from 16px to 8px per nesting level. | +| **Persistent page-tree state** | The sidebar page tree remembers which nodes you expanded/collapsed across reloads — saved in the browser (localStorage), scoped per workspace + user so accounts sharing a browser don't clash. Upstream Docmost forgets the tree on every reload. | +| **CI / images** | Release CI publishes container images to GHCR (`ghcr.io/vvzvlad/gitmost`) using the built-in `GITHUB_TOKEN` instead of Docker Hub. | + +### Embedded MCP server + +Gitmost has **our own MCP server** — [docmost-mcp](https://github.com/vvzvlad/docmost-mcp), +which we wrote — **built directly into the app** and served at `/mcp`. It exposes **38 +agent-native tools**: surgical per-block edits (patch / insert / delete by id), +structure-preserving find/replace, scripted `(doc) => doc` transforms with a dry-run diff, +structured table editing, version history with diff / restore, comments, images and share +links — all applied through Docmost's real-time-collaboration layer, so a write never +clobbers a concurrent human edit. + +**Better than Docmost's own MCP.** Docmost's built-in MCP is an enterprise feature, and its +tools are coarse — read a page as Markdown, create / move / delete pages, replace a whole +page. Ours is built around how an agent actually edits: address one block and patch it, or +*program* the change, instead of round-tripping a ~100 KB document through the model on +every little fix. And it needs no enterprise license. + +| | **Gitmost `/mcp` (our docmost-mcp)** | Docmost's built-in MCP | +| --- | :---: | :---: | +| **Enterprise license** | Not required | Required | +| **Tools** | 38, agent-native | Coarse (read Markdown, page CRUD, replace whole page) | +| **Per-block edits / find-replace / scripted transforms** | ✅ | — | +| **Structured table editing, version diff / restore** | ✅ | — | +| **Comments, images, share links** | ✅ | — | +| **Safe real-time-collab writes (no clobber)** | ✅ | — | + +**Same server as standalone docmost-mcp — just bundled.** This is the exact +[docmost-mcp](https://github.com/vvzvlad/docmost-mcp) you can also run on its own; embedding +it doesn't make it more capable, you simply don't have to install and run a separate +process. An admin flips one toggle in **Workspace settings → AI** and any MCP client +points at `${APP_URL}/mcp`. + +### AI agent chat + +Gitmost ships a **built-in AI agent chat** over your wiki — written from scratch as a +community feature, with no enterprise license. Open it from the page header; the agent can +**read and edit** your workspace on your behalf: + +- **Full read + write toolset (~40 tools).** Search and read pages, make surgical per-block + and table edits, create / rename / move pages, diff and restore page history, and create / + resolve comments — every action runs under *your* permissions (Docmost CASL), so the agent + can never see or change anything you couldn't. +- **Safe by design.** The agent is given only **reversible** operations (page history + + trash); permanent deletion is never exposed. Agent edits are marked in page history with an + "AI agent" badge linking back to the chat. +- **Search over your content.** Full-text search plus optional vector (RAG) semantic search + across pages. +- **Web access via external MCP.** Admins can connect external MCP servers (e.g. Tavily) to + give the agent web search / internet access. +- **Bring your own model.** Configure an OpenAI-compatible endpoint — OpenAI, OpenRouter, a + local Ollama, or any self-hosted server — plus the model and API key in + **Workspace settings → AI**. The key is encrypted and never leaves the server. + +## Roadmap + +### Done + +- ✅ **MCP server** — embedded community MCP server served at `/mcp`. +- ✅ **macOS app** — native macOS app ([gitmost-app](https://github.com/vvzvlad/gitmost-app)) that embeds the UI with multi-server tabs. +- ✅ **AI chat** — built-in AI agent chat over your wiki content (read + write, RAG search, configurable provider, optional web access via external MCP). +- ✅ **Voice dictation** — microphone button in the AI agent chat and the page editor; audio is transcribed server-side (Whisper / OpenAI-compatible STT) via the workspace AI provider, with an admin toggle to show/hide it. +- ✅ **Page templates** — flag a page as a template and embed its whole content live into other pages; edits to the template propagate to every place it is inserted (whole-page transclusion on top of the existing synced blocks). +- ✅ **Public-share AI assistant** — anonymous visitors of a shared page can ask the AI agent, scoped strictly to that share's page tree (read-only, share-scoped search), behind a workspace toggle. +- ✅ **Footnotes** — academic-style footnotes: a numbered superscript reference inline (read it in place via a hover popover), with the note text living as a real, editable block at the bottom of the page; auto-numbered, collaboration-safe, and round-trips through Markdown export/import and the AI agent / MCP. + +### In progress + +- 🚧 **Git synchronization** — two-way sync of pages with a Git repository. + +### Planned + +- 🔭 **Viewer comments** — let read-only viewers leave comments. +- 🔭 **Password-protected pages** — protect individual pages / shares with a password. +- 🔭 **Windows / Linux app** — native desktop app for Windows and Linux. +- 🔭 **Mobile app** — mobile apps (iOS first, Android to follow), reusing the existing responsive web UI and editor via a Capacitor wrapper, with offline planned for later. See [docs/mobile-app-plan.md](docs/mobile-app-plan.md). +- 🔭 **Offline mode** — offline sync & PWA support. +- 🔭 **Editor & UX improvements** — blocks inside tables (lists, to-do items), column layout, additional heading levels, highlight blocks, custom emoji in callouts, floating images, anchor links for page mentions, toggles (shared-page width, aside/sidebar, spellcheck, ligatures), sanitized space-tree export, and mentions in breadcrumbs. + +## Getting started + +Gitmost follows the upstream Docmost setup. See the Docmost +[documentation](https://docmost.com/docs) for self-hosting and development instructions; replace the +`docmost/docmost` image with `ghcr.io/vvzvlad/gitmost` where applicable. + +## Migration from Docmost + +Gitmost's database schema is a **strict superset** of Docmost's. Every Gitmost-specific migration +only **adds** new tables (`page_embeddings`, `ai_chats`, `ai_chat_messages`, +`ai_provider_credentials`, `ai_mcp_servers`) and **nullable** columns — it never drops or rewrites +existing Docmost data. Migrations run automatically on startup, so migrating an existing Docmost +instance is essentially **two image swaps**. + +The only hard requirement is the database image: the AI agent's RAG storage needs the +[pgvector](https://github.com/pgvector/pgvector) extension (`CREATE EXTENSION vector`), which the +stock `postgres` image does not ship. Swap it for `pgvector/pgvector:pgNN` — the same vanilla +Postgres plus pgvector bundled, built on the official `postgres` image and fully data-compatible +with it. + +### From a current Docmost on Postgres 18 + +If your Docmost already runs `postgres:18`, it's a clean **in-place** swap — no dump/restore needed, +the existing data directory is reused as-is: + +```diff + services: + docmost: +- image: docmost/docmost:latest ++ image: ghcr.io/vvzvlad/gitmost:latest + ... + db: +- image: postgres:18 ++ image: pgvector/pgvector:pg18 +``` + +`APP_SECRET`, `DATABASE_URL`, `REDIS_URL` and the storage volume stay unchanged. On the first +start the new migrations apply on top of your existing schema (`CREATE EXTENSION vector` plus the +`page_embeddings` and AI tables); watch the logs for `Migration "..." executed successfully`. + +> ⚠️ **Never change `APP_SECRET` after setup.** It does double duty: it signs JWTs *and* derives the +> AES-256-GCM key that encrypts stored AI-provider credentials (API keys). Rotating it makes every +> saved AI API key undecryptable (you'd have to re-enter them in AI settings) and invalidates all +> existing sessions. Pick it once, keep it stable, and back it up together with your database. + +### Notes + +- **Back up first.** Take a `pg_dump` before swapping — migrations apply in place, and the + container exits if a migration fails. +- **Volume layout is identical.** `pgvector/pgvector` is built on the official `postgres` image and + uses the same `PGDATA`, so keep your existing data volume and its mount path unchanged — the swap + reuses the directory as-is. +- **Match the Postgres major.** A Postgres data directory is not compatible across major versions. + If your Docmost runs an older major (e.g. Postgres 16), use the matching `pgvector/pgvector:pg16` + to keep the in-place swap, or move the data with `pg_dump` / `pg_restore` into the new instance. +- **Managed Postgres.** If you don't use the bundled `db` container, make sure pgvector is available + and your database role is allowed to run `CREATE EXTENSION vector`. +- **AI is opt-in.** The `page_embeddings` table stays empty until you configure an AI provider; + existing pages are indexed on their next edit. pgvector is still required for the migration to + apply at all. + +## Features + +- Real-time collaboration +- Diagrams (Draw.io, Excalidraw and Mermaid) +- Spaces +- Permissions management +- Groups +- Comments (with resolve / re-open) +- Page history +- Search +- File attachments +- Embeds (Airtable, Loom, Miro and more) +- Translations (10+ languages) +- Embedded MCP server (`/mcp`) +- AI agent chat over your wiki (read + write, RAG search, external MCP / web access) + +### Screenshots + +

+AI agent chat +home +editor +

+ +### License + +Gitmost is licensed under the open-source AGPL 3.0 license. + +Unlike upstream Docmost, this fork contains **no Enterprise-Edition code** — the `apps/server/src/ee`, +`apps/client/src/ee` and `packages/ee` directories have been removed, so there are no files governed +by an enterprise license. + +### Credits + +Gitmost is based on [Docmost](https://github.com/docmost/docmost) by the Docmost team. Huge thanks to +them for the original open-source project. + +Crowdin + +[Crowdin](https://crowdin.com/) for providing access to their localization platform. + +Algolia-mark-square-white + +[Algolia](https://www.algolia.com/) for providing full-text search to the docs. diff --git a/README.ru.md b/README.ru.md new file mode 100644 index 00000000..cb0d12ad --- /dev/null +++ b/README.ru.md @@ -0,0 +1,212 @@ +
+

Gitmost

+

+ Совместная вики и система документации с открытым исходным кодом. +
+ Полностью открытый community-форк Docmost. +

+
+
+ +[English](README.md) · **Русский** + +## Об этом форке + +**Gitmost** — это community-форк [Docmost](https://github.com/docmost/docmost), +открытой системы для совместной работы над вики и документацией. + +Цель форка — **сборка на 100% открытая, только под AGPL, без кода Enterprise-редакции (EE)**: + +- **Никакого EE-кода.** Весь проприетарный код Enterprise-редакции удалён — приватный + сабмодуль `apps/server/src/ee`, каталог `apps/client/src/ee` (201 файл) и пакет + `packages/ee`. Никаких лицензионных ограничений: все функции доступны всем. +- **Замены написаны с нуля.** Функции, которые раньше были скрыты за enterprise-лицензией + (например, резолв комментариев, чат с AI-агентом, сервер `/mcp`), переписаны с нуля поверх + community-кодовой базы. EE-код не переиспользуется, нет проверок entitlement и feature-флагов. +- **Никакого навязывания.** В интерфейсе нигде нет плашек «купите лицензию» / «перейдите на + Enterprise», напоминаний о триале и заглушек на месте заблокированных функций. +- Аутентификация — обычная пара email + пароль (без SSO/LDAP/облака/биллинга). + +## Чем отличается от Docmost + +| Изменение | Подробности | +| --- | --- | +| **Удалён EE-код** | Вырезан весь код Enterprise-редакции на клиенте и сервере; это чистая community/AGPL-сборка без лицензионных проверок. | +| **Резолв комментариев** | Переписан с нуля как community-функция (резолв / переоткрытие с вкладками «Открытые» / «Решённые»). EE-код не используется, доступно любому, кто может комментировать. | +| **Встроенный MCP-сервер** | Community MCP-сервер (`@docmost/mcp`, 38 инструментов) отдаётся по HTTP на `/mcp` — без enterprise-лицензии. Заменяет удалённый лицензируемый EE MCP. | +| **Чат с AI-агентом** | Встроенный чат с AI-агентом по содержимому вики, написанный с нуля как community-функция — без enterprise-лицензии. Агент читает и редактирует страницы от вашего имени (в рамках ваших прав), с полнотекстовым + векторным (RAG) поиском и опциональным доступом в интернет через внешние MCP-серверы. | +| **Ребрендинг** | Логотип / название приложения изменены с *Docmost* на *Gitmost*. | +| **Компактное дерево страниц** | Отступ дерева страниц по умолчанию уменьшен с 16px до 8px на уровень вложенности. | +| **Сохранение состояния дерева страниц** | Дерево страниц в сайдбаре запоминает, какие узлы вы раскрыли/свернули, между перезагрузками — состояние хранится в браузере (localStorage) отдельно для каждой пары воркспейс + пользователь, чтобы аккаунты в одном браузере не пересекались. В оригинальном Docmost дерево сбрасывается при каждой перезагрузке. | +| **CI / образы** | Release-CI публикует контейнерные образы в GHCR (`ghcr.io/vvzvlad/gitmost`) через встроенный `GITHUB_TOKEN` вместо Docker Hub. | + +### Встроенный MCP-сервер + +В Gitmost есть **наш собственный MCP-сервер** — [docmost-mcp](https://github.com/vvzvlad/docmost-mcp), +который мы написали сами, — **встроенный прямо в приложение** и доступный на `/mcp`. Он даёт +**38 agent-native инструментов**: точечное редактирование по блокам (patch / insert / delete +по id), find/replace с сохранением структуры, скриптовые трансформации `(doc) => doc` с +предпросмотром диффа, структурное редактирование таблиц, история версий с диффом / +восстановлением, комментарии, изображения и ссылки на шаринг — всё применяется через слой +real-time-коллаборации Docmost, поэтому запись никогда не затирает параллельную правку +человека. + +**Лучше, чем родной MCP у Docmost.** Встроенный MCP у Docmost — enterprise-функция, и его +инструменты примитивные: прочитать страницу как Markdown, создать / переместить / удалить +страницу, заменить страницу целиком. Наш сделан под то, как агент реально редактирует: +адресовать один блок и пропатчить его или *запрограммировать* изменение, а не гонять +документ на ~100 КБ через модель ради каждой мелкой правки. И enterprise-лицензия не нужна. + +| | **`/mcp` в Gitmost (наш docmost-mcp)** | Родной MCP у Docmost | +| --- | :---: | :---: | +| **Enterprise-лицензия** | Не нужна | Нужна | +| **Инструменты** | 38, agent-native | Примитивные (Markdown, CRUD страниц, замена целиком) | +| **Правки по блокам / find-replace / скриптовые трансформации** | ✅ | — | +| **Структурное редактирование таблиц, дифф / восстановление версий** | ✅ | — | +| **Комментарии, изображения, ссылки на шаринг** | ✅ | — | +| **Безопасная запись через real-time-коллаборацию (без затирания)** | ✅ | — | + +**Это тот же сервер, что и отдельный docmost-mcp, — просто встроенный.** Это ровно тот самый +[docmost-mcp](https://github.com/vvzvlad/docmost-mcp), который можно запускать и отдельно; +от встраивания он не становится «мощнее» — просто не нужно ставить и держать отдельный +процесс. Админ включает его одним переключателем в **Настройки воркспейса → AI**, а +любой MCP-клиент указывает на `${APP_URL}/mcp`. + +### Чат с AI-агентом + +В Gitmost есть **встроенный чат с AI-агентом** по содержимому вики — написанный с нуля +как community-функция, без enterprise-лицензии. Открывается из шапки страницы; агент умеет +**читать и редактировать** ваш воркспейс от вашего имени: + +- **Полный набор инструментов чтения + записи (~40 штук).** Поиск и чтение страниц, + точечные правки по блокам и в таблицах, создание / переименование / перемещение страниц, + дифф и восстановление версий, создание / резолв комментариев — каждое действие + выполняется под *вашими* правами (Docmost CASL), поэтому агент не видит и не меняет + ничего, чего не могли бы вы сами. +- **Безопасность по умолчанию.** Агенту доступны только **обратимые** операции (история + версий + корзина); перманентное удаление не экспонируется. Правки агента помечаются в + истории версий бейджем «AI-агент» со ссылкой на чат. +- **Поиск по вашему контенту.** Полнотекстовый поиск плюс опциональный векторный (RAG) + семантический поиск по страницам. +- **Доступ в интернет через внешние MCP.** Админ может подключить внешние MCP-серверы + (например, Tavily), чтобы дать агенту веб-поиск / доступ в интернет. +- **Своя модель.** OpenAI-совместимый эндпоинт — OpenAI, OpenRouter, локальный Ollama или + любой self-hosted-сервер — плюс модель и API-ключ настраиваются в + **Настройки воркспейса → AI**. Ключ шифруется и никогда не покидает сервер. + +## Дорожная карта + +### Готово + +- ✅ **MCP-сервер** — встроенный community MCP-сервер на `/mcp`. +- ✅ **Приложение для macOS** — нативное приложение для macOS ([gitmost-app](https://github.com/vvzvlad/gitmost-app)), встраивающее UI с вкладками для нескольких серверов. +- ✅ **AI-чат** — встроенный чат с AI-агентом по содержимому вики (чтение + запись, RAG-поиск, настраиваемый провайдер, опциональный доступ в интернет через внешние MCP). +- ✅ **Голосовая диктовка** — кнопка-микрофон в чате AI-агента и в редакторе страниц; аудио распознаётся на сервере (Whisper / OpenAI-совместимый STT) через AI-провайдер воркспейса, с тумблером админа для показа/скрытия. +- ✅ **Шаблоны страниц** — пометить страницу шаблоном и вставлять её содержимое живой ссылкой в другие страницы; правки шаблона распространяются на все места вставки (whole-page-транслюзия поверх существующих synced-блоков). +- ✅ **AI-ассистент на публичных шарах** — анонимный зритель расшаренной страницы может спросить AI-агента, который ищет строго по дереву этой шары (read-only, share-scoped поиск), за тумблером воркспейса. +- ✅ **Сноски** — сноски академического вида: нумерованная ссылка-надстрочник прямо в тексте (читается на месте во всплывающем окне по наведению), а текст сноски живёт реальным редактируемым блоком внизу страницы; авто-нумерация, безопасна для совместного редактирования, переживает экспорт/импорт Markdown и доступна AI-агенту / MCP. + +### В процессе + +- 🚧 **Синхронизация с Git** — двусторонняя синхронизация страниц с Git-репозиторием. + +### В планах + +- 🔭 **Комментарии зрителей** — возможность комментировать для пользователей с доступом только на чтение. +- 🔭 **Защищённые паролем страницы** — защита отдельных страниц / шар паролем. +- 🔭 **Приложение для Windows / Linux** — нативное десктоп-приложение для Windows и Linux. +- 🔭 **Мобильное приложение** — мобильные приложения (iOS обязательно, Android как пойдёт) на базе существующей адаптивной веб-версии и редактора через обёртку Capacitor; оффлайн запланирован на будущее. См. [docs/mobile-app-plan.md](docs/mobile-app-plan.md). +- 🔭 **Офлайн-режим** — офлайн-синхронизация и поддержка PWA. +- 🔭 **Улучшения редактора и UX** — блоки внутри таблиц (списки, чек-листы), колоночная вёрстка, дополнительные уровни заголовков, highlight-блоки, кастомные эмодзи в callout-ах, плавающие изображения, anchor-ссылки на упоминания страниц, тоглы (ширина шары, aside/сайдбар, spellcheck, лигатуры), санитизация экспорта дерева спейса и mentions в хлебных крошках. + +## С чего начать + +Gitmost повторяет процесс установки upstream-Docmost. Инструкции по self-hosting и разработке +смотрите в [документации](https://docmost.com/docs) Docmost; где это применимо, заменяйте образ +`docmost/docmost` на `ghcr.io/vvzvlad/gitmost`. + +## Миграция с Docmost + +Схема БД Gitmost — это **строгий superset** схемы Docmost. Все Gitmost-специфичные миграции только +**добавляют** новые таблицы (`page_embeddings`, `ai_chats`, `ai_chat_messages`, +`ai_provider_credentials`, `ai_mcp_servers`) и **nullable**-колонки — они никогда не удаляют и не +переписывают существующие данные Docmost. Миграции применяются автоматически при старте, поэтому +миграция существующего инстанса Docmost — это по сути **замена двух образов**. + +Единственное жёсткое требование — образ БД: RAG-хранилище AI-агента использует расширение +[pgvector](https://github.com/pgvector/pgvector) (`CREATE EXTENSION vector`), которого нет в +стоковом образе `postgres`. Замените его на `pgvector/pgvector:pgNN` — это тот же ванильный +Postgres со встроенным pgvector, собранный на базе официального образа `postgres` и полностью +data-совместимый с ним. + +### С текущего Docmost на Postgres 18 + +Если ваш Docmost уже работает на `postgres:18`, это чистая замена in-place — без +dump/restore, существующий каталог данных переиспользуется как есть: + +```diff + services: + docmost: +- image: docmost/docmost:latest ++ image: ghcr.io/vvzvlad/gitmost:latest + ... + db: +- image: postgres:18 ++ image: pgvector/pgvector:pg18 +``` + +`APP_SECRET`, `DATABASE_URL`, `REDIS_URL` и том сторейджа остаются прежними. При первом запуске +новые миграции применяются поверх вашей схемы (`CREATE EXTENSION vector` плюс таблицы +`page_embeddings` и AI-таблицы); следите в логах за строками `Migration "..." executed successfully`. + +> ⚠️ **Никогда не меняйте `APP_SECRET` после установки.** Он выполняет двойную роль: подписывает JWT +> *и* служит материалом для ключа AES-256-GCM, которым шифруются сохранённые ключи AI-провайдеров +> (API-ключи). Смена секрета сделает все сохранённые AI-ключи нерасшифровываемыми (придётся вводить +> их заново в настройках AI) и инвалидирует все текущие сессии. Задайте его один раз, держите +> неизменным и бэкапьте вместе с базой данных. + + +## Возможности + +- Совместная работа в реальном времени +- Диаграммы (Draw.io, Excalidraw и Mermaid) +- Пространства (Spaces) +- Управление правами доступа +- Группы +- Комментарии (с резолвом / переоткрытием) +- История страниц +- Поиск +- Вложения файлов +- Встраивания (Airtable, Loom, Miro и другие) +- Переводы (10+ языков) +- Встроенный MCP-сервер (`/mcp`) +- Чат с AI-агентом по вики (чтение + запись, RAG-поиск, внешние MCP / доступ в интернет) + +### Скриншоты + +

+Чат с AI-агентом +home +editor +

+ +### Лицензия + +Gitmost распространяется под открытой лицензией AGPL 3.0. + +В отличие от upstream-Docmost, этот форк **не содержит кода Enterprise-редакции** — каталоги +`apps/server/src/ee`, `apps/client/src/ee` и `packages/ee` удалены, поэтому файлов под +enterprise-лицензией здесь нет. + +### Благодарности + +Gitmost основан на [Docmost](https://github.com/docmost/docmost) от команды Docmost. Огромное +спасибо им за оригинальный открытый проект. + +Crowdin + +[Crowdin](https://crowdin.com/) — за доступ к их платформе локализации. + +Algolia-mark-square-white + +[Algolia](https://www.algolia.com/) — за полнотекстовый поиск по документации.