Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 11 additions & 25 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -204,33 +204,19 @@ GRACEFUL_SHUTDOWN_TIMEOUT=600
# Set to False if you want to allow requests during shutdown (not recommended)
SHUTDOWN_REJECT_NEW_REQUESTS=True

# Wiki feature configuration (System-level)
# Wiki tables are now stored in the main database (task_manager)
# Wiki generation is system-level: team and model are configured here, not selected by users
# Feature toggle
WIKI_ENABLED=True
# Default team name for wiki task execution (matches init_data/01-default-resources.yaml)
# The team's bot MUST have a model configured (bind_model or custom config)
# If no model is configured, wiki generation will be disabled in the frontend
WIKI_DEFAULT_TEAM_NAME=wiki-team
# Default agent type for wiki generation
WIKI_DEFAULT_AGENT_TYPE=ClaudeCode
# Default language for wiki documentation generation (en = English, zh = Chinese)
# This is a system-level configuration, users cannot change it in the frontend
# Code wiki configuration
# A code wiki is a knowledge base whose documents an agent writes from a repository.
# Whether new ones may be created. Off by default; existing wikis stay readable and
# regenerable regardless, so turning the rollout down stops it spreading rather than
# breaking what it already produced.
WIKI_CODE_WIKI_ENABLED=False
# Team that runs code wikis (matches init_data/02-public-resources.yaml).
# Its bot MUST have a model configured (bind_model or custom config)
WIKI_CODE_WIKI_TEAM_NAME=code-wiki-team
# Language the documentation is written in (en = English, zh = Chinese)
WIKI_DEFAULT_LANGUAGE=en
# User ID for wiki task creation (0 = use current user ID, set to specific user ID for system-level tasks)
# When set to a specific user ID, all wiki tasks will be created under that user
WIKI_DEFAULT_USER_ID=1
# Maximum concurrent wiki generations
WIKI_MAX_CONCURRENT_GENERATIONS=5
# Background polling interval (seconds)
WIKI_RESULT_POLL_INTERVAL_SECONDS=30
# Background polling batch size
WIKI_RESULT_POLL_BATCH_SIZE=20
# Maximum content size (bytes, default 10MB)
# Maximum content size for one write (bytes, default 10MB)
WIKI_MAX_CONTENT_SIZE=10485760
# Base URL for internal wiki content writer
WIKI_CONTENT_WRITE_BASE_URL=http://backend:8000

# Data Table Configuration
# JSON string containing table provider credentials (DingTalk, etc.)
Expand Down
153 changes: 0 additions & 153 deletions backend/WIKI_FEATURE.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""add knowledge content origin and wiki generation kb link

Revision ID: bd9c871a93d2
Revises: e5f6a7b8c9d0

Adds the two things a code wiki needs to coexist with ordinary knowledge content:

- ``origin`` on documents and folders, marking whether a row is agent-generated or
user-owned. It defaults to ``user`` so that every existing row, and every row this
service does not create itself, is excluded from the generated-content projection.
Getting this backwards would let a regeneration delete content nobody can restore.
- ``kind_id`` on wiki generations, binding a version line to a knowledge base. The
versions previously hung off ``wiki_projects``, whose ``source_url`` is globally
unique; leaving them there would share one version line between knowledge bases
tracking the same repository. ``0`` marks rows that predate code wikis.

