diff --git a/KNOWLEDGE_API.md b/KNOWLEDGE_API.md
new file mode 100644
index 0000000..1ec7159
--- /dev/null
+++ b/KNOWLEDGE_API.md
@@ -0,0 +1,1559 @@
+# Knowledge Module - API Documentation
+
+Complete REST API reference for the Knowledge Module endpoints.
+
+## Table of Contents
+
+1. [Authentication](#authentication)
+2. [Entry Endpoints](#entry-endpoints)
+3. [Search Endpoints](#search-endpoints)
+4. [Tag Endpoints](#tag-endpoints)
+5. [Import/Export Endpoints](#importexport-endpoints)
+6. [Stats Endpoints](#stats-endpoints)
+7. [Cache Endpoints](#cache-endpoints)
+8. [Error Responses](#error-responses)
+
+---
+
+## Authentication
+
+All Knowledge Module endpoints require authentication via Bearer token and workspace context.
+
+### Headers
+
+```http
+Authorization: Bearer {session_token}
+x-workspace-id: {workspace_uuid}
+```
+
+### Permission Levels
+
+- **WORKSPACE_ANY**: Any workspace member (including GUEST)
+- **WORKSPACE_MEMBER**: MEMBER role or higher
+- **WORKSPACE_ADMIN**: ADMIN or OWNER role
+
+---
+
+## Entry Endpoints
+
+### List Entries
+
+Get a paginated list of knowledge entries.
+
+```http
+GET /api/knowledge/entries
+```
+
+**Query Parameters:**
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `page` | integer | 1 | Page number |
+| `limit` | integer | 20 | Results per page (max: 100) |
+| `status` | string | - | Filter by status (DRAFT, PUBLISHED, ARCHIVED) |
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/entries?page=1&limit=20&status=PUBLISHED' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "data": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "slug": "react-hooks-guide",
+ "title": "React Hooks Guide",
+ "content": "# React Hooks Guide\n\nComprehensive guide...",
+ "contentHtml": "
React Hooks Guide
Comprehensive guide...
",
+ "summary": "Learn about React Hooks",
+ "status": "PUBLISHED",
+ "visibility": "WORKSPACE",
+ "createdAt": "2024-01-29T10:00:00Z",
+ "updatedAt": "2024-01-30T15:30:00Z",
+ "createdBy": "user-uuid",
+ "updatedBy": "user-uuid",
+ "tags": [
+ {
+ "id": "tag-uuid",
+ "name": "React",
+ "slug": "react",
+ "color": "#61dafb"
+ }
+ ]
+ }
+ ],
+ "pagination": {
+ "page": 1,
+ "limit": 20,
+ "total": 45,
+ "totalPages": 3
+ }
+}
+```
+
+---
+
+### Get Entry
+
+Retrieve a single knowledge entry by slug.
+
+```http
+GET /api/knowledge/entries/:slug
+```
+
+**Path Parameters:**
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `slug` | string | Entry slug (e.g., "react-hooks-guide") |
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/entries/react-hooks-guide' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "slug": "react-hooks-guide",
+ "title": "React Hooks Guide",
+ "content": "# React Hooks Guide\n\nUse [[useState]] and [[useEffect]]...",
+ "contentHtml": "React Hooks Guide
Use useState...
",
+ "summary": "Learn about React Hooks",
+ "status": "PUBLISHED",
+ "visibility": "WORKSPACE",
+ "createdAt": "2024-01-29T10:00:00Z",
+ "updatedAt": "2024-01-30T15:30:00Z",
+ "createdBy": "user-uuid",
+ "updatedBy": "user-uuid",
+ "tags": [
+ {
+ "id": "tag-uuid",
+ "name": "React",
+ "slug": "react",
+ "color": "#61dafb"
+ }
+ ]
+}
+```
+
+---
+
+### Create Entry
+
+Create a new knowledge entry.
+
+```http
+POST /api/knowledge/entries
+```
+
+**Permissions:** WORKSPACE_MEMBER
+
+**Request Body:**
+
+```json
+{
+ "title": "React Hooks Guide",
+ "content": "# React Hooks Guide\n\nContent here...",
+ "summary": "Learn about React Hooks",
+ "status": "DRAFT",
+ "visibility": "WORKSPACE",
+ "tags": ["react", "frontend"],
+ "changeNote": "Initial draft"
+}
+```
+
+**Body Schema:**
+
+| Field | Type | Required | Constraints |
+|-------|------|----------|-------------|
+| `title` | string | Yes | 1-500 characters |
+| `content` | string | Yes | Min 1 character |
+| `summary` | string | No | Max 1000 characters |
+| `status` | enum | No | DRAFT, PUBLISHED, ARCHIVED (default: DRAFT) |
+| `visibility` | enum | No | PRIVATE, WORKSPACE, PUBLIC (default: PRIVATE) |
+| `tags` | string[] | No | Array of tag slugs |
+| `changeNote` | string | No | Max 500 characters |
+
+**Example Request:**
+
+```bash
+curl -X POST 'http://localhost:3001/api/knowledge/entries' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "title": "React Hooks Guide",
+ "content": "# React Hooks Guide\n\nUse [[useState]] for state...",
+ "summary": "Learn about React Hooks",
+ "status": "DRAFT",
+ "tags": ["react", "frontend"],
+ "changeNote": "Initial draft"
+ }'
+```
+
+**Response:**
+
+```json
+{
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "slug": "react-hooks-guide",
+ "title": "React Hooks Guide",
+ "content": "# React Hooks Guide\n\nUse [[useState]] for state...",
+ "contentHtml": "React Hooks Guide
Use useState...
",
+ "summary": "Learn about React Hooks",
+ "status": "DRAFT",
+ "visibility": "WORKSPACE",
+ "createdAt": "2024-01-30T10:00:00Z",
+ "updatedAt": "2024-01-30T10:00:00Z",
+ "createdBy": "user-uuid",
+ "updatedBy": "user-uuid",
+ "tags": [
+ {
+ "id": "tag-uuid",
+ "name": "React",
+ "slug": "react",
+ "color": "#61dafb"
+ }
+ ]
+}
+```
+
+**Notes:**
+- Slug is auto-generated from title
+- If tags don't exist, they are created automatically
+- Wiki-links in content are automatically parsed and stored
+- First version (version 1) is created automatically
+
+---
+
+### Update Entry
+
+Update an existing knowledge entry.
+
+```http
+PUT /api/knowledge/entries/:slug
+```
+
+**Path Parameters:**
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `slug` | string | Entry slug |
+
+**Permissions:** WORKSPACE_MEMBER
+
+**Request Body:**
+
+```json
+{
+ "title": "React Hooks Guide (Updated)",
+ "content": "# React Hooks Guide\n\nUpdated content...",
+ "summary": "Updated summary",
+ "status": "PUBLISHED",
+ "visibility": "WORKSPACE",
+ "tags": ["react", "frontend", "tutorial"],
+ "changeNote": "Added examples and updated title"
+}
+```
+
+All fields are optional. Only provided fields are updated.
+
+**Example Request:**
+
+```bash
+curl -X PUT 'http://localhost:3001/api/knowledge/entries/react-hooks-guide' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "status": "PUBLISHED",
+ "changeNote": "Ready for publication"
+ }'
+```
+
+**Response:**
+
+```json
+{
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "slug": "react-hooks-guide",
+ "title": "React Hooks Guide (Updated)",
+ "content": "# React Hooks Guide\n\nUpdated content...",
+ "contentHtml": "React Hooks Guide
Updated...
",
+ "summary": "Updated summary",
+ "status": "PUBLISHED",
+ "visibility": "WORKSPACE",
+ "createdAt": "2024-01-30T10:00:00Z",
+ "updatedAt": "2024-01-30T12:00:00Z",
+ "createdBy": "user-uuid",
+ "updatedBy": "user-uuid",
+ "tags": [...]
+}
+```
+
+**Notes:**
+- A new version is created on every update
+- Wiki-links are re-parsed and synchronized
+- Cache is invalidated automatically
+
+---
+
+### Delete Entry
+
+Soft-delete (archive) a knowledge entry.
+
+```http
+DELETE /api/knowledge/entries/:slug
+```
+
+**Path Parameters:**
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `slug` | string | Entry slug |
+
+**Permissions:** WORKSPACE_ADMIN
+
+**Example Request:**
+
+```bash
+curl -X DELETE 'http://localhost:3001/api/knowledge/entries/react-hooks-guide' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "message": "Entry archived successfully"
+}
+```
+
+**Notes:**
+- This is a **soft delete** (sets status to ARCHIVED)
+- Entry remains in database with all versions
+- Links to this entry become unresolved
+- Can be restored by updating status back to DRAFT or PUBLISHED
+
+---
+
+### Get Backlinks
+
+Get all entries that link to this entry.
+
+```http
+GET /api/knowledge/entries/:slug/backlinks
+```
+
+**Path Parameters:**
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `slug` | string | Entry slug |
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/entries/react-hooks/backlinks' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "entry": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "slug": "react-hooks",
+ "title": "React Hooks"
+ },
+ "backlinks": [
+ {
+ "id": "link-uuid-1",
+ "sourceId": "entry-uuid-1",
+ "source": {
+ "id": "entry-uuid-1",
+ "slug": "frontend-guide",
+ "title": "Frontend Development Guide",
+ "summary": "Complete frontend guide"
+ },
+ "linkText": "React Hooks",
+ "context": "Learn about [[React Hooks]] for state management.",
+ "createdAt": "2024-01-29T10:00:00Z"
+ },
+ {
+ "id": "link-uuid-2",
+ "sourceId": "entry-uuid-2",
+ "source": {
+ "id": "entry-uuid-2",
+ "slug": "component-patterns",
+ "title": "React Component Patterns",
+ "summary": null
+ },
+ "linkText": "hooks",
+ "context": "Modern [[hooks|React hooks]] make state simple.",
+ "createdAt": "2024-01-30T08:00:00Z"
+ }
+ ],
+ "count": 2
+}
+```
+
+**Notes:**
+- Only resolved links are included
+- Context shows surrounding text (future feature)
+- Sorted by creation date (newest first)
+
+---
+
+### List Versions
+
+Get version history for an entry.
+
+```http
+GET /api/knowledge/entries/:slug/versions
+```
+
+**Path Parameters:**
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `slug` | string | Entry slug |
+
+**Query Parameters:**
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `page` | integer | 1 | Page number |
+| `limit` | integer | 20 | Results per page (max: 100) |
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/entries/react-hooks-guide/versions?page=1&limit=10' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "data": [
+ {
+ "id": "version-uuid-3",
+ "version": 3,
+ "title": "React Hooks Guide",
+ "summary": "Learn about React Hooks",
+ "changeNote": "Added examples",
+ "createdAt": "2024-01-30T15:00:00Z",
+ "createdBy": "user-uuid",
+ "author": {
+ "id": "user-uuid",
+ "name": "John Doe",
+ "email": "john@example.com"
+ }
+ },
+ {
+ "id": "version-uuid-2",
+ "version": 2,
+ "title": "React Hooks Guide",
+ "summary": "Learn about React Hooks",
+ "changeNote": "Fixed typos",
+ "createdAt": "2024-01-30T12:00:00Z",
+ "createdBy": "user-uuid",
+ "author": { ... }
+ },
+ {
+ "id": "version-uuid-1",
+ "version": 1,
+ "title": "React Hooks Guide",
+ "summary": null,
+ "changeNote": "Initial draft",
+ "createdAt": "2024-01-30T10:00:00Z",
+ "createdBy": "user-uuid",
+ "author": { ... }
+ }
+ ],
+ "pagination": {
+ "page": 1,
+ "limit": 10,
+ "total": 3,
+ "totalPages": 1
+ }
+}
+```
+
+**Notes:**
+- Versions sorted newest first
+- Content is NOT included (use Get Version endpoint for full content)
+- First version has `changeNote` from creation
+
+---
+
+### Get Version
+
+Get complete content for a specific version.
+
+```http
+GET /api/knowledge/entries/:slug/versions/:version
+```
+
+**Path Parameters:**
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `slug` | string | Entry slug |
+| `version` | integer | Version number |
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/entries/react-hooks-guide/versions/2' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "id": "version-uuid-2",
+ "entryId": "entry-uuid",
+ "version": 2,
+ "title": "React Hooks Guide",
+ "content": "# React Hooks Guide\n\nOld content...",
+ "summary": "Learn about React Hooks",
+ "changeNote": "Fixed typos",
+ "createdAt": "2024-01-30T12:00:00Z",
+ "createdBy": "user-uuid",
+ "author": {
+ "id": "user-uuid",
+ "name": "John Doe",
+ "email": "john@example.com"
+ }
+}
+```
+
+**Notes:**
+- Includes full content as it existed in that version
+- Use this to preview before restoring
+
+---
+
+### Restore Version
+
+Restore an entry to a previous version.
+
+```http
+POST /api/knowledge/entries/:slug/restore/:version
+```
+
+**Path Parameters:**
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `slug` | string | Entry slug |
+| `version` | integer | Version number to restore |
+
+**Permissions:** WORKSPACE_MEMBER
+
+**Request Body:**
+
+```json
+{
+ "changeNote": "Restored version 2 - reverted bad changes"
+}
+```
+
+| Field | Type | Required | Constraints |
+|-------|------|----------|-------------|
+| `changeNote` | string | Yes | Max 500 characters |
+
+**Example Request:**
+
+```bash
+curl -X POST 'http://localhost:3001/api/knowledge/entries/react-hooks-guide/restore/2' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "changeNote": "Restored version 2 - reverted bad changes"
+ }'
+```
+
+**Response:**
+
+```json
+{
+ "id": "entry-uuid",
+ "slug": "react-hooks-guide",
+ "title": "React Hooks Guide",
+ "content": "# React Hooks Guide\n\nRestored content...",
+ "summary": "Learn about React Hooks",
+ "status": "PUBLISHED",
+ "visibility": "WORKSPACE",
+ "createdAt": "2024-01-30T10:00:00Z",
+ "updatedAt": "2024-01-30T16:00:00Z",
+ "createdBy": "user-uuid",
+ "updatedBy": "current-user-uuid",
+ "tags": [...]
+}
+```
+
+**Notes:**
+- Creates a **new version** (e.g., version 4) with content from the specified version
+- Original versions remain untouched (no history rewriting)
+- Change note is required to document why you restored
+- Wiki-links are re-parsed from the restored content
+
+---
+
+## Search Endpoints
+
+### Full-Text Search
+
+Search across entry titles and content.
+
+```http
+GET /api/knowledge/search
+```
+
+**Query Parameters:**
+
+| Parameter | Type | Required | Default | Description |
+|-----------|------|----------|---------|-------------|
+| `q` | string | Yes | - | Search query |
+| `status` | enum | No | - | DRAFT, PUBLISHED, ARCHIVED |
+| `page` | integer | No | 1 | Page number |
+| `limit` | integer | No | 20 | Results per page (max: 100) |
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/search?q=react+hooks&page=1&limit=10' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "data": [
+ {
+ "id": "entry-uuid",
+ "slug": "react-hooks-guide",
+ "title": "React Hooks Guide",
+ "content": "# React Hooks Guide\n\nContent...",
+ "summary": "Learn about React Hooks",
+ "status": "PUBLISHED",
+ "visibility": "WORKSPACE",
+ "createdAt": "2024-01-30T10:00:00Z",
+ "updatedAt": "2024-01-30T12:00:00Z",
+ "createdBy": "user-uuid",
+ "updatedBy": "user-uuid",
+ "tags": [...]
+ }
+ ],
+ "pagination": {
+ "page": 1,
+ "limit": 10,
+ "total": 5,
+ "totalPages": 1
+ }
+}
+```
+
+**Search behavior:**
+- Searches both `title` and `content` fields
+- Case-insensitive
+- Partial word matching
+- Relevance-ranked results
+
+---
+
+### Search by Tags
+
+Find entries with specific tags.
+
+```http
+GET /api/knowledge/search/by-tags
+```
+
+**Query Parameters:**
+
+| Parameter | Type | Required | Default | Description |
+|-----------|------|----------|---------|-------------|
+| `tags` | string | Yes | - | Comma-separated tag slugs |
+| `status` | enum | No | - | DRAFT, PUBLISHED, ARCHIVED |
+| `page` | integer | No | 1 | Page number |
+| `limit` | integer | No | 20 | Results per page (max: 100) |
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/search/by-tags?tags=react,frontend&status=PUBLISHED' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+Same format as Full-Text Search response.
+
+**Notes:**
+- Finds entries with **ALL** specified tags (AND logic)
+- For OR logic, make separate requests
+
+---
+
+### Recent Entries
+
+Get recently modified entries.
+
+```http
+GET /api/knowledge/search/recent
+```
+
+**Query Parameters:**
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `limit` | integer | 10 | Number of entries (max: 50) |
+| `status` | enum | - | DRAFT, PUBLISHED, ARCHIVED |
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/search/recent?limit=5&status=PUBLISHED' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "data": [
+ {
+ "id": "entry-uuid",
+ "slug": "react-hooks-guide",
+ "title": "React Hooks Guide",
+ "summary": "Learn about React Hooks",
+ "status": "PUBLISHED",
+ "updatedAt": "2024-01-30T16:00:00Z",
+ "tags": [...]
+ }
+ ],
+ "count": 5
+}
+```
+
+**Notes:**
+- Sorted by `updatedAt` descending (newest first)
+- Does not include full content (use Get Entry for details)
+
+---
+
+## Tag Endpoints
+
+### List Tags
+
+Get all tags in the workspace.
+
+```http
+GET /api/knowledge/tags
+```
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/tags' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+[
+ {
+ "id": "tag-uuid-1",
+ "name": "React",
+ "slug": "react",
+ "color": "#61dafb",
+ "description": "React library and ecosystem"
+ },
+ {
+ "id": "tag-uuid-2",
+ "name": "Frontend",
+ "slug": "frontend",
+ "color": "#3b82f6",
+ "description": null
+ }
+]
+```
+
+---
+
+### Get Tag
+
+Get a single tag by slug.
+
+```http
+GET /api/knowledge/tags/:slug
+```
+
+**Path Parameters:**
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `slug` | string | Tag slug |
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/tags/react' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "id": "tag-uuid",
+ "name": "React",
+ "slug": "react",
+ "color": "#61dafb",
+ "description": "React library and ecosystem"
+}
+```
+
+---
+
+### Create Tag
+
+Create a new tag.
+
+```http
+POST /api/knowledge/tags
+```
+
+**Permissions:** WORKSPACE_MEMBER
+
+**Request Body:**
+
+```json
+{
+ "name": "TypeScript",
+ "color": "#3178c6",
+ "description": "TypeScript language and tooling"
+}
+```
+
+| Field | Type | Required | Constraints |
+|-------|------|----------|-------------|
+| `name` | string | Yes | Unique per workspace |
+| `color` | string | No | Hex color (e.g., "#3178c6") |
+| `description` | string | No | - |
+
+**Example Request:**
+
+```bash
+curl -X POST 'http://localhost:3001/api/knowledge/tags' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "TypeScript",
+ "color": "#3178c6"
+ }'
+```
+
+**Response:**
+
+```json
+{
+ "id": "new-tag-uuid",
+ "name": "TypeScript",
+ "slug": "typescript",
+ "color": "#3178c6",
+ "description": null
+}
+```
+
+**Notes:**
+- Slug is auto-generated from name
+- Tags are workspace-scoped (same slug can exist in different workspaces)
+
+---
+
+### Update Tag
+
+Update an existing tag.
+
+```http
+PUT /api/knowledge/tags/:slug
+```
+
+**Path Parameters:**
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `slug` | string | Tag slug |
+
+**Permissions:** WORKSPACE_MEMBER
+
+**Request Body:**
+
+All fields are optional. Only provided fields are updated.
+
+```json
+{
+ "name": "TypeScript (Updated)",
+ "color": "#2f74c0",
+ "description": "TypeScript programming language"
+}
+```
+
+**Example Request:**
+
+```bash
+curl -X PUT 'http://localhost:3001/api/knowledge/tags/typescript' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "color": "#2f74c0"
+ }'
+```
+
+**Response:**
+
+```json
+{
+ "id": "tag-uuid",
+ "name": "TypeScript (Updated)",
+ "slug": "typescript",
+ "color": "#2f74c0",
+ "description": "TypeScript programming language"
+}
+```
+
+---
+
+### Delete Tag
+
+Delete a tag (removes from all entries).
+
+```http
+DELETE /api/knowledge/tags/:slug
+```
+
+**Path Parameters:**
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `slug` | string | Tag slug |
+
+**Permissions:** WORKSPACE_ADMIN
+
+**Example Request:**
+
+```bash
+curl -X DELETE 'http://localhost:3001/api/knowledge/tags/typescript' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```
+204 No Content
+```
+
+**Notes:**
+- Tag is removed from all entries that used it
+- Entries themselves are NOT deleted
+
+---
+
+### Get Tag Entries
+
+Get all entries with a specific tag.
+
+```http
+GET /api/knowledge/tags/:slug/entries
+```
+
+**Path Parameters:**
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `slug` | string | Tag slug |
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/tags/react/entries' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+[
+ {
+ "id": "entry-uuid-1",
+ "slug": "react-hooks-guide",
+ "title": "React Hooks Guide",
+ "summary": "Learn about React Hooks",
+ "status": "PUBLISHED",
+ "createdAt": "2024-01-30T10:00:00Z",
+ "updatedAt": "2024-01-30T12:00:00Z"
+ },
+ {
+ "id": "entry-uuid-2",
+ "slug": "component-patterns",
+ "title": "React Component Patterns",
+ "summary": null,
+ "status": "PUBLISHED",
+ "createdAt": "2024-01-29T08:00:00Z",
+ "updatedAt": "2024-01-29T09:00:00Z"
+ }
+]
+```
+
+---
+
+## Import/Export Endpoints
+
+### Export Entries
+
+Export knowledge entries as a downloadable archive.
+
+```http
+GET /api/knowledge/export
+```
+
+**Query Parameters:**
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `format` | enum | markdown | Export format: `markdown` or `json` |
+| `entryIds` | string[] | - | Optional array of entry IDs to export |
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Requests:**
+
+Export all entries as Markdown:
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/export?format=markdown' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID" \
+ -o knowledge-export.zip
+```
+
+Export specific entries as JSON:
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/export?format=json&entryIds[]=uuid1&entryIds[]=uuid2' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID" \
+ -o knowledge-export.zip
+```
+
+**Response:**
+
+Binary `.zip` file with headers:
+```
+Content-Type: application/zip
+Content-Disposition: attachment; filename="knowledge-export-YYYYMMDD-HHMMSS.zip"
+```
+
+**Markdown format structure:**
+
+```
+knowledge-export.zip
+├── react-hooks-guide.md
+├── component-patterns.md
+└── state-management.md
+```
+
+Each `.md` file:
+```markdown
+---
+slug: react-hooks-guide
+title: React Hooks Guide
+status: PUBLISHED
+visibility: WORKSPACE
+tags: react, frontend
+createdAt: 2024-01-30T10:00:00Z
+updatedAt: 2024-01-30T12:00:00Z
+---
+
+# React Hooks Guide
+
+Content with [[wiki-links]]...
+```
+
+**JSON format structure:**
+
+```
+knowledge-export.zip
+└── entries.json
+```
+
+`entries.json`:
+```json
+[
+ {
+ "slug": "react-hooks-guide",
+ "title": "React Hooks Guide",
+ "content": "# React Hooks Guide\n\nContent...",
+ "summary": "Learn about React Hooks",
+ "status": "PUBLISHED",
+ "visibility": "WORKSPACE",
+ "tags": ["react", "frontend"],
+ "createdAt": "2024-01-30T10:00:00Z",
+ "updatedAt": "2024-01-30T12:00:00Z"
+ }
+]
+```
+
+---
+
+### Import Entries
+
+Import knowledge entries from uploaded file.
+
+```http
+POST /api/knowledge/import
+```
+
+**Permissions:** WORKSPACE_MEMBER
+
+**Request:**
+
+Multipart form data with file upload.
+
+```bash
+curl -X POST 'http://localhost:3001/api/knowledge/import' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID" \
+ -F "file=@knowledge-export.zip"
+```
+
+**Supported file types:**
+- `.md` (single Markdown file)
+- `.zip` (archive of Markdown files)
+
+**File size limit:** 50MB
+
+**Response:**
+
+```json
+{
+ "success": true,
+ "totalFiles": 10,
+ "imported": 8,
+ "failed": 2,
+ "results": [
+ {
+ "filename": "react-hooks.md",
+ "success": true,
+ "entryId": "new-entry-uuid",
+ "slug": "react-hooks"
+ },
+ {
+ "filename": "invalid-entry.md",
+ "success": false,
+ "error": "Title is required"
+ }
+ ]
+}
+```
+
+**Import behavior:**
+- New entries created with status DRAFT
+- Existing entries (matching slug) are skipped
+- Tags created automatically if they don't exist
+- Wiki-links preserved (will resolve if targets exist)
+
+**Front matter parsing:**
+
+The importer reads YAML front matter:
+```markdown
+---
+slug: custom-slug
+title: Entry Title
+summary: Optional summary
+status: PUBLISHED
+visibility: WORKSPACE
+tags: tag1, tag2
+---
+
+Content starts here...
+```
+
+If no front matter, slug is generated from filename.
+
+---
+
+## Stats Endpoints
+
+### Get Statistics
+
+Get knowledge base statistics for the workspace.
+
+```http
+GET /api/knowledge/stats
+```
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/stats' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "totalEntries": 42,
+ "entriesByStatus": {
+ "DRAFT": 5,
+ "PUBLISHED": 35,
+ "ARCHIVED": 2
+ },
+ "totalTags": 12,
+ "totalLinks": 87,
+ "totalVersions": 215,
+ "averageVersionsPerEntry": 5.1,
+ "topTags": [
+ {
+ "id": "tag-uuid",
+ "name": "React",
+ "slug": "react",
+ "count": 15
+ },
+ {
+ "id": "tag-uuid",
+ "name": "Frontend",
+ "slug": "frontend",
+ "count": 12
+ }
+ ],
+ "mostLinkedEntries": [
+ {
+ "id": "entry-uuid",
+ "slug": "react-hooks",
+ "title": "React Hooks",
+ "incomingLinkCount": 8
+ }
+ ],
+ "recentActivity": {
+ "last24h": 5,
+ "last7days": 18,
+ "last30days": 42
+ }
+}
+```
+
+---
+
+## Cache Endpoints
+
+### Get Cache Stats
+
+Get cache performance statistics.
+
+```http
+GET /api/knowledge/cache/stats
+```
+
+**Permissions:** WORKSPACE_ANY
+
+**Example Request:**
+
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/cache/stats' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "enabled": true,
+ "stats": {
+ "hits": 1250,
+ "misses": 180,
+ "sets": 195,
+ "deletes": 15,
+ "hitRate": 0.874
+ }
+}
+```
+
+**Stats explanation:**
+- `hits`: Cache hits (data found in cache)
+- `misses`: Cache misses (data not in cache, fetched from DB)
+- `sets`: Cache writes
+- `deletes`: Cache invalidations
+- `hitRate`: Percentage of requests served from cache (hits / (hits + misses))
+
+---
+
+### Clear Cache
+
+Clear all cached data for the workspace.
+
+```http
+POST /api/knowledge/cache/clear
+```
+
+**Permissions:** WORKSPACE_ADMIN
+
+**Example Request:**
+
+```bash
+curl -X POST 'http://localhost:3001/api/knowledge/cache/clear' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "message": "Cache cleared successfully"
+}
+```
+
+**Notes:**
+- Clears ALL knowledge caches for the workspace
+- Includes entry caches, search caches, and graph caches
+- Does not affect other workspaces
+
+---
+
+### Reset Cache Stats
+
+Reset cache statistics counters to zero.
+
+```http
+POST /api/knowledge/cache/stats/reset
+```
+
+**Permissions:** WORKSPACE_ADMIN
+
+**Example Request:**
+
+```bash
+curl -X POST 'http://localhost:3001/api/knowledge/cache/stats/reset' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Response:**
+
+```json
+{
+ "message": "Cache statistics reset successfully"
+}
+```
+
+---
+
+## Error Responses
+
+All endpoints follow standard HTTP error codes and return JSON error objects.
+
+### Error Format
+
+```json
+{
+ "statusCode": 404,
+ "message": "Entry not found",
+ "error": "Not Found"
+}
+```
+
+### Common Status Codes
+
+| Code | Meaning | Example |
+|------|---------|---------|
+| `400` | Bad Request | Invalid request body, missing required field |
+| `401` | Unauthorized | Missing or invalid auth token |
+| `403` | Forbidden | Insufficient permissions for action |
+| `404` | Not Found | Entry, tag, or version doesn't exist |
+| `409` | Conflict | Slug already exists, duplicate entry |
+| `413` | Payload Too Large | Import file exceeds 50MB limit |
+| `422` | Unprocessable Entity | Validation failed (e.g., invalid enum value) |
+| `500` | Internal Server Error | Unexpected server error |
+
+### Validation Errors
+
+When validation fails, you get detailed error information:
+
+```json
+{
+ "statusCode": 422,
+ "message": [
+ "title must not be empty",
+ "title must not exceed 500 characters",
+ "status must be a valid EntryStatus"
+ ],
+ "error": "Unprocessable Entity"
+}
+```
+
+---
+
+## JavaScript Examples
+
+### Using Fetch API
+
+```javascript
+const API_URL = 'http://localhost:3001/api';
+const token = 'YOUR_SESSION_TOKEN';
+const workspaceId = 'YOUR_WORKSPACE_ID';
+
+const headers = {
+ 'Authorization': `Bearer ${token}`,
+ 'x-workspace-id': workspaceId,
+ 'Content-Type': 'application/json'
+};
+
+// Create entry
+async function createEntry() {
+ const response = await fetch(`${API_URL}/knowledge/entries`, {
+ method: 'POST',
+ headers,
+ body: JSON.stringify({
+ title: 'My New Entry',
+ content: '# Hello World\n\nThis is my first [[wiki-link]]!',
+ tags: ['tutorial'],
+ status: 'DRAFT'
+ })
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed: ${response.status}`);
+ }
+
+ return await response.json();
+}
+
+// Search entries
+async function searchEntries(query) {
+ const params = new URLSearchParams({ q: query, limit: 10 });
+ const response = await fetch(`${API_URL}/knowledge/search?${params}`, {
+ headers
+ });
+
+ return await response.json();
+}
+
+// Export entries
+async function exportEntries() {
+ const response = await fetch(`${API_URL}/knowledge/export?format=markdown`, {
+ headers
+ });
+
+ const blob = await response.blob();
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'knowledge-export.zip';
+ a.click();
+}
+```
+
+### Using Axios
+
+```javascript
+import axios from 'axios';
+
+const api = axios.create({
+ baseURL: 'http://localhost:3001/api',
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'x-workspace-id': workspaceId
+ }
+});
+
+// Get entry
+const entry = await api.get('/knowledge/entries/react-hooks');
+console.log(entry.data);
+
+// Update entry
+const updated = await api.put('/knowledge/entries/react-hooks', {
+ status: 'PUBLISHED',
+ changeNote: 'Ready for publication'
+});
+
+// Import file
+const formData = new FormData();
+formData.append('file', fileInput.files[0]);
+const result = await api.post('/knowledge/import', formData, {
+ headers: { 'Content-Type': 'multipart/form-data' }
+});
+```
+
+---
+
+## Next Steps
+
+- **[User Guide](KNOWLEDGE_USER_GUIDE.md)** — Learn how to use the Knowledge Module
+- **[Developer Guide](KNOWLEDGE_DEV.md)** — Architecture and implementation details
+- **[Main README](README.md)** — Complete Mosaic Stack documentation
+
+---
+
+**Happy building! 🚀**
diff --git a/KNOWLEDGE_DEV.md b/KNOWLEDGE_DEV.md
new file mode 100644
index 0000000..1c3fe5d
--- /dev/null
+++ b/KNOWLEDGE_DEV.md
@@ -0,0 +1,1240 @@
+# Knowledge Module - Developer Guide
+
+Comprehensive developer documentation for the Knowledge Module implementation, architecture, and contribution guidelines.
+
+## Table of Contents
+
+1. [Architecture Overview](#architecture-overview)
+2. [Database Schema](#database-schema)
+3. [Service Layer](#service-layer)
+4. [Caching Strategy](#caching-strategy)
+5. [Wiki-Link System](#wiki-link-system)
+6. [Testing Guide](#testing-guide)
+7. [Contributing](#contributing)
+
+---
+
+## Architecture Overview
+
+The Knowledge Module follows a **layered architecture** pattern:
+
+```
+┌─────────────────────────────────────────┐
+│ Controllers (REST) │
+│ knowledge | search | tags | import │
+└────────────────┬────────────────────────┘
+ │
+┌────────────────▼────────────────────────┐
+│ Service Layer │
+│ KnowledgeService | SearchService │
+│ LinkSyncService | GraphService │
+│ TagsService | ImportExportService │
+│ StatsService | CacheService │
+└────────────────┬────────────────────────┘
+ │
+┌────────────────▼────────────────────────┐
+│ Data Access (Prisma ORM) │
+│ KnowledgeEntry | KnowledgeLink │
+│ KnowledgeTag | KnowledgeEntryVersion │
+│ KnowledgeEmbedding │
+└────────────────┬────────────────────────┘
+ │
+┌────────────────▼────────────────────────┐
+│ PostgreSQL 17 + pgvector │
+└─────────────────────────────────────────┘
+```
+
+### Module Structure
+
+```
+apps/api/src/knowledge/
+├── controllers/
+│ ├── knowledge.controller.ts # Entry CRUD endpoints
+│ ├── search.controller.ts # Search endpoints
+│ ├── tags.controller.ts # Tag management
+│ ├── import-export.controller.ts # Import/export
+│ └── stats.controller.ts # Statistics
+├── services/
+│ ├── cache.service.ts # Valkey caching
+│ ├── graph.service.ts # Graph traversal
+│ ├── import-export.service.ts # File import/export
+│ ├── link-resolution.service.ts # Link resolution
+│ ├── link-sync.service.ts # Link synchronization
+│ ├── search.service.ts # Full-text search
+│ └── stats.service.ts # Statistics aggregation
+├── entities/
+│ ├── knowledge-entry.entity.ts # Entry DTOs
+│ ├── knowledge-entry-version.entity.ts
+│ ├── graph.entity.ts # Graph DTOs
+│ └── stats.entity.ts # Stats DTOs
+├── dto/
+│ ├── create-entry.dto.ts
+│ ├── update-entry.dto.ts
+│ ├── entry-query.dto.ts
+│ ├── search-query.dto.ts
+│ └── ...
+├── utils/
+│ ├── wiki-link-parser.ts # Wiki-link parsing
+│ └── markdown.ts # Markdown rendering
+├── knowledge.service.ts # Core entry service
+├── tags.service.ts # Tag service
+└── knowledge.module.ts # NestJS module
+```
+
+### Key Responsibilities
+
+**Controllers**
+- HTTP request/response handling
+- Input validation (DTOs)
+- Permission enforcement (guards)
+- Error handling
+
+**Services**
+- Business logic
+- Data transformation
+- Transaction management
+- Cache invalidation
+
+**Repositories (Prisma)**
+- Database queries
+- Relation loading
+- Type-safe data access
+
+---
+
+## Database Schema
+
+### Core Models
+
+#### KnowledgeEntry
+
+Main entity for knowledge base entries.
+
+```prisma
+model KnowledgeEntry {
+ id String @id @default(uuid()) @db.Uuid
+ workspaceId String @map("workspace_id") @db.Uuid
+ workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
+
+ // Identity
+ slug String // URL-friendly identifier
+ title String // Display name
+
+ // Content
+ content String @db.Text // Raw markdown
+ contentHtml String? @map("content_html") @db.Text // Rendered HTML
+ summary String? // Optional brief description
+
+ // Status
+ status EntryStatus @default(DRAFT) // DRAFT | PUBLISHED | ARCHIVED
+ visibility Visibility @default(PRIVATE) // PRIVATE | WORKSPACE | PUBLIC
+
+ // Audit
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
+ updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
+ createdBy String @map("created_by") @db.Uuid
+ updatedBy String @map("updated_by") @db.Uuid
+
+ // Relations
+ tags KnowledgeEntryTag[]
+ outgoingLinks KnowledgeLink[] @relation("SourceEntry")
+ incomingLinks KnowledgeLink[] @relation("TargetEntry")
+ versions KnowledgeEntryVersion[]
+ embedding KnowledgeEmbedding?
+
+ @@unique([workspaceId, slug])
+ @@index([workspaceId, status])
+ @@index([workspaceId, updatedAt])
+ @@map("knowledge_entries")
+}
+```
+
+**Indexes:**
+- `workspaceId, slug` (unique constraint)
+- `workspaceId, status` (filtering)
+- `workspaceId, updatedAt` (recent entries)
+
+#### KnowledgeLink
+
+Represents wiki-links between entries.
+
+```prisma
+model KnowledgeLink {
+ id String @id @default(uuid()) @db.Uuid
+
+ sourceId String @map("source_id") @db.Uuid
+ source KnowledgeEntry @relation("SourceEntry", fields: [sourceId], references: [id], onDelete: Cascade)
+
+ targetId String @map("target_id") @db.Uuid
+ target KnowledgeEntry @relation("TargetEntry", fields: [targetId], references: [id], onDelete: Cascade)
+
+ // Link metadata
+ linkText String @map("link_text") // Original link text from markdown
+ context String? // Surrounding text (future feature)
+
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
+
+ @@unique([sourceId, targetId])
+ @@index([sourceId])
+ @@index([targetId])
+ @@map("knowledge_links")
+}
+```
+
+**Unique constraint:**
+- `sourceId, targetId` (prevents duplicate links)
+
+**Indexes:**
+- `sourceId` (outgoing links lookup)
+- `targetId` (backlinks lookup)
+
+#### KnowledgeEntryVersion
+
+Version history for entries.
+
+```prisma
+model KnowledgeEntryVersion {
+ id String @id @default(uuid()) @db.Uuid
+ entryId String @map("entry_id") @db.Uuid
+ entry KnowledgeEntry @relation(fields: [entryId], references: [id], onDelete: Cascade)
+
+ version Int // Version number (1, 2, 3, ...)
+ title String
+ content String @db.Text
+ summary String?
+
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
+ createdBy String @map("created_by") @db.Uuid
+ author User @relation("EntryVersionAuthor", fields: [createdBy], references: [id])
+ changeNote String? @map("change_note") // Optional change description
+
+ @@unique([entryId, version])
+ @@index([entryId, version])
+ @@map("knowledge_entry_versions")
+}
+```
+
+**Versioning strategy:**
+- Auto-incrementing version numbers
+- Immutable history (no updates or deletes)
+- Snapshot of title, content, summary at time of save
+
+#### KnowledgeTag
+
+Tags for categorization.
+
+```prisma
+model KnowledgeTag {
+ id String @id @default(uuid()) @db.Uuid
+ workspaceId String @map("workspace_id") @db.Uuid
+ workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
+
+ name String // Display name
+ slug String // URL-friendly identifier
+ color String? // Hex color (e.g., "#3b82f6")
+ description String?
+
+ entries KnowledgeEntryTag[]
+
+ @@unique([workspaceId, slug])
+ @@index([workspaceId])
+ @@map("knowledge_tags")
+}
+```
+
+#### KnowledgeEntryTag
+
+Many-to-many junction table for entries and tags.
+
+```prisma
+model KnowledgeEntryTag {
+ entryId String @map("entry_id") @db.Uuid
+ entry KnowledgeEntry @relation(fields: [entryId], references: [id], onDelete: Cascade)
+
+ tagId String @map("tag_id") @db.Uuid
+ tag KnowledgeTag @relation(fields: [tagId], references: [id], onDelete: Cascade)
+
+ @@id([entryId, tagId])
+ @@index([entryId])
+ @@index([tagId])
+ @@map("knowledge_entry_tags")
+}
+```
+
+#### KnowledgeEmbedding
+
+Semantic search embeddings (future feature).
+
+```prisma
+model KnowledgeEmbedding {
+ id String @id @default(uuid()) @db.Uuid
+ entryId String @unique @map("entry_id") @db.Uuid
+ entry KnowledgeEntry @relation(fields: [entryId], references: [id], onDelete: Cascade)
+
+ embedding Unsupported("vector(1536)") // pgvector type
+ model String // Model used (e.g., "text-embedding-ada-002")
+
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
+ updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
+
+ @@index([entryId])
+ @@map("knowledge_embeddings")
+}
+```
+
+---
+
+## Service Layer
+
+### KnowledgeService
+
+Core service for entry management.
+
+**Key methods:**
+
+```typescript
+class KnowledgeService {
+ // CRUD operations
+ async findAll(workspaceId, query): Promise
+ async findOne(workspaceId, slug): Promise
+ async create(workspaceId, userId, dto): Promise
+ async update(workspaceId, slug, userId, dto): Promise
+ async remove(workspaceId, slug, userId): Promise
+
+ // Version management
+ async findVersions(workspaceId, slug, page, limit): Promise
+ async findVersion(workspaceId, slug, version): Promise
+ async restoreVersion(workspaceId, slug, version, userId, changeNote): Promise
+}
+```
+
+**Create flow:**
+
+```typescript
+async create(workspaceId, userId, dto) {
+ // 1. Generate slug from title
+ const slug = this.generateUniqueSlug(dto.title, workspaceId);
+
+ // 2. Render markdown to HTML
+ const contentHtml = renderMarkdown(dto.content);
+
+ // 3. Create entry in transaction
+ const entry = await this.prisma.$transaction(async (tx) => {
+ // Create entry
+ const newEntry = await tx.knowledgeEntry.create({
+ data: {
+ workspaceId,
+ slug,
+ title: dto.title,
+ content: dto.content,
+ contentHtml,
+ summary: dto.summary,
+ status: dto.status || 'DRAFT',
+ visibility: dto.visibility || 'PRIVATE',
+ createdBy: userId,
+ updatedBy: userId,
+ },
+ });
+
+ // Create initial version (v1)
+ await tx.knowledgeEntryVersion.create({
+ data: {
+ entryId: newEntry.id,
+ version: 1,
+ title: newEntry.title,
+ content: newEntry.content,
+ summary: newEntry.summary,
+ createdBy: userId,
+ changeNote: dto.changeNote || 'Initial version',
+ },
+ });
+
+ // Handle tags (create or link)
+ if (dto.tags && dto.tags.length > 0) {
+ await this.linkTags(tx, newEntry.id, workspaceId, dto.tags);
+ }
+
+ return newEntry;
+ });
+
+ // 4. Parse and sync wiki-links (outside transaction)
+ await this.linkSync.syncLinks(entry.id, dto.content);
+
+ // 5. Invalidate caches
+ await this.cache.invalidateSearchCaches(workspaceId);
+
+ // 6. Return with tags
+ return this.findOne(workspaceId, slug);
+}
+```
+
+**Update flow:**
+
+```typescript
+async update(workspaceId, slug, userId, dto) {
+ // 1. Find existing entry
+ const existing = await this.findOne(workspaceId, slug);
+
+ // 2. Get next version number
+ const latestVersion = await this.getLatestVersion(existing.id);
+ const nextVersion = latestVersion.version + 1;
+
+ // 3. Render new HTML if content changed
+ const contentHtml = dto.content
+ ? renderMarkdown(dto.content)
+ : existing.contentHtml;
+
+ // 4. Update in transaction
+ const updated = await this.prisma.$transaction(async (tx) => {
+ // Update entry
+ const updatedEntry = await tx.knowledgeEntry.update({
+ where: { id: existing.id },
+ data: {
+ title: dto.title ?? existing.title,
+ content: dto.content ?? existing.content,
+ contentHtml,
+ summary: dto.summary ?? existing.summary,
+ status: dto.status ?? existing.status,
+ visibility: dto.visibility ?? existing.visibility,
+ updatedBy: userId,
+ },
+ });
+
+ // Create version snapshot
+ await tx.knowledgeEntryVersion.create({
+ data: {
+ entryId: updatedEntry.id,
+ version: nextVersion,
+ title: updatedEntry.title,
+ content: updatedEntry.content,
+ summary: updatedEntry.summary,
+ createdBy: userId,
+ changeNote: dto.changeNote || `Update to version ${nextVersion}`,
+ },
+ });
+
+ // Update tags if provided
+ if (dto.tags !== undefined) {
+ await this.replaceTags(tx, updatedEntry.id, workspaceId, dto.tags);
+ }
+
+ return updatedEntry;
+ });
+
+ // 5. Re-sync links if content changed
+ if (dto.content) {
+ await this.linkSync.syncLinks(updated.id, dto.content);
+ }
+
+ // 6. Invalidate caches
+ await this.cache.invalidateEntry(workspaceId, slug);
+ await this.cache.invalidateSearchCaches(workspaceId);
+ await this.cache.invalidateGraphCachesForEntry(updated.id);
+
+ // 7. Return updated entry
+ return this.findOne(workspaceId, slug);
+}
+```
+
+### LinkSyncService
+
+Manages wiki-link parsing and synchronization.
+
+**Key methods:**
+
+```typescript
+class LinkSyncService {
+ async syncLinks(entryId: string, content: string): Promise
+ async getBacklinks(entryId: string): Promise
+}
+```
+
+**Link sync flow:**
+
+```typescript
+async syncLinks(entryId, content) {
+ // 1. Parse wiki-links from content
+ const parsedLinks = parseWikiLinks(content);
+
+ // 2. Get source entry details
+ const entry = await this.prisma.knowledgeEntry.findUnique({
+ where: { id: entryId },
+ select: { workspaceId: true },
+ });
+
+ // 3. Delete existing links from this entry
+ await this.prisma.knowledgeLink.deleteMany({
+ where: { sourceId: entryId },
+ });
+
+ // 4. For each parsed link:
+ for (const link of parsedLinks) {
+ // Try to resolve target entry
+ const target = await this.linkResolver.resolve(
+ link.target,
+ entry.workspaceId
+ );
+
+ if (target) {
+ // Create resolved link
+ await this.prisma.knowledgeLink.create({
+ data: {
+ sourceId: entryId,
+ targetId: target.id,
+ linkText: link.displayText,
+ },
+ });
+ }
+ // Note: Unresolved links are simply not created
+ // They may resolve later when target entry is created
+ }
+
+ // 5. Invalidate graph caches
+ await this.cache.invalidateGraphCachesForEntry(entryId);
+}
+```
+
+### LinkResolutionService
+
+Resolves wiki-link targets to actual entries.
+
+**Resolution strategy:**
+
+```typescript
+async resolve(target: string, workspaceId: string) {
+ // Strategy 1: Match by exact slug
+ let entry = await this.prisma.knowledgeEntry.findUnique({
+ where: {
+ workspaceId_slug: { workspaceId, slug: target },
+ },
+ });
+
+ if (entry) return entry;
+
+ // Strategy 2: Generate slug from target and try again
+ const slugified = slugify(target, { lower: true, strict: true });
+ entry = await this.prisma.knowledgeEntry.findUnique({
+ where: {
+ workspaceId_slug: { workspaceId, slug: slugified },
+ },
+ });
+
+ if (entry) return entry;
+
+ // Strategy 3: Match by title (case-insensitive)
+ entry = await this.prisma.knowledgeEntry.findFirst({
+ where: {
+ workspaceId,
+ title: {
+ equals: target,
+ mode: 'insensitive',
+ },
+ },
+ });
+
+ return entry || null;
+}
+```
+
+**Resolution examples:**
+
+| Link Target | Resolution |
+|-------------|------------|
+| `react-hooks` | Exact slug match |
+| `React Hooks` | Slugify to `react-hooks`, then match |
+| `REACT HOOKS` | Case-insensitive title match → `React Hooks` |
+
+### SearchService
+
+Full-text search and filtering.
+
+**Key methods:**
+
+```typescript
+class SearchService {
+ async search(query, workspaceId, options): Promise
+ async searchByTags(tags, workspaceId, options): Promise
+ async recentEntries(workspaceId, limit, status?): Promise
+}
+```
+
+**Search implementation:**
+
+```typescript
+async search(query, workspaceId, options) {
+ // Check cache first
+ const cached = await this.cache.getSearch(workspaceId, query, options);
+ if (cached) return cached;
+
+ // Build where clause
+ const where: Prisma.KnowledgeEntryWhereInput = {
+ workspaceId,
+ OR: [
+ { title: { contains: query, mode: 'insensitive' } },
+ { content: { contains: query, mode: 'insensitive' } },
+ ],
+ };
+
+ if (options.status) {
+ where.status = options.status;
+ }
+
+ // Execute search with pagination
+ const [entries, total] = await Promise.all([
+ this.prisma.knowledgeEntry.findMany({
+ where,
+ include: { tags: { include: { tag: true } } },
+ orderBy: { updatedAt: 'desc' },
+ skip: (options.page - 1) * options.limit,
+ take: options.limit,
+ }),
+ this.prisma.knowledgeEntry.count({ where }),
+ ]);
+
+ const result = {
+ data: entries,
+ pagination: {
+ page: options.page,
+ limit: options.limit,
+ total,
+ totalPages: Math.ceil(total / options.limit),
+ },
+ };
+
+ // Cache the result
+ await this.cache.setSearch(workspaceId, query, options, result);
+
+ return result;
+}
+```
+
+### GraphService
+
+Knowledge graph traversal.
+
+**Key methods:**
+
+```typescript
+class GraphService {
+ async getEntryGraph(
+ workspaceId: string,
+ entryId: string,
+ maxDepth: number = 1
+ ): Promise
+}
+```
+
+**BFS graph traversal:**
+
+```typescript
+async getEntryGraph(workspaceId, entryId, maxDepth) {
+ // Check cache
+ const cached = await this.cache.getGraph(workspaceId, entryId, maxDepth);
+ if (cached) return cached;
+
+ const nodes: GraphNode[] = [];
+ const edges: GraphEdge[] = [];
+ const visited = new Set();
+ const queue: Array<[string, number]> = [[entryId, 0]];
+
+ visited.add(entryId);
+
+ while (queue.length > 0) {
+ const [currentId, depth] = queue.shift()!;
+
+ // Fetch entry with links
+ const entry = await this.prisma.knowledgeEntry.findUnique({
+ where: { id: currentId },
+ include: {
+ tags: { include: { tag: true } },
+ outgoingLinks: { include: { target: true } },
+ incomingLinks: { include: { source: true } },
+ },
+ });
+
+ if (!entry) continue;
+
+ // Add node
+ nodes.push({
+ id: entry.id,
+ slug: entry.slug,
+ title: entry.title,
+ summary: entry.summary,
+ tags: entry.tags.map(et => ({
+ id: et.tag.id,
+ name: et.tag.name,
+ slug: et.tag.slug,
+ color: et.tag.color,
+ })),
+ depth,
+ });
+
+ // Continue BFS if not at max depth
+ if (depth < maxDepth) {
+ // Process outgoing links
+ for (const link of entry.outgoingLinks) {
+ edges.push({
+ id: link.id,
+ sourceId: link.sourceId,
+ targetId: link.targetId,
+ linkText: link.linkText,
+ });
+
+ if (!visited.has(link.targetId)) {
+ visited.add(link.targetId);
+ queue.push([link.targetId, depth + 1]);
+ }
+ }
+
+ // Process incoming links
+ for (const link of entry.incomingLinks) {
+ const edgeExists = edges.some(
+ e => e.sourceId === link.sourceId && e.targetId === link.targetId
+ );
+ if (!edgeExists) {
+ edges.push({
+ id: link.id,
+ sourceId: link.sourceId,
+ targetId: link.targetId,
+ linkText: link.linkText,
+ });
+ }
+
+ if (!visited.has(link.sourceId)) {
+ visited.add(link.sourceId);
+ queue.push([link.sourceId, depth + 1]);
+ }
+ }
+ }
+ }
+
+ const result = {
+ centerNode: nodes.find(n => n.id === entryId)!,
+ nodes,
+ edges,
+ stats: {
+ totalNodes: nodes.length,
+ totalEdges: edges.length,
+ maxDepth,
+ },
+ };
+
+ // Cache result
+ await this.cache.setGraph(workspaceId, entryId, maxDepth, result);
+
+ return result;
+}
+```
+
+---
+
+## Caching Strategy
+
+The Knowledge Module uses **Valkey** (Redis-compatible) for high-performance caching.
+
+### Cache Keys
+
+```
+knowledge:entry:{workspaceId}:{slug}
+knowledge:search:{workspaceId}:{query}:{options_hash}
+knowledge:search-tags:{workspaceId}:{tags}:{options_hash}
+knowledge:graph:{workspaceId}:{entryId}:{depth}
+```
+
+### Cache Configuration
+
+```typescript
+class KnowledgeCacheService {
+ private readonly ENTRY_PREFIX = 'knowledge:entry:';
+ private readonly SEARCH_PREFIX = 'knowledge:search:';
+ private readonly SEARCH_TAGS_PREFIX = 'knowledge:search-tags:';
+ private readonly GRAPH_PREFIX = 'knowledge:graph:';
+
+ private readonly DEFAULT_TTL = 300; // 5 minutes
+ private readonly isEnabled: boolean;
+
+ constructor() {
+ this.isEnabled = process.env.KNOWLEDGE_CACHE_ENABLED !== 'false';
+ this.DEFAULT_TTL = parseInt(process.env.KNOWLEDGE_CACHE_TTL || '300');
+ }
+}
+```
+
+### Invalidation Strategy
+
+**Entry changes:**
+- **Create**: Invalidate search caches
+- **Update**: Invalidate entry cache, search caches, graph caches
+- **Delete**: Invalidate entry cache, search caches, graph caches
+
+**Link changes:**
+- Invalidate graph caches for source and target entries
+
+**Tag changes:**
+- Invalidate tag-based search caches
+
+```typescript
+async invalidateEntry(workspaceId: string, slug: string) {
+ const key = `${this.ENTRY_PREFIX}${workspaceId}:${slug}`;
+ await this.valkey.del(key);
+ this.stats.deletes++;
+}
+
+async invalidateSearchCaches(workspaceId: string) {
+ const pattern = `${this.SEARCH_PREFIX}${workspaceId}:*`;
+ const keys = await this.valkey.keys(pattern);
+ if (keys.length > 0) {
+ await this.valkey.del(...keys);
+ this.stats.deletes += keys.length;
+ }
+}
+
+async invalidateGraphCachesForEntry(entryId: string) {
+ // Graph caches include entryId in the key
+ const pattern = `${this.GRAPH_PREFIX}*:${entryId}:*`;
+ const keys = await this.valkey.keys(pattern);
+ if (keys.length > 0) {
+ await this.valkey.del(...keys);
+ this.stats.deletes += keys.length;
+ }
+}
+```
+
+### Performance Metrics
+
+Track cache effectiveness:
+
+```typescript
+interface CacheStats {
+ hits: number;
+ misses: number;
+ sets: number;
+ deletes: number;
+ hitRate: number;
+}
+
+getStats(): CacheStats {
+ const total = this.stats.hits + this.stats.misses;
+ return {
+ ...this.stats,
+ hitRate: total > 0 ? this.stats.hits / total : 0,
+ };
+}
+```
+
+---
+
+## Wiki-Link System
+
+### Parsing Algorithm
+
+The wiki-link parser handles complex edge cases:
+
+**Supported syntax:**
+```markdown
+[[Page Name]] → Link to "Page Name"
+[[page-slug]] → Link by slug
+[[Page Name|Display Text]] → Custom display text
+```
+
+**Edge cases handled:**
+- Nested brackets: `[[Link with [brackets] inside]]`
+- Code blocks: `` `[[not a link]]` ``
+- Fenced code: ` ```[[not a link]]``` `
+- Escaped brackets: `\[[not a link]]`
+- Triple brackets: `[[[not a link]]]`
+
+**Parsing flow:**
+
+1. **Find excluded regions** (code blocks, inline code)
+2. **Scan for `[[` patterns**
+3. **Find matching `]]`**
+4. **Validate link target**
+5. **Parse pipe separator for display text**
+6. **Return array of WikiLink objects**
+
+```typescript
+interface WikiLink {
+ raw: string; // "[[Page Name]]"
+ target: string; // "Page Name" or "page-slug"
+ displayText: string; // "Page Name" or custom text
+ start: number; // Position in content
+ end: number; // Position in content
+}
+```
+
+### Link Resolution
+
+**Three-step resolution:**
+
+```
+1. Exact slug match: "react-hooks" → entry with slug "react-hooks"
+2. Slugified match: "React Hooks" → slugify → "react-hooks" → match
+3. Title match: Case-insensitive title search
+```
+
+**Unresolved links:**
+- Not stored in database
+- Will auto-resolve when target entry is created
+- Future: UI indication for broken links
+
+### Link Synchronization
+
+**On entry create/update:**
+
+```
+1. Parse wiki-links from content
+2. Delete existing links from this entry
+3. For each parsed link:
+ a. Try to resolve target
+ b. If resolved, create KnowledgeLink record
+ c. If unresolved, skip (may resolve later)
+4. Invalidate graph caches
+```
+
+**Why delete-and-recreate?**
+- Simpler than diffing changes
+- Ensures consistency with current content
+- Links are cheap to recreate
+
+---
+
+## Testing Guide
+
+### Test Structure
+
+```
+apps/api/src/knowledge/
+├── knowledge.service.spec.ts
+├── tags.service.spec.ts
+├── search.controller.spec.ts
+├── tags.controller.spec.ts
+├── services/
+│ ├── cache.service.spec.ts
+│ ├── graph.service.spec.ts
+│ ├── link-resolution.service.spec.ts
+│ ├── link-sync.service.spec.ts
+│ └── search.service.spec.ts
+└── utils/
+ ├── markdown.spec.ts
+ └── wiki-link-parser.spec.ts
+```
+
+### Running Tests
+
+```bash
+# All knowledge module tests
+pnpm test knowledge
+
+# Specific test file
+pnpm test knowledge.service.spec.ts
+
+# Watch mode
+pnpm test:watch knowledge
+
+# Coverage
+pnpm test:coverage
+```
+
+### Test Coverage Requirements
+
+- **Minimum:** 85% overall coverage
+- **Critical paths:** 100% coverage required
+ - Entry CRUD operations
+ - Version management
+ - Link resolution
+ - Wiki-link parsing
+
+### Writing Tests
+
+**Service tests** (unit):
+
+```typescript
+describe('KnowledgeService', () => {
+ let service: KnowledgeService;
+ let prisma: PrismaService;
+ let linkSync: LinkSyncService;
+ let cache: KnowledgeCacheService;
+
+ beforeEach(async () => {
+ const module = await Test.createTestingModule({
+ providers: [
+ KnowledgeService,
+ {
+ provide: PrismaService,
+ useValue: mockDeep(),
+ },
+ {
+ provide: LinkSyncService,
+ useValue: mockDeep(),
+ },
+ {
+ provide: KnowledgeCacheService,
+ useValue: mockDeep(),
+ },
+ ],
+ }).compile();
+
+ service = module.get(KnowledgeService);
+ prisma = module.get(PrismaService);
+ linkSync = module.get(LinkSyncService);
+ cache = module.get(KnowledgeCacheService);
+ });
+
+ describe('create', () => {
+ it('should create entry with unique slug', async () => {
+ const dto = {
+ title: 'Test Entry',
+ content: '# Test',
+ };
+
+ prisma.knowledgeEntry.create.mockResolvedValue({
+ id: 'entry-id',
+ slug: 'test-entry',
+ ...dto,
+ });
+
+ const result = await service.create('workspace-id', 'user-id', dto);
+
+ expect(result.slug).toBe('test-entry');
+ expect(linkSync.syncLinks).toHaveBeenCalledWith('entry-id', dto.content);
+ expect(cache.invalidateSearchCaches).toHaveBeenCalled();
+ });
+ });
+});
+```
+
+**Controller tests** (integration):
+
+```typescript
+describe('KnowledgeController (e2e)', () => {
+ let app: INestApplication;
+ let prisma: PrismaService;
+
+ beforeAll(async () => {
+ const module = await Test.createTestingModule({
+ imports: [AppModule],
+ }).compile();
+
+ app = module.createNestApplication();
+ await app.init();
+
+ prisma = module.get(PrismaService);
+ });
+
+ afterAll(async () => {
+ await prisma.$disconnect();
+ await app.close();
+ });
+
+ describe('POST /knowledge/entries', () => {
+ it('should create entry and return 201', () => {
+ return request(app.getHttpServer())
+ .post('/knowledge/entries')
+ .set('Authorization', `Bearer ${authToken}`)
+ .set('x-workspace-id', workspaceId)
+ .send({
+ title: 'Test Entry',
+ content: '# Test Content',
+ status: 'DRAFT',
+ })
+ .expect(201)
+ .expect((res) => {
+ expect(res.body.slug).toBe('test-entry');
+ expect(res.body.status).toBe('DRAFT');
+ });
+ });
+ });
+});
+```
+
+**Utility tests:**
+
+```typescript
+describe('parseWikiLinks', () => {
+ it('should parse simple wiki link', () => {
+ const content = 'See [[My Page]] for details.';
+ const links = parseWikiLinks(content);
+
+ expect(links).toHaveLength(1);
+ expect(links[0]).toMatchObject({
+ target: 'My Page',
+ displayText: 'My Page',
+ raw: '[[My Page]]',
+ });
+ });
+
+ it('should parse link with custom display text', () => {
+ const content = 'See [[page-slug|custom text]] here.';
+ const links = parseWikiLinks(content);
+
+ expect(links[0]).toMatchObject({
+ target: 'page-slug',
+ displayText: 'custom text',
+ });
+ });
+
+ it('should ignore links in code blocks', () => {
+ const content = '```\n[[Not A Link]]\n```';
+ const links = parseWikiLinks(content);
+
+ expect(links).toHaveLength(0);
+ });
+});
+```
+
+### Test Data Setup
+
+Create reusable fixtures:
+
+```typescript
+// test/fixtures/knowledge.fixtures.ts
+export const createMockEntry = (overrides = {}) => ({
+ id: 'entry-uuid',
+ workspaceId: 'workspace-uuid',
+ slug: 'test-entry',
+ title: 'Test Entry',
+ content: '# Test',
+ contentHtml: 'Test
',
+ summary: null,
+ status: 'DRAFT',
+ visibility: 'PRIVATE',
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ createdBy: 'user-uuid',
+ updatedBy: 'user-uuid',
+ ...overrides,
+});
+
+export const createMockVersion = (entryId: string, version: number) => ({
+ id: `version-${version}-uuid`,
+ entryId,
+ version,
+ title: 'Test Entry',
+ content: `# Version ${version}`,
+ summary: null,
+ changeNote: `Update ${version}`,
+ createdAt: new Date(),
+ createdBy: 'user-uuid',
+});
+```
+
+---
+
+## Contributing
+
+### Development Workflow
+
+1. **Create feature branch**
+ ```bash
+ git checkout -b feature/your-feature develop
+ ```
+
+2. **Write tests first** (TDD approach)
+ ```bash
+ pnpm test:watch knowledge
+ ```
+
+3. **Implement feature**
+ - Follow TypeScript strict mode
+ - Use existing patterns
+ - Add JSDoc comments
+
+4. **Run tests and linting**
+ ```bash
+ pnpm test knowledge
+ pnpm lint
+ pnpm format
+ ```
+
+5. **Commit with conventional format**
+ ```bash
+ git commit -m "feat(knowledge): add semantic search endpoint"
+ ```
+
+6. **Create pull request to `develop`**
+
+### Code Style
+
+**TypeScript:**
+- Strict mode enabled
+- No `any` types
+- Explicit return types
+- Interface over type when possible
+
+**NestJS conventions:**
+- Services are `@Injectable()`
+- Controllers use `@Controller()`, `@Get()`, etc.
+- DTOs with class-validator decorators
+- Dependency injection via constructor
+
+**Naming:**
+- `camelCase` for variables and functions
+- `PascalCase` for classes and interfaces
+- `UPPER_SNAKE_CASE` for constants
+- `kebab-case` for file names
+
+### Adding New Features
+
+**New endpoint:**
+
+1. Create DTO in `dto/`
+2. Add controller method with proper guards
+3. Implement service method
+4. Write tests (unit + integration)
+5. Update API documentation
+
+**New service:**
+
+1. Create service class in `services/`
+2. Add `@Injectable()` decorator
+3. Register in `knowledge.module.ts`
+4. Write comprehensive tests
+5. Document public API with JSDoc
+
+**Database changes:**
+
+1. Update `schema.prisma`
+2. Create migration: `pnpm prisma migrate dev --name your_migration_name`
+3. Update entity interfaces in `entities/`
+4. Update services to use new schema
+5. Write migration tests
+
+### Performance Considerations
+
+**Always consider:**
+- Database query efficiency (use indexes)
+- N+1 query problems (use `include` wisely)
+- Cache invalidation strategy
+- Transaction boundaries
+- Large content handling
+
+**Optimization checklist:**
+- [ ] Proper indexes on database columns
+- [ ] Caching for expensive operations
+- [ ] Pagination for list endpoints
+- [ ] Lazy loading for relations
+- [ ] Bulk operations where possible
+
+### Security Checklist
+
+- [ ] Input validation (DTOs)
+- [ ] Permission guards on endpoints
+- [ ] Workspace isolation (never cross workspaces)
+- [ ] SQL injection prevention (Prisma handles this)
+- [ ] No sensitive data in logs
+- [ ] Rate limiting (future)
+
+---
+
+## Additional Resources
+
+- **[User Guide](KNOWLEDGE_USER_GUIDE.md)** — End-user documentation
+- **[API Documentation](KNOWLEDGE_API.md)** — Complete API reference
+- **[Main README](README.md)** — Project overview
+- **[NestJS Docs](https://docs.nestjs.com/)** — Framework documentation
+- **[Prisma Docs](https://www.prisma.io/docs)** — ORM documentation
+
+---
+
+**Happy coding! 🚀**
diff --git a/KNOWLEDGE_USER_GUIDE.md b/KNOWLEDGE_USER_GUIDE.md
new file mode 100644
index 0000000..46f2db1
--- /dev/null
+++ b/KNOWLEDGE_USER_GUIDE.md
@@ -0,0 +1,628 @@
+# Knowledge Module - User Guide
+
+The Knowledge Module is a powerful, personal wiki and knowledge management system built into Mosaic Stack. Create interconnected notes, organize with tags, track changes over time, and visualize relationships between your knowledge entries.
+
+## Table of Contents
+
+1. [Getting Started](#getting-started)
+2. [Creating Entries](#creating-entries)
+3. [Wiki-links and Backlinks](#wiki-links-and-backlinks)
+4. [Tags and Organization](#tags-and-organization)
+5. [Search](#search)
+6. [Import/Export](#importexport)
+7. [Version History](#version-history)
+8. [Graph Visualization](#graph-visualization)
+
+---
+
+## Getting Started
+
+The Knowledge Module provides a flexible way to capture and organize information:
+
+- **Markdown-based**: Write entries using Markdown for rich formatting
+- **Wiki-style linking**: Connect entries using `[[wiki-links]]`
+- **Tag-based organization**: Categorize entries with tags
+- **Full version history**: Every edit is tracked and recoverable
+- **Powerful search**: Find entries with full-text search
+- **Visual knowledge graph**: See relationships between entries
+- **Import/Export**: Bulk import/export for portability
+
+### Entry Lifecycle
+
+Each knowledge entry has three possible statuses:
+
+- **DRAFT** — Entry is being worked on, visible only to you
+- **PUBLISHED** — Entry is complete and visible to workspace members
+- **ARCHIVED** — Entry is hidden from normal views but preserved
+
+And three visibility levels:
+
+- **PRIVATE** — Only visible to you
+- **WORKSPACE** — Visible to all workspace members
+- **PUBLIC** — Visible to anyone (future feature)
+
+---
+
+## Creating Entries
+
+### Basic Entry Creation
+
+Every entry has:
+
+- **Title** (required) — The entry name (up to 500 characters)
+- **Content** (required) — Markdown-formatted text
+- **Summary** (optional) — Brief description (up to 1000 characters)
+- **Tags** (optional) — Categories for organization
+- **Status** — DRAFT, PUBLISHED, or ARCHIVED
+- **Visibility** — PRIVATE, WORKSPACE, or PUBLIC
+
+### Slugs
+
+When you create an entry, the system automatically generates a unique **slug** from the title:
+
+- `"My First Entry"` → `my-first-entry`
+- `"API Design Patterns"` → `api-design-patterns`
+- `"React Hooks Guide"` → `react-hooks-guide`
+
+Slugs are used in URLs and wiki-links. They're unique per workspace.
+
+### Example Entry
+
+```markdown
+Title: React Component Patterns
+
+Content:
+## Component Composition
+
+React components can be composed using several patterns:
+
+### Container/Presentational Pattern
+Separate data logic (containers) from UI (presentational).
+
+See also: [[React Hooks]], [[State Management]]
+
+Tags: react, frontend, patterns
+```
+
+### Change Notes
+
+When creating or updating entries, you can add an optional **change note** to describe what you changed:
+
+```
+"Added section on custom hooks"
+"Fixed typo in code example"
+"Initial draft"
+```
+
+Change notes appear in version history, making it easy to track why changes were made.
+
+---
+
+## Wiki-links and Backlinks
+
+### Creating Links
+
+Link to other entries using **wiki-link syntax**:
+
+```markdown
+[[Entry Title]]
+[[entry-slug]]
+[[Entry Title|Custom Link Text]]
+```
+
+Examples:
+
+```markdown
+For more details, see [[API Documentation]].
+Learn about [[react-hooks|React Hooks]].
+Related: [[Frontend Best Practices]], [[TypeScript Guide]]
+```
+
+### How Wiki-links Work
+
+1. **Automatic resolution**: The system finds the target entry by slug or title
+2. **Smart matching**: Links work with slugs (`react-hooks`) or titles (`React Hooks`)
+3. **Custom text**: Use `[[slug|display text]]` for custom link text
+4. **Auto-linking**: Links are parsed and resolved when you save the entry
+
+### Unresolved Links
+
+If you link to an entry that doesn't exist yet, it's marked as **unresolved**:
+
+```markdown
+[[Future Entry That Doesn't Exist Yet]]
+```
+
+Unresolved links:
+- Are still stored and tracked
+- Will automatically resolve when the target entry is created
+- Show as unlinked in the UI (implementation-specific)
+
+This lets you create links before creating the entries they point to!
+
+### Backlinks
+
+Every entry automatically tracks its **backlinks** — entries that link *to* it.
+
+**Example**: If entry "React Hooks" is linked from:
+- "Frontend Guide"
+- "Component Patterns"
+- "State Management"
+
+Then "React Hooks" will show 3 backlinks.
+
+**Use backlinks to:**
+- Discover related content
+- Understand entry relationships
+- Navigate bidirectionally through knowledge
+
+Access backlinks via: `GET /api/knowledge/entries/:slug/backlinks`
+
+---
+
+## Tags and Organization
+
+### Creating Tags
+
+Tags help categorize and organize entries. Create tags with:
+
+- **Name** (required) — Display name (e.g., "Frontend Development")
+- **Slug** (auto-generated) — URL-friendly identifier (e.g., "frontend-development")
+- **Color** (optional) — Hex color for visual organization (e.g., "#3b82f6")
+- **Description** (optional) — Tag purpose or usage notes
+
+### Tagging Entries
+
+Add tags when creating or updating entries:
+
+```json
+{
+ "title": "React Component Guide",
+ "content": "...",
+ "tags": ["react", "frontend", "tutorial"]
+}
+```
+
+### Finding Tagged Entries
+
+Search for entries with specific tags:
+
+```
+GET /api/knowledge/search/by-tags?tags=react,frontend
+```
+
+This finds entries that have **ALL** specified tags (AND logic).
+
+### Tag Management
+
+- **List all tags**: `GET /api/knowledge/tags`
+- **Get tag details**: `GET /api/knowledge/tags/:slug`
+- **Get tagged entries**: `GET /api/knowledge/tags/:slug/entries`
+- **Update tag**: `PUT /api/knowledge/tags/:slug`
+- **Delete tag**: `DELETE /api/knowledge/tags/:slug` (admin only)
+
+Deleting a tag removes it from all entries but doesn't delete the entries themselves.
+
+---
+
+## Search
+
+The Knowledge Module provides powerful search capabilities:
+
+### Full-Text Search
+
+Search across entry titles and content with relevance ranking:
+
+```
+GET /api/knowledge/search?q=react hooks&page=1&limit=20
+```
+
+**Features:**
+- Searches **title** and **content** fields
+- Relevance ranking (best matches first)
+- Case-insensitive
+- Partial word matching
+- Pagination support
+
+**Query parameters:**
+- `q` (required) — Search query string
+- `status` (optional) — Filter by status (DRAFT, PUBLISHED, ARCHIVED)
+- `page` (default: 1) — Page number
+- `limit` (default: 20, max: 100) — Results per page
+
+### Tag-Based Search
+
+Find entries with specific tags:
+
+```
+GET /api/knowledge/search/by-tags?tags=react,typescript
+```
+
+Returns entries that have **ALL** specified tags.
+
+### Recent Entries
+
+Get recently modified entries:
+
+```
+GET /api/knowledge/search/recent?limit=10&status=PUBLISHED
+```
+
+**Parameters:**
+- `limit` (default: 10, max: 50) — Number of entries
+- `status` (optional) — Filter by status
+
+Perfect for "what's new" or "recently updated" views.
+
+### Combining Filters
+
+All search endpoints support status filtering:
+
+- `status=DRAFT` — Only draft entries
+- `status=PUBLISHED` — Only published entries
+- `status=ARCHIVED` — Only archived entries
+- (no status) — All statuses
+
+---
+
+## Import/Export
+
+### Exporting Entries
+
+Export your knowledge base for backup or migration:
+
+```
+GET /api/knowledge/export?format=markdown
+```
+
+**Export formats:**
+
+1. **Markdown** (default)
+ - Each entry saved as `.md` file
+ - Filename: `{slug}.md`
+ - Front matter with metadata (title, tags, status, etc.)
+ - Returns `.zip` archive
+
+2. **JSON**
+ - Structured JSON format
+ - Complete entry data including metadata
+ - Returns `.zip` archive
+
+**Export specific entries:**
+
+```
+GET /api/knowledge/export?format=markdown&entryIds[]=uuid1&entryIds[]=uuid2
+```
+
+If `entryIds` is omitted, exports **all entries** in the workspace.
+
+**Example Markdown export:**
+
+```markdown
+---
+slug: react-hooks-guide
+title: React Hooks Guide
+status: PUBLISHED
+visibility: WORKSPACE
+tags: react, frontend
+createdAt: 2024-01-29T10:00:00Z
+updatedAt: 2024-01-30T15:30:00Z
+---
+
+# React Hooks Guide
+
+Content goes here...
+
+[[Related Entry]]
+```
+
+### Importing Entries
+
+Import entries from `.md` or `.zip` files:
+
+```bash
+curl -X POST http://localhost:3001/api/knowledge/import \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID" \
+ -F "file=@knowledge-export.zip"
+```
+
+**Supported formats:**
+
+1. **Single Markdown file** (`.md`)
+ - Creates one entry
+ - Reads front matter for metadata
+ - Generates slug from filename if not in front matter
+
+2. **Zip archive** (`.zip`)
+ - Multiple `.md` files
+ - Each file becomes one entry
+ - Front matter optional
+
+**Import behavior:**
+
+- **New entries**: Creates with status DRAFT
+- **Existing entries** (matching slug): Skipped (does not overwrite)
+- **Wiki-links**: Preserved and will resolve if targets exist
+- **Tags**: Created if they don't exist
+- **Validation**: Invalid entries are skipped with error details
+
+**Response:**
+
+```json
+{
+ "success": true,
+ "totalFiles": 10,
+ "imported": 8,
+ "failed": 2,
+ "results": [
+ {
+ "filename": "react-hooks.md",
+ "success": true,
+ "entryId": "uuid",
+ "slug": "react-hooks"
+ },
+ {
+ "filename": "invalid-entry.md",
+ "success": false,
+ "error": "Title is required"
+ }
+ ]
+}
+```
+
+### File Size Limits
+
+- Maximum file size: **50MB**
+- Accepted file types: `.md`, `.zip`
+
+---
+
+## Version History
+
+Every edit to a knowledge entry is automatically saved as a **version**. You can view history, compare changes, and restore previous versions.
+
+### How Versioning Works
+
+- **Automatic versioning**: Every update creates a new version
+- **Version numbers**: Auto-incremented (1, 2, 3, ...)
+- **What's tracked**: Title, content, summary
+- **Change notes**: Optional message describing the change
+- **Author tracking**: Who made each change
+- **Timestamps**: When each version was created
+
+### Viewing Version History
+
+**List all versions for an entry:**
+
+```
+GET /api/knowledge/entries/:slug/versions?page=1&limit=20
+```
+
+Returns paginated list of versions with:
+- Version number
+- Title
+- Summary
+- Change note
+- Author info
+- Timestamp
+
+**Get a specific version:**
+
+```
+GET /api/knowledge/entries/:slug/versions/:version
+```
+
+Returns the complete entry as it existed at that version:
+- Title
+- Content
+- Summary
+- Change note
+- Author
+- Created timestamp
+
+### Restoring a Previous Version
+
+Restore an entry to a previous version:
+
+```
+POST /api/knowledge/entries/:slug/restore/:version
+Body: { "changeNote": "Restored version 5" }
+```
+
+**What happens:**
+1. Creates a **new version** with content from the specified version
+2. The change note is required to document why you restored
+3. Original versions remain intact (no data loss)
+4. Version numbers continue incrementing (no rewriting history)
+
+**Example:**
+- Current version: 10
+- Restore version 5
+- New version created: 11 (with content from version 5)
+
+### Best Practices
+
+- **Write meaningful change notes**: "Added examples" is better than "Updated"
+- **Review before publishing**: Keep entries in DRAFT while iterating
+- **Restore carefully**: Preview the old version before restoring
+- **Use versions for comparison**: See how entries evolved over time
+
+---
+
+## Graph Visualization
+
+The Knowledge Module includes a powerful **graph visualization** feature (currently available via service layer, REST endpoint coming soon).
+
+### How the Graph Works
+
+The knowledge graph represents:
+
+- **Nodes**: Knowledge entries
+- **Edges**: Wiki-links between entries
+- **Relationships**: Bidirectional (incoming and outgoing links)
+- **Depth traversal**: Explore connections up to N levels deep
+
+### Entry-Centered Graph
+
+Get a graph view centered on a specific entry:
+
+```typescript
+// Service layer (REST endpoint coming soon)
+const graph = await graphService.getEntryGraph(
+ workspaceId,
+ entryId,
+ maxDepth // default: 1
+);
+```
+
+**Response structure:**
+
+```typescript
+{
+ centerNode: {
+ id: "uuid",
+ slug: "react-hooks",
+ title: "React Hooks Guide",
+ summary: "Comprehensive guide to React Hooks",
+ tags: [
+ { id: "uuid", name: "React", slug: "react", color: "#61dafb" }
+ ],
+ depth: 0
+ },
+ nodes: [
+ // All connected entries up to maxDepth
+ { id: "uuid", slug: "...", title: "...", depth: 1 },
+ { id: "uuid", slug: "...", title: "...", depth: 2 }
+ ],
+ edges: [
+ {
+ id: "uuid",
+ sourceId: "entry1-uuid",
+ targetId: "entry2-uuid",
+ linkText: "React Hooks"
+ }
+ ],
+ stats: {
+ totalNodes: 15,
+ totalEdges: 22,
+ maxDepth: 2
+ }
+}
+```
+
+### Graph Properties
+
+- **Depth 0**: Just the center node (no connections)
+- **Depth 1**: Center node + directly connected entries
+- **Depth 2**: Depth 1 + entries connected to depth 1 nodes
+- **Depth N**: Continue expanding N levels
+
+**Node information:**
+- Entry metadata (slug, title, summary)
+- Tags with colors
+- Depth level from center
+
+**Edge information:**
+- Source and target entry IDs
+- Original link text from the markdown
+- Unique link identifier
+
+### Use Cases
+
+- **Discover connections**: Find related entries
+- **Visualize knowledge structure**: See how concepts relate
+- **Navigate bidirectionally**: Follow links in both directions
+- **Cluster analysis**: Identify knowledge hubs (highly connected entries)
+- **Content gap analysis**: Find isolated entries needing more connections
+
+### Performance & Caching
+
+Graph queries are **cached** for performance:
+
+- **Cache key**: `workspace:entry:depth`
+- **TTL**: 5 minutes (configurable)
+- **Invalidation**: Automatic on entry or link updates
+
+Large graphs (depth > 2) can be expensive. The cache ensures fast repeat access.
+
+---
+
+## Tips & Best Practices
+
+### Content Organization
+
+1. **Start with outlines**: Create stub entries, fill in later
+2. **Link early and often**: Wiki-links are cheap, use them liberally
+3. **Tag consistently**: Establish a tag taxonomy early
+4. **Write summaries**: Help future-you find content faster
+5. **Use DRAFT status**: Iterate privately before publishing
+
+### Naming Conventions
+
+- **Titles**: Clear, descriptive, unique
+- **Slugs**: Auto-generated, don't worry about them
+- **Tags**: Short, lowercase, consistent naming (e.g., `react` not `React` or `ReactJS`)
+
+### Knowledge Graph Health
+
+- **Avoid orphans**: Link new entries to existing content
+- **Create hubs**: Some entries naturally become central (index pages)
+- **Bidirectional linking**: Link both ways when relationships are mutual
+- **Tag hubs**: Use tags for broad categories, links for specific relationships
+
+### Workflow Patterns
+
+**Personal Wiki:**
+```
+Draft → Link → Tag → Publish → Iterate
+```
+
+**Team Knowledge Base:**
+```
+Draft → Review → Link → Tag → Publish → Maintain
+```
+
+**Research Notes:**
+```
+Capture → Organize → Synthesize → Link → Archive
+```
+
+---
+
+## Permissions
+
+Knowledge Module endpoints require specific permissions:
+
+- **Read** (ANY workspace member)
+ - List entries
+ - View entries
+ - View backlinks
+ - View versions
+ - Search
+ - Export
+
+- **Write** (MEMBER role or higher)
+ - Create entries
+ - Update entries
+ - Import entries
+ - Restore versions
+
+- **Delete/Admin** (ADMIN role or higher)
+ - Archive entries
+ - Delete entries
+ - Clear cache
+
+See [API Documentation](KNOWLEDGE_API.md) for complete endpoint permissions.
+
+---
+
+## Next Steps
+
+- **[API Documentation](KNOWLEDGE_API.md)** — Complete REST API reference
+- **[Developer Guide](KNOWLEDGE_DEV.md)** — Architecture and implementation details
+- **[Main README](README.md)** — Full Mosaic Stack documentation
+
+---
+
+**Happy knowledge building! 🧠✨**
diff --git a/README.md b/README.md
index 49a8ecb..26d70c5 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ Multi-tenant personal assistant platform with PostgreSQL backend, Authentik SSO,
Mosaic Stack is a modern, PDA-friendly platform designed to help users manage their personal and professional lives with:
- **Multi-user workspaces** with team collaboration
+- **Knowledge management** with wiki-style linking and version history
- **Task management** with flexible organization
- **Event & calendar** integration
- **Project tracking** with Gantt charts and Kanban boards
@@ -185,6 +186,111 @@ mosaic-stack/
See the [issue tracker](https://git.mosaicstack.dev/mosaic/stack/issues) for complete roadmap.
+## Knowledge Module
+
+The **Knowledge Module** is a powerful personal wiki and knowledge management system built into Mosaic Stack. Create interconnected notes, organize with tags, track changes over time, and visualize relationships.
+
+### Features
+
+- **📝 Markdown-based entries** — Write using familiar Markdown syntax
+- **🔗 Wiki-style linking** — Connect entries using `[[wiki-links]]`
+- **🏷️ Tag organization** — Categorize and filter with flexible tagging
+- **📜 Full version history** — Every edit is tracked and recoverable
+- **🔍 Powerful search** — Full-text search across titles and content
+- **📊 Knowledge graph** — Visualize relationships between entries
+- **📤 Import/Export** — Bulk import/export for portability
+- **⚡ Valkey caching** — High-performance caching for fast access
+
+### Quick Examples
+
+**Create an entry:**
+```bash
+curl -X POST http://localhost:3001/api/knowledge/entries \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID" \
+ -d '{
+ "title": "React Hooks Guide",
+ "content": "# React Hooks\n\nSee [[Component Patterns]] for more.",
+ "tags": ["react", "frontend"],
+ "status": "PUBLISHED"
+ }'
+```
+
+**Search entries:**
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/search?q=react+hooks' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID"
+```
+
+**Export knowledge base:**
+```bash
+curl -X GET 'http://localhost:3001/api/knowledge/export?format=markdown' \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -H "x-workspace-id: WORKSPACE_ID" \
+ -o knowledge-export.zip
+```
+
+### Documentation
+
+- **[User Guide](KNOWLEDGE_USER_GUIDE.md)** — Getting started, features, and workflows
+- **[API Documentation](KNOWLEDGE_API.md)** — Complete REST API reference with examples
+- **[Developer Guide](KNOWLEDGE_DEV.md)** — Architecture, implementation, and contributing
+
+### Key Concepts
+
+**Wiki-links**
+Connect entries using double-bracket syntax:
+```markdown
+See [[Entry Title]] or [[entry-slug]] for details.
+Use [[Page|custom text]] for custom display text.
+```
+
+**Version History**
+Every edit creates a new version. View history, compare changes, and restore previous versions:
+```bash
+# List versions
+GET /api/knowledge/entries/:slug/versions
+
+# Get specific version
+GET /api/knowledge/entries/:slug/versions/:version
+
+# Restore version
+POST /api/knowledge/entries/:slug/restore/:version
+```
+
+**Backlinks**
+Automatically discover entries that link to a given entry:
+```bash
+GET /api/knowledge/entries/:slug/backlinks
+```
+
+**Tags**
+Organize entries with tags:
+```bash
+# Create tag
+POST /api/knowledge/tags
+{ "name": "React", "color": "#61dafb" }
+
+# Find entries with tags
+GET /api/knowledge/search/by-tags?tags=react,frontend
+```
+
+### Performance
+
+With Valkey caching enabled:
+- **Entry retrieval:** ~2-5ms (vs ~50ms uncached)
+- **Search queries:** ~2-5ms (vs ~200ms uncached)
+- **Graph traversals:** ~2-5ms (vs ~400ms uncached)
+- **Cache hit rates:** 70-90% for active workspaces
+
+Configure caching via environment variables:
+```bash
+VALKEY_URL=redis://localhost:6379
+KNOWLEDGE_CACHE_ENABLED=true
+KNOWLEDGE_CACHE_TTL=300 # 5 minutes
+```
+
## Development Workflow
### Branch Strategy