promptup-plugin 0.1.1

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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -0
  3. package/bin/install.cjs +306 -0
  4. package/bin/promptup-plugin +8 -0
  5. package/dist/config.d.ts +40 -0
  6. package/dist/config.js +123 -0
  7. package/dist/db.d.ts +35 -0
  8. package/dist/db.js +327 -0
  9. package/dist/decision-detector.d.ts +11 -0
  10. package/dist/decision-detector.js +47 -0
  11. package/dist/evaluator.d.ts +10 -0
  12. package/dist/evaluator.js +844 -0
  13. package/dist/git-activity-extractor.d.ts +35 -0
  14. package/dist/git-activity-extractor.js +167 -0
  15. package/dist/index.d.ts +12 -0
  16. package/dist/index.js +54 -0
  17. package/dist/pr-report-generator.d.ts +20 -0
  18. package/dist/pr-report-generator.js +421 -0
  19. package/dist/shared/decision-classifier.d.ts +60 -0
  20. package/dist/shared/decision-classifier.js +385 -0
  21. package/dist/shared/decision-score.d.ts +7 -0
  22. package/dist/shared/decision-score.js +31 -0
  23. package/dist/shared/dimensions.d.ts +43 -0
  24. package/dist/shared/dimensions.js +361 -0
  25. package/dist/shared/scoring.d.ts +89 -0
  26. package/dist/shared/scoring.js +161 -0
  27. package/dist/shared/types.d.ts +108 -0
  28. package/dist/shared/types.js +9 -0
  29. package/dist/tools.d.ts +30 -0
  30. package/dist/tools.js +456 -0
  31. package/dist/transcript-parser.d.ts +36 -0
  32. package/dist/transcript-parser.js +201 -0
  33. package/hooks/auto-eval.sh +44 -0
  34. package/hooks/check-update.sh +26 -0
  35. package/hooks/debug-hook.sh +3 -0
  36. package/hooks/hooks.json +36 -0
  37. package/hooks/render-eval.sh +137 -0
  38. package/package.json +60 -0
  39. package/skills/eval/SKILL.md +12 -0
  40. package/skills/pr-report/SKILL.md +37 -0
  41. package/skills/status/SKILL.md +28 -0
  42. package/statusline.sh +46 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gabikreal1
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # promptup-plugin
2
+
3
+ AI coding skill evaluator for Claude Code — 11-dimension scoring, decision intelligence, PR reports.
4
+
5
+ Zero infrastructure. Just SQLite at `~/.promptup/`.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npx promptup-plugin
11
+ ```
12
+
13
+ This installs everything: MCP server, skills, hooks, statusline, and default config. Restart Claude Code to activate.
14
+
15
+ ## What it does
16
+
17
+ **4 MCP Tools:**
18
+
19
+ | Tool | What |
20
+ |------|------|
21
+ | `evaluate_session` | Score a coding session across 11 skill dimensions |
22
+ | `generate_pr_report` | Decision Quality Score (DQS) report for a git branch |
23
+ | `get_status` | Sessions tracked, evaluations, decision counts |
24
+ | `configure` | View/modify all PromptUp settings |
25
+
26
+ **3 Skills:**
27
+ - `/eval` — Run an evaluation
28
+ - `/pr-report` — Generate PR report (optionally post to GitHub)
29
+ - `/status` — Check tracking status
30
+
31
+ **Statusline:** `pupmeter` shows your latest score in the Claude Code status bar.
32
+
33
+ **Hooks:** Passive tracking via PostToolUse + session capture on Stop. Auto-eval on prompt count (configurable). Update check on SessionStart.
34
+
35
+ ## Requirements
36
+
37
+ - Node.js >= 20
38
+ - Claude Code
39
+
40
+ ## Manual setup (alternative)
41
+
42
+ If you prefer not to use the installer:
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "promptup": {
48
+ "command": "node",
49
+ "args": ["~/.promptup/plugin/dist/index.js"]
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ## Configuration
56
+
57
+ Run `configure` with no args to see all settings:
58
+
59
+ ```
60
+ evaluation.auto_trigger off | prompt_count | session_end
61
+ evaluation.interval prompts between auto-evals (default: 10)
62
+ evaluation.weight_profile balanced | greenfield | bugfix | refactor | security_review
63
+ evaluation.feedback_detail brief | standard | detailed
64
+ decisions.signal_filter high | high+medium | all
65
+ pr_report.auto_post true | false
66
+ ```
67
+
68
+ Config file: `~/.promptup/config.json`
69
+
70
+ ## Uninstall
71
+
72
+ ```bash
73
+ npx promptup-plugin --uninstall
74
+ ```
75
+
76
+ ## License
77
+
78
+ MIT
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ // Colors
8
+ const cyan = '\x1b[36m';
9
+ const green = '\x1b[32m';
10
+ const yellow = '\x1b[33m';
11
+ const red = '\x1b[31m';
12
+ const bold = '\x1b[1m';
13
+ const dim = '\x1b[2m';
14
+ const reset = '\x1b[0m';
15
+
16
+ const pkg = require('../package.json');
17
+ const args = process.argv.slice(2);
18
+ const hasGlobal = args.includes('--global') || args.includes('-g');
19
+ const hasLocal = args.includes('--local') || args.includes('-l');
20
+ const hasUninstall = args.includes('--uninstall') || args.includes('-u');
21
+
22
+ const HOME = os.homedir();
23
+ const PLUGIN_DIR = path.join(HOME, '.promptup', 'plugin');
24
+ const DATA_DIR = path.join(HOME, '.promptup');
25
+ const CLAUDE_DIR = path.join(HOME, '.claude');
26
+
27
+ // ─── Banner ─────────────────────────────────────────────────────────────────
28
+
29
+ console.log(`
30
+ ${bold}${cyan} ┌─────────────────────────────────────┐${reset}
31
+ ${bold}${cyan} │ ${green}PROMPTUP${cyan} v${pkg.version} │${reset}
32
+ ${bold}${cyan} │ AI coding skill evaluator for │${reset}
33
+ ${bold}${cyan} │ Claude Code │${reset}
34
+ ${bold}${cyan} └─────────────────────────────────────┘${reset}
35
+ `);
36
+
37
+ // ─── Uninstall ──────────────────────────────────────────────────────────────
38
+
39
+ if (hasUninstall) {
40
+ console.log(`${yellow}Uninstalling PromptUp...${reset}\n`);
41
+
42
+ // Remove skills
43
+ const skillsDir = path.join(CLAUDE_DIR, 'skills');
44
+ for (const skill of ['eval', 'pr-report', 'status']) {
45
+ const dest = path.join(skillsDir, skill);
46
+ if (fs.existsSync(dest)) {
47
+ fs.rmSync(dest, { recursive: true });
48
+ console.log(` ${red}✗${reset} Removed skill: ${skill}`);
49
+ }
50
+ }
51
+
52
+ // Remove MCP from global settings
53
+ const settingsLocal = path.join(CLAUDE_DIR, 'settings.local.json');
54
+ if (fs.existsSync(settingsLocal)) {
55
+ try {
56
+ const settings = JSON.parse(fs.readFileSync(settingsLocal, 'utf-8'));
57
+ if (settings.mcpServers?.promptup) {
58
+ delete settings.mcpServers.promptup;
59
+ fs.writeFileSync(settingsLocal, JSON.stringify(settings, null, 2) + '\n');
60
+ console.log(` ${red}✗${reset} Removed MCP server from settings.local.json`);
61
+ }
62
+ } catch {}
63
+ }
64
+
65
+ console.log(`\n${green}PromptUp uninstalled.${reset}`);
66
+ console.log(`${dim}Data preserved at ${DATA_DIR} — delete manually if desired.${reset}\n`);
67
+ process.exit(0);
68
+ }
69
+
70
+ // ─── Detect scope ───────────────────────────────────────────────────────────
71
+
72
+ let scope = 'global';
73
+ if (hasLocal) scope = 'local';
74
+ if (hasGlobal) scope = 'global';
75
+
76
+ if (!hasLocal && !hasGlobal) {
77
+ // Default to global
78
+ scope = 'global';
79
+ console.log(`${dim}Installing globally (use --local for project-only)${reset}\n`);
80
+ }
81
+
82
+ // ─── Find package root (where npx extracted us) ────────────────────────────
83
+
84
+ const packageRoot = path.resolve(__dirname, '..');
85
+
86
+ // ─── Step 1: Copy plugin to ~/.promptup/plugin ─────────────────────────────
87
+
88
+ console.log(`${bold}Setting up PromptUp...${reset}\n`);
89
+
90
+ // Ensure dirs exist
91
+ fs.mkdirSync(PLUGIN_DIR, { recursive: true });
92
+ fs.mkdirSync(DATA_DIR, { recursive: true });
93
+
94
+ // Copy dist/
95
+ copyDirSync(path.join(packageRoot, 'dist'), path.join(PLUGIN_DIR, 'dist'));
96
+ console.log(` ${green}✓${reset} Installed plugin runtime`);
97
+
98
+ // Copy hooks/
99
+ copyDirSync(path.join(packageRoot, 'hooks'), path.join(PLUGIN_DIR, 'hooks'));
100
+ // Make hooks executable
101
+ for (const f of fs.readdirSync(path.join(PLUGIN_DIR, 'hooks'))) {
102
+ if (f.endsWith('.sh')) {
103
+ fs.chmodSync(path.join(PLUGIN_DIR, 'hooks', f), 0o755);
104
+ }
105
+ }
106
+ console.log(` ${green}✓${reset} Installed hooks`);
107
+
108
+ // Copy skills/
109
+ copyDirSync(path.join(packageRoot, 'skills'), path.join(PLUGIN_DIR, 'skills'));
110
+ console.log(` ${green}✓${reset} Installed skills`);
111
+
112
+ // Copy statusline
113
+ if (fs.existsSync(path.join(packageRoot, 'statusline.sh'))) {
114
+ fs.copyFileSync(
115
+ path.join(packageRoot, 'statusline.sh'),
116
+ path.join(PLUGIN_DIR, 'statusline.sh'),
117
+ );
118
+ fs.chmodSync(path.join(PLUGIN_DIR, 'statusline.sh'), 0o755);
119
+ console.log(` ${green}✓${reset} Installed statusline`);
120
+ }
121
+
122
+ // Copy package.json for version tracking
123
+ fs.copyFileSync(
124
+ path.join(packageRoot, 'package.json'),
125
+ path.join(PLUGIN_DIR, 'package.json'),
126
+ );
127
+
128
+ // ─── Step 2: Install skills to ~/.claude/skills/ ────────────────────────────
129
+
130
+ const skillsDir = path.join(CLAUDE_DIR, 'skills');
131
+ fs.mkdirSync(skillsDir, { recursive: true });
132
+
133
+ for (const skill of ['eval', 'pr-report', 'status']) {
134
+ const src = path.join(PLUGIN_DIR, 'skills', skill);
135
+ const dest = path.join(skillsDir, skill);
136
+ if (fs.existsSync(src)) {
137
+ copyDirSync(src, dest);
138
+ console.log(` ${green}✓${reset} Skill: /${skill}`);
139
+ }
140
+ }
141
+
142
+ // ─── Step 3: Configure MCP server ───────────────────────────────────────────
143
+
144
+ const mcpEntry = {
145
+ command: 'node',
146
+ args: [path.join(PLUGIN_DIR, 'dist', 'index.js')],
147
+ };
148
+
149
+ if (scope === 'global') {
150
+ // Add to ~/.claude/.mcp.json (global MCP config)
151
+ const mcpPath = path.join(CLAUDE_DIR, '.mcp.json');
152
+ const mcp = fs.existsSync(mcpPath)
153
+ ? JSON.parse(fs.readFileSync(mcpPath, 'utf-8'))
154
+ : {};
155
+
156
+ if (!mcp.mcpServers) mcp.mcpServers = {};
157
+ mcp.mcpServers.promptup = mcpEntry;
158
+ fs.writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + '\n');
159
+ console.log(` ${green}✓${reset} MCP server → ~/.claude/.mcp.json (global)`);
160
+ } else {
161
+ // Add to .mcp.json in current directory
162
+ const mcpPath = path.join(process.cwd(), '.mcp.json');
163
+ const mcp = fs.existsSync(mcpPath)
164
+ ? JSON.parse(fs.readFileSync(mcpPath, 'utf-8'))
165
+ : {};
166
+
167
+ if (!mcp.mcpServers) mcp.mcpServers = {};
168
+ mcp.mcpServers.promptup = mcpEntry;
169
+ fs.writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + '\n');
170
+ console.log(` ${green}✓${reset} MCP server → .mcp.json (local)`);
171
+ }
172
+
173
+ // ─── Step 4: Configure hooks ────────────────────────────────────────────────
174
+
175
+ const settingsPath = path.join(CLAUDE_DIR, 'settings.local.json');
176
+ const settings = fs.existsSync(settingsPath)
177
+ ? JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))
178
+ : {};
179
+
180
+ if (!settings.hooks) settings.hooks = {};
181
+
182
+ // SessionStart: update check
183
+ if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
184
+ const hasUpdateHook = settings.hooks.SessionStart.some(
185
+ (h) => h.hooks?.some((hk) => hk.command?.includes('check-update.sh')),
186
+ );
187
+ if (!hasUpdateHook) {
188
+ settings.hooks.SessionStart.push({
189
+ matcher: '',
190
+ hooks: [
191
+ {
192
+ type: 'command',
193
+ command: `bash ${path.join(PLUGIN_DIR, 'hooks', 'check-update.sh')}`,
194
+ async: true,
195
+ },
196
+ ],
197
+ });
198
+ console.log(` ${green}✓${reset} Hook: SessionStart → update check`);
199
+ }
200
+
201
+ // UserPromptSubmit: auto-eval
202
+ if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
203
+ const hasAutoEval = settings.hooks.UserPromptSubmit.some(
204
+ (h) => h.hooks?.some((hk) => hk.command?.includes('auto-eval.sh')),
205
+ );
206
+ if (!hasAutoEval) {
207
+ settings.hooks.UserPromptSubmit.push({
208
+ matcher: '',
209
+ hooks: [
210
+ {
211
+ type: 'command',
212
+ command: `bash ${path.join(PLUGIN_DIR, 'hooks', 'auto-eval.sh')}`,
213
+ async: true,
214
+ },
215
+ ],
216
+ });
217
+ console.log(` ${green}✓${reset} Hook: UserPromptSubmit → auto-eval`);
218
+ }
219
+
220
+ // Statusline
221
+ if (!settings.statusLine) {
222
+ settings.statusLine = {
223
+ type: 'command',
224
+ command: `bash ${path.join(PLUGIN_DIR, 'statusline.sh')}`,
225
+ padding: 2,
226
+ };
227
+ console.log(` ${green}✓${reset} Statusline: pupmeter`);
228
+ }
229
+
230
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
231
+
232
+ // ─── Step 5: Create default config ─────────────────────────────────────────
233
+
234
+ const configPath = path.join(DATA_DIR, 'config.json');
235
+ if (!fs.existsSync(configPath)) {
236
+ const defaultConfig = {
237
+ evaluation: {
238
+ auto_trigger: 'off',
239
+ interval: 10,
240
+ weight_profile: 'balanced',
241
+ timeout_seconds: 120,
242
+ feedback_detail: 'standard',
243
+ },
244
+ dimensions: {
245
+ enabled: ['all'],
246
+ custom_weights: null,
247
+ },
248
+ decisions: {
249
+ signal_filter: 'high+medium',
250
+ show_routine_count: true,
251
+ },
252
+ pr_report: {
253
+ auto_post: false,
254
+ base_branch: 'auto',
255
+ },
256
+ classification: {
257
+ bands: { junior: [0, 40], middle: [41, 70], senior: [71, 100] },
258
+ },
259
+ statusline: {
260
+ enabled: true,
261
+ show_recommendation: true,
262
+ },
263
+ };
264
+ fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + '\n');
265
+ console.log(` ${green}✓${reset} Config: ~/.promptup/config.json`);
266
+ }
267
+
268
+ // ─── Done ───────────────────────────────────────────────────────────────────
269
+
270
+ console.log(`
271
+ ${bold}${green}PromptUp installed!${reset}
272
+
273
+ ${bold}Tools:${reset}
274
+ evaluate_session — Evaluate coding sessions (11 dimensions)
275
+ generate_pr_report — DQS reports for git branches
276
+ get_status — Tracking status & activity
277
+ configure — View/modify settings
278
+
279
+ ${bold}Skills:${reset}
280
+ /eval — Run an evaluation
281
+ /pr-report — Generate PR report
282
+ /status — Check status
283
+
284
+ ${bold}Statusline:${reset}
285
+ pupmeter shows your latest score in the status bar
286
+
287
+ ${bold}Config:${reset}
288
+ ~/.promptup/config.json
289
+
290
+ ${dim}Restart Claude Code to activate. Run with --uninstall to remove.${reset}
291
+ `);
292
+
293
+ // ─── Helpers ────────────────────────────────────────────────────────────────
294
+
295
+ function copyDirSync(src, dest) {
296
+ fs.mkdirSync(dest, { recursive: true });
297
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
298
+ const srcPath = path.join(src, entry.name);
299
+ const destPath = path.join(dest, entry.name);
300
+ if (entry.isDirectory()) {
301
+ copyDirSync(srcPath, destPath);
302
+ } else {
303
+ fs.copyFileSync(srcPath, destPath);
304
+ }
305
+ }
306
+ }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, join } from 'node:path';
5
+ const require = createRequire(import.meta.url);
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ // Load the CJS installer
8
+ require(join(__dirname, 'install.cjs'));
@@ -0,0 +1,40 @@
1
+ /**
2
+ * PromptUp configuration manager.
3
+ *
4
+ * Manages ~/.promptup/config.json with defaults.
5
+ * First run creates the file with recommended settings.
6
+ * Users override what they want — the file IS the documentation.
7
+ */
8
+ export interface PromptUpConfig {
9
+ evaluation: {
10
+ auto_trigger: 'off' | 'prompt_count' | 'session_end';
11
+ interval: number;
12
+ weight_profile: 'balanced' | 'greenfield' | 'bugfix' | 'refactor' | 'security_review';
13
+ timeout_seconds: number;
14
+ feedback_detail: 'brief' | 'standard' | 'detailed';
15
+ };
16
+ dimensions: {
17
+ enabled: string[];
18
+ custom_weights: Record<string, number> | null;
19
+ };
20
+ decisions: {
21
+ signal_filter: 'high' | 'high+medium' | 'all';
22
+ show_routine_count: boolean;
23
+ };
24
+ pr_report: {
25
+ auto_post: boolean;
26
+ base_branch: string;
27
+ };
28
+ classification: {
29
+ bands: Record<string, [number, number]>;
30
+ };
31
+ statusline: {
32
+ enabled: boolean;
33
+ show_recommendation: boolean;
34
+ };
35
+ }
36
+ export declare const DEFAULT_CONFIG: PromptUpConfig;
37
+ export declare function loadConfig(): PromptUpConfig;
38
+ export declare function saveConfig(config: PromptUpConfig): void;
39
+ export declare function updateConfig(updates: Record<string, unknown>): PromptUpConfig;
40
+ export declare function getConfigValue(path: string): unknown;
package/dist/config.js ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * PromptUp configuration manager.
3
+ *
4
+ * Manages ~/.promptup/config.json with defaults.
5
+ * First run creates the file with recommended settings.
6
+ * Users override what they want — the file IS the documentation.
7
+ */
8
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
9
+ import { join, dirname } from 'node:path';
10
+ import { homedir } from 'node:os';
11
+ // ─── Defaults (the PromptUp opinion) ────────────────────────────────────────
12
+ export const DEFAULT_CONFIG = {
13
+ evaluation: {
14
+ auto_trigger: 'off',
15
+ interval: 10,
16
+ weight_profile: 'balanced',
17
+ timeout_seconds: 120,
18
+ feedback_detail: 'standard',
19
+ },
20
+ dimensions: {
21
+ enabled: ['all'],
22
+ custom_weights: null,
23
+ },
24
+ decisions: {
25
+ signal_filter: 'high+medium',
26
+ show_routine_count: true,
27
+ },
28
+ pr_report: {
29
+ auto_post: false,
30
+ base_branch: 'auto',
31
+ },
32
+ classification: {
33
+ bands: {
34
+ junior: [0, 40],
35
+ middle: [41, 70],
36
+ senior: [71, 100],
37
+ },
38
+ },
39
+ statusline: {
40
+ enabled: true,
41
+ show_recommendation: true,
42
+ },
43
+ };
44
+ // ─── Config path ────────────────────────────────────────────────────────────
45
+ function getConfigPath() {
46
+ const dir = process.env.CLAUDE_PLUGIN_DATA ?? join(homedir(), '.promptup');
47
+ return join(dir, 'config.json');
48
+ }
49
+ // ─── Read / Write ───────────────────────────────────────────────────────────
50
+ export function loadConfig() {
51
+ const path = getConfigPath();
52
+ if (!existsSync(path)) {
53
+ // First run — create with defaults
54
+ saveConfig(DEFAULT_CONFIG);
55
+ return { ...DEFAULT_CONFIG };
56
+ }
57
+ try {
58
+ const raw = JSON.parse(readFileSync(path, 'utf-8'));
59
+ // Deep merge with defaults so new fields get populated on upgrade
60
+ return deepMerge(DEFAULT_CONFIG, raw);
61
+ }
62
+ catch {
63
+ return { ...DEFAULT_CONFIG };
64
+ }
65
+ }
66
+ export function saveConfig(config) {
67
+ const path = getConfigPath();
68
+ const dir = dirname(path);
69
+ if (!existsSync(dir))
70
+ mkdirSync(dir, { recursive: true });
71
+ writeFileSync(path, JSON.stringify(config, null, 2) + '\n', 'utf-8');
72
+ }
73
+ export function updateConfig(updates) {
74
+ const config = loadConfig();
75
+ // Apply dot-path updates: "evaluation.interval" = 5
76
+ for (const [key, value] of Object.entries(updates)) {
77
+ setNestedValue(config, key, value);
78
+ }
79
+ saveConfig(config);
80
+ return config;
81
+ }
82
+ export function getConfigValue(path) {
83
+ const config = loadConfig();
84
+ return getNestedValue(config, path);
85
+ }
86
+ // ─── Helpers ────────────────────────────────────────────────────────────────
87
+ function deepMerge(defaults, overrides) {
88
+ const result = { ...defaults };
89
+ for (const key of Object.keys(overrides)) {
90
+ if (typeof defaults[key] === 'object' &&
91
+ defaults[key] !== null &&
92
+ !Array.isArray(defaults[key]) &&
93
+ typeof overrides[key] === 'object' &&
94
+ overrides[key] !== null &&
95
+ !Array.isArray(overrides[key])) {
96
+ result[key] = deepMerge(defaults[key], overrides[key]);
97
+ }
98
+ else {
99
+ result[key] = overrides[key];
100
+ }
101
+ }
102
+ return result;
103
+ }
104
+ function setNestedValue(obj, path, value) {
105
+ const parts = path.split('.');
106
+ let current = obj;
107
+ for (let i = 0; i < parts.length - 1; i++) {
108
+ if (current[parts[i]] === undefined)
109
+ current[parts[i]] = {};
110
+ current = current[parts[i]];
111
+ }
112
+ current[parts[parts.length - 1]] = value;
113
+ }
114
+ function getNestedValue(obj, path) {
115
+ const parts = path.split('.');
116
+ let current = obj;
117
+ for (const part of parts) {
118
+ if (current === undefined || current === null)
119
+ return undefined;
120
+ current = current[part];
121
+ }
122
+ return current;
123
+ }
package/dist/db.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * SQLite database layer for the standalone PromptUp plugin.
3
+ *
4
+ * Fully self-contained — no imports from @promptup/shared or any workspace package.
5
+ * Database lives at ${CLAUDE_PLUGIN_DATA}/promptup.db or ~/.promptup/promptup.db.
6
+ * WAL mode enabled for concurrent reads.
7
+ */
8
+ import Database from 'better-sqlite3';
9
+ import type { SessionRow, EvaluationRow, DecisionRow, GitActivityRow, PRReportRow, MessageRow } from './shared/types.js';
10
+ export declare function getDb(): Database.Database;
11
+ export declare function initDatabase(): void;
12
+ export declare function closeDatabase(): void;
13
+ export declare function insertSession(session: SessionRow): void;
14
+ export declare function getSession(id: string): SessionRow | null;
15
+ export declare function updateSession(id: string, updates: Partial<SessionRow>): void;
16
+ export declare function getRecentSessions(limit?: number): SessionRow[];
17
+ export declare function insertMessages(messages: MessageRow[]): void;
18
+ export declare function getMessagesBySession(sessionId: string, limit?: number, offset?: number): MessageRow[];
19
+ export declare function insertEvaluation(evaluation: EvaluationRow): void;
20
+ export declare function getLatestEvaluation(sessionId?: string): EvaluationRow | null;
21
+ export declare function getEvaluationsBySession(sessionId: string): EvaluationRow[];
22
+ export declare function getRecentEvaluations(limit?: number): EvaluationRow[];
23
+ export declare function insertDecision(decision: DecisionRow): void;
24
+ export declare function getDecisionsBySession(sessionId: string): DecisionRow[];
25
+ export declare function getDecisionsBySessions(sessionIds: string[]): DecisionRow[];
26
+ export declare function insertGitActivity(activity: GitActivityRow): void;
27
+ export declare function getSessionsByBranch(branch: string): string[];
28
+ export declare function getSessionsByTimeRange(from: string, to: string, projectPath?: string): SessionRow[];
29
+ export declare function insertPRReport(report: PRReportRow): void;
30
+ export declare function getPRReportByBranch(branch: string, repo: string): PRReportRow | null;
31
+ export declare function getStats(): {
32
+ sessions: number;
33
+ evaluations: number;
34
+ decisions: number;
35
+ };