sec-report-kit 0.2.5__tar.gz → 0.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/CHANGELOG.md +19 -0
  2. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/PKG-INFO +31 -1
  3. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/README.md +30 -0
  4. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/pyproject.toml +1 -1
  5. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/requirements.txt +1 -1
  6. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_consolidated_html.bat +12 -1
  7. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_consolidated_html.sh +8 -1
  8. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/__init__.py +1 -1
  9. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/cli.py +116 -13
  10. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/generate_consolidated_security_report.py +16 -0
  11. sec_report_kit-0.2.7/src/sec_report_kit/report/html_renderer.py +573 -0
  12. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/tests/test_cli.py +249 -5
  13. sec_report_kit-0.2.7/tests/test_html_renderer.py +166 -0
  14. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/tests/test_parsers.py +73 -0
  15. sec_report_kit-0.2.5/src/sec_report_kit/report/html_renderer.py +0 -214
  16. sec_report_kit-0.2.5/tests/test_html_renderer.py +0 -83
  17. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/.gitattributes +0 -0
  18. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/.github/workflows/pip-audit.yml +0 -0
  19. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/.github/workflows/publish-pypi.yml +0 -0
  20. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/.github/workflows/publish-testpypi.yml +0 -0
  21. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/.github/workflows/trivy-fs-audit.yml +0 -0
  22. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/.github/workflows/unit-test-coverage-gate.yml +0 -0
  23. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/.gitignore +0 -0
  24. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/bandit-gaps-vs-sast-open-source-tools.md +0 -0
  25. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/check_assets.py +0 -0
  26. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/download_safety_db.bat +0 -0
  27. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/download_safety_db.py +0 -0
  28. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/download_safety_db.sh +0 -0
  29. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/install_external_clis.py +0 -0
  30. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/install_tools.bat +0 -0
  31. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/install_tools.sh +0 -0
  32. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_bandit_html.bat +0 -0
  33. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_bandit_html.sh +0 -0
  34. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_checkov_html.bat +0 -0
  35. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_checkov_html.sh +0 -0
  36. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_codeql_html.bat +0 -0
  37. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_codeql_html.sh +0 -0
  38. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_gitleaks_html.bat +0 -0
  39. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_gitleaks_html.sh +0 -0
  40. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_osv_scanner_html.bat +0 -0
  41. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_osv_scanner_html.sh +0 -0
  42. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_pip_audit_html.bat +0 -0
  43. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_pip_audit_html.sh +0 -0
  44. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_safety_html.bat +0 -0
  45. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_safety_html.sh +0 -0
  46. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_semgrep_html.bat +0 -0
  47. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_semgrep_html.sh +0 -0
  48. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_tfsec_html.bat +0 -0
  49. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_tfsec_html.sh +0 -0
  50. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_trivy_html.bat +0 -0
  51. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_trivy_html.sh +0 -0
  52. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_trufflehog_html.bat +0 -0
  53. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/render_trufflehog_html.sh +0 -0
  54. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_bandit.bat +0 -0
  55. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_bandit.sh +0 -0
  56. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_checkov.bat +0 -0
  57. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_checkov.sh +0 -0
  58. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_codeql.bat +0 -0
  59. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_codeql.sh +0 -0
  60. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_gitleaks.bat +0 -0
  61. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_gitleaks.sh +0 -0
  62. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_osv_scanner.bat +0 -0
  63. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_osv_scanner.sh +0 -0
  64. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_pip_audit.bat +0 -0
  65. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_pip_audit.sh +0 -0
  66. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_safety.bat +0 -0
  67. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_safety.sh +0 -0
  68. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_semgrep.bat +0 -0
  69. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_semgrep.sh +0 -0
  70. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_tfsec.bat +0 -0
  71. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_tfsec.sh +0 -0
  72. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_trivy.bat +0 -0
  73. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_trivy.sh +0 -0
  74. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_trufflehog.bat +0 -0
  75. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_trufflehog.sh +0 -0
  76. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_unit_tests_with_coverage.bat +0 -0
  77. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/scripts/run_unit_tests_with_coverage.sh +0 -0
  78. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/__main__.py +0 -0
  79. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/mcp/__init__.py +0 -0
  80. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/mcp/server.py +0 -0
  81. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/models.py +0 -0
  82. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/__init__.py +0 -0
  83. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/bandit.py +0 -0
  84. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/checkov.py +0 -0
  85. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/codeql.py +0 -0
  86. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/gitleaks.py +0 -0
  87. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/osv_scanner.py +0 -0
  88. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/pip_audit.py +0 -0
  89. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/safety.py +0 -0
  90. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/semgrep.py +0 -0
  91. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/tfsec.py +0 -0
  92. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/trivy.py +0 -0
  93. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/trufflehog.py +0 -0
  94. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/report/__init__.py +0 -0
  95. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/services/__init__.py +0 -0
  96. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/services/normalize.py +0 -0
  97. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/src/sec_report_kit/services/summarize.py +0 -0
  98. {sec_report_kit-0.2.5 → sec_report_kit-0.2.7}/tests/test_mcp_server.py +0 -0
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## 0.2.7 - 2026-05-17
6
+
7
+ ### Added
8
+ - Test coverage now includes the consolidated date-filter helpers, Safety parser fallback branches, and consolidated HTML dashboard visibility paths, bringing enforced suite coverage to 100%.
9
+
10
+ ### Fixed
11
+ - `requirements.txt` now pins `urllib3==2.7.0` to resolve the Trivy-reported CVEs affecting `2.6.3`.
12
+
13
+ ## 0.2.6 - 2026-05-17
14
+
15
+ ### Added
16
+ - Consolidated report rendering now supports bounded file pickup with `--modified-until`, enabling date-range selection when combined with `--modified-since`.
17
+
18
+ ### Changed
19
+ - README examples now document both date-range filtering and "from date until today" usage for consolidated report generation.
20
+
21
+ ### Fixed
22
+ - `scripts/render_consolidated_html.bat` no longer forwards the first two positional arguments as unexpected extras on Windows.
23
+
5
24
  ## 0.2.5 - 2026-05-17
