CLI API Reference

Command line interface for reqtrace.

main(args=None)

CLI Entrypoint for reqtrace.

Source code in reqtrace/cli.py
def main(args: Optional[List[str]] = None):
    """CLI Entrypoint for reqtrace."""
    # @trace-start: REQ-CLI
    parser = argparse.ArgumentParser(description="Reqtrace: A GitOps-friendly requirements tracer.")

    parser.add_argument(
        "--reqs", default=["reqs/"], nargs="+", help="Path(s) to .rqtr requirement files or folder containing them. (Default: reqs/)"
    )

    parser.add_argument(
        "--src",
        default=["src/"],
        nargs="+",
        help="Path(s) to source code directories or files to scan. (Default: src/)",
    )

    parser.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        help="Enable verbose (DEBUG) logging output.",
    )

    parser.add_argument(
        "--html",
        metavar="DIR",
        help="Generate a structured multi-page HTML report in the specified directory.",
    )

    parsed_args = parser.parse_args(args)

    logging.basicConfig(
        level=logging.DEBUG if parsed_args.verbose else logging.INFO,
        format="%(message)s",
    )

    try:
        # 1. Load the requirements
        log.info("Loading requirements from %s...", parsed_args.reqs)
        all_req_data = _load_requirements(parsed_args.reqs)
        req_index = parse_requirements(all_req_data)

        # 2. Scan the source code
        log.info("Scanning source code at %s...", parsed_args.src)
        all_traces, file_lines = _load_source_code(parsed_args.src)

        # 3. Calculate Coverage
        log.info("Calculating traceability matrix...")
        report = calculate_coverage(req_index, all_traces, file_lines)

        # 4. Generate HTML Report (if requested)
        if parsed_args.html:
            enrich_metadata(req_index, report)

            output_dir = Path(parsed_args.html)
            generate_html(req_index, report, output_dir)
            log.info("Multi-page HTML report generated at: %s", output_dir.absolute())

        # 5. Print Summary to Console (plain output)
        print("\n=== REQTRACE COVERAGE REPORT ===")
        print(f"Total Requirements: {report.total_requirements}")
        print(f"Implemented (>=100%):  {report.implemented_requirements}")
        print(f"Partial (<100%):  {report.partial_requirements}")
        print(f"Missing (0%):     {report.missing_requirements}\n")

        if report.source_stats:
            print("--- SOURCE CODE COVERAGE ---")
            print(f"Total Source Files: {report.source_stats.total_files}")
            if report.source_stats.disabled_files > 0:
                print(f"Disabled Files:     {report.source_stats.disabled_files}")
            print(f"Total Source Lines: {report.source_stats.total_lines}")
            print(f"Mapped Lines:       {report.source_stats.mapped_lines}")
            print(f"Unmapped Lines:     {report.source_stats.unmapped_lines}\n")

        print("--- DETAILS ---")
        for req_id, cov in report.coverage_details.items():
            status = "✅ IMPLEMENTED" if cov.is_implemented else ("⚠️ PARTIAL" if cov.total_percentage > 0 else "❌ MISSING")
            print(f"[{status}] {req_id} - Coverage: {cov.total_percentage}%")

        if report.unmatched_traces:
            print("\n--- WARNING: UNMATCHED TRACE TAGS ---")
            print("The following trace tags refer to unknown requirement IDs.")
            for trace in report.unmatched_traces:
                pct_str = f"({trace.percentage}%)" if trace.percentage else ""
                print(f"Unknown ID: '{trace.req_id}' {pct_str} at {trace.file_path}:{trace.line_start}-{trace.line_end}")

        # Exit with error code if coverage is not 100%
        # (This fails the CI pipeline intentionally if requirements aren't met!)
        if report.missing_requirements > 0 or report.partial_requirements > 0:
            sys.exit(1)

        sys.exit(0)
        # @trace-end: REQ-CLI

    except Exception as e:  # pylint: disable=broad-exception-caught
        log.error("Error running reqtrace: %s", e)
        sys.exit(2)