@jigyasudham/veto 0.8.3 → 1.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 (114) hide show
  1. package/README.md +217 -54
  2. package/dist/adapters/index.js +4 -3
  3. package/dist/agents/executor.js +36 -3
  4. package/dist/cli.js +246 -7
  5. package/dist/context/reader.js +113 -0
  6. package/dist/council/index.js +3 -1
  7. package/dist/memory/local.js +18 -1
  8. package/dist/memory/schema.js +12 -10
  9. package/dist/plugins/loader.js +49 -0
  10. package/dist/router/index.js +2 -2
  11. package/dist/router/learning-updater.js +45 -1
  12. package/dist/server.js +507 -21
  13. package/dist/watcher/index.js +77 -0
  14. package/dist/workflow/pipeline.js +64 -0
  15. package/package.json +12 -3
  16. package/.claude/settings.local.json +0 -9
  17. package/src/adapters/claude.ts +0 -70
  18. package/src/adapters/codex.ts +0 -71
  19. package/src/adapters/gemini.ts +0 -71
  20. package/src/adapters/index.ts +0 -217
  21. package/src/agents/development/api.ts +0 -120
  22. package/src/agents/development/backend.ts +0 -85
  23. package/src/agents/development/coder.ts +0 -213
  24. package/src/agents/development/database.ts +0 -83
  25. package/src/agents/development/debugger.ts +0 -238
  26. package/src/agents/development/devops.ts +0 -86
  27. package/src/agents/development/frontend.ts +0 -85
  28. package/src/agents/development/migration.ts +0 -144
  29. package/src/agents/development/performance.ts +0 -144
  30. package/src/agents/development/refactor.ts +0 -86
  31. package/src/agents/development/reviewer.ts +0 -268
  32. package/src/agents/development/tester.ts +0 -151
  33. package/src/agents/executor.ts +0 -158
  34. package/src/agents/memory/context-manager.ts +0 -171
  35. package/src/agents/memory/decision-logger.ts +0 -160
  36. package/src/agents/memory/knowledge-base.ts +0 -124
  37. package/src/agents/memory/pattern-learner.ts +0 -143
  38. package/src/agents/memory/project-mapper.ts +0 -118
  39. package/src/agents/quality/accessibility.ts +0 -99
  40. package/src/agents/quality/code-quality.ts +0 -115
  41. package/src/agents/quality/compatibility.ts +0 -58
  42. package/src/agents/quality/documentation.ts +0 -105
  43. package/src/agents/quality/error-handling.ts +0 -96
  44. package/src/agents/research/competitor-analyzer.ts +0 -45
  45. package/src/agents/research/cost-analyzer.ts +0 -54
  46. package/src/agents/research/estimator.ts +0 -60
  47. package/src/agents/research/ethics-bias.ts +0 -113
  48. package/src/agents/research/researcher.ts +0 -114
  49. package/src/agents/research/risk-assessor.ts +0 -63
  50. package/src/agents/research/tech-advisor.ts +0 -55
  51. package/src/agents/security/auth.ts +0 -287
  52. package/src/agents/security/dependency-audit.ts +0 -337
  53. package/src/agents/security/penetration.ts +0 -262
  54. package/src/agents/security/privacy.ts +0 -285
  55. package/src/agents/security/scanner.ts +0 -322
  56. package/src/agents/security/secrets.ts +0 -249
  57. package/src/agents/types.ts +0 -66
  58. package/src/agents/workflow/automation.ts +0 -59
  59. package/src/agents/workflow/file-manager.ts +0 -52
  60. package/src/agents/workflow/git-agent.ts +0 -55
  61. package/src/agents/workflow/reporter.ts +0 -51
  62. package/src/agents/workflow/search-agent.ts +0 -40
  63. package/src/agents/workflow/task-coordinator.ts +0 -41
  64. package/src/agents/workflow/task-planner.ts +0 -47
  65. package/src/cli.ts +0 -204
  66. package/src/council/decision-engine.ts +0 -171
  67. package/src/council/devil-advocate.ts +0 -116
  68. package/src/council/index.ts +0 -44
  69. package/src/council/lead-developer.ts +0 -118
  70. package/src/council/legal-compliance.ts +0 -152
  71. package/src/council/product-manager.ts +0 -102
  72. package/src/council/security.ts +0 -172
  73. package/src/council/system-architect.ts +0 -132
  74. package/src/council/types.ts +0 -33
  75. package/src/council/ux-designer.ts +0 -121
  76. package/src/memory/local.ts +0 -305
  77. package/src/memory/schema.ts +0 -174
  78. package/src/memory/sync.ts +0 -274
  79. package/src/router/complexity-scorer.ts +0 -96
  80. package/src/router/context-compressor.ts +0 -74
  81. package/src/router/index.ts +0 -60
  82. package/src/router/learning-updater.ts +0 -271
  83. package/src/router/model-selector.ts +0 -83
  84. package/src/router/rate-monitor.ts +0 -103
  85. package/src/server.ts +0 -1038
  86. package/src/skills/development/skill-api-design.ts +0 -329
  87. package/src/skills/development/skill-auth.ts +0 -271
  88. package/src/skills/development/skill-ci-cd.ts +0 -0
  89. package/src/skills/development/skill-crud.ts +0 -209
  90. package/src/skills/development/skill-db-schema.ts +0 -0
  91. package/src/skills/development/skill-docker.ts +0 -0
  92. package/src/skills/development/skill-env-setup.ts +0 -0
  93. package/src/skills/development/skill-scaffold.ts +0 -323
  94. package/src/skills/intelligence/skill-complexity-score.ts +0 -69
  95. package/src/skills/intelligence/skill-cost-track.ts +0 -39
  96. package/src/skills/intelligence/skill-learning-loop.ts +0 -69
  97. package/src/skills/intelligence/skill-pattern-detect.ts +0 -38
  98. package/src/skills/intelligence/skill-rate-watch.ts +0 -61
  99. package/src/skills/memory/skill-context-compress.ts +0 -98
  100. package/src/skills/memory/skill-cross-sync.ts +0 -104
  101. package/src/skills/memory/skill-decision-log.ts +0 -119
  102. package/src/skills/memory/skill-session-restore.ts +0 -59
  103. package/src/skills/memory/skill-session-save.ts +0 -94
  104. package/src/skills/quality/skill-accessibility.ts +0 -0
  105. package/src/skills/quality/skill-code-review.ts +0 -84
  106. package/src/skills/quality/skill-docs-gen.ts +0 -0
  107. package/src/skills/quality/skill-perf-audit.ts +0 -0
  108. package/src/skills/quality/skill-security-scan.ts +0 -91
  109. package/src/skills/quality/skill-test-suite.ts +0 -290
  110. package/src/skills/workflow/skill-deploy.ts +0 -0
  111. package/src/skills/workflow/skill-git-workflow.ts +0 -0
  112. package/src/skills/workflow/skill-rollback.ts +0 -0
  113. package/src/skills/workflow/skill-task-breakdown.ts +0 -0
  114. package/tsconfig.json +0 -20
@@ -1,271 +0,0 @@
1
- // Records routing outcomes to SQLite and adjusts tier thresholds from historical quality data
2
-
3
- import { randomUUID } from 'node:crypto';
4
- import { getDb } from '../memory/local.js';
5
-
6
- export type LearningStats = {
7
- total_tasks: number;
8
- tier_breakdown: Record<number, { count: number; avg_quality: number }>;
9
- suggested_thresholds: { tier1_max: number; tier2_max: number };
10
- };
11
-
12
- export type AgentPerformanceStat = {
13
- agent: string;
14
- task_count: number;
15
- avg_quality: number;
16
- avg_tokens: number;
17
- low_quality_count: number;
18
- needs_attention: boolean;
19
- };
20
-
21
- export type TaskTypeBreakdown = {
22
- task_type: string;
23
- count: number;
24
- avg_complexity: number;
25
- avg_quality: number;
26
- dominant_tier: number;
27
- under_tiered: boolean;
28
- };
29
-
30
- export type CouncilInsight = {
31
- verdict: string;
32
- task_snippet: string;
33
- debated_at: string;
34
- days_to_debug: number | null;
35
- related_decision: string | null;
36
- };
37
-
38
- export type LearnedThresholds = {
39
- tier1_max: number;
40
- tier2_max: number;
41
- source: 'learned' | 'default';
42
- data_points: number;
43
- };
44
-
45
- // ─── Record ───────────────────────────────────────────────────────────────────
46
-
47
- export function recordOutcome(
48
- taskType: string,
49
- complexity: number,
50
- modelTier: 1 | 2 | 3,
51
- agent: string,
52
- outputQuality: number,
53
- tokensUsed = 0
54
- ): void {
55
- const db = getDb();
56
- db.prepare(
57
- `INSERT INTO learning_data (id, task_type, complexity, model_tier, output_quality, tokens_used, agent)
58
- VALUES (?, ?, ?, ?, ?, ?, ?)`
59
- ).run(randomUUID(), taskType, complexity, modelTier, outputQuality, tokensUsed, agent);
60
- }
61
-
62
- // ─── Stats ────────────────────────────────────────────────────────────────────
63
-
64
- export function getLearningStats(): LearningStats {
65
- const db = getDb();
66
- const rows = db.prepare(
67
- `SELECT model_tier, COUNT(*) as count, AVG(output_quality) as avg_quality
68
- FROM learning_data GROUP BY model_tier`
69
- ).all() as Array<{ model_tier: number; count: number; avg_quality: number }>;
70
-
71
- const total_tasks = rows.reduce((sum, r) => sum + r.count, 0);
72
- const tier_breakdown: Record<number, { count: number; avg_quality: number }> = {};
73
- for (const r of rows) {
74
- tier_breakdown[r.model_tier] = {
75
- count: r.count,
76
- avg_quality: Math.round((r.avg_quality ?? 0) * 100) / 100,
77
- };
78
- }
79
-
80
- const thresholds = computeSuggestedThresholds(tier_breakdown);
81
- return { total_tasks, tier_breakdown, suggested_thresholds: thresholds };
82
- }
83
-
84
- export function getAgentPerformanceStats(): AgentPerformanceStat[] {
85
- const db = getDb();
86
- const rows = db.prepare(`
87
- SELECT agent,
88
- COUNT(*) as task_count,
89
- AVG(output_quality) as avg_quality,
90
- AVG(tokens_used) as avg_tokens,
91
- SUM(CASE WHEN output_quality < 60 THEN 1 ELSE 0 END) as low_quality_count
92
- FROM learning_data
93
- WHERE agent IS NOT NULL
94
- GROUP BY agent
95
- ORDER BY avg_quality ASC
96
- `).all() as Array<{
97
- agent: string; task_count: number; avg_quality: number;
98
- avg_tokens: number; low_quality_count: number;
99
- }>;
100
-
101
- return rows.map(r => ({
102
- agent: r.agent,
103
- task_count: r.task_count,
104
- avg_quality: Math.round(r.avg_quality * 10) / 10,
105
- avg_tokens: Math.round(r.avg_tokens),
106
- low_quality_count: r.low_quality_count,
107
- needs_attention: r.avg_quality < 70 || (r.low_quality_count / r.task_count) > 0.25,
108
- }));
109
- }
110
-
111
- export function getTaskTypeBreakdown(): TaskTypeBreakdown[] {
112
- const db = getDb();
113
- const rows = db.prepare(`
114
- SELECT task_type,
115
- COUNT(*) as count,
116
- AVG(complexity) as avg_complexity,
117
- AVG(output_quality) as avg_quality,
118
- model_tier as dominant_tier
119
- FROM learning_data
120
- GROUP BY task_type, model_tier
121
- HAVING COUNT(*) = (
122
- SELECT MAX(c) FROM (
123
- SELECT COUNT(*) as c FROM learning_data l2
124
- WHERE l2.task_type = learning_data.task_type
125
- GROUP BY l2.model_tier
126
- )
127
- )
128
- ORDER BY count DESC
129
- LIMIT 50
130
- `).all() as Array<{
131
- task_type: string; count: number; avg_complexity: number;
132
- avg_quality: number; dominant_tier: number;
133
- }>;
134
-
135
- return rows.map(r => {
136
- // Under-tiered: task scored high quality but at a tier below what complexity suggests
137
- const expectedTier = r.avg_complexity <= 30 ? 1 : r.avg_complexity <= 70 ? 2 : 3;
138
- const under_tiered = r.dominant_tier < expectedTier && r.avg_quality < 70;
139
- return {
140
- task_type: r.task_type,
141
- count: r.count,
142
- avg_complexity: Math.round(r.avg_complexity),
143
- avg_quality: Math.round(r.avg_quality * 10) / 10,
144
- dominant_tier: r.dominant_tier,
145
- under_tiered,
146
- };
147
- });
148
- }
149
-
150
- export function getCouncilInsights(): CouncilInsight[] {
151
- const db = getDb();
152
-
153
- // Correlate council approvals (GREEN/YELLOW) with debugging sessions that came after
154
- const rows = db.prepare(`
155
- SELECT co.verdict, co.task, co.debated_at,
156
- d.decision as related_decision, d.made_at as decision_at
157
- FROM council_outcomes co
158
- LEFT JOIN decisions d ON d.session_id = co.session_id
159
- AND d.decision LIKE '%debug%' OR d.decision LIKE '%fix%' OR d.decision LIKE '%bug%'
160
- WHERE co.verdict IN ('GREEN', 'YELLOW')
161
- ORDER BY co.debated_at DESC
162
- LIMIT 30
163
- `).all() as Array<{
164
- verdict: string; task: string; debated_at: string;
165
- related_decision: string | null; decision_at: string | null;
166
- }>;
167
-
168
- return rows.map(r => {
169
- let days_to_debug: number | null = null;
170
- if (r.decision_at) {
171
- const debated = new Date(r.debated_at).getTime();
172
- const decided = new Date(r.decision_at).getTime();
173
- days_to_debug = Math.round((decided - debated) / (1000 * 60 * 60 * 24));
174
- }
175
- return {
176
- verdict: r.verdict,
177
- task_snippet: r.task.substring(0, 80),
178
- debated_at: r.debated_at,
179
- days_to_debug,
180
- related_decision: r.related_decision,
181
- };
182
- });
183
- }
184
-
185
- // ─── Threshold Computation ────────────────────────────────────────────────────
186
-
187
- function computeSuggestedThresholds(
188
- tier_breakdown: Record<number, { count: number; avg_quality: number }>
189
- ): { tier1_max: number; tier2_max: number } {
190
- const t1 = tier_breakdown[1];
191
- const t2 = tier_breakdown[2];
192
- const t3 = tier_breakdown[3];
193
-
194
- let tier1_max = 30;
195
- let tier2_max = 70;
196
-
197
- // Tier 2 consistently great → can handle more tasks, raise Tier 1 ceiling slightly
198
- if (t2 && t2.count >= 20 && t2.avg_quality >= 88) tier1_max = 35;
199
- // Tier 2 quality poor → tighten Tier 1 ceiling (fewer tasks escape to T2)
200
- if (t2 && t2.count >= 10 && t2.avg_quality < 65) tier1_max = 25;
201
-
202
- // Tier 3 consistently great → can push harder tasks to T3, lower T2 ceiling
203
- if (t3 && t3.count >= 10 && t3.avg_quality >= 88) tier2_max = 65;
204
- // Tier 3 quality poor → be more conservative, raise T2 ceiling (keep more in T2)
205
- if (t3 && t3.count >= 10 && t3.avg_quality < 65) tier2_max = 75;
206
-
207
- // Tier 1 quality poor → lower its ceiling (fewer tasks stay at T1)
208
- if (t1 && t1.count >= 10 && t1.avg_quality < 65) tier1_max = Math.max(20, tier1_max - 5);
209
-
210
- return { tier1_max, tier2_max };
211
- }
212
-
213
- // ─── Apply ────────────────────────────────────────────────────────────────────
214
-
215
- export function applyLearnedThresholds(): { applied: boolean; thresholds: { tier1_max: number; tier2_max: number }; data_points: number; reason: string } {
216
- const db = getDb();
217
- const total = (db.prepare('SELECT COUNT(*) as c FROM learning_data').get() as { c: number }).c;
218
-
219
- // Need at least 20 data points before adjusting
220
- if (total < 20) {
221
- return {
222
- applied: false,
223
- thresholds: { tier1_max: 30, tier2_max: 70 },
224
- data_points: total,
225
- reason: `Not enough data yet. Need 20 task outcomes, have ${total}. Record outcomes via veto_record_outcome.`,
226
- };
227
- }
228
-
229
- const stats = getLearningStats();
230
- const { tier1_max, tier2_max } = stats.suggested_thresholds;
231
-
232
- // Persist to patterns table so the router picks them up on next call
233
- const now = new Date().toISOString();
234
- for (const [key, val] of [['router.tier1_max', String(tier1_max)], ['router.tier2_max', String(tier2_max)]]) {
235
- const existing = db.prepare('SELECT id FROM patterns WHERE pattern_key = ?').get(key) as { id: string } | undefined;
236
- if (existing) {
237
- db.prepare('UPDATE patterns SET pattern_val = ?, updated_at = ? WHERE pattern_key = ?').run(val, now, key);
238
- } else {
239
- db.prepare('INSERT INTO patterns (id, pattern_key, pattern_val, confidence, seen_count, updated_at) VALUES (?, ?, ?, ?, ?, ?)')
240
- .run(randomUUID(), key, val, 1.0, total, now);
241
- }
242
- }
243
-
244
- return {
245
- applied: true,
246
- thresholds: { tier1_max, tier2_max },
247
- data_points: total,
248
- reason: `Thresholds updated from ${total} task outcomes. Default was 30/70; learned is ${tier1_max}/${tier2_max}.`,
249
- };
250
- }
251
-
252
- export function getLearnedThresholds(): LearnedThresholds {
253
- const db = getDb();
254
- const t1row = db.prepare('SELECT pattern_val, seen_count FROM patterns WHERE pattern_key = ?').get('router.tier1_max') as { pattern_val: string; seen_count: number } | undefined;
255
- const t2row = db.prepare('SELECT pattern_val, seen_count FROM patterns WHERE pattern_key = ?').get('router.tier2_max') as { pattern_val: string; seen_count: number } | undefined;
256
-
257
- if (!t1row || !t2row) {
258
- return { tier1_max: 30, tier2_max: 70, source: 'default', data_points: 0 };
259
- }
260
-
261
- return {
262
- tier1_max: parseInt(t1row.pattern_val, 10),
263
- tier2_max: parseInt(t2row.pattern_val, 10),
264
- source: 'learned',
265
- data_points: t1row.seen_count,
266
- };
267
- }
268
-
269
- export function getSuggestedThresholds(): { tier1_max: number; tier2_max: number } {
270
- return getLearningStats().suggested_thresholds;
271
- }
@@ -1,83 +0,0 @@
1
- // Assigns Tier 1/2/3 and specific model recommendations
2
- // Some agents are permanently locked to a tier regardless of complexity score
3
-
4
- export type AgentType =
5
- // Tier 3 locked — stakes too high
6
- | 'lead-developer' | 'system-architect' | 'security-scanner'
7
- | 'devil-advocate' | 'decision-engine' | 'risk-assessor'
8
- // Tier 2 locked — balanced complexity
9
- | 'coder' | 'tester' | 'reviewer' | 'database' | 'documentation'
10
- // Tier 1 locked — simple/structured operations
11
- | 'file-manager' | 'git-agent' | 'search-agent' | 'secrets' | 'reporter'
12
- // Dynamic — complexity score decides
13
- | 'dynamic';
14
-
15
- export type Tier = 1 | 2 | 3;
16
-
17
- export type ModelRecommendation = {
18
- tier: Tier;
19
- models: { claude: string; gemini: string; codex: string };
20
- reason: string;
21
- agent_locked: boolean;
22
- };
23
-
24
- const TIER3_LOCKED = new Set<AgentType>([
25
- 'lead-developer', 'system-architect', 'security-scanner',
26
- 'devil-advocate', 'decision-engine', 'risk-assessor',
27
- ]);
28
-
29
- const TIER2_LOCKED = new Set<AgentType>([
30
- 'coder', 'tester', 'reviewer', 'database', 'documentation',
31
- ]);
32
-
33
- const TIER1_LOCKED = new Set<AgentType>([
34
- 'file-manager', 'git-agent', 'search-agent', 'secrets', 'reporter',
35
- ]);
36
-
37
- const TIER_MODELS: Record<Tier, { claude: string; gemini: string; codex: string }> = {
38
- 1: { claude: 'claude-haiku-4-5', gemini: 'gemini-2.0-flash', codex: 'gpt-4o-mini' },
39
- 2: { claude: 'claude-sonnet-4-6', gemini: 'gemini-2.0-pro', codex: 'gpt-4o' },
40
- 3: { claude: 'claude-sonnet-4-6', gemini: 'gemini-2.0-advanced', codex: 'gpt-4o' },
41
- };
42
-
43
- export function selectModel(
44
- complexityScore: number,
45
- agentType: AgentType = 'dynamic'
46
- ): ModelRecommendation {
47
- if (TIER3_LOCKED.has(agentType)) {
48
- return {
49
- tier: 3,
50
- models: TIER_MODELS[3],
51
- reason: `${agentType} is always Tier 3 — stakes too high for cheaper models`,
52
- agent_locked: true,
53
- };
54
- }
55
-
56
- if (TIER2_LOCKED.has(agentType)) {
57
- return {
58
- tier: 2,
59
- models: TIER_MODELS[2],
60
- reason: `${agentType} is always Tier 2 — balanced complexity`,
61
- agent_locked: true,
62
- };
63
- }
64
-
65
- if (TIER1_LOCKED.has(agentType)) {
66
- return {
67
- tier: 1,
68
- models: TIER_MODELS[1],
69
- reason: `${agentType} is always Tier 1 — simple structured operations`,
70
- agent_locked: true,
71
- };
72
- }
73
-
74
- const tier: Tier = complexityScore <= 30 ? 1 : complexityScore <= 70 ? 2 : 3;
75
- const reason =
76
- tier === 1
77
- ? `Score ${complexityScore}/100 — simple task, use fastest/cheapest model`
78
- : tier === 2
79
- ? `Score ${complexityScore}/100 — mid-range task, use balanced model`
80
- : `Score ${complexityScore}/100 — complex task, use best available model`;
81
-
82
- return { tier, models: TIER_MODELS[tier], reason, agent_locked: false };
83
- }
@@ -1,103 +0,0 @@
1
- // Tracks request counts per platform per day, exposes routing advice
2
- // Veto cannot read actual API rate limits — it tracks its own routed requests
3
- // and lets users configure their daily limits in patterns table
4
-
5
- import { randomUUID } from 'node:crypto';
6
- import { getDb } from '../memory/local.js';
7
-
8
- export type Platform = 'claude' | 'gemini' | 'codex';
9
-
10
- export type RateLimitEntry = {
11
- platform: Platform;
12
- requests_today: number;
13
- daily_limit: number;
14
- used_percent: number;
15
- resets_at: string;
16
- status: 'normal' | 'warning' | 'critical';
17
- };
18
-
19
- export type RateStatus = {
20
- claude: RateLimitEntry;
21
- gemini: RateLimitEntry;
22
- codex: RateLimitEntry;
23
- updated_at: string;
24
- };
25
-
26
- const DEFAULT_LIMITS: Record<Platform, number> = {
27
- claude: 100,
28
- gemini: 200,
29
- codex: 150,
30
- };
31
-
32
- function getTodayKey(): string {
33
- return new Date().toISOString().slice(0, 10); // YYYY-MM-DD
34
- }
35
-
36
- function getNextResetISO(): string {
37
- const reset = new Date();
38
- reset.setUTCHours(0, 0, 0, 0);
39
- reset.setUTCDate(reset.getUTCDate() + 1);
40
- return reset.toISOString();
41
- }
42
-
43
- export function trackRequest(platform: Platform, count = 1): void {
44
- const db = getDb();
45
- const today = getTodayKey();
46
-
47
- const existing = db.prepare(
48
- 'SELECT id, request_count FROM rate_usage WHERE platform = ? AND date_key = ?'
49
- ).get(platform, today) as { id: string; request_count: number } | undefined;
50
-
51
- if (existing) {
52
- db.prepare(
53
- 'UPDATE rate_usage SET request_count = ?, updated_at = ? WHERE id = ?'
54
- ).run(existing.request_count + count, new Date().toISOString(), existing.id);
55
- } else {
56
- db.prepare(
57
- 'INSERT INTO rate_usage (id, platform, date_key, request_count) VALUES (?, ?, ?, ?)'
58
- ).run(randomUUID(), platform, today, count);
59
- }
60
- }
61
-
62
- function getRequestCount(platform: Platform): number {
63
- const db = getDb();
64
- const row = db.prepare(
65
- 'SELECT request_count FROM rate_usage WHERE platform = ? AND date_key = ?'
66
- ).get(platform, getTodayKey()) as { request_count: number } | undefined;
67
- return row?.request_count ?? 0;
68
- }
69
-
70
- function buildEntry(platform: Platform): RateLimitEntry {
71
- const requests_today = getRequestCount(platform);
72
- const daily_limit = DEFAULT_LIMITS[platform];
73
- const used_percent = Math.min(100, Math.round((requests_today / daily_limit) * 100));
74
- let status: 'normal' | 'warning' | 'critical';
75
- if (used_percent >= 90) status = 'critical';
76
- else if (used_percent >= 70) status = 'warning';
77
- else status = 'normal';
78
- return { platform, requests_today, daily_limit, used_percent, resets_at: getNextResetISO(), status };
79
- }
80
-
81
- export function getRateStatus(): RateStatus {
82
- return {
83
- claude: buildEntry('claude'),
84
- gemini: buildEntry('gemini'),
85
- codex: buildEntry('codex'),
86
- updated_at: new Date().toISOString(),
87
- };
88
- }
89
-
90
- // Returns a fallback platform when the preferred one is at warning/critical level
91
- export function getRoutingAdvice(preferred: Platform): Platform {
92
- const entry = buildEntry(preferred);
93
- if (entry.status === 'critical') {
94
- if (preferred === 'claude') return 'gemini';
95
- if (preferred === 'gemini') return 'codex';
96
- return 'claude';
97
- }
98
- if (entry.status === 'warning' && preferred === 'claude') {
99
- // Tier 1+2 tasks shift to Gemini; caller decides based on tier
100
- return 'gemini';
101
- }
102
- return preferred;
103
- }