升级本机 Trellis 至 0.6.12(upgrade-trellis-0-6-12)

这个提交包含在:
2026-08-02 16:46:56 +08:00
父节点 45dc86c27a
当前提交 1c557e4926
共修改 19 个文件,包含 335 行新增和 50 行删除
+5 -2
查看文件
@@ -612,13 +612,16 @@ def clear_active_task(
platform_input: dict[str, Any] | None = None,
platform: str | None = None,
) -> ActiveTask:
"""Clear the active task by deleting the current session context file."""
"""Clear the active task by deleting its resolved session context file."""
context_key = resolve_context_key(platform_input, platform)
if not context_key:
return ActiveTask(None, "none")
previous = resolve_active_task(repo_root, platform_input, platform)
context_path = _context_path(repo_root, context_key)
if not previous.task_path or not previous.context_key:
return previous
context_path = _context_path(repo_root, previous.context_key)
if context_path.is_file():
_remove_file(context_path)
return previous
+9 -2
查看文件
@@ -10,11 +10,17 @@ import subprocess
from pathlib import Path
def run_git(args: list[str], cwd: Path | None = None) -> tuple[int, str, str]:
def run_git(
args: list[str],
cwd: Path | None = None,
timeout: float | None = None,
) -> tuple[int, str, str]:
"""Run a git command and return (returncode, stdout, stderr).
Uses UTF-8 encoding with -c i18n.logOutputEncoding=UTF-8 to ensure
consistent output across all platforms (Windows, macOS, Linux).
consistent output across all platforms (Windows, macOS, Linux). Callers
may provide a timeout for best-effort probes; normal Git operations remain
unbounded by default.
"""
try:
git_args = ["git", "-c", "i18n.logOutputEncoding=UTF-8"] + args
@@ -25,6 +31,7 @@ def run_git(args: list[str], cwd: Path | None = None) -> tuple[int, str, str]:
text=True,
encoding="utf-8",
errors="replace",
timeout=timeout,
)
return result.returncode, result.stdout, result.stderr
except Exception as e:
+64 -11
查看文件
@@ -17,6 +17,7 @@ import json
import os
import re
import subprocess
import sys
from pathlib import Path
from .active_task import resolve_context_key
@@ -64,11 +65,17 @@ _POLYREPO_IGNORED_DIRS = {
"__pycache__",
}
_POLYREPO_SCAN_MAX_DEPTH = 2
_POLYREPO_SCAN_MAX_REPOS = 8
_GIT_PROBE_TIMEOUT_SECONDS = 2.0
def _is_git_worktree(path: Path) -> bool:
"""Return True when path is inside a Git worktree."""
rc, out, _ = run_git(["rev-parse", "--is-inside-work-tree"], cwd=path)
rc, out, _ = run_git(
["rev-parse", "--is-inside-work-tree"],
cwd=path,
timeout=_GIT_PROBE_TIMEOUT_SECONDS,
)
return rc == 0 and out.strip().lower() == "true"
@@ -91,13 +98,27 @@ def _collect_git_repo_info(name: str, rel_path: str, repo_dir: Path) -> dict | N
if not (repo_dir / ".git").exists():
return None
_, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_dir)
status_rc, status_out, _ = run_git(
["status", "--porcelain"],
cwd=repo_dir,
timeout=_GIT_PROBE_TIMEOUT_SECONDS,
)
if status_rc != 0:
return None
changes = len([line for line in status_out.splitlines() if line.strip()])
_, branch_out, _ = run_git(
["branch", "--show-current"],
cwd=repo_dir,
timeout=_GIT_PROBE_TIMEOUT_SECONDS,
)
branch = branch_out.strip() or "unknown"
_, status_out, _ = run_git(["status", "--porcelain"], cwd=repo_dir)
changes = len([l for l in status_out.splitlines() if l.strip()])
_, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=repo_dir)
_, log_out, _ = run_git(
["log", "--oneline", "-5"],
cwd=repo_dir,
timeout=_GIT_PROBE_TIMEOUT_SECONDS,
)
return {
"name": name,
@@ -120,20 +141,36 @@ def _collect_root_git_info(repo_root: Path) -> dict:
"recentCommits": [],
}
_, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
_, branch_out, _ = run_git(
["branch", "--show-current"],
cwd=repo_root,
timeout=_GIT_PROBE_TIMEOUT_SECONDS,
)
branch = branch_out.strip() or "unknown"
_, status_out, _ = run_git(["status", "--porcelain"], cwd=repo_root)
status_rc, status_out, _ = run_git(
["status", "--porcelain"],
cwd=repo_root,
timeout=_GIT_PROBE_TIMEOUT_SECONDS,
)
status_lines = [line for line in status_out.splitlines() if line.strip()]
_, short_out, _ = run_git(["status", "--short"], cwd=repo_root)
_, short_out, _ = run_git(
["status", "--short"],
cwd=repo_root,
timeout=_GIT_PROBE_TIMEOUT_SECONDS,
)
_, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=repo_root)
_, log_out, _ = run_git(
["log", "--oneline", "-5"],
cwd=repo_root,
timeout=_GIT_PROBE_TIMEOUT_SECONDS,
)
return {
"isRepo": True,
"branch": branch,
"isClean": len(status_lines) == 0,
"isClean": status_rc == 0 and len(status_lines) == 0,
"uncommittedChanges": len(status_lines),
"statusShort": short_out.splitlines(),
"recentCommits": _parse_recent_commits(log_out),
@@ -143,12 +180,16 @@ def _collect_root_git_info(repo_root: Path) -> dict:
def _discover_child_git_repos(repo_root: Path) -> list[tuple[str, str]]:
"""Discover child Git repositories using the init-time polyrepo heuristic."""
found: list[str] = []
overflow = False
def is_candidate_dir(path: Path) -> bool:
name = path.name
return not name.startswith(".") and name not in _POLYREPO_IGNORED_DIRS
def scan(rel_dir: Path, depth: int) -> None:
nonlocal overflow
if overflow:
return
if depth >= _POLYREPO_SCAN_MAX_DEPTH:
return
abs_dir = repo_root / rel_dir
@@ -165,11 +206,23 @@ def _discover_child_git_repos(repo_root: Path) -> list[tuple[str, str]]:
rel_dir / child.name if rel_dir != Path(".") else Path(child.name)
)
if (child / ".git").exists():
if len(found) >= _POLYREPO_SCAN_MAX_REPOS:
overflow = True
return
found.append(child_rel.as_posix())
continue
scan(child_rel, depth + 1)
scan(Path("."), 0)
if overflow:
print(
"warning: found more than "
f"{_POLYREPO_SCAN_MAX_REPOS} child Git repositories; "
"skipping automatic Git status collection. Configure explicit "
"packages entries with path and git: true in .trellis/config.yaml.",
file=sys.stderr,
)
return []
if len(found) < 2:
return []
return [(path.replace("/", "_"), path) for path in sorted(found)]
+4 -4
查看文件
@@ -236,22 +236,22 @@ def _validate_jsonl(jsonl_file: Path, repo_root: Path, task_dir: Path | None = N
if extension in _CODE_FILE_EXTENSIONS and not _is_exempt_from_code_file_warning(
file_path, task_rel
):
warning = (
warning_message = (
f"{file_name}:{line_num}: Warning: {file_path} looks like a code file — "
"implement/check.jsonl should reference spec/research docs; "
"agents read code themselves"
)
print(f" {colored(warning, Colors.YELLOW)}")
print(f" {colored(warning_message, Colors.YELLOW)}")
if max_file_bytes:
size = full_path.stat().st_size
if size > max_file_bytes:
warning = (
warning_message = (
f"{file_name}:{line_num}: Warning: {file_path} is {size} bytes, "
f"exceeds context_injection.max_file_bytes ({max_file_bytes}); "
"injection will truncate it"
)
print(f" {colored(warning, Colors.YELLOW)}")
print(f" {colored(warning_message, Colors.YELLOW)}")
if errors == 0:
print(f" {colored(f'{file_name}: ✓ ({real_entries} entries)', Colors.GREEN)}")