torusguard 1.3.6 → 2.0.0-alpha

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 (110) hide show
  1. package/.torusguard/.manifest.json +9 -8
  2. package/.torusguard/auth.json +5 -0
  3. package/.torusguard/rules_catalog.json +242 -518
  4. package/.torusguard/runs/report-latest.html +3529 -364
  5. package/.torusguard/runs/run-20260918-105940-audit/manifest.json +2 -1
  6. package/.torusguard/runs/run-20260918-105940-audit/remediation.md +7 -0
  7. package/.torusguard/runs/run-20260919-145701-audit/findings.json +1 -0
  8. package/.torusguard/runs/run-20260919-145701-audit/findings.md +8 -0
  9. package/.torusguard/runs/run-20260919-145701-audit/manifest.json +12 -0
  10. package/.torusguard/runs/run-20260919-145701-audit/summary.md +5 -0
  11. package/.torusguard/runs/run-20260919-151729-audit/findings.json +1 -0
  12. package/.torusguard/runs/run-20260919-151729-audit/findings.md +8 -0
  13. package/.torusguard/runs/run-20260919-151729-audit/manifest.json +13 -0
  14. package/.torusguard/runs/run-20260919-151729-audit/remediation.md +7 -0
  15. package/.torusguard/runs/run-20260919-151729-audit/summary.md +5 -0
  16. package/.torusguard/runs/run-20260919-152340-audit/findings.json +1 -0
  17. package/.torusguard/runs/run-20260919-152340-audit/findings.md +8 -0
  18. package/.torusguard/runs/run-20260919-152340-audit/manifest.json +12 -0
  19. package/.torusguard/runs/run-20260919-152340-audit/summary.md +5 -0
  20. package/.torusguard/runs/run-20260919-152345-audit/findings.json +1 -0
  21. package/.torusguard/runs/run-20260919-152345-audit/findings.md +8 -0
  22. package/.torusguard/runs/run-20260919-152345-audit/manifest.json +12 -0
  23. package/.torusguard/runs/run-20260919-152345-audit/summary.md +5 -0
  24. package/.torusguard/runs/run-20260919-153500-audit/findings.json +1 -0
  25. package/.torusguard/runs/run-20260919-153500-audit/findings.md +8 -0
  26. package/.torusguard/runs/run-20260919-153500-audit/manifest.json +12 -0
  27. package/.torusguard/runs/run-20260919-153500-audit/summary.md +5 -0
  28. package/.torusguard/scripts/__pycache__/apply_runner.cpython-314.pyc +0 -0
  29. package/.torusguard/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  30. package/.torusguard/scripts/__pycache__/harden_runner.cpython-314.pyc +0 -0
  31. package/.torusguard/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
  32. package/.torusguard/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
  33. package/.torusguard/scripts/__pycache__/recipes_runner.cpython-314.pyc +0 -0
  34. package/.torusguard/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
  35. package/.torusguard/scripts/apply_runner.py +101 -8
  36. package/.torusguard/scripts/audit_runner.py +116 -11
  37. package/.torusguard/scripts/harden_runner.py +122 -68
  38. package/.torusguard/scripts/html_reporter.py +1170 -253
  39. package/.torusguard/scripts/manifest_builder.py +2 -0
  40. package/.torusguard/scripts/recheck_runner.py +2 -1
  41. package/.torusguard/scripts/recipes_runner.py +91 -8
  42. package/.torusguard/scripts/report_sync.py +134 -7
  43. package/.torusguard/scripts/status_runner.py +267 -0
  44. package/.torusguard/snapshots/20260921-182638/go.mod.bak +11 -0
  45. package/.torusguard/snapshots/20260921-185213/file.go.bak +5 -0
  46. package/README.md +314 -364
  47. package/bin/torusguard.js +190 -20
  48. package/package.json +6 -2
  49. package/skills/torusguard/__pycache__/bootstrap.cpython-314.pyc +0 -0
  50. package/skills/torusguard/bootstrap.py +45 -6
  51. package/skills/torusguard/payload/.manifest.json +29 -14
  52. package/skills/torusguard/payload/TORUSGUARD.md +145 -145
  53. package/skills/torusguard/payload/agents/auditor.md +41 -41
  54. package/skills/torusguard/payload/agents/profiler.md +49 -49
  55. package/skills/torusguard/payload/agents/remediator.md +41 -41
  56. package/skills/torusguard/payload/agents/reviewer.md +39 -39
  57. package/skills/torusguard/payload/agents/validator.md +46 -46
  58. package/skills/torusguard/payload/config/scope.json +24 -24
  59. package/skills/torusguard/payload/references/csharp-security.md +41 -41
  60. package/skills/torusguard/payload/references/go-security.md +41 -41
  61. package/skills/torusguard/payload/references/java-security.md +40 -40
  62. package/skills/torusguard/payload/references/polyglot-security-matrix.md +25 -25
  63. package/skills/torusguard/payload/references/rust-security.md +40 -40
  64. package/skills/torusguard/payload/rules/custom/.gitkeep +1 -1
  65. package/skills/torusguard/payload/rules/custom/README.md +30 -30
  66. package/skills/torusguard/payload/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  67. package/skills/torusguard/payload/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
  68. package/skills/torusguard/payload/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
  69. package/skills/torusguard/payload/scripts/__pycache__/sarif_exporter.cpython-314.pyc +0 -0
  70. package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
  71. package/skills/torusguard/payload/scripts/apply_runner.py +101 -8
  72. package/skills/torusguard/payload/scripts/audit_runner.py +116 -11
  73. package/skills/torusguard/payload/scripts/harden_runner.py +122 -68
  74. package/skills/torusguard/payload/scripts/html_reporter.py +1170 -253
  75. package/skills/torusguard/payload/scripts/manifest_builder.py +2 -0
  76. package/skills/torusguard/payload/scripts/recheck_runner.py +2 -1
  77. package/skills/torusguard/payload/scripts/recipes_runner.py +91 -8
  78. package/skills/torusguard/payload/scripts/report_sync.py +134 -7
  79. package/skills/torusguard/payload/scripts/rules_sync.py +321 -321
  80. package/skills/torusguard/payload/scripts/safety_gate.py +64 -64
  81. package/skills/torusguard/payload/scripts/status_runner.py +267 -0
  82. package/skills/torusguard/payload/skills/torusguard/references/csharp-security.md +41 -41
  83. package/skills/torusguard/payload/skills/torusguard/references/go-security.md +41 -41
  84. package/skills/torusguard/payload/skills/torusguard/references/java-security.md +40 -40
  85. package/skills/torusguard/payload/skills/torusguard/references/polyglot-security-matrix.md +25 -25
  86. package/skills/torusguard/payload/skills/torusguard/references/rust-security.md +40 -40
  87. package/skills/torusguard/payload/templates/audit-report.template.md +54 -54
  88. package/skills/torusguard/payload/templates/authorization.template.md +34 -34
  89. package/skills/torusguard/payload/templates/finding-card.template.md +32 -32
  90. package/skills/torusguard/payload/templates/remediation-bundle.template.md +35 -35
  91. package/skills/torusguard/payload/workflows/apply.md +9 -1
  92. package/skills/torusguard/payload/workflows/audit.md +8 -2
  93. package/skills/torusguard/payload/workflows/harden.md +6 -1
  94. package/skills/torusguard/payload/workflows/init.md +7 -1
  95. package/skills/torusguard/payload/workflows/recipes.md +55 -0
  96. package/skills/torusguard/payload/workflows/report.md +59 -55
  97. package/skills/torusguard/payload/workflows/status.md +4 -1
  98. package/skills/torusguard/payload/workflows/torusguard-apply.md +110 -0
  99. package/skills/torusguard/payload/workflows/torusguard-audit.md +105 -0
  100. package/skills/torusguard/payload/workflows/torusguard-authorize.md +96 -0
  101. package/skills/torusguard/payload/workflows/torusguard-exploit-check.md +98 -0
  102. package/skills/torusguard/payload/workflows/torusguard-harden.md +104 -0
  103. package/skills/torusguard/payload/workflows/torusguard-init.md +106 -0
  104. package/skills/torusguard/payload/workflows/torusguard-recheck.md +101 -0
  105. package/skills/torusguard/payload/workflows/torusguard-recipes.md +55 -0
  106. package/skills/torusguard/payload/workflows/torusguard-report.md +105 -0
  107. package/skills/torusguard/payload/workflows/torusguard-status.md +103 -0
  108. package/skills/torusguard/payload/workflows/torusguard-verify.md +100 -0
  109. package/skills/torusguard/payload/workflows/torusguard-web-validate.md +96 -0
  110. package/skills/torusguard/payload/workflows/torusguard.md +19 -0
