@jigyasudham/veto 0.8.3 → 1.0.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 (111) hide show
  1. package/README.md +209 -52
  2. package/dist/agents/executor.js +36 -3
  3. package/dist/cli.js +246 -7
  4. package/dist/context/reader.js +113 -0
  5. package/dist/council/index.js +3 -1
  6. package/dist/plugins/loader.js +49 -0
  7. package/dist/router/index.js +2 -2
  8. package/dist/router/learning-updater.js +45 -1
  9. package/dist/server.js +478 -14
  10. package/dist/watcher/index.js +77 -0
  11. package/dist/workflow/pipeline.js +64 -0
  12. package/package.json +12 -3
  13. package/.claude/settings.local.json +0 -9
  14. package/src/adapters/claude.ts +0 -70
  15. package/src/adapters/codex.ts +0 -71
  16. package/src/adapters/gemini.ts +0 -71
  17. package/src/adapters/index.ts +0 -217
  18. package/src/agents/development/api.ts +0 -120
  19. package/src/agents/development/backend.ts +0 -85
  20. package/src/agents/development/coder.ts +0 -213
  21. package/src/agents/development/database.ts +0 -83
  22. package/src/agents/development/debugger.ts +0 -238
  23. package/src/agents/development/devops.ts +0 -86
  24. package/src/agents/development/frontend.ts +0 -85
  25. package/src/agents/development/migration.ts +0 -144
  26. package/src/agents/development/performance.ts +0 -144
  27. package/src/agents/development/refactor.ts +0 -86
  28. package/src/agents/development/reviewer.ts +0 -268
  29. package/src/agents/development/tester.ts +0 -151
  30. package/src/agents/executor.ts +0 -158
  31. package/src/agents/memory/context-manager.ts +0 -171
  32. package/src/agents/memory/decision-logger.ts +0 -160
  33. package/src/agents/memory/knowledge-base.ts +0 -124
  34. package/src/agents/memory/pattern-learner.ts +0 -143
  35. package/src/agents/memory/project-mapper.ts +0 -118
  36. package/src/agents/quality/accessibility.ts +0 -99
  37. package/src/agents/quality/code-quality.ts +0 -115
  38. package/src/agents/quality/compatibility.ts +0 -58
  39. package/src/agents/quality/documentation.ts +0 -105
  40. package/src/agents/quality/error-handling.ts +0 -96
  41. package/src/agents/research/competitor-analyzer.ts +0 -45
  42. package/src/agents/research/cost-analyzer.ts +0 -54
  43. package/src/agents/research/estimator.ts +0 -60
  44. package/src/agents/research/ethics-bias.ts +0 -113
  45. package/src/agents/research/researcher.ts +0 -114
  46. package/src/agents/research/risk-assessor.ts +0 -63
  47. package/src/agents/research/tech-advisor.ts +0 -55
  48. package/src/agents/security/auth.ts +0 -287
  49. package/src/agents/security/dependency-audit.ts +0 -337
  50. package/src/agents/security/penetration.ts +0 -262
  51. package/src/agents/security/privacy.ts +0 -285
  52. package/src/agents/security/scanner.ts +0 -322
  53. package/src/agents/security/secrets.ts +0 -249
  54. package/src/agents/types.ts +0 -66
  55. package/src/agents/workflow/automation.ts +0 -59
  56. package/src/agents/workflow/file-manager.ts +0 -52
  57. package/src/agents/workflow/git-agent.ts +0 -55
  58. package/src/agents/workflow/reporter.ts +0 -51
  59. package/src/agents/workflow/search-agent.ts +0 -40
  60. package/src/agents/workflow/task-coordinator.ts +0 -41
  61. package/src/agents/workflow/task-planner.ts +0 -47
  62. package/src/cli.ts +0 -204
  63. package/src/council/decision-engine.ts +0 -171
  64. package/src/council/devil-advocate.ts +0 -116
  65. package/src/council/index.ts +0 -44
  66. package/src/council/lead-developer.ts +0 -118
  67. package/src/council/legal-compliance.ts +0 -152
  68. package/src/council/product-manager.ts +0 -102
  69. package/src/council/security.ts +0 -172
  70. package/src/council/system-architect.ts +0 -132
  71. package/src/council/types.ts +0 -33
  72. package/src/council/ux-designer.ts +0 -121
  73. package/src/memory/local.ts +0 -305
  74. package/src/memory/schema.ts +0 -174
  75. package/src/memory/sync.ts +0 -274
  76. package/src/router/complexity-scorer.ts +0 -96
  77. package/src/router/context-compressor.ts +0 -74
  78. package/src/router/index.ts +0 -60
  79. package/src/router/learning-updater.ts +0 -271
  80. package/src/router/model-selector.ts +0 -83
  81. package/src/router/rate-monitor.ts +0 -103
  82. package/src/server.ts +0 -1038
  83. package/src/skills/development/skill-api-design.ts +0 -329
  84. package/src/skills/development/skill-auth.ts +0 -271
  85. package/src/skills/development/skill-ci-cd.ts +0 -0
  86. package/src/skills/development/skill-crud.ts +0 -209
  87. package/src/skills/development/skill-db-schema.ts +0 -0
  88. package/src/skills/development/skill-docker.ts +0 -0
  89. package/src/skills/development/skill-env-setup.ts +0 -0
  90. package/src/skills/development/skill-scaffold.ts +0 -323
  91. package/src/skills/intelligence/skill-complexity-score.ts +0 -69
  92. package/src/skills/intelligence/skill-cost-track.ts +0 -39
  93. package/src/skills/intelligence/skill-learning-loop.ts +0 -69
  94. package/src/skills/intelligence/skill-pattern-detect.ts +0 -38
  95. package/src/skills/intelligence/skill-rate-watch.ts +0 -61
  96. package/src/skills/memory/skill-context-compress.ts +0 -98
  97. package/src/skills/memory/skill-cross-sync.ts +0 -104
  98. package/src/skills/memory/skill-decision-log.ts +0 -119
  99. package/src/skills/memory/skill-session-restore.ts +0 -59
  100. package/src/skills/memory/skill-session-save.ts +0 -94
  101. package/src/skills/quality/skill-accessibility.ts +0 -0
  102. package/src/skills/quality/skill-code-review.ts +0 -84
  103. package/src/skills/quality/skill-docs-gen.ts +0 -0
  104. package/src/skills/quality/skill-perf-audit.ts +0 -0
  105. package/src/skills/quality/skill-security-scan.ts +0 -91
  106. package/src/skills/quality/skill-test-suite.ts +0 -290
  107. package/src/skills/workflow/skill-deploy.ts +0 -0
  108. package/src/skills/workflow/skill-git-workflow.ts +0 -0
  109. package/src/skills/workflow/skill-rollback.ts +0 -0
  110. package/src/skills/workflow/skill-task-breakdown.ts +0 -0
  111. 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
- }