@promptvet/hook 0.1.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.
Files changed (50) hide show
  1. package/README.md +214 -0
  2. package/dist/adapters/claudeCode.d.ts +2 -0
  3. package/dist/adapters/claudeCode.d.ts.map +1 -0
  4. package/dist/adapters/claudeCode.js +31 -0
  5. package/dist/adapters/claudeCode.js.map +1 -0
  6. package/dist/adapters/fieldPath.d.ts +2 -0
  7. package/dist/adapters/fieldPath.d.ts.map +1 -0
  8. package/dist/adapters/fieldPath.js +15 -0
  9. package/dist/adapters/fieldPath.js.map +1 -0
  10. package/dist/cli/check.d.ts +7 -0
  11. package/dist/cli/check.d.ts.map +1 -0
  12. package/dist/cli/check.js +49 -0
  13. package/dist/cli/check.js.map +1 -0
  14. package/dist/cli/index.d.ts +11 -0
  15. package/dist/cli/index.d.ts.map +1 -0
  16. package/dist/cli/index.js +105 -0
  17. package/dist/cli/index.js.map +1 -0
  18. package/dist/history/db.d.ts +14 -0
  19. package/dist/history/db.d.ts.map +1 -0
  20. package/dist/history/db.js +35 -0
  21. package/dist/history/db.js.map +1 -0
  22. package/dist/history/dbPath.d.ts +2 -0
  23. package/dist/history/dbPath.d.ts.map +1 -0
  24. package/dist/history/dbPath.js +4 -0
  25. package/dist/history/dbPath.js.map +1 -0
  26. package/dist/history/hashPrompt.d.ts +2 -0
  27. package/dist/history/hashPrompt.d.ts.map +1 -0
  28. package/dist/history/hashPrompt.js +5 -0
  29. package/dist/history/hashPrompt.js.map +1 -0
  30. package/dist/report/formatReport.d.ts +3 -0
  31. package/dist/report/formatReport.d.ts.map +1 -0
  32. package/dist/report/formatReport.js +25 -0
  33. package/dist/report/formatReport.js.map +1 -0
  34. package/dist/report/formatVerboseReport.d.ts +3 -0
  35. package/dist/report/formatVerboseReport.d.ts.map +1 -0
  36. package/dist/report/formatVerboseReport.js +31 -0
  37. package/dist/report/formatVerboseReport.js.map +1 -0
  38. package/dist/report/summarize.d.ts +15 -0
  39. package/dist/report/summarize.d.ts.map +1 -0
  40. package/dist/report/summarize.js +36 -0
  41. package/dist/report/summarize.js.map +1 -0
  42. package/dist/report/verboseHistory.d.ts +9 -0
  43. package/dist/report/verboseHistory.d.ts.map +1 -0
  44. package/dist/report/verboseHistory.js +21 -0
  45. package/dist/report/verboseHistory.js.map +1 -0
  46. package/dist/worker.d.ts +8 -0
  47. package/dist/worker.d.ts.map +1 -0
  48. package/dist/worker.js +43 -0
  49. package/dist/worker.js.map +1 -0
  50. package/package.json +52 -0
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ <div align="center">
2
+
3
+ # PromptVet Hook
4
+
5
+ ### Every prompt scored in the background. Nothing blocked, nothing slowed down.
6
+
7
+ A Claude Code hook (`PreToolUse` and `UserPromptSubmit`) that gives you a local, queryable prompt history and audit trail — observability for your AI agent, backed by SQLite, without touching enforcement.
8
+
9
+ [![npm](https://img.shields.io/npm/v/%40promptvet%2Fhook?color=cb3837)](https://www.npmjs.com/package/@promptvet/hook)
10
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](../../LICENSE)
11
+ [![Node](https://img.shields.io/badge/node-22-339933?logo=node.js&logoColor=white)](https://nodejs.org)
12
+ [![Diary mode](https://img.shields.io/badge/mode-diary%2C%20not%20a%20gate-blue)](#what-it-is)
13
+ [![TDD Built](https://img.shields.io/badge/built%20with-TDD-blueviolet)](CLAUDE.md)
14
+
15
+ *Wires [`promptvet-core`](../core/README.md)'s `scan` into Claude Code's hook system, so every prompt you (or Claude) submit gets scored automatically — and you can look back at the history any time.*
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ ## What It Is
22
+
23
+ `@promptvet/hook` is a **diary, not a gate.** It listens on Claude Code's hook events, scans each prompt in the background with `promptvet-core`, and records the result to a local history database. It never blocks, never denies, never adds latency to what you were doing — `check` always exits `0`, no matter what it finds.
24
+
25
+ If you want prompt quality *enforcement* (deny on critical issues before a tool fires), that's a separate, not-yet-built mode — see [promptvet-core's roadmap](../core/README.md#️-roadmap). This package is the observability half: **know what you've been sending, after the fact, without lifting a finger.**
26
+
27
+ ---
28
+
29
+ ## Quick Start
30
+
31
+ ```bash
32
+ npm install --save-dev @promptvet/hook
33
+ ```
34
+
35
+ Register it in `.claude/settings.local.json` for both hook events it supports:
36
+
37
+ ```json
38
+ {
39
+ "hooks": {
40
+ "PreToolUse": [
41
+ {
42
+ "matcher": "*",
43
+ "hooks": [
44
+ {
45
+ "type": "command",
46
+ "command": "node /path/to/node_modules/@promptvet/hook/dist/cli/index.js check --adapter claude-code --store-raw-prompt"
47
+ }
48
+ ]
49
+ }
50
+ ],
51
+ "UserPromptSubmit": [
52
+ {
53
+ "hooks": [
54
+ {
55
+ "type": "command",
56
+ "command": "node /path/to/node_modules/@promptvet/hook/dist/cli/index.js check --adapter claude-code --store-raw-prompt"
57
+ }
58
+ ]
59
+ }
60
+ ]
61
+ }
62
+ }
63
+ ```
64
+
65
+ That's it — every prompt you type, and every prompt Claude hands to a subagent, now gets scanned and recorded automatically.
66
+
67
+ ```bash
68
+ promptvet-hook report
69
+ ```
70
+
71
+ ---
72
+
73
+ ## How It Works
74
+
75
+ ```
76
+ ┌──────────────────────────────────────────────────────────────┐
77
+ │ UserPromptSubmit → your own typed prompt, before Claude │
78
+ │ PreToolUse → Claude's own Task/Agent subagent calls │
79
+ └───────────────────────────┬────────────────────────────────────┘
80
+ │ stdin: hook event JSON
81
+
82
+ ┌──────────────────────────────────────────────────────────────┐
83
+ │ check --adapter claude-code │
84
+ │ Extracts the real prompt text, spawns a detached background │
85
+ │ worker, and exits 0 immediately — the calling hook never │
86
+ │ waits on a scan. │
87
+ └───────────────────────────┬────────────────────────────────────┘
88
+ │ (background, detached)
89
+
90
+ ┌──────────────────────────────────────────────────────────────┐
91
+ │ worker.js → scans with promptvet-core, writes one row to │
92
+ │ ~/.promptvet/history.db (SQLite, created on first use) │
93
+ └──────────────────────────────────────────────────────────────┘
94
+ ```
95
+
96
+ **Two event types, one adapter.** `UserPromptSubmit` fires on *your own* typed prompts — its payload carries `prompt` at the top level. `PreToolUse` fires on Claude's own outgoing tool calls — it only carries a genuine natural-language prompt when Claude is dispatching a Task/Agent subagent (`tool_input.prompt`); every other tool call (`Bash`, `Read`, `Write`, …) is correctly ignored, since a shell command or file path isn't a prompt to critique. Both shapes are handled by the same `claude-code` adapter, branching on `hook_event_name`.
97
+
98
+ **Not using Claude Code?** A generic `--field-path` adapter pulls the prompt from any dotted path in arbitrary JSON — useful for wiring in another tool's hook system once you've confirmed its payload shape. (Don't guess the shape — capture a real payload first, the same way this package's own Claude Code fixtures were built.)
99
+
100
+ ---
101
+
102
+ ## Commands
103
+
104
+ ### `check` — fired by the hook, not run by hand
105
+
106
+ ```bash
107
+ promptvet-hook check --adapter claude-code [--store-raw-prompt]
108
+ promptvet-hook check --field-path payload.text [--store-raw-prompt]
109
+ ```
110
+
111
+ | Flag | Meaning |
112
+ |---|---|
113
+ | `--adapter claude-code` | Handles both `PreToolUse` and `UserPromptSubmit` Claude Code payload shapes |
114
+ | `--field-path <path>` | Generic dotted-path extraction for other JSON payloads |
115
+ | `--store-raw-prompt` | Store the actual prompt text, not just its hash (see `report --verbose` below) |
116
+
117
+ Without `--store-raw-prompt`, only a hash of the prompt is stored — the history still tracks scores/verdicts/issues over time, just without the original text.
118
+
119
+ ### `report` — the aggregate summary (default)
120
+
121
+ ```bash
122
+ promptvet-hook report
123
+ promptvet-hook report --json
124
+ ```
125
+
126
+ ```
127
+ PromptVet Hook Report
128
+ ─────────────────────
129
+ Total checks: 42
130
+
131
+ Verdicts:
132
+ Pass: 35
133
+ Fail: 5
134
+ Error: 2
135
+
136
+ Average score: 81.4
137
+
138
+ Top issue categories:
139
+ 1. HEDGE_DETECTED 12
140
+ 2. MISSING_FRAMEWORK 8
141
+ 3. NO_ACTIONABLE_INTENT 5
142
+ ```
143
+
144
+ ### `report --verbose` — individual rows
145
+
146
+ ```bash
147
+ promptvet-hook report --verbose
148
+ promptvet-hook report --verbose --recent 20
149
+ promptvet-hook report --verbose --json
150
+ ```
151
+
152
+ Lists each check individually — timestamp, score, verdict, issue categories, and the prompt text itself — instead of the aggregate summary:
153
+
154
+ ```
155
+ PromptVet Hook Report (verbose)
156
+ ────────────────────────────────
157
+
158
+ 1. 2026-07-04T12:05:00.000Z score: 90.0 verdict: pass
159
+ issues: (none)
160
+ prompt: Write Vitest tests for the tax calculator in src/tax.ts
161
+
162
+ 2. 2026-07-04T12:00:00.000Z score: 40.0 verdict: fail
163
+ issues: NO_ACTIONABLE_INTENT
164
+ prompt: fix it
165
+ ```
166
+
167
+ | Flag | Meaning |
168
+ |---|---|
169
+ | `--recent N` | Cap output to the N most recent checks (ignored without `--verbose`) |
170
+ | `--json` | Full, untruncated row data as a JSON array — for scripts/tooling |
171
+
172
+ **Two display rules worth knowing:**
173
+
174
+ - **No stored prompt?** If `--store-raw-prompt` wasn't set for a row, the prompt column shows `[hash-only, no raw prompt stored]` — never the hash itself, since a hash isn't human-readable and would just be noise.
175
+ - **Giant or multi-line prompt?** The human-readable view truncates to the first line / first 200 characters, with a `[truncated, N chars total]` marker — so pasting a full terminal transcript as a prompt doesn't dump the whole thing back at you on every `report` call. `--json` is never truncated; it always returns the full text, for anything that needs to process it programmatically.
176
+
177
+ ---
178
+
179
+ ## History Storage
180
+
181
+ A local SQLite database at `~/.promptvet/history.db`, created automatically on first `check`. Nothing leaves your machine — same zero-network philosophy as `promptvet-core`'s `scan`.
182
+
183
+ | Column | Notes |
184
+ |---|---|
185
+ | `timestamp`, `source`, `cwd` | When, via which adapter, and from where |
186
+ | `prompt_hash` | Always recorded |
187
+ | `raw_prompt` | Only when `--store-raw-prompt` was passed; `NULL` otherwise |
188
+ | `score`, `verdict`, `issue_categories` | From `promptvet-core`'s `scan` |
189
+
190
+ ---
191
+
192
+ ## Architecture
193
+
194
+ | | |
195
+ |---|---|
196
+ | 🗒️ **Diary, not a gate** | `check` always exits `0` — a bad prompt is recorded, never blocked |
197
+ | 🚫 **Never blocks the calling hook** | The scan + write happen in a detached background process; the hook returns instantly |
198
+ | 🔒 **Local-first** | SQLite on disk, `promptvet-core`'s `scan` runs locally — nothing leaves your machine |
199
+ | 🧪 **TDD-built** | Adapter fixtures are real captured payloads, not guessed shapes |
200
+ | 📘 **TypeScript strict**, Node 22, ESM only | |
201
+
202
+ ---
203
+
204
+ ## Contributing
205
+
206
+ Read [`CLAUDE.md`](../../CLAUDE.md) first — same TDD contract as `promptvet-core`, plus this package's own rules (the diary-safety guarantee, the `system`-role/role-capture rule doesn't apply here but the "verify real payloads, don't guess" discipline very much does).
207
+
208
+ ---
209
+
210
+ <div align="center">
211
+
212
+ **MIT Licensed** · Part of the [PromptVet](../core/README.md) family.
213
+
214
+ </div>
@@ -0,0 +1,2 @@
1
+ export declare function extractClaudeCodePrompt(data: unknown): string | undefined;
2
+ //# sourceMappingURL=claudeCode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claudeCode.d.ts","sourceRoot":"","sources":["../../src/adapters/claudeCode.ts"],"names":[],"mappings":"AAcA,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAmBzE"}
@@ -0,0 +1,31 @@
1
+ // Two event shapes share this one adapter, branched on hook_event_name:
2
+ //
3
+ // - UserPromptSubmit: the actual hook point for the user's own typed prompts, fired
4
+ // before Claude processes them. `prompt` sits at the payload's top level (verified
5
+ // against a real captured payload, see claudeCode.test.ts fixtures) — PreToolUse
6
+ // never sees these at all, it only sees Claude's own outgoing tool calls.
7
+ // - PreToolUse: scoped to tool_input.prompt only (Task/Agent-style subagent calls) —
8
+ // the one case there that's genuinely a natural-language prompt, empirically
9
+ // confirmed against a real captured payload. Earlier versions also fell back to
10
+ // tool_input.command/description for other tools (e.g. Bash), but those are the
11
+ // agent's own terse action summaries, not prompts — scanning them with
12
+ // promptvet-core's dev-prompt heuristics produced false failures (e.g.
13
+ // NO_ACTIONABLE_INTENT on a plain `echo "hello world"` call), confirmed via a live
14
+ // end-to-end test. Non-prompt tool calls now correctly yield undefined (no scan).
15
+ export function extractClaudeCodePrompt(data) {
16
+ if (data === null || typeof data !== 'object') {
17
+ return undefined;
18
+ }
19
+ const record = data;
20
+ if (record['hook_event_name'] === 'UserPromptSubmit') {
21
+ const prompt = record['prompt'];
22
+ return typeof prompt === 'string' && prompt.length > 0 ? prompt : undefined;
23
+ }
24
+ const toolInput = record['tool_input'];
25
+ if (toolInput === null || typeof toolInput !== 'object') {
26
+ return undefined;
27
+ }
28
+ const prompt = toolInput['prompt'];
29
+ return typeof prompt === 'string' && prompt.length > 0 ? prompt : undefined;
30
+ }
31
+ //# sourceMappingURL=claudeCode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claudeCode.js","sourceRoot":"","sources":["../../src/adapters/claudeCode.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,EAAE;AACF,oFAAoF;AACpF,qFAAqF;AACrF,mFAAmF;AACnF,4EAA4E;AAC5E,qFAAqF;AACrF,+EAA+E;AAC/E,kFAAkF;AAClF,kFAAkF;AAClF,yEAAyE;AACzE,yEAAyE;AACzE,qFAAqF;AACrF,oFAAoF;AACpF,MAAM,UAAU,uBAAuB,CAAC,IAAa;IACnD,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9C,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,IAA+B,CAAA;IAE9C,IAAI,MAAM,CAAC,iBAAiB,CAAC,KAAK,kBAAkB,EAAE,CAAC;QACrD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC/B,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;IAC7E,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;IACtC,IAAI,SAAS,KAAK,IAAI,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QACxD,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,MAAM,GAAI,SAAqC,CAAC,QAAQ,CAAC,CAAA;IAC/D,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;AAC7E,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function extractByFieldPath(data: unknown, path: string): string | undefined;
2
+ //# sourceMappingURL=fieldPath.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldPath.d.ts","sourceRoot":"","sources":["../../src/adapters/fieldPath.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAelF"}
@@ -0,0 +1,15 @@
1
+ export function extractByFieldPath(data, path) {
2
+ const segments = path
3
+ .replace(/\[(\d+)\]/g, '.$1')
4
+ .split('.')
5
+ .filter((segment) => segment.length > 0);
6
+ let current = data;
7
+ for (const segment of segments) {
8
+ if (current === null || typeof current !== 'object') {
9
+ return undefined;
10
+ }
11
+ current = current[segment];
12
+ }
13
+ return typeof current === 'string' ? current : undefined;
14
+ }
15
+ //# sourceMappingURL=fieldPath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldPath.js","sourceRoot":"","sources":["../../src/adapters/fieldPath.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,kBAAkB,CAAC,IAAa,EAAE,IAAY;IAC5D,MAAM,QAAQ,GAAG,IAAI;SAClB,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC;SAC5B,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAE1C,IAAI,OAAO,GAAY,IAAI,CAAA;IAC3B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YACpD,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,OAAO,GAAI,OAAmC,CAAC,OAAO,CAAC,CAAA;IACzD,CAAC;IAED,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;AAC1D,CAAC"}
@@ -0,0 +1,7 @@
1
+ export interface CheckOptions {
2
+ adapter?: string;
3
+ fieldPath?: string;
4
+ storeRawPrompt?: boolean;
5
+ }
6
+ export declare function handleCheck(stdinInput: string, options: CheckOptions): number;
7
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/cli/check.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAID,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,CA6B7E"}
@@ -0,0 +1,49 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { extractClaudeCodePrompt } from '../adapters/claudeCode.js';
4
+ import { extractByFieldPath } from '../adapters/fieldPath.js';
5
+ const WORKER_PATH = fileURLToPath(new URL('../worker.js', import.meta.url));
6
+ export function handleCheck(stdinInput, options) {
7
+ let data;
8
+ try {
9
+ data = JSON.parse(stdinInput);
10
+ }
11
+ catch {
12
+ return 0;
13
+ }
14
+ const prompt = extractPrompt(data, options);
15
+ if (prompt === undefined || prompt.length === 0) {
16
+ return 0;
17
+ }
18
+ const cwd = extractCwd(data) ?? process.cwd();
19
+ const source = options.adapter === 'claude-code' ? 'claude-code' : 'field-path';
20
+ const payload = {
21
+ prompt,
22
+ source,
23
+ cwd,
24
+ storeRawPrompt: options.storeRawPrompt ?? false,
25
+ };
26
+ const child = spawn(process.execPath, [WORKER_PATH, JSON.stringify(payload)], {
27
+ detached: true,
28
+ stdio: 'ignore',
29
+ });
30
+ child.unref();
31
+ return 0;
32
+ }
33
+ function extractPrompt(data, options) {
34
+ if (options.adapter === 'claude-code') {
35
+ return extractClaudeCodePrompt(data);
36
+ }
37
+ if (options.fieldPath) {
38
+ return extractByFieldPath(data, options.fieldPath);
39
+ }
40
+ return undefined;
41
+ }
42
+ function extractCwd(data) {
43
+ if (data === null || typeof data !== 'object') {
44
+ return undefined;
45
+ }
46
+ const cwd = data['cwd'];
47
+ return typeof cwd === 'string' ? cwd : undefined;
48
+ }
49
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/cli/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAA;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAQ7D,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAE3E,MAAM,UAAU,WAAW,CAAC,UAAkB,EAAE,OAAqB;IACnE,IAAI,IAAa,CAAA;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAA;IACV,CAAC;IAED,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC3C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,OAAO,CAAC,CAAA;IACV,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAA;IAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAA;IAC/E,MAAM,OAAO,GAAG;QACd,MAAM;QACN,MAAM;QACN,GAAG;QACH,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,KAAK;KAChD,CAAA;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE;QAC5E,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,QAAQ;KAChB,CAAC,CAAA;IACF,KAAK,CAAC,KAAK,EAAE,CAAA;IAEb,OAAO,CAAC,CAAA;AACV,CAAC;AAED,SAAS,aAAa,CAAC,IAAa,EAAE,OAAqB;IACzD,IAAI,OAAO,CAAC,OAAO,KAAK,aAAa,EAAE,CAAC;QACtC,OAAO,uBAAuB,CAAC,IAAI,CAAC,CAAA;IACtC,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;IACpD,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAS,UAAU,CAAC,IAAa;IAC/B,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9C,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,GAAG,GAAI,IAAgC,CAAC,KAAK,CAAC,CAAA;IACpD,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;AAClD,CAAC"}
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { type CheckOptions } from './check.js';
3
+ export declare function parseArgs(argv: string[]): CheckOptions;
4
+ export declare function readStdin(stream?: NodeJS.ReadableStream): Promise<string>;
5
+ export declare function runCheckCommand(argv: string[], stream?: NodeJS.ReadableStream): Promise<number>;
6
+ export interface ReportCommandResult {
7
+ exitCode: number;
8
+ output: string;
9
+ }
10
+ export declare function runReportCommand(argv: string[], dbPath: string): ReportCommandResult;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,YAAY,CAAA;AAO3D,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,YAAY,CAetD;AAED,wBAAgB,SAAS,CAAC,MAAM,GAAE,MAAM,CAAC,cAA8B,GAAG,OAAO,CAAC,MAAM,CAAC,CAYxF;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,GAAE,MAAM,CAAC,cAA8B,GAAG,OAAO,CAAC,MAAM,CAAC,CAQpH;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf;AAYD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAiBpF"}
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ import { pathToFileURL } from 'node:url';
3
+ import { handleCheck } from './check.js';
4
+ import { summarizeHistory } from '../report/summarize.js';
5
+ import { formatReport } from '../report/formatReport.js';
6
+ import { getVerboseHistory } from '../report/verboseHistory.js';
7
+ import { formatVerboseReport } from '../report/formatVerboseReport.js';
8
+ import { DEFAULT_DB_PATH } from '../history/dbPath.js';
9
+ export function parseArgs(argv) {
10
+ const options = {};
11
+ for (let i = 0; i < argv.length; i++) {
12
+ const arg = argv[i];
13
+ if (arg === '--adapter') {
14
+ const value = argv[++i];
15
+ if (value !== undefined)
16
+ options.adapter = value;
17
+ }
18
+ else if (arg === '--field-path') {
19
+ const value = argv[++i];
20
+ if (value !== undefined)
21
+ options.fieldPath = value;
22
+ }
23
+ else if (arg === '--store-raw-prompt') {
24
+ options.storeRawPrompt = true;
25
+ }
26
+ }
27
+ return options;
28
+ }
29
+ export function readStdin(stream = process.stdin) {
30
+ return new Promise((resolve) => {
31
+ let data = '';
32
+ stream.setEncoding('utf-8');
33
+ stream.on('data', (chunk) => {
34
+ data += chunk;
35
+ });
36
+ stream.on('end', () => resolve(data));
37
+ // Never let a stdin stream error propagate as a failure — check's diary
38
+ // guarantee is that it always exits 0, no matter what goes wrong internally.
39
+ stream.on('error', () => resolve(data));
40
+ });
41
+ }
42
+ export async function runCheckCommand(argv, stream = process.stdin) {
43
+ try {
44
+ const options = parseArgs(argv);
45
+ const stdin = await readStdin(stream);
46
+ return handleCheck(stdin, options);
47
+ }
48
+ catch {
49
+ return 0;
50
+ }
51
+ }
52
+ function parseRecentLimit(argv) {
53
+ const index = argv.indexOf('--recent');
54
+ if (index === -1)
55
+ return undefined;
56
+ const parsed = Number.parseInt(argv[index + 1] ?? '', 10);
57
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
58
+ }
59
+ // Unlike check, report is not covered by the diary-safety guarantee — it's a normal
60
+ // informational command, so a genuine failure (e.g. a corrupted db) fails loud with
61
+ // exit 1 rather than being swallowed.
62
+ export function runReportCommand(argv, dbPath) {
63
+ try {
64
+ const json = argv.includes('--json');
65
+ if (argv.includes('--verbose')) {
66
+ const entries = getVerboseHistory(dbPath, parseRecentLimit(argv));
67
+ const output = json ? JSON.stringify(entries) + '\n' : formatVerboseReport(entries);
68
+ return { exitCode: 0, output };
69
+ }
70
+ const summary = summarizeHistory(dbPath);
71
+ const output = json ? JSON.stringify(summary) + '\n' : formatReport(summary);
72
+ return { exitCode: 0, output };
73
+ }
74
+ catch (error) {
75
+ const message = error instanceof Error ? error.message : String(error);
76
+ return { exitCode: 1, output: `Failed to read history: ${message}\n` };
77
+ }
78
+ }
79
+ const USAGE = 'Usage: promptvet-hook check [--adapter <name>] [--field-path <path>] [--store-raw-prompt]\n' +
80
+ ' promptvet-hook report [--json] [--verbose [--recent <N>]]\n';
81
+ async function main() {
82
+ const [command, ...rest] = process.argv.slice(2);
83
+ if (command === 'check') {
84
+ const exitCode = await runCheckCommand(rest);
85
+ process.exit(exitCode);
86
+ }
87
+ if (command === 'report') {
88
+ const { exitCode, output } = runReportCommand(rest, DEFAULT_DB_PATH);
89
+ if (exitCode === 0) {
90
+ process.stdout.write(output);
91
+ }
92
+ else {
93
+ process.stderr.write(output);
94
+ }
95
+ process.exit(exitCode);
96
+ }
97
+ process.stderr.write(USAGE);
98
+ process.exit(1);
99
+ }
100
+ // Guard so importing this module (e.g. index.test.ts importing parseArgs) never
101
+ // triggers process.exit() — main() only runs when this file is the entry point.
102
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
103
+ await main();
104
+ }
105
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import Database from 'better-sqlite3';
2
+ export interface HistoryEntry {
3
+ timestamp: string;
4
+ source: string;
5
+ cwd: string;
6
+ promptHash: string;
7
+ rawPrompt: string | null;
8
+ score: number | null;
9
+ verdict: 'pass' | 'fail' | 'error';
10
+ issueCategories: string[];
11
+ }
12
+ export declare function openHistoryDb(dbPath: string): Database.Database;
13
+ export declare function insertHistoryEntry(db: Database.Database, entry: HistoryEntry): void;
14
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/history/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AAIrC,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAClC,eAAe,EAAE,MAAM,EAAE,CAAA;CAC1B;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAiB/D;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAcnF"}
@@ -0,0 +1,35 @@
1
+ import Database from 'better-sqlite3';
2
+ import { mkdirSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ export function openHistoryDb(dbPath) {
5
+ mkdirSync(dirname(dbPath), { recursive: true });
6
+ const db = new Database(dbPath);
7
+ db.exec(`
8
+ CREATE TABLE IF NOT EXISTS history (
9
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
10
+ timestamp TEXT NOT NULL,
11
+ source TEXT NOT NULL,
12
+ cwd TEXT NOT NULL,
13
+ prompt_hash TEXT NOT NULL,
14
+ raw_prompt TEXT,
15
+ score REAL,
16
+ verdict TEXT NOT NULL,
17
+ issue_categories TEXT NOT NULL
18
+ )
19
+ `);
20
+ return db;
21
+ }
22
+ export function insertHistoryEntry(db, entry) {
23
+ db.prepare(`INSERT INTO history (timestamp, source, cwd, prompt_hash, raw_prompt, score, verdict, issue_categories)
24
+ VALUES (@timestamp, @source, @cwd, @promptHash, @rawPrompt, @score, @verdict, @issueCategories)`).run({
25
+ timestamp: entry.timestamp,
26
+ source: entry.source,
27
+ cwd: entry.cwd,
28
+ promptHash: entry.promptHash,
29
+ rawPrompt: entry.rawPrompt,
30
+ score: entry.score,
31
+ verdict: entry.verdict,
32
+ issueCategories: JSON.stringify(entry.issueCategories),
33
+ });
34
+ }
35
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/history/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAanC,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC/C,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC/B,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;GAYP,CAAC,CAAA;IACF,OAAO,EAAE,CAAA;AACX,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,EAAqB,EAAE,KAAmB;IAC3E,EAAE,CAAC,OAAO,CACR;qGACiG,CAClG,CAAC,GAAG,CAAC;QACJ,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,eAAe,CAAC;KACvD,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_DB_PATH: string;
2
+ //# sourceMappingURL=dbPath.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dbPath.d.ts","sourceRoot":"","sources":["../../src/history/dbPath.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,eAAe,QAA8C,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ export const DEFAULT_DB_PATH = join(homedir(), '.promptvet', 'history.db');
4
+ //# sourceMappingURL=dbPath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dbPath.js","sourceRoot":"","sources":["../../src/history/dbPath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,YAAY,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export declare function hashPrompt(prompt: string): string;
2
+ //# sourceMappingURL=hashPrompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashPrompt.d.ts","sourceRoot":"","sources":["../../src/history/hashPrompt.ts"],"names":[],"mappings":"AAEA,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD"}
@@ -0,0 +1,5 @@
1
+ import { createHash } from 'node:crypto';
2
+ export function hashPrompt(prompt) {
3
+ return createHash('sha256').update(prompt).digest('hex');
4
+ }
5
+ //# sourceMappingURL=hashPrompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashPrompt.js","sourceRoot":"","sources":["../../src/history/hashPrompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC1D,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ReportSummary } from './summarize.js';
2
+ export declare function formatReport(summary: ReportSummary): string;
3
+ //# sourceMappingURL=formatReport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatReport.d.ts","sourceRoot":"","sources":["../../src/report/formatReport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CA0B3D"}
@@ -0,0 +1,25 @@
1
+ export function formatReport(summary) {
2
+ if (summary.totalChecks === 0) {
3
+ return 'PromptVet Hook Report\n─────────────────────\nNo history recorded yet.\n';
4
+ }
5
+ const lines = [];
6
+ lines.push('PromptVet Hook Report');
7
+ lines.push('─────────────────────');
8
+ lines.push(`Total checks: ${summary.totalChecks}`);
9
+ lines.push('');
10
+ lines.push('Verdicts:');
11
+ lines.push(` Pass: ${summary.verdictCounts.pass}`);
12
+ lines.push(` Fail: ${summary.verdictCounts.fail}`);
13
+ lines.push(` Error: ${summary.verdictCounts.error}`);
14
+ lines.push('');
15
+ lines.push(`Average score: ${summary.averageScore !== null ? summary.averageScore.toFixed(1) : 'N/A'}`);
16
+ if (summary.topIssueCategories.length > 0) {
17
+ lines.push('');
18
+ lines.push('Top issue categories:');
19
+ summary.topIssueCategories.slice(0, 5).forEach((issue, index) => {
20
+ lines.push(` ${index + 1}. ${issue.code.padEnd(24)} ${issue.count}`);
21
+ });
22
+ }
23
+ return lines.join('\n') + '\n';
24
+ }
25
+ //# sourceMappingURL=formatReport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatReport.js","sourceRoot":"","sources":["../../src/report/formatReport.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAAC,OAAsB;IACjD,IAAI,OAAO,CAAC,WAAW,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,0EAA0E,CAAA;IACnF,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAA;IACnC,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAA;IACnC,KAAK,CAAC,IAAI,CAAC,iBAAiB,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;IAClD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACd,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACvB,KAAK,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAA;IACpD,KAAK,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAA;IACpD,KAAK,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,CAAA;IACrD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACd,KAAK,CAAC,IAAI,CAAC,kBAAkB,OAAO,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAA;IAEvG,IAAI,OAAO,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACd,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAA;QACnC,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YAC9D,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;QACvE,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;AAChC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { VerboseHistoryEntry } from './verboseHistory.js';
2
+ export declare function formatVerboseReport(entries: VerboseHistoryEntry[]): string;
3
+ //# sourceMappingURL=formatVerboseReport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatVerboseReport.d.ts","sourceRoot":"","sources":["../../src/report/formatVerboseReport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAmB9D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,MAAM,CAoB1E"}
@@ -0,0 +1,31 @@
1
+ // Display-only cap — a pasted terminal transcript or other giant paste otherwise
2
+ // gets echoed back in full every time the report is checked. JSON output is
3
+ // unaffected: --verbose --json always returns the full, untruncated promptText.
4
+ const MAX_PROMPT_DISPLAY_LENGTH = 200;
5
+ function truncatePromptForDisplay(promptText) {
6
+ const firstLine = promptText.split('\n')[0] ?? '';
7
+ const isMultiLine = promptText.includes('\n');
8
+ const truncatedLine = firstLine.length > MAX_PROMPT_DISPLAY_LENGTH ? firstLine.slice(0, MAX_PROMPT_DISPLAY_LENGTH) : firstLine;
9
+ if (!isMultiLine && truncatedLine === promptText) {
10
+ return promptText;
11
+ }
12
+ return `${truncatedLine}… [truncated, ${promptText.length} chars total]`;
13
+ }
14
+ export function formatVerboseReport(entries) {
15
+ if (entries.length === 0) {
16
+ return 'PromptVet Hook Report (verbose)\n────────────────────────────────\nNo history recorded yet.\n';
17
+ }
18
+ const lines = [];
19
+ lines.push('PromptVet Hook Report (verbose)');
20
+ lines.push('────────────────────────────────');
21
+ entries.forEach((entry, index) => {
22
+ const score = entry.score !== null ? entry.score.toFixed(1) : 'N/A';
23
+ const issues = entry.issueCategories.length > 0 ? entry.issueCategories.join(', ') : '(none)';
24
+ lines.push('');
25
+ lines.push(`${index + 1}. ${entry.timestamp} score: ${score} verdict: ${entry.verdict}`);
26
+ lines.push(` issues: ${issues}`);
27
+ lines.push(` prompt: ${truncatePromptForDisplay(entry.promptText)}`);
28
+ });
29
+ return lines.join('\n') + '\n';
30
+ }
31
+ //# sourceMappingURL=formatVerboseReport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatVerboseReport.js","sourceRoot":"","sources":["../../src/report/formatVerboseReport.ts"],"names":[],"mappings":"AAEA,iFAAiF;AACjF,4EAA4E;AAC5E,gFAAgF;AAChF,MAAM,yBAAyB,GAAG,GAAG,CAAA;AAErC,SAAS,wBAAwB,CAAC,UAAkB;IAClD,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IACjD,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC7C,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,GAAG,yBAAyB,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAE9H,IAAI,CAAC,WAAW,IAAI,aAAa,KAAK,UAAU,EAAE,CAAC;QACjD,OAAO,UAAU,CAAA;IACnB,CAAC;IAED,OAAO,GAAG,aAAa,iBAAiB,UAAU,CAAC,MAAM,eAAe,CAAA;AAC1E,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAA8B;IAChE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,+FAA+F,CAAA;IACxG,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAA;IAC7C,KAAK,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAA;IAE9C,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;QACnE,MAAM,MAAM,GAAG,KAAK,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;QAE7F,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACd,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,KAAK,KAAK,CAAC,SAAS,YAAY,KAAK,cAAc,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;QAC1F,KAAK,CAAC,IAAI,CAAC,cAAc,MAAM,EAAE,CAAC,CAAA;QAClC,KAAK,CAAC,IAAI,CAAC,cAAc,wBAAwB,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;IACxE,CAAC,CAAC,CAAA;IAEF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;AAChC,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface ReportSummary {
2
+ totalChecks: number;
3
+ verdictCounts: {
4
+ pass: number;
5
+ fail: number;
6
+ error: number;
7
+ };
8
+ averageScore: number | null;
9
+ topIssueCategories: Array<{
10
+ code: string;
11
+ count: number;
12
+ }>;
13
+ }
14
+ export declare function summarizeHistory(dbPath: string): ReportSummary;
15
+ //# sourceMappingURL=summarize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../../src/report/summarize.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5D,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,kBAAkB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC3D;AAQD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAqC9D"}
@@ -0,0 +1,36 @@
1
+ import { openHistoryDb } from '../history/db.js';
2
+ export function summarizeHistory(dbPath) {
3
+ const db = openHistoryDb(dbPath);
4
+ try {
5
+ const rows = db.prepare('SELECT score, verdict, issue_categories FROM history').all();
6
+ const verdictCounts = { pass: 0, fail: 0, error: 0 };
7
+ const scores = [];
8
+ const issueCounts = new Map();
9
+ for (const row of rows) {
10
+ if (row.verdict === 'pass' || row.verdict === 'fail' || row.verdict === 'error') {
11
+ verdictCounts[row.verdict]++;
12
+ }
13
+ if (row.score !== null) {
14
+ scores.push(row.score);
15
+ }
16
+ const categories = JSON.parse(row.issue_categories);
17
+ for (const code of categories) {
18
+ issueCounts.set(code, (issueCounts.get(code) ?? 0) + 1);
19
+ }
20
+ }
21
+ const averageScore = scores.length > 0 ? scores.reduce((a, b) => a + b, 0) / scores.length : null;
22
+ const topIssueCategories = [...issueCounts.entries()]
23
+ .map(([code, count]) => ({ code, count }))
24
+ .sort((a, b) => b.count - a.count || a.code.localeCompare(b.code));
25
+ return {
26
+ totalChecks: rows.length,
27
+ verdictCounts,
28
+ averageScore,
29
+ topIssueCategories,
30
+ };
31
+ }
32
+ finally {
33
+ db.close();
34
+ }
35
+ }
36
+ //# sourceMappingURL=summarize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarize.js","sourceRoot":"","sources":["../../src/report/summarize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAehD,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IAChC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,sDAAsD,CAAC,CAAC,GAAG,EAAkB,CAAA;QAErG,MAAM,aAAa,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAA;QACpD,MAAM,MAAM,GAAa,EAAE,CAAA;QAC3B,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAA;QAE7C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,GAAG,CAAC,OAAO,KAAK,MAAM,IAAI,GAAG,CAAC,OAAO,KAAK,MAAM,IAAI,GAAG,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;gBAChF,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAA;YAC9B,CAAC;YACD,IAAI,GAAG,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;gBACvB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YACxB,CAAC;YACD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAa,CAAA;YAC/D,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;gBAC9B,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;YACzD,CAAC;QACH,CAAC;QAED,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;QAEjG,MAAM,kBAAkB,GAAG,CAAC,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC;aAClD,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;aACzC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QAEpE,OAAO;YACL,WAAW,EAAE,IAAI,CAAC,MAAM;YACxB,aAAa;YACb,YAAY;YACZ,kBAAkB;SACnB,CAAA;IACH,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAA;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface VerboseHistoryEntry {
2
+ timestamp: string;
3
+ score: number | null;
4
+ verdict: 'pass' | 'fail' | 'error';
5
+ issueCategories: string[];
6
+ promptText: string;
7
+ }
8
+ export declare function getVerboseHistory(dbPath: string, limit?: number): VerboseHistoryEntry[];
9
+ //# sourceMappingURL=verboseHistory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verboseHistory.d.ts","sourceRoot":"","sources":["../../src/report/verboseHistory.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAClC,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;CACnB;AAYD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAkBvF"}
@@ -0,0 +1,21 @@
1
+ import { openHistoryDb } from '../history/db.js';
2
+ const NO_RAW_PROMPT_PLACEHOLDER = '[hash-only, no raw prompt stored]';
3
+ export function getVerboseHistory(dbPath, limit) {
4
+ const db = openHistoryDb(dbPath);
5
+ try {
6
+ const query = 'SELECT timestamp, raw_prompt, score, verdict, issue_categories FROM history ORDER BY id DESC' +
7
+ (limit !== undefined ? ' LIMIT @limit' : '');
8
+ const rows = (limit !== undefined ? db.prepare(query).all({ limit }) : db.prepare(query).all());
9
+ return rows.map((row) => ({
10
+ timestamp: row.timestamp,
11
+ score: row.score,
12
+ verdict: row.verdict,
13
+ issueCategories: JSON.parse(row.issue_categories),
14
+ promptText: row.raw_prompt ?? NO_RAW_PROMPT_PLACEHOLDER,
15
+ }));
16
+ }
17
+ finally {
18
+ db.close();
19
+ }
20
+ }
21
+ //# sourceMappingURL=verboseHistory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verboseHistory.js","sourceRoot":"","sources":["../../src/report/verboseHistory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAUhD,MAAM,yBAAyB,GAAG,mCAAmC,CAAA;AAUrE,MAAM,UAAU,iBAAiB,CAAC,MAAc,EAAE,KAAc;IAC9D,MAAM,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IAChC,IAAI,CAAC;QACH,MAAM,KAAK,GACT,8FAA8F;YAC9F,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAC9C,MAAM,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAiB,CAAA;QAE/G,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACxB,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,OAAO,EAAE,GAAG,CAAC,OAAoC;YACjD,eAAe,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAa;YAC7D,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,yBAAyB;SACxD,CAAC,CAAC,CAAA;IACL,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAA;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ export interface WorkerPayload {
2
+ prompt: string;
3
+ source: string;
4
+ cwd: string;
5
+ storeRawPrompt: boolean;
6
+ }
7
+ export declare function runWorker(payload: WorkerPayload, dbPath: string): Promise<void>;
8
+ //# sourceMappingURL=worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,cAAc,EAAE,OAAO,CAAA;CACxB;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkCrF"}
package/dist/worker.js ADDED
@@ -0,0 +1,43 @@
1
+ import { scan } from 'promptvet-core';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { hashPrompt } from './history/hashPrompt.js';
4
+ import { openHistoryDb, insertHistoryEntry } from './history/db.js';
5
+ import { DEFAULT_DB_PATH } from './history/dbPath.js';
6
+ export async function runWorker(payload, dbPath) {
7
+ const result = await scan(payload.prompt);
8
+ const rawPrompt = payload.storeRawPrompt ? payload.prompt : null;
9
+ const promptHash = hashPrompt(payload.prompt);
10
+ const entry = 'error' in result
11
+ ? {
12
+ timestamp: new Date().toISOString(),
13
+ source: payload.source,
14
+ cwd: payload.cwd,
15
+ promptHash,
16
+ rawPrompt,
17
+ score: null,
18
+ verdict: 'error',
19
+ issueCategories: [result.code],
20
+ }
21
+ : {
22
+ timestamp: new Date().toISOString(),
23
+ source: payload.source,
24
+ cwd: payload.cwd,
25
+ promptHash,
26
+ rawPrompt,
27
+ score: result.overall,
28
+ verdict: result.issues.some((issue) => issue.severity === 'critical') ? 'fail' : 'pass',
29
+ issueCategories: result.issues.map((issue) => issue.code),
30
+ };
31
+ const db = openHistoryDb(dbPath);
32
+ try {
33
+ insertHistoryEntry(db, entry);
34
+ }
35
+ finally {
36
+ db.close();
37
+ }
38
+ }
39
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
40
+ const payload = JSON.parse(process.argv[2] ?? '{}');
41
+ await runWorker(payload, DEFAULT_DB_PATH);
42
+ }
43
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAA;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAqB,MAAM,iBAAiB,CAAA;AACtF,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AASrD,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAsB,EAAE,MAAc;IACpE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IACzC,MAAM,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;IAChE,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAE7C,MAAM,KAAK,GACT,OAAO,IAAI,MAAM;QACf,CAAC,CAAC;YACE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,UAAU;YACV,SAAS;YACT,KAAK,EAAE,IAAI;YACX,OAAO,EAAE,OAAO;YAChB,eAAe,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC;SAC/B;QACH,CAAC,CAAC;YACE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,UAAU;YACV,SAAS;YACT,KAAK,EAAE,MAAM,CAAC,OAAO;YACrB,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YACvF,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SAC1D,CAAA;IAEP,MAAM,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IAChC,IAAI,CAAC;QACH,kBAAkB,CAAC,EAAE,EAAE,KAAK,CAAC,CAAA;IAC/B,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAA;IACZ,CAAC;AACH,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/E,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAkB,CAAA;IACpE,MAAM,SAAS,CAAC,OAAO,EAAE,eAAe,CAAC,CAAA;AAC3C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@promptvet/hook",
3
+ "version": "0.1.0",
4
+ "description": "Diary-mode hook: scans prompts with promptvet-core in the background and records results to local history. Not a gate.",
5
+ "keywords": [
6
+ "claude-code",
7
+ "claude-code-hook",
8
+ "pretooluse",
9
+ "userpromptsubmit",
10
+ "prompt-history",
11
+ "prompt-audit-trail",
12
+ "observability",
13
+ "background-scan",
14
+ "llm",
15
+ "ai-agent",
16
+ "sqlite",
17
+ "cli"
18
+ ],
19
+ "license": "MIT",
20
+ "type": "module",
21
+ "bin": {
22
+ "promptvet-hook": "dist/cli/index.js"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "prepublishOnly": "npm run build",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest",
29
+ "test:coverage": "vitest run --coverage",
30
+ "typecheck": "tsc --noEmit"
31
+ },
32
+ "dependencies": {
33
+ "better-sqlite3": "^12.11.1",
34
+ "promptvet-core": "^1.0.8"
35
+ },
36
+ "devDependencies": {
37
+ "@types/better-sqlite3": "^7.6.13",
38
+ "@types/node": "^25.9.3",
39
+ "@vitest/coverage-v8": "^2.1.0",
40
+ "typescript": "^5.5.0",
41
+ "vitest": "^2.1.0"
42
+ },
43
+ "engines": {
44
+ "node": ">=22"
45
+ },
46
+ "files": [
47
+ "dist"
48
+ ],
49
+ "publishConfig": {
50
+ "access": "public"
51
+ }
52
+ }