Approach: Walk the repo recursively, scan text files for lines containing TODO or FIXME (case-insensitive), collect per-file line numbers and counts, and emit a JSON summary. Use argparse for CLI, skip binary files and common ignore patterns.python
#!/usr/bin/env python3
import argparse, json, os, re, sys
PAT = re.compile(r'\b(TODO|FIXME)\b', re.IGNORECASE)
def is_text_file(path, blocksize=512):
try:
with open(path, 'rb') as f:
chunk = f.read(blocksize)
if b'\0' in chunk:
return False
except Exception:
return False
return True
def scan_file(path):
hits = []
try:
with open(path, 'r', encoding='utf-8', errors='replace') as f:
for i, line in enumerate(f, 1):
if PAT.search(line):
hits.append(i)
except Exception:
return None
return hits
def walk_and_scan(root, ignore_dirs):
report = {}
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in ignore_dirs]
for fn in filenames:
path = os.path.join(dirpath, fn)
if not is_text_file(path):
continue
hits = scan_file(path)
if hits:
report[path] = {'lines': hits, 'count': len(hits)}
return report
def main():
p = argparse.ArgumentParser(description='Scan repo for TODO/FIXME and emit JSON')
p.add_argument('root', nargs='?', default='.')
p.add_argument('--ignore', nargs='*', default=['.git','node_modules','venv'])
p.add_argument('--output', '-o', default='todos.json')
args = p.parse_args()
report = walk_and_scan(args.root, set(args.ignore))
summary = {
'files': report,
'total_files': len(report),
'total_comments': sum(v['count'] for v in report.values())
}
with open(args.output, 'w', encoding='utf-8') as out:
json.dump(summary, out, indent=2)
print(f"Wrote {args.output}: {summary['total_files']} files, {summary['total_comments']} comments")
if __name__ == '__main__':
main()
Key points:- Time complexity: O(total_bytes) — scans each file once.- Space: O(number_of_matches) for the report.Edge cases:- Binary files and encoding errors are skipped/handled.- Large single files: streaming line-by-line avoids loading entire file.Scaling for very large monorepos:- Use file globs and language filters to avoid scanning generated/binary files.- Parallelize file scanning with a worker pool (multiprocessing) to utilize multiple cores.- Use native fast tools (ripgrep) and parse its output for best throughput.- Cache file mtimes/checksums to do incremental scans; store results in a lightweight DB.- Stream JSON (newline-delimited) for incremental consumption to avoid building huge in-memory structures.Trade-offs:- Parallelism increases throughput but costs more I/O/CPU and complexity.- Using external tools (ripgrep) gives speed but adds dependency and portability considerations.- Caching reduces repeated work but adds storage and invalidation complexity.