hex-audit 0.4.4 → 0.5.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/DEMO.md +14 -12
- package/README.md +66 -61
- package/dashboard/.next/BUILD_ID +1 -1
- package/dashboard/.next/app-build-manifest.json +18 -11
- package/dashboard/.next/app-path-routes-manifest.json +1 -1
- package/dashboard/.next/build-manifest.json +4 -4
- package/dashboard/.next/next-server.js.nft.json +1 -1
- package/dashboard/.next/prerender-manifest.js +1 -1
- package/dashboard/.next/prerender-manifest.json +1 -1
- package/dashboard/.next/required-server-files.json +1 -1
- package/dashboard/.next/routes-manifest.json +1 -1
- package/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/_not-found.html +1 -1
- package/dashboard/.next/server/app/_not-found.rsc +4 -4
- package/dashboard/.next/server/app/access/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/api/flows/route.js +1 -1
- package/dashboard/.next/server/app/api/issues/[id]/move/route.js +1 -1
- package/dashboard/.next/server/app/api/issues/[id]/route.js +1 -1
- package/dashboard/.next/server/app/calls/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/conformance/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/diagram/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/diagram.html +1 -1
- package/dashboard/.next/server/app/diagram.rsc +4 -4
- package/dashboard/.next/server/app/flows/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/flows.html +1 -1
- package/dashboard/.next/server/app/flows.rsc +4 -4
- package/dashboard/.next/server/app/functions/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/issues/page.js +12 -1
- package/dashboard/.next/server/app/issues/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/overleaf/page.js +1 -0
- package/dashboard/.next/server/app/overleaf/page.js.nft.json +1 -0
- package/dashboard/.next/server/app/overleaf/page_client-reference-manifest.js +1 -0
- package/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/progress/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/stats/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app-paths-manifest.json +9 -8
- package/dashboard/.next/server/chunks/431.js +1 -1
- package/dashboard/.next/server/functions-config-manifest.json +1 -1
- package/dashboard/.next/server/middleware-build-manifest.js +1 -1
- package/dashboard/.next/server/pages/404.html +1 -1
- package/dashboard/.next/server/pages/500.html +1 -1
- package/dashboard/.next/server/server-reference-manifest.js +1 -1
- package/dashboard/.next/server/server-reference-manifest.json +1 -1
- package/dashboard/.next/static/chunks/app/issues/page-2bb52a36f20497fe.js +1 -0
- package/dashboard/.next/static/chunks/app/{layout-527d8aa86e483fbd.js → layout-9c3c84993dd895f6.js} +1 -1
- package/dashboard/.next/static/chunks/app/overleaf/page-bcea12a80c97b92f.js +1 -0
- package/dashboard/.next/static/chunks/{main-70c6d841ad572657.js → main-8f1e837090aef5cf.js} +1 -1
- package/dashboard/.next/static/css/9c84313d0484b74f.css +3 -0
- package/dashboard/app/api/flows/route.ts +4 -0
- package/dashboard/app/api/issues/[id]/move/route.ts +29 -58
- package/dashboard/app/api/issues/[id]/route.ts +11 -102
- package/dashboard/app/issues/IssuesBoardClient.tsx +155 -50
- package/dashboard/app/issues/page.tsx +40 -3
- package/dashboard/app/overleaf/OverleafClient.tsx +97 -0
- package/dashboard/app/overleaf/page.tsx +32 -0
- package/dashboard/components/DashboardLayout.tsx +8 -2
- package/dashboard/lib/finding-markdown.ts +110 -0
- package/dashboard/next.config.mjs +4 -0
- package/dist/analysis/external-calls.d.ts +8 -1
- package/dist/analysis/external-calls.d.ts.map +1 -1
- package/dist/analysis/external-calls.js +57 -13
- package/dist/analysis/external-calls.js.map +1 -1
- package/dist/commands/calls.d.ts.map +1 -1
- package/dist/commands/calls.js +15 -6
- package/dist/commands/calls.js.map +1 -1
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +12 -3
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/init.js +19 -19
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/issue.d.ts +3 -0
- package/dist/commands/issue.d.ts.map +1 -0
- package/dist/commands/issue.js +195 -0
- package/dist/commands/issue.js.map +1 -0
- package/dist/core/config.d.ts +1 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +10 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/issues.d.ts +116 -0
- package/dist/core/issues.d.ts.map +1 -0
- package/dist/core/issues.js +403 -0
- package/dist/core/issues.js.map +1 -0
- package/dist/core/schema.d.ts +24 -8
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +12 -1
- package/dist/core/schema.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/skills/generate-overleaf.md +18 -10
- package/dist/skills/ingest-aa-report.md +15 -19
- package/dist/skills/init-audit.md +13 -18
- package/dist/skills/sync-issues.md +86 -195
- package/dist/skills/validate-all-findings.md +50 -0
- package/dist/skills/validate-issue.md +20 -10
- package/dist/skills/write-finding.md +23 -28
- package/package.json +1 -1
- package/dashboard/.next/server/app/api/flows.body +0 -1
- package/dashboard/.next/server/app/api/flows.meta +0 -1
- package/dashboard/.next/static/chunks/app/issues/page-dc110d4cc0a5c398.js +0 -1
- package/dashboard/.next/static/css/7a38e5062ba6d403.css +0 -3
- /package/dashboard/.next/static/{ePObaFrrwft0BlvlllUMo → NrFPHIFHWQ5jNF62Px_X8}/_buildManifest.js +0 -0
- /package/dashboard/.next/static/{ePObaFrrwft0BlvlllUMo → NrFPHIFHWQ5jNF62Px_X8}/_ssgManifest.js +0 -0
|
@@ -1,266 +1,157 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: 'Two-way sync between local Hex findings and GitHub Issues —
|
|
2
|
+
description: 'Two-way sync between local Hex findings and GitHub Issues — GitHub is the source of truth; identity is the issue number'
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Skill: Sync Issues
|
|
6
6
|
|
|
7
7
|
**Recommended model:** Sonnet
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Hex's GitHub Issues integration. Hex never authenticates with GitHub — you authenticate once via `gh auth login` and this skill drives the `gh` CLI. One invocation runs both directions.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## The model: GitHub is the source of truth, identity is the issue number
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Once an issue is on GitHub it is **read-only in Hex** (status `synced`, locked on the board). Edits — severity, description, recommendation, status — happen on GitHub; this skill pulls them back into the local finding. `/generate-overleaf` reports **only** from synced issues.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
- `<output_dir>/config.json` — needs `settings.github.repo` set.
|
|
17
|
-
- `<output_dir>/findings.json` — local canonical findings.
|
|
18
|
-
- `<output_dir>/tracking.json` — for status filtering on push (default: only `verified` is pushed).
|
|
19
|
-
- `<output_dir>/external/github/findings.json` if it exists — last pulled state.
|
|
20
|
-
- `<output_dir>/external/github/sync-status.json` for `last_synced_at`.
|
|
15
|
+
**Identity is the GitHub issue number `#N`.** There is no hidden footer in the issue body (auditors create issues by hand, so a footer can't be relied on). Reconciliation matches a local finding to the issue number it has stored in `github.issue_number`. The body is exactly the five-field template, nothing hidden.
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
- `<output_dir>/findings.json` — updates `github.*` blocks on local findings.
|
|
24
|
-
- `<output_dir>/external/github/findings.json` — teammate findings in `AiResultFile` shape.
|
|
25
|
-
- `<output_dir>/external/github/sync-status.json` — counters + timestamp.
|
|
26
|
-
- `<output_dir>/external/github/raw-issues.json` — debug cache of the gh JSON.
|
|
27
|
-
- `<output_dir>/tracking.json` — teammate findings as new entries; local entries flipped to `duplicate` on match.
|
|
28
|
-
- `<output_dir>/comparison.json` — appended with `match_signals` for each dedup hit.
|
|
17
|
+
All board mutations go through `hex issue` — never hand-edit `tracking.json` / `findings.json`.
|
|
29
18
|
|
|
30
|
-
##
|
|
19
|
+
## Context
|
|
31
20
|
|
|
32
|
-
|
|
21
|
+
- `<output_dir>/config.json` — needs `settings.github.repo`.
|
|
22
|
+
- `<output_dir>/findings.json` — local findings (some already carry a `github.issue_number`).
|
|
23
|
+
- `<output_dir>/tracking.json` — status (`verified` are push candidates).
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
gh CLI is not authenticated. Run `gh auth login` once on this machine, then re-run /sync-issues.
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
…and stop. Hex never stores GitHub credentials.
|
|
25
|
+
## Phase 0 — Preflight
|
|
39
26
|
|
|
40
|
-
If `gh`
|
|
27
|
+
**0a.** `gh auth status`. If non-zero, print "gh CLI is not authenticated. Run `gh auth login` once, then re-run /sync-issues." and stop. If `gh` is missing, point to `https://cli.github.com/`.
|
|
41
28
|
|
|
42
|
-
**0b
|
|
29
|
+
**0b.** Resolve `settings.github.repo`. If missing, **AskUserQuestion** for `owner/repo`, validate the shape, persist to `config.json`.
|
|
43
30
|
|
|
44
|
-
**0c
|
|
31
|
+
**0c.** `gh repo view <repo> --json viewerPermission`. If `READ`/null, **AskUserQuestion**: "Read-only access — push will fail. Continue pull-only? (yes/no)". Track `push_enabled`.
|
|
45
32
|
|
|
46
|
-
## Phase 1 — Pull
|
|
33
|
+
## Phase 1 — Pull (all issues, GitHub → local)
|
|
47
34
|
|
|
48
|
-
**1a. Fetch
|
|
35
|
+
**1a. Fetch every issue — no label filter.** Audit repos contain only audit findings, so pull them all:
|
|
49
36
|
|
|
50
37
|
```bash
|
|
51
|
-
gh issue list --repo <repo> --
|
|
52
|
-
--json number,title,body,
|
|
38
|
+
gh issue list --repo <repo> --state all --limit 1000 \
|
|
39
|
+
--json number,title,body,state,author,createdAt,updatedAt,url
|
|
53
40
|
```
|
|
54
41
|
|
|
55
|
-
|
|
42
|
+
**1b. Parse each issue.** Title is `[Severity] Title`. Body is the five-field template:
|
|
56
43
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
gh issue view <number> --repo <repo> --json comments
|
|
61
|
-
```
|
|
44
|
+
```markdown
|
|
45
|
+
**File(s)**: [`<file>`](<url>)
|
|
62
46
|
|
|
63
|
-
|
|
47
|
+
**Description**: <text>
|
|
64
48
|
|
|
65
|
-
**
|
|
49
|
+
**Recommendation(s)**: <text>
|
|
66
50
|
|
|
67
|
-
|
|
51
|
+
**Status**: <Fixed|Mitigated|Acknowledged|Not Fixed|Unresolved>
|
|
68
52
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
"issue_number": <num>,
|
|
73
|
-
"issue_url": "<url>",
|
|
74
|
-
"state": "open|closed",
|
|
75
|
-
"last_synced_at": "<now>",
|
|
76
|
-
"sync_status": "in_sync",
|
|
77
|
-
"comments": [{ "author": "<login>", "body": "<text>", "created_at": "<iso>", "url": "<url>" }]
|
|
78
|
-
}
|
|
79
|
-
```
|
|
53
|
+
**Update from the client**: <text>
|
|
54
|
+
```
|
|
80
55
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
- **No footer** → teammate finding. Two writes:
|
|
84
|
-
1. Add to the in-memory `external/github/findings.json` AiResultFile (`tool: "github"`, severity parsed from `severity:*` labels, default Medium):
|
|
85
|
-
```json
|
|
86
|
-
{
|
|
87
|
-
"id": "github-<issue_number>",
|
|
88
|
-
"tool": "github",
|
|
89
|
-
"title": "<issue title>",
|
|
90
|
-
"severity": "Critical|High|Medium|Low|Info",
|
|
91
|
-
"description": "<body without footer>",
|
|
92
|
-
"affected_code": [<best-effort file refs extracted from body>],
|
|
93
|
-
"confidence": "medium",
|
|
94
|
-
"category": "<from label if present>",
|
|
95
|
-
"raw_category": "github-issue"
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
2. Add to `tracking.json`:
|
|
99
|
-
```json
|
|
100
|
-
{
|
|
101
|
-
"id": "github-<issue_number>",
|
|
102
|
-
"title": "<title>",
|
|
103
|
-
"severity": "<severity>",
|
|
104
|
-
"source": "github",
|
|
105
|
-
"status": "pending_validation",
|
|
106
|
-
"poc_status": "not_started",
|
|
107
|
-
"poc_file": null,
|
|
108
|
-
"duplicates": [],
|
|
109
|
-
"notes": "issue=<url> author=<author>"
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Write `external/github/findings.json` atomically after the pass.
|
|
114
|
-
|
|
115
|
-
## Phase 2 — Inline dedup (GitHub-canonical)
|
|
116
|
-
|
|
117
|
-
For each **new github entry** added in Phase 1 step 1b's third branch, run a semantic comparison against every **existing** non-github tracking entry. Match signals:
|
|
56
|
+
Extract severity from the title, and the four body fields. If a body is **blatantly malformed** (no `**Description**:` marker), warn (`Skipping #N: not in the Hex finding format`) and skip it — do not fail the whole sync. Otherwise treat it as a valid finding (most will be).
|
|
118
57
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
- Flip the **local** matching entry to `status: "duplicate"`, set `duplicate_of: "github-<issue_number>"` on it.
|
|
134
|
-
- The github entry stays `pending_validation` (it's the canonical the team is working from).
|
|
135
|
-
- Append to `comparison.json` under `duplicates`:
|
|
136
|
-
```json
|
|
137
|
-
{
|
|
138
|
-
"ai_finding": "<local-entry-id>",
|
|
139
|
-
"matches": "github-<issue_number>",
|
|
140
|
-
"confidence": "high|medium|low",
|
|
141
|
-
"match_signals": { "contract": true, "function": true, "root_cause": "same", "attack_vector": "same" },
|
|
142
|
-
"reasoning": "<one short sentence>"
|
|
143
|
-
}
|
|
58
|
+
**1c. Reconcile by issue number `#N`:**
|
|
59
|
+
- **A local finding already records `github.issue_number === N`** → it's the same issue; pull GitHub's content into it:
|
|
60
|
+
```bash
|
|
61
|
+
npx hex issue sync-set <local-H-id> --issue-number N --issue-url <url> --state <open|closed> \
|
|
62
|
+
--title "<title-without-[Severity]>" --severity <Severity> \
|
|
63
|
+
--description-file /tmp/N_desc.md --recommendation-file /tmp/N_reco.md \
|
|
64
|
+
--resolution "<Status>" --update-from-client-file /tmp/N_update.md
|
|
65
|
+
```
|
|
66
|
+
- **No local finding has `#N`** (a teammate's hand-added issue, or one created outside Hex) → create a local finding for it, then sync-set:
|
|
67
|
+
```bash
|
|
68
|
+
ID=$(npx hex issue new --source github --source-ref N --title "<title>" --severity <Severity>)
|
|
69
|
+
npx hex issue sync-set "$ID" --issue-number N --issue-url <url> --state <open|closed> \
|
|
70
|
+
--description-file /tmp/N_desc.md --recommendation-file /tmp/N_reco.md \
|
|
71
|
+
--resolution "<Status>" --update-from-client-file /tmp/N_update.md
|
|
144
72
|
```
|
|
145
|
-
(The schema field is named `ai_finding` for historical reasons but is repurposed here as "the local duplicate.")
|
|
146
|
-
|
|
147
|
-
Persist updated `tracking.json` and `comparison.json` after the dedup pass.
|
|
148
|
-
|
|
149
|
-
## Phase 3 — Push
|
|
150
|
-
|
|
151
|
-
Skip if `push_enabled = false`.
|
|
152
|
-
|
|
153
|
-
**3a. Candidates.** From `findings.json`, select each finding whose tracking entry has `status` in `settings.github.publish_status` (default `["verified"]`). Skip findings already marked `status: "duplicate"` (no point publishing a known duplicate).
|
|
154
73
|
|
|
155
|
-
|
|
156
|
-
- Has `github.issue_number` → update via `gh issue edit`.
|
|
157
|
-
- No `github.issue_number` → create via `gh issue create`.
|
|
74
|
+
Either way the card lands in the **Synced** column, locked. Reconcile strictly by `#N`; never scan the body for an identifier.
|
|
158
75
|
|
|
159
|
-
|
|
76
|
+
## Phase 1.5 — Dedup before push (GitHub is canonical)
|
|
160
77
|
|
|
161
|
-
|
|
162
|
-
**Severity:** <Severity>
|
|
78
|
+
Before pushing, guard against filing a finding that is already on GitHub (e.g. a teammate created it by hand, and you pulled it into Synced in Phase 1). For each **verified** local finding that has no `github.issue_number` yet, semantically compare it against every **synced** finding on these axes:
|
|
163
79
|
|
|
164
|
-
|
|
80
|
+
| Signal | Compare |
|
|
81
|
+
|---|---|
|
|
82
|
+
| **contract** (boolean) | affected contract name |
|
|
83
|
+
| **function** (boolean) | affected function name |
|
|
84
|
+
| **root_cause** (`same`/`overlapping`/`different`) | the technical mechanism |
|
|
85
|
+
| **attack_vector** (`same`/`overlapping`/`different`) | what an attacker actually does |
|
|
165
86
|
|
|
166
|
-
|
|
87
|
+
A match is at least one of: `root_cause = same` AND `function = true`; or `root_cause = same` AND `contract = true` AND `attack_vector = same`; or `root_cause = overlapping` AND `function = true` AND `attack_vector = same`.
|
|
167
88
|
|
|
168
|
-
|
|
169
|
-
**`<location.file>`**
|
|
89
|
+
On match — the synced (on-GitHub) finding is canonical, so the local verified copy becomes the duplicate. Do **not** push it:
|
|
170
90
|
|
|
171
|
-
```
|
|
172
|
-
<
|
|
91
|
+
```bash
|
|
92
|
+
npx hex issue move <verified-H-id> --to duplicate --duplicate-of <synced-H-id>
|
|
173
93
|
```
|
|
174
|
-
</for>
|
|
175
|
-
|
|
176
|
-
### Recommendation
|
|
177
|
-
|
|
178
|
-
<finding.recommendation>
|
|
179
|
-
|
|
180
|
-
---
|
|
181
94
|
|
|
182
|
-
|
|
95
|
+
Then append to `<output_dir>/comparison.json` under `duplicates` (the board renders the chip + signals from this):
|
|
183
96
|
|
|
184
|
-
|
|
97
|
+
```json
|
|
98
|
+
{ "ai_finding": "<verified-H-id>", "matches": "<synced-H-id>", "confidence": "high|medium|low",
|
|
99
|
+
"match_signals": { "contract": true, "function": true, "root_cause": "same", "attack_vector": "same" },
|
|
100
|
+
"reasoning": "<one sentence>" }
|
|
185
101
|
```
|
|
186
102
|
|
|
187
|
-
|
|
103
|
+
If `comparison.json` doesn't exist, create it with `{ "duplicates": [], "novel": [], "rejected": [] }`.
|
|
188
104
|
|
|
189
|
-
|
|
105
|
+
## Phase 2 — Push (verified local findings → GitHub)
|
|
190
106
|
|
|
191
|
-
|
|
192
|
-
<default_labels...> severity:<lowercased> source:<finding.source or 'manual'> status:<tracking.status>
|
|
193
|
-
```
|
|
107
|
+
Skip if `push_enabled` is false.
|
|
194
108
|
|
|
195
|
-
|
|
109
|
+
Candidates: findings whose tracking status is in `settings.github.publish_status` (default `["verified"]`) **and** which have no `github.issue_number` yet, **and** which Phase 1.5 did not flag as duplicates. (Synced findings are already on GitHub; duplicates/rejected/unverified are never pushed.)
|
|
196
110
|
|
|
197
|
-
|
|
111
|
+
For each candidate, render the body with the exact five-field template (see Phase 1b — `dashboard/lib/finding-markdown.ts::findingToGithubBody` is the canonical renderer). Write it to a temp file. **No labels, no footer.**
|
|
198
112
|
|
|
199
|
-
Create:
|
|
200
113
|
```bash
|
|
201
|
-
gh issue create --repo <repo> --title "[<Severity>] <title>" --body-file <tmp>
|
|
202
|
-
--label "hex" --label "audit" --label "severity:..." --label "source:..." --label "status:..."
|
|
114
|
+
gh issue create --repo <repo> --title "[<Severity>] <title>" --body-file <tmp>
|
|
203
115
|
```
|
|
204
116
|
|
|
205
|
-
Parse the returned URL
|
|
117
|
+
Parse the returned URL for `#N`, then lock it into Synced and record the number:
|
|
206
118
|
|
|
207
|
-
Update existing:
|
|
208
119
|
```bash
|
|
209
|
-
|
|
210
|
-
--add-label "severity:..." --add-label "status:..."
|
|
120
|
+
npx hex issue sync-set <H-id> --issue-number N --issue-url <url> --state open
|
|
211
121
|
```
|
|
212
122
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
If push fails for any single finding: capture stderr, set its `github.sync_status` to `conflict`, append a one-line error, continue with the next finding. Never abort the whole sync on one failure.
|
|
123
|
+
From now on the finding is identified by `#N`; the next pull matches it by number, so it never duplicates.
|
|
216
124
|
|
|
217
|
-
|
|
125
|
+
If a single `gh` call fails, log the error and continue with the next candidate — never abort the whole sync.
|
|
218
126
|
|
|
219
|
-
## Phase
|
|
220
|
-
|
|
221
|
-
Write `external/github/sync-status.json`:
|
|
222
|
-
|
|
223
|
-
```json
|
|
224
|
-
{
|
|
225
|
-
"repo": "<repo>",
|
|
226
|
-
"last_synced_at": "<now>",
|
|
227
|
-
"pushed": <new-created>,
|
|
228
|
-
"updated": <existing-updated>,
|
|
229
|
-
"pulled": <total-issues-seen>,
|
|
230
|
-
"teammate_findings": <count-of-footer-less>,
|
|
231
|
-
"duplicates_detected": <local-entries-flipped-to-duplicate>,
|
|
232
|
-
"errors": [<one-line-per-error>]
|
|
233
|
-
}
|
|
234
|
-
```
|
|
127
|
+
## Phase 3 — Status report
|
|
235
128
|
|
|
236
129
|
Print:
|
|
237
130
|
|
|
238
131
|
```
|
|
239
132
|
GitHub sync complete (<repo>)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
<if errors:>
|
|
244
|
-
Errors:
|
|
245
|
-
- <error 1>
|
|
133
|
+
↓ Pulled K issues (S synced into the board, M skipped as malformed)
|
|
134
|
+
↑ Pushed P verified findings as new issues
|
|
135
|
+
Errors: <n>
|
|
246
136
|
```
|
|
247
137
|
|
|
248
|
-
## Conflict resolution
|
|
138
|
+
## Conflict resolution
|
|
249
139
|
|
|
250
|
-
- **
|
|
251
|
-
- **State (open/closed)** —
|
|
252
|
-
- **Comments** —
|
|
140
|
+
- **Field content** — GitHub wins for synced issues. Every pull overwrites the local finding's title/severity/description/recommendation/status/update from the issue.
|
|
141
|
+
- **State (open/closed)** — from GitHub, stored on `github.state`.
|
|
142
|
+
- **Comments** — Hex never reads or posts comments.
|
|
253
143
|
|
|
254
144
|
## Error handling
|
|
255
145
|
|
|
256
|
-
- Per-issue errors are isolated — log
|
|
257
|
-
- Rate limit (403
|
|
258
|
-
- Network failures: print the gh error and stop the current phase
|
|
146
|
+
- Per-issue errors are isolated — log and continue.
|
|
147
|
+
- Rate limit (403 / "rate limit"): print "Rate-limited — try again later." and stop the current phase.
|
|
148
|
+
- Network failures: print the `gh` error and stop the current phase; completed work stays persisted.
|
|
259
149
|
|
|
260
150
|
## What this skill does NOT do
|
|
261
151
|
|
|
262
|
-
-
|
|
263
|
-
-
|
|
264
|
-
-
|
|
265
|
-
-
|
|
266
|
-
-
|
|
152
|
+
- Authenticate (use `gh auth login`).
|
|
153
|
+
- Post or read comments.
|
|
154
|
+
- Apply, create, or remove labels (the repo owner manages labels).
|
|
155
|
+
- Embed any hidden footer or marker in the issue body.
|
|
156
|
+
- Delete remote issues.
|
|
157
|
+
- Push `rejected`, `unverified`, `duplicate`, or already-`synced` findings.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Walk every Potential issue on the board and validate them one by one (interactive)"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Skill: Validate All Findings
|
|
6
|
+
|
|
7
|
+
**Recommended model:** Opus
|
|
8
|
+
|
|
9
|
+
Runs `/validate-issue` over every issue currently in the **Potential** column, one at a time, so the auditor can clear the backlog in a single pass without naming each id.
|
|
10
|
+
|
|
11
|
+
## Step 1 — Build the work list
|
|
12
|
+
|
|
13
|
+
Read `<output_dir>/tracking.json`. The Potential column is every entry whose `status` is `pending_validation` or `unverified`. Collect their ids in a stable order: severity (Critical → High → Medium → Low → Info), then id. Call this list `POTENTIAL` with size `N`.
|
|
14
|
+
|
|
15
|
+
If `N` is 0, report "No Potential issues to validate." and stop.
|
|
16
|
+
|
|
17
|
+
## Step 2 — Loop
|
|
18
|
+
|
|
19
|
+
For each issue `i` of `N` (1-based), print a header:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
── Validating i/N: <id> [<severity>] <title> ──
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then run the full **`/validate-issue`** flow for that `<id>` exactly as that skill defines it:
|
|
26
|
+
1. Assemble context (the tracking entry + its source record + the affected source).
|
|
27
|
+
2. Trace the claim in the code.
|
|
28
|
+
3. Reach a verdict:
|
|
29
|
+
- **Valid** → ask PoC-vs-memo (AskUserQuestion), write the validation memo, optionally generate the PoC, then `hex issue patch <id> ...` + `hex issue move <id> --to verified`; offer the severity-adjustment prompt.
|
|
30
|
+
- **Invalid** → `hex issue move <id> --to invalid`, memo records why.
|
|
31
|
+
- **Uncertain** → leave in place, `hex issue patch <id> --notes "<open questions>"`.
|
|
32
|
+
|
|
33
|
+
Do not batch or skip the interactive prompts — this skill is the same per-issue UX as `/validate-issue`, just iterated. The auditor stays in control of each verdict.
|
|
34
|
+
|
|
35
|
+
## Step 3 — Resume safety
|
|
36
|
+
|
|
37
|
+
Re-fetch the entry's current status before validating it (an earlier loop iteration, or a previous run, may have already moved it). Skip any id that is no longer in the Potential column and note it: `Skipping <id> (already <status>)`. This makes the skill safe to re-run after an interruption.
|
|
38
|
+
|
|
39
|
+
## Step 4 — Summary
|
|
40
|
+
|
|
41
|
+
After the loop, print a tally:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Validated N Potential issues:
|
|
45
|
+
→ Verified: V
|
|
46
|
+
→ Invalid: X
|
|
47
|
+
→ Left for review (uncertain): U
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Remind the auditor of the next steps: `/sync-issues` to push the Verified findings to GitHub, then `/generate-overleaf` for the report.
|
|
@@ -77,22 +77,32 @@ For conformance items, the "claim" is `spec_text` + the finding paragraph — i.
|
|
|
77
77
|
>
|
|
78
78
|
> Reply **1** or **2**.
|
|
79
79
|
|
|
80
|
-
2. **If PoC chosen:** invoke the `generate-poc` skill flow (read the project test setup, write the PoC, run and iterate until it passes).
|
|
80
|
+
2. **If PoC chosen:** invoke the `generate-poc` skill flow (read the project test setup, write the PoC, run and iterate until it passes). Note the test path, e.g. `test/hex-pocs/<id>_<name>.t.sol`.
|
|
81
81
|
|
|
82
|
-
3. **If memo-only chosen:**
|
|
82
|
+
3. **If memo-only chosen:** no PoC file.
|
|
83
83
|
|
|
84
|
-
4.
|
|
84
|
+
4. **Persist via the CLI — do not hand-edit `findings.json` or `tracking.json`.** The `hex issue` command is the single source of truth for board mutations; it materializes a `findings.json` entry from the source record (conformance / auditagent / github) when one does not exist yet, and routes fields correctly. Write the validation memo and the recommendation to temp files, then:
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
```bash
|
|
87
|
+
# Land the editable fields (materializes a finding for conformance/auditagent/github ids):
|
|
88
|
+
npx hex issue patch <id> \
|
|
89
|
+
--severity <Severity> \
|
|
90
|
+
--description-file <output_dir>/validations/<id>_memo.md \
|
|
91
|
+
--recommendation-file /tmp/<id>_reco.md
|
|
92
|
+
# Promote to Verified:
|
|
93
|
+
npx hex issue move <id> --to verified
|
|
94
|
+
```
|
|
87
95
|
|
|
88
|
-
|
|
96
|
+
For a memo-only verdict, the same two commands apply (the PoC fields just stay unset). For `--description-file`, you may pass either the memo or a dedicated description file; prefer a clean description file if the memo is long.
|
|
89
97
|
|
|
90
|
-
|
|
98
|
+
Hex does not write `@audit-issue` comments into the client's source files — the board and finding record are the only place the issue lives.
|
|
99
|
+
|
|
100
|
+
5. Use **AskUserQuestion** to ask:
|
|
91
101
|
> Issue verified as **\<id\>** with severity **\<current severity\>**.
|
|
92
102
|
>
|
|
93
103
|
> Would you like to change the severity? Reply with the new severity (Critical / High / Medium / Low / Info), or **no** to keep it.
|
|
94
104
|
|
|
95
|
-
|
|
105
|
+
6. If the user provides a new severity, run `npx hex issue patch <id> --severity <NewSeverity>`.
|
|
96
106
|
|
|
97
107
|
**Invalid:**
|
|
98
108
|
|
|
@@ -100,14 +110,14 @@ For conformance items, the "claim" is `spec_text` + the finding paragraph — i.
|
|
|
100
110
|
- Is the attack path blocked? By what specifically (modifier, require, state check)?
|
|
101
111
|
- Are the preconditions impossible? Why?
|
|
102
112
|
- Is the impact assessment wrong? What actually happens?
|
|
103
|
-
2.
|
|
104
|
-
3. Do NOT write a finding
|
|
113
|
+
2. Move it to the Invalid column: `npx hex issue move <id> --to invalid`.
|
|
114
|
+
3. Do NOT write a finding body. The memo alone records the reasoning.
|
|
105
115
|
|
|
106
116
|
**Uncertain:**
|
|
107
117
|
|
|
108
118
|
1. List specific questions that need manual investigation.
|
|
109
119
|
2. State what would confirm or deny the issue.
|
|
110
|
-
3. Leave
|
|
120
|
+
3. Leave the column as-is (no `hex issue move`). Record the open questions on the card: `npx hex issue patch <id> --notes "<open questions>"`.
|
|
111
121
|
|
|
112
122
|
## Output: validation memo
|
|
113
123
|
|
|
@@ -13,13 +13,13 @@ This skill records a manual issue that the auditor has spotted during review. Th
|
|
|
13
13
|
Read:
|
|
14
14
|
- The issue description provided by the auditor.
|
|
15
15
|
- The relevant source code.
|
|
16
|
-
- `<output_dir>/findings.json` and `<output_dir>/tracking.json` — to
|
|
16
|
+
- `<output_dir>/findings.json` and `<output_dir>/tracking.json` — to check for duplicates (ids are allocated by `hex issue new`, never hand-assigned).
|
|
17
17
|
|
|
18
18
|
## Step 0: Duplicate gate
|
|
19
19
|
|
|
20
20
|
Scan `findings.json` and `tracking.json` for any existing entry that covers the same vulnerability (same affected contract/function, same or overlapping root cause). If a match is found, warn the auditor:
|
|
21
21
|
|
|
22
|
-
> "This appears to duplicate
|
|
22
|
+
> "This appears to duplicate `<H-NNN>`: \<title\>. Proceed anyway?"
|
|
23
23
|
|
|
24
24
|
Only continue if confirmed.
|
|
25
25
|
|
|
@@ -51,9 +51,11 @@ Cross-reference both before assigning a severity.
|
|
|
51
51
|
|
|
52
52
|
Write the finding following this structure:
|
|
53
53
|
|
|
54
|
+
The `id`, `poc`, and `created_at` fields are managed by `hex issue new` (see Actions) — you supply the rest via `hex issue patch`. The full record shape, for reference:
|
|
55
|
+
|
|
54
56
|
```json
|
|
55
57
|
{
|
|
56
|
-
"id": "
|
|
58
|
+
"id": "H-NNN (allocated by hex issue new)",
|
|
57
59
|
"title": "<concise, descriptive title>",
|
|
58
60
|
"severity": "Critical|High|Medium|Low|Info",
|
|
59
61
|
"category": "<e.g., Math / Rounding, Access Control, Reentrancy, Oracle Manipulation>",
|
|
@@ -108,35 +110,28 @@ All inserted audit comments must be full sentences starting with a capital lette
|
|
|
108
110
|
|
|
109
111
|
## Actions
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
"duplicates": [],
|
|
123
|
-
"notes": ""
|
|
124
|
-
}
|
|
113
|
+
Persist through the `hex issue` CLI — never hand-edit `findings.json` or `tracking.json`. The CLI allocates the uniform `H-NNN` id, writes the tracking entry (`status: pending_validation`, `source: manual`), and creates the findings.json skeleton.
|
|
114
|
+
|
|
115
|
+
1. **Create the issue:**
|
|
116
|
+
```bash
|
|
117
|
+
npx hex issue new --source manual --title "<concise title>" --severity <Severity>
|
|
118
|
+
```
|
|
119
|
+
This prints the allocated id (e.g. `H-007`). Capture it.
|
|
120
|
+
|
|
121
|
+
2. **Fill the fields** (write the description and recommendation to temp files first, to preserve newlines):
|
|
122
|
+
```bash
|
|
123
|
+
npx hex issue patch <id> --description-file /tmp/<id>_desc.md --recommendation-file /tmp/<id>_reco.md
|
|
125
124
|
```
|
|
126
|
-
|
|
127
|
-
- Open the source file at `file`.
|
|
128
|
-
- Find the first significant line of the `snippet` (skip blank lines and `// ....`).
|
|
129
|
-
- If an existing `// @audit-issue ...` comment is directly above, replace it with `// @audit-issue F<NNN> <short title>`.
|
|
130
|
-
- Otherwise insert `// @audit-issue F<NNN> <short title>` directly above, matching indentation.
|
|
125
|
+
The description must be self-contained (what the issue is, why it exists, the impact). The recommendation is prose only, hedged (`Consider...`), no code.
|
|
131
126
|
|
|
132
|
-
|
|
127
|
+
There is **no source-file annotation** — Hex does not write `@audit-issue` comments into the client's `.sol` files. The board and the finding record are the single place the issue lives.
|
|
133
128
|
|
|
134
129
|
## After writing
|
|
135
130
|
|
|
136
|
-
The finding
|
|
131
|
+
The finding appears on the dashboard's `/issues` board as a Potential card (`H-NNN`). The auditor can:
|
|
137
132
|
|
|
138
|
-
- Run `/validate-issue
|
|
139
|
-
- Drag the card
|
|
140
|
-
- Run `/sync-issues`
|
|
133
|
+
- Run `/validate-issue <id>` to write a validation memo and optionally generate a PoC; on success the card moves to Verified.
|
|
134
|
+
- Drag the card from Potential → Verified on the board if no PoC is needed (e.g., trivial Info-level issues).
|
|
135
|
+
- Run `/sync-issues` (once findings are Verified and `settings.github.repo` is set) to push them to GitHub.
|
|
141
136
|
|
|
142
|
-
Do NOT run `/validate-issue` or `/sync-issues` automatically — those are auditor-driven
|
|
137
|
+
Do NOT run `/validate-issue` or `/sync-issues` automatically — those are auditor-driven.
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"flows":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"status":200,"headers":{"content-type":"application/json","x-next-cache-tags":"_N_T_/layout,_N_T_/api/layout,_N_T_/api/flows/layout,_N_T_/api/flows/route,_N_T_/api/flows"}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[4521],{4260:function(e,t,a){Promise.resolve().then(a.bind(a,6310))},6310:function(e,t,a){"use strict";a.d(t,{IssuesBoardClient:function(){return x}});var r=a(7821),n=a(8078),s=a(3924);let i={Critical:"bg-[var(--critical)]/18 text-[var(--critical)]",High:"bg-[var(--high)]/18 text-[var(--high)]",Medium:"bg-[var(--medium)]/18 text-[var(--medium)]",Low:"bg-[var(--low)]/18 text-[var(--low)]",Info:"bg-[var(--info)]/18 text-[var(--info)]"};function o(e){var t;let{severity:a}=e,n=null!==(t=i[a])&&void 0!==t?t:i.Info;return(0,r.jsx)("span",{className:"inline-flex items-center rounded-md px-2 py-0.5 text-caption font-medium ".concat(n),children:a})}let l=[{id:"potential",label:"Potential",description:"Awaiting validation"},{id:"verified",label:"Verified",description:"Confirmed issues ready to report"},{id:"invalid",label:"Invalid",description:"Rejected after review"},{id:"duplicate",label:"Duplicate",description:"Covered by another entry"}],c={Critical:0,High:1,Medium:2,Low:3,Info:4},d=["Critical","High","Medium","Low","Info"],u=["Fixed","Mitigated","Acknowledged","Not Fixed","Unresolved"];function x(e){var t;let{issues:a}=e,[i,o]=(0,n.useState)(a),[d,u]=(0,n.useState)(null),[x,f]=(0,n.useState)("all");(0,n.useEffect)(()=>{o(a)},[a]);let b=(0,s.Dy)((0,s.VT)(s.we,{activationConstraint:{distance:4}})),v=(0,n.useMemo)(()=>"all"===x?i:i.filter(e=>e.source===x),[i,x]),g=(0,n.useMemo)(()=>{let e={potential:[],verified:[],invalid:[],duplicate:[]};for(let t of v)e[t.column].push(t);for(let t of Object.values(e))t.sort((e,t)=>{var a,r;return(null!==(a=c[e.severity])&&void 0!==a?a:5)-(null!==(r=c[t.severity])&&void 0!==r?r:5)||e.id.localeCompare(t.id)});return e},[v]),y=(0,n.useMemo)(()=>{let e={potential:0,verified:0,invalid:0,duplicate:0};for(let t of i)e[t.column]++;return e},[i]),j=(0,n.useCallback)(async(e,t)=>{o(a=>a.map(a=>a.id===e?{...a,column:t,status:function(e){switch(e){case"potential":return"pending_validation";case"verified":return"verified";case"invalid":return"rejected";case"duplicate":return"duplicate"}}(t),duplicate_of:"duplicate"===t?a.duplicate_of:null}:a));try{let a=await fetch("/api/issues/".concat(encodeURIComponent(e),"/move"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({column:t})});if(!a.ok){var r;let e=await a.json().catch(()=>({}));throw Error(null!==(r=e.error)&&void 0!==r?r:"move failed")}}catch(t){console.error(t),o(t=>t.map(t=>{var r;return t.id===e&&null!==(r=a.find(t=>t.id===e))&&void 0!==r?r:t}))}},[a]),N=(0,n.useCallback)(async(e,t)=>{o(a=>a.map(a=>a.id===e?{...a,...t}:a));try{let r=await fetch("/api/issues/".concat(encodeURIComponent(e)),{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok){var a;let e=await r.json().catch(()=>({}));throw Error(null!==(a=e.error)&&void 0!==a?a:"update failed")}}catch(e){console.error(e)}},[]),w=null!==(t=i.find(e=>e.id===d))&&void 0!==t?t:null;return(0,r.jsxs)("div",{children:[(0,r.jsxs)("header",{className:"mb-6 flex flex-wrap items-end justify-between gap-3",children:[(0,r.jsxs)("div",{children:[(0,r.jsx)("h1",{className:"text-title font-semibold text-text-primary",children:"Issues"}),(0,r.jsxs)("p",{className:"mt-1 text-body text-text-secondary",children:[i.length," total — ",y.potential," potential, ",y.verified," verified,"," ",y.invalid," invalid, ",y.duplicate," duplicate. Drag a card to change column; click to edit."]})]}),(0,r.jsx)(m,{value:x,onChange:f})]}),(0,r.jsx)(s.LB,{sensors:b,onDragEnd:e=>{let{active:t,over:a}=e;if(!a)return;let r=a.id,n=t.id,s=i.find(e=>e.id===n);s&&s.column!==r&&j(n,r)},children:(0,r.jsx)("div",{className:"grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-4",children:l.map(e=>(0,r.jsx)(p,{column:e,issues:g[e.id],onOpen:u},e.id))})}),w&&(0,r.jsx)(h,{issue:w,onClose:()=>u(null),onSave:e=>N(w.id,e)})]})}function m(e){let{value:t,onChange:a}=e;return(0,r.jsx)("div",{className:"flex flex-wrap gap-1.5",children:[{id:"all",label:"All sources"},{id:"manual",label:"Manual"},{id:"auditagent",label:"AuditAgent"},{id:"conformance",label:"Conformance"},{id:"github",label:"GitHub"}].map(e=>{let n=t===e.id;return(0,r.jsx)("button",{type:"button",onClick:()=>a(e.id),className:"rounded-md px-2.5 py-1 text-caption font-medium ".concat(n?"bg-accent text-surface-0":"bg-surface-2 text-text-secondary hover:text-text-primary"),children:e.label},e.id)})})}function p(e){let{column:t,issues:a,onOpen:n}=e,{isOver:i,setNodeRef:o}=(0,s.Zj)({id:t.id});return(0,r.jsxs)("section",{ref:o,"aria-label":t.label,className:"flex flex-col gap-2 rounded-lg border bg-surface-1 p-3 transition-colors ".concat(i?"border-accent bg-surface-2":"border-border-subtle"),children:[(0,r.jsxs)("header",{className:"flex items-baseline justify-between",children:[(0,r.jsx)("h2",{className:"text-heading font-medium text-text-primary",children:t.label}),(0,r.jsx)("span",{className:"text-caption text-text-tertiary",children:a.length})]}),(0,r.jsx)("p",{className:"text-caption text-text-tertiary",children:t.description}),(0,r.jsxs)("div",{className:"mt-1 flex flex-col gap-2",children:[a.map(e=>(0,r.jsx)(f,{issue:e,onOpen:n},e.id)),0===a.length&&(0,r.jsx)("div",{className:"rounded-md border border-dashed border-border-subtle py-6 text-center text-caption text-text-tertiary",children:"Empty"})]})]})}function f(e){let{issue:t,onOpen:a}=e,{attributes:n,listeners:i,setNodeRef:l,isDragging:c,transform:d}=(0,s.O1)({id:t.id}),u=d?{transform:"translate3d(".concat(d.x,"px, ").concat(d.y,"px, 0)"),zIndex:30}:{};return(0,r.jsxs)("article",{ref:l,style:u,...n,...i,onClick:e=>{d||(e.stopPropagation(),a(t.id))},className:"group cursor-grab rounded-md border bg-surface-0 p-3 text-left active:cursor-grabbing ".concat(c?"border-accent shadow-lg":"border-border-subtle hover:border-border-default"),children:[(0,r.jsxs)("div",{className:"flex items-start justify-between gap-2",children:[(0,r.jsx)("span",{className:"font-mono text-caption text-text-tertiary",children:t.id}),(0,r.jsx)(o,{severity:t.severity})]}),(0,r.jsx)("h3",{className:"mt-1 text-body font-medium text-text-primary line-clamp-2",children:t.title}),(0,r.jsxs)("div",{className:"mt-2 flex flex-wrap items-center gap-1.5 text-caption",children:[(0,r.jsx)(b,{source:t.source}),t.github_synced&&(0,r.jsxs)("a",{href:t.github_issue_url,target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),className:"rounded-md bg-surface-2 px-1.5 py-0.5 text-text-secondary hover:bg-surface-3 hover:text-text-primary",children:["GH ","closed"===t.github_state?"✕":"✓"]}),t.duplicate_of&&(0,r.jsxs)("span",{className:"rounded-md bg-surface-2 px-1.5 py-0.5 text-text-secondary",children:["dup of ",t.duplicate_of]}),"passing"===t.poc_status&&(0,r.jsx)("span",{className:"rounded-md bg-[var(--low)]/15 px-1.5 py-0.5 text-[var(--low)]",children:"PoC ✓"}),"failing"===t.poc_status&&(0,r.jsx)("span",{className:"rounded-md bg-[var(--critical)]/15 px-1.5 py-0.5 text-[var(--critical)]",children:"PoC ✕"}),t.resolution&&(0,r.jsx)("span",{className:"rounded-md bg-surface-2 px-1.5 py-0.5 text-text-secondary",children:t.resolution})]}),t.match_signals&&(0,r.jsx)("div",{className:"mt-2 text-caption text-text-tertiary",children:(0,r.jsx)("span",{className:"font-mono",children:[t.match_signals.contract?"contract":null,t.match_signals.function?"function":null,"different"!==t.match_signals.root_cause?"rc:".concat(t.match_signals.root_cause):null,"different"!==t.match_signals.attack_vector?"av:".concat(t.match_signals.attack_vector):null].filter(Boolean).join(" \xb7 ")})})]})}function b(e){var t;let{source:a}=e;return(0,r.jsx)("span",{className:"rounded-md bg-surface-2 px-1.5 py-0.5 text-text-tertiary",children:null!==(t=({manual:"Manual",auditagent:"AuditAgent",conformance:"Conformance",github:"GitHub"})[a])&&void 0!==t?t:a})}function h(e){var t,a,s;let{issue:i,onClose:o,onSave:l}=e,[c,x]=(0,n.useState)(i.title),[m,p]=(0,n.useState)(i.severity),[f,h]=(0,n.useState)(i.description),[g,y]=(0,n.useState)(i.recommendation),[j,N]=(0,n.useState)(null!==(t=i.resolution)&&void 0!==t?t:""),[w,_]=(0,n.useState)(null!==(a=i.update_from_client)&&void 0!==a?a:""),[C,k]=(0,n.useState)(!1),S=(0,n.useRef)(null);(0,n.useEffect)(()=>{var e;null===(e=S.current)||void 0===e||e.focus()},[]);let M=async()=>{k(!0),await l({title:c,severity:m,description:f,recommendation:g,resolution:j||void 0,update_from_client:w||void 0}),k(!1),o()};return(0,r.jsx)("div",{ref:S,tabIndex:-1,className:"fixed inset-0 z-50 flex items-center justify-center p-4 outline-none",style:{background:"var(--overlay-bg)"},onClick:o,onKeyDown:e=>{"Escape"===e.key&&o()},children:(0,r.jsxs)("div",{role:"dialog","aria-modal":"true","aria-label":"Edit issue ".concat(i.id),className:"relative flex max-h-[90vh] w-full max-w-2xl flex-col overflow-hidden border border-border-emphasis bg-surface-1 shadow-2xl",style:{borderRadius:"var(--radius-md)",boxShadow:"var(--shadow-accent)"},onClick:e=>e.stopPropagation(),children:[(0,r.jsxs)("div",{className:"flex items-center justify-between border-b border-border-default bg-surface-2 px-4 py-3",children:[(0,r.jsxs)("div",{className:"flex items-center gap-2",children:[(0,r.jsx)("span",{className:"font-mono text-body text-text-tertiary",children:i.id}),(0,r.jsx)(b,{source:i.source}),i.github_synced&&i.github_issue_url&&(0,r.jsx)("a",{href:i.github_issue_url,target:"_blank",rel:"noopener noreferrer",className:"rounded-md bg-surface-3 px-1.5 py-0.5 text-caption text-text-secondary hover:text-text-primary",children:"GitHub issue"})]}),(0,r.jsx)("button",{type:"button",onClick:o,"aria-label":"Close",className:"rounded p-1 text-text-tertiary hover:bg-surface-3 hover:text-text-primary",children:(0,r.jsx)("svg",{className:"h-5 w-5","aria-hidden":"true",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M6 18L18 6M6 6l12 12"})})})]}),(0,r.jsxs)("div",{className:"flex-1 overflow-y-auto p-4",children:[(0,r.jsx)(v,{label:"Title",children:(0,r.jsx)("input",{type:"text",value:c,onChange:e=>x(e.target.value),className:"w-full rounded-md border border-border-default bg-surface-0 px-3 py-2 text-body text-text-primary focus:border-accent focus:outline-none"})}),(0,r.jsxs)("div",{className:"mt-3 grid grid-cols-2 gap-3",children:[(0,r.jsx)(v,{label:"Severity",children:(0,r.jsx)("select",{value:m,onChange:e=>p(e.target.value),className:"w-full rounded-md border border-border-default bg-surface-0 px-3 py-2 text-body text-text-primary focus:border-accent focus:outline-none",children:d.map(e=>(0,r.jsx)("option",{value:e,children:e},e))})}),(0,r.jsx)(v,{label:"Resolution (verified only)",children:(0,r.jsxs)("select",{value:j,onChange:e=>N(e.target.value),className:"w-full rounded-md border border-border-default bg-surface-0 px-3 py-2 text-body text-text-primary focus:border-accent focus:outline-none",children:[(0,r.jsx)("option",{value:"",children:"— none —"}),u.map(e=>(0,r.jsx)("option",{value:e,children:e},e))]})})]}),(0,r.jsx)(v,{label:"Description",className:"mt-3",children:(0,r.jsx)("textarea",{value:f,onChange:e=>h(e.target.value),rows:8,className:"w-full rounded-md border border-border-default bg-surface-0 px-3 py-2 font-mono text-caption text-text-primary focus:border-accent focus:outline-none"})}),(0,r.jsx)(v,{label:"Recommendation",className:"mt-3",children:(0,r.jsx)("textarea",{value:g,onChange:e=>y(e.target.value),rows:4,className:"w-full rounded-md border border-border-default bg-surface-0 px-3 py-2 font-mono text-caption text-text-primary focus:border-accent focus:outline-none"})}),(0,r.jsx)(v,{label:"Update from the client",className:"mt-3",children:(0,r.jsx)("textarea",{value:w,onChange:e=>_(e.target.value),rows:4,placeholder:"How the client responded — used by /generate-overleaf.",className:"w-full rounded-md border border-border-default bg-surface-0 px-3 py-2 font-mono text-caption text-text-primary focus:border-accent focus:outline-none"})}),i.code_locations.length>0&&(0,r.jsxs)("div",{className:"mt-4",children:[(0,r.jsx)("div",{className:"mb-1 text-caption font-medium text-text-tertiary",children:"Code locations"}),(0,r.jsx)("ul",{className:"space-y-1 font-mono text-caption text-text-secondary",children:i.code_locations.map((e,t)=>(0,r.jsx)("li",{children:e.file},"".concat(e.file,"-").concat(t)))})]}),i.match_signals&&(0,r.jsxs)("div",{className:"mt-4",children:[(0,r.jsx)("div",{className:"mb-1 text-caption font-medium text-text-tertiary",children:"Duplicate signals"}),(0,r.jsx)("p",{className:"text-caption text-text-secondary",children:null!==(s=i.reasoning)&&void 0!==s?s:"Matched against another entry."}),(0,r.jsxs)("div",{className:"mt-1 flex flex-wrap gap-1.5 font-mono text-caption text-text-tertiary",children:[(0,r.jsxs)("span",{children:["contract: ",i.match_signals.contract?"✓":"—"]}),(0,r.jsxs)("span",{children:["function: ",i.match_signals.function?"✓":"—"]}),(0,r.jsxs)("span",{children:["root_cause: ",i.match_signals.root_cause]}),(0,r.jsxs)("span",{children:["attack_vector: ",i.match_signals.attack_vector]})]})]})]}),(0,r.jsxs)("div",{className:"flex items-center justify-end gap-2 border-t border-border-default bg-surface-2 px-4 py-3",children:[(0,r.jsx)("button",{type:"button",onClick:o,className:"rounded-md px-3 py-1.5 text-body text-text-secondary hover:text-text-primary",children:"Cancel"}),(0,r.jsx)("button",{type:"button",onClick:M,disabled:C,className:"rounded-md bg-accent px-3 py-1.5 text-body font-medium text-surface-0 hover:bg-accent-secondary disabled:opacity-50",children:C?"Saving…":"Save"})]})]})})}function v(e){let{label:t,className:a,children:n}=e;return(0,r.jsxs)("label",{className:"block ".concat(null!=a?a:""),children:[(0,r.jsx)("span",{className:"mb-1 block text-caption font-medium text-text-tertiary",children:t}),n]})}}},function(e){e.O(0,[3924,7115,3205,1744],function(){return e(e.s=4260)}),_N_E=e.O()}]);
|