hex-audit 0.4.5 → 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 +12 -11
- package/README.md +38 -37
- package/dashboard/.next/BUILD_ID +1 -1
- package/dashboard/.next/app-build-manifest.json +13 -13
- package/dashboard/.next/app-path-routes-manifest.json +1 -1
- package/dashboard/.next/build-manifest.json +2 -2
- package/dashboard/.next/next-server.js.nft.json +1 -1
- package/dashboard/.next/prerender-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/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 +2 -2
- package/dashboard/.next/server/app/issues/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/overleaf/page_client-reference-manifest.js +1 -1
- 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 +7 -7
- 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-cde7d75ea6df4f81.js → layout-9c3c84993dd895f6.js} +1 -1
- package/dashboard/.next/static/css/{36c6bb859d4545fd.css → 9c84313d0484b74f.css} +1 -1
- package/dashboard/app/issues/IssuesBoardClient.tsx +55 -24
- package/dashboard/app/issues/page.tsx +6 -3
- package/dashboard/components/DashboardLayout.tsx +1 -1
- package/dashboard/lib/finding-markdown.ts +53 -2
- 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.d.ts.map +1 -1
- package/dist/commands/init.js +2 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/issue.d.ts.map +1 -1
- package/dist/commands/issue.js +85 -5
- package/dist/commands/issue.js.map +1 -1
- 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 +49 -12
- package/dist/core/issues.d.ts.map +1 -1
- package/dist/core/issues.js +174 -24
- package/dist/core/issues.js.map +1 -1
- 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 +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 -20
- package/dist/skills/sync-issues.md +87 -179
- package/dist/skills/validate-all-findings.md +50 -0
- package/dist/skills/validate-issue.md +3 -3
- package/dist/skills/write-finding.md +23 -28
- package/package.json +1 -1
- package/dashboard/.next/static/chunks/app/issues/page-30f7bcde0f4f6ddb.js +0 -1
- /package/dashboard/.next/static/{4HWoeGmBSZi6iGcKL3VWA → NrFPHIFHWQ5jNF62Px_X8}/_buildManifest.js +0 -0
- /package/dashboard/.next/static/{4HWoeGmBSZi6iGcKL3VWA → NrFPHIFHWQ5jNF62Px_X8}/_ssgManifest.js +0 -0
|
@@ -1,249 +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
|
-
##
|
|
31
|
-
|
|
32
|
-
**0a. `gh` installed and authenticated?** Run `gh auth status`. If non-zero exit, print:
|
|
19
|
+
## Context
|
|
33
20
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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).
|
|
37
24
|
|
|
38
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
For each issue whose `updatedAt` is newer than the prior `last_synced_at`, fetch comments:
|
|
42
|
+
**1b. Parse each issue.** Title is `[Severity] Title`. Body is the five-field template:
|
|
58
43
|
|
|
59
|
-
```
|
|
60
|
-
|
|
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
73
|
|
|
147
|
-
|
|
74
|
+
Either way the card lands in the **Synced** column, locked. Reconcile strictly by `#N`; never scan the body for an identifier.
|
|
148
75
|
|
|
149
|
-
## Phase
|
|
76
|
+
## Phase 1.5 — Dedup before push (GitHub is canonical)
|
|
150
77
|
|
|
151
|
-
|
|
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:
|
|
152
79
|
|
|
153
|
-
|
|
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 |
|
|
154
86
|
|
|
155
|
-
|
|
156
|
-
- Has `github.issue_number` → update via `gh issue edit`.
|
|
157
|
-
- No `github.issue_number` → create via `gh issue create`.
|
|
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`.
|
|
158
88
|
|
|
159
|
-
|
|
89
|
+
On match — the synced (on-GitHub) finding is canonical, so the local verified copy becomes the duplicate. Do **not** push it:
|
|
160
90
|
|
|
161
|
-
```
|
|
162
|
-
|
|
91
|
+
```bash
|
|
92
|
+
npx hex issue move <verified-H-id> --to duplicate --duplicate-of <synced-H-id>
|
|
93
|
+
```
|
|
163
94
|
|
|
164
|
-
|
|
95
|
+
Then append to `<output_dir>/comparison.json` under `duplicates` (the board renders the chip + signals from this):
|
|
165
96
|
|
|
166
|
-
|
|
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>" }
|
|
101
|
+
```
|
|
167
102
|
|
|
168
|
-
|
|
103
|
+
If `comparison.json` doesn't exist, create it with `{ "duplicates": [], "novel": [], "rejected": [] }`.
|
|
169
104
|
|
|
170
|
-
|
|
105
|
+
## Phase 2 — Push (verified local findings → GitHub)
|
|
171
106
|
|
|
172
|
-
|
|
173
|
-
```
|
|
107
|
+
Skip if `push_enabled` is false.
|
|
174
108
|
|
|
175
|
-
|
|
176
|
-
- **File(s)** — one markdown link per affected file in `root_cause.locations[]`, comma-separated when there is more than one: `` [`src/Vault.sol`](url1), [`src/MockToken.sol`](url2) ``. Build each URL as `<settings.report.repository_url>/blob/<settings.report.initial_commit>/<file>`. If either `repository_url` or `initial_commit` is missing from `config.json`, use an empty link target: `` [`<file>`]() ``.
|
|
177
|
-
- **Status** — the finding's `resolution` field if set, otherwise the literal `Unresolved`.
|
|
178
|
-
- **Update from the client** — the finding's `update_from_client` field if set, otherwise leave it blank after the colon.
|
|
179
|
-
- Do NOT add a `**Severity:**` line (severity is already in the title), an `### Affected code` section, code blocks, or a "Synced from Hex" attribution line.
|
|
180
|
-
- The `<!-- hex-finding-id: <finding.id> -->` HTML comment at the very bottom is **mandatory and load-bearing** — it is the round-trip dedup key, invisible in GitHub's rendered view. Never omit it.
|
|
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.)
|
|
181
110
|
|
|
182
|
-
|
|
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.**
|
|
183
112
|
|
|
184
|
-
Create:
|
|
185
113
|
```bash
|
|
186
114
|
gh issue create --repo <repo> --title "[<Severity>] <title>" --body-file <tmp>
|
|
187
115
|
```
|
|
188
116
|
|
|
189
|
-
Parse the returned URL
|
|
117
|
+
Parse the returned URL for `#N`, then lock it into Synced and record the number:
|
|
190
118
|
|
|
191
|
-
Update existing:
|
|
192
119
|
```bash
|
|
193
|
-
|
|
120
|
+
npx hex issue sync-set <H-id> --issue-number N --issue-url <url> --state open
|
|
194
121
|
```
|
|
195
122
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
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.
|
|
199
|
-
|
|
200
|
-
**3e. Persist** `findings.json` once after all pushes, atomically.
|
|
123
|
+
From now on the finding is identified by `#N`; the next pull matches it by number, so it never duplicates.
|
|
201
124
|
|
|
202
|
-
|
|
125
|
+
If a single `gh` call fails, log the error and continue with the next candidate — never abort the whole sync.
|
|
203
126
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
```json
|
|
207
|
-
{
|
|
208
|
-
"repo": "<repo>",
|
|
209
|
-
"last_synced_at": "<now>",
|
|
210
|
-
"pushed": <new-created>,
|
|
211
|
-
"updated": <existing-updated>,
|
|
212
|
-
"pulled": <total-issues-seen>,
|
|
213
|
-
"teammate_findings": <count-of-footer-less>,
|
|
214
|
-
"duplicates_detected": <local-entries-flipped-to-duplicate>,
|
|
215
|
-
"errors": [<one-line-per-error>]
|
|
216
|
-
}
|
|
217
|
-
```
|
|
127
|
+
## Phase 3 — Status report
|
|
218
128
|
|
|
219
129
|
Print:
|
|
220
130
|
|
|
221
131
|
```
|
|
222
132
|
GitHub sync complete (<repo>)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
<if errors:>
|
|
227
|
-
Errors:
|
|
228
|
-
- <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>
|
|
229
136
|
```
|
|
230
137
|
|
|
231
|
-
## Conflict resolution
|
|
138
|
+
## Conflict resolution
|
|
232
139
|
|
|
233
|
-
- **
|
|
234
|
-
- **State (open/closed)** —
|
|
235
|
-
- **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.
|
|
236
143
|
|
|
237
144
|
## Error handling
|
|
238
145
|
|
|
239
|
-
- Per-issue errors are isolated — log
|
|
240
|
-
- Rate limit (403
|
|
241
|
-
- 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.
|
|
242
149
|
|
|
243
150
|
## What this skill does NOT do
|
|
244
151
|
|
|
245
|
-
-
|
|
246
|
-
-
|
|
247
|
-
-
|
|
248
|
-
-
|
|
249
|
-
-
|
|
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.
|
|
@@ -95,14 +95,14 @@ For conformance items, the "claim" is `spec_text` + the finding paragraph — i.
|
|
|
95
95
|
|
|
96
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.
|
|
97
97
|
|
|
98
|
-
|
|
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
99
|
|
|
100
|
-
|
|
100
|
+
5. Use **AskUserQuestion** to ask:
|
|
101
101
|
> Issue verified as **\<id\>** with severity **\<current severity\>**.
|
|
102
102
|
>
|
|
103
103
|
> Would you like to change the severity? Reply with the new severity (Critical / High / Medium / Low / Info), or **no** to keep it.
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
6. If the user provides a new severity, run `npx hex issue patch <id> --severity <NewSeverity>`.
|
|
106
106
|
|
|
107
107
|
**Invalid:**
|
|
108
108
|
|
|
@@ -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
|
-
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[4521],{4260:function(e,t,a){Promise.resolve().then(a.bind(a,7063))},7063:function(e,t,a){"use strict";a.d(t,{IssuesBoardClient:function(){return x}});var r=a(7821),n=a(8078),o=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 s(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,formatCtx:i={}}=e,[s,d]=(0,n.useState)(a),[u,x]=(0,n.useState)(null),[f,b]=(0,n.useState)("all");(0,n.useEffect)(()=>{d(a)},[a]);let h=(0,o.Dy)((0,o.VT)(o.we,{activationConstraint:{distance:4}})),g=(0,n.useMemo)(()=>"all"===f?s:s.filter(e=>e.source===f),[s,f]),y=(0,n.useMemo)(()=>{let e={potential:[],verified:[],invalid:[],duplicate:[]};for(let t of g)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},[g]),j=(0,n.useMemo)(()=>{let e={potential:0,verified:0,invalid:0,duplicate:0};for(let t of s)e[t.column]++;return e},[s]),N=(0,n.useCallback)(async(e,t)=>{d(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),d(t=>t.map(t=>{var r;return t.id===e&&null!==(r=a.find(t=>t.id===e))&&void 0!==r?r:t}))}},[a]),w=(0,n.useCallback)(async(e,t)=>{d(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)}},[]),_=null!==(t=s.find(e=>e.id===u))&&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:[s.length," total — ",j.potential," potential, ",j.verified," verified,"," ",j.invalid," invalid, ",j.duplicate," duplicate. Drag a card to change column; click to edit."]})]}),(0,r.jsx)(m,{value:f,onChange:b})]}),(0,r.jsx)(o.LB,{sensors:h,onDragEnd:e=>{let{active:t,over:a}=e;if(!a)return;let r=a.id,n=t.id,o=s.find(e=>e.id===n);o&&o.column!==r&&N(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:y[e.id],onOpen:x},e.id))})}),_&&(0,r.jsx)(v,{issue:_,formatCtx:i,onClose:()=>x(null),onSave:e=>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:s}=(0,o.Zj)({id:t.id});return(0,r.jsxs)("section",{ref:s,"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,o.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)(s,{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}),(0,r.jsx)(h,{issue:t}),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;let{issue:n}=e,o=null!==(t=n.sync_state)&&void 0!==t?t:"none";if("none"===o)return null;if("unsynced"===o)return(0,r.jsx)("span",{title:"Run /sync-issues to push verified findings to GitHub.",className:"rounded-md border border-dashed border-border-default px-1.5 py-0.5 text-text-tertiary",children:"GH unsynced"});if("conflict"===o)return(0,r.jsx)("span",{title:"Local and remote diverged. Re-run /sync-issues to reconcile.",className:"rounded-md bg-[var(--critical)]/15 px-1.5 py-0.5 text-[var(--critical)]",children:"GH conflict"});let i="synced_closed"===o,s="GH #".concat(null!==(a=n.github_issue_number)&&void 0!==a?a:"?"," ").concat(i?"closed":"open"),l=i?"bg-surface-2 text-text-tertiary":"bg-[var(--low)]/15 text-[var(--low)]";return n.github_issue_url?(0,r.jsx)("a",{href:n.github_issue_url,target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),className:"rounded-md px-1.5 py-0.5 hover:underline ".concat(l),children:s}):(0,r.jsx)("span",{className:"rounded-md px-1.5 py-0.5 ".concat(l),children:s})}function v(e){var t,a,o;let{issue:i,formatCtx:s,onClose:l,onSave:c}=e,[x,m]=(0,n.useState)(i.title),[p,f]=(0,n.useState)(i.severity),[v,y]=(0,n.useState)(i.description),[j,N]=(0,n.useState)(i.recommendation),[w,_]=(0,n.useState)(null!==(t=i.resolution)&&void 0!==t?t:""),[C,k]=(0,n.useState)(null!==(a=i.update_from_client)&&void 0!==a?a:""),[S,M]=(0,n.useState)(!1),[H,E]=(0,n.useState)(!1),I=(0,n.useRef)(null);(0,n.useEffect)(()=>{var e;null===(e=I.current)||void 0===e||e.focus()},[]);let R=async()=>{let e=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return"## [".concat(e.severity,"] ").concat(e.title,"\n\n").concat(function(e,t){var a;let r=new Set,n=e.code_locations.map(e=>e.file).filter(e=>e&&!r.has(e)&&r.add(e)).map(e=>(function(e,t){if(t.repositoryUrl&&t.commit){let a=t.repositoryUrl.replace(/\/+$/,"");return"[`".concat(e,"`](").concat(a,"/blob/").concat(t.commit,"/").concat(e,")")}return"[`".concat(e,"`]()")})(e,t)).join(", "),o=e.resolution||"Unresolved",i=null!==(a=e.update_from_client)&&void 0!==a?a:"";return["**File(s)**: ".concat(n),"","**Description**: ".concat(e.description),"","**Recommendation(s)**: ".concat(e.recommendation),"","**Status**: ".concat(o),"","**Update from the client**: ".concat(i)].join("\n")}(e,t),"\n")}({...i,title:x,severity:p,description:v,recommendation:j,resolution:w||void 0,update_from_client:C||void 0},s);try{await navigator.clipboard.writeText(e),E(!0),setTimeout(()=>E(!1),1500)}catch(e){}},D=async()=>{M(!0),await c({title:x,severity:p,description:v,recommendation:j,resolution:w||void 0,update_from_client:C||void 0}),M(!1),l()};return(0,r.jsx)("div",{ref:I,tabIndex:-1,className:"fixed inset-0 z-50 flex items-center justify-center p-4 outline-none",style:{background:"var(--overlay-bg)"},onClick:l,onKeyDown:e=>{"Escape"===e.key&&l()},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 text-caption",children:[(0,r.jsx)("span",{className:"font-mono text-body text-text-tertiary",children:i.id}),(0,r.jsx)(b,{source:i.source}),(0,r.jsx)(h,{issue:i})]}),(0,r.jsx)("button",{type:"button",onClick:l,"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)(g,{label:"Title",children:(0,r.jsx)("input",{type:"text",value:x,onChange:e=>m(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)(g,{label:"Severity",children:(0,r.jsx)("select",{value:p,onChange:e=>f(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)(g,{label:"Resolution (verified only)",children:(0,r.jsxs)("select",{value:w,onChange:e=>_(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)(g,{label:"Description",className:"mt-3",children:(0,r.jsx)("textarea",{value:v,onChange:e=>y(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)(g,{label:"Recommendation",className:"mt-3",children:(0,r.jsx)("textarea",{value:j,onChange:e=>N(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)(g,{label:"Update from the client",className:"mt-3",children:(0,r.jsx)("textarea",{value:C,onChange:e=>k(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!==(o=i.reasoning)&&void 0!==o?o:"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-between gap-2 border-t border-border-default bg-surface-2 px-4 py-3",children:[(0,r.jsx)("button",{type:"button",onClick:R,className:"rounded-md border border-border-default px-3 py-1.5 text-body text-text-secondary hover:bg-surface-3 hover:text-text-primary",children:H?"Copied!":"Copy as HackMD"}),(0,r.jsxs)("div",{className:"flex items-center gap-2",children:[(0,r.jsx)("button",{type:"button",onClick:l,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:D,disabled:S,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:S?"Saving…":"Save"})]})]})]})})}function g(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()}]);
|
/package/dashboard/.next/static/{4HWoeGmBSZi6iGcKL3VWA → NrFPHIFHWQ5jNF62Px_X8}/_buildManifest.js
RENAMED
|
File without changes
|
/package/dashboard/.next/static/{4HWoeGmBSZi6iGcKL3VWA → NrFPHIFHWQ5jNF62Px_X8}/_ssgManifest.js
RENAMED
|
File without changes
|