All three columns are NOT NULL with a server default, as required for existing rows to
backfill without a nullable intermediate state.
"""

from collections.abc import Sequence
from typing import Union

import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "bd9c871a93d2"
down_revision: Union[str, Sequence[str], None] = "e5f6a7b8c9d0"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
Comment on lines +28 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# List all revision/down_revision declarations to detect missing parents or multiple heads.
fd -e py . backend/alembic/versions --exec rg -n '^revision(: str)? *=|^down_revision' {} \; -x true
python3 - <<'PY'
import ast, pathlib
revs, downs = {}, {}
for p in sorted(pathlib.Path("backend/alembic/versions").glob("*.py")):
    src = p.read_text()
    tree = ast.parse(src, filename=str(p))
    a = {}
    for node in tree.body:
        if isinstance(node, ast.Assign):
            for t in node.targets:
                if isinstance(t, ast.Name):
                    try:
                        a[t.id] = ast.literal_eval(node.value)
                    except Exception:
                        pass
        if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
            try:
                a[node.target.id] = ast.literal_eval(node.value)
            except Exception:
                pass
    if "revision" in a:
        revs[a["revision"]] = str(p)
        downs[a["revision"]] = a.get("down_revision")
parents = {d for d in downs.values() if d}
print("missing parents:", sorted(p for p in parents if p not in revs))
print("heads:", sorted(r for r in revs if r not in parents))
PY

Repository: wecode-ai/Wegent

Length of output: 2396


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Candidate migration files containing bd9c871a93d2 / b9c0d1e2f3a4"
rg -n "bd9c871a93d2|b9c0d1e2f3a4|revision = \"(c8d2e3f4a5b6|e6f7a8b9c013|e6f7a8b9c012|a2b3c4d5e6f7)\"" backend/alembic/versions || true

echo
echo "## Alembic env/config files"
git ls-files 'backend/alembic/**'
python3 - <<'PY'
import pathlib
for p in pathlib.Path("backend/alembic").rglob("*"):
    if p.is_file() and p.suffix in {".py", ".ini"}:
        if any(x in p.parts for x in {"versions", "env.py", "script.py.mako", "config.py"}):
            print(p)
PY

Repository: wecode-ai/Wegent

Length of output: 5842


Resolve the Alembic graph before applying this migration.

bd9c871a93d2 correctly points to b9c0d1e2f3a4, but the migration tree still has multiple heads (bd9c871a93d2, c8d2e3f4a5b6, e6f7a8b9c012, e6f7a8b9c013, and a2b3c4d5e6f7). Merge or relink the branches so upgrade head applies one linear sequence and the schema is not split across heads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/alembic/versions/20260731_bd9c871a93d2_add_knowledge_content_origin_and_wiki_.py`
around lines 28 - 32, Resolve the Alembic revision graph around revision
bd9c871a93d2 and the other listed heads by merging or relinking the migration
branches into one upgrade path. Update the affected revision identifiers and add
any required merge revision so upgrade head resolves to a single linear sequence
while preserving the existing schema changes.

Source: Coding guidelines



def upgrade() -> None:
"""Upgrade schema."""
op.add_column(
"knowledge_documents",
sa.Column(
"origin",
sa.String(length=20),
nullable=False,
server_default="user",
comment="Content ownership: 'generated' (agent-owned) or 'user'",
),
)
op.add_column(
"knowledge_folders",
sa.Column(
"origin",
sa.String(length=20),
nullable=False,
server_default="user",
comment="Content ownership: 'generated' (agent-owned) or 'user'",
),
)
op.add_column(
"wiki_generations",
sa.Column(
"kind_id",
sa.Integer(),
nullable=False,
server_default="0",
comment="Knowledge base this version line belongs to; 0 = legacy row",
),
)
op.create_index(
"ix_wiki_generations_kind_id", "wiki_generations", ["kind_id"], unique=False
)


def downgrade() -> None:
"""Downgrade schema."""
op.drop_index("ix_wiki_generations_kind_id", table_name="wiki_generations")
op.drop_column("wiki_generations", "kind_id")
op.drop_column("knowledge_folders", "origin")
op.drop_column("knowledge_documents", "origin")
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""link wiki projects to their code wiki

Revision ID: 2b5791acc5fa
Revises: bd9c871a93d2

``wiki_projects.source_url`` is already UNIQUE, which makes this table the only place
that can enforce "one repository, one code wiki" against two people creating at the
same moment. Recording the knowledge base here, rather than checking a JSON field on
the knowledge base itself, turns a check-then-insert into a database constraint.

``0`` marks a project row with no code wiki, which is every row that exists today.
"""

from collections.abc import Sequence
from typing import Union

import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "2b5791acc5fa"
down_revision: Union[str, Sequence[str], None] = "bd9c871a93d2"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
op.add_column(
"wiki_projects",
sa.Column(
"kind_id",
sa.Integer(),
nullable=False,
server_default="0",
comment="Code wiki knowledge base built from this repository; 0 = none",
),
)
op.create_index(
"ix_wiki_projects_kind_id", "wiki_projects", ["kind_id"], unique=False
)


def downgrade() -> None:
"""Downgrade schema."""
op.drop_index("ix_wiki_projects_kind_id", table_name="wiki_projects")
op.drop_column("wiki_projects", "kind_id")
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""allow several wikis per repository

Revision ID: c3d4e5f6a7b8
Revises: 2b5791acc5fa

A code wiki belongs to whoever created it, so a wiki built by one person is invisible
to everyone else under the ordinary knowledge-base ACL. "One repository, one wiki"
therefore stopped being a saving and became a way to take a wiki away from the second
person to ask for one.

``wiki_projects`` accordingly holds one row per ``(repository, wiki)`` rather than one
per repository. The UNIQUE moves from ``source_url`` alone to the pair, which is still
a database constraint rather than a check-then-insert: it settles two requests racing
for the same pair, and it lets ``COUNT(*) WHERE source_url = ?`` answer "how many
wikis already exist for this repository" without reading a JSON field.

Legacy wiki rows carry ``kind_id = 0`` and stay at most one per repository, which the
same constraint gives for free.
"""

from collections.abc import Sequence
from typing import Union

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "c3d4e5f6a7b8"
down_revision: Union[str, Sequence[str], None] = "2b5791acc5fa"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

# MySQL names the constraint after the column it was declared on.
OLD_UNIQUE = "source_url"
NEW_UNIQUE = "uq_wiki_projects_source_url_kind_id"


def upgrade() -> None:
"""Upgrade schema."""
# Created before the old one is dropped: between the two statements the table is
# covered by both rather than by neither, so a concurrent insert cannot slip a
# duplicate pair in through the gap.
op.create_unique_constraint(NEW_UNIQUE, "wiki_projects", ["source_url", "kind_id"])
op.drop_constraint(OLD_UNIQUE, "wiki_projects", type_="unique")


def downgrade() -> None:
"""Downgrade schema.

Fails if any repository has more than one wiki, which is correct: silently
discarding one of them would destroy a generated knowledge base. Delete the
surplus wikis first if this has to be reversed.
"""
op.create_unique_constraint(OLD_UNIQUE, "wiki_projects", ["source_url"])
op.drop_constraint(NEW_UNIQUE, "wiki_projects", type_="unique")
1 change: 0 additions & 1 deletion backend/app/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@
model_runtime.router, prefix="/model-runtime", tags=["model-runtime"]
)
api_router.include_router(retrievers.router, prefix="/retrievers", tags=["retrievers"])
api_router.include_router(wiki.router, prefix="/wiki", tags=["wiki"])
api_router.include_router(
wiki.internal_router, prefix="/internal/wiki", tags=["wiki-internal"]
)
Expand Down
Loading
Loading