@ekkos/cli 0.2.17 → 0.3.3

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 (44) hide show
  1. package/LICENSE +21 -0
  2. package/dist/capture/eviction-client.d.ts +139 -0
  3. package/dist/capture/eviction-client.js +454 -0
  4. package/dist/capture/index.d.ts +2 -0
  5. package/dist/capture/index.js +2 -0
  6. package/dist/capture/jsonl-rewriter.d.ts +96 -0
  7. package/dist/capture/jsonl-rewriter.js +1369 -0
  8. package/dist/capture/transcript-repair.d.ts +50 -0
  9. package/dist/capture/transcript-repair.js +308 -0
  10. package/dist/commands/doctor.js +23 -1
  11. package/dist/commands/run.d.ts +2 -0
  12. package/dist/commands/run.js +1229 -293
  13. package/dist/commands/setup.js +62 -16
  14. package/dist/commands/usage.d.ts +7 -0
  15. package/dist/commands/usage.js +214 -0
  16. package/dist/cron/index.d.ts +7 -0
  17. package/dist/cron/index.js +13 -0
  18. package/dist/cron/promoter.d.ts +70 -0
  19. package/dist/cron/promoter.js +403 -0
  20. package/dist/index.js +24 -3
  21. package/dist/lib/usage-monitor.d.ts +47 -0
  22. package/dist/lib/usage-monitor.js +124 -0
  23. package/dist/lib/usage-parser.d.ts +72 -0
  24. package/dist/lib/usage-parser.js +238 -0
  25. package/dist/restore/RestoreOrchestrator.d.ts +4 -0
  26. package/dist/restore/RestoreOrchestrator.js +118 -30
  27. package/package.json +12 -12
  28. package/templates/cursor-hooks/after-agent-response.sh +0 -0
  29. package/templates/cursor-hooks/before-submit-prompt.sh +0 -0
  30. package/templates/cursor-hooks/stop.sh +0 -0
  31. package/templates/ekkos-manifest.json +2 -2
  32. package/templates/hooks/assistant-response.sh +0 -0
  33. package/templates/hooks/session-start.sh +0 -0
  34. package/templates/hooks/user-prompt-submit.sh +6 -0
  35. package/templates/plan-template.md +0 -0
  36. package/templates/spec-template.md +0 -0
  37. package/templates/agents/README.md +0 -182
  38. package/templates/agents/code-reviewer.md +0 -166
  39. package/templates/agents/debug-detective.md +0 -169
  40. package/templates/agents/ekkOS_Vercel.md +0 -99
  41. package/templates/agents/extension-manager.md +0 -229
  42. package/templates/agents/git-companion.md +0 -185
  43. package/templates/agents/github-test-agent.md +0 -321
  44. package/templates/agents/railway-manager.md +0 -215
@@ -10,6 +10,7 @@ const fs_1 = require("fs");
10
10
  const chalk_1 = __importDefault(require("chalk"));
11
11
  const inquirer_1 = __importDefault(require("inquirer"));
12
12
  const ora_1 = __importDefault(require("ora"));
13
+ const hooks_js_1 = require("./hooks.js");
13
14
  const EKKOS_API_URL = 'https://mcp.ekkos.dev';
14
15
  const CONFIG_DIR = (0, path_1.join)((0, os_1.homedir)(), '.ekkos');
15
16
  const CONFIG_FILE = (0, path_1.join)(CONFIG_DIR, 'config.json');
