FossilRepo
Rotate secrets, deploy.sh reads from .env.deploy, gitignore fixes
Commit
45192efa4fe1bfabbcdddd1121c312ede1c181e6a0c4729f7153c7d0a6ef950b
Parent
afe42d07c9479b7…
17 files changed
+11
-1
+15
-12
+188
+180
+180
+131
-88
+205
+131
+108
-110
+6
+5
+73
+1
-1
+8
-1
+36
-6
+1
-1
+2
-2
~
core/context_processors.py
~
deploy.sh
+
docs/api/agentic-development.md
+
docs/api/reference.md
+
docs/architecture/api-reference.md
~
docs/architecture/overview.md
+
docs/features.md
+
docs/getting-started/administration.md
~
docs/getting-started/installation.md
~
fossil/views.py
~
mkdocs.yml
+
scripts/sync_to_fossil.sh
~
templates/dashboard.html
~
templates/fossil/wiki_list.html
~
templates/includes/sidebar.html
~
templates/pages/page_form.html
~
templates/pages/page_list.html
+11
-1
| --- core/context_processors.py | ||
| +++ core/context_processors.py | ||
| @@ -22,11 +22,21 @@ | ||
| 22 | 22 | grouped_projects.append({"group": group, "projects": group_projects}) |
| 23 | 23 | grouped_ids.update(p.id for p in group_projects) |
| 24 | 24 | |
| 25 | 25 | ungrouped_projects = [p for p in projects if p.id not in grouped_ids] |
| 26 | 26 | |
| 27 | + # Split pages: product docs (known slugs) vs org knowledge base (user-created) | |
| 28 | + PRODUCT_DOC_SLUGS = { | |
| 29 | + "agentic-development", "api-reference", "architecture", | |
| 30 | + "administration", "setup-guide", | |
| 31 | + } | |
| 32 | + product_docs = [p for p in pages if p.slug in PRODUCT_DOC_SLUGS] | |
| 33 | + kb_pages = [p for p in pages if p.slug not in PRODUCT_DOC_SLUGS] | |
| 34 | + | |
| 27 | 35 | return { |
| 28 | 36 | "sidebar_projects": projects, |
| 29 | 37 | "sidebar_grouped": grouped_projects, |
| 30 | 38 | "sidebar_ungrouped": ungrouped_projects, |
| 31 | - "sidebar_pages": pages, | |
| 39 | + "sidebar_pages": pages, # Keep for backwards compat | |
| 40 | + "sidebar_product_docs": product_docs, | |
| 41 | + "sidebar_kb_pages": kb_pages, | |
| 32 | 42 | } |
| 33 | 43 |
| --- core/context_processors.py | |
| +++ core/context_processors.py | |
| @@ -22,11 +22,21 @@ | |
| 22 | grouped_projects.append({"group": group, "projects": group_projects}) |
| 23 | grouped_ids.update(p.id for p in group_projects) |
| 24 | |
| 25 | ungrouped_projects = [p for p in projects if p.id not in grouped_ids] |
| 26 | |
| 27 | return { |
| 28 | "sidebar_projects": projects, |
| 29 | "sidebar_grouped": grouped_projects, |
| 30 | "sidebar_ungrouped": ungrouped_projects, |
| 31 | "sidebar_pages": pages, |
| 32 | } |
| 33 |
| --- core/context_processors.py | |
| +++ core/context_processors.py | |
| @@ -22,11 +22,21 @@ | |
| 22 | grouped_projects.append({"group": group, "projects": group_projects}) |
| 23 | grouped_ids.update(p.id for p in group_projects) |
| 24 | |
| 25 | ungrouped_projects = [p for p in projects if p.id not in grouped_ids] |
| 26 | |
| 27 | # Split pages: product docs (known slugs) vs org knowledge base (user-created) |
| 28 | PRODUCT_DOC_SLUGS = { |
| 29 | "agentic-development", "api-reference", "architecture", |
| 30 | "administration", "setup-guide", |
| 31 | } |
| 32 | product_docs = [p for p in pages if p.slug in PRODUCT_DOC_SLUGS] |
| 33 | kb_pages = [p for p in pages if p.slug not in PRODUCT_DOC_SLUGS] |
| 34 | |
| 35 | return { |
| 36 | "sidebar_projects": projects, |
| 37 | "sidebar_grouped": grouped_projects, |
| 38 | "sidebar_ungrouped": ungrouped_projects, |
| 39 | "sidebar_pages": pages, # Keep for backwards compat |
| 40 | "sidebar_product_docs": product_docs, |
| 41 | "sidebar_kb_pages": kb_pages, |
| 42 | } |
| 43 |
+15
-12
| --- deploy.sh | ||
| +++ deploy.sh | ||
| @@ -1,28 +1,33 @@ | ||
| 1 | 1 | #!/bin/bash |
| 2 | 2 | # deploy.sh — push local state to fossilrepo.io |
| 3 | 3 | # |
| 4 | 4 | # Usage: ./deploy.sh [message] |
| 5 | -# 1. Checks in all changes to Fossil | |
| 6 | -# 2. Pushes to fossilrepo.io | |
| 7 | -# 3. SSM deploys code + syncs DB + repos to EC2 | |
| 5 | +# | |
| 6 | +# Requires .env.deploy with: | |
| 7 | +# FOSSIL_REMOTE_URL=https://admin:[email protected]/projects/fossilrepo/fossil/xfer | |
| 8 | +# EC2_INSTANCE_ID=i-xxxx | |
| 9 | +# S3_BUCKET=dev-fossilrepo-storage | |
| 10 | +# AWS_REGION=us-west-2 | |
| 8 | 11 | |
| 9 | 12 | set -euo pipefail |
| 10 | 13 | |
| 11 | -REMOTE="https://admin:[email protected]/projects/fossilrepo/fossil/xfer" | |
| 12 | -INSTANCE_ID="i-0013c73c6f5146181" | |
| 13 | -S3_BUCKET="dev-fossilrepo-storage" | |
| 14 | -AWS_REGION="us-west-2" | |
| 14 | +if [[ ! -f .env.deploy ]]; then | |
| 15 | + echo "Missing .env.deploy -- copy .env.deploy.example and fill in your values" | |
| 16 | + exit 1 | |
| 17 | +fi | |
| 18 | + | |
| 19 | +source .env.deploy | |
| 15 | 20 | |
| 16 | 21 | MSG="${1:-Deploy $(date +%Y-%m-%d-%H%M)}" |
| 17 | 22 | |
| 18 | 23 | echo "=== Fossil commit ===" |
| 19 | 24 | fossil addremove 2>/dev/null || true |
| 20 | 25 | fossil commit -m "$MSG" 2>&1 || echo "Nothing to commit" |
| 21 | 26 | |
| 22 | -echo "=== Fossil push to fossilrepo.io ===" | |
| 23 | -fossil push "$REMOTE" | |
| 27 | +echo "=== Fossil push ===" | |
| 28 | +fossil push "$FOSSIL_REMOTE_URL" | |
| 24 | 29 | |
| 25 | 30 | echo "=== Sync repos to S3 ===" |
| 26 | 31 | AWS_PROFILE=fossiladmin aws s3 sync repos/ "s3://${S3_BUCKET}/sync/repos/" --region "$AWS_REGION" --exclude "*.fossil-shm" --exclude "*.fossil-wal" 2>&1 | tail -3 |
| 27 | 32 | |
| 28 | 33 | echo "=== Sync DB to S3 ===" |
| @@ -32,17 +37,15 @@ | ||
| 32 | 37 | echo "=== Push code to git ===" |
| 33 | 38 | git add -A && git commit -m "$MSG" 2>/dev/null || true |
| 34 | 39 | git push origin main 2>&1 || echo "Git push failed (non-critical)" |
| 35 | 40 | |
| 36 | 41 | echo "=== Deploy to EC2 ===" |
| 37 | -AWS_PROFILE=fossiladmin aws s3 cp /tmp/sync-to-cloud.sh "s3://${S3_BUCKET}/sync-to-cloud.sh" --region "$AWS_REGION" 2>/dev/null || true | |
| 38 | 42 | AWS_PROFILE=fossiladmin aws ssm send-command \ |
| 39 | - --instance-ids "$INSTANCE_ID" \ | |
| 43 | + --instance-ids "$EC2_INSTANCE_ID" \ | |
| 40 | 44 | --document-name "AWS-RunShellScript" \ |
| 41 | 45 | --timeout-seconds 300 \ |
| 42 | 46 | --parameters "{\"commands\":[\"export HOME=/root && aws s3 cp s3://${S3_BUCKET}/sync-to-cloud.sh /tmp/sync-to-cloud.sh --region ${AWS_REGION} && bash /tmp/sync-to-cloud.sh 2>&1\"]}" \ |
| 43 | 47 | --region "$AWS_REGION" \ |
| 44 | 48 | --query "Command.CommandId" \ |
| 45 | 49 | --output text |
| 46 | 50 | |
| 47 | 51 | echo "=== Deploy triggered ===" |
| 48 | -echo "Monitor: AWS_PROFILE=fossiladmin aws ssm list-commands --instance-id $INSTANCE_ID --region $AWS_REGION --max-results 1" | |
| 49 | 52 | |
| 50 | 53 | ADDED docs/api/agentic-development.md |
| 51 | 54 | ADDED docs/api/reference.md |
| 52 | 55 | ADDED docs/architecture/api-reference.md |
| --- deploy.sh | |
| +++ deploy.sh | |
| @@ -1,28 +1,33 @@ | |
| 1 | #!/bin/bash |
| 2 | # deploy.sh — push local state to fossilrepo.io |
| 3 | # |
| 4 | # Usage: ./deploy.sh [message] |
| 5 | # 1. Checks in all changes to Fossil |
| 6 | # 2. Pushes to fossilrepo.io |
| 7 | # 3. SSM deploys code + syncs DB + repos to EC2 |
| 8 | |
| 9 | set -euo pipefail |
| 10 | |
| 11 | REMOTE="https://admin:[email protected]/projects/fossilrepo/fossil/xfer" |
| 12 | INSTANCE_ID="i-0013c73c6f5146181" |
| 13 | S3_BUCKET="dev-fossilrepo-storage" |
| 14 | AWS_REGION="us-west-2" |
| 15 | |
| 16 | MSG="${1:-Deploy $(date +%Y-%m-%d-%H%M)}" |
| 17 | |
| 18 | echo "=== Fossil commit ===" |
| 19 | fossil addremove 2>/dev/null || true |
| 20 | fossil commit -m "$MSG" 2>&1 || echo "Nothing to commit" |
| 21 | |
| 22 | echo "=== Fossil push to fossilrepo.io ===" |
| 23 | fossil push "$REMOTE" |
| 24 | |
| 25 | echo "=== Sync repos to S3 ===" |
| 26 | AWS_PROFILE=fossiladmin aws s3 sync repos/ "s3://${S3_BUCKET}/sync/repos/" --region "$AWS_REGION" --exclude "*.fossil-shm" --exclude "*.fossil-wal" 2>&1 | tail -3 |
| 27 | |
| 28 | echo "=== Sync DB to S3 ===" |
| @@ -32,17 +37,15 @@ | |
| 32 | echo "=== Push code to git ===" |
| 33 | git add -A && git commit -m "$MSG" 2>/dev/null || true |
| 34 | git push origin main 2>&1 || echo "Git push failed (non-critical)" |
| 35 | |
| 36 | echo "=== Deploy to EC2 ===" |
| 37 | AWS_PROFILE=fossiladmin aws s3 cp /tmp/sync-to-cloud.sh "s3://${S3_BUCKET}/sync-to-cloud.sh" --region "$AWS_REGION" 2>/dev/null || true |
| 38 | AWS_PROFILE=fossiladmin aws ssm send-command \ |
| 39 | --instance-ids "$INSTANCE_ID" \ |
| 40 | --document-name "AWS-RunShellScript" \ |
| 41 | --timeout-seconds 300 \ |
| 42 | --parameters "{\"commands\":[\"export HOME=/root && aws s3 cp s3://${S3_BUCKET}/sync-to-cloud.sh /tmp/sync-to-cloud.sh --region ${AWS_REGION} && bash /tmp/sync-to-cloud.sh 2>&1\"]}" \ |
| 43 | --region "$AWS_REGION" \ |
| 44 | --query "Command.CommandId" \ |
| 45 | --output text |
| 46 | |
| 47 | echo "=== Deploy triggered ===" |
| 48 | echo "Monitor: AWS_PROFILE=fossiladmin aws ssm list-commands --instance-id $INSTANCE_ID --region $AWS_REGION --max-results 1" |
| 49 | |
| 50 | DDED docs/api/agentic-development.md |
| 51 | DDED docs/api/reference.md |
| 52 | DDED docs/architecture/api-reference.md |
| --- deploy.sh | |
| +++ deploy.sh | |
| @@ -1,28 +1,33 @@ | |
| 1 | #!/bin/bash |
| 2 | # deploy.sh — push local state to fossilrepo.io |
| 3 | # |
| 4 | # Usage: ./deploy.sh [message] |
| 5 | # |
| 6 | # Requires .env.deploy with: |
| 7 | # FOSSIL_REMOTE_URL=https://admin:[email protected]/projects/fossilrepo/fossil/xfer |
| 8 | # EC2_INSTANCE_ID=i-xxxx |
| 9 | # S3_BUCKET=dev-fossilrepo-storage |
| 10 | # AWS_REGION=us-west-2 |
| 11 | |
| 12 | set -euo pipefail |
| 13 | |
| 14 | if [[ ! -f .env.deploy ]]; then |
| 15 | echo "Missing .env.deploy -- copy .env.deploy.example and fill in your values" |
| 16 | exit 1 |
| 17 | fi |
| 18 | |
| 19 | source .env.deploy |
| 20 | |
| 21 | MSG="${1:-Deploy $(date +%Y-%m-%d-%H%M)}" |
| 22 | |
| 23 | echo "=== Fossil commit ===" |
| 24 | fossil addremove 2>/dev/null || true |
| 25 | fossil commit -m "$MSG" 2>&1 || echo "Nothing to commit" |
| 26 | |
| 27 | echo "=== Fossil push ===" |
| 28 | fossil push "$FOSSIL_REMOTE_URL" |
| 29 | |
| 30 | echo "=== Sync repos to S3 ===" |
| 31 | AWS_PROFILE=fossiladmin aws s3 sync repos/ "s3://${S3_BUCKET}/sync/repos/" --region "$AWS_REGION" --exclude "*.fossil-shm" --exclude "*.fossil-wal" 2>&1 | tail -3 |
| 32 | |
| 33 | echo "=== Sync DB to S3 ===" |
| @@ -32,17 +37,15 @@ | |
| 37 | echo "=== Push code to git ===" |
| 38 | git add -A && git commit -m "$MSG" 2>/dev/null || true |
| 39 | git push origin main 2>&1 || echo "Git push failed (non-critical)" |
| 40 | |
| 41 | echo "=== Deploy to EC2 ===" |
| 42 | AWS_PROFILE=fossiladmin aws ssm send-command \ |
| 43 | --instance-ids "$EC2_INSTANCE_ID" \ |
| 44 | --document-name "AWS-RunShellScript" \ |
| 45 | --timeout-seconds 300 \ |
| 46 | --parameters "{\"commands\":[\"export HOME=/root && aws s3 cp s3://${S3_BUCKET}/sync-to-cloud.sh /tmp/sync-to-cloud.sh --region ${AWS_REGION} && bash /tmp/sync-to-cloud.sh 2>&1\"]}" \ |
| 47 | --region "$AWS_REGION" \ |
| 48 | --query "Command.CommandId" \ |
| 49 | --output text |
| 50 | |
| 51 | echo "=== Deploy triggered ===" |
| 52 | |
| 53 | DDED docs/api/agentic-development.md |
| 54 | DDED docs/api/reference.md |
| 55 | DDED docs/architecture/api-reference.md |
| --- a/docs/api/agentic-development.md | ||
| +++ b/docs/api/agentic-development.md | ||
| @@ -0,0 +1,188 @@ | ||
| 1 | +# Agentic Development | |
| 2 | + | |
| 3 | +FossilRepo is built for AI-assisted development at scale. Traditional Git forges impose rate limits that cripple agent workflows. FossilRepo eliminates these bottlenecks. | |
| 4 | + | |
| 5 | +## The Problem | |
| 6 | + | |
| 7 | +AI coding agents make dozens of API calls per task. On GitHub: | |
| 8 | +- 5,000 API calls/hour limit (agents burn through this in minutes) | |
| 9 | +- 30 search requests/minute | |
| 10 | +- Webhook delivery delays | |
| 11 | +- Actions queue congestion | |
| 12 | + | |
| 13 | +Multiple agents working in parallel hit rate limits within seconds. | |
| 14 | + | |
| 15 | +## The Solution | |
| 16 | + | |
| 17 | +``` | |
| 18 | +AI Agent (Claude Code, Cursor, etc.) | |
| 19 | + | | |
| 20 | + v | |
| 21 | +FossilRepo MCP Server / API <-- zero rate limits | |
| 22 | + | | |
| 23 | + v | |
| 24 | +Fossil repos (.fossil SQLite) <-- local disk, instant | |
| 25 | + | | |
| 26 | + v (scheduled, batched) | |
| 27 | +Git Mirror --> GitHub <-- rate-limit-aware sync | |
| 28 | +``` | |
| 29 | + | |
| 30 | +Agents work against FossilRepo locally with no limits. Changes sync to GitHub on a schedule. GitHub becomes a downstream mirror, not the bottleneck. | |
| 31 | + | |
| 32 | +## Connecting AI Tools | |
| 33 | + | |
| 34 | +### MCP Server (Recommended) | |
| 35 | + | |
| 36 | +The MCP server gives AI tools native access to all FossilRepo capabilities. | |
| 37 | + | |
| 38 | +```bash | |
| 39 | +pip install fossilrepo | |
| 40 | +fossilrepo-mcp | |
| 41 | +``` | |
| 42 | + | |
| 43 | +Claude Code config: | |
| 44 | +```json | |
| 45 | +{ | |
| 46 | + "mcpServers": { | |
| 47 | + "fossilrepo": { | |
| 48 | + "command": "fossilrepo-mcp" | |
| 49 | + } | |
| 50 | + } | |
| 51 | +} | |
| 52 | +``` | |
| 53 | + | |
| 54 | +17 tools available: browse code, read files, search, manage tickets, view timeline/diffs/blame, create tickets, run SQL queries. | |
| 55 | + | |
| 56 | +### JSON API | |
| 57 | + | |
| 58 | +For tools without MCP support: | |
| 59 | +``` | |
| 60 | +curl -H "Authorization: Bearer frp_abc123..." \ | |
| 61 | + http://localhost:8000/projects/myproject/fossil/api/timeline | |
| 62 | +``` | |
| 63 | + | |
| 64 | +### Batch API | |
| 65 | + | |
| 66 | +Reduce round-trips by 25x: | |
| 67 | +```json | |
| 68 | +POST /api/batch | |
| 69 | +{"requests": [ | |
| 70 | + {"method": "GET", "path": "/api/timeline"}, | |
| 71 | + {"method": "GET", "path": "/api/tickets", "params": {"status": "Open"}}, | |
| 72 | + {"method": "GET", "path": "/api/wiki/Home"} | |
| 73 | +]} | |
| 74 | +``` | |
| 75 | + | |
| 76 | +## The Agent Workflow | |
| 77 | + | |
| 78 | +### 1. Discover Work | |
| 79 | + | |
| 80 | +Browse open, unclaimed tickets: | |
| 81 | +``` | |
| 82 | +GET /api/tickets/unclaimed | |
| 83 | +``` | |
| 84 | + | |
| 85 | +### 2. Claim a Ticket | |
| 86 | + | |
| 87 | +Atomic claiming prevents two agents from working on the same thing: | |
| 88 | +``` | |
| 89 | +POST /api/tickets/<uuid>/claim | |
| 90 | +{"agent_id": "claude-session-abc"} | |
| 91 | +``` | |
| 92 | +Returns 200 if claimed, 409 if already taken by another agent. | |
| 93 | + | |
| 94 | +### 3. Create an Isolated Workspace | |
| 95 | + | |
| 96 | +Each agent gets its own Fossil branch and checkout directory: | |
| 97 | +``` | |
| 98 | +POST /api/workspaces/create | |
| 99 | +{"name": "fix-auth-bug", "agent_id": "claude-session-abc"} | |
| 100 | +``` | |
| 101 | + | |
| 102 | +No interference with other agents or the main branch. | |
| 103 | + | |
| 104 | +### 4. Do the Work | |
| 105 | + | |
| 106 | +Read code, understand context, make changes, commit. All via MCP tools or API: | |
| 107 | +``` | |
| 108 | +POST /api/workspaces/fix-auth-bug/commit | |
| 109 | +{"message": "Fix null check in auth middleware"} | |
| 110 | +``` | |
| 111 | + | |
| 112 | +### 5. Submit for Review | |
| 113 | + | |
| 114 | +``` | |
| 115 | +POST /api/reviews/create | |
| 116 | +{ | |
| 117 | + "title": "Fix null pointer in auth module", | |
| 118 | + "description": "The auth check was failing when...", | |
| 119 | + "diff": "--- a/src/auth.py\n+++ b/src/auth.py\n...", | |
| 120 | + "workspace": "fix-auth-bug" | |
| 121 | +} | |
| 122 | +``` | |
| 123 | + | |
| 124 | +### 6. Review and Merge | |
| 125 | + | |
| 126 | +Another agent (or human) reviews: | |
| 127 | +``` | |
| 128 | +POST /api/reviews/<id>/approve | |
| 129 | +POST /api/reviews/<id>/merge | |
| 130 | +``` | |
| 131 | + | |
| 132 | +### 7. Release the Claim | |
| 133 | + | |
| 134 | +``` | |
| 135 | +POST /api/tickets/<uuid>/submit | |
| 136 | +{"summary": "Fixed by closing the null check gap"} | |
| 137 | +``` | |
| 138 | + | |
| 139 | +## Real-Time Coordination | |
| 140 | + | |
| 141 | +### Server-Sent Events | |
| 142 | + | |
| 143 | +Agents subscribe to a live event stream instead of polling: | |
| 144 | +``` | |
| 145 | +GET /api/events | |
| 146 | +``` | |
| 147 | + | |
| 148 | +Events: | |
| 149 | +- `checkin` — new commits pushed | |
| 150 | +- `claim` — ticket claimed or released | |
| 151 | +- `workspace` — workspace created, merged, or abandoned | |
| 152 | + | |
| 153 | +### Multi-Agent Safety | |
| 154 | + | |
| 155 | +FossilRepo prevents agent collisions through: | |
| 156 | + | |
| 157 | +1. **Atomic ticket claiming** — database-level locking via `select_for_update`. Only one agent can claim a ticket. | |
| 158 | +2. **Isolated workspaces** — each agent works on its own Fossil branch in its own checkout directory. No merge conflicts during work. | |
| 159 | +3. **Code review gate** — changes must be reviewed (by human or another agent) before merging to trunk. | |
| 160 | +4. **Branch protection** — protected branches block non-admin pushes. CI status checks can be required. | |
| 161 | +5. **SSE events** — agents know what others are doing in real-time, avoiding duplicate work. | |
| 162 | + | |
| 163 | +## Comparison with GitHub | |
| 164 | + | |
| 165 | +| Feature | GitHub | FossilRepo | | |
| 166 | +|---------|--------|------------| | |
| 167 | +| API rate limit | 5,000/hour | Unlimited | | |
| 168 | +| Search rate limit | 30/min | Unlimited | | |
| 169 | +| Agent workspace | Shared branch | Isolated checkout | | |
| 170 | +| Task claiming | None (race conditions) | Atomic (DB-locked) | | |
| 171 | +| Batch API | None | 25 calls/request | | |
| 172 | +| Real-time events | Webhooks (delayed) | SSE (instant) | | |
| 173 | +| Code review | Pull request (heavyweight) | Lightweight API | | |
| 174 | +| MCP support | No | 17 tools | | |
| 175 | +| CI status API | Rate-limited | Unlimited | | |
| 176 | +| Self-hosted | No | Yes | | |
| 177 | +| Cost | Per-seat | Free (MIT) | | |
| 178 | + | |
| 179 | +## Why Fossil for Agents? | |
| 180 | + | |
| 181 | +Fossil's architecture is uniquely suited for agentic development: | |
| 182 | + | |
| 183 | +- **Single-file repos** — each `.fossil` file is a complete SQLite database. No complex storage, no git pack files, no network dependencies. | |
| 184 | +- **Built-in everything** — tickets, wiki, forum, technotes all in one file. Agents manage the full development lifecycle without switching tools. | |
| 185 | +- **SQLite = instant reads** — FossilReader opens the file directly. No API calls, no HTTP, no rate limits. Microsecond latency. | |
| 186 | +- **Offline-first** — works without internet. Sync to GitHub when ready, on your schedule. | |
| 187 | +- **Clone = complete backup** — `fossil clone` gives you everything: code, tickets, wiki, forum. One file, one copy. | |
| 188 | +- **Branching without overhead** — Fossil branches are lightweight metadata, not separate directory trees. Creating 50 agent workspaces costs nothing. |
| --- a/docs/api/agentic-development.md | |
| +++ b/docs/api/agentic-development.md | |
| @@ -0,0 +1,188 @@ | |
| --- a/docs/api/agentic-development.md | |
| +++ b/docs/api/agentic-development.md | |
| @@ -0,0 +1,188 @@ | |
| 1 | # Agentic Development |
| 2 | |
| 3 | FossilRepo is built for AI-assisted development at scale. Traditional Git forges impose rate limits that cripple agent workflows. FossilRepo eliminates these bottlenecks. |
| 4 | |
| 5 | ## The Problem |
| 6 | |
| 7 | AI coding agents make dozens of API calls per task. On GitHub: |
| 8 | - 5,000 API calls/hour limit (agents burn through this in minutes) |
| 9 | - 30 search requests/minute |
| 10 | - Webhook delivery delays |
| 11 | - Actions queue congestion |
| 12 | |
| 13 | Multiple agents working in parallel hit rate limits within seconds. |
| 14 | |
| 15 | ## The Solution |
| 16 | |
| 17 | ``` |
| 18 | AI Agent (Claude Code, Cursor, etc.) |
| 19 | | |
| 20 | v |
| 21 | FossilRepo MCP Server / API <-- zero rate limits |
| 22 | | |
| 23 | v |
| 24 | Fossil repos (.fossil SQLite) <-- local disk, instant |
| 25 | | |
| 26 | v (scheduled, batched) |
| 27 | Git Mirror --> GitHub <-- rate-limit-aware sync |
| 28 | ``` |
| 29 | |
| 30 | Agents work against FossilRepo locally with no limits. Changes sync to GitHub on a schedule. GitHub becomes a downstream mirror, not the bottleneck. |
| 31 | |
| 32 | ## Connecting AI Tools |
| 33 | |
| 34 | ### MCP Server (Recommended) |
| 35 | |
| 36 | The MCP server gives AI tools native access to all FossilRepo capabilities. |
| 37 | |
| 38 | ```bash |
| 39 | pip install fossilrepo |
| 40 | fossilrepo-mcp |
| 41 | ``` |
| 42 | |
| 43 | Claude Code config: |
| 44 | ```json |
| 45 | { |
| 46 | "mcpServers": { |
| 47 | "fossilrepo": { |
| 48 | "command": "fossilrepo-mcp" |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | ``` |
| 53 | |
| 54 | 17 tools available: browse code, read files, search, manage tickets, view timeline/diffs/blame, create tickets, run SQL queries. |
| 55 | |
| 56 | ### JSON API |
| 57 | |
| 58 | For tools without MCP support: |
| 59 | ``` |
| 60 | curl -H "Authorization: Bearer frp_abc123..." \ |
| 61 | http://localhost:8000/projects/myproject/fossil/api/timeline |
| 62 | ``` |
| 63 | |
| 64 | ### Batch API |
| 65 | |
| 66 | Reduce round-trips by 25x: |
| 67 | ```json |
| 68 | POST /api/batch |
| 69 | {"requests": [ |
| 70 | {"method": "GET", "path": "/api/timeline"}, |
| 71 | {"method": "GET", "path": "/api/tickets", "params": {"status": "Open"}}, |
| 72 | {"method": "GET", "path": "/api/wiki/Home"} |
| 73 | ]} |
| 74 | ``` |
| 75 | |
| 76 | ## The Agent Workflow |
| 77 | |
| 78 | ### 1. Discover Work |
| 79 | |
| 80 | Browse open, unclaimed tickets: |
| 81 | ``` |
| 82 | GET /api/tickets/unclaimed |
| 83 | ``` |
| 84 | |
| 85 | ### 2. Claim a Ticket |
| 86 | |
| 87 | Atomic claiming prevents two agents from working on the same thing: |
| 88 | ``` |
| 89 | POST /api/tickets/<uuid>/claim |
| 90 | {"agent_id": "claude-session-abc"} |
| 91 | ``` |
| 92 | Returns 200 if claimed, 409 if already taken by another agent. |
| 93 | |
| 94 | ### 3. Create an Isolated Workspace |
| 95 | |
| 96 | Each agent gets its own Fossil branch and checkout directory: |
| 97 | ``` |
| 98 | POST /api/workspaces/create |
| 99 | {"name": "fix-auth-bug", "agent_id": "claude-session-abc"} |
| 100 | ``` |
| 101 | |
| 102 | No interference with other agents or the main branch. |
| 103 | |
| 104 | ### 4. Do the Work |
| 105 | |
| 106 | Read code, understand context, make changes, commit. All via MCP tools or API: |
| 107 | ``` |
| 108 | POST /api/workspaces/fix-auth-bug/commit |
| 109 | {"message": "Fix null check in auth middleware"} |
| 110 | ``` |
| 111 | |
| 112 | ### 5. Submit for Review |
| 113 | |
| 114 | ``` |
| 115 | POST /api/reviews/create |
| 116 | { |
| 117 | "title": "Fix null pointer in auth module", |
| 118 | "description": "The auth check was failing when...", |
| 119 | "diff": "--- a/src/auth.py\n+++ b/src/auth.py\n...", |
| 120 | "workspace": "fix-auth-bug" |
| 121 | } |
| 122 | ``` |
| 123 | |
| 124 | ### 6. Review and Merge |
| 125 | |
| 126 | Another agent (or human) reviews: |
| 127 | ``` |
| 128 | POST /api/reviews/<id>/approve |
| 129 | POST /api/reviews/<id>/merge |
| 130 | ``` |
| 131 | |
| 132 | ### 7. Release the Claim |
| 133 | |
| 134 | ``` |
| 135 | POST /api/tickets/<uuid>/submit |
| 136 | {"summary": "Fixed by closing the null check gap"} |
| 137 | ``` |
| 138 | |
| 139 | ## Real-Time Coordination |
| 140 | |
| 141 | ### Server-Sent Events |
| 142 | |
| 143 | Agents subscribe to a live event stream instead of polling: |
| 144 | ``` |
| 145 | GET /api/events |
| 146 | ``` |
| 147 | |
| 148 | Events: |
| 149 | - `checkin` — new commits pushed |
| 150 | - `claim` — ticket claimed or released |
| 151 | - `workspace` — workspace created, merged, or abandoned |
| 152 | |
| 153 | ### Multi-Agent Safety |
| 154 | |
| 155 | FossilRepo prevents agent collisions through: |
| 156 | |
| 157 | 1. **Atomic ticket claiming** — database-level locking via `select_for_update`. Only one agent can claim a ticket. |
| 158 | 2. **Isolated workspaces** — each agent works on its own Fossil branch in its own checkout directory. No merge conflicts during work. |
| 159 | 3. **Code review gate** — changes must be reviewed (by human or another agent) before merging to trunk. |
| 160 | 4. **Branch protection** — protected branches block non-admin pushes. CI status checks can be required. |
| 161 | 5. **SSE events** — agents know what others are doing in real-time, avoiding duplicate work. |
| 162 | |
| 163 | ## Comparison with GitHub |
| 164 | |
| 165 | | Feature | GitHub | FossilRepo | |
| 166 | |---------|--------|------------| |
| 167 | | API rate limit | 5,000/hour | Unlimited | |
| 168 | | Search rate limit | 30/min | Unlimited | |
| 169 | | Agent workspace | Shared branch | Isolated checkout | |
| 170 | | Task claiming | None (race conditions) | Atomic (DB-locked) | |
| 171 | | Batch API | None | 25 calls/request | |
| 172 | | Real-time events | Webhooks (delayed) | SSE (instant) | |
| 173 | | Code review | Pull request (heavyweight) | Lightweight API | |
| 174 | | MCP support | No | 17 tools | |
| 175 | | CI status API | Rate-limited | Unlimited | |
| 176 | | Self-hosted | No | Yes | |
| 177 | | Cost | Per-seat | Free (MIT) | |
| 178 | |
| 179 | ## Why Fossil for Agents? |
| 180 | |
| 181 | Fossil's architecture is uniquely suited for agentic development: |
| 182 | |
| 183 | - **Single-file repos** — each `.fossil` file is a complete SQLite database. No complex storage, no git pack files, no network dependencies. |
| 184 | - **Built-in everything** — tickets, wiki, forum, technotes all in one file. Agents manage the full development lifecycle without switching tools. |
| 185 | - **SQLite = instant reads** — FossilReader opens the file directly. No API calls, no HTTP, no rate limits. Microsecond latency. |
| 186 | - **Offline-first** — works without internet. Sync to GitHub when ready, on your schedule. |
| 187 | - **Clone = complete backup** — `fossil clone` gives you everything: code, tickets, wiki, forum. One file, one copy. |
| 188 | - **Branching without overhead** — Fossil branches are lightweight metadata, not separate directory trees. Creating 50 agent workspaces costs nothing. |
+180
| --- a/docs/api/reference.md | ||
| +++ b/docs/api/reference.md | ||
| @@ -0,0 +1,180 @@ | ||
| 1 | +# API Reference | |
| 2 | + | |
| 3 | +FossilRepo provides a JSON API for programmatic access and an MCP server for AI tool integration. | |
| 4 | + | |
| 5 | +## Authentication | |
| 6 | + | |
| 7 | +All API endpoints accept authentication via: | |
| 8 | + | |
| 9 | +1. **Bearer token** (recommended for automation): | |
| 10 | + ``` | |
| 11 | + Authorization: Bearer frp_abc123... | |
| 12 | + ``` | |
| 13 | + Tokens can be project-scoped (API Token) or user-scoped (Personal Access Token). | |
| 14 | + | |
| 15 | +2. **Session cookie** (for browser-based testing): | |
| 16 | + Log in via the web UI, then call API endpoints in the same browser session. | |
| 17 | + | |
| 18 | +## JSON API Endpoints | |
| 19 | + | |
| 20 | +Base URL: `/projects/<slug>/fossil/api/` | |
| 21 | + | |
| 22 | +### Project | |
| 23 | + | |
| 24 | +**GET /api/project** — Project metadata | |
| 25 | +```json | |
| 26 | +{"name": "FossilRepo", "slug": "fossilrepo", "description": "...", "visibility": "public", "star_count": 5} | |
| 27 | +``` | |
| 28 | + | |
| 29 | +### Timeline | |
| 30 | + | |
| 31 | +**GET /api/timeline** — Recent checkins | |
| 32 | +- `?page=1` — Page number | |
| 33 | +- `?per_page=25` — Items per page | |
| 34 | +- `?branch=trunk` — Filter by branch | |
| 35 | + | |
| 36 | +### Tickets | |
| 37 | + | |
| 38 | +**GET /api/tickets** — Ticket list | |
| 39 | +- `?status=Open` — Filter by status | |
| 40 | +- `?page=1&per_page=25` — Pagination | |
| 41 | + | |
| 42 | +**GET /api/tickets/\<uuid\>** — Single ticket with comments | |
| 43 | + | |
| 44 | +**GET /api/tickets/unclaimed** — Tickets available for agent claiming | |
| 45 | + | |
| 46 | +**POST /api/tickets/\<uuid\>/claim** — Claim ticket for exclusive work | |
| 47 | +```json | |
| 48 | +{"agent_id": "claude-session-abc"} | |
| 49 | +``` | |
| 50 | + | |
| 51 | +**POST /api/tickets/\<uuid\>/release** — Release a claim | |
| 52 | + | |
| 53 | +**POST /api/tickets/\<uuid\>/submit** — Submit completed work | |
| 54 | +```json | |
| 55 | +{"summary": "Fixed by...", "workspace": "agent-fix-123"} | |
| 56 | +``` | |
| 57 | + | |
| 58 | +### Wiki | |
| 59 | + | |
| 60 | +**GET /api/wiki** — Wiki page list | |
| 61 | +**GET /api/wiki/\<name\>** — Page content (raw + rendered HTML) | |
| 62 | + | |
| 63 | +### Branches and Tags | |
| 64 | + | |
| 65 | +**GET /api/branches** — All branches with open/closed status | |
| 66 | +**GET /api/tags** — All tags | |
| 67 | + | |
| 68 | +### Releases | |
| 69 | + | |
| 70 | +**GET /api/releases** — Release list with assets | |
| 71 | + | |
| 72 | +### Search | |
| 73 | + | |
| 74 | +**GET /api/search?q=term** — Search across checkins, tickets, wiki | |
| 75 | + | |
| 76 | +### CI Status | |
| 77 | + | |
| 78 | +**POST /api/status** — Report CI build status (Bearer token required) | |
| 79 | +```json | |
| 80 | +{"checkin": "abc123", "context": "ci/tests", "state": "success", "description": "All tests passed", "target_url": "https://ci.example.com/123"} | |
| 81 | +``` | |
| 82 | + | |
| 83 | +**GET /api/status/\<checkin_uuid\>/badge.svg** — SVG status badge | |
| 84 | + | |
| 85 | +### Batch API | |
| 86 | + | |
| 87 | +**POST /api/batch** — Execute up to 25 API calls in one request | |
| 88 | +```json | |
| 89 | +{"requests": [ | |
| 90 | + {"method": "GET", "path": "/api/timeline", "params": {"per_page": 5}}, | |
| 91 | + {"method": "GET", "path": "/api/tickets", "params": {"status": "Open"}}, | |
| 92 | + {"method": "GET", "path": "/api/wiki/Home"} | |
| 93 | +]} | |
| 94 | +``` | |
| 95 | + | |
| 96 | +### Agent Workspaces | |
| 97 | + | |
| 98 | +**GET /api/workspaces** — List active workspaces | |
| 99 | +**POST /api/workspaces/create** — Create isolated workspace | |
| 100 | +```json | |
| 101 | +{"name": "fix-auth-bug", "agent_id": "claude-abc", "description": "Fixing auth issue"} | |
| 102 | +``` | |
| 103 | +**GET /api/workspaces/\<name\>** — Workspace details | |
| 104 | +**POST /api/workspaces/\<name\>/commit** — Commit changes | |
| 105 | +**POST /api/workspaces/\<name\>/merge** — Merge back to trunk | |
| 106 | +**DELETE /api/workspaces/\<name\>/abandon** — Abandon workspace | |
| 107 | + | |
| 108 | +### Code Reviews | |
| 109 | + | |
| 110 | +**GET /api/reviews** — List reviews (filterable by status) | |
| 111 | +**POST /api/reviews/create** — Submit code for review | |
| 112 | +```json | |
| 113 | +{"title": "Fix null pointer", "description": "...", "diff": "--- a/...", "files_changed": ["src/auth.py"]} | |
| 114 | +``` | |
| 115 | +**GET /api/reviews/\<id\>** — Review with comments | |
| 116 | +**POST /api/reviews/\<id\>/comment** — Add review comment | |
| 117 | +**POST /api/reviews/\<id\>/approve** — Approve | |
| 118 | +**POST /api/reviews/\<id\>/request-changes** — Request changes | |
| 119 | +**POST /api/reviews/\<id\>/merge** — Merge approved review | |
| 120 | + | |
| 121 | +### Server-Sent Events | |
| 122 | + | |
| 123 | +**GET /api/events** — Real-time event stream | |
| 124 | +``` | |
| 125 | +event: checkin | |
| 126 | +data: {"uuid": "abc123", "user": "dev", "comment": "Fix bug"} | |
| 127 | + | |
| 128 | +event: claim | |
| 129 | +data: {"ticket": "def456", "agent": "claude-abc", "status": "claimed"} | |
| 130 | + | |
| 131 | +event: workspace | |
| 132 | +data: {"name": "fix-auth", "branch": "workspace/fix-auth", "status": "merged"} | |
| 133 | +``` | |
| 134 | + | |
| 135 | +## MCP Server | |
| 136 | + | |
| 137 | +The MCP (Model Context Protocol) server gives AI tools native access to FossilRepo. | |
| 138 | + | |
| 139 | +### Setup | |
| 140 | + | |
| 141 | +```bash | |
| 142 | +pip install fossilrepo | |
| 143 | +fossilrepo-mcp | |
| 144 | +``` | |
| 145 | + | |
| 146 | +### Claude Code Configuration | |
| 147 | + | |
| 148 | +```json | |
| 149 | +{ | |
| 150 | + "mcpServers": { | |
| 151 | + "fossilrepo": { | |
| 152 | + "command": "fossilrepo-mcp" | |
| 153 | + } | |
| 154 | + } | |
| 155 | +} | |
| 156 | +``` | |
| 157 | + | |
| 158 | +### Available Tools | |
| 159 | + | |
| 160 | +| Tool | Description | | |
| 161 | +|------|-------------| | |
| 162 | +| list_projects | List all projects | | |
| 163 | +| get_project | Project details with repo stats | | |
| 164 | +| browse_code | List files in a directory | | |
| 165 | +| read_file | Read file content | | |
| 166 | +| get_timeline | Recent checkins (optional branch filter) | | |
| 167 | +| get_checkin | Checkin detail with file changes | | |
| 168 | +| search_code | Search across checkins, tickets, wiki | | |
| 169 | +| list_tickets | List tickets (optional status filter) | | |
| 170 | +| get_ticket | Ticket detail with comments | | |
| 171 | +| create_ticket | Create a new ticket | | |
| 172 | +| update_ticket | Update ticket status, add comment | | |
| 173 | +| list_wiki_pages | List all wiki pages | | |
| 174 | +| get_wiki_page | Read wiki page content | | |
| 175 | +| list_branches | List all branches | | |
| 176 | +| get_file_blame | Blame annotations for a file | | |
| 177 | +| get_file_history | Commit history for a file | | |
| 178 | +| sql_query | Run read-only SQL against Fossil SQLite | | |
| 179 | + | |
| 180 | +All tools accept a `slug` parameter to identify the project. |
| --- a/docs/api/reference.md | |
| +++ b/docs/api/reference.md | |
| @@ -0,0 +1,180 @@ | |
| --- a/docs/api/reference.md | |
| +++ b/docs/api/reference.md | |
| @@ -0,0 +1,180 @@ | |
| 1 | # API Reference |
| 2 | |
| 3 | FossilRepo provides a JSON API for programmatic access and an MCP server for AI tool integration. |
| 4 | |
| 5 | ## Authentication |
| 6 | |
| 7 | All API endpoints accept authentication via: |
| 8 | |
| 9 | 1. **Bearer token** (recommended for automation): |
| 10 | ``` |
| 11 | Authorization: Bearer frp_abc123... |
| 12 | ``` |
| 13 | Tokens can be project-scoped (API Token) or user-scoped (Personal Access Token). |
| 14 | |
| 15 | 2. **Session cookie** (for browser-based testing): |
| 16 | Log in via the web UI, then call API endpoints in the same browser session. |
| 17 | |
| 18 | ## JSON API Endpoints |
| 19 | |
| 20 | Base URL: `/projects/<slug>/fossil/api/` |
| 21 | |
| 22 | ### Project |
| 23 | |
| 24 | **GET /api/project** — Project metadata |
| 25 | ```json |
| 26 | {"name": "FossilRepo", "slug": "fossilrepo", "description": "...", "visibility": "public", "star_count": 5} |
| 27 | ``` |
| 28 | |
| 29 | ### Timeline |
| 30 | |
| 31 | **GET /api/timeline** — Recent checkins |
| 32 | - `?page=1` — Page number |
| 33 | - `?per_page=25` — Items per page |
| 34 | - `?branch=trunk` — Filter by branch |
| 35 | |
| 36 | ### Tickets |
| 37 | |
| 38 | **GET /api/tickets** — Ticket list |
| 39 | - `?status=Open` — Filter by status |
| 40 | - `?page=1&per_page=25` — Pagination |
| 41 | |
| 42 | **GET /api/tickets/\<uuid\>** — Single ticket with comments |
| 43 | |
| 44 | **GET /api/tickets/unclaimed** — Tickets available for agent claiming |
| 45 | |
| 46 | **POST /api/tickets/\<uuid\>/claim** — Claim ticket for exclusive work |
| 47 | ```json |
| 48 | {"agent_id": "claude-session-abc"} |
| 49 | ``` |
| 50 | |
| 51 | **POST /api/tickets/\<uuid\>/release** — Release a claim |
| 52 | |
| 53 | **POST /api/tickets/\<uuid\>/submit** — Submit completed work |
| 54 | ```json |
| 55 | {"summary": "Fixed by...", "workspace": "agent-fix-123"} |
| 56 | ``` |
| 57 | |
| 58 | ### Wiki |
| 59 | |
| 60 | **GET /api/wiki** — Wiki page list |
| 61 | **GET /api/wiki/\<name\>** — Page content (raw + rendered HTML) |
| 62 | |
| 63 | ### Branches and Tags |
| 64 | |
| 65 | **GET /api/branches** — All branches with open/closed status |
| 66 | **GET /api/tags** — All tags |
| 67 | |
| 68 | ### Releases |
| 69 | |
| 70 | **GET /api/releases** — Release list with assets |
| 71 | |
| 72 | ### Search |
| 73 | |
| 74 | **GET /api/search?q=term** — Search across checkins, tickets, wiki |
| 75 | |
| 76 | ### CI Status |
| 77 | |
| 78 | **POST /api/status** — Report CI build status (Bearer token required) |
| 79 | ```json |
| 80 | {"checkin": "abc123", "context": "ci/tests", "state": "success", "description": "All tests passed", "target_url": "https://ci.example.com/123"} |
| 81 | ``` |
| 82 | |
| 83 | **GET /api/status/\<checkin_uuid\>/badge.svg** — SVG status badge |
| 84 | |
| 85 | ### Batch API |
| 86 | |
| 87 | **POST /api/batch** — Execute up to 25 API calls in one request |
| 88 | ```json |
| 89 | {"requests": [ |
| 90 | {"method": "GET", "path": "/api/timeline", "params": {"per_page": 5}}, |
| 91 | {"method": "GET", "path": "/api/tickets", "params": {"status": "Open"}}, |
| 92 | {"method": "GET", "path": "/api/wiki/Home"} |
| 93 | ]} |
| 94 | ``` |
| 95 | |
| 96 | ### Agent Workspaces |
| 97 | |
| 98 | **GET /api/workspaces** — List active workspaces |
| 99 | **POST /api/workspaces/create** — Create isolated workspace |
| 100 | ```json |
| 101 | {"name": "fix-auth-bug", "agent_id": "claude-abc", "description": "Fixing auth issue"} |
| 102 | ``` |
| 103 | **GET /api/workspaces/\<name\>** — Workspace details |
| 104 | **POST /api/workspaces/\<name\>/commit** — Commit changes |
| 105 | **POST /api/workspaces/\<name\>/merge** — Merge back to trunk |
| 106 | **DELETE /api/workspaces/\<name\>/abandon** — Abandon workspace |
| 107 | |
| 108 | ### Code Reviews |
| 109 | |
| 110 | **GET /api/reviews** — List reviews (filterable by status) |
| 111 | **POST /api/reviews/create** — Submit code for review |
| 112 | ```json |
| 113 | {"title": "Fix null pointer", "description": "...", "diff": "--- a/...", "files_changed": ["src/auth.py"]} |
| 114 | ``` |
| 115 | **GET /api/reviews/\<id\>** — Review with comments |
| 116 | **POST /api/reviews/\<id\>/comment** — Add review comment |
| 117 | **POST /api/reviews/\<id\>/approve** — Approve |
| 118 | **POST /api/reviews/\<id\>/request-changes** — Request changes |
| 119 | **POST /api/reviews/\<id\>/merge** — Merge approved review |
| 120 | |
| 121 | ### Server-Sent Events |
| 122 | |
| 123 | **GET /api/events** — Real-time event stream |
| 124 | ``` |
| 125 | event: checkin |
| 126 | data: {"uuid": "abc123", "user": "dev", "comment": "Fix bug"} |
| 127 | |
| 128 | event: claim |
| 129 | data: {"ticket": "def456", "agent": "claude-abc", "status": "claimed"} |
| 130 | |
| 131 | event: workspace |
| 132 | data: {"name": "fix-auth", "branch": "workspace/fix-auth", "status": "merged"} |
| 133 | ``` |
| 134 | |
| 135 | ## MCP Server |
| 136 | |
| 137 | The MCP (Model Context Protocol) server gives AI tools native access to FossilRepo. |
| 138 | |
| 139 | ### Setup |
| 140 | |
| 141 | ```bash |
| 142 | pip install fossilrepo |
| 143 | fossilrepo-mcp |
| 144 | ``` |
| 145 | |
| 146 | ### Claude Code Configuration |
| 147 | |
| 148 | ```json |
| 149 | { |
| 150 | "mcpServers": { |
| 151 | "fossilrepo": { |
| 152 | "command": "fossilrepo-mcp" |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | ``` |
| 157 | |
| 158 | ### Available Tools |
| 159 | |
| 160 | | Tool | Description | |
| 161 | |------|-------------| |
| 162 | | list_projects | List all projects | |
| 163 | | get_project | Project details with repo stats | |
| 164 | | browse_code | List files in a directory | |
| 165 | | read_file | Read file content | |
| 166 | | get_timeline | Recent checkins (optional branch filter) | |
| 167 | | get_checkin | Checkin detail with file changes | |
| 168 | | search_code | Search across checkins, tickets, wiki | |
| 169 | | list_tickets | List tickets (optional status filter) | |
| 170 | | get_ticket | Ticket detail with comments | |
| 171 | | create_ticket | Create a new ticket | |
| 172 | | update_ticket | Update ticket status, add comment | |
| 173 | | list_wiki_pages | List all wiki pages | |
| 174 | | get_wiki_page | Read wiki page content | |
| 175 | | list_branches | List all branches | |
| 176 | | get_file_blame | Blame annotations for a file | |
| 177 | | get_file_history | Commit history for a file | |
| 178 | | sql_query | Run read-only SQL against Fossil SQLite | |
| 179 | |
| 180 | All tools accept a `slug` parameter to identify the project. |
| --- a/docs/architecture/api-reference.md | ||
| +++ b/docs/architecture/api-reference.md | ||
| @@ -0,0 +1,180 @@ | ||
| 1 | +# API Reference | |
| 2 | + | |
| 3 | +FossilRepo provides a JSON API for programmatic access and an MCP server for AI tool integration. | |
| 4 | + | |
| 5 | +## Authentication | |
| 6 | + | |
| 7 | +All API endpoints accept authentication via: | |
| 8 | + | |
| 9 | +1. **Bearer token** (recommended for automation): | |
| 10 | + ``` | |
| 11 | + Authorization: Bearer frp_abc123... | |
| 12 | + ``` | |
| 13 | + Tokens can be project-scoped (API Token) or user-scoped (Personal Access Token). | |
| 14 | + | |
| 15 | +2. **Session cookie** (for browser-based testing): | |
| 16 | + Log in via the web UI, then call API endpoints in the same browser session. | |
| 17 | + | |
| 18 | +## JSON API Endpoints | |
| 19 | + | |
| 20 | +Base URL: `/projects/<slug>/fossil/api/` | |
| 21 | + | |
| 22 | +### Project | |
| 23 | + | |
| 24 | +**GET /api/project** — Project metadata | |
| 25 | +```json | |
| 26 | +{"name": "FossilRepo", "slug": "fossilrepo", "description": "...", "visibility": "public", "star_count": 5} | |
| 27 | +``` | |
| 28 | + | |
| 29 | +### Timeline | |
| 30 | + | |
| 31 | +**GET /api/timeline** — Recent checkins | |
| 32 | +- `?page=1` — Page number | |
| 33 | +- `?per_page=25` — Items per page | |
| 34 | +- `?branch=trunk` — Filter by branch | |
| 35 | + | |
| 36 | +### Tickets | |
| 37 | + | |
| 38 | +**GET /api/tickets** — Ticket list | |
| 39 | +- `?status=Open` — Filter by status | |
| 40 | +- `?page=1&per_page=25` — Pagination | |
| 41 | + | |
| 42 | +**GET /api/tickets/\<uuid\>** — Single ticket with comments | |
| 43 | + | |
| 44 | +**GET /api/tickets/unclaimed** — Tickets available for agent claiming | |
| 45 | + | |
| 46 | +**POST /api/tickets/\<uuid\>/claim** — Claim ticket for exclusive work | |
| 47 | +```json | |
| 48 | +{"agent_id": "claude-session-abc"} | |
| 49 | +``` | |
| 50 | + | |
| 51 | +**POST /api/tickets/\<uuid\>/release** — Release a claim | |
| 52 | + | |
| 53 | +**POST /api/tickets/\<uuid\>/submit** — Submit completed work | |
| 54 | +```json | |
| 55 | +{"summary": "Fixed by...", "workspace": "agent-fix-123"} | |
| 56 | +``` | |
| 57 | + | |
| 58 | +### Wiki | |
| 59 | + | |
| 60 | +**GET /api/wiki** — Wiki page list | |
| 61 | +**GET /api/wiki/\<name\>** — Page content (raw + rendered HTML) | |
| 62 | + | |
| 63 | +### Branches and Tags | |
| 64 | + | |
| 65 | +**GET /api/branches** — All branches with open/closed status | |
| 66 | +**GET /api/tags** — All tags | |
| 67 | + | |
| 68 | +### Releases | |
| 69 | + | |
| 70 | +**GET /api/releases** — Release list with assets | |
| 71 | + | |
| 72 | +### Search | |
| 73 | + | |
| 74 | +**GET /api/search?q=term** — Search across checkins, tickets, wiki | |
| 75 | + | |
| 76 | +### CI Status | |
| 77 | + | |
| 78 | +**POST /api/status** — Report CI build status (Bearer token required) | |
| 79 | +```json | |
| 80 | +{"checkin": "abc123", "context": "ci/tests", "state": "success", "description": "All tests passed", "target_url": "https://ci.example.com/123"} | |
| 81 | +``` | |
| 82 | + | |
| 83 | +**GET /api/status/\<checkin_uuid\>/badge.svg** — SVG status badge | |
| 84 | + | |
| 85 | +### Batch API | |
| 86 | + | |
| 87 | +**POST /api/batch** — Execute up to 25 API calls in one request | |
| 88 | +```json | |
| 89 | +{"requests": [ | |
| 90 | + {"method": "GET", "path": "/api/timeline", "params": {"per_page": 5}}, | |
| 91 | + {"method": "GET", "path": "/api/tickets", "params": {"status": "Open"}}, | |
| 92 | + {"method": "GET", "path": "/api/wiki/Home"} | |
| 93 | +]} | |
| 94 | +``` | |
| 95 | + | |
| 96 | +### Agent Workspaces | |
| 97 | + | |
| 98 | +**GET /api/workspaces** — List active workspaces | |
| 99 | +**POST /api/workspaces/create** — Create isolated workspace | |
| 100 | +```json | |
| 101 | +{"name": "fix-auth-bug", "agent_id": "claude-abc", "description": "Fixing auth issue"} | |
| 102 | +``` | |
| 103 | +**GET /api/workspaces/\<name\>** — Workspace details | |
| 104 | +**POST /api/workspaces/\<name\>/commit** — Commit changes | |
| 105 | +**POST /api/workspaces/\<name\>/merge** — Merge back to trunk | |
| 106 | +**DELETE /api/workspaces/\<name\>/abandon** — Abandon workspace | |
| 107 | + | |
| 108 | +### Code Reviews | |
| 109 | + | |
| 110 | +**GET /api/reviews** — List reviews (filterable by status) | |
| 111 | +**POST /api/reviews/create** — Submit code for review | |
| 112 | +```json | |
| 113 | +{"title": "Fix null pointer", "description": "...", "diff": "--- a/...", "files_changed": ["src/auth.py"]} | |
| 114 | +``` | |
| 115 | +**GET /api/reviews/\<id\>** — Review with comments | |
| 116 | +**POST /api/reviews/\<id\>/comment** — Add review comment | |
| 117 | +**POST /api/reviews/\<id\>/approve** — Approve | |
| 118 | +**POST /api/reviews/\<id\>/request-changes** — Request changes | |
| 119 | +**POST /api/reviews/\<id\>/merge** — Merge approved review | |
| 120 | + | |
| 121 | +### Server-Sent Events | |
| 122 | + | |
| 123 | +**GET /api/events** — Real-time event stream | |
| 124 | +``` | |
| 125 | +event: checkin | |
| 126 | +data: {"uuid": "abc123", "user": "dev", "comment": "Fix bug"} | |
| 127 | + | |
| 128 | +event: claim | |
| 129 | +data: {"ticket": "def456", "agent": "claude-abc", "status": "claimed"} | |
| 130 | + | |
| 131 | +event: workspace | |
| 132 | +data: {"name": "fix-auth", "branch": "workspace/fix-auth", "status": "merged"} | |
| 133 | +``` | |
| 134 | + | |
| 135 | +## MCP Server | |
| 136 | + | |
| 137 | +The MCP (Model Context Protocol) server gives AI tools native access to FossilRepo. | |
| 138 | + | |
| 139 | +### Setup | |
| 140 | + | |
| 141 | +```bash | |
| 142 | +pip install fossilrepo | |
| 143 | +fossilrepo-mcp | |
| 144 | +``` | |
| 145 | + | |
| 146 | +### Claude Code Configuration | |
| 147 | + | |
| 148 | +```json | |
| 149 | +{ | |
| 150 | + "mcpServers": { | |
| 151 | + "fossilrepo": { | |
| 152 | + "command": "fossilrepo-mcp" | |
| 153 | + } | |
| 154 | + } | |
| 155 | +} | |
| 156 | +``` | |
| 157 | + | |
| 158 | +### Available Tools | |
| 159 | + | |
| 160 | +| Tool | Description | | |
| 161 | +|------|-------------| | |
| 162 | +| list_projects | List all projects | | |
| 163 | +| get_project | Project details with repo stats | | |
| 164 | +| browse_code | List files in a directory | | |
| 165 | +| read_file | Read file content | | |
| 166 | +| get_timeline | Recent checkins (optional branch filter) | | |
| 167 | +| get_checkin | Checkin detail with file changes | | |
| 168 | +| search_code | Search across checkins, tickets, wiki | | |
| 169 | +| list_tickets | List tickets (optional status filter) | | |
| 170 | +| get_ticket | Ticket detail with comments | | |
| 171 | +| create_ticket | Create a new ticket | | |
| 172 | +| update_ticket | Update ticket status, add comment | | |
| 173 | +| list_wiki_pages | List all wiki pages | | |
| 174 | +| get_wiki_page | Read wiki page content | | |
| 175 | +| list_branches | List all branches | | |
| 176 | +| get_file_blame | Blame annotations for a file | | |
| 177 | +| get_file_history | Commit history for a file | | |
| 178 | +| sql_query | Run read-only SQL against Fossil SQLite | | |
| 179 | + | |
| 180 | +All tools accept a `slug` parameter to identify the project. |
| --- a/docs/architecture/api-reference.md | |
| +++ b/docs/architecture/api-reference.md | |
| @@ -0,0 +1,180 @@ | |
| --- a/docs/architecture/api-reference.md | |
| +++ b/docs/architecture/api-reference.md | |
| @@ -0,0 +1,180 @@ | |
| 1 | # API Reference |
| 2 | |
| 3 | FossilRepo provides a JSON API for programmatic access and an MCP server for AI tool integration. |
| 4 | |
| 5 | ## Authentication |
| 6 | |
| 7 | All API endpoints accept authentication via: |
| 8 | |
| 9 | 1. **Bearer token** (recommended for automation): |
| 10 | ``` |
| 11 | Authorization: Bearer frp_abc123... |
| 12 | ``` |
| 13 | Tokens can be project-scoped (API Token) or user-scoped (Personal Access Token). |
| 14 | |
| 15 | 2. **Session cookie** (for browser-based testing): |
| 16 | Log in via the web UI, then call API endpoints in the same browser session. |
| 17 | |
| 18 | ## JSON API Endpoints |
| 19 | |
| 20 | Base URL: `/projects/<slug>/fossil/api/` |
| 21 | |
| 22 | ### Project |
| 23 | |
| 24 | **GET /api/project** — Project metadata |
| 25 | ```json |
| 26 | {"name": "FossilRepo", "slug": "fossilrepo", "description": "...", "visibility": "public", "star_count": 5} |
| 27 | ``` |
| 28 | |
| 29 | ### Timeline |
| 30 | |
| 31 | **GET /api/timeline** — Recent checkins |
| 32 | - `?page=1` — Page number |
| 33 | - `?per_page=25` — Items per page |
| 34 | - `?branch=trunk` — Filter by branch |
| 35 | |
| 36 | ### Tickets |
| 37 | |
| 38 | **GET /api/tickets** — Ticket list |
| 39 | - `?status=Open` — Filter by status |
| 40 | - `?page=1&per_page=25` — Pagination |
| 41 | |
| 42 | **GET /api/tickets/\<uuid\>** — Single ticket with comments |
| 43 | |
| 44 | **GET /api/tickets/unclaimed** — Tickets available for agent claiming |
| 45 | |
| 46 | **POST /api/tickets/\<uuid\>/claim** — Claim ticket for exclusive work |
| 47 | ```json |
| 48 | {"agent_id": "claude-session-abc"} |
| 49 | ``` |
| 50 | |
| 51 | **POST /api/tickets/\<uuid\>/release** — Release a claim |
| 52 | |
| 53 | **POST /api/tickets/\<uuid\>/submit** — Submit completed work |
| 54 | ```json |
| 55 | {"summary": "Fixed by...", "workspace": "agent-fix-123"} |
| 56 | ``` |
| 57 | |
| 58 | ### Wiki |
| 59 | |
| 60 | **GET /api/wiki** — Wiki page list |
| 61 | **GET /api/wiki/\<name\>** — Page content (raw + rendered HTML) |
| 62 | |
| 63 | ### Branches and Tags |
| 64 | |
| 65 | **GET /api/branches** — All branches with open/closed status |
| 66 | **GET /api/tags** — All tags |
| 67 | |
| 68 | ### Releases |
| 69 | |
| 70 | **GET /api/releases** — Release list with assets |
| 71 | |
| 72 | ### Search |
| 73 | |
| 74 | **GET /api/search?q=term** — Search across checkins, tickets, wiki |
| 75 | |
| 76 | ### CI Status |
| 77 | |
| 78 | **POST /api/status** — Report CI build status (Bearer token required) |
| 79 | ```json |
| 80 | {"checkin": "abc123", "context": "ci/tests", "state": "success", "description": "All tests passed", "target_url": "https://ci.example.com/123"} |
| 81 | ``` |
| 82 | |
| 83 | **GET /api/status/\<checkin_uuid\>/badge.svg** — SVG status badge |
| 84 | |
| 85 | ### Batch API |
| 86 | |
| 87 | **POST /api/batch** — Execute up to 25 API calls in one request |
| 88 | ```json |
| 89 | {"requests": [ |
| 90 | {"method": "GET", "path": "/api/timeline", "params": {"per_page": 5}}, |
| 91 | {"method": "GET", "path": "/api/tickets", "params": {"status": "Open"}}, |
| 92 | {"method": "GET", "path": "/api/wiki/Home"} |
| 93 | ]} |
| 94 | ``` |
| 95 | |
| 96 | ### Agent Workspaces |
| 97 | |
| 98 | **GET /api/workspaces** — List active workspaces |
| 99 | **POST /api/workspaces/create** — Create isolated workspace |
| 100 | ```json |
| 101 | {"name": "fix-auth-bug", "agent_id": "claude-abc", "description": "Fixing auth issue"} |
| 102 | ``` |
| 103 | **GET /api/workspaces/\<name\>** — Workspace details |
| 104 | **POST /api/workspaces/\<name\>/commit** — Commit changes |
| 105 | **POST /api/workspaces/\<name\>/merge** — Merge back to trunk |
| 106 | **DELETE /api/workspaces/\<name\>/abandon** — Abandon workspace |
| 107 | |
| 108 | ### Code Reviews |
| 109 | |
| 110 | **GET /api/reviews** — List reviews (filterable by status) |
| 111 | **POST /api/reviews/create** — Submit code for review |
| 112 | ```json |
| 113 | {"title": "Fix null pointer", "description": "...", "diff": "--- a/...", "files_changed": ["src/auth.py"]} |
| 114 | ``` |
| 115 | **GET /api/reviews/\<id\>** — Review with comments |
| 116 | **POST /api/reviews/\<id\>/comment** — Add review comment |
| 117 | **POST /api/reviews/\<id\>/approve** — Approve |
| 118 | **POST /api/reviews/\<id\>/request-changes** — Request changes |
| 119 | **POST /api/reviews/\<id\>/merge** — Merge approved review |
| 120 | |
| 121 | ### Server-Sent Events |
| 122 | |
| 123 | **GET /api/events** — Real-time event stream |
| 124 | ``` |
| 125 | event: checkin |
| 126 | data: {"uuid": "abc123", "user": "dev", "comment": "Fix bug"} |
| 127 | |
| 128 | event: claim |
| 129 | data: {"ticket": "def456", "agent": "claude-abc", "status": "claimed"} |
| 130 | |
| 131 | event: workspace |
| 132 | data: {"name": "fix-auth", "branch": "workspace/fix-auth", "status": "merged"} |
| 133 | ``` |
| 134 | |
| 135 | ## MCP Server |
| 136 | |
| 137 | The MCP (Model Context Protocol) server gives AI tools native access to FossilRepo. |
| 138 | |
| 139 | ### Setup |
| 140 | |
| 141 | ```bash |
| 142 | pip install fossilrepo |
| 143 | fossilrepo-mcp |
| 144 | ``` |
| 145 | |
| 146 | ### Claude Code Configuration |
| 147 | |
| 148 | ```json |
| 149 | { |
| 150 | "mcpServers": { |
| 151 | "fossilrepo": { |
| 152 | "command": "fossilrepo-mcp" |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | ``` |
| 157 | |
| 158 | ### Available Tools |
| 159 | |
| 160 | | Tool | Description | |
| 161 | |------|-------------| |
| 162 | | list_projects | List all projects | |
| 163 | | get_project | Project details with repo stats | |
| 164 | | browse_code | List files in a directory | |
| 165 | | read_file | Read file content | |
| 166 | | get_timeline | Recent checkins (optional branch filter) | |
| 167 | | get_checkin | Checkin detail with file changes | |
| 168 | | search_code | Search across checkins, tickets, wiki | |
| 169 | | list_tickets | List tickets (optional status filter) | |
| 170 | | get_ticket | Ticket detail with comments | |
| 171 | | create_ticket | Create a new ticket | |
| 172 | | update_ticket | Update ticket status, add comment | |
| 173 | | list_wiki_pages | List all wiki pages | |
| 174 | | get_wiki_page | Read wiki page content | |
| 175 | | list_branches | List all branches | |
| 176 | | get_file_blame | Blame annotations for a file | |
| 177 | | get_file_history | Commit history for a file | |
| 178 | | sql_query | Run read-only SQL against Fossil SQLite | |
| 179 | |
| 180 | All tools accept a `slug` parameter to identify the project. |
+131
-88
| --- docs/architecture/overview.md | ||
| +++ docs/architecture/overview.md | ||
| @@ -1,98 +1,141 @@ | ||
| 1 | -# Architecture Overview | |
| 2 | - | |
| 3 | -Fossilrepo is a thin orchestration layer around Fossil SCM. Fossil does the heavy lifting -- fossilrepo handles provisioning, routing, backups, and the management UI. | |
| 4 | - | |
| 5 | -## System Diagram | |
| 6 | - | |
| 7 | -```mermaid | |
| 8 | -graph TB | |
| 9 | - subgraph Internet | |
| 10 | - User[User / Browser] | |
| 11 | - end | |
| 12 | - | |
| 13 | - subgraph Fossilrepo Server | |
| 14 | - Caddy[Caddy<br/>SSL + Routing] | |
| 15 | - Django[Django<br/>Management UI] | |
| 16 | - Fossil[Fossil Server<br/>--repolist] | |
| 17 | - Celery[Celery Workers] | |
| 18 | - Redis[Redis] | |
| 19 | - Postgres[(PostgreSQL)] | |
| 20 | - Litestream[Litestream] | |
| 21 | - end | |
| 22 | - | |
| 23 | - subgraph Storage | |
| 24 | - Repos["/data/repos/<br/>*.fossil files"] | |
| 25 | - S3[(S3 / MinIO)] | |
| 26 | - end | |
| 27 | - | |
| 28 | - subgraph Mirrors | |
| 29 | - GitHub[GitHub] | |
| 30 | - GitLab[GitLab] | |
| 31 | - end | |
| 32 | - | |
| 33 | - User --> Caddy | |
| 34 | - Caddy -->|"app.domain.com"| Django | |
| 35 | - Caddy -->|"repo.domain.com"| Fossil | |
| 36 | - Django --> Postgres | |
| 37 | - Django --> Redis | |
| 38 | - Celery --> Redis | |
| 39 | - Celery -->|sync bridge| GitHub | |
| 40 | - Celery -->|sync bridge| GitLab | |
| 41 | - Fossil --> Repos | |
| 42 | - Litestream --> Repos | |
| 43 | - Litestream --> S3 | |
| 1 | +# Architecture | |
| 2 | + | |
| 3 | +## Overview | |
| 4 | + | |
| 5 | +FossilRepo is a Django web application that wraps Fossil SCM repositories with a modern UI. It reads `.fossil` files directly as SQLite databases for speed, and uses the `fossil` CLI binary for write operations to maintain artifact integrity. | |
| 6 | + | |
| 7 | +``` | |
| 8 | +Browser (HTMX + Alpine.js + Tailwind CSS) | |
| 9 | + | | |
| 10 | + v | |
| 11 | +Django 5 (views, ORM, permissions) | |
| 12 | + | | |
| 13 | + |-- FossilReader (direct SQLite reads, ?mode=ro) | |
| 14 | + |-- FossilCLI (subprocess for writes: commit, ticket, wiki, push/pull) | |
| 15 | + |-- fossil http (CGI proxy for clone/push/pull) | |
| 16 | + | | |
| 17 | + |-- PostgreSQL 16 (app data: users, orgs, teams, projects, settings) | |
| 18 | + |-- Redis 7 (Celery broker, cache) | |
| 19 | + |-- Celery (background: metadata sync, git mirror, webhooks, digest) | |
| 20 | + | | |
| 21 | + v | |
| 22 | +.fossil files (SQLite: code + wiki + tickets + forum + technotes) | |
| 23 | + | | |
| 24 | + v | |
| 25 | +Litestream --> S3 (continuous SQLite replication) | |
| 26 | +``` | |
| 27 | + | |
| 28 | +## Core Components | |
| 29 | + | |
| 30 | +### FossilReader | |
| 31 | + | |
| 32 | +Opens `.fossil` repository files directly as read-only SQLite databases. No network calls to a running Fossil server. Python's `sqlite3` module with `?mode=ro` URI. | |
| 33 | + | |
| 34 | +Handles: | |
| 35 | +- Blob decompression (zlib with 4-byte size prefix) | |
| 36 | +- Delta chain resolution (Fossil's delta-encoded artifacts) | |
| 37 | +- Julian day timestamp conversion | |
| 38 | +- Timeline queries, file tree at any checkin, ticket/wiki/forum reads | |
| 39 | +- Commit activity aggregation, contributor stats, search | |
| 40 | + | |
| 41 | +### FossilCLI | |
| 42 | + | |
| 43 | +Thin subprocess wrapper around the `fossil` binary. Used for all write operations: | |
| 44 | +- Repository init, clone, push, pull, sync | |
| 45 | +- Ticket create/change, wiki create/commit | |
| 46 | +- Technote creation, blame, Pikchr rendering | |
| 47 | +- Git export for mirror sync | |
| 48 | +- Tarball and zip archive generation | |
| 49 | +- Unversioned file management | |
| 50 | +- Artifact shunning | |
| 51 | + | |
| 52 | +All calls set `USER=fossilrepo` in the environment and call `ensure_default_user()` to prevent "cannot figure out who you are" errors. | |
| 53 | + | |
| 54 | +### HTTP Sync Proxy | |
| 55 | + | |
| 56 | +The `fossil_xfer` view proxies Fossil's wire protocol through Django. Clients clone/push/pull via: | |
| 57 | + | |
| 58 | +``` | |
| 59 | +fossil clone http://your-server/projects/<slug>/fossil/xfer repo.fossil | |
| 60 | +``` | |
| 61 | + | |
| 62 | +Django handles authentication and access control. Public repos allow anonymous pull (no `--localauth`). Authenticated users with write access get full push via `--localauth`. Branch protection rules are enforced at this layer. | |
| 63 | + | |
| 64 | +### SSH Sync | |
| 65 | + | |
| 66 | +An `sshd` instance runs on port 2222 with a restricted `fossil-shell` forced command. Users upload SSH public keys via their profile. The `authorized_keys` file is regenerated from the database on key add/remove. | |
| 67 | + | |
| 68 | +``` | |
| 69 | +fossil clone ssh://fossil@host:2222/<slug> repo.fossil | |
| 44 | 70 | ``` |
| 45 | 71 | |
| 46 | -## Components | |
| 72 | +## Data Architecture | |
| 73 | + | |
| 74 | +### Two Databases | |
| 75 | + | |
| 76 | +1. **PostgreSQL** — application state: users, organizations, teams, projects, releases, webhooks, API tokens, workspace claims, code reviews, notification preferences | |
| 77 | +2. **Fossil .fossil files** — repository data: code history, tickets, wiki, forum, technotes, unversioned files | |
| 78 | + | |
| 79 | +### Model Base Classes | |
| 80 | + | |
| 81 | +- `Tracking` (abstract) — `version`, `created_at/by`, `updated_at/by`, `deleted_at/by`, `history` (django-simple-history) | |
| 82 | +- `BaseCoreModel(Tracking)` — adds `guid` (UUID4), `name`, `slug` (auto-generated), `description` | |
| 83 | +- Soft deletes only: `obj.soft_delete(user=request.user)`, never `.delete()` | |
| 84 | +- `ActiveManager` on `objects` excludes soft-deleted; `all_objects` includes them | |
| 85 | + | |
| 86 | +### Permission Model | |
| 87 | + | |
| 88 | +Two layers: | |
| 89 | +1. **Org-level roles** (Admin/Manager/Developer/Viewer) — Django Groups with permission bundles, assignable per user | |
| 90 | +2. **Project-level RBAC** (read/write/admin) — per team, via ProjectTeam model | |
| 91 | + | |
| 92 | +Project visibility: | |
| 93 | +- **Public** — anyone can read (including anonymous) | |
| 94 | +- **Internal** — authenticated users can read | |
| 95 | +- **Private** — team members only | |
| 96 | + | |
| 97 | +### Encryption | |
| 98 | + | |
| 99 | +SSH keys and OAuth tokens encrypted at rest using Fernet (AES-128-CBC + HMAC-SHA256), keyed from Django's `SECRET_KEY`. Implemented as `EncryptedTextField` in `core/fields.py`. | |
| 100 | + | |
| 101 | +## Infrastructure | |
| 102 | + | |
| 103 | +### Docker (Omnibus) | |
| 104 | + | |
| 105 | +Single multi-stage Dockerfile: | |
| 106 | +1. Stage 1: compile Fossil 2.24 from source (Debian bookworm) | |
| 107 | +2. Stage 2: Python 3.12 runtime with Fossil binary, sshd, gosu | |
| 108 | + | |
| 109 | +Entrypoint starts sshd as root, drops to unprivileged `app` user for gunicorn. | |
| 47 | 110 | |
| 48 | -### Fossil Server | |
| 111 | +### Celery Tasks | |
| 49 | 112 | |
| 50 | -A single `fossil server --repolist /data/repos/` process serves all repositories. Each `.fossil` file is a self-contained SQLite database with VCS history, issues, wiki, and forum. | |
| 51 | - | |
| 52 | -Adding a new repo is just `fossil init /data/repos/name.fossil` -- no restart needed. | |
| 113 | +| Task | Schedule | Purpose | | |
| 114 | +|------|----------|---------| | |
| 115 | +| sync_metadata | Every 5 min | Update repo stats (size, checkin count) | | |
| 116 | +| check_upstream | Every 15 min | Check for new upstream artifacts | | |
| 117 | +| dispatch_notifications | Every 5 min | Send pending email notifications | | |
| 118 | +| send_digest (daily) | Every 24h | Daily notification digest | | |
| 119 | +| send_digest (weekly) | Every 7d | Weekly notification digest | | |
| 120 | +| dispatch_webhook | On event | Deliver webhook with retry | | |
| 121 | +| run_git_sync | On schedule | Git mirror export | | |
| 53 | 122 | |
| 54 | 123 | ### Caddy |
| 55 | 124 | |
| 56 | -Handles SSL termination and subdomain routing: | |
| 57 | - | |
| 58 | -- `your-domain.com` routes to the Django management UI | |
| 59 | -- `reponame.your-domain.com` routes directly to Fossil's web UI | |
| 60 | - | |
| 61 | -Caddy automatically provisions and renews Let's Encrypt certificates. | |
| 62 | - | |
| 63 | -### Django Management Layer | |
| 64 | - | |
| 65 | -Provides the administrative interface: | |
| 66 | - | |
| 67 | -- Repository lifecycle (create, configure, archive) | |
| 68 | -- User and organization management | |
| 69 | -- Dashboard and analytics | |
| 70 | -- Sync bridge configuration | |
| 71 | - | |
| 72 | -Django uses HTMX for interactive UI without a JavaScript framework. | |
| 125 | +SSL termination and subdomain routing. Each repo can get its own subdomain. | |
| 73 | 126 | |
| 74 | 127 | ### Litestream |
| 75 | 128 | |
| 76 | -Continuously replicates every `.fossil` SQLite file to S3-compatible storage. Provides: | |
| 77 | - | |
| 78 | -- **Continuous backup** -- WAL frames replicated in near-real-time | |
| 79 | -- **Point-in-time recovery** -- restore to any moment, not just snapshots | |
| 80 | -- **Zero-config per repo** -- new `.fossil` files are picked up automatically | |
| 81 | - | |
| 82 | -### Celery Workers | |
| 83 | - | |
| 84 | -Handle background tasks: | |
| 85 | - | |
| 86 | -- Sync bridge execution (Fossil to Git mirroring) | |
| 87 | -- Scheduled sync jobs | |
| 88 | -- Upstream pull operations | |
| 89 | - | |
| 90 | -## Data Flow | |
| 91 | - | |
| 92 | -1. **User pushes to Fossil** -- standard `fossil push` or `fossil sync` | |
| 93 | -2. **Fossil writes to `.fossil` file** -- SQLite transactions | |
| 94 | -3. **Litestream replicates** -- WAL frames streamed to S3 | |
| 95 | -4. **Sync bridge runs** -- Celery task mirrors changes to Git remotes | |
| 96 | -5. **Django reflects state** -- reads from Fossil SQLite for dashboards | |
| 97 | - | |
| 98 | -Fossil is always the source of truth. Everything else is derived. | |
| 129 | +Continuous SQLite-to-S3 replication for `.fossil` files. Point-in-time recovery. | |
| 130 | + | |
| 131 | +## Django Apps | |
| 132 | + | |
| 133 | +| App | Purpose | | |
| 134 | +|-----|---------| | |
| 135 | +| `core` | Base models, permissions, pagination, sanitization, encryption | | |
| 136 | +| `accounts` | Login/logout, SSH keys, user profile, personal access tokens | | |
| 137 | +| `organization` | Org settings, teams, members, roles | | |
| 138 | +| `projects` | Projects, project groups, project stars, team assignment | | |
| 139 | +| `pages` | FossilRepo KB (knowledge base articles) | | |
| 140 | +| `fossil` | Everything Fossil: reader, CLI, views, sync, webhooks, releases, CI, workspaces, reviews | | |
| 141 | +| `mcp_server` | MCP server for AI tool integration | | |
| 99 | 142 | |
| 100 | 143 | ADDED docs/features.md |
| 101 | 144 | ADDED docs/getting-started/administration.md |
| --- docs/architecture/overview.md | |
| +++ docs/architecture/overview.md | |
| @@ -1,98 +1,141 @@ | |
| 1 | # Architecture Overview |
| 2 | |
| 3 | Fossilrepo is a thin orchestration layer around Fossil SCM. Fossil does the heavy lifting -- fossilrepo handles provisioning, routing, backups, and the management UI. |
| 4 | |
| 5 | ## System Diagram |
| 6 | |
| 7 | ```mermaid |
| 8 | graph TB |
| 9 | subgraph Internet |
| 10 | User[User / Browser] |
| 11 | end |
| 12 | |
| 13 | subgraph Fossilrepo Server |
| 14 | Caddy[Caddy<br/>SSL + Routing] |
| 15 | Django[Django<br/>Management UI] |
| 16 | Fossil[Fossil Server<br/>--repolist] |
| 17 | Celery[Celery Workers] |
| 18 | Redis[Redis] |
| 19 | Postgres[(PostgreSQL)] |
| 20 | Litestream[Litestream] |
| 21 | end |
| 22 | |
| 23 | subgraph Storage |
| 24 | Repos["/data/repos/<br/>*.fossil files"] |
| 25 | S3[(S3 / MinIO)] |
| 26 | end |
| 27 | |
| 28 | subgraph Mirrors |
| 29 | GitHub[GitHub] |
| 30 | GitLab[GitLab] |
| 31 | end |
| 32 | |
| 33 | User --> Caddy |
| 34 | Caddy -->|"app.domain.com"| Django |
| 35 | Caddy -->|"repo.domain.com"| Fossil |
| 36 | Django --> Postgres |
| 37 | Django --> Redis |
| 38 | Celery --> Redis |
| 39 | Celery -->|sync bridge| GitHub |
| 40 | Celery -->|sync bridge| GitLab |
| 41 | Fossil --> Repos |
| 42 | Litestream --> Repos |
| 43 | Litestream --> S3 |
| 44 | ``` |
| 45 | |
| 46 | ## Components |
| 47 | |
| 48 | ### Fossil Server |
| 49 | |
| 50 | A single `fossil server --repolist /data/repos/` process serves all repositories. Each `.fossil` file is a self-contained SQLite database with VCS history, issues, wiki, and forum. |
| 51 | |
| 52 | Adding a new repo is just `fossil init /data/repos/name.fossil` -- no restart needed. |
| 53 | |
| 54 | ### Caddy |
| 55 | |
| 56 | Handles SSL termination and subdomain routing: |
| 57 | |
| 58 | - `your-domain.com` routes to the Django management UI |
| 59 | - `reponame.your-domain.com` routes directly to Fossil's web UI |
| 60 | |
| 61 | Caddy automatically provisions and renews Let's Encrypt certificates. |
| 62 | |
| 63 | ### Django Management Layer |
| 64 | |
| 65 | Provides the administrative interface: |
| 66 | |
| 67 | - Repository lifecycle (create, configure, archive) |
| 68 | - User and organization management |
| 69 | - Dashboard and analytics |
| 70 | - Sync bridge configuration |
| 71 | |
| 72 | Django uses HTMX for interactive UI without a JavaScript framework. |
| 73 | |
| 74 | ### Litestream |
| 75 | |
| 76 | Continuously replicates every `.fossil` SQLite file to S3-compatible storage. Provides: |
| 77 | |
| 78 | - **Continuous backup** -- WAL frames replicated in near-real-time |
| 79 | - **Point-in-time recovery** -- restore to any moment, not just snapshots |
| 80 | - **Zero-config per repo** -- new `.fossil` files are picked up automatically |
| 81 | |
| 82 | ### Celery Workers |
| 83 | |
| 84 | Handle background tasks: |
| 85 | |
| 86 | - Sync bridge execution (Fossil to Git mirroring) |
| 87 | - Scheduled sync jobs |
| 88 | - Upstream pull operations |
| 89 | |
| 90 | ## Data Flow |
| 91 | |
| 92 | 1. **User pushes to Fossil** -- standard `fossil push` or `fossil sync` |
| 93 | 2. **Fossil writes to `.fossil` file** -- SQLite transactions |
| 94 | 3. **Litestream replicates** -- WAL frames streamed to S3 |
| 95 | 4. **Sync bridge runs** -- Celery task mirrors changes to Git remotes |
| 96 | 5. **Django reflects state** -- reads from Fossil SQLite for dashboards |
| 97 | |
| 98 | Fossil is always the source of truth. Everything else is derived. |
| 99 | |
| 100 | DDED docs/features.md |
| 101 | DDED docs/getting-started/administration.md |
| --- docs/architecture/overview.md | |
| +++ docs/architecture/overview.md | |
| @@ -1,98 +1,141 @@ | |
| 1 | # Architecture |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | FossilRepo is a Django web application that wraps Fossil SCM repositories with a modern UI. It reads `.fossil` files directly as SQLite databases for speed, and uses the `fossil` CLI binary for write operations to maintain artifact integrity. |
| 6 | |
| 7 | ``` |
| 8 | Browser (HTMX + Alpine.js + Tailwind CSS) |
| 9 | | |
| 10 | v |
| 11 | Django 5 (views, ORM, permissions) |
| 12 | | |
| 13 | |-- FossilReader (direct SQLite reads, ?mode=ro) |
| 14 | |-- FossilCLI (subprocess for writes: commit, ticket, wiki, push/pull) |
| 15 | |-- fossil http (CGI proxy for clone/push/pull) |
| 16 | | |
| 17 | |-- PostgreSQL 16 (app data: users, orgs, teams, projects, settings) |
| 18 | |-- Redis 7 (Celery broker, cache) |
| 19 | |-- Celery (background: metadata sync, git mirror, webhooks, digest) |
| 20 | | |
| 21 | v |
| 22 | .fossil files (SQLite: code + wiki + tickets + forum + technotes) |
| 23 | | |
| 24 | v |
| 25 | Litestream --> S3 (continuous SQLite replication) |
| 26 | ``` |
| 27 | |
| 28 | ## Core Components |
| 29 | |
| 30 | ### FossilReader |
| 31 | |
| 32 | Opens `.fossil` repository files directly as read-only SQLite databases. No network calls to a running Fossil server. Python's `sqlite3` module with `?mode=ro` URI. |
| 33 | |
| 34 | Handles: |
| 35 | - Blob decompression (zlib with 4-byte size prefix) |
| 36 | - Delta chain resolution (Fossil's delta-encoded artifacts) |
| 37 | - Julian day timestamp conversion |
| 38 | - Timeline queries, file tree at any checkin, ticket/wiki/forum reads |
| 39 | - Commit activity aggregation, contributor stats, search |
| 40 | |
| 41 | ### FossilCLI |
| 42 | |
| 43 | Thin subprocess wrapper around the `fossil` binary. Used for all write operations: |
| 44 | - Repository init, clone, push, pull, sync |
| 45 | - Ticket create/change, wiki create/commit |
| 46 | - Technote creation, blame, Pikchr rendering |
| 47 | - Git export for mirror sync |
| 48 | - Tarball and zip archive generation |
| 49 | - Unversioned file management |
| 50 | - Artifact shunning |
| 51 | |
| 52 | All calls set `USER=fossilrepo` in the environment and call `ensure_default_user()` to prevent "cannot figure out who you are" errors. |
| 53 | |
| 54 | ### HTTP Sync Proxy |
| 55 | |
| 56 | The `fossil_xfer` view proxies Fossil's wire protocol through Django. Clients clone/push/pull via: |
| 57 | |
| 58 | ``` |
| 59 | fossil clone http://your-server/projects/<slug>/fossil/xfer repo.fossil |
| 60 | ``` |
| 61 | |
| 62 | Django handles authentication and access control. Public repos allow anonymous pull (no `--localauth`). Authenticated users with write access get full push via `--localauth`. Branch protection rules are enforced at this layer. |
| 63 | |
| 64 | ### SSH Sync |
| 65 | |
| 66 | An `sshd` instance runs on port 2222 with a restricted `fossil-shell` forced command. Users upload SSH public keys via their profile. The `authorized_keys` file is regenerated from the database on key add/remove. |
| 67 | |
| 68 | ``` |
| 69 | fossil clone ssh://fossil@host:2222/<slug> repo.fossil |
| 70 | ``` |
| 71 | |
| 72 | ## Data Architecture |
| 73 | |
| 74 | ### Two Databases |
| 75 | |
| 76 | 1. **PostgreSQL** — application state: users, organizations, teams, projects, releases, webhooks, API tokens, workspace claims, code reviews, notification preferences |
| 77 | 2. **Fossil .fossil files** — repository data: code history, tickets, wiki, forum, technotes, unversioned files |
| 78 | |
| 79 | ### Model Base Classes |
| 80 | |
| 81 | - `Tracking` (abstract) — `version`, `created_at/by`, `updated_at/by`, `deleted_at/by`, `history` (django-simple-history) |
| 82 | - `BaseCoreModel(Tracking)` — adds `guid` (UUID4), `name`, `slug` (auto-generated), `description` |
| 83 | - Soft deletes only: `obj.soft_delete(user=request.user)`, never `.delete()` |
| 84 | - `ActiveManager` on `objects` excludes soft-deleted; `all_objects` includes them |
| 85 | |
| 86 | ### Permission Model |
| 87 | |
| 88 | Two layers: |
| 89 | 1. **Org-level roles** (Admin/Manager/Developer/Viewer) — Django Groups with permission bundles, assignable per user |
| 90 | 2. **Project-level RBAC** (read/write/admin) — per team, via ProjectTeam model |
| 91 | |
| 92 | Project visibility: |
| 93 | - **Public** — anyone can read (including anonymous) |
| 94 | - **Internal** — authenticated users can read |
| 95 | - **Private** — team members only |
| 96 | |
| 97 | ### Encryption |
| 98 | |
| 99 | SSH keys and OAuth tokens encrypted at rest using Fernet (AES-128-CBC + HMAC-SHA256), keyed from Django's `SECRET_KEY`. Implemented as `EncryptedTextField` in `core/fields.py`. |
| 100 | |
| 101 | ## Infrastructure |
| 102 | |
| 103 | ### Docker (Omnibus) |
| 104 | |
| 105 | Single multi-stage Dockerfile: |
| 106 | 1. Stage 1: compile Fossil 2.24 from source (Debian bookworm) |
| 107 | 2. Stage 2: Python 3.12 runtime with Fossil binary, sshd, gosu |
| 108 | |
| 109 | Entrypoint starts sshd as root, drops to unprivileged `app` user for gunicorn. |
| 110 | |
| 111 | ### Celery Tasks |
| 112 | |
| 113 | | Task | Schedule | Purpose | |
| 114 | |------|----------|---------| |
| 115 | | sync_metadata | Every 5 min | Update repo stats (size, checkin count) | |
| 116 | | check_upstream | Every 15 min | Check for new upstream artifacts | |
| 117 | | dispatch_notifications | Every 5 min | Send pending email notifications | |
| 118 | | send_digest (daily) | Every 24h | Daily notification digest | |
| 119 | | send_digest (weekly) | Every 7d | Weekly notification digest | |
| 120 | | dispatch_webhook | On event | Deliver webhook with retry | |
| 121 | | run_git_sync | On schedule | Git mirror export | |
| 122 | |
| 123 | ### Caddy |
| 124 | |
| 125 | SSL termination and subdomain routing. Each repo can get its own subdomain. |
| 126 | |
| 127 | ### Litestream |
| 128 | |
| 129 | Continuous SQLite-to-S3 replication for `.fossil` files. Point-in-time recovery. |
| 130 | |
| 131 | ## Django Apps |
| 132 | |
| 133 | | App | Purpose | |
| 134 | |-----|---------| |
| 135 | | `core` | Base models, permissions, pagination, sanitization, encryption | |
| 136 | | `accounts` | Login/logout, SSH keys, user profile, personal access tokens | |
| 137 | | `organization` | Org settings, teams, members, roles | |
| 138 | | `projects` | Projects, project groups, project stars, team assignment | |
| 139 | | `pages` | FossilRepo KB (knowledge base articles) | |
| 140 | | `fossil` | Everything Fossil: reader, CLI, views, sync, webhooks, releases, CI, workspaces, reviews | |
| 141 | | `mcp_server` | MCP server for AI tool integration | |
| 142 | |
| 143 | DDED docs/features.md |
| 144 | DDED docs/getting-started/administration.md |
+205
| --- a/docs/features.md | ||
| +++ b/docs/features.md | ||
| @@ -0,0 +1,205 @@ | ||
| 1 | +# Features | |
| 2 | + | |
| 3 | +## Code Browser | |
| 4 | +- Directory navigation with breadcrumbs and file size display | |
| 5 | +- Syntax-highlighted source view with line numbers and permalinks | |
| 6 | +- Copy-link popover on line number click | |
| 7 | +- Blame with age-based coloring (newest = red, oldest = gray) | |
| 8 | +- File history showing all checkins that touched a file | |
| 9 | +- Raw file download | |
| 10 | +- Rendered preview for Markdown, HTML, and other markup | |
| 11 | +- README auto-rendering at directory level | |
| 12 | + | |
| 13 | +## Timeline | |
| 14 | +- DAG graph with fork/merge connectors and color-coded branches (8-color palette) | |
| 15 | +- Merge commit diamonds, leaf indicators (open circles) | |
| 16 | +- Date headers grouping commits by day | |
| 17 | +- Keyboard navigation (j/k to move, Enter to open) | |
| 18 | +- HTMX infinite scroll for seamless loading | |
| 19 | +- Event type filtering (checkins, wiki, tickets) | |
| 20 | +- RSS feed | |
| 21 | + | |
| 22 | +## Diffs | |
| 23 | +- Unified and side-by-side view (toggle with localStorage preference) | |
| 24 | +- Syntax highlighting via highlight.js (auto-detected from file extension) | |
| 25 | +- Color-coded additions (green) and deletions (red) | |
| 26 | +- Line-level permalinks | |
| 27 | +- Compare any two checkins | |
| 28 | +- Fossil delta decoding for accurate diff computation | |
| 29 | + | |
| 30 | +## Tickets | |
| 31 | +- Full CRUD: create, edit, close/reopen, add comments | |
| 32 | +- Filter by status, type, priority, severity | |
| 33 | +- Pagination with configurable per-page (25/50/100) | |
| 34 | +- Live search via HTMX | |
| 35 | +- CSV export | |
| 36 | +- Custom field definitions (text, textarea, select, checkbox, date, URL) | |
| 37 | +- Custom SQL ticket reports with injection prevention | |
| 38 | +- Per-page count selector | |
| 39 | + | |
| 40 | +## Wiki | |
| 41 | +- Markdown + Fossil wiki markup + raw HTML rendering | |
| 42 | +- Pikchr diagram rendering (via fossil CLI) | |
| 43 | +- Create and edit pages | |
| 44 | +- Right-sidebar table of contents | |
| 45 | +- Internal link rewriting (Fossil URLs mapped to app URLs) | |
| 46 | +- Footnotes, tables, fenced code blocks | |
| 47 | + | |
| 48 | +## Forum | |
| 49 | +- Threaded discussions (Fossil-native + Django-backed posts) | |
| 50 | +- Create new threads with markdown body | |
| 51 | +- Post replies with threading | |
| 52 | +- Merged view showing both Fossil and Django posts | |
| 53 | + | |
| 54 | +## Releases | |
| 55 | +- Versioned releases with tag names and markdown changelogs | |
| 56 | +- Source code archives: tar.gz and zip (via fossil tarball/zip) | |
| 57 | +- File attachments with download counts | |
| 58 | +- Draft and prerelease support | |
| 59 | +- CRUD for authorized users | |
| 60 | + | |
| 61 | +## Technotes | |
| 62 | +- Create and edit developer journal entries | |
| 63 | +- Markdown body with preview | |
| 64 | +- Timestamped, shown in timeline | |
| 65 | + | |
| 66 | +## Unversioned Files | |
| 67 | +- Browse Fossil's unversioned content (equivalent to Git LFS) | |
| 68 | +- File list with size and date | |
| 69 | +- Download individual files | |
| 70 | +- Admin upload via fossil uv CLI | |
| 71 | + | |
| 72 | +## Branches, Tags, Technotes | |
| 73 | +- List all branches with open/closed status | |
| 74 | +- List all tags | |
| 75 | +- Searchable and paginated | |
| 76 | + | |
| 77 | +## Search | |
| 78 | +- Full-text search across checkins, tickets, and wiki pages | |
| 79 | +- Global search shortcut (/ key) | |
| 80 | +- Per-project scoped search | |
| 81 | + | |
| 82 | +## Sync | |
| 83 | +- Pull from upstream Fossil remotes | |
| 84 | +- Push to downstream Fossil remotes | |
| 85 | +- Bidirectional sync | |
| 86 | +- Git mirror to GitHub/GitLab via OAuth or SSH key auth | |
| 87 | +- Multiple mirrors per repo, each with own schedule and direction | |
| 88 | +- Configurable sync modes: on-change, scheduled (cron), both, disabled | |
| 89 | +- Clone/push/pull over HTTP (fossil http CGI proxy) | |
| 90 | +- Clone/push/pull over SSH (port 2222, forced command) | |
| 91 | + | |
| 92 | +## Webhooks | |
| 93 | +- Outbound HTTP webhooks on checkin, ticket, wiki, release events | |
| 94 | +- HMAC-SHA256 signed payloads | |
| 95 | +- Exponential backoff retry (3 attempts) | |
| 96 | +- Delivery log with response status and timing | |
| 97 | +- Per-project webhook configuration | |
| 98 | + | |
| 99 | +## CI Status Checks | |
| 100 | +- External API for CI systems to POST build status per checkin | |
| 101 | +- Bearer token authentication | |
| 102 | +- SVG badge endpoint for embedding in READMEs | |
| 103 | +- Status display on checkin detail page (green/red/yellow icons) | |
| 104 | + | |
| 105 | +## Releases | |
| 106 | +- Create/edit/delete versioned releases | |
| 107 | +- Link to Fossil checkin | |
| 108 | +- Markdown changelog body | |
| 109 | +- Source code download (tar.gz, zip) | |
| 110 | +- File attachments with download tracking | |
| 111 | +- Draft and prerelease flags | |
| 112 | + | |
| 113 | +## Organization Management | |
| 114 | +- Single-org model with settings, website, description | |
| 115 | +- Member management: create, edit, deactivate, change password | |
| 116 | +- Team management: create, assign members | |
| 117 | +- Project groups: organize related repos under a group header | |
| 118 | +- Project-level team roles: read, write, admin | |
| 119 | + | |
| 120 | +## Roles and Permissions | |
| 121 | +- Predefined roles: Admin, Manager, Developer, Viewer | |
| 122 | +- Custom role creation with permission picker (grouped by app) | |
| 123 | +- Role assignment on user create/edit | |
| 124 | +- Permissions synced to Django Groups automatically | |
| 125 | +- Two-layer model: org-level roles + project-level RBAC | |
| 126 | + | |
| 127 | +## User Profiles | |
| 128 | +- Personal profile page: name, email, @handle, bio, location, website | |
| 129 | +- SSH key management with encrypted storage | |
| 130 | +- Personal access tokens (frp_ prefix, hash-only storage) | |
| 131 | +- Notification preferences (immediate/daily/weekly/off + event toggles) | |
| 132 | +- Change password | |
| 133 | + | |
| 134 | +## Project Features | |
| 135 | +- Project starring with counts | |
| 136 | +- Explore/discover page for public projects (sort by stars/recent/name) | |
| 137 | +- Project groups for organizing related repos | |
| 138 | +- Public/internal/private visibility | |
| 139 | +- Anonymous access for public repos (all read views) | |
| 140 | + | |
| 141 | +## API Tokens and Deploy Keys | |
| 142 | +- Project-scoped API tokens with SHA-256 hashed storage | |
| 143 | +- Token shown once on creation, never stored in plaintext | |
| 144 | +- Configurable permissions and expiry | |
| 145 | +- Last-used tracking | |
| 146 | + | |
| 147 | +## Branch Protection | |
| 148 | +- Per-branch protection rules with glob pattern matching | |
| 149 | +- Restrict push to admins only | |
| 150 | +- Required CI status check contexts | |
| 151 | +- Enforced on HTTP sync, CLI push/sync, and SSH push | |
| 152 | + | |
| 153 | +## Artifact Shunning | |
| 154 | +- Admin UI for permanently removing artifacts | |
| 155 | +- Type-to-confirm safety (must enter first 8 chars of UUID) | |
| 156 | +- Calls fossil shun CLI | |
| 157 | +- Irreversible with clear warning | |
| 158 | + | |
| 159 | +## SQLite Explorer | |
| 160 | +- Visual schema map with category-colored table cards | |
| 161 | +- SVG relationship graph showing Fossil's internal table connections | |
| 162 | +- HTMX-powered table browser with column definitions and paginated data | |
| 163 | +- Custom SQL query runner (SELECT only, validated against injection) | |
| 164 | +- Admin-only access | |
| 165 | + | |
| 166 | +## Audit Log | |
| 167 | +- Unified view of all model changes via django-simple-history | |
| 168 | +- Filter by model type (Project, Organization, Team, Repository) | |
| 169 | +- Shows user, action (Created/Changed/Deleted), timestamp | |
| 170 | +- Superuser/org-admin access | |
| 171 | + | |
| 172 | +## Email Notifications | |
| 173 | +- HTML email templates (dark themed, inline CSS for email clients) | |
| 174 | +- Immediate delivery per event | |
| 175 | +- Daily/weekly digest mode | |
| 176 | +- Per-user event type toggles (checkins, tickets, wiki, releases, forum) | |
| 177 | +- Unsubscribe links | |
| 178 | + | |
| 179 | +## Agentic Development Platform | |
| 180 | +- MCP server with 17 tools for AI assistant integration | |
| 181 | +- JSON API: 10+ read endpoints with Bearer token auth | |
| 182 | +- Batch API: execute up to 25 API calls in one request | |
| 183 | +- Agent workspaces: isolated Fossil branches per agent | |
| 184 | +- Atomic ticket claiming for multi-agent coordination | |
| 185 | +- Server-Sent Events for real-time notifications | |
| 186 | +- Code review API: submit diffs, comment, approve, merge | |
| 187 | + | |
| 188 | +## UI/UX | |
| 189 | +- Dark/light theme with system preference detection | |
| 190 | +- Collapsible sidebar with project tree navigation | |
| 191 | +- Keyboard shortcuts (j/k, Enter, /, ?) | |
| 192 | +- Consistent pagination (25/50/100 per-page selector) across all lists | |
| 193 | +- HTMX live search with 300ms debounce | |
| 194 | +- Mobile responsive (slide-out drawer) | |
| 195 | +- Custom branded error pages (403, 404, 500) | |
| 196 | +- Public nav for anonymous users (logo, Explore, Sign in) | |
| 197 | + | |
| 198 | +## Infrastructure | |
| 199 | +- Omnibus Docker image (Fossil compiled from source) | |
| 200 | +- Multi-arch builds (amd64 + arm64) | |
| 201 | +- Caddy for SSL termination and subdomain routing | |
| 202 | +- Litestream for continuous SQLite-to-S3 replication | |
| 203 | +- Supply chain attestations (SLSA provenance + SBOM) | |
| 204 | +- Non-root container execution (gosu privilege dropping) | |
| 205 | +- Celery Beat for scheduled tasks |
| --- a/docs/features.md | |
| +++ b/docs/features.md | |
| @@ -0,0 +1,205 @@ | |
| --- a/docs/features.md | |
| +++ b/docs/features.md | |
| @@ -0,0 +1,205 @@ | |
| 1 | # Features |
| 2 | |
| 3 | ## Code Browser |
| 4 | - Directory navigation with breadcrumbs and file size display |
| 5 | - Syntax-highlighted source view with line numbers and permalinks |
| 6 | - Copy-link popover on line number click |
| 7 | - Blame with age-based coloring (newest = red, oldest = gray) |
| 8 | - File history showing all checkins that touched a file |
| 9 | - Raw file download |
| 10 | - Rendered preview for Markdown, HTML, and other markup |
| 11 | - README auto-rendering at directory level |
| 12 | |
| 13 | ## Timeline |
| 14 | - DAG graph with fork/merge connectors and color-coded branches (8-color palette) |
| 15 | - Merge commit diamonds, leaf indicators (open circles) |
| 16 | - Date headers grouping commits by day |
| 17 | - Keyboard navigation (j/k to move, Enter to open) |
| 18 | - HTMX infinite scroll for seamless loading |
| 19 | - Event type filtering (checkins, wiki, tickets) |
| 20 | - RSS feed |
| 21 | |
| 22 | ## Diffs |
| 23 | - Unified and side-by-side view (toggle with localStorage preference) |
| 24 | - Syntax highlighting via highlight.js (auto-detected from file extension) |
| 25 | - Color-coded additions (green) and deletions (red) |
| 26 | - Line-level permalinks |
| 27 | - Compare any two checkins |
| 28 | - Fossil delta decoding for accurate diff computation |
| 29 | |
| 30 | ## Tickets |
| 31 | - Full CRUD: create, edit, close/reopen, add comments |
| 32 | - Filter by status, type, priority, severity |
| 33 | - Pagination with configurable per-page (25/50/100) |
| 34 | - Live search via HTMX |
| 35 | - CSV export |
| 36 | - Custom field definitions (text, textarea, select, checkbox, date, URL) |
| 37 | - Custom SQL ticket reports with injection prevention |
| 38 | - Per-page count selector |
| 39 | |
| 40 | ## Wiki |
| 41 | - Markdown + Fossil wiki markup + raw HTML rendering |
| 42 | - Pikchr diagram rendering (via fossil CLI) |
| 43 | - Create and edit pages |
| 44 | - Right-sidebar table of contents |
| 45 | - Internal link rewriting (Fossil URLs mapped to app URLs) |
| 46 | - Footnotes, tables, fenced code blocks |
| 47 | |
| 48 | ## Forum |
| 49 | - Threaded discussions (Fossil-native + Django-backed posts) |
| 50 | - Create new threads with markdown body |
| 51 | - Post replies with threading |
| 52 | - Merged view showing both Fossil and Django posts |
| 53 | |
| 54 | ## Releases |
| 55 | - Versioned releases with tag names and markdown changelogs |
| 56 | - Source code archives: tar.gz and zip (via fossil tarball/zip) |
| 57 | - File attachments with download counts |
| 58 | - Draft and prerelease support |
| 59 | - CRUD for authorized users |
| 60 | |
| 61 | ## Technotes |
| 62 | - Create and edit developer journal entries |
| 63 | - Markdown body with preview |
| 64 | - Timestamped, shown in timeline |
| 65 | |
| 66 | ## Unversioned Files |
| 67 | - Browse Fossil's unversioned content (equivalent to Git LFS) |
| 68 | - File list with size and date |
| 69 | - Download individual files |
| 70 | - Admin upload via fossil uv CLI |
| 71 | |
| 72 | ## Branches, Tags, Technotes |
| 73 | - List all branches with open/closed status |
| 74 | - List all tags |
| 75 | - Searchable and paginated |
| 76 | |
| 77 | ## Search |
| 78 | - Full-text search across checkins, tickets, and wiki pages |
| 79 | - Global search shortcut (/ key) |
| 80 | - Per-project scoped search |
| 81 | |
| 82 | ## Sync |
| 83 | - Pull from upstream Fossil remotes |
| 84 | - Push to downstream Fossil remotes |
| 85 | - Bidirectional sync |
| 86 | - Git mirror to GitHub/GitLab via OAuth or SSH key auth |
| 87 | - Multiple mirrors per repo, each with own schedule and direction |
| 88 | - Configurable sync modes: on-change, scheduled (cron), both, disabled |
| 89 | - Clone/push/pull over HTTP (fossil http CGI proxy) |
| 90 | - Clone/push/pull over SSH (port 2222, forced command) |
| 91 | |
| 92 | ## Webhooks |
| 93 | - Outbound HTTP webhooks on checkin, ticket, wiki, release events |
| 94 | - HMAC-SHA256 signed payloads |
| 95 | - Exponential backoff retry (3 attempts) |
| 96 | - Delivery log with response status and timing |
| 97 | - Per-project webhook configuration |
| 98 | |
| 99 | ## CI Status Checks |
| 100 | - External API for CI systems to POST build status per checkin |
| 101 | - Bearer token authentication |
| 102 | - SVG badge endpoint for embedding in READMEs |
| 103 | - Status display on checkin detail page (green/red/yellow icons) |
| 104 | |
| 105 | ## Releases |
| 106 | - Create/edit/delete versioned releases |
| 107 | - Link to Fossil checkin |
| 108 | - Markdown changelog body |
| 109 | - Source code download (tar.gz, zip) |
| 110 | - File attachments with download tracking |
| 111 | - Draft and prerelease flags |
| 112 | |
| 113 | ## Organization Management |
| 114 | - Single-org model with settings, website, description |
| 115 | - Member management: create, edit, deactivate, change password |
| 116 | - Team management: create, assign members |
| 117 | - Project groups: organize related repos under a group header |
| 118 | - Project-level team roles: read, write, admin |
| 119 | |
| 120 | ## Roles and Permissions |
| 121 | - Predefined roles: Admin, Manager, Developer, Viewer |
| 122 | - Custom role creation with permission picker (grouped by app) |
| 123 | - Role assignment on user create/edit |
| 124 | - Permissions synced to Django Groups automatically |
| 125 | - Two-layer model: org-level roles + project-level RBAC |
| 126 | |
| 127 | ## User Profiles |
| 128 | - Personal profile page: name, email, @handle, bio, location, website |
| 129 | - SSH key management with encrypted storage |
| 130 | - Personal access tokens (frp_ prefix, hash-only storage) |
| 131 | - Notification preferences (immediate/daily/weekly/off + event toggles) |
| 132 | - Change password |
| 133 | |
| 134 | ## Project Features |
| 135 | - Project starring with counts |
| 136 | - Explore/discover page for public projects (sort by stars/recent/name) |
| 137 | - Project groups for organizing related repos |
| 138 | - Public/internal/private visibility |
| 139 | - Anonymous access for public repos (all read views) |
| 140 | |
| 141 | ## API Tokens and Deploy Keys |
| 142 | - Project-scoped API tokens with SHA-256 hashed storage |
| 143 | - Token shown once on creation, never stored in plaintext |
| 144 | - Configurable permissions and expiry |
| 145 | - Last-used tracking |
| 146 | |
| 147 | ## Branch Protection |
| 148 | - Per-branch protection rules with glob pattern matching |
| 149 | - Restrict push to admins only |
| 150 | - Required CI status check contexts |
| 151 | - Enforced on HTTP sync, CLI push/sync, and SSH push |
| 152 | |
| 153 | ## Artifact Shunning |
| 154 | - Admin UI for permanently removing artifacts |
| 155 | - Type-to-confirm safety (must enter first 8 chars of UUID) |
| 156 | - Calls fossil shun CLI |
| 157 | - Irreversible with clear warning |
| 158 | |
| 159 | ## SQLite Explorer |
| 160 | - Visual schema map with category-colored table cards |
| 161 | - SVG relationship graph showing Fossil's internal table connections |
| 162 | - HTMX-powered table browser with column definitions and paginated data |
| 163 | - Custom SQL query runner (SELECT only, validated against injection) |
| 164 | - Admin-only access |
| 165 | |
| 166 | ## Audit Log |
| 167 | - Unified view of all model changes via django-simple-history |
| 168 | - Filter by model type (Project, Organization, Team, Repository) |
| 169 | - Shows user, action (Created/Changed/Deleted), timestamp |
| 170 | - Superuser/org-admin access |
| 171 | |
| 172 | ## Email Notifications |
| 173 | - HTML email templates (dark themed, inline CSS for email clients) |
| 174 | - Immediate delivery per event |
| 175 | - Daily/weekly digest mode |
| 176 | - Per-user event type toggles (checkins, tickets, wiki, releases, forum) |
| 177 | - Unsubscribe links |
| 178 | |
| 179 | ## Agentic Development Platform |
| 180 | - MCP server with 17 tools for AI assistant integration |
| 181 | - JSON API: 10+ read endpoints with Bearer token auth |
| 182 | - Batch API: execute up to 25 API calls in one request |
| 183 | - Agent workspaces: isolated Fossil branches per agent |
| 184 | - Atomic ticket claiming for multi-agent coordination |
| 185 | - Server-Sent Events for real-time notifications |
| 186 | - Code review API: submit diffs, comment, approve, merge |
| 187 | |
| 188 | ## UI/UX |
| 189 | - Dark/light theme with system preference detection |
| 190 | - Collapsible sidebar with project tree navigation |
| 191 | - Keyboard shortcuts (j/k, Enter, /, ?) |
| 192 | - Consistent pagination (25/50/100 per-page selector) across all lists |
| 193 | - HTMX live search with 300ms debounce |
| 194 | - Mobile responsive (slide-out drawer) |
| 195 | - Custom branded error pages (403, 404, 500) |
| 196 | - Public nav for anonymous users (logo, Explore, Sign in) |
| 197 | |
| 198 | ## Infrastructure |
| 199 | - Omnibus Docker image (Fossil compiled from source) |
| 200 | - Multi-arch builds (amd64 + arm64) |
| 201 | - Caddy for SSL termination and subdomain routing |
| 202 | - Litestream for continuous SQLite-to-S3 replication |
| 203 | - Supply chain attestations (SLSA provenance + SBOM) |
| 204 | - Non-root container execution (gosu privilege dropping) |
| 205 | - Celery Beat for scheduled tasks |
| --- a/docs/getting-started/administration.md | ||
| +++ b/docs/getting-started/administration.md | ||
| @@ -0,0 +1,131 @@ | ||
| 1 | +# Administration | |
| 2 | + | |
| 3 | +## User Management | |
| 4 | + | |
| 5 | +Navigate to **Admin > Members** in the sidebar. | |
| 6 | + | |
| 7 | +### Creating Users | |
| 8 | +1. Click "Create User" | |
| 9 | +2. Fill in username, email, name, password | |
| 10 | +3. Optionally assign an org role | |
| 11 | +4. User is automatically added as an organization member | |
| 12 | + | |
| 13 | +### Editing Users | |
| 14 | +Click a username to view their profile, then "Edit" to change: | |
| 15 | +- Name, email | |
| 16 | +- Active/inactive status | |
| 17 | +- Staff status (access to Super Admin) | |
| 18 | +- Org role assignment | |
| 19 | + | |
| 20 | +### Changing Passwords | |
| 21 | +From the user detail page, click "Change Password". Admins can change any user's password. Users can change their own password from their profile page. | |
| 22 | + | |
| 23 | +### Deactivating Users | |
| 24 | +Edit the user and uncheck "Active". This prevents login without deleting the account. The user's history and contributions are preserved. | |
| 25 | + | |
| 26 | +## Roles | |
| 27 | + | |
| 28 | +Navigate to **Admin > Roles** in the sidebar. | |
| 29 | + | |
| 30 | +### Predefined Roles | |
| 31 | + | |
| 32 | +| Role | Access Level | | |
| 33 | +|------|-------------| | |
| 34 | +| Admin | Full access to everything | | |
| 35 | +| Manager | Manage projects, teams, members, pages | | |
| 36 | +| Developer | Contribute: view projects, create tickets | | |
| 37 | +| Viewer | Read-only access to all content | | |
| 38 | + | |
| 39 | +### Custom Roles | |
| 40 | +Click "Create Role" to define a custom role with a specific permission set. The permission picker groups permissions by app (Organization, Projects, Pages, Fossil). | |
| 41 | + | |
| 42 | +### Initializing Roles | |
| 43 | +If no roles exist, click "Initialize Roles" to create the four predefined roles. This runs the `seed_roles` management command. | |
| 44 | + | |
| 45 | +### How Roles Work | |
| 46 | +Each role maps to a Django Group with the same permissions. When a user is assigned a role, their previous role group is removed and the new one added. Permissions are synced automatically. | |
| 47 | + | |
| 48 | +## Teams | |
| 49 | + | |
| 50 | +Navigate to **Admin > Teams** in the sidebar. | |
| 51 | + | |
| 52 | +Teams are groups of users that can be assigned to projects with specific access levels. | |
| 53 | + | |
| 54 | +### Creating Teams | |
| 55 | +1. Click "New Team" | |
| 56 | +2. Enter name and description | |
| 57 | +3. Add members from the user list | |
| 58 | + | |
| 59 | +### Assigning Teams to Projects | |
| 60 | +1. Go to the project overview | |
| 61 | +2. Click the project name > Teams section | |
| 62 | +3. Click "Add Team" | |
| 63 | +4. Select team and role (read/write/admin) | |
| 64 | + | |
| 65 | +## Project Groups | |
| 66 | + | |
| 67 | +Navigate to **Admin > Groups** in the sidebar. | |
| 68 | + | |
| 69 | +Groups organize related projects together in the sidebar. For example, "Fossil SCM" group might contain the source code repo, forum repo, and docs repo. | |
| 70 | + | |
| 71 | +### Creating Groups | |
| 72 | +1. Click "Create Group" | |
| 73 | +2. Enter name and description | |
| 74 | +3. Assign projects to the group via the project edit form | |
| 75 | + | |
| 76 | +## Organization Settings | |
| 77 | + | |
| 78 | +Navigate to **Admin > Settings** in the sidebar. | |
| 79 | + | |
| 80 | +Configure the organization name, website, and description. This appears in the site header and various admin pages. | |
| 81 | + | |
| 82 | +## Audit Log | |
| 83 | + | |
| 84 | +Navigate to **Admin > Audit Log** in the sidebar. | |
| 85 | + | |
| 86 | +Shows all model changes across the application, powered by django-simple-history. Filter by model type to see changes to specific entities. | |
| 87 | + | |
| 88 | +## Super Admin | |
| 89 | + | |
| 90 | +Navigate to **Admin > Super Admin** in the sidebar. | |
| 91 | + | |
| 92 | +This is Django's built-in admin interface. Use it for: | |
| 93 | +- Direct database access to any model | |
| 94 | +- Constance runtime settings | |
| 95 | +- Celery task results and beat schedule | |
| 96 | +- Advanced permission management | |
| 97 | +- Data import/export | |
| 98 | + | |
| 99 | +Most day-to-day operations should be done through the main UI, not Super Admin. | |
| 100 | + | |
| 101 | +## Project Settings | |
| 102 | + | |
| 103 | +Each project has its own settings tab (visible to project admins): | |
| 104 | + | |
| 105 | +### Repository Info | |
| 106 | +- Filename, file size, project code, checkin/ticket/wiki counts | |
| 107 | + | |
| 108 | +### Remote URL | |
| 109 | +- Configure upstream Fossil remote for pull/push/sync | |
| 110 | + | |
| 111 | +### Clone URLs | |
| 112 | +- HTTP clone URL for users | |
| 113 | +- SSH clone URL | |
| 114 | + | |
| 115 | +### Tokens | |
| 116 | +- Project-scoped API tokens for CI/CD integration | |
| 117 | + | |
| 118 | +### Branch Protection | |
| 119 | +- Per-branch rules: restrict push, require CI status checks | |
| 120 | + | |
| 121 | +### Webhooks | |
| 122 | +- Outbound webhooks on repository events | |
| 123 | + | |
| 124 | +## Notification Settings | |
| 125 | + | |
| 126 | +Users configure their own notification preferences at **/auth/notifications/**: | |
| 127 | + | |
| 128 | +- **Delivery mode**: Immediate, Daily Digest, Weekly Digest, Off | |
| 129 | +- **Event types**: Checkins, Tickets, Wiki, Releases, Forum | |
| 130 | + | |
| 131 | +Admins can view user preferences via Super Admin. |
| --- a/docs/getting-started/administration.md | |
| +++ b/docs/getting-started/administration.md | |
| @@ -0,0 +1,131 @@ | |
| --- a/docs/getting-started/administration.md | |
| +++ b/docs/getting-started/administration.md | |
| @@ -0,0 +1,131 @@ | |
| 1 | # Administration |
| 2 | |
| 3 | ## User Management |
| 4 | |
| 5 | Navigate to **Admin > Members** in the sidebar. |
| 6 | |
| 7 | ### Creating Users |
| 8 | 1. Click "Create User" |
| 9 | 2. Fill in username, email, name, password |
| 10 | 3. Optionally assign an org role |
| 11 | 4. User is automatically added as an organization member |
| 12 | |
| 13 | ### Editing Users |
| 14 | Click a username to view their profile, then "Edit" to change: |
| 15 | - Name, email |
| 16 | - Active/inactive status |
| 17 | - Staff status (access to Super Admin) |
| 18 | - Org role assignment |
| 19 | |
| 20 | ### Changing Passwords |
| 21 | From the user detail page, click "Change Password". Admins can change any user's password. Users can change their own password from their profile page. |
| 22 | |
| 23 | ### Deactivating Users |
| 24 | Edit the user and uncheck "Active". This prevents login without deleting the account. The user's history and contributions are preserved. |
| 25 | |
| 26 | ## Roles |
| 27 | |
| 28 | Navigate to **Admin > Roles** in the sidebar. |
| 29 | |
| 30 | ### Predefined Roles |
| 31 | |
| 32 | | Role | Access Level | |
| 33 | |------|-------------| |
| 34 | | Admin | Full access to everything | |
| 35 | | Manager | Manage projects, teams, members, pages | |
| 36 | | Developer | Contribute: view projects, create tickets | |
| 37 | | Viewer | Read-only access to all content | |
| 38 | |
| 39 | ### Custom Roles |
| 40 | Click "Create Role" to define a custom role with a specific permission set. The permission picker groups permissions by app (Organization, Projects, Pages, Fossil). |
| 41 | |
| 42 | ### Initializing Roles |
| 43 | If no roles exist, click "Initialize Roles" to create the four predefined roles. This runs the `seed_roles` management command. |
| 44 | |
| 45 | ### How Roles Work |
| 46 | Each role maps to a Django Group with the same permissions. When a user is assigned a role, their previous role group is removed and the new one added. Permissions are synced automatically. |
| 47 | |
| 48 | ## Teams |
| 49 | |
| 50 | Navigate to **Admin > Teams** in the sidebar. |
| 51 | |
| 52 | Teams are groups of users that can be assigned to projects with specific access levels. |
| 53 | |
| 54 | ### Creating Teams |
| 55 | 1. Click "New Team" |
| 56 | 2. Enter name and description |
| 57 | 3. Add members from the user list |
| 58 | |
| 59 | ### Assigning Teams to Projects |
| 60 | 1. Go to the project overview |
| 61 | 2. Click the project name > Teams section |
| 62 | 3. Click "Add Team" |
| 63 | 4. Select team and role (read/write/admin) |
| 64 | |
| 65 | ## Project Groups |
| 66 | |
| 67 | Navigate to **Admin > Groups** in the sidebar. |
| 68 | |
| 69 | Groups organize related projects together in the sidebar. For example, "Fossil SCM" group might contain the source code repo, forum repo, and docs repo. |
| 70 | |
| 71 | ### Creating Groups |
| 72 | 1. Click "Create Group" |
| 73 | 2. Enter name and description |
| 74 | 3. Assign projects to the group via the project edit form |
| 75 | |
| 76 | ## Organization Settings |
| 77 | |
| 78 | Navigate to **Admin > Settings** in the sidebar. |
| 79 | |
| 80 | Configure the organization name, website, and description. This appears in the site header and various admin pages. |
| 81 | |
| 82 | ## Audit Log |
| 83 | |
| 84 | Navigate to **Admin > Audit Log** in the sidebar. |
| 85 | |
| 86 | Shows all model changes across the application, powered by django-simple-history. Filter by model type to see changes to specific entities. |
| 87 | |
| 88 | ## Super Admin |
| 89 | |
| 90 | Navigate to **Admin > Super Admin** in the sidebar. |
| 91 | |
| 92 | This is Django's built-in admin interface. Use it for: |
| 93 | - Direct database access to any model |
| 94 | - Constance runtime settings |
| 95 | - Celery task results and beat schedule |
| 96 | - Advanced permission management |
| 97 | - Data import/export |
| 98 | |
| 99 | Most day-to-day operations should be done through the main UI, not Super Admin. |
| 100 | |
| 101 | ## Project Settings |
| 102 | |
| 103 | Each project has its own settings tab (visible to project admins): |
| 104 | |
| 105 | ### Repository Info |
| 106 | - Filename, file size, project code, checkin/ticket/wiki counts |
| 107 | |
| 108 | ### Remote URL |
| 109 | - Configure upstream Fossil remote for pull/push/sync |
| 110 | |
| 111 | ### Clone URLs |
| 112 | - HTTP clone URL for users |
| 113 | - SSH clone URL |
| 114 | |
| 115 | ### Tokens |
| 116 | - Project-scoped API tokens for CI/CD integration |
| 117 | |
| 118 | ### Branch Protection |
| 119 | - Per-branch rules: restrict push, require CI status checks |
| 120 | |
| 121 | ### Webhooks |
| 122 | - Outbound webhooks on repository events |
| 123 | |
| 124 | ## Notification Settings |
| 125 | |
| 126 | Users configure their own notification preferences at **/auth/notifications/**: |
| 127 | |
| 128 | - **Delivery mode**: Immediate, Daily Digest, Weekly Digest, Off |
| 129 | - **Event types**: Checkins, Tickets, Wiki, Releases, Forum |
| 130 | |
| 131 | Admins can view user preferences via Super Admin. |
+108
-110
| --- docs/getting-started/installation.md | ||
| +++ docs/getting-started/installation.md | ||
| @@ -1,115 +1,113 @@ | ||
| 1 | -# Installation | |
| 1 | +# Setup Guide | |
| 2 | 2 | |
| 3 | -## Clone the Repository | |
| 3 | +## Quick Start (Docker) | |
| 4 | 4 | |
| 5 | 5 | ```bash |
| 6 | 6 | git clone https://github.com/ConflictHQ/fossilrepo.git |
| 7 | 7 | cd fossilrepo |
| 8 | -``` | |
| 9 | - | |
| 10 | -## Environment Configuration | |
| 11 | - | |
| 12 | -Copy the example environment file and configure it: | |
| 13 | - | |
| 14 | -```bash | |
| 15 | -cp .env.example .env | |
| 16 | -``` | |
| 17 | - | |
| 18 | -Edit `.env` with your settings: | |
| 19 | - | |
| 20 | -```ini | |
| 21 | -# Django | |
| 22 | -SECRET_KEY=your-secret-key-here | |
| 23 | -DEBUG=True | |
| 24 | -ALLOWED_HOSTS=localhost,127.0.0.1 | |
| 25 | - | |
| 26 | -# Database | |
| 27 | -POSTGRES_DB=fossilrepo | |
| 28 | -POSTGRES_USER=fossilrepo | |
| 29 | -POSTGRES_PASSWORD=your-db-password | |
| 30 | - | |
| 31 | -# Redis | |
| 32 | -REDIS_URL=redis://redis:6379/0 | |
| 33 | - | |
| 34 | -# Fossil | |
| 35 | -FOSSIL_REPO_DIR=/data/repos | |
| 36 | -FOSSIL_BASE_URL=https://your-domain.com | |
| 37 | -``` | |
| 38 | - | |
| 39 | -## Start the Stack | |
| 40 | - | |
| 41 | -### Development | |
| 42 | - | |
| 43 | -```bash | |
| 44 | -# Build and start all services | |
| 45 | -make build | |
| 46 | - | |
| 47 | -# Run database migrations | |
| 48 | -make migrate | |
| 49 | - | |
| 50 | -# Create an admin user | |
| 51 | -make superuser | |
| 52 | - | |
| 53 | -# Load sample data (optional) | |
| 54 | -make seed | |
| 55 | -``` | |
| 56 | - | |
| 57 | -The development stack includes: | |
| 58 | - | |
| 59 | -- Django dev server on `http://localhost:8000` | |
| 60 | -- PostgreSQL 16 | |
| 61 | -- Redis | |
| 62 | -- Celery worker + beat | |
| 63 | -- Mailpit on `http://localhost:8025` | |
| 64 | - | |
| 65 | -### Production | |
| 66 | - | |
| 67 | -For production, you'll also configure Caddy and Litestream: | |
| 68 | - | |
| 69 | -```bash | |
| 70 | -# Copy production configs | |
| 71 | -cp docker/Caddyfile.example docker/Caddyfile | |
| 72 | -cp docker/litestream.yml.example docker/litestream.yml | |
| 73 | - | |
| 74 | -# Edit with your domain and S3 credentials | |
| 75 | -# Then start with the production compose file | |
| 76 | -docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d | |
| 77 | -``` | |
| 78 | - | |
| 79 | -## Verify Installation | |
| 80 | - | |
| 81 | -```bash | |
| 82 | -# Check all services are running | |
| 83 | -docker compose ps | |
| 84 | - | |
| 85 | -# Hit the health endpoint | |
| 86 | -curl http://localhost:8000/health/ | |
| 87 | - | |
| 88 | -# Open the dashboard | |
| 89 | -open http://localhost:8000 | |
| 90 | -``` | |
| 91 | - | |
| 92 | -!!! success "You should see" | |
| 93 | - The fossilrepo dashboard with navigation, login page, and (after seeding) sample repositories. | |
| 94 | - | |
| 95 | -## Common Issues | |
| 96 | - | |
| 97 | -??? question "Port 8000 already in use" | |
| 98 | - Change the Django port mapping in `docker-compose.yml`: | |
| 99 | - ```yaml | |
| 100 | - ports: | |
| 101 | - - "8001:8000" | |
| 102 | - ``` | |
| 103 | - | |
| 104 | -??? question "Database connection refused" | |
| 105 | - Ensure PostgreSQL has started before Django: | |
| 106 | - ```bash | |
| 107 | - docker compose logs postgres | |
| 108 | - ``` | |
| 109 | - The Django container waits for Postgres to be ready, but network issues on some Docker Desktop versions can cause timeouts. Restart with `make down && make up`. | |
| 110 | - | |
| 111 | -??? question "Permission denied on /data/repos" | |
| 112 | - The Fossil repo directory needs to be writable by the container user: | |
| 113 | - ```bash | |
| 114 | - sudo chown -R 1000:1000 /data/repos | |
| 115 | - ``` | |
| 8 | +docker compose up -d --build | |
| 9 | +docker compose exec backend python manage.py migrate | |
| 10 | +docker compose exec backend python manage.py seed | |
| 11 | +docker compose exec backend python manage.py seed_roles | |
| 12 | +``` | |
| 13 | + | |
| 14 | +Visit http://localhost:8000. Login: `admin` / `admin`. | |
| 15 | + | |
| 16 | +## Default Users | |
| 17 | + | |
| 18 | +| Username | Password | Role | | |
| 19 | +|----------|----------|------| | |
| 20 | +| admin | admin | Superuser | | |
| 21 | +| viewer | viewer | View-only | | |
| 22 | +| role-admin | role-admin | Admin role | | |
| 23 | +| role-manager | role-manager | Manager role | | |
| 24 | +| role-developer | role-developer | Developer role | | |
| 25 | +| role-viewer | role-viewer | Viewer role | | |
| 26 | + | |
| 27 | +## Configuration | |
| 28 | + | |
| 29 | +### Environment Variables | |
| 30 | + | |
| 31 | +Copy `.env.example` to `.env` and customize. Key variables: | |
| 32 | + | |
| 33 | +| Variable | Default | Description | | |
| 34 | +|----------|---------|-------------| | |
| 35 | +| DJANGO_SECRET_KEY | change-me | **Required in production** | | |
| 36 | +| DJANGO_DEBUG | false | Enable debug mode | | |
| 37 | +| DJANGO_ALLOWED_HOSTS | localhost | Comma-separated hostnames | | |
| 38 | +| POSTGRES_DB | fossilrepo | Database name | | |
| 39 | +| POSTGRES_USER | dbadmin | Database user | | |
| 40 | +| POSTGRES_PASSWORD | Password123 | Database password | | |
| 41 | +| REDIS_URL | redis://localhost:6379/1 | Redis connection | | |
| 42 | +| EMAIL_HOST | localhost | SMTP server | | |
| 43 | +| CORS_ALLOWED_ORIGINS | http://localhost:8000 | CORS origins | | |
| 44 | +| SENTRY_DSN | (empty) | Sentry error tracking | | |
| 45 | + | |
| 46 | +### Runtime Settings (Constance) | |
| 47 | + | |
| 48 | +Configurable via Django admin at `/admin/constance/config/`: | |
| 49 | + | |
| 50 | +| Setting | Default | Description | | |
| 51 | +|---------|---------|-------------| | |
| 52 | +| SITE_NAME | Fossilrepo | Display name | | |
| 53 | +| FOSSIL_DATA_DIR | /data/repos | Where .fossil files live | | |
| 54 | +| FOSSIL_BINARY_PATH | fossil | Path to fossil binary | | |
| 55 | +| FOSSIL_STORE_IN_DB | false | Store snapshots via Django file storage | | |
| 56 | +| FOSSIL_S3_TRACKING | false | Track S3 replication | | |
| 57 | +| GIT_SYNC_MODE | disabled | Default sync mode | | |
| 58 | +| GIT_SYNC_SCHEDULE | */15 * * * * | Default cron for git sync | | |
| 59 | + | |
| 60 | +### OAuth (GitHub/GitLab) | |
| 61 | + | |
| 62 | +For Git mirror sync via OAuth: | |
| 63 | +1. Create an OAuth App on GitHub/GitLab | |
| 64 | +2. Set Client ID and Secret in Constance (Django admin) | |
| 65 | +3. Callback URLs are handled automatically | |
| 66 | + | |
| 67 | +## Adding Repositories | |
| 68 | + | |
| 69 | +### Create Empty | |
| 70 | + | |
| 71 | +1. Go to Projects > + New | |
| 72 | +2. Fill in name and description | |
| 73 | +3. Select "Create empty repository" | |
| 74 | +4. Done — empty .fossil file created | |
| 75 | + | |
| 76 | +### Clone from Fossil URL | |
| 77 | + | |
| 78 | +1. Go to Projects > + New | |
| 79 | +2. Select "Clone from Fossil URL" | |
| 80 | +3. Enter the URL (e.g., `https://fossil-scm.org/home`) | |
| 81 | +4. FossilRepo clones the repo and links it | |
| 82 | + | |
| 83 | +### Clone Fossil SCM (Example) | |
| 84 | + | |
| 85 | +```bash | |
| 86 | +# Clone the official Fossil SCM repo | |
| 87 | +docker compose exec backend fossil clone https://fossil-scm.org/home /data/repos/fossil-scm.fossil | |
| 88 | +``` | |
| 89 | + | |
| 90 | +Then create a Project in the UI and link it to the file. | |
| 91 | + | |
| 92 | +## Production Deployment | |
| 93 | + | |
| 94 | +See `.env.production.example` for production settings. Key steps: | |
| 95 | + | |
| 96 | +1. Set a strong `DJANGO_SECRET_KEY` | |
| 97 | +2. Set `DJANGO_DEBUG=false` | |
| 98 | +3. Configure `DJANGO_ALLOWED_HOSTS` to your domain | |
| 99 | +4. Use a proper database password | |
| 100 | +5. Configure email (SES, SMTP) | |
| 101 | +6. Set up HTTPS via Caddy or reverse proxy | |
| 102 | +7. Configure S3 for Litestream backups (optional) | |
| 103 | + | |
| 104 | +## Ports | |
| 105 | + | |
| 106 | +| Port | Service | | |
| 107 | +|------|---------| | |
| 108 | +| 8000 | Django (HTTP) | | |
| 109 | +| 2222 | SSH (Fossil sync) | | |
| 110 | +| 5432 | PostgreSQL | | |
| 111 | +| 6379 | Redis | | |
| 112 | +| 1025 | Mailpit SMTP (dev) | | |
| 113 | +| 8025 | Mailpit UI (dev) | | |
| 116 | 114 |
| --- docs/getting-started/installation.md | |
| +++ docs/getting-started/installation.md | |
| @@ -1,115 +1,113 @@ | |
| 1 | # Installation |
| 2 | |
| 3 | ## Clone the Repository |
| 4 | |
| 5 | ```bash |
| 6 | git clone https://github.com/ConflictHQ/fossilrepo.git |
| 7 | cd fossilrepo |
| 8 | ``` |
| 9 | |
| 10 | ## Environment Configuration |
| 11 | |
| 12 | Copy the example environment file and configure it: |
| 13 | |
| 14 | ```bash |
| 15 | cp .env.example .env |
| 16 | ``` |
| 17 | |
| 18 | Edit `.env` with your settings: |
| 19 | |
| 20 | ```ini |
| 21 | # Django |
| 22 | SECRET_KEY=your-secret-key-here |
| 23 | DEBUG=True |
| 24 | ALLOWED_HOSTS=localhost,127.0.0.1 |
| 25 | |
| 26 | # Database |
| 27 | POSTGRES_DB=fossilrepo |
| 28 | POSTGRES_USER=fossilrepo |
| 29 | POSTGRES_PASSWORD=your-db-password |
| 30 | |
| 31 | # Redis |
| 32 | REDIS_URL=redis://redis:6379/0 |
| 33 | |
| 34 | # Fossil |
| 35 | FOSSIL_REPO_DIR=/data/repos |
| 36 | FOSSIL_BASE_URL=https://your-domain.com |
| 37 | ``` |
| 38 | |
| 39 | ## Start the Stack |
| 40 | |
| 41 | ### Development |
| 42 | |
| 43 | ```bash |
| 44 | # Build and start all services |
| 45 | make build |
| 46 | |
| 47 | # Run database migrations |
| 48 | make migrate |
| 49 | |
| 50 | # Create an admin user |
| 51 | make superuser |
| 52 | |
| 53 | # Load sample data (optional) |
| 54 | make seed |
| 55 | ``` |
| 56 | |
| 57 | The development stack includes: |
| 58 | |
| 59 | - Django dev server on `http://localhost:8000` |
| 60 | - PostgreSQL 16 |
| 61 | - Redis |
| 62 | - Celery worker + beat |
| 63 | - Mailpit on `http://localhost:8025` |
| 64 | |
| 65 | ### Production |
| 66 | |
| 67 | For production, you'll also configure Caddy and Litestream: |
| 68 | |
| 69 | ```bash |
| 70 | # Copy production configs |
| 71 | cp docker/Caddyfile.example docker/Caddyfile |
| 72 | cp docker/litestream.yml.example docker/litestream.yml |
| 73 | |
| 74 | # Edit with your domain and S3 credentials |
| 75 | # Then start with the production compose file |
| 76 | docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d |
| 77 | ``` |
| 78 | |
| 79 | ## Verify Installation |
| 80 | |
| 81 | ```bash |
| 82 | # Check all services are running |
| 83 | docker compose ps |
| 84 | |
| 85 | # Hit the health endpoint |
| 86 | curl http://localhost:8000/health/ |
| 87 | |
| 88 | # Open the dashboard |
| 89 | open http://localhost:8000 |
| 90 | ``` |
| 91 | |
| 92 | !!! success "You should see" |
| 93 | The fossilrepo dashboard with navigation, login page, and (after seeding) sample repositories. |
| 94 | |
| 95 | ## Common Issues |
| 96 | |
| 97 | ??? question "Port 8000 already in use" |
| 98 | Change the Django port mapping in `docker-compose.yml`: |
| 99 | ```yaml |
| 100 | ports: |
| 101 | - "8001:8000" |
| 102 | ``` |
| 103 | |
| 104 | ??? question "Database connection refused" |
| 105 | Ensure PostgreSQL has started before Django: |
| 106 | ```bash |
| 107 | docker compose logs postgres |
| 108 | ``` |
| 109 | The Django container waits for Postgres to be ready, but network issues on some Docker Desktop versions can cause timeouts. Restart with `make down && make up`. |
| 110 | |
| 111 | ??? question "Permission denied on /data/repos" |
| 112 | The Fossil repo directory needs to be writable by the container user: |
| 113 | ```bash |
| 114 | sudo chown -R 1000:1000 /data/repos |
| 115 | ``` |
| 116 |
| --- docs/getting-started/installation.md | |
| +++ docs/getting-started/installation.md | |
| @@ -1,115 +1,113 @@ | |
| 1 | # Setup Guide |
| 2 | |
| 3 | ## Quick Start (Docker) |
| 4 | |
| 5 | ```bash |
| 6 | git clone https://github.com/ConflictHQ/fossilrepo.git |
| 7 | cd fossilrepo |
| 8 | docker compose up -d --build |
| 9 | docker compose exec backend python manage.py migrate |
| 10 | docker compose exec backend python manage.py seed |
| 11 | docker compose exec backend python manage.py seed_roles |
| 12 | ``` |
| 13 | |
| 14 | Visit http://localhost:8000. Login: `admin` / `admin`. |
| 15 | |
| 16 | ## Default Users |
| 17 | |
| 18 | | Username | Password | Role | |
| 19 | |----------|----------|------| |
| 20 | | admin | admin | Superuser | |
| 21 | | viewer | viewer | View-only | |
| 22 | | role-admin | role-admin | Admin role | |
| 23 | | role-manager | role-manager | Manager role | |
| 24 | | role-developer | role-developer | Developer role | |
| 25 | | role-viewer | role-viewer | Viewer role | |
| 26 | |
| 27 | ## Configuration |
| 28 | |
| 29 | ### Environment Variables |
| 30 | |
| 31 | Copy `.env.example` to `.env` and customize. Key variables: |
| 32 | |
| 33 | | Variable | Default | Description | |
| 34 | |----------|---------|-------------| |
| 35 | | DJANGO_SECRET_KEY | change-me | **Required in production** | |
| 36 | | DJANGO_DEBUG | false | Enable debug mode | |
| 37 | | DJANGO_ALLOWED_HOSTS | localhost | Comma-separated hostnames | |
| 38 | | POSTGRES_DB | fossilrepo | Database name | |
| 39 | | POSTGRES_USER | dbadmin | Database user | |
| 40 | | POSTGRES_PASSWORD | Password123 | Database password | |
| 41 | | REDIS_URL | redis://localhost:6379/1 | Redis connection | |
| 42 | | EMAIL_HOST | localhost | SMTP server | |
| 43 | | CORS_ALLOWED_ORIGINS | http://localhost:8000 | CORS origins | |
| 44 | | SENTRY_DSN | (empty) | Sentry error tracking | |
| 45 | |
| 46 | ### Runtime Settings (Constance) |
| 47 | |
| 48 | Configurable via Django admin at `/admin/constance/config/`: |
| 49 | |
| 50 | | Setting | Default | Description | |
| 51 | |---------|---------|-------------| |
| 52 | | SITE_NAME | Fossilrepo | Display name | |
| 53 | | FOSSIL_DATA_DIR | /data/repos | Where .fossil files live | |
| 54 | | FOSSIL_BINARY_PATH | fossil | Path to fossil binary | |
| 55 | | FOSSIL_STORE_IN_DB | false | Store snapshots via Django file storage | |
| 56 | | FOSSIL_S3_TRACKING | false | Track S3 replication | |
| 57 | | GIT_SYNC_MODE | disabled | Default sync mode | |
| 58 | | GIT_SYNC_SCHEDULE | */15 * * * * | Default cron for git sync | |
| 59 | |
| 60 | ### OAuth (GitHub/GitLab) |
| 61 | |
| 62 | For Git mirror sync via OAuth: |
| 63 | 1. Create an OAuth App on GitHub/GitLab |
| 64 | 2. Set Client ID and Secret in Constance (Django admin) |
| 65 | 3. Callback URLs are handled automatically |
| 66 | |
| 67 | ## Adding Repositories |
| 68 | |
| 69 | ### Create Empty |
| 70 | |
| 71 | 1. Go to Projects > + New |
| 72 | 2. Fill in name and description |
| 73 | 3. Select "Create empty repository" |
| 74 | 4. Done — empty .fossil file created |
| 75 | |
| 76 | ### Clone from Fossil URL |
| 77 | |
| 78 | 1. Go to Projects > + New |
| 79 | 2. Select "Clone from Fossil URL" |
| 80 | 3. Enter the URL (e.g., `https://fossil-scm.org/home`) |
| 81 | 4. FossilRepo clones the repo and links it |
| 82 | |
| 83 | ### Clone Fossil SCM (Example) |
| 84 | |
| 85 | ```bash |
| 86 | # Clone the official Fossil SCM repo |
| 87 | docker compose exec backend fossil clone https://fossil-scm.org/home /data/repos/fossil-scm.fossil |
| 88 | ``` |
| 89 | |
| 90 | Then create a Project in the UI and link it to the file. |
| 91 | |
| 92 | ## Production Deployment |
| 93 | |
| 94 | See `.env.production.example` for production settings. Key steps: |
| 95 | |
| 96 | 1. Set a strong `DJANGO_SECRET_KEY` |
| 97 | 2. Set `DJANGO_DEBUG=false` |
| 98 | 3. Configure `DJANGO_ALLOWED_HOSTS` to your domain |
| 99 | 4. Use a proper database password |
| 100 | 5. Configure email (SES, SMTP) |
| 101 | 6. Set up HTTPS via Caddy or reverse proxy |
| 102 | 7. Configure S3 for Litestream backups (optional) |
| 103 | |
| 104 | ## Ports |
| 105 | |
| 106 | | Port | Service | |
| 107 | |------|---------| |
| 108 | | 8000 | Django (HTTP) | |
| 109 | | 2222 | SSH (Fossil sync) | |
| 110 | | 5432 | PostgreSQL | |
| 111 | | 6379 | Redis | |
| 112 | | 1025 | Mailpit SMTP (dev) | |
| 113 | | 8025 | Mailpit UI (dev) | |
| 114 |
+6
| --- fossil/views.py | ||
| +++ fossil/views.py | ||
| @@ -761,10 +761,13 @@ | ||
| 761 | 761 | |
| 762 | 762 | with reader: |
| 763 | 763 | pages = reader.get_wiki_pages() |
| 764 | 764 | home_page = reader.get_wiki_page("Home") |
| 765 | 765 | |
| 766 | + # Sort: Home first, then alphabetical | |
| 767 | + pages = sorted(pages, key=lambda p: ("" if p.name == "Home" else "~" + p.name.lower())) | |
| 768 | + | |
| 766 | 769 | search = request.GET.get("search", "").strip() |
| 767 | 770 | if search: |
| 768 | 771 | pages = [p for p in pages if search.lower() in p.name.lower()] |
| 769 | 772 | |
| 770 | 773 | per_page = get_per_page(request) |
| @@ -801,10 +804,13 @@ | ||
| 801 | 804 | all_pages = reader.get_wiki_pages() |
| 802 | 805 | |
| 803 | 806 | if not page: |
| 804 | 807 | raise Http404(f"Wiki page not found: {page_name}") |
| 805 | 808 | |
| 809 | + # Sort: Home first, then alphabetical | |
| 810 | + all_pages = sorted(all_pages, key=lambda p: ("" if p.name == "Home" else "~" + p.name.lower())) | |
| 811 | + | |
| 806 | 812 | content_html = mark_safe(sanitize_html(_render_fossil_content(page.content, project_slug=slug))) |
| 807 | 813 | |
| 808 | 814 | return render( |
| 809 | 815 | request, |
| 810 | 816 | "fossil/wiki_page.html", |
| 811 | 817 |
| --- fossil/views.py | |
| +++ fossil/views.py | |
| @@ -761,10 +761,13 @@ | |
| 761 | |
| 762 | with reader: |
| 763 | pages = reader.get_wiki_pages() |
| 764 | home_page = reader.get_wiki_page("Home") |
| 765 | |
| 766 | search = request.GET.get("search", "").strip() |
| 767 | if search: |
| 768 | pages = [p for p in pages if search.lower() in p.name.lower()] |
| 769 | |
| 770 | per_page = get_per_page(request) |
| @@ -801,10 +804,13 @@ | |
| 801 | all_pages = reader.get_wiki_pages() |
| 802 | |
| 803 | if not page: |
| 804 | raise Http404(f"Wiki page not found: {page_name}") |
| 805 | |
| 806 | content_html = mark_safe(sanitize_html(_render_fossil_content(page.content, project_slug=slug))) |
| 807 | |
| 808 | return render( |
| 809 | request, |
| 810 | "fossil/wiki_page.html", |
| 811 |
| --- fossil/views.py | |
| +++ fossil/views.py | |
| @@ -761,10 +761,13 @@ | |
| 761 | |
| 762 | with reader: |
| 763 | pages = reader.get_wiki_pages() |
| 764 | home_page = reader.get_wiki_page("Home") |
| 765 | |
| 766 | # Sort: Home first, then alphabetical |
| 767 | pages = sorted(pages, key=lambda p: ("" if p.name == "Home" else "~" + p.name.lower())) |
| 768 | |
| 769 | search = request.GET.get("search", "").strip() |
| 770 | if search: |
| 771 | pages = [p for p in pages if search.lower() in p.name.lower()] |
| 772 | |
| 773 | per_page = get_per_page(request) |
| @@ -801,10 +804,13 @@ | |
| 804 | all_pages = reader.get_wiki_pages() |
| 805 | |
| 806 | if not page: |
| 807 | raise Http404(f"Wiki page not found: {page_name}") |
| 808 | |
| 809 | # Sort: Home first, then alphabetical |
| 810 | all_pages = sorted(all_pages, key=lambda p: ("" if p.name == "Home" else "~" + p.name.lower())) |
| 811 | |
| 812 | content_html = mark_safe(sanitize_html(_render_fossil_content(page.content, project_slug=slug))) |
| 813 | |
| 814 | return render( |
| 815 | request, |
| 816 | "fossil/wiki_page.html", |
| 817 |
+5
| --- mkdocs.yml | ||
| +++ mkdocs.yml | ||
| @@ -67,18 +67,23 @@ | ||
| 67 | 67 | - toc: |
| 68 | 68 | permalink: true |
| 69 | 69 | |
| 70 | 70 | nav: |
| 71 | 71 | - Home: index.md |
| 72 | + - Features: features.md | |
| 72 | 73 | - Getting Started: |
| 73 | 74 | - Prerequisites: getting-started/prerequisites.md |
| 74 | 75 | - Installation: getting-started/installation.md |
| 75 | 76 | - Configuration: getting-started/configuration.md |
| 76 | 77 | - First Repository: getting-started/first-repo.md |
| 78 | + - Administration: getting-started/administration.md | |
| 77 | 79 | - Architecture: |
| 78 | 80 | - Overview: architecture/overview.md |
| 79 | 81 | - Sync Bridge: architecture/sync-bridge.md |
| 82 | + - API: | |
| 83 | + - Reference: api/reference.md | |
| 84 | + - Agentic Development: api/agentic-development.md | |
| 80 | 85 | |
| 81 | 86 | extra: |
| 82 | 87 | social: |
| 83 | 88 | - icon: fontawesome/brands/github |
| 84 | 89 | link: https://github.com/ConflictHQ/fossilrepo |
| 85 | 90 | |
| 86 | 91 | ADDED scripts/sync_to_fossil.sh |
| --- mkdocs.yml | |
| +++ mkdocs.yml | |
| @@ -67,18 +67,23 @@ | |
| 67 | - toc: |
| 68 | permalink: true |
| 69 | |
| 70 | nav: |
| 71 | - Home: index.md |
| 72 | - Getting Started: |
| 73 | - Prerequisites: getting-started/prerequisites.md |
| 74 | - Installation: getting-started/installation.md |
| 75 | - Configuration: getting-started/configuration.md |
| 76 | - First Repository: getting-started/first-repo.md |
| 77 | - Architecture: |
| 78 | - Overview: architecture/overview.md |
| 79 | - Sync Bridge: architecture/sync-bridge.md |
| 80 | |
| 81 | extra: |
| 82 | social: |
| 83 | - icon: fontawesome/brands/github |
| 84 | link: https://github.com/ConflictHQ/fossilrepo |
| 85 | |
| 86 | DDED scripts/sync_to_fossil.sh |
| --- mkdocs.yml | |
| +++ mkdocs.yml | |
| @@ -67,18 +67,23 @@ | |
| 67 | - toc: |
| 68 | permalink: true |
| 69 | |
| 70 | nav: |
| 71 | - Home: index.md |
| 72 | - Features: features.md |
| 73 | - Getting Started: |
| 74 | - Prerequisites: getting-started/prerequisites.md |
| 75 | - Installation: getting-started/installation.md |
| 76 | - Configuration: getting-started/configuration.md |
| 77 | - First Repository: getting-started/first-repo.md |
| 78 | - Administration: getting-started/administration.md |
| 79 | - Architecture: |
| 80 | - Overview: architecture/overview.md |
| 81 | - Sync Bridge: architecture/sync-bridge.md |
| 82 | - API: |
| 83 | - Reference: api/reference.md |
| 84 | - Agentic Development: api/agentic-development.md |
| 85 | |
| 86 | extra: |
| 87 | social: |
| 88 | - icon: fontawesome/brands/github |
| 89 | link: https://github.com/ConflictHQ/fossilrepo |
| 90 | |
| 91 | DDED scripts/sync_to_fossil.sh |
| --- a/scripts/sync_to_fossil.sh | ||
| +++ b/scripts/sync_to_fossil.sh | ||
| @@ -0,0 +1,73 @@ | ||
| 1 | +#!/bin/bash | |
| 2 | +# sync_to_fossil.sh — Commit current code into the existing .fossil repo. | |
| 3 | +# | |
| 4 | +# Opens a temporary checkout, rsyncs the working tree in, commits changes. | |
| 5 | +# NEVER replaces or reimports the .fossil file — preserves all tickets, | |
| 6 | +# wiki, forum, and other Fossil-native artifacts. | |
| 7 | +# | |
| 8 | +# Usage: ./scripts/sync_to_fossil.sh ["commit message"] | |
| 9 | +# Run from inside the container or via: | |
| 10 | +# docker compose exec backend bash scripts/sync_to_fossil.sh "message" | |
| 11 | + | |
| 12 | +set -euo pipefail | |
| 13 | + | |
| 14 | +REPO="/data/repos/fossilrepo.fossil" | |
| 15 | +WORKDIR="/tmp/fossil-checkout-$$" | |
| 16 | +MESSAGE="${1:-Sync from working tree}" | |
| 17 | + | |
| 18 | +export USER="${USER:-ragelink}" | |
| 19 | + | |
| 20 | +if [ ! -f "$REPO" ]; then | |
| 21 | + echo "Error: $REPO not found" >&2 | |
| 22 | + exit 1 | |
| 23 | +fi | |
| 24 | + | |
| 25 | +echo "=== Committing to Fossil ===" | |
| 26 | + | |
| 27 | +# Create temp checkout | |
| 28 | +rm -rf "$WORKDIR" | |
| 29 | +mkdir -p "$WORKDIR" | |
| 30 | +cd "$WORKDIR" | |
| 31 | + | |
| 32 | +fossil open "$REPO" --workdir "$WORKDIR" 2>/dev/null | |
| 33 | +fossil update trunk 2>/dev/null || true | |
| 34 | + | |
| 35 | +# Sync code from /app — use tar to copy with exclusions (rsync not available) | |
| 36 | +cd /app | |
| 37 | +tar cf - \ | |
| 38 | + --exclude='.git' \ | |
| 39 | + --exclude='__pycache__' \ | |
| 40 | + --exclude='*.pyc' \ | |
| 41 | + --exclude='.ruff_cache' \ | |
| 42 | + --exclude='node_modules' \ | |
| 43 | + --exclude='assets' \ | |
| 44 | + --exclude='.env' \ | |
| 45 | + --exclude='repos' \ | |
| 46 | + --exclude='.fslckout' \ | |
| 47 | + --exclude='_FOSSIL_' \ | |
| 48 | + --exclude='*.fossil' \ | |
| 49 | + --exclude='.claude' \ | |
| 50 | + . | (cd "$WORKDIR" && tar xf -) | |
| 51 | +cd "$WORKDIR" | |
| 52 | + | |
| 53 | +# Register new/deleted files | |
| 54 | +fossil addremove 2>/dev/null || true | |
| 55 | + | |
| 56 | +# Commit if there are changes | |
| 57 | +CHANGES=$(fossil changes 2>/dev/null | wc -l | tr -d ' ') | |
| 58 | +if [ "$CHANGES" -gt 0 ]; then | |
| 59 | + fossil commit -m "$MESSAGE" --no-warnings 2>&1 | tail -3 | |
| 60 | + echo "Committed $CHANGES changed files." | |
| 61 | +else | |
| 62 | + echo "No changes to commit." | |
| 63 | +fi | |
| 64 | + | |
| 65 | +# Cleanup | |
| 66 | +fossil close --force 2>/dev/null || true | |
| 67 | +cd / | |
| 68 | +rm -rf "$WORKDIR" | |
| 69 | + | |
| 70 | +echo "=== Status ===" | |
| 71 | +echo "Checkins: $(fossil sql -R "$REPO" "SELECT count(*) FROM event WHERE type='ci';" | tr -d "' ")" | |
| 72 | +echo "Wiki: $(fossil wiki list -R "$REPO" | wc -l) pages" | |
| 73 | +echo "Tickets: $(fossil sql -R "$REPO" "SELECT count(*) FROM ticket;" | tr -d "' ")" |
| --- a/scripts/sync_to_fossil.sh | |
| +++ b/scripts/sync_to_fossil.sh | |
| @@ -0,0 +1,73 @@ | |
| --- a/scripts/sync_to_fossil.sh | |
| +++ b/scripts/sync_to_fossil.sh | |
| @@ -0,0 +1,73 @@ | |
| 1 | #!/bin/bash |
| 2 | # sync_to_fossil.sh — Commit current code into the existing .fossil repo. |
| 3 | # |
| 4 | # Opens a temporary checkout, rsyncs the working tree in, commits changes. |
| 5 | # NEVER replaces or reimports the .fossil file — preserves all tickets, |
| 6 | # wiki, forum, and other Fossil-native artifacts. |
| 7 | # |
| 8 | # Usage: ./scripts/sync_to_fossil.sh ["commit message"] |
| 9 | # Run from inside the container or via: |
| 10 | # docker compose exec backend bash scripts/sync_to_fossil.sh "message" |
| 11 | |
| 12 | set -euo pipefail |
| 13 | |
| 14 | REPO="/data/repos/fossilrepo.fossil" |
| 15 | WORKDIR="/tmp/fossil-checkout-$$" |
| 16 | MESSAGE="${1:-Sync from working tree}" |
| 17 | |
| 18 | export USER="${USER:-ragelink}" |
| 19 | |
| 20 | if [ ! -f "$REPO" ]; then |
| 21 | echo "Error: $REPO not found" >&2 |
| 22 | exit 1 |
| 23 | fi |
| 24 | |
| 25 | echo "=== Committing to Fossil ===" |
| 26 | |
| 27 | # Create temp checkout |
| 28 | rm -rf "$WORKDIR" |
| 29 | mkdir -p "$WORKDIR" |
| 30 | cd "$WORKDIR" |
| 31 | |
| 32 | fossil open "$REPO" --workdir "$WORKDIR" 2>/dev/null |
| 33 | fossil update trunk 2>/dev/null || true |
| 34 | |
| 35 | # Sync code from /app — use tar to copy with exclusions (rsync not available) |
| 36 | cd /app |
| 37 | tar cf - \ |
| 38 | --exclude='.git' \ |
| 39 | --exclude='__pycache__' \ |
| 40 | --exclude='*.pyc' \ |
| 41 | --exclude='.ruff_cache' \ |
| 42 | --exclude='node_modules' \ |
| 43 | --exclude='assets' \ |
| 44 | --exclude='.env' \ |
| 45 | --exclude='repos' \ |
| 46 | --exclude='.fslckout' \ |
| 47 | --exclude='_FOSSIL_' \ |
| 48 | --exclude='*.fossil' \ |
| 49 | --exclude='.claude' \ |
| 50 | . | (cd "$WORKDIR" && tar xf -) |
| 51 | cd "$WORKDIR" |
| 52 | |
| 53 | # Register new/deleted files |
| 54 | fossil addremove 2>/dev/null || true |
| 55 | |
| 56 | # Commit if there are changes |
| 57 | CHANGES=$(fossil changes 2>/dev/null | wc -l | tr -d ' ') |
| 58 | if [ "$CHANGES" -gt 0 ]; then |
| 59 | fossil commit -m "$MESSAGE" --no-warnings 2>&1 | tail -3 |
| 60 | echo "Committed $CHANGES changed files." |
| 61 | else |
| 62 | echo "No changes to commit." |
| 63 | fi |
| 64 | |
| 65 | # Cleanup |
| 66 | fossil close --force 2>/dev/null || true |
| 67 | cd / |
| 68 | rm -rf "$WORKDIR" |
| 69 | |
| 70 | echo "=== Status ===" |
| 71 | echo "Checkins: $(fossil sql -R "$REPO" "SELECT count(*) FROM event WHERE type='ci';" | tr -d "' ")" |
| 72 | echo "Wiki: $(fossil wiki list -R "$REPO" | wc -l) pages" |
| 73 | echo "Tickets: $(fossil sql -R "$REPO" "SELECT count(*) FROM ticket;" | tr -d "' ")" |
+1
-1
| --- templates/dashboard.html | ||
| +++ templates/dashboard.html | ||
| @@ -106,11 +106,11 @@ | ||
| 106 | 106 | <p class="mt-1 text-xs text-gray-500">Organize members into teams</p> |
| 107 | 107 | </a> |
| 108 | 108 | {% endif %} |
| 109 | 109 | {% if perms.pages.view_page %} |
| 110 | 110 | <a href="{% url 'pages:list' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors"> |
| 111 | - <h3 class="text-sm font-semibold text-gray-100">FossilRepo KB</h3> | |
| 111 | + <h3 class="text-sm font-semibold text-gray-100">FossilRepo Docs</h3> | |
| 112 | 112 | <p class="mt-1 text-xs text-gray-500">Guides, runbooks, documentation</p> |
| 113 | 113 | </a> |
| 114 | 114 | {% endif %} |
| 115 | 115 | {% if perms.organization.view_organization %} |
| 116 | 116 | <a href="{% url 'organization:settings' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors"> |
| 117 | 117 |
| --- templates/dashboard.html | |
| +++ templates/dashboard.html | |
| @@ -106,11 +106,11 @@ | |
| 106 | <p class="mt-1 text-xs text-gray-500">Organize members into teams</p> |
| 107 | </a> |
| 108 | {% endif %} |
| 109 | {% if perms.pages.view_page %} |
| 110 | <a href="{% url 'pages:list' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors"> |
| 111 | <h3 class="text-sm font-semibold text-gray-100">FossilRepo KB</h3> |
| 112 | <p class="mt-1 text-xs text-gray-500">Guides, runbooks, documentation</p> |
| 113 | </a> |
| 114 | {% endif %} |
| 115 | {% if perms.organization.view_organization %} |
| 116 | <a href="{% url 'organization:settings' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors"> |
| 117 |
| --- templates/dashboard.html | |
| +++ templates/dashboard.html | |
| @@ -106,11 +106,11 @@ | |
| 106 | <p class="mt-1 text-xs text-gray-500">Organize members into teams</p> |
| 107 | </a> |
| 108 | {% endif %} |
| 109 | {% if perms.pages.view_page %} |
| 110 | <a href="{% url 'pages:list' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors"> |
| 111 | <h3 class="text-sm font-semibold text-gray-100">FossilRepo Docs</h3> |
| 112 | <p class="mt-1 text-xs text-gray-500">Guides, runbooks, documentation</p> |
| 113 | </a> |
| 114 | {% endif %} |
| 115 | {% if perms.organization.view_organization %} |
| 116 | <a href="{% url 'organization:settings' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors"> |
| 117 |
+8
-1
| --- templates/fossil/wiki_list.html | ||
| +++ templates/fossil/wiki_list.html | ||
| @@ -50,12 +50,19 @@ | ||
| 50 | 50 | <div class="sticky top-6"> |
| 51 | 51 | <h3 class="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-3">Wiki Pages</h3> |
| 52 | 52 | <nav class="space-y-0.5"> |
| 53 | 53 | {% for p in pages %} |
| 54 | 54 | <a href="{% url 'fossil:wiki_page' slug=project.slug page_name=p.name %}" |
| 55 | - class="block rounded-md px-3 py-1.5 text-sm text-gray-400 hover:text-gray-200 hover:bg-gray-800/50"> | |
| 55 | + class="block rounded-md px-3 py-1.5 text-sm {% if p.name == 'Home' %}text-brand-light font-medium{% else %}text-gray-400{% endif %} hover:text-gray-200 hover:bg-gray-800/50"> | |
| 56 | + {% if p.name == "Home" %} | |
| 57 | + <span class="flex items-center gap-1.5"> | |
| 58 | + <svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955a1.126 1.126 0 011.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" /></svg> | |
| 59 | + {{ p.name }} | |
| 60 | + </span> | |
| 61 | + {% else %} | |
| 56 | 62 | {{ p.name }} |
| 63 | + {% endif %} | |
| 57 | 64 | </a> |
| 58 | 65 | {% endfor %} |
| 59 | 66 | </nav> |
| 60 | 67 | {% if not pages %} |
| 61 | 68 | <p class="text-xs text-gray-600 px-3">No pages.</p> |
| 62 | 69 |
| --- templates/fossil/wiki_list.html | |
| +++ templates/fossil/wiki_list.html | |
| @@ -50,12 +50,19 @@ | |
| 50 | <div class="sticky top-6"> |
| 51 | <h3 class="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-3">Wiki Pages</h3> |
| 52 | <nav class="space-y-0.5"> |
| 53 | {% for p in pages %} |
| 54 | <a href="{% url 'fossil:wiki_page' slug=project.slug page_name=p.name %}" |
| 55 | class="block rounded-md px-3 py-1.5 text-sm text-gray-400 hover:text-gray-200 hover:bg-gray-800/50"> |
| 56 | {{ p.name }} |
| 57 | </a> |
| 58 | {% endfor %} |
| 59 | </nav> |
| 60 | {% if not pages %} |
| 61 | <p class="text-xs text-gray-600 px-3">No pages.</p> |
| 62 |
| --- templates/fossil/wiki_list.html | |
| +++ templates/fossil/wiki_list.html | |
| @@ -50,12 +50,19 @@ | |
| 50 | <div class="sticky top-6"> |
| 51 | <h3 class="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-3">Wiki Pages</h3> |
| 52 | <nav class="space-y-0.5"> |
| 53 | {% for p in pages %} |
| 54 | <a href="{% url 'fossil:wiki_page' slug=project.slug page_name=p.name %}" |
| 55 | class="block rounded-md px-3 py-1.5 text-sm {% if p.name == 'Home' %}text-brand-light font-medium{% else %}text-gray-400{% endif %} hover:text-gray-200 hover:bg-gray-800/50"> |
| 56 | {% if p.name == "Home" %} |
| 57 | <span class="flex items-center gap-1.5"> |
| 58 | <svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955a1.126 1.126 0 011.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" /></svg> |
| 59 | {{ p.name }} |
| 60 | </span> |
| 61 | {% else %} |
| 62 | {{ p.name }} |
| 63 | {% endif %} |
| 64 | </a> |
| 65 | {% endfor %} |
| 66 | </nav> |
| 67 | {% if not pages %} |
| 68 | <p class="text-xs text-gray-600 px-3">No pages.</p> |
| 69 |
+36
-6
| --- templates/includes/sidebar.html | ||
| +++ templates/includes/sidebar.html | ||
| @@ -85,33 +85,63 @@ | ||
| 85 | 85 | {% endif %} |
| 86 | 86 | </div> |
| 87 | 87 | </div> |
| 88 | 88 | {% endif %} |
| 89 | 89 | |
| 90 | - <!-- FossilRepo KB section --> | |
| 91 | - {% if perms.pages.view_page %} | |
| 92 | - <div> | |
| 90 | + <!-- FossilRepo Docs (product docs — read-only) --> | |
| 91 | + {% if sidebar_product_docs %} | |
| 92 | + <div x-data="{ docsOpen: false }"> | |
| 93 | 93 | <button @click="collapsed ? (collapsed = false, docsOpen = true) : (docsOpen = !docsOpen)" |
| 94 | 94 | class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/kb/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}" |
| 95 | - :title="collapsed ? 'FossilRepo KB' : ''"> | |
| 95 | + :title="collapsed ? 'FossilRepo Docs' : ''"> | |
| 96 | 96 | <span class="flex items-center gap-2"> |
| 97 | 97 | <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 98 | 98 | <path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" /> |
| 99 | 99 | </svg> |
| 100 | - <span x-show="!collapsed" class="truncate">FossilRepo KB</span> | |
| 100 | + <span x-show="!collapsed" class="truncate">FossilRepo Docs</span> | |
| 101 | 101 | </span> |
| 102 | 102 | <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="docsOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 103 | 103 | <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> |
| 104 | 104 | </svg> |
| 105 | 105 | </button> |
| 106 | 106 | <div x-show="docsOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-3"> |
| 107 | - {% for p in sidebar_pages %} | |
| 107 | + {% for p in sidebar_product_docs %} | |
| 108 | + <a href="{% url 'pages:detail' slug=p.slug %}" | |
| 109 | + class="block rounded-md px-3 py-1.5 text-sm {% if p.slug in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %} truncate"> | |
| 110 | + {{ p.name }} | |
| 111 | + </a> | |
| 112 | + {% endfor %} | |
| 113 | + </div> | |
| 114 | + </div> | |
| 115 | + {% endif %} | |
| 116 | + | |
| 117 | + <!-- Knowledge Base (org wiki — user-editable) --> | |
| 118 | + {% if perms.pages.view_page %} | |
| 119 | + <div x-data="{ kbOpen: false }"> | |
| 120 | + <button @click="collapsed ? (collapsed = false, kbOpen = true) : (kbOpen = !kbOpen)" | |
| 121 | + class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium text-gray-400 hover:bg-gray-800 hover:text-white" | |
| 122 | + :title="collapsed ? 'Knowledge Base' : ''"> | |
| 123 | + <span class="flex items-center gap-2"> | |
| 124 | + <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> | |
| 125 | + <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776" /> | |
| 126 | + </svg> | |
| 127 | + <span x-show="!collapsed" class="truncate">Knowledge Base</span> | |
| 128 | + </span> | |
| 129 | + <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="kbOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> | |
| 130 | + <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> | |
| 131 | + </svg> | |
| 132 | + </button> | |
| 133 | + <div x-show="kbOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-3"> | |
| 134 | + {% for p in sidebar_kb_pages %} | |
| 108 | 135 | <a href="{% url 'pages:detail' slug=p.slug %}" |
| 109 | 136 | class="block rounded-md px-3 py-1.5 text-sm {% if p.slug in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %} truncate"> |
| 110 | 137 | {{ p.name }} |
| 111 | 138 | </a> |
| 112 | 139 | {% endfor %} |
| 140 | + {% if sidebar_kb_pages|length == 0 %} | |
| 141 | + <p class="px-3 py-1.5 text-xs text-gray-600">No articles yet.</p> | |
| 142 | + {% endif %} | |
| 113 | 143 | {% if perms.pages.add_page %} |
| 114 | 144 | <a href="{% url 'pages:create' %}" |
| 115 | 145 | class="block rounded-md px-3 py-1.5 text-sm text-gray-600 hover:text-brand-light"> |
| 116 | 146 | + New |
| 117 | 147 | </a> |
| 118 | 148 |
| --- templates/includes/sidebar.html | |
| +++ templates/includes/sidebar.html | |
| @@ -85,33 +85,63 @@ | |
| 85 | {% endif %} |
| 86 | </div> |
| 87 | </div> |
| 88 | {% endif %} |
| 89 | |
| 90 | <!-- FossilRepo KB section --> |
| 91 | {% if perms.pages.view_page %} |
| 92 | <div> |
| 93 | <button @click="collapsed ? (collapsed = false, docsOpen = true) : (docsOpen = !docsOpen)" |
| 94 | class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/kb/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}" |
| 95 | :title="collapsed ? 'FossilRepo KB' : ''"> |
| 96 | <span class="flex items-center gap-2"> |
| 97 | <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 98 | <path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" /> |
| 99 | </svg> |
| 100 | <span x-show="!collapsed" class="truncate">FossilRepo KB</span> |
| 101 | </span> |
| 102 | <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="docsOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 103 | <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> |
| 104 | </svg> |
| 105 | </button> |
| 106 | <div x-show="docsOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-3"> |
| 107 | {% for p in sidebar_pages %} |
| 108 | <a href="{% url 'pages:detail' slug=p.slug %}" |
| 109 | class="block rounded-md px-3 py-1.5 text-sm {% if p.slug in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %} truncate"> |
| 110 | {{ p.name }} |
| 111 | </a> |
| 112 | {% endfor %} |
| 113 | {% if perms.pages.add_page %} |
| 114 | <a href="{% url 'pages:create' %}" |
| 115 | class="block rounded-md px-3 py-1.5 text-sm text-gray-600 hover:text-brand-light"> |
| 116 | + New |
| 117 | </a> |
| 118 |
| --- templates/includes/sidebar.html | |
| +++ templates/includes/sidebar.html | |
| @@ -85,33 +85,63 @@ | |
| 85 | {% endif %} |
| 86 | </div> |
| 87 | </div> |
| 88 | {% endif %} |
| 89 | |
| 90 | <!-- FossilRepo Docs (product docs — read-only) --> |
| 91 | {% if sidebar_product_docs %} |
| 92 | <div x-data="{ docsOpen: false }"> |
| 93 | <button @click="collapsed ? (collapsed = false, docsOpen = true) : (docsOpen = !docsOpen)" |
| 94 | class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/kb/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}" |
| 95 | :title="collapsed ? 'FossilRepo Docs' : ''"> |
| 96 | <span class="flex items-center gap-2"> |
| 97 | <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 98 | <path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" /> |
| 99 | </svg> |
| 100 | <span x-show="!collapsed" class="truncate">FossilRepo Docs</span> |
| 101 | </span> |
| 102 | <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="docsOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 103 | <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> |
| 104 | </svg> |
| 105 | </button> |
| 106 | <div x-show="docsOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-3"> |
| 107 | {% for p in sidebar_product_docs %} |
| 108 | <a href="{% url 'pages:detail' slug=p.slug %}" |
| 109 | class="block rounded-md px-3 py-1.5 text-sm {% if p.slug in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %} truncate"> |
| 110 | {{ p.name }} |
| 111 | </a> |
| 112 | {% endfor %} |
| 113 | </div> |
| 114 | </div> |
| 115 | {% endif %} |
| 116 | |
| 117 | <!-- Knowledge Base (org wiki — user-editable) --> |
| 118 | {% if perms.pages.view_page %} |
| 119 | <div x-data="{ kbOpen: false }"> |
| 120 | <button @click="collapsed ? (collapsed = false, kbOpen = true) : (kbOpen = !kbOpen)" |
| 121 | class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium text-gray-400 hover:bg-gray-800 hover:text-white" |
| 122 | :title="collapsed ? 'Knowledge Base' : ''"> |
| 123 | <span class="flex items-center gap-2"> |
| 124 | <svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 125 | <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776" /> |
| 126 | </svg> |
| 127 | <span x-show="!collapsed" class="truncate">Knowledge Base</span> |
| 128 | </span> |
| 129 | <svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="kbOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 130 | <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> |
| 131 | </svg> |
| 132 | </button> |
| 133 | <div x-show="kbOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-3"> |
| 134 | {% for p in sidebar_kb_pages %} |
| 135 | <a href="{% url 'pages:detail' slug=p.slug %}" |
| 136 | class="block rounded-md px-3 py-1.5 text-sm {% if p.slug in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %} truncate"> |
| 137 | {{ p.name }} |
| 138 | </a> |
| 139 | {% endfor %} |
| 140 | {% if sidebar_kb_pages|length == 0 %} |
| 141 | <p class="px-3 py-1.5 text-xs text-gray-600">No articles yet.</p> |
| 142 | {% endif %} |
| 143 | {% if perms.pages.add_page %} |
| 144 | <a href="{% url 'pages:create' %}" |
| 145 | class="block rounded-md px-3 py-1.5 text-sm text-gray-600 hover:text-brand-light"> |
| 146 | + New |
| 147 | </a> |
| 148 |
+1
-1
| --- templates/pages/page_form.html | ||
| +++ templates/pages/page_form.html | ||
| @@ -1,11 +1,11 @@ | ||
| 1 | 1 | {% extends "base.html" %} |
| 2 | 2 | {% block title %}{{ title }} — Fossilrepo{% endblock %} |
| 3 | 3 | |
| 4 | 4 | {% block content %} |
| 5 | 5 | <div class="mb-6"> |
| 6 | - <a href="{% url 'pages:list' %}" class="text-sm text-brand-light hover:text-brand">← Back to FossilRepo KB</a> | |
| 6 | + <a href="{% url 'pages:list' %}" class="text-sm text-brand-light hover:text-brand">← Back to FossilRepo Docs</a> | |
| 7 | 7 | </div> |
| 8 | 8 | |
| 9 | 9 | <div class="mx-auto max-w-4xl"> |
| 10 | 10 | <h1 class="text-2xl font-bold text-gray-100 mb-6">{{ title }}</h1> |
| 11 | 11 | |
| 12 | 12 |
| --- templates/pages/page_form.html | |
| +++ templates/pages/page_form.html | |
| @@ -1,11 +1,11 @@ | |
| 1 | {% extends "base.html" %} |
| 2 | {% block title %}{{ title }} — Fossilrepo{% endblock %} |
| 3 | |
| 4 | {% block content %} |
| 5 | <div class="mb-6"> |
| 6 | <a href="{% url 'pages:list' %}" class="text-sm text-brand-light hover:text-brand">← Back to FossilRepo KB</a> |
| 7 | </div> |
| 8 | |
| 9 | <div class="mx-auto max-w-4xl"> |
| 10 | <h1 class="text-2xl font-bold text-gray-100 mb-6">{{ title }}</h1> |
| 11 | |
| 12 |
| --- templates/pages/page_form.html | |
| +++ templates/pages/page_form.html | |
| @@ -1,11 +1,11 @@ | |
| 1 | {% extends "base.html" %} |
| 2 | {% block title %}{{ title }} — Fossilrepo{% endblock %} |
| 3 | |
| 4 | {% block content %} |
| 5 | <div class="mb-6"> |
| 6 | <a href="{% url 'pages:list' %}" class="text-sm text-brand-light hover:text-brand">← Back to FossilRepo Docs</a> |
| 7 | </div> |
| 8 | |
| 9 | <div class="mx-auto max-w-4xl"> |
| 10 | <h1 class="text-2xl font-bold text-gray-100 mb-6">{{ title }}</h1> |
| 11 | |
| 12 |
+2
-2
| --- templates/pages/page_list.html | ||
| +++ templates/pages/page_list.html | ||
| @@ -1,11 +1,11 @@ | ||
| 1 | 1 | {% extends "base.html" %} |
| 2 | -{% block title %}FossilRepo KB — Fossilrepo{% endblock %} | |
| 2 | +{% block title %}FossilRepo Docs — Fossilrepo{% endblock %} | |
| 3 | 3 | |
| 4 | 4 | {% block content %} |
| 5 | 5 | <div class="md:flex md:items-center md:justify-between mb-6"> |
| 6 | - <h1 class="text-2xl font-bold text-gray-100">FossilRepo KB</h1> | |
| 6 | + <h1 class="text-2xl font-bold text-gray-100">FossilRepo Docs</h1> | |
| 7 | 7 | {% if perms.pages.add_page %} |
| 8 | 8 | <a href="{% url 'pages:create' %}" |
| 9 | 9 | class="mt-4 md:mt-0 inline-flex items-center rounded-md bg-brand px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-brand-hover"> |
| 10 | 10 | New Page |
| 11 | 11 | </a> |
| 12 | 12 |
| --- templates/pages/page_list.html | |
| +++ templates/pages/page_list.html | |
| @@ -1,11 +1,11 @@ | |
| 1 | {% extends "base.html" %} |
| 2 | {% block title %}FossilRepo KB — Fossilrepo{% endblock %} |
| 3 | |
| 4 | {% block content %} |
| 5 | <div class="md:flex md:items-center md:justify-between mb-6"> |
| 6 | <h1 class="text-2xl font-bold text-gray-100">FossilRepo KB</h1> |
| 7 | {% if perms.pages.add_page %} |
| 8 | <a href="{% url 'pages:create' %}" |
| 9 | class="mt-4 md:mt-0 inline-flex items-center rounded-md bg-brand px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-brand-hover"> |
| 10 | New Page |
| 11 | </a> |
| 12 |
| --- templates/pages/page_list.html | |
| +++ templates/pages/page_list.html | |
| @@ -1,11 +1,11 @@ | |
| 1 | {% extends "base.html" %} |
| 2 | {% block title %}FossilRepo Docs — Fossilrepo{% endblock %} |
| 3 | |
| 4 | {% block content %} |
| 5 | <div class="md:flex md:items-center md:justify-between mb-6"> |
| 6 | <h1 class="text-2xl font-bold text-gray-100">FossilRepo Docs</h1> |
| 7 | {% if perms.pages.add_page %} |
| 8 | <a href="{% url 'pages:create' %}" |
| 9 | class="mt-4 md:mt-0 inline-flex items-center rounded-md bg-brand px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-brand-hover"> |
| 10 | New Page |
| 11 | </a> |
| 12 |