@@ -33,6 +33,8 @@ def scan_workspace_files(base_dir):
33
33
  dirs.remove("snapshots")
34
34
  if "memory" in dirs:
35
35
  dirs.remove("memory")
36
+ if "reports" in dirs:
37
+ dirs.remove("reports")
36
38
  if "__pycache__" in dirs:
37
39
  dirs.remove("__pycache__")
38
40
 
@@ -233,11 +233,12 @@ def execute_recheck(target_root: Path, run_id_arg: Optional[str] = None) -> Dict
233
233
  def main():
234
234
  parser = argparse.ArgumentParser(description="TorusGuard Differential Recheck Engine")
235
235
  parser.add_argument("path", nargs="?", default=".", help="Target project root directory")
236
+ parser.add_argument("--target", "-t", help="Target project root directory (alias for path)")
236
237
  parser.add_argument("--run", "-r", help="Explicit run ID to recheck")
237
238
  parser.add_argument("--json", action="store_true", help="Output raw JSON")
238
239
  args = parser.parse_args()
239
240
 
240
- target = Path(args.path).resolve()
241
+ target = Path(args.target or args.path).resolve()
241
242
  result = execute_recheck(target, run_id_arg=args.run)
242
243
 
243
244
  if args.json:
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- TorusGuard Golden Fix Recipe Explorer (v1.3.6)
3
+ TorusGuard Golden Fix Recipe Explorer (v1.4.0)
4
4
  Inspect, list, and export reusable, verified AST remediation code snippets
