升级本机 Trellis 至 0.6.12(upgrade-trellis-0-6-12)
这个提交包含在:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)}")
|
||||
|
||||
在新工单中引用
屏蔽一个用户