Coverage for little_loops / work_verification.py: 15%
40 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-03-18 16:18 -0500
« prev ^ index » next coverage.py v7.12.0, created at 2026-03-18 16:18 -0500
1"""Work verification utilities for little-loops.
3Contains shared functions for verifying that actual implementation work
4was done, used by both issue_manager (ll-auto) and worker_pool (ll-parallel).
5"""
7from __future__ import annotations
9import subprocess
10from typing import TYPE_CHECKING
12if TYPE_CHECKING:
13 from little_loops.logger import Logger
16# Directories that are excluded when verifying work was done.
17# Changes to files in these directories don't count as "real work".
18EXCLUDED_DIRECTORIES = (
19 ".issues/",
20 "issues/", # Support non-dotted variant (issues.base_dir = "issues")
21 ".speckit/",
22 "thoughts/",
23 ".worktrees/",
24 ".auto-manage",
25)
28def filter_excluded_files(files: list[str]) -> list[str]:
29 """Filter out files in excluded directories.
31 Args:
32 files: List of file paths to filter
34 Returns:
35 List of files not in excluded directories
36 """
37 return [
38 f
39 for f in files
40 if f and not any(f.startswith(excluded) for excluded in EXCLUDED_DIRECTORIES)
41 ]
44def verify_work_was_done(logger: Logger, changed_files: list[str] | None = None) -> bool:
45 """Verify that actual work was done (not just issue file moves).
47 Returns True if there's evidence of implementation work - changes to files
48 outside of excluded directories like .issues/, thoughts/, etc.
50 This prevents marking issues as "completed" when no actual fix was implemented.
52 Args:
53 logger: Logger for output
54 changed_files: Optional list of changed files. If not provided,
55 will detect via git diff commands.
57 Returns:
58 True if meaningful file changes were detected
59 """
60 # If changed_files provided, use them directly (ll-parallel case)
61 if changed_files is not None:
62 meaningful_changes = filter_excluded_files(changed_files)
63 if meaningful_changes:
64 logger.info(
65 f"Found {len(meaningful_changes)} file(s) changed: {meaningful_changes[:5]}"
66 )
67 return True
68 # Log which excluded files were modified for diagnostic purposes
69 excluded_files = [f for f in changed_files if f]
70 logger.warning(
71 f"No meaningful changes detected - only excluded files modified: {excluded_files[:10]}"
72 )
73 return False
75 # Otherwise detect via git (ll-auto case)
76 all_excluded_files: list[str] = []
77 try:
78 # Check for uncommitted changes
79 result = subprocess.run(
80 ["git", "diff", "--name-only"],
81 capture_output=True,
82 text=True,
83 )
84 if result.returncode == 0:
85 files = result.stdout.strip().split("\n")
86 meaningful_changes = filter_excluded_files(files)
87 if meaningful_changes:
88 logger.info(
89 f"Found {len(meaningful_changes)} file(s) changed: {meaningful_changes[:5]}"
90 )
91 return True
92 # Collect excluded files for diagnostic logging
93 all_excluded_files.extend([f for f in files if f])
95 # Also check staged changes
96 result = subprocess.run(
97 ["git", "diff", "--cached", "--name-only"],
98 capture_output=True,
99 text=True,
100 )
101 if result.returncode == 0:
102 staged = result.stdout.strip().split("\n")
103 meaningful_staged = filter_excluded_files(staged)
104 if meaningful_staged:
105 logger.info(
106 f"Found {len(meaningful_staged)} staged file(s): {meaningful_staged[:5]}"
107 )
108 return True
109 # Collect excluded files for diagnostic logging
110 all_excluded_files.extend([f for f in staged if f and f not in all_excluded_files])
112 # Log which excluded files were modified for diagnostic purposes
113 if all_excluded_files:
114 logger.warning(
115 f"No meaningful changes detected - only excluded files modified: "
116 f"{all_excluded_files[:10]}"
117 )
118 else:
119 logger.warning("No meaningful changes detected - no files modified")
120 return False
122 except Exception as e:
123 logger.error(f"Could not verify work: {e}")
124 # Be conservative - don't assume work was done if we can't verify
125 return False