5
5
  distilled from passing security patches and persistent memory patterns.
6
6
  Standardized 75-column terminal UI formatting.
@@ -47,7 +47,7 @@ def box_line(content: str, width: int = 67, border: str = "│", border_color: s
47
47
  return term_ui.format_box_line(content, width=width, border=border, border_color=border_color)
48
48
  return f" {border_color}{border}{RESET} {content}"
49
49
 
50
- def box_header(title: str, subtitle: str = "", version: str = "v1.3.6", border_color: str = CYAN) -> str:
50
+ def box_header(title: str, subtitle: str = "", version: str = "v1.4.0", border_color: str = CYAN) -> str:
51
51
  if term_ui:
52
52
  return term_ui.card_header(title, subtitle, version, border_color)
53
53
  return f"=== {title} ({version}) ==="
@@ -63,28 +63,100 @@ def border_bottom(border_color: str = CYAN, double: bool = False) -> str:
63
63
  return "└" + "─" * 71 + "┘"
64
64
 
65
65
 
66
- def list_recipes(target_root: Path, detail_id: Optional[str] = None, json_output: bool = False) -> None:
66
+ def list_recipes(target_root: Path, detail_id: Optional[str] = None, search_query: Optional[str] = None, export_path: Optional[str] = None, apply_id: Optional[str] = None, json_output: bool = False) -> None:
67
67
  patterns_file = target_root / ".torusguard" / "memory" / "patterns.json"
68
68
  recipes: List[Dict[str, Any]] = []
69
69
 
70
70
  if patterns_file.is_file():
71
71
  try:
72
72
  patterns = json.loads(patterns_file.read_text(encoding="utf-8"))
73
- recipes = [p for p in patterns if p.get("pattern_type") == "golden_fix_recipe"]
73
+ if isinstance(patterns, list):
74
+ recipes = [p for p in patterns if p.get("pattern_type") == "golden_fix_recipe"]
75
+ elif isinstance(patterns, dict) and "patterns" in patterns:
76
+ recipes = [p for p in patterns["patterns"] if p.get("pattern_type") == "golden_fix_recipe"]
74
77
  except Exception:
75
78
  recipes = []
76
79
 
80
+ # 1. Search filtering
81
+ if search_query:
82
+ sq = search_query.lower()
83
+ recipes = [
84
+ r for r in recipes
85
+ if sq in r.get("rule_id", "").lower()
86
+ or sq in r.get("recipe_id", "").lower()
87
+ or sq in r.get("description", "").lower()
88
+ or sq in r.get("recipe_data", {}).get("diff_snippet", "").lower()
89
+ ]
90
+
91
+ # 2. Export requested
92
+ if export_path:
93
+ out_p = Path(export_path)
94
+ if not out_p.is_absolute():
95
+ out_p = (target_root / out_p).resolve()
96
+ out_p.parent.mkdir(parents=True, exist_ok=True)
97
+ if out_p.suffix.lower() == ".json":
98
+ out_p.write_text(json.dumps(recipes, indent=2), encoding="utf-8")
99
+ else:
100
+ md_lines = [
101
+ "# TorusGuard Golden Fix Recipes Catalog",
102
+ f"- Total Recipes: {len(recipes)}",
103
+ f"- Exported From: {target_root}",
104
+ ""
105
+ ]
106
+ for r in recipes:
107
+ md_lines.append(f"## [{r.get('rule_id')}] {r.get('recipe_id')}")
108
+ md_lines.append(f"- **Description:** {r.get('description')}")
109
+ rdata = r.get("recipe_data", {})
110
+ md_lines.append(f"- **Verified Count:** {rdata.get('verified_count', 1)}x")
111
+ md_lines.append("```diff")
112
+ md_lines.append(rdata.get("diff_snippet", ""))
113
+ md_lines.append("```\n")
114
+ out_p.write_text("\n".join(md_lines), encoding="utf-8")
115
+ print(f"\n {GREEN}✔ Exported {len(recipes)} golden recipe(s) to:{RESET} {CYAN}{out_p}{RESET}\n")
116
+ return
117
+
118
+ # 3. Apply inspection requested
119
+ if apply_id:
120
+ target_rec = next((r for r in recipes if r.get("recipe_id") == apply_id or apply_id in r.get("recipe_id", "")), None)
121
+ if not target_rec:
122
+ print(f"\n {RED}✖ Recipe '{apply_id}' not found in golden recipe memory.{RESET}\n")
123
+ return
124
+ rdata = target_rec.get("recipe_data", {})
125
+ print()
126
+ print(box_header("🛡️ GOLDEN RECIPE APPLICATION PREVIEW", f"Recipe ID: {target_rec.get('recipe_id')}", "v1.4.0"))
127
+ print()
128
+ print(border_top("Recipe Metadata"))
129
+ print(box_line(f"Rule ID: {YELLOW}{target_rec.get('rule_id')}{RESET}"))
130
+ print(box_line(f"Description: {WHITE}{target_rec.get('description')}{RESET}"))
131
+ metrics = rdata.get("ponytail_metrics", {})
132
+ print(box_line(f"Ponytail: {GREEN}+{metrics.get('additions', 0)} / -{metrics.get('deletions', 0)} lines{RESET}"))
133
+ print(border_bottom())
134
+ print()
135
+ print(f" {BOLD}Diff Projection:{RESET}")
136
+ print(" ```diff")
137
+ for line in rdata.get("diff_snippet", "").splitlines():
138
+ if line.startswith("+"):
139
+ print(f" {GREEN}{line}{RESET}")
140
+ elif line.startswith("-"):
141
+ print(f" {RED}{line}{RESET}")
142
+ else:
143
+ print(f" {DIM}{line}{RESET}")
144
+ print(" ```")
145
+ print(f"\n {DIM}To apply automatically across all matching findings, run: {CYAN}npx torusguard harden && npx torusguard apply{RESET}\n")
146
+ return
147
+
77
148
  if json_output:
78
149
  print(json.dumps(recipes, indent=2))
79
150
  return
80
151
 
81
152
  # Header Card
82
153
  print()
83
- print(box_header("🛡️ TORUSGUARD GOLDEN FIX RECIPES", "Distilled Ponytail Fixes (<= 35 add, <= 25 del) in Persistent Memory", "v1.3.6"))
154
+ sub_title = f"Filtered by '{search_query}'" if search_query else "Distilled Ponytail Fixes (<= 35 add, <= 25 del) in Persistent Memory"
155
+ print(box_header("🛡️ TORUSGUARD GOLDEN FIX RECIPES", sub_title, "v1.4.0"))
84
156
  print()
85
157
 
86
158
  if not recipes:
87
- print(f" {YELLOW}ℹ No golden fix recipes captured yet.{RESET}")
159
+ print(f" {YELLOW}ℹ No golden fix recipes found matching query.{RESET}")
88
160
  print(f" Run {CYAN}npx torusguard harden{RESET} followed by {CYAN}npx torusguard apply{RESET} to distill verified recipes.\n")
89
161
  return
90
162
 
@@ -125,12 +197,23 @@ def list_recipes(target_root: Path, detail_id: Optional[str] = None, json_output
125
197
  def main():
126
198
  parser = argparse.ArgumentParser(description="TorusGuard Golden Fix Recipe Explorer")
127
199
  parser.add_argument("path", nargs="?", default=".", help="Target project root directory")
200
+ parser.add_argument("--target", "-t", help="Target project root directory (alias for path)")
128
201
  parser.add_argument("--detail", "-d", help="Inspect specific recipe ID")
202
+ parser.add_argument("--search", "-s", help="Filter recipes by rule, keyword, or language")
203
+ parser.add_argument("--export", "-e", help="Export recipes to JSON or Markdown file path")
204
+ parser.add_argument("--apply", "-a", help="Project and display application diff for specific recipe ID")
129
205
  parser.add_argument("--json", action="store_true", help="Output raw JSON")
130
206
  args = parser.parse_args()
131
207
 
132
- target = Path(args.path).resolve()
133
- list_recipes(target, detail_id=args.detail, json_output=args.json)
208
+ target = Path(args.target or args.path).resolve()
209
+ list_recipes(
210
+ target,
211
+ detail_id=args.detail,
212
+ search_query=args.search,
213
+ export_path=args.export,
214
+ apply_id=args.apply,
215
+ json_output=args.json
216
+ )
134
217
 
135
218
 
136
219
  if __name__ == "__main__":
@@ -184,9 +184,9 @@ def parse_existing_report(report_path: Path) -> Dict[str, Any]:
184
184
 
185
185
  # ── Format B (new v2): ### TG-RULE-001 · Title `status` `confidence` ──
186
186
  card_pattern_v2 = re.compile(
187
- r'###\s+(TG-[A-Z]+-\d+)\s+·\s+(.*?)\s+`([^`]+)`\s+`([^`]+)`\n'
188
- r'- \*\*Finding ID:\*\*\s+`([^`]+)`(?:[^\n]*\*\*Severity:\*\*\s+`([^`]+)`)?\n'
189
- r'(?:📁|- \*\*Target:\*\*)\s*`([^`\n]+)`',
187
+ r'###\s+(TG-[A-Z]+-\d+)\s+·\s+(.*?)\s+`([^`]+)`\s+`([^`]+)`[\r\n]+'
188
+ r'-\s+\*\*Finding ID:\*\*\s+`([^`]+)`(?:[^\r\n]*\*\*Severity:\*\*\s+`([^`]+)`)?(?:[^\r\n]*\*\*Cluster:\*\*\s+`([^`]+)`)?[^\r\n]*[\r\n]+'
189
+ r'(?:📁|- \*\*Target:\*\*)\s*`?([^\r\n`]+)`?(?:[\r\n]+\*\*Problem:\*\*\s+([^\r\n]+))?',
190
190
  re.MULTILINE
191
191
  )
192
192
 
@@ -197,7 +197,9 @@ def parse_existing_report(report_path: Path) -> Dict[str, Any]:
197
197
  confidence = m.group(4).strip()
198
198
  finding_id = m.group(5).strip()
199
199
  sev_explicit = m.group(6).strip() if m.group(6) else None
200
- target = m.group(7).strip()
200
+ cluster_explicit = m.group(7).strip() if m.group(7) else None
201
+ target = m.group(8).strip()
202
+ problem = m.group(9).strip() if m.group(9) else None
201
203
 
202
204
  if finding_id not in findings: # Don't overwrite legacy matches
203
205
  if sev_explicit:
@@ -220,6 +222,8 @@ def parse_existing_report(report_path: Path) -> Dict[str, Any]:
220
222
  "severity": severity,
221
223
  "confidence": confidence,
222
224
  "target": target,
225
+ "cluster": cluster_explicit or "cluster-general",
226
+ "description": problem or "Security violation detected.",
223
227
  "history": []
224
228
  }
225
229
 
@@ -257,6 +261,37 @@ def parse_existing_report(report_path: Path) -> Dict[str, Any]:
257
261
  "details": "Restored from inline history"
258
262
  })
