docs: 更新 HTTP 错误码、用户积分、占卜运行及用户资料协议文档

This commit is contained in:
qzl
2026-04-10 16:45:45 +08:00
parent 1bc8bc6a27
commit 17ef460391
78 changed files with 18680 additions and 25 deletions
+305
View File
@@ -0,0 +1,305 @@
#!/usr/bin/env python3
"""
Worktree utilities for Multi-Agent Pipeline.
Provides:
get_worktree_config - Get worktree.yaml path
get_worktree_base_dir - Get worktree storage directory
get_worktree_copy_files - Get files to copy list
get_worktree_post_create_hooks - Get post-create hooks
get_agents_dir - Get agents registry directory
"""
from __future__ import annotations
from pathlib import Path
from .paths import (
DIR_WORKFLOW,
get_repo_root,
get_workspace_dir,
)
# =============================================================================
# YAML Simple Parser (no dependencies)
# =============================================================================
def _unquote(s: str) -> str:
"""Remove exactly one layer of matching surrounding quotes.
Unlike str.strip('"'), this only removes the outermost pair,
preserving any nested quotes inside the value.
Examples:
_unquote('"hello"') -> 'hello'
_unquote("'hello'") -> 'hello'
_unquote('"echo \\'hi\\'"') -> "echo 'hi'"
_unquote('hello') -> 'hello'
_unquote('"hello\\'') -> '"hello\\'' (mismatched, unchanged)
"""
if len(s) >= 2 and s[0] == s[-1] and s[0] in ('"', "'"):
return s[1:-1]
return s
def parse_simple_yaml(content: str) -> dict:
"""Parse simple YAML with nested dict support (no dependencies).
Supports:
- key: value (string)
- key: (followed by list items)
- item1
- item2
- key: (followed by nested dict)
nested_key: value
nested_key2:
- item
Uses indentation to detect nesting (2+ spaces deeper = child).
Args:
content: YAML content string.
Returns:
Parsed dict (values can be str, list[str], or dict).
"""
lines = content.splitlines()
result: dict = {}
_parse_yaml_block(lines, 0, 0, result)
return result
def _parse_yaml_block(
lines: list[str], start: int, min_indent: int, target: dict
) -> int:
"""Parse a YAML block into target dict, returning next line index."""
i = start
current_list: list | None = None
while i < len(lines):
line = lines[i]
stripped = line.strip()
# Skip empty lines and comments
if not stripped or stripped.startswith("#"):
i += 1
continue
# Calculate indentation
indent = len(line) - len(line.lstrip())
# If dedented past our block, we're done
if indent < min_indent:
break
if stripped.startswith("- "):
if current_list is not None:
current_list.append(_unquote(stripped[2:].strip()))
i += 1
elif ":" in stripped:
key, _, value = stripped.partition(":")
key = key.strip()
value = _unquote(value.strip())
current_list = None
if value:
# key: value
target[key] = value
i += 1
else:
# key: (no value) — peek ahead to determine list vs nested dict
next_i, next_line = _next_content_line(lines, i + 1)
if next_i >= len(lines):
target[key] = {}
i = next_i
elif next_line.strip().startswith("- "):
# It's a list
current_list = []
target[key] = current_list
i += 1
else:
next_indent = len(next_line) - len(next_line.lstrip())
if next_indent > indent:
# It's a nested dict
nested: dict = {}
target[key] = nested
i = _parse_yaml_block(lines, i + 1, next_indent, nested)
else:
# Empty value, same or less indent follows
target[key] = {}
i += 1
else:
i += 1
return i
def _next_content_line(lines: list[str], start: int) -> tuple[int, str]:
"""Find the next non-empty, non-comment line."""
i = start
while i < len(lines):
stripped = lines[i].strip()
if stripped and not stripped.startswith("#"):
return i, lines[i]
i += 1
return i, ""
def _yaml_get_value(config_file: Path, key: str) -> str | None:
"""Read simple value from worktree.yaml.
Args:
config_file: Path to config file.
key: Key to read.
Returns:
Value string or None.
"""
try:
content = config_file.read_text(encoding="utf-8")
data = parse_simple_yaml(content)
value = data.get(key)
if isinstance(value, str):
return value
except (OSError, IOError):
pass
return None
def _yaml_get_list(config_file: Path, section: str) -> list[str]:
"""Read list from worktree.yaml.
Args:
config_file: Path to config file.
section: Section name.
Returns:
List of items.
"""
try:
content = config_file.read_text(encoding="utf-8")
data = parse_simple_yaml(content)
value = data.get(section)
if isinstance(value, list):
return [str(item) for item in value]
except (OSError, IOError):
pass
return []
# =============================================================================
# Worktree Configuration
# =============================================================================
# Worktree config file relative path (relative to repo root)
WORKTREE_CONFIG_PATH = f"{DIR_WORKFLOW}/worktree.yaml"
def get_worktree_config(repo_root: Path | None = None) -> Path:
"""Get worktree.yaml config file path.
Args:
repo_root: Repository root path. Defaults to auto-detected.
Returns:
Absolute path to config file.
"""
if repo_root is None:
repo_root = get_repo_root()
return repo_root / WORKTREE_CONFIG_PATH
def get_worktree_base_dir(repo_root: Path | None = None) -> Path:
"""Get worktree base directory.
Args:
repo_root: Repository root path. Defaults to auto-detected.
Returns:
Absolute path to worktree base directory.
"""
if repo_root is None:
repo_root = get_repo_root()
config = get_worktree_config(repo_root)
worktree_dir = _yaml_get_value(config, "worktree_dir")
# Default value
if not worktree_dir:
worktree_dir = "../worktrees"
# Handle relative path
if worktree_dir.startswith("../") or worktree_dir.startswith("./"):
# Relative to repo_root
return repo_root / worktree_dir
else:
# Absolute path
return Path(worktree_dir)
def get_worktree_copy_files(repo_root: Path | None = None) -> list[str]:
"""Get files to copy list.
Args:
repo_root: Repository root path. Defaults to auto-detected.
Returns:
List of file paths to copy.
"""
if repo_root is None:
repo_root = get_repo_root()
config = get_worktree_config(repo_root)
return _yaml_get_list(config, "copy")
def get_worktree_post_create_hooks(repo_root: Path | None = None) -> list[str]:
"""Get post_create hooks.
Args:
repo_root: Repository root path. Defaults to auto-detected.
Returns:
List of commands to run.
"""
if repo_root is None:
repo_root = get_repo_root()
config = get_worktree_config(repo_root)
return _yaml_get_list(config, "post_create")
# =============================================================================
# Agents Registry
# =============================================================================
def get_agents_dir(repo_root: Path | None = None) -> Path | None:
"""Get agents directory for current developer.
Args:
repo_root: Repository root path. Defaults to auto-detected.
Returns:
Absolute path to agents directory, or None if no workspace.
"""
if repo_root is None:
repo_root = get_repo_root()
workspace_dir = get_workspace_dir(repo_root)
if workspace_dir:
return workspace_dir / ".agents"
return None
# =============================================================================
# Main Entry (for testing)
# =============================================================================
if __name__ == "__main__":
repo = get_repo_root()
print(f"Repository root: {repo}")
print(f"Worktree config: {get_worktree_config(repo)}")
print(f"Worktree base dir: {get_worktree_base_dir(repo)}")
print(f"Copy files: {get_worktree_copy_files(repo)}")
print(f"Post create hooks: {get_worktree_post_create_hooks(repo)}")
print(f"Agents dir: {get_agents_dir(repo)}")