6
25
 
7
26
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sec-report-kit
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: Generate HTML security reports from Trivy, pip-audit, Safety, Bandit, and Gitleaks JSON
5
5
  Author: Ravi Konduru
6
6
  License: MIT
@@ -252,6 +252,36 @@ scripts\render_consolidated_html.bat
252
252
  scripts\render_consolidated_html.bat security_reports sec-report-kit
253
253
  ```
254
254
 
255
+ You can also restrict which report files are picked up by modification time and cap how many of the newest files are included:
256
+
257
+ ```bash
258
+ # Only files modified today
259
+ bash scripts/render_consolidated_html.sh security_reports sec-report-kit --modified-since today
260
+ scripts\render_consolidated_html.bat security_reports sec-report-kit --modified-since today
261
+
262
+ # Since yesterday
263
+ bash scripts/render_consolidated_html.sh security_reports sec-report-kit --modified-since yesterday
264
+ scripts\render_consolidated_html.bat security_reports sec-report-kit --modified-since yesterday
265
+
266
+ # Last 7 days
267
+ bash scripts/render_consolidated_html.sh security_reports sec-report-kit --modified-since last-7-days
268
+ scripts\render_consolidated_html.bat security_reports sec-report-kit --modified-since last-7-days
269
+
270
+ # Since a fixed date and only the 10 newest matching files
271
+ bash scripts/render_consolidated_html.sh security_reports sec-report-kit --modified-since 2026-05-01 --limit 10
272
+ scripts\render_consolidated_html.bat security_reports sec-report-kit --modified-since 2026-05-01 --limit 10
273
+
274
+ # Between two dates
275
+ bash scripts/render_consolidated_html.sh security_reports sec-report-kit --modified-since 2026-05-01 --modified-until 2026-05-10
276
+ scripts\render_consolidated_html.bat security_reports sec-report-kit --modified-since 2026-05-01 --modified-until 2026-05-10
277
+
278
+ # From a fixed date until today
279
+ bash scripts/render_consolidated_html.sh security_reports sec-report-kit --modified-since 2026-05-01
280
+ scripts\render_consolidated_html.bat security_reports sec-report-kit --modified-since 2026-05-01
281
+ ```
282
+
283
+ `--modified-since` accepts ISO dates/datetimes plus `today`, `yesterday`, `last-week`, and `last-7-days`. `--modified-until` accepts ISO dates/datetimes plus `today` and `yesterday`. Use `--modified-since` by itself for "from date until now", or combine both options for a bounded date range. When `--limit` is used, the newest matching files are selected first.
284
+
255
285
  Convert sample Bandit JSON report to HTML:
256
286
 
257
287
  ```bash
