hex-audit 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/dashboard/.next/BUILD_ID +1 -1
  2. package/dashboard/.next/app-build-manifest.json +20 -20
  3. package/dashboard/.next/app-path-routes-manifest.json +1 -1
  4. package/dashboard/.next/build-manifest.json +2 -2
  5. package/dashboard/.next/next-minimal-server.js.nft.json +1 -1
  6. package/dashboard/.next/next-server.js.nft.json +1 -1
  7. package/dashboard/.next/prerender-manifest.js +1 -1
  8. package/dashboard/.next/prerender-manifest.json +1 -1
  9. package/dashboard/.next/server/app/_not-found/page.js +1 -1
  10. package/dashboard/.next/server/app/_not-found/page.js.nft.json +1 -1
  11. package/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/dashboard/.next/server/app/_not-found.html +1 -1
  13. package/dashboard/.next/server/app/_not-found.rsc +4 -4
  14. package/dashboard/.next/server/app/access/page.js +1 -1
  15. package/dashboard/.next/server/app/access/page.js.nft.json +1 -1
  16. package/dashboard/.next/server/app/access/page_client-reference-manifest.js +1 -1
  17. package/dashboard/.next/server/app/api/flows/route.js +1 -1
  18. package/dashboard/.next/server/app/api/github-status/route.js +1 -1
  19. package/dashboard/.next/server/app/api/issues/[id]/move/route.js +1 -1
  20. package/dashboard/.next/server/app/api/issues/[id]/move/route.js.nft.json +1 -1
  21. package/dashboard/.next/server/app/api/issues/[id]/route.js +1 -1
  22. package/dashboard/.next/server/app/api/issues/[id]/route.js.nft.json +1 -1
  23. package/dashboard/.next/server/app/api/mermaid/route.js +1 -1
  24. package/dashboard/.next/server/app/api/progress/route.js +1 -1
  25. package/dashboard/.next/server/app/api/watch/route.js +2 -2
  26. package/dashboard/.next/server/app/calls/page.js +1 -1
  27. package/dashboard/.next/server/app/calls/page.js.nft.json +1 -1
  28. package/dashboard/.next/server/app/calls/page_client-reference-manifest.js +1 -1
  29. package/dashboard/.next/server/app/conformance/page.js +1 -1
  30. package/dashboard/.next/server/app/conformance/page.js.nft.json +1 -1
  31. package/dashboard/.next/server/app/conformance/page_client-reference-manifest.js +1 -1
  32. package/dashboard/.next/server/app/diagram/page.js +1 -1
  33. package/dashboard/.next/server/app/diagram/page.js.nft.json +1 -1
  34. package/dashboard/.next/server/app/diagram/page_client-reference-manifest.js +1 -1
  35. package/dashboard/.next/server/app/diagram.html +1 -1
  36. package/dashboard/.next/server/app/diagram.rsc +4 -4
  37. package/dashboard/.next/server/app/flows/page.js +1 -1
  38. package/dashboard/.next/server/app/flows/page.js.nft.json +1 -1
  39. package/dashboard/.next/server/app/flows/page_client-reference-manifest.js +1 -1
  40. package/dashboard/.next/server/app/flows.html +1 -1
  41. package/dashboard/.next/server/app/flows.rsc +4 -4
  42. package/dashboard/.next/server/app/functions/page.js +1 -1
  43. package/dashboard/.next/server/app/functions/page.js.nft.json +1 -1
  44. package/dashboard/.next/server/app/functions/page_client-reference-manifest.js +1 -1
  45. package/dashboard/.next/server/app/issues/page.js +5 -3
  46. package/dashboard/.next/server/app/issues/page.js.nft.json +1 -1
  47. package/dashboard/.next/server/app/issues/page_client-reference-manifest.js +1 -1
  48. package/dashboard/.next/server/app/overleaf/page.js +1 -1
  49. package/dashboard/.next/server/app/overleaf/page.js.nft.json +1 -1
  50. package/dashboard/.next/server/app/overleaf/page_client-reference-manifest.js +1 -1
  51. package/dashboard/.next/server/app/page.js +2 -2
  52. package/dashboard/.next/server/app/page.js.nft.json +1 -1
  53. package/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  54. package/dashboard/.next/server/app/progress/page.js +1 -1
  55. package/dashboard/.next/server/app/progress/page.js.nft.json +1 -1
  56. package/dashboard/.next/server/app/progress/page_client-reference-manifest.js +1 -1
  57. package/dashboard/.next/server/app/stats/page.js +1 -1
  58. package/dashboard/.next/server/app/stats/page.js.nft.json +1 -1
  59. package/dashboard/.next/server/app/stats/page_client-reference-manifest.js +1 -1
  60. package/dashboard/.next/server/app-paths-manifest.json +6 -6
  61. package/dashboard/.next/server/chunks/42.js +1 -0
  62. package/dashboard/.next/server/chunks/44.js +1 -0
  63. package/dashboard/.next/server/chunks/{431.js → 530.js} +1 -1
  64. package/dashboard/.next/server/functions-config-manifest.json +1 -1
  65. package/dashboard/.next/server/middleware-build-manifest.js +1 -1
  66. package/dashboard/.next/server/pages/404.html +1 -1
  67. package/dashboard/.next/server/pages/500.html +1 -1
  68. package/dashboard/.next/server/server-reference-manifest.js +1 -1
  69. package/dashboard/.next/server/server-reference-manifest.json +1 -1
  70. package/dashboard/.next/static/chunks/app/issues/{page-2bb52a36f20497fe.js → page-6bfbf80884d3c9d8.js} +1 -1
  71. package/dashboard/.next/static/chunks/app/{layout-9c3c84993dd895f6.js → layout-23765c356f43208c.js} +1 -1
  72. package/dashboard/app/layout.tsx +2 -1
  73. package/dashboard/components/DashboardLayout.tsx +8 -2
  74. package/dashboard/lib/data.ts +11 -19
  75. package/dashboard/lib/finding-markdown.ts +42 -3
  76. package/dist/commands/access.d.ts.map +1 -1
  77. package/dist/commands/access.js +3 -4
  78. package/dist/commands/access.js.map +1 -1
  79. package/dist/commands/ai-status.d.ts.map +1 -1
  80. package/dist/commands/ai-status.js +8 -10
  81. package/dist/commands/ai-status.js.map +1 -1
  82. package/dist/commands/calls.d.ts.map +1 -1
  83. package/dist/commands/calls.js +3 -4
  84. package/dist/commands/calls.js.map +1 -1
  85. package/dist/commands/constraints.d.ts.map +1 -1
  86. package/dist/commands/constraints.js +2 -4
  87. package/dist/commands/constraints.js.map +1 -1
  88. package/dist/commands/context.d.ts.map +1 -1
  89. package/dist/commands/context.js +2 -4
  90. package/dist/commands/context.js.map +1 -1
  91. package/dist/commands/dashboard.d.ts.map +1 -1
  92. package/dist/commands/dashboard.js +19 -12
  93. package/dist/commands/dashboard.js.map +1 -1
  94. package/dist/commands/deps.d.ts.map +1 -1
  95. package/dist/commands/deps.js +2 -4
  96. package/dist/commands/deps.js.map +1 -1
  97. package/dist/commands/issue.d.ts.map +1 -1
  98. package/dist/commands/issue.js +12 -6
  99. package/dist/commands/issue.js.map +1 -1
  100. package/dist/commands/note.d.ts +3 -0
  101. package/dist/commands/note.d.ts.map +1 -0
  102. package/dist/commands/note.js +145 -0
  103. package/dist/commands/note.js.map +1 -0
  104. package/dist/commands/patterns.d.ts.map +1 -1
  105. package/dist/commands/patterns.js +2 -4
  106. package/dist/commands/patterns.js.map +1 -1
  107. package/dist/commands/state.d.ts.map +1 -1
  108. package/dist/commands/state.js +3 -4
  109. package/dist/commands/state.js.map +1 -1
  110. package/dist/commands/stats.d.ts.map +1 -1
  111. package/dist/commands/stats.js +3 -4
  112. package/dist/commands/stats.js.map +1 -1
  113. package/dist/commands/surface.d.ts.map +1 -1
  114. package/dist/commands/surface.js +3 -4
  115. package/dist/commands/surface.js.map +1 -1
  116. package/dist/core/config.d.ts +13 -0
  117. package/dist/core/config.d.ts.map +1 -1
  118. package/dist/core/config.js +23 -16
  119. package/dist/core/config.js.map +1 -1
  120. package/dist/core/issues.d.ts +2 -0
  121. package/dist/core/issues.d.ts.map +1 -1
  122. package/dist/core/issues.js +12 -1
  123. package/dist/core/issues.js.map +1 -1
  124. package/dist/core/locate.d.ts +26 -0
  125. package/dist/core/locate.d.ts.map +1 -0
  126. package/dist/core/locate.js +56 -0
  127. package/dist/core/locate.js.map +1 -0
  128. package/dist/core/notes.d.ts +62 -0
  129. package/dist/core/notes.d.ts.map +1 -0
  130. package/dist/core/notes.js +160 -0
  131. package/dist/core/notes.js.map +1 -0
  132. package/dist/core/whisper.d.ts +24 -0
  133. package/dist/core/whisper.d.ts.map +1 -0
  134. package/dist/core/whisper.js +124 -0
  135. package/dist/core/whisper.js.map +1 -0
  136. package/dist/index.js +6 -1
  137. package/dist/index.js.map +1 -1
  138. package/dist/skills/diane.md +99 -0
  139. package/dist/skills/ingest-aa-report.md +1 -1
  140. package/dist/skills/sync-issues.md +23 -3
  141. package/dist/skills/validate-issue.md +2 -0
  142. package/dist/skills/write-finding.md +15 -5
  143. package/package.json +1 -1
  144. package/dashboard/.next/server/chunks/557.js +0 -1
  145. /package/dashboard/.next/static/{NrFPHIFHWQ5jNF62Px_X8 → QeyYu_4EslVR8FTiyQpNs}/_buildManifest.js +0 -0
  146. /package/dashboard/.next/static/{NrFPHIFHWQ5jNF62Px_X8 → QeyYu_4EslVR8FTiyQpNs}/_ssgManifest.js +0 -0
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Canonical notes-mutation logic for Diane — the voice-narration audit
3
+ * companion. Shared by the `hex note` CLI command, the `/diane` skill (which
4
+ * shells `hex note ...`), and the dashboard's Notes API routes.
5
+ *
6
+ * Notes live under `<output_dir>/notes/`:
7
+ * notes.json index: active contract, contract list, sessions
8
+ * general.md cross-cutting / protocol-wide notes
9
+ * contracts/<Name>.md one Obsidian-style doc per contract
10
+ * sessions/<ts>.md raw transcripts (audit trail; never auto-deleted)
11
+ * audio/<ts>.webm raw recordings (kept until transcribed)
12
+ *
13
+ * Like src/core/issues.ts, this module depends ONLY on node:fs / node:path and
14
+ * is parameterized by `outputDir`, so the dashboard can import it across the
15
+ * src/ <-> dashboard/ boundary via `experimental.externalDir` without dragging
16
+ * CLI deps (chalk/ora/zod) into the Next.js bundle. Keep it that way.
17
+ */
18
+ import fs from 'node:fs';
19
+ import path from 'node:path';
20
+ const EMPTY_INDEX = { active_contract: null, contracts: [], sessions: [] };
21
+ // ─── Paths ──────────────────────────────────────────────────────────
22
+ function notesDir(outputDir) {
23
+ return path.join(outputDir, 'notes');
24
+ }
25
+ /** Filesystem-safe doc filename from a contract display name. */
26
+ function safeName(name) {
27
+ return name.replace(/[/\\:*?"<>|]/g, '_').trim();
28
+ }
29
+ /** Resolve a note target (`general` or a contract name) to its `.md` path. */
30
+ export function notePath(outputDir, target) {
31
+ if (target === 'general')
32
+ return path.join(notesDir(outputDir), 'general.md');
33
+ return path.join(notesDir(outputDir), 'contracts', safeName(target) + '.md');
34
+ }
35
+ // ─── Low-level IO (atomic) ──────────────────────────────────────────
36
+ function readText(file) {
37
+ try {
38
+ return fs.readFileSync(file, 'utf-8');
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ function writeTextAtomic(file, content) {
45
+ const dir = path.dirname(file);
46
+ if (!fs.existsSync(dir))
47
+ fs.mkdirSync(dir, { recursive: true });
48
+ const tmp = file + '.tmp';
49
+ fs.writeFileSync(tmp, content, 'utf-8');
50
+ fs.renameSync(tmp, file);
51
+ }
52
+ // ─── Index ──────────────────────────────────────────────────────────
53
+ export function readNotesIndex(outputDir) {
54
+ const raw = readText(path.join(notesDir(outputDir), 'notes.json'));
55
+ if (!raw)
56
+ return { ...EMPTY_INDEX };
57
+ try {
58
+ const parsed = JSON.parse(raw);
59
+ return {
60
+ active_contract: parsed.active_contract ?? null,
61
+ contracts: Array.isArray(parsed.contracts) ? parsed.contracts : [],
62
+ sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
63
+ };
64
+ }
65
+ catch {
66
+ return { ...EMPTY_INDEX };
67
+ }
68
+ }
69
+ export function writeNotesIndex(outputDir, index) {
70
+ writeTextAtomic(path.join(notesDir(outputDir), 'notes.json'), JSON.stringify(index, null, 2) + '\n');
71
+ }
72
+ /** Ensure a contract is tracked in the index (no-op for `general`). */
73
+ function trackContract(index, target) {
74
+ if (target === 'general')
75
+ return;
76
+ if (!index.contracts.includes(target))
77
+ index.contracts.push(target);
78
+ }
79
+ // ─── Notes ──────────────────────────────────────────────────────────
80
+ /** Read a note doc. Returns '' when it doesn't exist yet. */
81
+ export function readNote(outputDir, target) {
82
+ return readText(notePath(outputDir, target)) ?? '';
83
+ }
84
+ /** Replace a note doc wholesale (used by the dashboard editor's Save). */
85
+ export function writeNote(outputDir, target, body) {
86
+ writeTextAtomic(notePath(outputDir, target), body.endsWith('\n') ? body : body + '\n');
87
+ const index = readNotesIndex(outputDir);
88
+ trackContract(index, target);
89
+ writeNotesIndex(outputDir, index);
90
+ }
91
+ /**
92
+ * Append a block to a note doc, separated by a blank line. This is how `/diane`
93
+ * accretes structured notes across narration sessions.
94
+ */
95
+ export function appendNote(outputDir, target, body) {
96
+ const existing = readNote(outputDir, target);
97
+ const sep = existing.trim() === '' ? '' : (existing.endsWith('\n') ? '\n' : '\n\n');
98
+ writeNote(outputDir, target, existing + sep + body.trim() + '\n');
99
+ }
100
+ export function listContracts(outputDir) {
101
+ return readNotesIndex(outputDir).contracts;
102
+ }
103
+ export function setActiveContract(outputDir, name) {
104
+ const index = readNotesIndex(outputDir);
105
+ index.active_contract = name;
106
+ trackContract(index, name);
107
+ writeNotesIndex(outputDir, index);
108
+ }
109
+ // ─── Sessions ───────────────────────────────────────────────────────
110
+ /**
111
+ * Register a freshly recorded narration session and make its contract active.
112
+ * `file` / `audio` are paths relative to the notes dir.
113
+ */
114
+ export function addSession(outputDir, entry) {
115
+ const index = readNotesIndex(outputDir);
116
+ const session = {
117
+ file: entry.file,
118
+ contract: entry.contract,
119
+ ts: entry.ts,
120
+ processed: false,
121
+ ...(entry.audio ? { audio: entry.audio } : {}),
122
+ };
123
+ index.sessions.push(session);
124
+ index.active_contract = entry.contract;
125
+ trackContract(index, entry.contract);
126
+ writeNotesIndex(outputDir, index);
127
+ return session;
128
+ }
129
+ /** The most recent session Diane hasn't ingested yet, with its transcript. */
130
+ export function latestUnprocessedSession(outputDir) {
131
+ const index = readNotesIndex(outputDir);
132
+ for (let i = index.sessions.length - 1; i >= 0; i--) {
133
+ const s = index.sessions[i];
134
+ if (!s.processed) {
135
+ const text = readText(path.join(notesDir(outputDir), s.file)) ?? '';
136
+ return { ...s, text };
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+ export function markSessionProcessed(outputDir, file) {
142
+ const index = readNotesIndex(outputDir);
143
+ const session = index.sessions.find((s) => s.file === file);
144
+ if (!session)
145
+ throw new Error(`No session with file ${file}`);
146
+ session.processed = true;
147
+ writeNotesIndex(outputDir, index);
148
+ }
149
+ /**
150
+ * Write a transcript into `sessions/<ts>.md` and register it. Returns the
151
+ * session's relative file path. Used by the dashboard record route (after
152
+ * transcription) and by `hex note session add` (for testing without audio).
153
+ */
154
+ export function createSession(outputDir, opts) {
155
+ const rel = path.join('sessions', `${opts.ts}.md`);
156
+ const header = `# Narration — ${opts.contract} — ${opts.ts}\n\n`;
157
+ writeTextAtomic(path.join(notesDir(outputDir), rel), header + opts.transcript.trim() + '\n');
158
+ return addSession(outputDir, { file: rel, contract: opts.contract, ts: opts.ts, audio: opts.audio });
159
+ }
160
+ //# sourceMappingURL=notes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notes.js","sourceRoot":"","sources":["../../src/core/notes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /** Thrown when no local Whisper engine is installed. The caller turns this into
2
+ * a setup hint and preserves the raw audio so no narration is lost. */
3
+ export declare class WhisperMissingError extends Error {
4
+ constructor();
5
+ }
6
+ export type WhisperEngine = {
7
+ kind: 'openai';
8
+ bin: string;
9
+ } | {
10
+ kind: 'faster';
11
+ bin: string;
12
+ } | {
13
+ kind: 'cpp';
14
+ bin: string;
15
+ model: string;
16
+ };
17
+ /** Find an installed engine, or null. */
18
+ export declare function detectWhisper(): WhisperEngine | null;
19
+ /**
20
+ * Transcribe `audioPath` to plain text. Throws WhisperMissingError when no
21
+ * engine is installed; the caller keeps the raw audio and surfaces the hint.
22
+ */
23
+ export declare function transcribe(audioPath: string): string;
24
+ //# sourceMappingURL=whisper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whisper.d.ts","sourceRoot":"","sources":["../../src/core/whisper.ts"],"names":[],"mappings":"AAmBA;wEACwE;AACxE,qBAAa,mBAAoB,SAAQ,KAAK;;CAU7C;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAsBhD,yCAAyC;AACzC,wBAAgB,aAAa,IAAI,aAAa,GAAG,IAAI,CAUpD;AA0BD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAoCpD"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Local speech-to-text for Diane. Records stay on the machine: we shell out to
3
+ * a locally-installed Whisper engine, never a cloud API. Audit confidentiality
4
+ * is the whole point of keeping this on-device.
5
+ *
6
+ * Engines, in detection order (all auto-download their model on first run):
7
+ * 1. openai-whisper (`whisper` CLI, `pip install openai-whisper`)
8
+ * 2. faster-whisper (python module, `pip install faster-whisper`)
9
+ * 3. whisper.cpp (`whisper-cli`/`whisper-cpp`, needs HEX_WHISPER_MODEL=<model.bin>)
10
+ *
11
+ * Like src/core/notes.ts this stays node-builtins-only (child_process / fs /
12
+ * path / os) so the dashboard's record route can import it across the
13
+ * src/ <-> dashboard/ boundary without bundling CLI-only deps.
14
+ */
15
+ import { execFileSync } from 'node:child_process';
16
+ import fs from 'node:fs';
17
+ import os from 'node:os';
18
+ import path from 'node:path';
19
+ /** Thrown when no local Whisper engine is installed. The caller turns this into
20
+ * a setup hint and preserves the raw audio so no narration is lost. */
21
+ export class WhisperMissingError extends Error {
22
+ constructor() {
23
+ super('No local Whisper engine found. Install one (audio stays on your machine):\n' +
24
+ ' • pip install openai-whisper (provides the `whisper` command)\n' +
25
+ ' • pip install faster-whisper (lighter, faster on CPU)\n' +
26
+ ' • whisper.cpp + HEX_WHISPER_MODEL=/path/to/ggml-base.en.bin');
27
+ this.name = 'WhisperMissingError';
28
+ }
29
+ }
30
+ const MODEL = process.env.HEX_WHISPER_MODEL_NAME || 'base.en';
31
+ function commandExists(bin) {
32
+ try {
33
+ execFileSync('sh', ['-c', `command -v ${bin}`], { stdio: 'ignore' });
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ function pythonHasFasterWhisper(bin) {
41
+ try {
42
+ execFileSync(bin, ['-c', 'import faster_whisper'], { stdio: 'ignore' });
43
+ return true;
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ }
49
+ /** Find an installed engine, or null. */
50
+ export function detectWhisper() {
51
+ if (commandExists('whisper'))
52
+ return { kind: 'openai', bin: 'whisper' };
53
+ for (const py of ['python3', 'python']) {
54
+ if (commandExists(py) && pythonHasFasterWhisper(py))
55
+ return { kind: 'faster', bin: py };
56
+ }
57
+ const cppModel = process.env.HEX_WHISPER_MODEL;
58
+ for (const bin of ['whisper-cli', 'whisper-cpp']) {
59
+ if (cppModel && commandExists(bin))
60
+ return { kind: 'cpp', bin, model: cppModel };
61
+ }
62
+ return null;
63
+ }
64
+ function ensureFfmpeg() {
65
+ if (!commandExists('ffmpeg')) {
66
+ throw new Error('ffmpeg is required to convert recordings for transcription, but was not found.');
67
+ }
68
+ }
69
+ /** Convert any recording to 16 kHz mono WAV (what every engine wants). */
70
+ function toWav(audioPath) {
71
+ ensureFfmpeg();
72
+ const wav = path.join(os.tmpdir(), `hex-narration-${path.basename(audioPath)}.wav`);
73
+ execFileSync('ffmpeg', ['-y', '-i', audioPath, '-ar', '16000', '-ac', '1', '-f', 'wav', wav], {
74
+ stdio: 'ignore',
75
+ });
76
+ return wav;
77
+ }
78
+ const FASTER_SCRIPT = `
79
+ import sys
80
+ from faster_whisper import WhisperModel
81
+ model = WhisperModel(sys.argv[2], device="cpu", compute_type="int8")
82
+ segments, _ = model.transcribe(sys.argv[1], language="en")
83
+ sys.stdout.write(" ".join(s.text.strip() for s in segments).strip())
84
+ `;
85
+ /**
86
+ * Transcribe `audioPath` to plain text. Throws WhisperMissingError when no
87
+ * engine is installed; the caller keeps the raw audio and surfaces the hint.
88
+ */
89
+ export function transcribe(audioPath) {
90
+ const engine = detectWhisper();
91
+ if (!engine)
92
+ throw new WhisperMissingError();
93
+ const wav = toWav(audioPath);
94
+ try {
95
+ if (engine.kind === 'openai') {
96
+ const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hex-whisper-'));
97
+ execFileSync(engine.bin, [wav, '--model', MODEL, '--language', 'en', '--output_format', 'txt', '--output_dir', outDir, '--task', 'transcribe'], { stdio: 'ignore' });
98
+ const txt = path.join(outDir, path.basename(wav).replace(/\.wav$/, '') + '.txt');
99
+ return (fs.readFileSync(txt, 'utf-8')).trim();
100
+ }
101
+ if (engine.kind === 'faster') {
102
+ const fasterModel = process.env.HEX_WHISPER_MODEL_NAME || 'base';
103
+ return execFileSync(engine.bin, ['-c', FASTER_SCRIPT, wav, fasterModel], {
104
+ encoding: 'utf-8',
105
+ maxBuffer: 64 * 1024 * 1024,
106
+ }).trim();
107
+ }
108
+ // whisper.cpp
109
+ const outBase = wav.replace(/\.wav$/, '');
110
+ execFileSync(engine.bin, ['-m', engine.model, '-f', wav, '-otxt', '-of', outBase, '-l', 'en'], {
111
+ stdio: 'ignore',
112
+ });
113
+ return fs.readFileSync(outBase + '.txt', 'utf-8').trim();
114
+ }
115
+ finally {
116
+ try {
117
+ fs.unlinkSync(wav);
118
+ }
119
+ catch {
120
+ /* best-effort cleanup */
121
+ }
122
+ }
123
+ }
124
+ //# sourceMappingURL=whisper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whisper.js","sourceRoot":"","sources":["../../src/core/whisper.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
2
3
  import { Command } from 'commander';
3
4
  import { initCommand } from './commands/init.js';
4
5
  import { statsCommand } from './commands/stats.js';
@@ -18,11 +19,15 @@ import { doctorCommand } from './commands/doctor.js';
18
19
  import { aiStatusCommand } from './commands/ai-status.js';
19
20
  import { updateCommand } from './commands/update.js';
20
21
  import { issueCommand } from './commands/issue.js';
22
+ // Single source of truth for the version: package.json (always shipped at the
23
+ // package root, one dir up from dist/index.js). Avoids drift between the npm
24
+ // version and what `hex --version` reports.
25
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
21
26
  const program = new Command();
22
27
  program
23
28
  .name('hex')
24
29
  .description('CLI toolkit for Solidity smart contract auditors')
25
- .version('0.5.0');
30
+ .version(pkg.version);
26
31
  program.addCommand(initCommand);
27
32
  program.addCommand(statsCommand);
28
33
  program.addCommand(depsCommand);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,KAAK,CAAC;KACX,WAAW,CAAC,kDAAkD,CAAC;KAC/D,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;AACvC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACrC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AAEjC,OAAO,CAAC,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,8EAA8E;AAC9E,6EAA6E;AAC7E,4CAA4C;AAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAC3C,CAAC;AAEzB,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,KAAK,CAAC;KACX,WAAW,CAAC,kDAAkD,CAAC;KAC/D,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAExB,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;AACvC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACrC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AAEjC,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,99 @@
1
+ ---
2
+ description: "Diane — your audit recorder. Ingest the latest voice narration into structured per-contract notes, route spoken triggers, and answer questions from the codebase."
3
+ ---
4
+
5
+ # Skill: Diane
6
+
7
+ **Recommended model:** Sonnet
8
+
9
+ You are **Diane**, the auditor's recording companion — the Twin Peaks framing: the auditor is Agent Cooper, narrating their thought process aloud while reading a contract, and you are Diane, who receives the dictation, organizes it into clean notes, and assists. The auditor records narration from the dashboard **Notes** tab (Record button + contract dropdown); the recording is transcribed locally and lands as a *session*. This skill picks up that session and processes it.
10
+
11
+ Persona: attentive, measured, observational. Address the auditor by name occasionally ("Got it, Cooper — logging your read of the withdraw path"). Be concise. Never sycophantic, never narrate your own process at length.
12
+
13
+ ## Step 1 — Pick up the latest narration
14
+
15
+ ```bash
16
+ npx hex note session latest
17
+ ```
18
+
19
+ This prints JSON: `{ file, contract, ts, processed, text }` for the most recent unprocessed session, or `null`. If `null`, tell the auditor there's nothing new to process and stop. Otherwise:
20
+
21
+ - `contract` is the **active contract** the notes belong under (the dropdown selection). `general` means cross-cutting.
22
+ - `text` is the raw transcript — disfluent, with filler, repetition, and false starts. That's expected.
23
+
24
+ ## Step 2 — Segment the transcript into thought-units
25
+
26
+ Read `text` and split it into discrete thoughts. For each, classify it as a **trigger** or a **note**:
27
+
28
+ - A **trigger** is an explicit instruction to you. Recognize these three (match intent, not exact words):
29
+ - **create a finding** — "this is a bug", "write a finding", "flag this", "log an issue".
30
+ - **answer a question** — "how is this encoded?", "is this reachable?", "where is X set?", "what calls this?".
31
+ - **search online** — "look up EIP-x", "search for the spec", "find the reference for…".
32
+ - Everything else is a **note**: observations, hypotheses, things to revisit.
33
+
34
+ When a unit is ambiguous, treat it as a note (the safe default — nothing is lost, and the auditor can re-narrate a real instruction).
35
+
36
+ ## Step 3 — Route the triggers
37
+
38
+ **create a finding** → invoke the existing `/write-finding` skill, prefilled from the narration (the affected contract/function and the auditor's stated impact). Let that skill own duplicate-checking and the board write. After it returns, drop a one-line pointer into the contract note (`- Finding logged: <H-NNN> <title>`).
39
+
40
+ **answer a question** → answer it yourself using codebase access (Read / Grep over the project scope). Be concrete and cite `file:line`. Then log the exchange into the active contract's note:
41
+
42
+ ```
43
+ **Q:** <the question, cleaned up>
44
+ **A:** <your answer, with file:line references>
45
+ ```
46
+
47
+ **search online** → use WebSearch, read the most relevant result, and summarize it into the active contract's note under a short heading, with the source link. Keep it to what's relevant to the audit.
48
+
49
+ ## Step 4 — File everything else as structured notes
50
+
51
+ Turn the remaining thought-units into clean, organized notes. **Clean up** filler, repetition, and false starts — preserve the auditor's reasoning, not their disfluency. Organize under these headings (include only the ones that apply):
52
+
53
+ ```
54
+ ## <ts>
55
+
56
+ ### Observations
57
+ - …
58
+
59
+ ### Questions
60
+ - … (open questions the auditor raised but didn't ask you to answer)
61
+
62
+ ### Risks
63
+ - … (suspected issues not yet ready to be findings)
64
+
65
+ ### TODO
66
+ - …
67
+ ```
68
+
69
+ Write to the active contract's doc (cross-cutting remarks go to `general`). Use a temp file to preserve newlines, then append:
70
+
71
+ ```bash
72
+ cat > /tmp/diane_<ts>.md <<'EOF'
73
+ ## <ts>
74
+
75
+ ### Observations
76
+ - ...
77
+ EOF
78
+ npx hex note append "<contract>" --body-file /tmp/diane_<ts>.md
79
+ ```
80
+
81
+ `<contract>` is the session's `contract` value verbatim (e.g. `Vault.sol`). Q&A blocks and search summaries from Step 3 can be appended in the same block under their own subheadings.
82
+
83
+ ## Step 5 — Close the loop
84
+
85
+ Mark the session processed so it isn't ingested again:
86
+
87
+ ```bash
88
+ npx hex note session mark-processed "<file>"
89
+ ```
90
+
91
+ Then report a one-line summary, Diane-style: what you filed, any findings created, any questions answered. Example:
92
+
93
+ > "Filed 4 observations and 2 risks on Vault.sol, answered your question about `_convertToShares` rounding (it rounds down — see Vault.sol:142), and logged finding H-007. Marked the session processed."
94
+
95
+ ## Notes
96
+
97
+ - **Never hand-edit** `notes.json` or the `.md` files directly — always go through `npx hex note …`, exactly as the other skills route through `npx hex issue …`.
98
+ - One session per invocation. If the auditor wants the next one, they run `/diane` again (or process them in a loop if they ask).
99
+ - Do not create PoCs from narration — that stays an explicit, auditor-driven `/validate-issue` step.
@@ -63,7 +63,7 @@ Parse the AuditAgent output (markdown with severity-tagged sections per finding)
63
63
  "tool": "auditagent",
64
64
  "title": "<finding title>",
65
65
  "severity": "Critical | High | Medium | Low | Info",
66
- "description": "<full description from the report>",
66
+ "description": "<concise description — what it is, why it exists, the impact; trim AuditAgent's verbosity. The verbatim original stays in raw-output.md>",
67
67
  "affected_code": [
68
68
  { "file": "<path>", "snippet": "<relevant code if present>" }
69
69
  ],
@@ -30,6 +30,14 @@ All board mutations go through `hex issue` — never hand-edit `tracking.json` /
30
30
 
31
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`.
32
32
 
33
+ **0d. Fetch the repo's existing labels** — Hex stamps issues it creates, but **never creates a label**, so it can only use labels that already exist:
34
+
35
+ ```bash
36
+ gh label list --repo <repo> --json name --jq '.[].name'
37
+ ```
38
+
39
+ Keep the result as the set `EXISTING`. If the call fails or returns nothing, treat `EXISTING` as empty (issues are then created without labels rather than failing).
40
+
33
41
  ## Phase 1 — Pull (all issues, GitHub → local)
34
42
 
35
43
  **1a. Fetch every issue — no label filter.** Audit repos contain only audit findings, so pull them all:
@@ -108,12 +116,24 @@ Skip if `push_enabled` is false.
108
116
 
109
117
  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.)
110
118
 
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.**
119
+ 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 body footer.**
120
+
121
+ **Labels (creation only).** Stamp each newly-created issue with the standard finding labels, keeping **only those present in `EXISTING`** (Hex never creates a label):
122
+
123
+ - `Draft`
124
+ - `Finding`
125
+ - `Severity: <Severity>` — the finding's severity verbatim (`Critical` / `High` / `Medium` / `Low` / `Info`).
126
+ - `Status: Unresolved` — new findings start Unresolved.
127
+
128
+ Drop any of these four that aren't in `EXISTING`, then pass the survivors as repeated `--label` flags:
112
129
 
113
130
  ```bash
114
- gh issue create --repo <repo> --title "[<Severity>] <title>" --body-file <tmp>
131
+ gh issue create --repo <repo> --title "[<Severity>] <title>" --body-file <tmp> \
132
+ --label "Draft" --label "Finding" --label "Severity: <Severity>" --label "Status: Unresolved"
115
133
  ```
116
134
 
135
+ Stamp labels **only at creation** — never edit labels on an issue that already exists, and never create a new label.
136
+
117
137
  Parse the returned URL for `#N`, then lock it into Synced and record the number:
118
138
 
119
139
  ```bash
@@ -151,7 +171,7 @@ GitHub sync complete (<repo>)
151
171
 
152
172
  - Authenticate (use `gh auth login`).
153
173
  - Post or read comments.
154
- - Apply, create, or remove labels (the repo owner manages labels).
174
+ - Create a new label, or touch labels on issues that already exist. (Hex only stamps the fixed set — `Draft`, `Finding`, `Severity: <X>`, `Status: Unresolved`, filtered to labels already present on the repo — on issues it creates.)
155
175
  - Embed any hidden footer or marker in the issue body.
156
176
  - Delete remote issues.
157
177
  - Push `rejected`, `unverified`, `duplicate`, or already-`synced` findings.
@@ -95,6 +95,8 @@ 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
+ Any description you write must be **flat prose** (plus a fenced code block only when a snippet is genuinely needed) — no markdown headings or header-like section labels (`Summary`/`Details`/`Impact`/`Affected code`); weave impact into the prose. **Keep it concise** — a single tight paragraph covering what the issue is, why it exists, and its impact, plus a 1–2 sentence hedged recommendation; trim anything that doesn't drive the point home. This matches the finding template used by the dashboard copy and the GitHub issue body. If the affected files are missing or wrong, set them with `--file` (repeatable) rather than narrating them in the description: `npx hex issue patch <id> --file src/Foo.sol [--file src/Bar.sol]`.
99
+
98
100
  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
101
 
100
102
  5. Use **AskUserQuestion** to ask:
@@ -59,7 +59,7 @@ The `id`, `poc`, and `created_at` fields are managed by `hex issue new` (see Act
59
59
  "title": "<concise, descriptive title>",
60
60
  "severity": "Critical|High|Medium|Low|Info",
61
61
  "category": "<e.g., Math / Rounding, Access Control, Reentrancy, Oracle Manipulation>",
62
- "description": "<clear, self-contained description covering what the vulnerability is, why it exists, and what the impact would be if exploited>",
62
+ "description": "<flat prose covering what the vulnerability is, why it exists, and its impact — no markdown headings; see Description format below>",
63
63
  "root_cause": {
64
64
  "locations": [
65
65
  { "file": "<path>", "snippet": "<relevant code>" }
@@ -84,10 +84,19 @@ Phrase recommendations as suggestions, not commands. The auditor proposes direct
84
84
 
85
85
  - Start with `Consider...`, `One option is...`, `The team may want to...`, `It would be worth...`.
86
86
  - Avoid `Replace X with Y`, `You must...`, `Always do...`, `Never do...`.
87
- - Keep under three sentences when possible. Detailed analysis belongs in the description.
87
+ - Keep it to 1–2 sentences. Detailed analysis belongs in the description, not here.
88
88
 
89
89
  **Recommendation must be prose only.** No code snippets in the `recommendation` field. Code belongs in `root_cause.locations[].snippet`.
90
90
 
91
+ ### Description format (STRICT)
92
+
93
+ The finding is rendered as a flat template (`## [Severity] Title`, then `**Description**:`, `**Recommendation(s)**:` …). The description goes inline after `**Description**:`, so it must read as **flat prose**:
94
+
95
+ - **No markdown headings and nothing that looks like one** — no `#`/`##`/`###`, and no section labels such as `Summary`, `Details`, `Impact`, `Affected code`, whether as a heading or a bold line on its own. Weave the impact into the prose instead of giving it its own section.
96
+ - **Code blocks are allowed when a snippet is genuinely needed** — a fenced ```` ```solidity ```` block illustrating the bug is fine. Keep it to what's relevant; follow the Code Block Formatting Rules below.
97
+ - Affected file paths belong in `--file` (the File(s) line), not in an "Affected code" section in the description.
98
+ - **Be concise.** Prefer a single tight paragraph: what the issue is, why it exists, and its impact — just enough to drive the point home. Don't restate the title, don't pad with background the reader doesn't need, and don't repeat the recommendation. There's no hard word count; brevity is the goal.
99
+
91
100
  ## Code Block Formatting Rules (STRICT)
92
101
 
93
102
  When including code snippets in the finding:
@@ -112,9 +121,10 @@ All inserted audit comments must be full sentences starting with a capital lette
112
121
 
113
122
  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
123
 
115
- 1. **Create the issue:**
124
+ 1. **Create the issue** — pass `--file` once per affected file so the **File(s)** line is populated (repeatable). Do **not** narrate the affected files in the description instead:
116
125
  ```bash
117
- npx hex issue new --source manual --title "<concise title>" --severity <Severity>
126
+ npx hex issue new --source manual --title "<concise title>" --severity <Severity> \
127
+ --file src/Foo.sol [--file src/Bar.sol]
118
128
  ```
119
129
  This prints the allocated id (e.g. `H-007`). Capture it.
120
130
 
@@ -122,7 +132,7 @@ Persist through the `hex issue` CLI — never hand-edit `findings.json` or `trac
122
132
  ```bash
123
133
  npx hex issue patch <id> --description-file /tmp/<id>_desc.md --recommendation-file /tmp/<id>_reco.md
124
134
  ```
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.
135
+ The description must be self-contained (what the issue is, why it exists, the impact) and follow the **Description format** rules below. The recommendation is prose only, hedged (`Consider...`), no code.
126
136
 
127
137
  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.
128
138
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hex-audit",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "description": "CLI toolkit for Solidity smart contract auditors",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- "use strict";exports.id=557,exports.ids=[557],exports.modules={983:(e,t,r)=>{r.d(t,{v:()=>o});var n=r(9899),s=r(5507),i=r(665),l=r(1492);function o({columns:e,data:t,title:r,defaultOpen:o=!0,rowClassName:a,onRowClick:d,expandedRow:c}){let[u,x]=(0,s.useState)(o),[f,m]=(0,s.useState)([]),[p,h]=(0,s.useState)([]),[g,b]=(0,s.useState)(null),y=(0,s.useMemo)(()=>e.map(e=>({id:e.id,accessorKey:e.accessorKey,header:e.header,enableColumnFilter:e.enableColumnFilter??!1,filterFn:e.enableColumnFilter?"equalsString":void 0,...e.sortingFn?{sortingFn:e.sortingFn}:{},cell:e.cell?({row:t})=>e.cell(t.original):({getValue:e})=>n.jsx("span",{className:"text-text-primary",children:String(e()??"")})})),[e]),v=(0,i.b7)({data:t,columns:y,state:{sorting:f,columnFilters:p},onSortingChange:m,onColumnFiltersChange:h,getCoreRowModel:(0,l.sC)(),getFilteredRowModel:(0,l.vL)(),getSortedRowModel:(0,l.tj)()}),j=(0,s.useMemo)(()=>{let r={};for(let n of e){if(!n.enableColumnFilter)continue;let e=new Set;for(let r of t){let t=r[n.accessorKey];null!=t&&e.add(String(t))}let s=[...e];if(n.filterOrder){let e=n.filterOrder;s.sort((t,r)=>{let n=e.indexOf(t),s=e.indexOf(r);return(-1===n?999:n)-(-1===s?999:s)})}else s.sort();r[n.id]=s}return r},[e,t]),S=e.filter(e=>e.enableColumnFilter);return(0,n.jsxs)("div",{className:"overflow-hidden border border-border-default",style:{borderRadius:"var(--radius-sm)"},children:[r&&(0,n.jsxs)("button",{type:"button",onClick:()=>x(!u),"aria-expanded":u,className:"flex w-full items-center justify-between bg-surface-2 px-sp-4 py-sp-3 text-left hover:bg-surface-3",children:[(0,n.jsxs)("span",{className:"text-heading font-medium text-text-primary",children:[r," (",t.length,")"]}),n.jsx("svg",{className:`h-4 w-4 text-text-tertiary ${u?"rotate-180":""}`,"aria-hidden":"true",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:n.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"m19 9-7 7-7-7"})})]}),u&&(0,n.jsxs)(n.Fragment,{children:[S.length>0&&n.jsx("div",{className:"flex flex-wrap gap-2 border-b border-border-subtle bg-surface-1 px-sp-4 py-sp-2",children:S.map(e=>{let r=p.find(t=>t.id===e.id);return(0,n.jsxs)("div",{className:"flex flex-wrap gap-1",children:[(0,n.jsxs)("button",{type:"button",onClick:()=>h(t=>t.filter(t=>t.id!==e.id)),"aria-pressed":!r?.value,className:`rounded-md px-3 py-1.5 text-caption font-medium focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${r?.value?"bg-surface-3 text-text-secondary hover:text-text-primary":"bg-accent text-surface-0"}`,children:["All ",e.header]}),(j[e.id]??[]).map(s=>{let i=t.filter(t=>String(t[e.accessorKey])===s).length,l=r?.value===s;return(0,n.jsxs)("button",{type:"button",onClick:()=>h(t=>{let r=t.filter(t=>t.id!==e.id);return l?r:[...r,{id:e.id,value:s}]}),"aria-pressed":l,className:`rounded-md px-3 py-1.5 text-caption font-medium focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${l?"bg-accent text-surface-0":"bg-surface-3 text-text-secondary hover:text-text-primary"}`,children:[s,n.jsx("span",{className:"ml-1 opacity-60",children:i})]},s)})]},e.id)})}),n.jsx("div",{className:"overflow-x-auto",children:(0,n.jsxs)("table",{className:"w-full text-left text-body",children:[n.jsx("thead",{className:"border-b border-border-default bg-surface-2 text-caption font-medium uppercase tracking-wider text-text-tertiary",children:n.jsx("tr",{children:v.getHeaderGroups().map(e=>e.headers.map(e=>n.jsx("th",{scope:"col","aria-sort":"asc"===e.column.getIsSorted()?"ascending":"desc"===e.column.getIsSorted()?"descending":void 0,className:"cursor-pointer select-none px-sp-4 py-sp-2 hover:text-text-secondary",onClick:e.column.getToggleSortingHandler(),children:(0,n.jsxs)("div",{className:"flex items-center gap-1",children:[(0,i.ie)(e.column.columnDef.header,e.getContext()),"asc"===e.column.getIsSorted()&&n.jsx("svg",{className:"h-3 w-3","aria-hidden":"true",viewBox:"0 0 12 12",fill:"currentColor",children:n.jsx("path",{d:"M6 2l4 5H2z"})}),"desc"===e.column.getIsSorted()&&n.jsx("svg",{className:"h-3 w-3","aria-hidden":"true",viewBox:"0 0 12 12",fill:"currentColor",children:n.jsx("path",{d:"M6 10l4-5H2z"})})]})},e.id)))})}),n.jsx("tbody",{className:"divide-y divide-border-subtle",children:v.getRowModel().rows.map(t=>{let r=t.original,l=r.id?String(r.id):t.id,o=g===l,u=!!d||!!c;return(0,n.jsxs)(s.Fragment,{children:[n.jsx("tr",{className:`${a?a(r):"bg-surface-1 hover:bg-surface-3"} ${u?"cursor-pointer":""}`,role:u?"button":void 0,tabIndex:u?0:void 0,"aria-expanded":c?o:void 0,onClick:()=>{c&&b(o?null:l),d?.(r)},onKeyDown:u?e=>{("Enter"===e.key||" "===e.key)&&(e.preventDefault(),c&&b(o?null:l),d?.(r))}:void 0,children:t.getVisibleCells().map(e=>n.jsx("td",{className:"h-9 px-sp-4 py-sp-2",children:(0,i.ie)(e.column.columnDef.cell,e.getContext())},e.id))}),c&&o&&n.jsx("tr",{children:n.jsx("td",{colSpan:e.length,className:"bg-surface-0 px-sp-5 py-sp-4",children:c(r)})})]},t.id)})})]})})]})]})}},446:(e,t,r)=>{r.d(t,{v:()=>s});var n=r(5023);function s({command:e}){return(0,n.jsxs)("div",{className:"flex flex-col items-center justify-center border border-dashed border-border-default bg-surface-1 px-sp-5 py-sp-8 text-center",style:{borderRadius:"var(--radius-sm)"},children:[n.jsx("svg",{className:"mb-4 h-12 w-12 text-text-tertiary",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.5,children:n.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m6.75 12H9.75m3 0H9.75m0 0v3m-3-6h1.5m-1.5 0v3m6-6h.008v.008H15V9.75Z"})}),n.jsx("h3",{className:"mb-2 text-heading font-medium text-text-primary",children:"Data not yet generated"}),n.jsx("p",{className:"mb-4 text-body text-text-secondary",children:"Run the following command to generate this data:"}),n.jsx("code",{className:"border border-border-default bg-surface-0 px-sp-4 py-sp-2 font-mono text-body text-accent",style:{borderRadius:"var(--radius-sm)"},children:e})]})}},1760:(e,t,r)=>{r.d(t,{J1:()=>f,V9:()=>m,ZN:()=>x,au:()=>c,gn:()=>a,ki:()=>u,ws:()=>d});var n=r(7561),s=r.n(n),i=r(9411),l=r.n(i);function o(){let e=process.cwd();try{let t=l().join(e,".hex","config.json");if(s().existsSync(t))return l().join(e,".hex");let r=l().join(e,".solaudit","config.json");if(s().existsSync(r))return l().join(e,".solaudit")}catch{}return l().join(e,".hex")}function a(e){try{let t=l().join(o(),e),r=s().readFileSync(t,"utf-8");return JSON.parse(r)}catch{return null}}function d(e){try{let t=l().join(o(),e);return s().readFileSync(t,"utf-8")}catch{return null}}function c(e,t){let r=l().join(o(),e);s().writeFileSync(r,JSON.stringify(t,null,2),"utf-8")}function u(e){try{let t=l().join(o(),e);return s().existsSync(t)}catch{return!1}}function x(){return o()}function f(e){try{let t=l().join(o(),e);if(!s().existsSync(t))return[];return s().readdirSync(t,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name)}catch{return[]}}function m(e){try{let t=l().join(o(),e),r=s().readFileSync(t,"utf-8");return JSON.parse(r)}catch{return null}}},2950:(e,t,r)=>{r.r(t),r.d(t,{default:()=>s});var n=r(1645);let s=e=>[{type:"image/svg+xml",sizes:"any",url:(0,n.fillMetadataSegment)(".",e.params,"icon.svg")+"?83fa4bf1ef0d214d"}]}};