259
263
 
264
+ # Enrich findings from run history if available
265
+ runs_dir = report_path.parent / ".torusguard" / "runs"
266
+ run_findings_map: Dict[str, Any] = {}
267
+ if runs_dir.is_dir():
268
+ for d in sorted(runs_dir.iterdir()):
269
+ if d.is_dir() and (d / "findings.json").is_file():
270
+ try:
271
+ f_items = json.loads((d / "findings.json").read_text(encoding="utf-8"))
272
+ for it in f_items:
273
+ if isinstance(it, dict) and it.get("finding_id"):
274
+ run_findings_map[it["finding_id"]] = it
275
+ except Exception:
276
+ pass
277
+
278
+ for fid, f in findings.items():
279
+ if fid in run_findings_map:
280
+ rf = run_findings_map[fid]
281
+ if not f.get("cluster") or f.get("cluster") == "cluster-general":
282
+ f["cluster"] = rf.get("cluster") or "cluster-general"
283
+ if not f.get("category"):
284
+ f["category"] = rf.get("category") or "general"
285
+ if not f.get("target") or "\n" in f.get("target", "") or "**Problem:**" in f.get("target", "") or f.get("target") == "unknown":
286
+ f["target"] = f"{rf.get('file_path', 'unknown')}:{rf.get('line_number', 1)}"
287
+ if not f.get("description") or f.get("description") == "Security violation detected.":
288
+ f["description"] = rf.get("description") or "Security invariant check failed."
289
+ if not f.get("snippet"):
290
+ f["snippet"] = rf.get("snippet") or rf.get("matched_text", "")
291
+ if not f.get("file_path"):
292
+ f["file_path"] = rf.get("file_path", "")
293
+ if not f.get("line_number"):
294
+ f["line_number"] = rf.get("line_number", 1)
260
295
 
261
296
  return {"findings": findings, "raw_content": content}
262
297
 
@@ -384,6 +419,83 @@ def render_report(findings: List[Dict[str, Any]], target_root: Path) -> str:
384
419
  lines.append(f"> ⚠️ **Action Required:** {crit_open} critical finding{'s' if crit_open != 1 else ''} require immediate attention. Run `npx torusguard harden` to generate patches.")
385
420
  lines.append("")
386
421
 
422
+ # ── Governed Remediation & Problem-Solved Velocity Table ──
423
+ crit_resolved = max(0, crit_count - crit_open)
424
+ high_resolved = max(0, high_count - high_open)
425
+ med_resolved = max(0, med_count - med_open)
426
+ low_resolved = max(0, low_count)
427
+
428
+ crit_pct = int((crit_resolved / crit_count) * 100) if crit_count > 0 else 100
429
+ high_pct = int((high_resolved / high_count) * 100) if high_count > 0 else 100
430
+ med_pct = int((med_resolved / med_count) * 100) if med_count > 0 else 100
431
+ overall_pct = int((resolved_count / total) * 100) if total > 0 else 100
432
+
433
+ lines.extend([
434
+ "### ⚡ Governed Remediation & Problem-Solved Velocity",
435
+ "",
436
+ "| Severity Layer | Total Discovered | Remediated & Hardened | Active Exposure | Closure Rate | Status |",
437
+ "| :--- | :---: | :---: | :---: | :---: | :---: |",
438
+ f"| **Critical** | {crit_count} | {crit_resolved} | {crit_open} | {crit_pct}% | {'🟢 100% DEFENDED' if crit_open == 0 else '🔴 ACTION REQUIRED'} |",
439
+ f"| **High** | {high_count} | {high_resolved} | {high_open} | {high_pct}% | {'🟢 100% DEFENDED' if high_open == 0 else '🟠 NEEDS ATTENTION'} |",
440
+ f"| **Medium / Low** | {med_count + low_count} | {med_resolved + low_resolved} | {med_open} | {med_pct}% | {'🟢 100% DEFENDED' if med_open == 0 else '🟡 PENDING REVIEW'} |",
441
+ f"| **Total Flaws** | **{total}** | **{resolved_count}** | **{open_count}** | **{overall_pct}%** | **{'🟢 100% HARDENED' if open_count == 0 else '🟡 REMEDIATION IN PROGRESS'}** |",
442
+ "",
443
+ ])
444
+
445
+ # ── Directory Attack Surface Overview Table ──
446
+ dir_stats: Dict[str, Dict[str, int]] = {}
447
+ for f in findings:
448
+ t = f.get('target', '')
449
+ fp = t.split(':')[0] if ':' in t else t
450
+ fp = fp.replace('\\', '/')
451
+ d_name = f"{fp.split('/')[0]}/" if '/' in fp else "root"
452
+ if d_name not in dir_stats:
453
+ dir_stats[d_name] = {"total": 0, "resolved": 0, "open": 0}
454
+ dir_stats[d_name]["total"] += 1
455
+ if "RESOLVED" in f.get("status", ""):
456
+ dir_stats[d_name]["resolved"] += 1
457
+ else:
458
+ dir_stats[d_name]["open"] += 1
459
+
460
+ if dir_stats:
461
+ lines.extend([
462
+ "### 🗺️ Directory Attack Surface Defended",
463
+ "",
464
+ "| Directory / Module | Total Invariants Checked | Vulnerabilities Neutralized | Active Risk | Defense Posture |",
465
+ "| :--- | :---: | :---: | :---: | :--- |",
466
+ ])
467
+ for d_name, d_data in sorted(dir_stats.items(), key=lambda x: x[1]["total"], reverse=True):
468
+ posture_str = "🟢 Hardened Zone (100% Invariants Enforced)" if d_data["open"] == 0 else f"🔴 Active Exposure ({d_data['open']} Open Flaws)"
469
+ lines.append(f"| `{d_name}` | {d_data['total']} | {d_data['resolved']} | {d_data['open']} | {posture_str} |")
470
+ lines.append("")
471
+
472
+ # ── Root-Cause Architectural Clusters Overview Table ──
473
+ cluster_stats: Dict[str, Dict[str, Any]] = {}
474
+ for f in findings:
475
+ c_id = f.get('cluster') or f.get('category') or 'cluster-general'
476
+ if c_id not in cluster_stats:
477
+ cluster_stats[c_id] = {"total": 0, "resolved": 0, "open": 0, "rules": set()}
478
+ cluster_stats[c_id]["total"] += 1
479
+ cluster_stats[c_id]["rules"].add(f.get("rule_id", "TG-GEN"))
480
+ if "RESOLVED" in f.get("status", ""):
481
+ cluster_stats[c_id]["resolved"] += 1
482
+ else:
483
+ cluster_stats[c_id]["open"] += 1
484
+
485
+ if cluster_stats:
486
+ lines.extend([
487
+ "### 🏛️ Root-Cause Architectural Clusters",
488
+ "",
489
+ "| Cluster ID | Threat Architectural Domain | Neutralized Issues | Enforced Invariants | Posture State |",
490
+ "| :--- | :--- | :---: | :--- | :--- |",
491
+ ])
492
+ for c_id, c_data in sorted(cluster_stats.items(), key=lambda x: x[1]["total"], reverse=True):
493
+ c_domain = c_id.replace("cluster-", "").replace("-", " ").title()
494
+ rules_str = ", ".join(f"`{r}`" for r in sorted(c_data["rules"])[:3])
495
+ c_state = "🟢 Dismantled & Defended" if c_data["open"] == 0 else f"🔴 {c_data['open']} Active Flaws"
496
+ lines.append(f"| `{c_id}` | {c_domain} | {c_data['resolved']} | {rules_str} | {c_state} |")
497
+ lines.append("")
498
+
387
499
  lines.append("---")
388
500
  lines.append("")
389
501
 
@@ -445,11 +557,26 @@ def _render_finding_card(f: Dict[str, Any]) -> List[str]:
445
557
  status_full = f["status"]
446
558
  conf = f.get('confidence', '70% (High Confidence)')
447
559
  sev = f.get('severity', 'High')
560
+ target = f.get('target', 'unknown')
561
+ if "\n" in target:
562
+ target = target.splitlines()[0]
563
+ target = target.strip().strip("`")
564
+ if target.startswith("📁"):
565
+ target = target.replace("📁", "").strip()
566
+
567
+ desc = f.get('what_is_wrong') or f.get('description') or 'Security violation detected.'
568
+ if "\n" in desc:
569
+ desc = desc.splitlines()[0]
570
+ desc = desc.strip().strip("`")
571
+ if desc.startswith("**Problem:**"):
572
+ desc = desc.replace("**Problem:**", "").strip()
573
+
574
+ cluster_badge = f" · **Cluster:** `{f['cluster']}`" if f.get("cluster") else ""
448
575
  card = [
449
576
  f"### {f['rule_id']} · {f['title']} `{status_full}` `{conf}`",
450
- f"- **Finding ID:** `{f['finding_id']}` · **Severity:** `{sev}`",
451
- f"📁 `{f['target']}`",
452
- f"**Problem:** {f.get('what_is_wrong', f.get('description', 'Security violation detected.'))}",
577
+ f"- **Finding ID:** `{f['finding_id']}` · **Severity:** `{sev}`{cluster_badge}",
578
+ f"📁 `{target}`",
579
+ f"**Problem:** {desc}",
453
580
  ]
454
581
  if f.get("what_should_change"):
455
582
  card.append(f"**Fix:** {f['what_should_change']}")