@@ -225,6 +225,36 @@ scripts\render_consolidated_html.bat
225
225
  scripts\render_consolidated_html.bat security_reports sec-report-kit
226
226
  ```
227
227
 
228
+ You can also restrict which report files are picked up by modification time and cap how many of the newest files are included:
229
+
230
+ ```bash
231
+ # Only files modified today
232
+ bash scripts/render_consolidated_html.sh security_reports sec-report-kit --modified-since today
233
+ scripts\render_consolidated_html.bat security_reports sec-report-kit --modified-since today
234
+
235
+ # Since yesterday
236
+ bash scripts/render_consolidated_html.sh security_reports sec-report-kit --modified-since yesterday
237
+ scripts\render_consolidated_html.bat security_reports sec-report-kit --modified-since yesterday
238
+
239
+ # Last 7 days
240
+ bash scripts/render_consolidated_html.sh security_reports sec-report-kit --modified-since last-7-days
241
+ scripts\render_consolidated_html.bat security_reports sec-report-kit --modified-since last-7-days
242
+
243
+ # Since a fixed date and only the 10 newest matching files
244
+ bash scripts/render_consolidated_html.sh security_reports sec-report-kit --modified-since 2026-05-01 --limit 10
245
+ scripts\render_consolidated_html.bat security_reports sec-report-kit --modified-since 2026-05-01 --limit 10
246
+
247
+ # Between two dates
248
+ bash scripts/render_consolidated_html.sh security_reports sec-report-kit --modified-since 2026-05-01 --modified-until 2026-05-10
249
+ scripts\render_consolidated_html.bat security_reports sec-report-kit --modified-since 2026-05-01 --modified-until 2026-05-10
250
+
251
+ # From a fixed date until today
252
+ bash scripts/render_consolidated_html.sh security_reports sec-report-kit --modified-since 2026-05-01
253
+ scripts\render_consolidated_html.bat security_reports sec-report-kit --modified-since 2026-05-01
254
+ ```
255
+
256
+ `--modified-since` accepts ISO dates/datetimes plus `today`, `yesterday`, `last-week`, and `last-7-days`. `--modified-until` accepts ISO dates/datetimes plus `today` and `yesterday`. Use `--modified-since` by itself for "from date until now", or combine both options for a bounded date range. When `--limit` is used, the newest matching files are selected first.
257
+
228
258
  Convert sample Bandit JSON report to HTML:
229
259
 
230
260
  ```bash
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sec-report-kit"
7
- version = "0.2.5"
7
+ version = "0.2.7"
8
8
  description = "Generate HTML security reports from Trivy, pip-audit, Safety, Bandit, and Gitleaks JSON"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -80,5 +80,5 @@ twine==6.2.0
80
80
  typer==0.25.1
81
81
  typing-inspection==0.4.2
82
82
  typing_extensions==4.15.0
83
- urllib3==2.6.3
83
+ urllib3==2.7.0
84
84
  uvicorn==0.46.0
@@ -10,6 +10,17 @@ if "%REPORT_DIR%"=="" set "REPORT_DIR=security_reports"
10
10
  set "TARGET_NAME=%~2"
