feat(chronology): regenerate chronology.md with v2 git-history classifier (closes 5-day desync gap)

This commit is contained in:
ed
2026-07-01 23:50:19 -04:00
parent fedfa6efc8
commit f5a08634b3
3 changed files with 320 additions and 235 deletions
+61 -14
View File
@@ -38,12 +38,22 @@ _METADATA_FIELD_PREFIXES = (
"**Parent umbrella:**",
"**Status:**",
"**Confidence:**",
"**Ancestors:**",
)
_WORK_COMMIT_PREFIXES = ("feat:", "fix:", "refactor:", "perf:", "test:", "docs(report):")
_WORK_COMMIT_PREFIXES = ("feat", "fix", "refactor", "perf", "test", "docs(report)")
_METADATA_COMMIT_PREFIXES = ("conductor(plan):", "conductor(state):", "conductor(track):", "docs(spec):", "docs(plan):")
def _is_work_commit(msg: str) -> bool:
"""Check if a commit message is a work commit (feat/fix/refactor/perf/test with optional scope)."""
for prefix in _WORK_COMMIT_PREFIXES:
if msg.startswith(prefix + ":") or msg.startswith(prefix + "("):
if not any(msg.startswith(m) for m in _METADATA_COMMIT_PREFIXES):
return True
return False
def extract_slug_date(folder_name: str) -> Optional[str]:
m = _SLUG_DATE_RE.search(folder_name)
if not m:
@@ -123,6 +133,23 @@ def _git_log(folder_relpath: str, *args: str) -> str:
return ""
def _git_log_multi(*folder_relpaths: str) -> str:
"""Get git log for multiple paths in a single subprocess call."""
try:
result = subprocess.run(
["git", "log", "--oneline", "--", *folder_relpaths],
capture_output=True,
text=True,
timeout=_GIT_TIMEOUT,
check=False,
)
if result.returncode != 0:
return ""
return result.stdout
except (subprocess.SubprocessError, OSError):
return ""
def _git_first_line(folder_relpath: str, *args: str) -> str:
out = _git_log(folder_relpath, *args)
stripped = out.strip()
@@ -188,16 +215,20 @@ def _last_commit_date(folder_relpath: str) -> str:
return "never"
def _count_work_commits(folder_relpath: str) -> int:
log: str = _git_log(folder_relpath, "--oneline")
def _count_work_commits_from_log(log: str) -> int:
count: int = 0
for line in log.splitlines():
msg: str = line.split(" ", 1)[1] if " " in line else ""
if msg.startswith(_WORK_COMMIT_PREFIXES) and not msg.startswith(_METADATA_COMMIT_PREFIXES):
if _is_work_commit(msg):
count += 1
return count
def _count_work_commits(folder_relpath: str) -> int:
log: str = _git_log(folder_relpath, "--oneline")
return _count_work_commits_from_log(log)
def _has_report_matching(reports_dir: Path, track_id: str, prefix: str) -> bool:
if not reports_dir.is_dir():
return False
@@ -215,6 +246,7 @@ def classify_status(
reports_dir: Path,
has_abort_report: bool = False,
state_status: str = "",
work_commits: int = -1,
) -> tuple[str, str, str]:
"""Git-history evidence classifier returning (status, confidence, reason).
@@ -238,7 +270,8 @@ def classify_status(
# 2. Git commit evidence
is_archive = folder_link.startswith("conductor/archive/")
is_tracks = folder_link.startswith("conductor/tracks/")
work_commits: int = _count_work_commits(folder_link)
if work_commits < 0:
work_commits = _count_work_commits(folder_link)
if work_commits >= 3:
return ("Completed", "medium", f"{work_commits} work commits")
if 1 <= work_commits <= 2 and is_tracks:
@@ -273,11 +306,31 @@ def walk_track_folders(root: Path) -> list[dict]:
folder_relpath = _to_posix(str(folder))
track_id: str = folder.name
slug_date = extract_slug_date(track_id)
# Get the full oneline log once. For archive folders, include both the
# archive path and the original tracks/ path (git mv preserves history
# but git log -- <archive_path> alone may miss pre-move commits)
if folder_relpath.startswith("conductor/archive/"):
original_path: str = folder_relpath.replace("conductor/archive/", "conductor/tracks/", 1)
oneline_log: str = _git_log_multi(folder_relpath, original_path)
else:
oneline_log = _git_log(folder_relpath, "--oneline")
log_lines: list[str] = [l for l in oneline_log.splitlines() if l.strip()]
commit_count: int = len(log_lines)
if slug_date:
date = slug_date
else:
elif log_lines:
# First commit date via reverse log
first_commit = _git_first_line(folder_relpath, "--reverse", "--format=%aI")
date = first_commit[:10] if first_commit else ""
else:
date = ""
# Derive init_sha and end_sha from the oneline log
if log_lines:
init_sha: str = log_lines[-1].split(" ", 1)[0] # oldest (last in reverse-chronological git log)
end_sha: str = log_lines[0].split(" ", 1)[0] # newest (first in git log)
else:
init_sha = ""
end_sha = ""
metadata_path = folder / "metadata.json"
meta_status: str = ""
if metadata_path.is_file():
@@ -287,6 +340,7 @@ def walk_track_folders(root: Path) -> list[dict]:
except (json.JSONDecodeError, OSError):
pass
state_status: str = _parse_state_status(folder / "state.toml")
work_commits: int = _count_work_commits_from_log(oneline_log)
status, confidence, reason = classify_status(
folder_link=folder_relpath,
current=meta_status or default_status,
@@ -294,16 +348,9 @@ def walk_track_folders(root: Path) -> list[dict]:
repo_root=repo_root,
reports_dir=reports_dir,
state_status=state_status,
work_commits=work_commits,
)
summary: str = extract_summary(folder)
init_sha: str = _git_first_line(folder_relpath, "--reverse", "--format=%h")
end_sha: str = _git_first_line(folder_relpath, "-1", "--format=%h")
if init_sha and end_sha:
range_log = _git_log(folder_relpath, "--oneline", f"{init_sha}..{end_sha}")
commit_count: int = range_log.count("\n") + (1 if init_sha != end_sha else 0)
else:
fallback_log = _git_log(folder_relpath, "--oneline")
commit_count = fallback_log.count("\n")
try:
folder_link = _to_posix(str(folder.relative_to(repo_root)))
except ValueError: