feat(docs): save chat answers as docs — edit screen, commit + push to the .env docs branch
This commit is contained in:
+56
-1
@@ -9,7 +9,7 @@ import os
|
||||
import re
|
||||
from functools import lru_cache
|
||||
|
||||
from pydantic import field_validator
|
||||
from pydantic import ValidationInfo, field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
#: The built-in DEFAULT import formats (PLAN anchor A9, revised 2026-08-21;
|
||||
@@ -178,6 +178,30 @@ class Settings(BaseSettings):
|
||||
#: pattern).
|
||||
upload_max_mb: int = 512
|
||||
|
||||
# --- Docs push (phase 59: save a chat answer as documentation) ---
|
||||
#: The git repo a saved chat answer is committed to (phase 59, D3):
|
||||
#: **any** remote — a URL (``https://``, ``ssh://``, ``git@``) or a
|
||||
#: local path (generic git remote — no ``gh``, no GitHub assumption).
|
||||
#: While empty the feature is inert: the "Save as doc" action is
|
||||
#: hidden and the push endpoint 409s (the optional-feature pattern of
|
||||
#: the git-sources env fallback).
|
||||
docs_repo: str = ""
|
||||
#: The branch pushes land on (phase 59): each push cuts it fresh from
|
||||
#: ``docs_base_branch`` and ``git push --ff-only``s it — the owner
|
||||
#: opens the PR themselves (D3: no PR tooling). A git branch token,
|
||||
#: so no whitespace and no ``..`` (the validator below —
|
||||
#: all-or-nothing with ``docs_repo``).
|
||||
docs_branch: str = "bor-docs"
|
||||
#: The branch each push bases off (fetched/reset before the
|
||||
#: ``checkout -B`` of ``docs_branch``). Same token shape rules as
|
||||
#: ``docs_branch``.
|
||||
docs_base_branch: str = "main"
|
||||
#: Where ``docs_repo`` is checked out on the server. Raw string —
|
||||
#: ``Path.expanduser()`` is applied by the push service, not here
|
||||
#: (the ``sources_dir``/``upload_dir`` convention). Deliberately kept
|
||||
#: separate from ``sources_dir`` (the source checkouts).
|
||||
docs_work_dir: str = "~/bor-docs"
|
||||
|
||||
@field_validator("import_extensions")
|
||||
@classmethod
|
||||
def _import_extensions_known(cls, v: str) -> str:
|
||||
@@ -215,6 +239,28 @@ class Settings(BaseSettings):
|
||||
raise ValueError("upload_max_mb must be > 0 (MiB)")
|
||||
return v
|
||||
|
||||
@field_validator("docs_branch", "docs_base_branch")
|
||||
@classmethod
|
||||
def _docs_branch_tokens(cls, v: str, info: ValidationInfo) -> str:
|
||||
"""Git branch-token shape guard (phase 59, D3) — all-or-nothing:
|
||||
while ``docs_repo`` is empty the feature is inert, so the
|
||||
(ignored) branch values must not block startup; once a repo IS
|
||||
set, a blank / whitespace-bearing / ``..``-bearing branch is a
|
||||
typo that would corrupt a ``git checkout`` argument, so it fails
|
||||
loudly at startup (the ``agent_max_rounds`` pattern), naming the
|
||||
field."""
|
||||
repo = info.data.get("docs_repo")
|
||||
if not isinstance(repo, str) or not repo.strip():
|
||||
return v
|
||||
name = info.field_name or "docs branch"
|
||||
if not v.strip():
|
||||
raise ValueError(f"{name} must not be empty while docs_repo is set")
|
||||
if re.search(r"\s", v):
|
||||
raise ValueError(f"{name} must not contain whitespace (a git branch token)")
|
||||
if ".." in v:
|
||||
raise ValueError(f"{name} must not contain '..' (a git branch token)")
|
||||
return v
|
||||
|
||||
# Suggested questions (onboarding + empty state).
|
||||
suggestions: list[str] = [
|
||||
"How is my Kubernetes cluster set up?",
|
||||
@@ -242,6 +288,15 @@ class Settings(BaseSettings):
|
||||
"""
|
||||
return [part.strip() for part in self.git_sources.split(",") if part.strip()]
|
||||
|
||||
@property
|
||||
def docs_configured(self) -> bool:
|
||||
"""True while a docs repo is configured (phase 59): the "Save as
|
||||
doc" surface is live. Empty (or whitespace-only) ``docs_repo``
|
||||
→ the feature is inert — no button for anyone, the push
|
||||
endpoint 409s (the optional-feature pattern of the git-sources
|
||||
env fallback)."""
|
||||
return bool(self.docs_repo.strip())
|
||||
|
||||
@property
|
||||
def effective_api_key(self) -> str:
|
||||
"""API key for aipi: explicit setting, then $AIPI_KEY, then a placeholder."""
|
||||
|
||||
Reference in New Issue
Block a user