@sentry/warden 0.35.0 → 0.36.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.
- package/dist/cli/args.d.ts +12 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +14 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/commands/init.js +2 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +16 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.d.ts +4 -2
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +34 -13
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/output/jsonl-schema-gen.d.ts.map +1 -1
- package/dist/cli/output/jsonl-schema-gen.js +3 -1
- package/dist/cli/output/jsonl-schema-gen.js.map +1 -1
- package/dist/cli/output/jsonl.d.ts +54 -0
- package/dist/cli/output/jsonl.d.ts.map +1 -1
- package/dist/cli/output/jsonl.js +6 -1
- package/dist/cli/output/jsonl.js.map +1 -1
- package/dist/cli/output/tasks.d.ts.map +1 -1
- package/dist/cli/output/tasks.js +7 -0
- package/dist/cli/output/tasks.js.map +1 -1
- package/dist/config/loader.d.ts +2 -2
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +3 -3
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +5 -5
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +3 -3
- package/dist/config/schema.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/sdk/analyze.d.ts.map +1 -1
- package/dist/sdk/analyze.js +139 -10
- package/dist/sdk/analyze.js.map +1 -1
- package/dist/sdk/auth.d.ts +5 -4
- package/dist/sdk/auth.d.ts.map +1 -1
- package/dist/sdk/auth.js +9 -7
- package/dist/sdk/auth.js.map +1 -1
- package/dist/sdk/circuit-breaker.js +3 -3
- package/dist/sdk/circuit-breaker.js.map +1 -1
- package/dist/sdk/errors.d.ts +8 -0
- package/dist/sdk/errors.d.ts.map +1 -1
- package/dist/sdk/errors.js +44 -1
- package/dist/sdk/errors.js.map +1 -1
- package/dist/sdk/haiku.d.ts.map +1 -1
- package/dist/sdk/haiku.js +21 -12
- package/dist/sdk/haiku.js.map +1 -1
- package/dist/sdk/otel.d.ts +15 -0
- package/dist/sdk/otel.d.ts.map +1 -1
- package/dist/sdk/otel.js +50 -6
- package/dist/sdk/otel.js.map +1 -1
- package/dist/sdk/post-process.d.ts +2 -2
- package/dist/sdk/post-process.d.ts.map +1 -1
- package/dist/sdk/post-process.js +1 -1
- package/dist/sdk/post-process.js.map +1 -1
- package/dist/sdk/runtimes/claude.d.ts.map +1 -1
- package/dist/sdk/runtimes/claude.js +45 -35
- package/dist/sdk/runtimes/claude.js.map +1 -1
- package/dist/sdk/runtimes/pi.d.ts.map +1 -1
- package/dist/sdk/runtimes/pi.js +90 -6
- package/dist/sdk/runtimes/pi.js.map +1 -1
- package/dist/sdk/runtimes/types.d.ts +2 -2
- package/dist/sdk/runtimes/types.d.ts.map +1 -1
- package/dist/sdk/types.d.ts +12 -4
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/verify.d.ts +2 -2
- package/dist/sdk/verify.d.ts.map +1 -1
- package/dist/sdk/verify.js +1 -1
- package/dist/sdk/verify.js.map +1 -1
- package/dist/sentry-trace.d.ts +29 -0
- package/dist/sentry-trace.d.ts.map +1 -0
- package/dist/sentry-trace.js +154 -0
- package/dist/sentry-trace.js.map +1 -0
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +56 -0
- package/dist/sentry.js.map +1 -1
- package/dist/types/index.d.ts +72 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +37 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/warden/SKILL.md +0 -76
- package/skills/warden/SOURCES.md +0 -39
- package/skills/warden/SPEC.md +0 -112
- package/skills/warden/references/cli-reference.md +0 -149
- package/skills/warden/references/config-schema.md +0 -141
- package/skills/warden/references/configuration.md +0 -143
- package/skills/warden/references/creating-skills.md +0 -87
- package/skills/warden-sweep/SKILL.md +0 -78
- package/skills/warden-sweep/SOURCES.md +0 -43
- package/skills/warden-sweep/SPEC.md +0 -126
- package/skills/warden-sweep/references/issue-phase.md +0 -24
- package/skills/warden-sweep/references/organize-phase.md +0 -31
- package/skills/warden-sweep/references/patch-phase.md +0 -142
- package/skills/warden-sweep/references/patch-prompt.md +0 -72
- package/skills/warden-sweep/references/resume-and-artifacts.md +0 -51
- package/skills/warden-sweep/references/scan-phase.md +0 -46
- package/skills/warden-sweep/references/script-interfaces.md +0 -127
- package/skills/warden-sweep/references/verify-phase.md +0 -69
- package/skills/warden-sweep/references/verify-prompt.md +0 -25
- package/skills/warden-sweep/scripts/_utils.py +0 -99
- package/skills/warden-sweep/scripts/create_issue.py +0 -189
- package/skills/warden-sweep/scripts/extract_findings.py +0 -219
- package/skills/warden-sweep/scripts/find_reviewers.py +0 -114
- package/skills/warden-sweep/scripts/generate_report.py +0 -266
- package/skills/warden-sweep/scripts/index_prs.py +0 -187
- package/skills/warden-sweep/scripts/organize.py +0 -422
- package/skills/warden-sweep/scripts/scan.py +0 -584
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# Script Interfaces
|
|
2
|
-
|
|
3
|
-
Use this reference before running Warden Sweep scripts. Run scripts from the repository root and pass the host skill-root path.
|
|
4
|
-
|
|
5
|
-
## Contents
|
|
6
|
-
|
|
7
|
-
- `scan.py`
|
|
8
|
-
- `index_prs.py`
|
|
9
|
-
- `create_issue.py`
|
|
10
|
-
- `organize.py`
|
|
11
|
-
- `extract_findings.py`
|
|
12
|
-
- `generate_report.py`
|
|
13
|
-
- `find_reviewers.py`
|
|
14
|
-
|
|
15
|
-
## `scripts/scan.py`
|
|
16
|
-
|
|
17
|
-
Runs setup and scan in one call: generates a run ID, creates the sweep directory, checks dependencies, creates the `warden` label, enumerates files, runs Warden per file, writes `scan-index.jsonl`, and extracts findings.
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
uv run <skill-root>/scripts/scan.py [file ...]
|
|
21
|
-
uv run <skill-root>/scripts/scan.py --sweep-dir .warden/sweeps/<run-id>
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Stdout JSON:
|
|
25
|
-
|
|
26
|
-
```json
|
|
27
|
-
{
|
|
28
|
-
"runId": "abc123",
|
|
29
|
-
"sweepDir": ".warden/sweeps/abc123",
|
|
30
|
-
"filesScanned": 10,
|
|
31
|
-
"filesTimedOut": 0,
|
|
32
|
-
"filesErrored": 0,
|
|
33
|
-
"totalFindings": 3,
|
|
34
|
-
"findings": []
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Exit codes: `0` success, `1` fatal error, `2` partial scan.
|
|
39
|
-
|
|
40
|
-
## `scripts/index_prs.py`
|
|
41
|
-
|
|
42
|
-
Fetches open Warden-labeled PRs, builds a file-to-PR dedup index, and caches diffs for overlapping PRs.
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
uv run <skill-root>/scripts/index_prs.py <sweep-dir>
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Stdout JSON includes `fileIndex`. Side effects:
|
|
49
|
-
|
|
50
|
-
- writes `data/existing-prs.json`
|
|
51
|
-
- writes `data/pr-diffs/<number>.diff` for overlapping PRs
|
|
52
|
-
|
|
53
|
-
## `scripts/create_issue.py`
|
|
54
|
-
|
|
55
|
-
Creates a GitHub tracking issue summarizing verified sweep results.
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
uv run <skill-root>/scripts/create_issue.py <sweep-dir>
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Stdout JSON:
|
|
62
|
-
|
|
63
|
-
```json
|
|
64
|
-
{
|
|
65
|
-
"issueUrl": "https://github.com/owner/repo/issues/123",
|
|
66
|
-
"issueNumber": 123
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Idempotent: skips creation when `issueUrl` already exists in the manifest.
|
|
71
|
-
|
|
72
|
-
## `scripts/organize.py`
|
|
73
|
-
|
|
74
|
-
Tags security findings, labels security PRs, updates finding reports with PR links, posts final results to the tracking issue, generates the summary report, and finalizes the manifest.
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
uv run <skill-root>/scripts/organize.py <sweep-dir>
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Stdout JSON includes final sweep counts and report paths. Side effects:
|
|
81
|
-
|
|
82
|
-
- creates `security/index.jsonl`
|
|
83
|
-
- copies security finding reports to `security/`
|
|
84
|
-
- creates or reuses the `security` GitHub label
|
|
85
|
-
- labels security PRs
|
|
86
|
-
- appends PR links to `findings/*.md`
|
|
87
|
-
- writes `summary.md` and `data/report.json`
|
|
88
|
-
- updates `phases.organize` in `data/manifest.json`
|
|
89
|
-
|
|
90
|
-
## `scripts/extract_findings.py`
|
|
91
|
-
|
|
92
|
-
Parses Warden JSONL log files and extracts normalized findings. Usually called by `scan.py`.
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
uv run <skill-root>/scripts/extract_findings.py <log-path-or-directory> -o <output.jsonl>
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Writes one normalized finding per line to `<output.jsonl>`.
|
|
99
|
-
|
|
100
|
-
## `scripts/generate_report.py`
|
|
101
|
-
|
|
102
|
-
Builds `summary.md` and `report.json` from sweep data. Usually called by `organize.py`.
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
uv run <skill-root>/scripts/generate_report.py <sweep-dir>
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Side effects:
|
|
109
|
-
|
|
110
|
-
- writes `<sweep-dir>/summary.md`
|
|
111
|
-
- writes `<sweep-dir>/data/report.json`
|
|
112
|
-
|
|
113
|
-
## `scripts/find_reviewers.py`
|
|
114
|
-
|
|
115
|
-
Finds the top two git contributors for a file from the last 12 months.
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
uv run <skill-root>/scripts/find_reviewers.py <file-path>
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Stdout JSON:
|
|
122
|
-
|
|
123
|
-
```json
|
|
124
|
-
{
|
|
125
|
-
"reviewers": ["user1", "user2"]
|
|
126
|
-
}
|
|
127
|
-
```
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# Verify Phase
|
|
2
|
-
|
|
3
|
-
Deep-trace every finding before patching. This phase qualifies true issues and rejects false positives.
|
|
4
|
-
|
|
5
|
-
## Input
|
|
6
|
-
|
|
7
|
-
Read findings from:
|
|
8
|
-
|
|
9
|
-
```text
|
|
10
|
-
<sweep-dir>/data/all-findings.jsonl
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Process
|
|
14
|
-
|
|
15
|
-
For each finding:
|
|
16
|
-
|
|
17
|
-
1. If `data/verify/<finding-id>.json` exists, skip it.
|
|
18
|
-
2. Launch verification work using the host agent's task/delegation mechanism when available. Process findings in parallel batches up to 8 if the host supports parallel work.
|
|
19
|
-
3. Read `references/verify-prompt.md` and substitute the finding values into the `${...}` placeholders.
|
|
20
|
-
4. Parse the returned JSON.
|
|
21
|
-
5. Write the raw result to `data/verify/<finding-id>.json`.
|
|
22
|
-
6. Append verified findings to `data/verified.jsonl`.
|
|
23
|
-
7. Append rejected findings to `data/rejected.jsonl`.
|
|
24
|
-
8. For verified findings, generate `findings/<finding-id>.md`.
|
|
25
|
-
|
|
26
|
-
If the host does not support delegated tasks, run the same verification prompt serially.
|
|
27
|
-
|
|
28
|
-
## Verified Finding Report
|
|
29
|
-
|
|
30
|
-
````markdown
|
|
31
|
-
# ${TITLE}
|
|
32
|
-
|
|
33
|
-
**ID**: ${FINDING_ID} | **Severity**: ${SEVERITY} | **Confidence**: ${CONFIDENCE}
|
|
34
|
-
**Skill**: ${SKILL} | **File**: ${FILE_PATH}:${START_LINE}
|
|
35
|
-
|
|
36
|
-
## Description
|
|
37
|
-
${DESCRIPTION}
|
|
38
|
-
|
|
39
|
-
## Verification
|
|
40
|
-
**Verdict**: Verified (${VERIFICATION_CONFIDENCE})
|
|
41
|
-
**Reasoning**: ${REASONING}
|
|
42
|
-
**Code trace**: ${TRACE_NOTES}
|
|
43
|
-
|
|
44
|
-
## Suggested Fix
|
|
45
|
-
${FIX_DESCRIPTION}
|
|
46
|
-
```diff
|
|
47
|
-
${FIX_DIFF}
|
|
48
|
-
```
|
|
49
|
-
````
|
|
50
|
-
|
|
51
|
-
Update the manifest: set `phases.verify` to `"complete"`.
|
|
52
|
-
|
|
53
|
-
## Report Template
|
|
54
|
-
|
|
55
|
-
```markdown
|
|
56
|
-
## Verification Complete
|
|
57
|
-
|
|
58
|
-
**{verified}** verified, **{rejected}** rejected.
|
|
59
|
-
|
|
60
|
-
### Verified Findings
|
|
61
|
-
|
|
62
|
-
| # | Severity | Confidence | File | Title | Reasoning |
|
|
63
|
-
|---|----------|------------|------|-------|-----------|
|
|
64
|
-
| 1 | **HIGH** | high | `src/db/query.ts:42` | SQL injection in query builder | User input flows directly into... |
|
|
65
|
-
|
|
66
|
-
### Rejected ({rejected_count})
|
|
67
|
-
|
|
68
|
-
- `{findingId}` {file}: {reasoning}
|
|
69
|
-
```
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
Verify a code analysis finding. Determine if this is a TRUE issue or a FALSE POSITIVE.
|
|
2
|
-
Do NOT write or edit any files. Research only.
|
|
3
|
-
|
|
4
|
-
## Finding
|
|
5
|
-
- Title: ${TITLE}
|
|
6
|
-
- Severity: ${SEVERITY} | Confidence: ${CONFIDENCE}
|
|
7
|
-
- Skill: ${SKILL}
|
|
8
|
-
- Location: ${FILE_PATH}:${START_LINE}-${END_LINE}
|
|
9
|
-
- Description: ${DESCRIPTION}
|
|
10
|
-
- Verification hint: ${VERIFICATION}
|
|
11
|
-
|
|
12
|
-
## Instructions
|
|
13
|
-
1. Read the file at the reported location. Examine at least 50 lines of surrounding context.
|
|
14
|
-
2. Trace data flow to/from the flagged code using Grep/Glob.
|
|
15
|
-
3. Check if the issue is mitigated elsewhere (guards, validation, try/catch upstream).
|
|
16
|
-
4. Check if the issue is actually reachable in practice.
|
|
17
|
-
|
|
18
|
-
Return your verdict as JSON:
|
|
19
|
-
{
|
|
20
|
-
"findingId": "${FINDING_ID}",
|
|
21
|
-
"verdict": "verified" or "rejected",
|
|
22
|
-
"confidence": "high" or "medium" or "low",
|
|
23
|
-
"reasoning": "2-3 sentence explanation",
|
|
24
|
-
"traceNotes": "What code paths you examined"
|
|
25
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
"""Shared utilities for warden-sweep scripts."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import os
|
|
6
|
-
import subprocess
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def run_cmd(
|
|
11
|
-
args: list[str], timeout: int = 30, cwd: str | None = None
|
|
12
|
-
) -> subprocess.CompletedProcess[str]:
|
|
13
|
-
"""Run a command and return the result."""
|
|
14
|
-
return subprocess.run(
|
|
15
|
-
args,
|
|
16
|
-
capture_output=True,
|
|
17
|
-
text=True,
|
|
18
|
-
timeout=timeout,
|
|
19
|
-
cwd=cwd,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def run_cmd_stdout(
|
|
24
|
-
args: list[str], timeout: int = 30, cwd: str | None = None
|
|
25
|
-
) -> str | None:
|
|
26
|
-
"""Run a command and return stripped stdout, or None on failure."""
|
|
27
|
-
try:
|
|
28
|
-
result = run_cmd(args, timeout=timeout, cwd=cwd)
|
|
29
|
-
return result.stdout.strip() if result.returncode == 0 else None
|
|
30
|
-
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
31
|
-
return None
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def read_json(path: str) -> dict[str, Any] | None:
|
|
35
|
-
"""Read a JSON file and return parsed object, or None on failure."""
|
|
36
|
-
if not os.path.exists(path):
|
|
37
|
-
return None
|
|
38
|
-
try:
|
|
39
|
-
with open(path) as f:
|
|
40
|
-
return json.load(f)
|
|
41
|
-
except (json.JSONDecodeError, OSError):
|
|
42
|
-
return None
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def write_json(path: str, data: dict[str, Any]) -> None:
|
|
46
|
-
"""Write a dict to a JSON file with trailing newline."""
|
|
47
|
-
with open(path, "w") as f:
|
|
48
|
-
json.dump(data, f, indent=2)
|
|
49
|
-
f.write("\n")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def read_jsonl(path: str) -> list[dict[str, Any]]:
|
|
53
|
-
"""Read a JSONL file and return list of parsed objects."""
|
|
54
|
-
entries: list[dict[str, Any]] = []
|
|
55
|
-
if not os.path.exists(path):
|
|
56
|
-
return entries
|
|
57
|
-
with open(path) as f:
|
|
58
|
-
for line in f:
|
|
59
|
-
line = line.strip()
|
|
60
|
-
if not line:
|
|
61
|
-
continue
|
|
62
|
-
try:
|
|
63
|
-
entries.append(json.loads(line))
|
|
64
|
-
except json.JSONDecodeError:
|
|
65
|
-
continue
|
|
66
|
-
return entries
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def severity_badge(severity: str) -> str:
|
|
70
|
-
"""Return a markdown-friendly severity indicator."""
|
|
71
|
-
badges = {
|
|
72
|
-
"critical": "**CRITICAL**",
|
|
73
|
-
"high": "**HIGH**",
|
|
74
|
-
"medium": "MEDIUM",
|
|
75
|
-
"low": "LOW",
|
|
76
|
-
"info": "info",
|
|
77
|
-
}
|
|
78
|
-
return badges.get(severity, severity)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def pr_number_from_url(pr_url: str) -> str:
|
|
82
|
-
"""Extract the PR or issue number from a GitHub URL's last path segment."""
|
|
83
|
-
return pr_url.rstrip("/").split("/")[-1]
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def ensure_github_label(name: str, color: str, description: str) -> None:
|
|
87
|
-
"""Create a GitHub label if it doesn't exist (idempotent)."""
|
|
88
|
-
try:
|
|
89
|
-
subprocess.run(
|
|
90
|
-
[
|
|
91
|
-
"gh", "label", "create", name,
|
|
92
|
-
"--color", color,
|
|
93
|
-
"--description", description,
|
|
94
|
-
],
|
|
95
|
-
capture_output=True,
|
|
96
|
-
timeout=15,
|
|
97
|
-
)
|
|
98
|
-
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
99
|
-
pass
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# /// script
|
|
3
|
-
# requires-python = ">=3.9"
|
|
4
|
-
# ///
|
|
5
|
-
"""
|
|
6
|
-
Warden Sweep: Create tracking issue.
|
|
7
|
-
|
|
8
|
-
Creates a GitHub issue summarizing the sweep results after verification
|
|
9
|
-
but before patching. Gives every PR a parent to reference and gives
|
|
10
|
-
reviewers a single place to see the full picture.
|
|
11
|
-
|
|
12
|
-
Usage:
|
|
13
|
-
uv run create_issue.py <sweep-dir>
|
|
14
|
-
|
|
15
|
-
Stdout: JSON with issueUrl and issueNumber
|
|
16
|
-
Stderr: Progress lines
|
|
17
|
-
|
|
18
|
-
Idempotent: if issueUrl already exists in manifest, skips creation.
|
|
19
|
-
"""
|
|
20
|
-
from __future__ import annotations
|
|
21
|
-
|
|
22
|
-
import argparse
|
|
23
|
-
import json
|
|
24
|
-
import os
|
|
25
|
-
import subprocess
|
|
26
|
-
import sys
|
|
27
|
-
from typing import Any
|
|
28
|
-
|
|
29
|
-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
30
|
-
from _utils import ( # noqa: E402
|
|
31
|
-
ensure_github_label,
|
|
32
|
-
pr_number_from_url,
|
|
33
|
-
read_json,
|
|
34
|
-
read_jsonl,
|
|
35
|
-
severity_badge,
|
|
36
|
-
write_json,
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def build_issue_body(
|
|
41
|
-
run_id: str,
|
|
42
|
-
scan_index: list[dict[str, Any]],
|
|
43
|
-
all_findings: list[dict[str, Any]],
|
|
44
|
-
verified: list[dict[str, Any]],
|
|
45
|
-
rejected: list[dict[str, Any]],
|
|
46
|
-
) -> str:
|
|
47
|
-
"""Build the GitHub issue body markdown."""
|
|
48
|
-
files_scanned = sum(1 for e in scan_index if e.get("status") == "complete")
|
|
49
|
-
files_timed_out = sum(
|
|
50
|
-
1 for e in scan_index
|
|
51
|
-
if e.get("status") == "error" and e.get("error") == "timeout"
|
|
52
|
-
)
|
|
53
|
-
files_errored = sum(
|
|
54
|
-
1 for e in scan_index
|
|
55
|
-
if e.get("status") == "error" and e.get("error") != "timeout"
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
# Collect unique skills from scan index
|
|
59
|
-
skills: set[str] = set()
|
|
60
|
-
for entry in scan_index:
|
|
61
|
-
for skill in entry.get("skills", []):
|
|
62
|
-
skills.add(skill)
|
|
63
|
-
|
|
64
|
-
lines = [
|
|
65
|
-
f"## Warden Sweep `{run_id}`",
|
|
66
|
-
"",
|
|
67
|
-
"| Metric | Count |",
|
|
68
|
-
"|--------|-------|",
|
|
69
|
-
f"| Files scanned | {files_scanned} |",
|
|
70
|
-
f"| Files timed out | {files_timed_out} |",
|
|
71
|
-
f"| Files errored | {files_errored} |",
|
|
72
|
-
f"| Total findings | {len(all_findings)} |",
|
|
73
|
-
f"| Verified | {len(verified)} |",
|
|
74
|
-
f"| Rejected | {len(rejected)} |",
|
|
75
|
-
"",
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
if verified:
|
|
79
|
-
lines.append("### Verified Findings")
|
|
80
|
-
lines.append("")
|
|
81
|
-
lines.append("| Severity | Skill | File | Title |")
|
|
82
|
-
lines.append("|----------|-------|------|-------|")
|
|
83
|
-
for f in verified:
|
|
84
|
-
sev = severity_badge(f.get("severity", "info"))
|
|
85
|
-
skill = f.get("skill", "")
|
|
86
|
-
file_path = f.get("file", "")
|
|
87
|
-
start_line = f.get("startLine")
|
|
88
|
-
location = f"{file_path}:{start_line}" if start_line else file_path
|
|
89
|
-
title = f.get("title", "")
|
|
90
|
-
lines.append(f"| {sev} | {skill} | `{location}` | {title} |")
|
|
91
|
-
lines.append("")
|
|
92
|
-
|
|
93
|
-
if skills:
|
|
94
|
-
lines.append("### Skills Run")
|
|
95
|
-
lines.append("")
|
|
96
|
-
lines.append(", ".join(sorted(skills)))
|
|
97
|
-
lines.append("")
|
|
98
|
-
|
|
99
|
-
lines.append("> Generated by Warden Sweep. PRs referencing this issue will appear below.")
|
|
100
|
-
|
|
101
|
-
return "\n".join(lines) + "\n"
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def create_github_issue(title: str, body: str) -> dict[str, Any]:
|
|
105
|
-
"""Create a GitHub issue with the warden label. Returns issueUrl and issueNumber."""
|
|
106
|
-
ensure_github_label("warden", "5319E7", "Automated fix from Warden Sweep")
|
|
107
|
-
|
|
108
|
-
result = subprocess.run(
|
|
109
|
-
[
|
|
110
|
-
"gh", "issue", "create",
|
|
111
|
-
"--label", "warden",
|
|
112
|
-
"--title", title,
|
|
113
|
-
"--body", body,
|
|
114
|
-
],
|
|
115
|
-
capture_output=True,
|
|
116
|
-
text=True,
|
|
117
|
-
timeout=30,
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
if result.returncode != 0:
|
|
121
|
-
raise RuntimeError(f"gh issue create failed: {result.stderr.strip()}")
|
|
122
|
-
|
|
123
|
-
issue_url = result.stdout.strip()
|
|
124
|
-
try:
|
|
125
|
-
issue_number = int(pr_number_from_url(issue_url))
|
|
126
|
-
except (ValueError, IndexError):
|
|
127
|
-
raise RuntimeError(f"Could not parse issue number from gh output: {issue_url}")
|
|
128
|
-
|
|
129
|
-
return {"issueUrl": issue_url, "issueNumber": issue_number}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def main() -> None:
|
|
133
|
-
parser = argparse.ArgumentParser(
|
|
134
|
-
description="Warden Sweep: Create tracking issue"
|
|
135
|
-
)
|
|
136
|
-
parser.add_argument("sweep_dir", help="Path to the sweep directory")
|
|
137
|
-
args = parser.parse_args()
|
|
138
|
-
|
|
139
|
-
sweep_dir = args.sweep_dir
|
|
140
|
-
data_dir = os.path.join(sweep_dir, "data")
|
|
141
|
-
manifest_path = os.path.join(data_dir, "manifest.json")
|
|
142
|
-
|
|
143
|
-
if not os.path.isdir(sweep_dir):
|
|
144
|
-
print(
|
|
145
|
-
json.dumps({"error": f"Sweep directory not found: {sweep_dir}"}),
|
|
146
|
-
file=sys.stdout,
|
|
147
|
-
)
|
|
148
|
-
sys.exit(1)
|
|
149
|
-
|
|
150
|
-
manifest = read_json(manifest_path) or {}
|
|
151
|
-
|
|
152
|
-
# Idempotency: if issue already exists, return existing values
|
|
153
|
-
if manifest.get("issueUrl"):
|
|
154
|
-
output = {
|
|
155
|
-
"issueUrl": manifest["issueUrl"],
|
|
156
|
-
"issueNumber": manifest.get("issueNumber", 0),
|
|
157
|
-
}
|
|
158
|
-
print(json.dumps(output))
|
|
159
|
-
return
|
|
160
|
-
|
|
161
|
-
run_id = manifest.get("runId", "unknown")
|
|
162
|
-
|
|
163
|
-
# Read sweep data
|
|
164
|
-
scan_index = read_jsonl(os.path.join(data_dir, "scan-index.jsonl"))
|
|
165
|
-
all_findings = read_jsonl(os.path.join(data_dir, "all-findings.jsonl"))
|
|
166
|
-
verified = read_jsonl(os.path.join(data_dir, "verified.jsonl"))
|
|
167
|
-
rejected = read_jsonl(os.path.join(data_dir, "rejected.jsonl"))
|
|
168
|
-
|
|
169
|
-
files_scanned = sum(1 for e in scan_index if e.get("status") == "complete")
|
|
170
|
-
|
|
171
|
-
# Build issue
|
|
172
|
-
title = f"Warden Sweep {run_id}: {len(verified)} findings across {files_scanned} files"
|
|
173
|
-
body = build_issue_body(run_id, scan_index, all_findings, verified, rejected)
|
|
174
|
-
|
|
175
|
-
print("Creating tracking issue...", file=sys.stderr)
|
|
176
|
-
result = create_github_issue(title, body)
|
|
177
|
-
print(f"Created issue: {result['issueUrl']}", file=sys.stderr)
|
|
178
|
-
|
|
179
|
-
# Write issueUrl and issueNumber to manifest
|
|
180
|
-
manifest["issueUrl"] = result["issueUrl"]
|
|
181
|
-
manifest["issueNumber"] = result["issueNumber"]
|
|
182
|
-
manifest.setdefault("phases", {})["issue"] = "complete"
|
|
183
|
-
write_json(manifest_path, manifest)
|
|
184
|
-
|
|
185
|
-
print(json.dumps(result))
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if __name__ == "__main__":
|
|
189
|
-
main()
|