hex-audit 0.5.1 → 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 (82) hide show
  1. package/dashboard/.next/BUILD_ID +1 -1
  2. package/dashboard/.next/app-build-manifest.json +19 -19
  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-server.js.nft.json +1 -1
  6. package/dashboard/.next/prerender-manifest.js +1 -1
  7. package/dashboard/.next/prerender-manifest.json +1 -1
  8. package/dashboard/.next/server/app/_not-found/page.js +1 -1
  9. package/dashboard/.next/server/app/_not-found/page.js.nft.json +1 -1
  10. package/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  11. package/dashboard/.next/server/app/_not-found.html +1 -1
  12. package/dashboard/.next/server/app/_not-found.rsc +4 -4
  13. package/dashboard/.next/server/app/access/page.js +1 -1
  14. package/dashboard/.next/server/app/access/page.js.nft.json +1 -1
  15. package/dashboard/.next/server/app/access/page_client-reference-manifest.js +1 -1
  16. package/dashboard/.next/server/app/calls/page.js +1 -1
  17. package/dashboard/.next/server/app/calls/page.js.nft.json +1 -1
  18. package/dashboard/.next/server/app/calls/page_client-reference-manifest.js +1 -1
  19. package/dashboard/.next/server/app/conformance/page.js +1 -1
  20. package/dashboard/.next/server/app/conformance/page.js.nft.json +1 -1
  21. package/dashboard/.next/server/app/conformance/page_client-reference-manifest.js +1 -1
  22. package/dashboard/.next/server/app/diagram/page.js +1 -1
  23. package/dashboard/.next/server/app/diagram/page.js.nft.json +1 -1
  24. package/dashboard/.next/server/app/diagram/page_client-reference-manifest.js +1 -1
  25. package/dashboard/.next/server/app/diagram.html +1 -1
  26. package/dashboard/.next/server/app/diagram.rsc +4 -4
  27. package/dashboard/.next/server/app/flows/page.js +1 -1
  28. package/dashboard/.next/server/app/flows/page.js.nft.json +1 -1
  29. package/dashboard/.next/server/app/flows/page_client-reference-manifest.js +1 -1
  30. package/dashboard/.next/server/app/flows.html +1 -1
  31. package/dashboard/.next/server/app/flows.rsc +4 -4
  32. package/dashboard/.next/server/app/functions/page.js +1 -1
  33. package/dashboard/.next/server/app/functions/page.js.nft.json +1 -1
  34. package/dashboard/.next/server/app/functions/page_client-reference-manifest.js +1 -1
  35. package/dashboard/.next/server/app/issues/page.js +2 -2
  36. package/dashboard/.next/server/app/issues/page.js.nft.json +1 -1
  37. package/dashboard/.next/server/app/issues/page_client-reference-manifest.js +1 -1
  38. package/dashboard/.next/server/app/overleaf/page.js +1 -1
  39. package/dashboard/.next/server/app/overleaf/page.js.nft.json +1 -1
  40. package/dashboard/.next/server/app/overleaf/page_client-reference-manifest.js +1 -1
  41. package/dashboard/.next/server/app/page.js +2 -2
  42. package/dashboard/.next/server/app/page.js.nft.json +1 -1
  43. package/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  44. package/dashboard/.next/server/app/progress/page.js +1 -1
  45. package/dashboard/.next/server/app/progress/page.js.nft.json +1 -1
  46. package/dashboard/.next/server/app/progress/page_client-reference-manifest.js +1 -1
  47. package/dashboard/.next/server/app/stats/page.js +1 -1
  48. package/dashboard/.next/server/app/stats/page.js.nft.json +1 -1
  49. package/dashboard/.next/server/app/stats/page_client-reference-manifest.js +1 -1
  50. package/dashboard/.next/server/app-paths-manifest.json +6 -6
  51. package/dashboard/.next/server/chunks/{431.js → 530.js} +1 -1
  52. package/dashboard/.next/server/functions-config-manifest.json +1 -1
  53. package/dashboard/.next/server/middleware-build-manifest.js +1 -1
  54. package/dashboard/.next/server/pages/404.html +1 -1
  55. package/dashboard/.next/server/pages/500.html +1 -1
  56. package/dashboard/.next/server/server-reference-manifest.js +1 -1
  57. package/dashboard/.next/server/server-reference-manifest.json +1 -1
  58. package/dashboard/.next/static/chunks/app/{layout-91d2987f5030a3c4.js → layout-23765c356f43208c.js} +1 -1
  59. package/dashboard/app/layout.tsx +2 -1
  60. package/dashboard/components/DashboardLayout.tsx +8 -2
  61. package/dist/commands/note.d.ts +3 -0
  62. package/dist/commands/note.d.ts.map +1 -0
  63. package/dist/commands/note.js +145 -0
  64. package/dist/commands/note.js.map +1 -0
  65. package/dist/core/notes.d.ts +62 -0
  66. package/dist/core/notes.d.ts.map +1 -0
  67. package/dist/core/notes.js +160 -0
  68. package/dist/core/notes.js.map +1 -0
  69. package/dist/core/whisper.d.ts +24 -0
  70. package/dist/core/whisper.d.ts.map +1 -0
  71. package/dist/core/whisper.js +124 -0
  72. package/dist/core/whisper.js.map +1 -0
  73. package/dist/index.js +6 -1
  74. package/dist/index.js.map +1 -1
  75. package/dist/skills/diane.md +99 -0
  76. package/dist/skills/ingest-aa-report.md +1 -1
  77. package/dist/skills/sync-issues.md +23 -3
  78. package/dist/skills/validate-issue.md +1 -1
  79. package/dist/skills/write-finding.md +2 -1
  80. package/package.json +1 -1
  81. /package/dashboard/.next/static/{dm-uzRRik5AgaBPedT1iR → QeyYu_4EslVR8FTiyQpNs}/_buildManifest.js +0 -0
  82. /package/dashboard/.next/static/{dm-uzRRik5AgaBPedT1iR → 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":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;wEACwE;AACxE,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C;QACE,KAAK,CACH,6EAA6E;YAC3E,yEAAyE;YACzE,iEAAiE;YACjE,+DAA+D,CAClE,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAOD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,SAAS,CAAC;AAE9D,SAAS,aAAa,CAAC,GAAW;IAChC,IAAI,CAAC;QACH,YAAY,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,cAAc,GAAG,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACrE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,GAAW;IACzC,IAAI,CAAC;QACH,YAAY,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,uBAAuB,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACxE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,aAAa;IAC3B,IAAI,aAAa,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;IACxE,KAAK,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC;QACvC,IAAI,aAAa,CAAC,EAAE,CAAC,IAAI,sBAAsB,CAAC,EAAE,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAC1F,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC/C,KAAK,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC,EAAE,CAAC;QACjD,IAAI,QAAQ,IAAI,aAAa,CAAC,GAAG,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IACnF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,YAAY;IACnB,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,gFAAgF,CAAC,CAAC;IACpG,CAAC;AACH,CAAC;AAED,0EAA0E;AAC1E,SAAS,KAAK,CAAC,SAAiB;IAC9B,YAAY,EAAE,CAAC;IACf,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,iBAAiB,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACpF,YAAY,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE;QAC5F,KAAK,EAAE,QAAQ;KAChB,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,aAAa,GAAG;;;;;;CAMrB,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,mBAAmB,EAAE,CAAC;IAE7C,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IAC7B,IAAI,CAAC;QACH,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;YACtE,YAAY,CACV,MAAM,CAAC,GAAG,EACV,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,CAAC,EACrH,EAAE,KAAK,EAAE,QAAQ,EAAE,CACpB,CAAC;YACF,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC;YACjF,OAAO,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAChD,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,MAAM,CAAC;YACjE,OAAO,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE;gBACvE,QAAQ,EAAE,OAAO;gBACjB,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;aAC5B,CAAC,CAAC,IAAI,EAAE,CAAC;QACZ,CAAC;QACD,cAAc;QACd,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC1C,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE;YAC7F,KAAK,EAAE,QAAQ;SAChB,CAAC,CAAC;QACH,OAAO,EAAE,CAAC,YAAY,CAAC,OAAO,GAAG,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3D,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,yBAAyB;QAC3B,CAAC;IACH,CAAC;AACH,CAAC"}
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.1');
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,7 +95,7 @@ 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. 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]`.
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
99
 
100
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.
101
101
 
@@ -84,7 +84,7 @@ 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
 
@@ -95,6 +95,7 @@ The finding is rendered as a flat template (`## [Severity] Title`, then `**Descr
95
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
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
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.
98
99
 
99
100
  ## Code Block Formatting Rules (STRICT)
100
101
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hex-audit",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "description": "CLI toolkit for Solidity smart contract auditors",
6
6
  "bin": {