11
11
  if "%TARGET_NAME%"=="" for %%I in ("%~dp0..") do set "TARGET_NAME=%%~nxI"
12
12
 
13
+ if not "%~1"=="" shift
14
+ if not "%~1"=="" shift
15
+
16
+ set "EXTRA_ARGS="
17
+ :collect_args
18
+ if "%~1"=="" goto after_collect_args
19
+ set "EXTRA_ARGS=%EXTRA_ARGS% "%~1""
20
+ shift
21
+ goto collect_args
22
+ :after_collect_args
23
+
13
24
  if not exist "%REPORT_DIR%" (
14
25
  echo Missing input directory: %REPORT_DIR%
15
26
  exit /b 1
@@ -17,7 +28,7 @@ if not exist "%REPORT_DIR%" (
17
28
 
18
29
  set "OUT_HTML=%REPORT_DIR%\consolidated-security-report.html"
19
30
 
20
- "%PYTHON_BIN%" -m sec_report_kit render consolidated --input "%REPORT_DIR%" --output "%REPORT_DIR%" --target "%TARGET_NAME%"
31
+ "%PYTHON_BIN%" -m sec_report_kit render consolidated --input "%REPORT_DIR%" --output "%REPORT_DIR%" --target "%TARGET_NAME%" %EXTRA_ARGS%
21
32
  if errorlevel 1 exit /b 1
22
33
 
23
34
  echo HTML report written to %OUT_HTML%
@@ -11,6 +11,13 @@ ROOT_NAME="$(basename "$(cd "$(dirname "$0")/.." && pwd)")"
11
11
  TARGET_NAME="${2:-${ROOT_NAME}}"
12
12
  OUT_HTML="${REPORT_DIR}/consolidated-security-report.html"
13
13
 
14
+ if [[ $# -gt 0 ]]; then
15
+ shift
16
+ fi
17
+ if [[ $# -gt 0 ]]; then
18
+ shift
19
+ fi
20
+
14
21
  open_html() {
15
22
  if command -v xdg-open >/dev/null 2>&1; then
16
23
  xdg-open "$1" >/dev/null 2>&1 &
@@ -28,7 +35,7 @@ if [[ ! -d "${REPORT_DIR}" ]]; then
28
35
  exit 1
29
36
  fi
30
37
 
31
- "${PYTHON_BIN}" -m sec_report_kit render consolidated --input "${REPORT_DIR}" --output "${REPORT_DIR}" --target "${TARGET_NAME}"
38
+ "${PYTHON_BIN}" -m sec_report_kit render consolidated --input "${REPORT_DIR}" --output "${REPORT_DIR}" --target "${TARGET_NAME}" "$@"
32
39
 
33
40
  echo "HTML report written to ${OUT_HTML}"
34
41
  open_html "${OUT_HTML}"
@@ -1,4 +1,4 @@
1
1
  """sec_report_kit package."""
2
2
 
3
3
  __all__ = ["__version__"]
4
- __version__ = "0.2.5"
4
+ __version__ = "0.2.7"
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import datetime as dt
3
4
  import json
4
5
  from pathlib import Path
5
6
  from typing import Any
@@ -18,7 +19,7 @@ from sec_report_kit.parsers.semgrep import parse_semgrep_json
18
19
  from sec_report_kit.parsers.tfsec import parse_tfsec_json
19
20
  from sec_report_kit.parsers.trivy import parse_trivy_json
20
21
  from sec_report_kit.parsers.trufflehog import parse_trufflehog_json
21
- from sec_report_kit.report.html_renderer import render_html_report
22
+ from sec_report_kit.report.html_renderer import render_consolidated_dashboard_report, render_html_report
22
23
  from sec_report_kit.services.summarize import count_by_severity, sort_findings
23
24
 
24
25
  app = typer.Typer(help="Security report kit CLI")
@@ -28,6 +29,90 @@ app.add_typer(render_app, name="render")
28
29
  app.add_typer(mcp_app, name="mcp")
29
30
 
30
31
 
32
+ def _parse_modified_since(value: str) -> dt.datetime:
33
+ local_tz = dt.datetime.now().astimezone().tzinfo or dt.timezone.utc
34
+ now = dt.datetime.now(local_tz)
35
+ normalized = value.strip().lower()
36
+
37
+ if normalized == "today":
38
+ return now.replace(hour=0, minute=0, second=0, microsecond=0)
39
+ if normalized == "yesterday":
40
+ return (now - dt.timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
41
+ if normalized in {"last-week", "last-7-days"}:
42
+ return now - dt.timedelta(days=7)
43
+
44
+ try:
45
+ parsed = dt.datetime.fromisoformat(value.replace("Z", "+00:00"))
46
+ except ValueError as exc:
47
+ raise typer.BadParameter(
48
+ "Use ISO date/datetime or one of: today, yesterday, last-week, last-7-days"
49
+ ) from exc
50
+
51
+ if parsed.tzinfo is None:
52
+ return parsed.replace(tzinfo=local_tz)
53
+ return parsed.astimezone(local_tz)
54
+
55
+
56
+ def _parse_modified_until(value: str) -> dt.datetime:
57
+ local_tz = dt.datetime.now().astimezone().tzinfo or dt.timezone.utc
58
+ normalized = value.strip().lower()
59
+
60
+ if normalized == "today":
61
+ return dt.datetime.now(local_tz).replace(hour=23, minute=59, second=59, microsecond=999999)
62
+ if normalized == "yesterday":
63
+ return (dt.datetime.now(local_tz) - dt.timedelta(days=1)).replace(
64
+ hour=23,
65
+ minute=59,
66
+ second=59,
67
+ microsecond=999999,
68
+ )
69
+
70
+ parsed = _parse_modified_since(value)
71
+ if "T" not in value and " " not in value:
72
+ return parsed.replace(hour=23, minute=59, second=59, microsecond=999999)
73
+ return parsed
74
+
75
+
76
+ def _collect_consolidated_candidates(
77
+ input_dir: Path,
78
+ modified_since: str | None,
79
+ modified_until: str | None,
80
+ limit: int | None,
81
+ ) -> list[Path]:
82
+ candidates = [
83
+ path
84
+ for path in input_dir.iterdir()
85
+ if path.is_file()
86
+ and (
87
+ path.suffix.lower() in {".json", ".sarif", ".ndjson", ".jsonl"}
88
+ or path.name.lower().endswith(".sarif.json")
89
+ )
90
+ ]
91
+
92
+ if modified_since:
93
+ cutoff = _parse_modified_since(modified_since)
94
+ candidates = [
95
+ path
96
+ for path in candidates
97
+ if dt.datetime.fromtimestamp(path.stat().st_mtime, tz=cutoff.tzinfo) >= cutoff
98
+ ]
99
+
100
+ if modified_until:
101
+ cutoff = _parse_modified_until(modified_until)
102
+ candidates = [
103
+ path
104
+ for path in candidates
105
+ if dt.datetime.fromtimestamp(path.stat().st_mtime, tz=cutoff.tzinfo) <= cutoff
106
+ ]
107
+
108
+ candidates.sort(key=lambda path: (-path.stat().st_mtime, path.name.lower()))
109
+
110
+ if limit is not None:
111
+ candidates = candidates[:limit]
112
+
113
+ return candidates
114
+
115
+
31
116
  def _load_json(path: Path) -> Any:
32
117
  # Accept both UTF-8 and UTF-8 BOM encoded JSON files.
33
118
  raw = path.read_text(encoding="utf-8-sig")
@@ -227,18 +312,29 @@ def render_consolidated(
227
312
  help="Output folder where consolidated HTML will be written",
228
313
  ),
229
314
  target: str = typer.Option("consolidated-scan", "--target", help="Consolidated target label"),
315
+ modified_since: str | None = typer.Option(
316
+ None,
317
+ "--modified-since",
318
+ help="Only include files modified on or after this time. Accepts ISO date/datetime or: today, yesterday, last-week, last-7-days",
319
+ ),
320
+ modified_until: str | None = typer.Option(
321
+ None,
322
+ "--modified-until",
323
+ help="Only include files modified on or before this time. Accepts ISO date/datetime, today, or yesterday. Combine with --modified-since for a date range.",
324
+ ),
325
+ limit: int | None = typer.Option(
326
+ None,
327
+ "--limit",
328
+ min=1,
329
+ help="Maximum number of most recently modified report files to include after filtering",
330
+ ),
230
331
  ) -> None:
231
332
  """Render a consolidated HTML report from all supported report files in a directory."""
232
- candidates = sorted(
233
- [
234
- path
235
- for path in input.iterdir()
236
- if path.is_file()
237
- and (
238
- path.suffix.lower() in {".json", ".sarif", ".ndjson", ".jsonl"}
239
- or path.name.lower().endswith(".sarif.json")
240
- )
241
- ]
333
+ candidates = _collect_consolidated_candidates(
334
+ input_dir=input,
335
+ modified_since=modified_since,
336
+ modified_until=modified_until,
337
+ limit=limit,
242
338
  )
243
339
 
244
340
  all_findings = []
@@ -258,11 +354,12 @@ def render_consolidated(
258
354
 
259
355
  all_findings = sort_findings(all_findings)
260
356
  counts = count_by_severity(all_findings)
261
- report_html = render_html_report(
357
+ report_html = render_consolidated_dashboard_report(
262
358
  target_ref=target,
263
- source_label="consolidated",
264
359
  findings=all_findings,
265
360
  counts=counts,
361
+ reports_dir=input,
362
+ output_dir=output,
266
363
  )
267
364
 
268
365
  output.mkdir(parents=True, exist_ok=True)
@@ -271,6 +368,12 @@ def render_consolidated(
271
368
 
272
369
  typer.echo(f"[OK] Report generated: {output_file}")
273
370
  typer.echo(f"[INFO] Input directory: {input}")
371
+ if modified_since:
372
+ typer.echo(f"[INFO] Modified since filter: {modified_since}")
373
+ if modified_until:
374
+ typer.echo(f"[INFO] Modified until filter: {modified_until}")
375
+ if limit is not None:
376
+ typer.echo(f"[INFO] File limit: {limit}")
274
377
  typer.echo(f"[INFO] Files included: {included_files}")
275
378
  typer.echo(f"[INFO] Files skipped: {skipped_files}")
276
379
  typer.echo(f"[INFO] Total findings: {len(all_findings)}")
@@ -13,12 +13,28 @@ def main() -> int:
13
13
  parser.add_argument("--input", required=True, help="Input folder containing report files")
14
14
  parser.add_argument("--output", required=True, help="Output folder for generated HTML")
15
15
  parser.add_argument("--target", default="consolidated-scan", help="Target label used in HTML report")
16
+ parser.add_argument(
17
+ "--modified-since",
18
+ help="Only include files modified on or after this time. Accepts ISO date/datetime or: today, yesterday, last-week, last-7-days",
19
+ )
20
+ parser.add_argument(
21
+ "--modified-until",
22
+ help="Only include files modified on or before this time. Accepts ISO date/datetime, today, or yesterday. Combine with --modified-since for a date range.",
23
+ )
24
+ parser.add_argument(
25
+ "--limit",
26
+ type=int,
27
+ help="Maximum number of most recently modified report files to include after filtering",
28
+ )
16
29
  args = parser.parse_args()
17
30
 
18
31
  render_consolidated(
19
32
  input=Path(args.input),
20
33
  output=Path(args.output),
21
34
  target=args.target,
35
+ modified_since=args.modified_since,
36
+ modified_until=args.modified_until,
37
+ limit=args.limit,
22
38
  )
23
39
  return 0
24
40