@@ -207,24 +208,74 @@ async function setupIDE(ide, apiKey, config) {
207
208
  async function setupClaudeCode(apiKey) {
208
209
  const claudeDir = (0, path_1.join)((0, os_1.homedir)(), '.claude');
209
210
  const hooksDir = (0, path_1.join)(claudeDir, 'hooks');
210
- if (!(0, fs_1.existsSync)(hooksDir)) {
211
- (0, fs_1.mkdirSync)(hooksDir, { recursive: true });
211
+ const stateDir = (0, path_1.join)(claudeDir, 'state');
212
+ // Create directories
213
+ (0, fs_1.mkdirSync)(claudeDir, { recursive: true });
214
+ (0, fs_1.mkdirSync)(hooksDir, { recursive: true });
215
+ (0, fs_1.mkdirSync)(stateDir, { recursive: true });
216
+ // Check for existing custom hooks (don't have EKKOS_MANAGED=1 marker)
217
+ const isWindows = (0, os_1.platform)() === 'win32';
218
+ const hookExt = isWindows ? '.ps1' : '.sh';
219
+ const hookFiles = ['user-prompt-submit', 'stop', 'session-start', 'assistant-response'];
220
+ let hasCustomHooks = false;
221
+ for (const hookName of hookFiles) {
222
+ const hookPath = (0, path_1.join)(hooksDir, `${hookName}${hookExt}`);
223
+ if ((0, fs_1.existsSync)(hookPath)) {
224
+ const content = (0, fs_1.readFileSync)(hookPath, 'utf-8');
225
+ if (!content.includes('EKKOS_MANAGED=1')) {
226
+ hasCustomHooks = true;
227
+ break;
228
+ }
229
+ }
230
+ }
231
+ if (hasCustomHooks) {
232
+ // User has custom hooks - don't overwrite, use minimal approach
233
+ console.log(chalk_1.default.yellow(' Detected custom hooks - preserving your hooks'));
234
+ console.log(chalk_1.default.gray(' Run `ekkos hooks install --global` to upgrade to managed hooks'));
235
+ console.log(chalk_1.default.gray(' (This will overwrite existing hooks with full-featured versions)'));
236
+ // Still save API key for existing hooks to use
212
237
  }
238
+ else {
239
+ // No custom hooks OR all managed - safe to install full templates
240
+ try {
241
+ await (0, hooks_js_1.hooksInstall)({ global: true, verbose: false });
242
+ }
243
+ catch (err) {
244
+ // Fallback: if manifest-driven install fails, generate basic hooks
245
+ console.log(chalk_1.default.yellow(' Note: Could not install hooks from templates'));
246
+ console.log(chalk_1.default.gray(' Generating basic hooks instead...'));
247
+ await generateBasicHooks(hooksDir, apiKey);
248
+ }
249
+ }
250
+ // Save API key to config for hooks to use
251
+ const ekkosConfigDir = (0, path_1.join)((0, os_1.homedir)(), '.ekkos');
252
+ (0, fs_1.mkdirSync)(ekkosConfigDir, { recursive: true });
253
+ const configPath = (0, path_1.join)(ekkosConfigDir, 'config.json');
254
+ let existingConfig = {};
255
+ if ((0, fs_1.existsSync)(configPath)) {
256
+ try {
257
+ existingConfig = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf-8'));
258
+ }
259
+ catch { }
260
+ }
261
+ // Update config with API key (hookApiKey for hooks, apiKey for compatibility)
262
+ existingConfig.hookApiKey = apiKey;
263
+ existingConfig.apiKey = apiKey;
264
+ existingConfig.updatedAt = new Date().toISOString();
265
+ (0, fs_1.writeFileSync)(configPath, JSON.stringify(existingConfig, null, 2));
266
+ }
267
+ /**
268
+ * Generate basic inline hooks as fallback when templates aren't available
269
+ */
270
+ async function generateBasicHooks(hooksDir, apiKey) {
213
271
  const isWindows = (0, os_1.platform)() === 'win32';
214
272
  if (isWindows) {
215
- // Windows: PowerShell hooks
216
273
  const promptSubmitHook = generatePromptSubmitHookPS(apiKey);
217
- const promptSubmitPath = (0, path_1.join)(hooksDir, 'user-prompt-submit.ps1');
218
- (0, fs_1.writeFileSync)(promptSubmitPath, promptSubmitHook);
274
+ (0, fs_1.writeFileSync)((0, path_1.join)(hooksDir, 'user-prompt-submit.ps1'), promptSubmitHook);
219
275
  const stopHook = generateStopHookPS(apiKey);
220
- const stopPath = (0, path_1.join)(hooksDir, 'stop.ps1');
221
- (0, fs_1.writeFileSync)(stopPath, stopHook);
222
- // Create wrapper batch files for Windows
223
- (0, fs_1.writeFileSync)((0, path_1.join)(hooksDir, 'user-prompt-submit.cmd'), `@echo off\npowershell -ExecutionPolicy Bypass -File "%~dp0user-prompt-submit.ps1"`);
224
- (0, fs_1.writeFileSync)((0, path_1.join)(hooksDir, 'stop.cmd'), `@echo off\npowershell -ExecutionPolicy Bypass -File "%~dp0stop.ps1"`);
276
+ (0, fs_1.writeFileSync)((0, path_1.join)(hooksDir, 'stop.ps1'), stopHook);
225
277
  }
226
278
  else {
227
- // Unix: Bash hooks
228
279
  const promptSubmitHook = generatePromptSubmitHook(apiKey);
229
280
  const promptSubmitPath = (0, path_1.join)(hooksDir, 'user-prompt-submit.sh');
230
281
  (0, fs_1.writeFileSync)(promptSubmitPath, promptSubmitHook);
@@ -234,11 +285,6 @@ async function setupClaudeCode(apiKey) {
234
285
  (0, fs_1.writeFileSync)(stopPath, stopHook);
235
286
  (0, fs_1.chmodSync)(stopPath, '755');
236
287
  }
237
- // Create state directory
238
- const stateDir = (0, path_1.join)(claudeDir, 'state');
239
- if (!(0, fs_1.existsSync)(stateDir)) {
240
- (0, fs_1.mkdirSync)(stateDir, { recursive: true });
241
- }
242
288
  }
243
289
  async function setupCursor(apiKey) {
244
290
  // Cursor uses .cursorrules for system prompt
@@ -0,0 +1,7 @@
1
+ /**
2
+ * ekkos usage [session-id] [--instance instance-id]
3
+ *
4
+ * Track Claude Code session token usage and context percentage
5
+ * Powered by ccusage (https://ccusage.com)
6
+ */
7
+ export declare function usageCommand(args: string[]): Promise<void>;
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.usageCommand = usageCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const usage_parser_js_1 = require("../lib/usage-parser.js");
9
+ /**
10
+ * ekkos usage [session-id] [--instance instance-id]
11
+ *
12
+ * Track Claude Code session token usage and context percentage
13
+ * Powered by ccusage (https://ccusage.com)
14
+ */
15
+ async function usageCommand(args) {
16
+ const sessionIdArg = args[0];
17
+ const instanceIdFlag = args.indexOf('--instance');
18
+ const listFlag = args.includes('--list');
19
+ // Default instance ID (current project)
20
+ let instanceId = '-Volumes-MacMiniPort-DEV-EKKOS'; // Default to current project
21
+ if (instanceIdFlag !== -1 && args[instanceIdFlag + 1]) {
22
+ instanceId = args[instanceIdFlag + 1];
23
+ }
24
+ // List available sessions
25
+ if (listFlag) {
26
+ await listAvailableSessions(instanceId);
27
+ return;
28
+ }
29
+ // Show usage for specific session
30
+ if (!sessionIdArg) {
31
+ console.log(chalk_1.default.red('❌ Session ID required'));
32
+ console.log(chalk_1.default.gray('\nUsage:'));
33
+ console.log(chalk_1.default.gray(' ekkos usage <session-id> Show usage for session'));
34
+ console.log(chalk_1.default.gray(' ekkos usage --list List available sessions'));
35
+ console.log(chalk_1.default.gray(' ekkos usage <id> --instance <inst> Use specific instance'));
36
+ process.exit(1);
37
+ }
38
+ try {
39
+ console.log(chalk_1.default.gray('Fetching usage data from ccusage...'));
40
+ const usage = await (0, usage_parser_js_1.getSessionUsage)(sessionIdArg, instanceId);
41
+ if (!usage) {
42
+ console.log(chalk_1.default.yellow('⚠️ No usage data found for session'));
43
+ return;
44
+ }
45
+ displaySessionUsage(usage);
46
+ }
47
+ catch (err) {
48
+ console.log(chalk_1.default.red(`❌ Error: ${err.message}`));
49
+ process.exit(1);
50
+ }
51
+ }
52
+ /**
53
+ * Display session usage in formatted table
54
+ */
55
+ function displaySessionUsage(usage) {
56
+ console.log();
57
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
58
+ console.log(chalk_1.default.bold.cyan(` Session Usage: ${usage.session_name}`));
59
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
60
+ console.log();
61
+ // Summary section
62
+ console.log(chalk_1.default.bold('📊 Summary'));
63
+ console.log(chalk_1.default.gray('─'.repeat(80)));
64
+ console.log(` ${chalk_1.default.bold('Session ID:')} ${usage.session_id}`);
65
+ console.log(` ${chalk_1.default.bold('Turn Count:')} ${chalk_1.default.green(usage.turn_count.toString())}`);
66
+ console.log(` ${chalk_1.default.bold('Total Tokens:')} ${formatNumber(usage.total_tokens)}`);
67
+ console.log(` ${chalk_1.default.bold('Avg Context:')} ${formatPercentage(usage.avg_context_percentage)}`);
68
+ console.log(` ${chalk_1.default.bold('Max Context:')} ${formatPercentage(usage.max_context_percentage)}`);
69
+ console.log(` ${chalk_1.default.bold('Started:')} ${formatTimestamp(usage.started_at)}`);
70
+ console.log(` ${chalk_1.default.bold('Last Activity:')} ${formatTimestamp(usage.last_activity)}`);
71
+ console.log();
72
+ // Token breakdown
73
+ console.log(chalk_1.default.bold('🔢 Token Breakdown'));
74
+ console.log(chalk_1.default.gray('─'.repeat(80)));
75
+ console.log(` ${chalk_1.default.bold('Input Tokens:')} ${formatNumber(usage.total_input_tokens)}`);
76
+ console.log(` ${chalk_1.default.bold('Output Tokens:')} ${formatNumber(usage.total_output_tokens)}`);
77
+ console.log(` ${chalk_1.default.bold('Cache Read:')} ${formatNumber(usage.total_cache_read_tokens)} ${chalk_1.default.gray('(90% discount)')}`);
78
+ console.log(` ${chalk_1.default.bold('Cache Creation:')} ${formatNumber(usage.total_cache_creation_tokens)} ${chalk_1.default.gray('(25% premium)')}`);
79
+ console.log();
80
+ // Cost breakdown
81
+ console.log(chalk_1.default.bold('💰 Cost'));
82
+ console.log(chalk_1.default.gray('─'.repeat(80)));
83
+ console.log(` ${chalk_1.default.bold('Total Cost:')} ${chalk_1.default.green('$' + usage.total_cost.toFixed(2))}`);
84
+ console.log(` ${chalk_1.default.bold('Models Used:')} ${chalk_1.default.cyan(usage.models_used.join(', '))}`);
85
+ console.log();
86
+ // ekkOS pattern metrics (if available)
87
+ if (usage.patterns_retrieved !== undefined) {
88
+ console.log(chalk_1.default.bold('🧠 ekkOS Pattern Metrics'));
89
+ console.log(chalk_1.default.gray('─'.repeat(80)));
90
+ console.log(` ${chalk_1.default.bold('Patterns Retrieved:')} ${chalk_1.default.cyan(usage.patterns_retrieved.toString())}`);
91
+ console.log(` ${chalk_1.default.bold('Patterns Applied:')} ${chalk_1.default.green(usage.patterns_applied?.toString() || '0')}`);
92
+ console.log(` ${chalk_1.default.bold('Patterns Learned:')} ${chalk_1.default.yellow(usage.patterns_learned?.toString() || '0')}`);
93
+ console.log(` ${chalk_1.default.bold('Confidence Gain:')} ${chalk_1.default.magenta(`+${((usage.confidence_gain || 0) * 100).toFixed(1)}%`)}`);
94
+ console.log();
95
+ }
96
+ // Turn-by-turn breakdown (if available)
97
+ if (usage.turns.length > 0) {
98
+ console.log(chalk_1.default.bold('📈 Turn-by-Turn Breakdown'));
99
+ console.log(chalk_1.default.gray('─'.repeat(80)));
100
+ console.log(chalk_1.default.gray(' Turn') +
101
+ chalk_1.default.gray(' │ ') +
102
+ chalk_1.default.gray('Context %') +
103
+ chalk_1.default.gray(' │ ') +
104
+ chalk_1.default.gray('Input') +
105
+ chalk_1.default.gray(' │ ') +
106
+ chalk_1.default.gray('Output') +
107
+ chalk_1.default.gray(' │ ') +
108
+ chalk_1.default.gray('Cache Read') +
109
+ chalk_1.default.gray(' │ ') +
110
+ chalk_1.default.gray('Total'));
111
+ console.log(chalk_1.default.gray('─'.repeat(80)));
112
+ for (const turn of usage.turns) {
113
+ const turnStr = turn.turn_number.toString().padStart(4);
114
+ const contextStr = formatPercentage(turn.context_percentage).padStart(10);
115
+ const inputStr = formatNumber(turn.input_tokens).padStart(7);
116
+ const outputStr = formatNumber(turn.output_tokens).padStart(7);
117
+ const cacheStr = formatNumber(turn.cache_read_tokens).padStart(11);
118
+ const totalStr = formatNumber(turn.total_tokens).padStart(8);
119
+ // Color code context percentage
120
+ const contextColor = getContextColor(turn.context_percentage);
121
+ console.log(` ${turnStr} │ ${contextColor(contextStr)} │ ${inputStr} │ ${outputStr} │ ${cacheStr} │ ${totalStr}`);
122
+ }
123
+ console.log();
124
+ }
125
+ // VM validation status
126
+ const isVmWorking = validateVmBehavior(usage);
127
+ if (isVmWorking) {
128
+ console.log(chalk_1.default.green.bold('✅ VM Working: Context staying in constant band (good!)'));
129
+ }
130
+ else {
131
+ console.log(chalk_1.default.yellow.bold('⚠️ VM Warning: Context growing linearly (check proxy eviction)'));
132
+ }
133
+ console.log();
134
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
135
+ console.log();
136
+ }
137
+ /**
138
+ * List available sessions for an instance
139
+ */
140
+ async function listAvailableSessions(instanceId) {
141
+ console.log();
142
+ console.log(chalk_1.default.bold.cyan('📋 Available Sessions (via ccusage)'));
143
+ console.log(chalk_1.default.gray('─'.repeat(80)));
144
+ console.log(chalk_1.default.gray('Fetching sessions...'));
145
+ const sessions = await (0, usage_parser_js_1.getAllSessions)(instanceId);
146
+ if (sessions.length === 0) {
147
+ console.log(chalk_1.default.yellow(' No sessions found'));
148
+ console.log();
149
+ return;
150
+ }
151
+ for (const session of sessions) {
152
+ console.log(` ${chalk_1.default.bold(session.session_id)}`);
153
+ console.log(` ${chalk_1.default.gray('Turns:')} ${chalk_1.default.green(session.turn_count.toString())}, ${chalk_1.default.gray('Cost:')} ${chalk_1.default.green('$' + session.total_cost.toFixed(2))}, ${chalk_1.default.gray('Context:')} ${formatPercentage(session.avg_context_percentage)}`);
154
+ console.log(` ${chalk_1.default.gray('Last:')} ${session.last_activity}, ${chalk_1.default.gray('Models:')} ${chalk_1.default.cyan(session.models_used.join(', '))}`);
155
+ console.log();
156
+ }
157
+ }
158
+ /**
159
+ * Validate VM behavior based on context growth pattern
160
+ */
161
+ function validateVmBehavior(usage) {
162
+ if (usage.turn_count < 5) {
163
+ return true; // Too few turns to judge
164
+ }
165
+ // Check if context is staying in a constant band (±10%)
166
+ const contextValues = usage.turns.map(t => t.context_percentage);
167
+ const min = Math.min(...contextValues);
168
+ const max = Math.max(...contextValues);
169
+ const range = max - min;
170
+ // If range is small (< 15%), VM is working
171
+ return range < 15;
172
+ }
173
+ /**
174
+ * Format numbers with commas
175
+ */
176
+ function formatNumber(num) {
177
+ return num.toLocaleString('en-US');
178
+ }
179
+ /**
180
+ * Format percentage
181
+ */
182
+ function formatPercentage(pct) {
183
+ return `${pct.toFixed(1)}%`;
184
+ }
185
+ /**
186
+ * Format timestamp
187
+ */
188
+ function formatTimestamp(ts) {
189
+ try {
190
+ const date = new Date(ts);
191
+ return date.toLocaleString('en-US', {
192
+ month: 'short',
193
+ day: 'numeric',
194
+ hour: 'numeric',
195
+ minute: '2-digit',
196
+ hour12: true
197
+ });
198
+ }
199
+ catch {
200
+ return ts;
201
+ }
202
+ }
203
+ /**
204
+ * Get color for context percentage
205
+ */
206
+ function getContextColor(pct) {
207
+ if (pct < 30)
208
+ return chalk_1.default.green;
209
+ if (pct < 50)
210
+ return chalk_1.default.yellow;
211
+ if (pct < 70)
212
+ return chalk_1.default.hex('#FFA500'); // Orange
213
+ return chalk_1.default.red;
214
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * ekkOS Cron Jobs
3
+ * ================
4
+ *
5
+ * Background jobs for ekkOS maintenance and evolution.
6
+ */
7
+ export { evaluatePromotions, queryPatternStats, writePatchConfig, type PromoterConfig, type PromotionResult, type PromotedPatchConfig, type PromotedPattern, } from './promoter.js';
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ /**
3
+ * ekkOS Cron Jobs
4
+ * ================
5
+ *
6
+ * Background jobs for ekkOS maintenance and evolution.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.writePatchConfig = exports.queryPatternStats = exports.evaluatePromotions = void 0;
10
+ var promoter_js_1 = require("./promoter.js");
11
+ Object.defineProperty(exports, "evaluatePromotions", { enumerable: true, get: function () { return promoter_js_1.evaluatePromotions; } });
12
+ Object.defineProperty(exports, "queryPatternStats", { enumerable: true, get: function () { return promoter_js_1.queryPatternStats; } });
13
+ Object.defineProperty(exports, "writePatchConfig", { enumerable: true, get: function () { return promoter_js_1.writePatchConfig; } });
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ekkOS PROMETHEUS Pattern Promoter
4
+ * ==================================
5
+ *
6
+ * Daily cron job that evaluates patterns for promotion to constitutional memory.
7
+ *
8
+ * Constitutional patterns are injected into the system prompt via @ekkos/patch,
9
+ * making them "instincts" that don't require retrieval.
10
+ *
11
+ * Promotion Criteria (default):
12
+ * - Success rate ≥ 85%
13
+ * - Applications ≥ 5
14
+ * - Skip rate ≤ 10%
15
+ * - Confidence ≥ 70%
16
+ * - Used within last 30 days
17
+ * - Used in ≥ 2 unique sessions
18
+ *
19
+ * Usage:
20
+ * npx ekkos-promote # Run promotion evaluation
21
+ * npx ekkos-promote --dry-run # Preview without applying changes
22
+ * npx ekkos-promote --user <uuid> # Promote for specific user
23
+ *
24
+ * Schedule via launchd (macOS) or cron:
25
+ * 0 6 * * * /path/to/node /path/to/ekkos-promote
26
+ */
27
+ import { SupabaseClient } from '@supabase/supabase-js';
28
+ import { type PatternStats } from '@ekkos/prometheus';
29
+ interface PromoterConfig {
30
+ supabaseUrl: string;
31
+ supabaseKey: string;
32
+ dryRun: boolean;
33
+ userId?: string;
34
+ patchConfigPath: string;
35
+ verbose: boolean;
36
+ }
37
+ interface PromotionResult {
38
+ evaluated: number;
39
+ promoted: number;
40
+ demoted: number;
41
+ patterns: Array<{
42
+ patternId: string;
43
+ title: string;
44
+ action: 'promoted' | 'demoted' | 'unchanged';
45
+ score: number;
46
+ reason?: string;
47
+ }>;
48
+ patchConfig?: PromotedPatchConfig;
49
+ }
50
+ interface PromotedPattern {
51
+ id: string;
52
+ title: string;
53
+ problem: string;
54
+ solution: string;
55
+ promotedAt: string;
56
+ successRate: number;
57
+ appliedCount: number;
58
+ tags?: string[];
59
+ }
60
+ interface PromotedPatchConfig {
61
+ version: string;
62
+ generatedAt: string;
63
+ promotedPatterns: PromotedPattern[];
64
+ totalPatterns: number;
65
+ }
66
+ declare function queryPatternStats(supabase: SupabaseClient, userId?: string): Promise<PatternStats[]>;
67
+ declare function evaluatePromotions(supabase: SupabaseClient, config: PromoterConfig): Promise<PromotionResult>;
68
+ declare function writePatchConfig(config: PromotedPatchConfig, outputPath: string): void;
69
+ export { evaluatePromotions, queryPatternStats, writePatchConfig };
70
+ export type { PromoterConfig, PromotionResult, PromotedPatchConfig, PromotedPattern };