@sentry/warden 0.12.0 → 0.14.0

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 (145) hide show
  1. package/agents.lock +66 -0
  2. package/dist/cli/args.d.ts +17 -9
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +51 -2
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/commands/add.js +1 -1
  7. package/dist/cli/commands/add.js.map +1 -1
  8. package/dist/cli/commands/init.d.ts +0 -3
  9. package/dist/cli/commands/init.d.ts.map +1 -1
  10. package/dist/cli/commands/init.js +219 -24
  11. package/dist/cli/commands/init.js.map +1 -1
  12. package/dist/cli/commands/logs.d.ts +19 -0
  13. package/dist/cli/commands/logs.d.ts.map +1 -0
  14. package/dist/cli/commands/logs.js +419 -0
  15. package/dist/cli/commands/logs.js.map +1 -0
  16. package/dist/cli/commands/sync.d.ts.map +1 -1
  17. package/dist/cli/commands/sync.js +16 -4
  18. package/dist/cli/commands/sync.js.map +1 -1
  19. package/dist/cli/fix.d.ts.map +1 -1
  20. package/dist/cli/fix.js +6 -1
  21. package/dist/cli/fix.js.map +1 -1
  22. package/dist/cli/log-cleanup.d.ts +6 -5
  23. package/dist/cli/log-cleanup.d.ts.map +1 -1
  24. package/dist/cli/log-cleanup.js +11 -10
  25. package/dist/cli/log-cleanup.js.map +1 -1
  26. package/dist/cli/main.d.ts.map +1 -1
  27. package/dist/cli/main.js +87 -29
  28. package/dist/cli/main.js.map +1 -1
  29. package/dist/cli/output/formatters.d.ts +8 -2
  30. package/dist/cli/output/formatters.d.ts.map +1 -1
  31. package/dist/cli/output/formatters.js +40 -19
  32. package/dist/cli/output/formatters.js.map +1 -1
  33. package/dist/cli/output/index.d.ts +2 -2
  34. package/dist/cli/output/index.d.ts.map +1 -1
  35. package/dist/cli/output/index.js +2 -2
  36. package/dist/cli/output/index.js.map +1 -1
  37. package/dist/cli/output/ink-runner.js +1 -1
  38. package/dist/cli/output/ink-runner.js.map +1 -1
  39. package/dist/cli/output/jsonl.d.ts +51 -14
  40. package/dist/cli/output/jsonl.d.ts.map +1 -1
  41. package/dist/cli/output/jsonl.js +140 -7
  42. package/dist/cli/output/jsonl.js.map +1 -1
  43. package/dist/cli/output/reporter.d.ts +4 -0
  44. package/dist/cli/output/reporter.d.ts.map +1 -1
  45. package/dist/cli/output/reporter.js +14 -0
  46. package/dist/cli/output/reporter.js.map +1 -1
  47. package/dist/cli/output/tasks.d.ts +3 -1
  48. package/dist/cli/output/tasks.d.ts.map +1 -1
  49. package/dist/cli/output/tasks.js +7 -4
  50. package/dist/cli/output/tasks.js.map +1 -1
  51. package/dist/cli/terminal.d.ts +4 -3
  52. package/dist/cli/terminal.d.ts.map +1 -1
  53. package/dist/cli/terminal.js +22 -11
  54. package/dist/cli/terminal.js.map +1 -1
  55. package/dist/config/loader.d.ts +3 -1
  56. package/dist/config/loader.d.ts.map +1 -1
  57. package/dist/config/loader.js +2 -0
  58. package/dist/config/loader.js.map +1 -1
  59. package/dist/config/schema.d.ts +84 -70
  60. package/dist/config/schema.d.ts.map +1 -1
  61. package/dist/config/schema.js +7 -1
  62. package/dist/config/schema.js.map +1 -1
  63. package/dist/evals/types.d.ts +9 -15
  64. package/dist/evals/types.d.ts.map +1 -1
  65. package/dist/index.d.ts +2 -2
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +2 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/output/dedup.d.ts +14 -10
  70. package/dist/output/dedup.d.ts.map +1 -1
  71. package/dist/output/dedup.js +39 -17
  72. package/dist/output/dedup.js.map +1 -1
  73. package/dist/output/github-checks.d.ts +5 -3
  74. package/dist/output/github-checks.d.ts.map +1 -1
  75. package/dist/output/github-checks.js +14 -16
  76. package/dist/output/github-checks.js.map +1 -1
  77. package/dist/output/issue-renderer.js +1 -1
  78. package/dist/output/issue-renderer.js.map +1 -1
  79. package/dist/output/renderer.d.ts.map +1 -1
  80. package/dist/output/renderer.js +11 -7
  81. package/dist/output/renderer.js.map +1 -1
  82. package/dist/output/types.d.ts +3 -1
  83. package/dist/output/types.d.ts.map +1 -1
  84. package/dist/sdk/analyze.d.ts.map +1 -1
  85. package/dist/sdk/analyze.js +12 -5
  86. package/dist/sdk/analyze.js.map +1 -1
  87. package/dist/sdk/auth.d.ts +16 -0
  88. package/dist/sdk/auth.d.ts.map +1 -0
  89. package/dist/sdk/auth.js +37 -0
  90. package/dist/sdk/auth.js.map +1 -0
  91. package/dist/sdk/errors.d.ts +5 -0
  92. package/dist/sdk/errors.d.ts.map +1 -1
  93. package/dist/sdk/errors.js +20 -0
  94. package/dist/sdk/errors.js.map +1 -1
  95. package/dist/sdk/prompt.d.ts.map +1 -1
  96. package/dist/sdk/prompt.js +3 -1
  97. package/dist/sdk/prompt.js.map +1 -1
  98. package/dist/sdk/runner.d.ts +2 -1
  99. package/dist/sdk/runner.d.ts.map +1 -1
  100. package/dist/sdk/runner.js +3 -1
  101. package/dist/sdk/runner.js.map +1 -1
  102. package/dist/skills/remote.d.ts +4 -0
  103. package/dist/skills/remote.d.ts.map +1 -1
  104. package/dist/skills/remote.js +47 -27
  105. package/dist/skills/remote.js.map +1 -1
  106. package/dist/types/index.d.ts +42 -22
  107. package/dist/types/index.d.ts.map +1 -1
  108. package/dist/types/index.js +45 -7
  109. package/dist/types/index.js.map +1 -1
  110. package/package.json +1 -1
  111. package/{plugins/warden/skills → skills}/warden/SKILL.md +2 -4
  112. package/{plugins/warden/skills → skills}/warden/references/cli-reference.md +7 -9
  113. package/{plugins/warden/skills → skills}/warden/references/config-schema.md +5 -7
  114. package/{plugins/warden/skills → skills}/warden/references/configuration.md +10 -8
  115. package/{plugins/warden/skills → skills}/warden/references/creating-skills.md +6 -6
  116. package/skills/warden-sweep/SKILL.md +407 -0
  117. package/skills/warden-sweep/scripts/_utils.py +37 -0
  118. package/skills/warden-sweep/scripts/extract_findings.py +219 -0
  119. package/skills/warden-sweep/scripts/find_reviewers.py +115 -0
  120. package/skills/warden-sweep/scripts/generate_report.py +271 -0
  121. package/skills/warden-sweep/scripts/index_prs.py +187 -0
  122. package/skills/warden-sweep/scripts/organize.py +315 -0
  123. package/skills/warden-sweep/scripts/scan.py +632 -0
  124. package/.claude-plugin/marketplace.json +0 -20
  125. package/.mcp.json +0 -8
  126. package/agents.toml +0 -7
  127. package/conductor.json +0 -8
  128. package/evals/README.md +0 -154
  129. package/evals/bug-detection.yaml +0 -56
  130. package/evals/fixtures/ignores-style-issues/utils.ts +0 -48
  131. package/evals/fixtures/missing-await/cache.ts +0 -45
  132. package/evals/fixtures/null-property-access/handler.ts +0 -36
  133. package/evals/fixtures/off-by-one/paginator.ts +0 -38
  134. package/evals/fixtures/sql-injection/api.ts +0 -59
  135. package/evals/fixtures/stale-closure/counter.tsx +0 -33
  136. package/evals/fixtures/wrong-comparison/validator.ts +0 -52
  137. package/evals/fixtures/xss-reflected/server.ts +0 -55
  138. package/evals/precision.yaml +0 -15
  139. package/evals/security-scanning.yaml +0 -24
  140. package/evals/skills/bug-detection.md +0 -33
  141. package/evals/skills/precision.md +0 -18
  142. package/evals/skills/security-scanning.md +0 -32
  143. package/plugins/.claude-plugin/marketplace.json +0 -14
  144. package/plugins/warden/.claude-plugin/plugin.json +0 -7
  145. package/scripts/update-pricing.ts +0 -88
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.9"
4
+ # ///
5
+ """
6
+ Warden Sweep: Organize phase.
7
+
8
+ Identifies security findings, creates security indexes, labels security PRs,
9
+ updates finding reports with PR links, generates summary report, and
10
+ finalizes the manifest.
11
+
12
+ Usage:
13
+ uv run organize.py <sweep-dir>
14
+
15
+ Stdout: JSON summary (for LLM consumption)
16
+ Stderr: Progress lines
17
+
18
+ Side effects:
19
+ - Creates security/index.jsonl with security findings
20
+ - Copies security finding .md files to security/
21
+ - Creates "security" label on GitHub
22
+ - Labels security PRs with "security"
23
+ - Appends PR links to findings/*.md
24
+ - Runs generate_report.py for summary.md and report.json
25
+ - Updates manifest phases.organize to "complete"
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import argparse
30
+ import json
31
+ import os
32
+ import shutil
33
+ import subprocess
34
+ import sys
35
+ from datetime import datetime, timezone
36
+ from typing import Any
37
+
38
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
39
+ from _utils import read_jsonl # noqa: E402
40
+
41
+
42
+ SECURITY_SKILL_PATTERNS = [
43
+ "security-review",
44
+ "owasp-review",
45
+ "security-audit",
46
+ ]
47
+
48
+
49
+ def is_security_skill(skill_name: str) -> bool:
50
+ """Check if a skill name indicates a security-related skill."""
51
+ name_lower = skill_name.lower()
52
+ if "security" in name_lower:
53
+ return True
54
+ return name_lower in SECURITY_SKILL_PATTERNS
55
+
56
+
57
+ def identify_security_findings(
58
+ sweep_dir: str,
59
+ ) -> list[dict[str, Any]]:
60
+ """Find security-related verified findings and write security/index.jsonl."""
61
+ verified = read_jsonl(os.path.join(sweep_dir, "data", "verified.jsonl"))
62
+
63
+ security_findings: list[dict[str, Any]] = []
64
+ for finding in verified:
65
+ skill = finding.get("skill", "")
66
+ if is_security_skill(skill):
67
+ entry = {
68
+ "findingId": finding.get("findingId", ""),
69
+ "skill": skill,
70
+ "severity": finding.get("severity", "info"),
71
+ "file": finding.get("file", ""),
72
+ "title": finding.get("title", ""),
73
+ }
74
+ security_findings.append(entry)
75
+
76
+ # Write security index
77
+ security_dir = os.path.join(sweep_dir, "security")
78
+ os.makedirs(security_dir, exist_ok=True)
79
+ index_path = os.path.join(security_dir, "index.jsonl")
80
+ with open(index_path, "w") as f:
81
+ for entry in security_findings:
82
+ f.write(json.dumps(entry) + "\n")
83
+
84
+ return security_findings
85
+
86
+
87
+ def copy_security_findings(
88
+ sweep_dir: str, security_findings: list[dict[str, Any]]
89
+ ) -> None:
90
+ """Copy security finding .md files to security/ directory."""
91
+ findings_dir = os.path.join(sweep_dir, "findings")
92
+ security_dir = os.path.join(sweep_dir, "security")
93
+
94
+ for finding in security_findings:
95
+ fid = finding.get("findingId", "")
96
+ src = os.path.join(findings_dir, f"{fid}.md")
97
+ dst = os.path.join(security_dir, f"{fid}.md")
98
+ if os.path.exists(src):
99
+ shutil.copy2(src, dst)
100
+
101
+
102
+ def create_security_label() -> None:
103
+ """Create the security label on GitHub (idempotent)."""
104
+ try:
105
+ subprocess.run(
106
+ [
107
+ "gh", "label", "create", "security",
108
+ "--color", "D93F0B",
109
+ "--description", "Security-related changes",
110
+ ],
111
+ capture_output=True,
112
+ timeout=15,
113
+ )
114
+ except (subprocess.TimeoutExpired, FileNotFoundError):
115
+ pass
116
+
117
+
118
+ def label_security_prs(
119
+ sweep_dir: str, security_findings: list[dict[str, Any]]
120
+ ) -> int:
121
+ """Add "security" label to PRs for security findings. Returns count labeled."""
122
+ patches = read_jsonl(os.path.join(sweep_dir, "data", "patches.jsonl"))
123
+ security_ids = {f.get("findingId", "") for f in security_findings}
124
+
125
+ labeled = 0
126
+ for patch in patches:
127
+ if patch.get("status") != "created":
128
+ continue
129
+ if patch.get("findingId", "") not in security_ids:
130
+ continue
131
+
132
+ pr_url = patch.get("prUrl", "")
133
+ if not pr_url:
134
+ continue
135
+
136
+ try:
137
+ result = subprocess.run(
138
+ ["gh", "pr", "edit", pr_url, "--add-label", "security"],
139
+ capture_output=True,
140
+ text=True,
141
+ timeout=15,
142
+ )
143
+ if result.returncode == 0:
144
+ labeled += 1
145
+ else:
146
+ print(
147
+ f"Warning: Failed to label PR {pr_url}: {result.stderr.strip()}",
148
+ file=sys.stderr,
149
+ )
150
+ except (subprocess.TimeoutExpired, FileNotFoundError):
151
+ print(
152
+ f"Warning: Failed to label PR {pr_url}",
153
+ file=sys.stderr,
154
+ )
155
+
156
+ return labeled
157
+
158
+
159
+ def update_findings_with_pr_links(sweep_dir: str) -> None:
160
+ """Append PR links to findings/*.md for created PRs."""
161
+ patches = read_jsonl(os.path.join(sweep_dir, "data", "patches.jsonl"))
162
+ findings_dir = os.path.join(sweep_dir, "findings")
163
+
164
+ for patch in patches:
165
+ if patch.get("status") != "created":
166
+ continue
167
+
168
+ fid = patch.get("findingId", "")
169
+ pr_url = patch.get("prUrl", "")
170
+ branch = patch.get("branch", "")
171
+ reviewers = patch.get("reviewers", [])
172
+
173
+ if not fid or not pr_url:
174
+ continue
175
+
176
+ md_path = os.path.join(findings_dir, f"{fid}.md")
177
+ if not os.path.exists(md_path):
178
+ continue
179
+
180
+ # Check if PR section already appended
181
+ with open(md_path) as f:
182
+ content = f.read()
183
+ if "## Pull Request" in content:
184
+ continue
185
+
186
+ reviewers_str = ", ".join(reviewers) if reviewers else "none"
187
+ pr_section = (
188
+ f"\n\n## Pull Request\n"
189
+ f"**PR**: {pr_url}\n"
190
+ f"**Branch**: {branch}\n"
191
+ f"**Reviewers**: {reviewers_str}\n"
192
+ )
193
+
194
+ with open(md_path, "a") as f:
195
+ f.write(pr_section)
196
+
197
+
198
+ def run_generate_report(sweep_dir: str, script_dir: str) -> None:
199
+ """Run generate_report.py as a subprocess."""
200
+ report_script = os.path.join(script_dir, "generate_report.py")
201
+
202
+ try:
203
+ result = subprocess.run(
204
+ [sys.executable, report_script, sweep_dir],
205
+ capture_output=True,
206
+ text=True,
207
+ timeout=60,
208
+ )
209
+ if result.returncode != 0:
210
+ print(
211
+ f"Warning: generate_report.py failed: {result.stderr}",
212
+ file=sys.stderr,
213
+ )
214
+ except Exception as e:
215
+ print(f"Warning: generate_report.py failed: {e}", file=sys.stderr)
216
+
217
+
218
+ def update_manifest(sweep_dir: str) -> None:
219
+ """Mark organize phase complete and add completedAt timestamp."""
220
+ manifest_path = os.path.join(sweep_dir, "data", "manifest.json")
221
+ if not os.path.exists(manifest_path):
222
+ return
223
+
224
+ with open(manifest_path) as f:
225
+ manifest = json.load(f)
226
+
227
+ manifest.setdefault("phases", {})["organize"] = "complete"
228
+ manifest["completedAt"] = datetime.now(timezone.utc).strftime(
229
+ "%Y-%m-%dT%H:%M:%SZ"
230
+ )
231
+
232
+ with open(manifest_path, "w") as f:
233
+ json.dump(manifest, f, indent=2)
234
+ f.write("\n")
235
+
236
+
237
+ def main() -> None:
238
+ parser = argparse.ArgumentParser(
239
+ description="Warden Sweep: Organize phase"
240
+ )
241
+ parser.add_argument("sweep_dir", help="Path to the sweep directory")
242
+ args = parser.parse_args()
243
+
244
+ sweep_dir = args.sweep_dir
245
+
246
+ if not os.path.isdir(sweep_dir):
247
+ print(
248
+ json.dumps({"error": f"Sweep directory not found: {sweep_dir}"}),
249
+ file=sys.stdout,
250
+ )
251
+ sys.exit(1)
252
+
253
+ script_dir = os.path.dirname(os.path.abspath(__file__))
254
+
255
+ # Step 1: Identify security findings
256
+ print("Identifying security findings...", file=sys.stderr)
257
+ security_findings = identify_security_findings(sweep_dir)
258
+ print(
259
+ f"Found {len(security_findings)} security finding(s)",
260
+ file=sys.stderr,
261
+ )
262
+
263
+ # Step 2: Label security PRs
264
+ security_prs_labeled = 0
265
+ if security_findings:
266
+ print("Creating security label...", file=sys.stderr)
267
+ create_security_label()
268
+ print("Labeling security PRs...", file=sys.stderr)
269
+ security_prs_labeled = label_security_prs(sweep_dir, security_findings)
270
+
271
+ # Step 3: Update finding reports with PR links
272
+ print("Updating finding reports with PR links...", file=sys.stderr)
273
+ update_findings_with_pr_links(sweep_dir)
274
+
275
+ # Step 4: Copy security finding reports (after PR links are added)
276
+ copy_security_findings(sweep_dir, security_findings)
277
+
278
+ # Step 5: Generate summary and report
279
+ print("Generating summary and report...", file=sys.stderr)
280
+ run_generate_report(sweep_dir, script_dir)
281
+
282
+ # Step 6: Update manifest
283
+ update_manifest(sweep_dir)
284
+
285
+ # Gather stats for output
286
+ scan_index = read_jsonl(os.path.join(sweep_dir, "data", "scan-index.jsonl"))
287
+ verified = read_jsonl(os.path.join(sweep_dir, "data", "verified.jsonl"))
288
+ rejected = read_jsonl(os.path.join(sweep_dir, "data", "rejected.jsonl"))
289
+ patches = read_jsonl(os.path.join(sweep_dir, "data", "patches.jsonl"))
290
+
291
+ files_scanned = sum(1 for e in scan_index if e.get("status") == "complete")
292
+ prs_created = sum(1 for p in patches if p.get("status") == "created")
293
+
294
+ summary_path = os.path.join(sweep_dir, "summary.md")
295
+ report_path = os.path.join(sweep_dir, "data", "report.json")
296
+
297
+ output = {
298
+ "securityFindings": len(security_findings),
299
+ "securityPRsLabeled": security_prs_labeled,
300
+ "summaryPath": summary_path,
301
+ "reportPath": report_path,
302
+ "stats": {
303
+ "filesScanned": files_scanned,
304
+ "verified": len(verified),
305
+ "rejected": len(rejected),
306
+ "prsCreated": prs_created,
307
+ "securityFindings": len(security_findings),
308
+ },
309
+ }
310
+
311
+ print(json.dumps(output, indent=2))
312
+
313
+
314
+ if __name__ == "__main__":
315
+ main()