77
88from pb_spec .exceptions import FileReadError
99from pb_spec .git_utils import get_git_modified_files
10+ from pb_spec .validation .io import read_file_content , read_spec_file
1011from pb_spec .validation .parser import (
1112 TASK_CHECKBOX_RE ,
1213 UNCHECKED_TASK_CHECKBOX_RE ,
14+ TaskBlock ,
1315 parse_task_blocks ,
1416 task_display_name ,
1517)
16- from pb_spec .validation .plan import read_file_content , read_spec_file
1718from pb_spec .validation .result import (
1819 ErrorSeverity ,
1920 ValidationError ,
@@ -51,23 +52,24 @@ def validate_feature_scenarios(spec_dir: Path) -> list[ValidationError]:
5152 return errors
5253
5354
54- def run_codebase_scan (mode : ValidationMode ) -> ScanResult :
55+ def run_codebase_scan (mode : ValidationMode , root_dir : Path | str = "." ) -> ScanResult :
5556 """Scan codebase for code quality issues.
5657
5758 Args:
5859 mode: ValidationMode.TASK for subagent self-check (scoped to git-modified
5960 files), ValidationMode.BUILD for full scan.
61+ root_dir: Root directory to scan. Defaults to current working directory.
6062
6163 Returns:
6264 Pure ScanResult without side effects. Callers are responsible
6365 for presenting the results.
6466 """
6567 target_files : set [Path ] | None = None
6668 if mode == ValidationMode .TASK :
67- target_files = get_git_modified_files ()
69+ target_files = get_git_modified_files (root_dir )
6870
6971 scanner = CodeScanner (
70- root_dir = "." ,
72+ root_dir = root_dir ,
7173 check_skipped_tests = True ,
7274 check_not_implemented = True ,
7375 check_todos = True ,
@@ -99,7 +101,7 @@ def _scan_result_to_errors(scan_result: ScanResult) -> list[ValidationError]:
99101 return errors
100102
101103
102- def _validate_task_completion (task_blocks : list , content : str ) -> list [ValidationError ]:
104+ def _validate_task_completion (task_blocks : list [ TaskBlock ] , content : str ) -> list [ValidationError ]:
103105 """Validate that all tasks are marked DONE with complete steps.
104106
105107 Args:
@@ -176,7 +178,7 @@ def _validate_task_completion(task_blocks: list, content: str) -> list[Validatio
176178 return errors
177179
178180
179- def _validate_task_completion_warnings (task_blocks : list ) -> list [str ]:
181+ def _validate_task_completion_warnings (task_blocks : list [ TaskBlock ] ) -> list [str ]:
180182 """Collect warnings for non-DONE tasks (SKIPPED, OBSOLETE).
181183
182184 Args:
@@ -193,9 +195,9 @@ def _validate_task_completion_warnings(task_blocks: list) -> list[str]:
193195 return warnings
194196
195197
196- def _validate_codebase_scan () -> list [ValidationError ]:
198+ def _validate_codebase_scan (root_dir : Path | str = "." ) -> list [ValidationError ]:
197199 """Run codebase scan and return errors."""
198- scan_result = run_codebase_scan (mode = ValidationMode .BUILD )
200+ scan_result = run_codebase_scan (mode = ValidationMode .BUILD , root_dir = root_dir )
199201 if not scan_result .has_issues :
200202 return []
201203
@@ -231,7 +233,6 @@ def validate_build(spec_dir: Path) -> ValidationResult:
231233 ],
232234 )
233235
234- tasks_file = spec_dir / "tasks.md"
235236 content , error_result = read_spec_file (tasks_file )
236237 if error_result is not None :
237238 return error_result
@@ -250,19 +251,27 @@ def validate_build(spec_dir: Path) -> ValidationResult:
250251 task_blocks = parse_task_blocks (content )
251252 warnings .extend (_validate_task_completion_warnings (task_blocks ))
252253 errors .extend (_validate_task_completion (task_blocks , content ))
253- errors .extend (_validate_codebase_scan ())
254+
255+ # Determine project root: spec_dir is typically specs/xxx, so root is two levels up
256+ project_root = spec_dir .parent .parent if spec_dir .parent .name == "specs" else spec_dir .parent
257+ errors .extend (_validate_codebase_scan (root_dir = project_root ))
258+
254259 errors .extend (validate_feature_scenarios (spec_dir ))
255260
256261 return ValidationResult (is_valid = len (errors ) == 0 , errors = errors , warnings = warnings )
257262
258263
259- def validate_task () -> ValidationResult :
264+ def validate_task (root_dir : Path | str = "." ) -> ValidationResult :
260265 """Subagent self-check before signaling READY_FOR_EVAL.
261266
262- Returns a pure ValidationResult without side effects.
263- Callers are responsible for presenting the results.
267+ Args:
268+ root_dir: Root directory to scan. Defaults to current working directory.
269+
270+ Returns:
271+ A pure ValidationResult without side effects.
272+ Callers are responsible for presenting the results.
264273 """
265- scan_result = run_codebase_scan (mode = ValidationMode .TASK )
274+ scan_result = run_codebase_scan (mode = ValidationMode .TASK , root_dir = root_dir )
266275 if not scan_result .has_issues :
267276 return ValidationResult (is_valid = True )
268277
0 commit comments