ai-cto 0.1.0 → 0.1.2

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.
package/bin/cli.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import readline from 'readline';
4
+ import { fileURLToPath } from 'url';
5
+ import fs from 'fs';
4
6
 
5
7
  // ---------------------------------------------------------
6
8
  // Game Data Definition
@@ -485,7 +487,20 @@ async function main() {
485
487
  process.exit(0);
486
488
  }
487
489
 
488
- if (process.argv[1] && process.argv[1].endsWith('cli.js')) {
490
+ function isMainModule() {
491
+ if (!process.argv[1]) return false;
492
+ try {
493
+ const mainPath = fs.realpathSync(process.argv[1]);
494
+ const currentPath = fileURLToPath(import.meta.url);
495
+ if (mainPath === currentPath) return true;
496
+ } catch {
497
+ // ignore
498
+ }
499
+ const argv1 = process.argv[1];
500
+ return argv1.endsWith('cli.js') || argv1.endsWith('ai-cto') || argv1.endsWith('aicto');
501
+ }
502
+
503
+ if (isMainModule()) {
489
504
  main().catch(console.error);
490
505
  }
491
506
 
package/docs/STACK.md CHANGED
@@ -33,7 +33,7 @@
33
33
  ## 🗂️ Project Directory Structure
34
34
 
35
35
  ```
36
- aicto/
36
+ ai-cto/
37
37
  ├── src/
38
38
  │ ├── assets/ # Sound FX, custom graphics, and icons
39
39
  │ │ ├── icons/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-cto",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Run an AI startup for 5 minutes (Terminal CLI Edition)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,9 @@
8
8
  },
9
9
  "scripts": {
10
10
  "play": "node ./bin/cli.js",
11
- "test": "node ./scripts/playtest.js"
11
+ "test": "node --test tests/*.test.js",
12
+ "test:playtest": "node ./scripts/playtest.js",
13
+ "test:exhaustive": "node ./scripts/exhaustive.js"
12
14
  },
13
15
  "keywords": [
14
16
  "cli",
@@ -17,4 +19,4 @@
17
19
  "simulation"
18
20
  ],
19
21
  "license": "MIT"
20
- }
22
+ }
@@ -0,0 +1,277 @@
1
+ import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
2
+ import { fileURLToPath } from 'url';
3
+ import os from 'os';
4
+ import { MODELS, REQUESTS } from '../bin/cli.js';
5
+
6
+ // ---------------------------------------------------------
7
+ // Helper: Precompute Expected Values for (Request, Model)
8
+ // ---------------------------------------------------------
9
+ function computeStatsMatrix() {
10
+ const matrix = []; // matrix[reqIdx][modelIdx] = { expProfit, expTrustDelta, prob, winProfit, lossProfit }
11
+
12
+ for (let r = 0; r < REQUESTS.length; r++) {
13
+ const req = REQUESTS[r];
14
+ const reqRow = [];
15
+
16
+ for (let m = 0; m < MODELS.length; m++) {
17
+ const model = MODELS[m];
18
+
19
+ const latencyRatio = model.latency / req.sla;
20
+ const latencyMet = model.latency <= req.sla;
21
+ const gaps = [
22
+ req.req.quality - model.cap.quality,
23
+ req.req.coding - model.cap.coding,
24
+ req.req.reasoning - model.cap.reasoning,
25
+ req.req.vision - model.cap.vision,
26
+ req.req.context - model.cap.context,
27
+ ];
28
+ const maxGap = Math.max(0, ...gaps);
29
+
30
+ let successProb = 1.0;
31
+ if (latencyRatio > 2.0) {
32
+ successProb = 0.0;
33
+ } else if (!latencyMet) {
34
+ successProb *= 0.50;
35
+ }
36
+
37
+ if (maxGap > 0) {
38
+ if (maxGap <= 1) successProb *= 0.75;
39
+ else if (maxGap <= 2) successProb *= 0.35;
40
+ else successProb *= 0.05;
41
+ }
42
+
43
+ const isOverEngineered = model.id === 'large' && req.diff === 'Low';
44
+ const overEngineeringPenalty = isOverEngineered ? 3.00 : 0.00;
45
+
46
+ const winProfit = req.reward - model.cost - overEngineeringPenalty;
47
+ const lossProfit = req.penalty - model.cost;
48
+ const expProfit = successProb * winProfit + (1 - successProb) * lossProfit;
49
+
50
+ let winTrust = 3;
51
+ if (isOverEngineered) winTrust = -1;
52
+ else if (model.id === 'nano' && req.diff !== 'Low') winTrust = 4;
53
+ else if (maxGap > 0) winTrust = 2;
54
+ else if (!latencyMet) winTrust = 1;
55
+
56
+ let lossTrust = -6;
57
+ if (latencyRatio > 2.0) lossTrust = -8;
58
+
59
+ const expTrustDelta = successProb * winTrust + (1 - successProb) * lossTrust;
60
+
61
+ reqRow.push({
62
+ modelId: model.id,
63
+ modelName: model.name,
64
+ prob: successProb,
65
+ winProfit,
66
+ lossProfit,
67
+ expProfit,
68
+ winTrust,
69
+ lossTrust,
70
+ expTrustDelta,
71
+ });
72
+ }
73
+
74
+ matrix.push(reqRow);
75
+ }
76
+
77
+ return matrix;
78
+ }
79
+
80
+ const MODEL_NAMES = ['Nano', 'Mini', 'Large'];
81
+
82
+ // ---------------------------------------------------------
83
+ // Worker Execution Logic
84
+ // ---------------------------------------------------------
85
+ if (!isMainThread) {
86
+ const { matrix, taskPrefixes } = workerData;
87
+ const numRequests = matrix.length;
88
+
89
+ let totalEvaluated = 0;
90
+ let sumCapital = 0;
91
+
92
+ let maxCapital = -Infinity;
93
+ let maxPath = null;
94
+
95
+ let minCapital = Infinity;
96
+ let minPath = null;
97
+
98
+ const ranks = { S: 0, A: 0, B: 0, C: 0, Bankrupt: 0 };
99
+
100
+ const currentPath = new Array(numRequests);
101
+
102
+ for (const prefix of taskPrefixes) {
103
+ const prefixLen = prefix.length;
104
+ for (let p = 0; p < prefixLen; p++) {
105
+ currentPath[p] = prefix[p];
106
+ }
107
+
108
+ function search(reqIdx, currentCap, currentTrust) {
109
+ if (reqIdx === numRequests) {
110
+ totalEvaluated++;
111
+ sumCapital += currentCap;
112
+
113
+ if (currentCap > maxCapital) {
114
+ maxCapital = currentCap;
115
+ maxPath = currentPath.slice();
116
+ }
117
+ if (currentCap < minCapital) {
118
+ minCapital = currentCap;
119
+ minPath = currentPath.slice();
120
+ }
121
+
122
+ if (currentCap > 250 && currentTrust >= 90) ranks.S++;
123
+ else if (currentCap > 150) ranks.A++;
124
+ else if (currentCap > 50) ranks.B++;
125
+ else if (currentCap < 0) ranks.Bankrupt++;
126
+ else ranks.C++;
127
+
128
+ return;
129
+ }
130
+
131
+ const reqRow = matrix[reqIdx];
132
+ for (let m = 0; m < 3; m++) {
133
+ currentPath[reqIdx] = m;
134
+ const cell = reqRow[m];
135
+ const nextCap = currentCap + cell.expProfit;
136
+ const nextTrust = Math.min(100, Math.max(0, currentTrust + cell.expTrustDelta));
137
+
138
+ search(reqIdx + 1, nextCap, nextTrust);
139
+ }
140
+ }
141
+
142
+ let initialCap = 100.00;
143
+ let initialTrust = 85;
144
+
145
+ for (let p = 0; p < prefixLen; p++) {
146
+ const m = prefix[p];
147
+ const cell = matrix[p][m];
148
+ initialCap += cell.expProfit;
149
+ initialTrust = Math.min(100, Math.max(0, initialTrust + cell.expTrustDelta));
150
+ }
151
+
152
+ search(prefixLen, initialCap, initialTrust);
153
+ }
154
+
155
+ parentPort.postMessage({
156
+ totalEvaluated,
157
+ sumCapital,
158
+ maxCapital,
159
+ maxPath,
160
+ minCapital,
161
+ minPath,
162
+ ranks,
163
+ });
164
+ }
165
+
166
+ // ---------------------------------------------------------
167
+ // Main Thread Master Orchestrator
168
+ // ---------------------------------------------------------
169
+ if (isMainThread) {
170
+ const startTime = Date.now();
171
+ const matrix = computeStatsMatrix();
172
+
173
+ console.log('================================================================');
174
+ console.log('⚡ AI CTO — EXHAUSTIVE DECISION SPACE EXPLORER (3,486,784,401 PATHS)');
175
+ console.log('================================================================\n');
176
+
177
+ // Build tasks for worker distribution (prefix of depth 2: 3^2 = 9 sub-tasks)
178
+ const taskPrefixes = [];
179
+ for (let m0 = 0; m0 < 3; m0++) {
180
+ for (let m1 = 0; m1 < 3; m1++) {
181
+ taskPrefixes.push([m0, m1]);
182
+ }
183
+ }
184
+
185
+ const numCores = Math.min(os.cpus().length || 4, taskPrefixes.length);
186
+ console.log(`🚀 Spawning ${numCores} Worker Threads to search 3^20 combinations...\n`);
187
+
188
+ // Distribute 9 tasks evenly across workers
189
+ const workerTasks = Array.from({ length: numCores }, () => []);
190
+ taskPrefixes.forEach((task, idx) => {
191
+ workerTasks[idx % numCores].push(task);
192
+ });
193
+
194
+ const workerPromises = workerTasks.map((tasks, i) => {
195
+ return new Promise((resolve, reject) => {
196
+ const __filename = fileURLToPath(import.meta.url);
197
+ const worker = new Worker(__filename, {
198
+ workerData: { matrix, taskPrefixes: tasks },
199
+ });
200
+
201
+ worker.on('message', resolve);
202
+ worker.on('error', reject);
203
+ worker.on('exit', (code) => {
204
+ if (code !== 0) reject(new Error(`Worker ${i} exited with code ${code}`));
205
+ });
206
+ });
207
+ });
208
+
209
+ Promise.all(workerPromises).then((results) => {
210
+ const totalTimeMs = Date.now() - startTime;
211
+
212
+ let globalEvaluated = 0;
213
+ let globalSumCapital = 0;
214
+
215
+ let globalMaxCapital = -Infinity;
216
+ let globalMaxPath = null;
217
+
218
+ let globalMinCapital = Infinity;
219
+ let globalMinPath = null;
220
+
221
+ const globalRanks = { S: 0, A: 0, B: 0, C: 0, Bankrupt: 0 };
222
+
223
+ for (const res of results) {
224
+ globalEvaluated += res.totalEvaluated;
225
+ globalSumCapital += res.sumCapital;
226
+
227
+ if (res.maxCapital > globalMaxCapital) {
228
+ globalMaxCapital = res.maxCapital;
229
+ globalMaxPath = res.maxPath;
230
+ }
231
+ if (res.minCapital < globalMinCapital) {
232
+ globalMinCapital = res.minCapital;
233
+ globalMinPath = res.minPath;
234
+ }
235
+
236
+ globalRanks.S += res.ranks.S;
237
+ globalRanks.A += res.ranks.A;
238
+ globalRanks.B += res.ranks.B;
239
+ globalRanks.C += res.ranks.C;
240
+ globalRanks.Bankrupt += res.ranks.Bankrupt;
241
+ }
242
+
243
+ const avgCapital = globalSumCapital / globalEvaluated;
244
+ const throughput = (globalEvaluated / (totalTimeMs / 1000)).toLocaleString('en-US', { maximumFractionDigits: 0 });
245
+
246
+ console.log('================================================================');
247
+ console.log('📊 EXHAUSTIVE SIMULATION RESULTS');
248
+ console.log('================================================================');
249
+ console.log(` Total Combinations Tested: ${globalEvaluated.toLocaleString()} (3^20)`);
250
+ console.log(` Time Elapsed: ${(totalTimeMs / 1000).toFixed(2)} seconds`);
251
+ console.log(` Processing Throughput: ${throughput} evaluations/sec`);
252
+ console.log('----------------------------------------------------------------');
253
+
254
+ console.log(`\n🏆 BEST STRATEGY (GLOBAL MAX EXPECTED CAPITAL):`);
255
+ console.log(` Expected Capital: $${globalMaxCapital.toFixed(2)}`);
256
+ console.log(` Decision Sequence: [${globalMaxPath.map(m => MODEL_NAMES[m]).join(', ')}]`);
257
+
258
+ console.log(`\n💥 WORST STRATEGY (GLOBAL MIN EXPECTED CAPITAL):`);
259
+ console.log(` Expected Capital: $${globalMinCapital.toFixed(2)}`);
260
+ console.log(` Decision Sequence: [${globalMinPath.map(m => MODEL_NAMES[m]).join(', ')}]`);
261
+
262
+ console.log(`\n📈 STATISTICAL SUMMARY ACROSS ALL COMBINATIONS:`);
263
+ console.log(` Average Expected Capital: $${avgCapital.toFixed(2)}`);
264
+ console.log(` Ranks Breakdown:`);
265
+ console.log(` - S Tier (Unicorn) : ${globalRanks.S.toLocaleString().padStart(13)} (${((globalRanks.S / globalEvaluated) * 100).toFixed(2)}%)`);
266
+ console.log(` - A Tier (Profitable): ${globalRanks.A.toLocaleString().padStart(13)} (${((globalRanks.A / globalEvaluated) * 100).toFixed(2)}%)`);
267
+ console.log(` - B Tier (Ops) : ${globalRanks.B.toLocaleString().padStart(13)} (${((globalRanks.B / globalEvaluated) * 100).toFixed(2)}%)`);
268
+ console.log(` - C Tier (Break-even): ${globalRanks.C.toLocaleString().padStart(13)} (${((globalRanks.C / globalEvaluated) * 100).toFixed(2)}%)`);
269
+ console.log(` - Bankrupt Founder : ${globalRanks.Bankrupt.toLocaleString().padStart(13)} (${((globalRanks.Bankrupt / globalEvaluated) * 100).toFixed(2)}%)`);
270
+ console.log('================================================================\n');
271
+
272
+ process.exit(0);
273
+ }).catch((err) => {
274
+ console.error('Fatal Error during exhaustive simulation:', err);
275
+ process.exit(1);
276
+ });
277
+ }
@@ -0,0 +1,36 @@
1
+
2
+ > ai-cto@0.1.1 test:exhaustive
3
+ > node ./scripts/exhaustive.js
4
+
5
+ ================================================================
6
+ ⚡ AI CTO — EXHAUSTIVE DECISION SPACE EXPLORER (3,486,784,401 PATHS)
7
+ ================================================================
8
+
9
+ 🚀 Spawning 8 Worker Threads to search 3^20 combinations...
10
+
11
+ ================================================================
12
+ 📊 EXHAUSTIVE SIMULATION RESULTS
13
+ ================================================================
14
+ Total Combinations Tested: 3,486,784,401 (3^20)
15
+ Time Elapsed: 22.50 seconds
16
+ Processing Throughput: 154,975,083 evaluations/sec
17
+ ----------------------------------------------------------------
18
+
19
+ 🏆 BEST STRATEGY (GLOBAL MAX EXPECTED CAPITAL):
20
+ Expected Capital: $652.87
21
+ Decision Sequence: [Nano, Nano, Mini, Mini, Mini, Nano, Mini, Mini, Large, Large, Nano, Large, Large, Nano, Large, Large, Mini, Large, Mini, Large]
22
+
23
+ 💥 WORST STRATEGY (GLOBAL MIN EXPECTED CAPITAL):
24
+ Expected Capital: $-1199.22
25
+ Decision Sequence: [Large, Large, Large, Nano, Nano, Large, Nano, Large, Nano, Nano, Large, Nano, Nano, Large, Mini, Nano, Large, Nano, Nano, Nano]
26
+
27
+ 📈 STATISTICAL SUMMARY ACROSS ALL COMBINATIONS:
28
+ Average Expected Capital: $-341.13
29
+ Ranks Breakdown:
30
+ - S Tier (Unicorn) : 16,393,131 (0.47%)
31
+ - A Tier (Profitable): 95,170,922 (2.73%)
32
+ - B Tier (Ops) : 138,287,106 (3.97%)
33
+ - C Tier (Break-even): 101,786,392 (2.92%)
34
+ - Bankrupt Founder : 3,135,146,850 (89.92%)
35
+ ================================================================
36
+
@@ -0,0 +1,82 @@
1
+
2
+ > ai-cto@0.1.1 test:playtest
3
+ > node ./scripts/playtest.js
4
+
5
+ ================================================================
6
+ 🧪 AI CTO — AUTOMATED SIMULATION & ANOMALY DETECTION SUITE
7
+ ================================================================
8
+
9
+
10
+ --- Running 10 Playthroughs: Optimal Strategy ---
11
+ Run # 1: Capital = $ 688.70 | Trust = 100% | Score = 20/20 Successes | Rank = S Tier (Unicorn Founder)
12
+ Run # 2: Capital = $ 621.70 | Trust = 100% | Score = 19/20 Successes | Rank = S Tier (Unicorn Founder)
13
+ Run # 3: Capital = $ 665.70 | Trust = 100% | Score = 19/20 Successes | Rank = S Tier (Unicorn Founder)
14
+ Run # 4: Capital = $ 641.70 | Trust = 100% | Score = 18/20 Successes | Rank = S Tier (Unicorn Founder)
15
+ Run # 5: Capital = $ 665.70 | Trust = 100% | Score = 19/20 Successes | Rank = S Tier (Unicorn Founder)
16
+ Run # 6: Capital = $ 571.70 | Trust = 100% | Score = 17/20 Successes | Rank = S Tier (Unicorn Founder)
17
+ Run # 7: Capital = $ 613.70 | Trust = 100% | Score = 19/20 Successes | Rank = S Tier (Unicorn Founder)
18
+ Run # 8: Capital = $ 641.70 | Trust = 100% | Score = 18/20 Successes | Rank = S Tier (Unicorn Founder)
19
+ Run # 9: Capital = $ 571.70 | Trust = 100% | Score = 17/20 Successes | Rank = S Tier (Unicorn Founder)
20
+ Run #10: Capital = $ 598.70 | Trust = 100% | Score = 18/20 Successes | Rank = S Tier (Unicorn Founder)
21
+ 📊 SUMMARY (Optimal Strategy): Avg Profit = $628.10 | Avg Trust = 100.0% | Avg Wins = 18.4/20
22
+
23
+ --- Running 10 Playthroughs: Nano Spam Strategy ---
24
+ Run # 1: Capital = $-1039.10 | Trust = 44% | Score = 9/20 Successes | Rank = Bankrupt Founder
25
+ Run # 2: Capital = $-1164.10 | Trust = 34% | Score = 8/20 Successes | Rank = Bankrupt Founder
26
+ Run # 3: Capital = $-1164.10 | Trust = 34% | Score = 8/20 Successes | Rank = Bankrupt Founder
27
+ Run # 4: Capital = $-1050.10 | Trust = 28% | Score = 7/20 Successes | Rank = Bankrupt Founder
28
+ Run # 5: Capital = $-858.10 | Trust = 36% | Score = 8/20 Successes | Rank = Bankrupt Founder
29
+ Run # 6: Capital = $-1191.10 | Trust = 26% | Score = 7/20 Successes | Rank = Bankrupt Founder
30
+ Run # 7: Capital = $-1037.10 | Trust = 36% | Score = 8/20 Successes | Rank = Bankrupt Founder
31
+ Run # 8: Capital = $-1188.10 | Trust = 26% | Score = 7/20 Successes | Rank = Bankrupt Founder
32
+ Run # 9: Capital = $-1164.10 | Trust = 34% | Score = 8/20 Successes | Rank = Bankrupt Founder
33
+ Run #10: Capital = $-1214.10 | Trust = 18% | Score = 6/20 Successes | Rank = Bankrupt Founder
34
+ 📊 SUMMARY (Nano Spam Strategy): Avg Profit = $-1107.00 | Avg Trust = 31.6% | Avg Wins = 7.6/20
35
+
36
+ --- Running 10 Playthroughs: Large Safe Strategy ---
37
+ Run # 1: Capital = $ 278.00 | Trust = 34% | Score = 9/20 Successes | Rank = A Tier (Profitable Startup)
38
+ Run # 2: Capital = $ 444.00 | Trust = 53% | Score = 12/20 Successes | Rank = A Tier (Profitable Startup)
39
+ Run # 3: Capital = $ 406.00 | Trust = 56% | Score = 13/20 Successes | Rank = A Tier (Profitable Startup)
40
+ Run # 4: Capital = $ 426.00 | Trust = 48% | Score = 11/20 Successes | Rank = A Tier (Profitable Startup)
41
+ Run # 5: Capital = $ 421.00 | Trust = 53% | Score = 12/20 Successes | Rank = A Tier (Profitable Startup)
42
+ Run # 6: Capital = $ 406.00 | Trust = 48% | Score = 11/20 Successes | Rank = A Tier (Profitable Startup)
43
+ Run # 7: Capital = $ 458.00 | Trust = 63% | Score = 14/20 Successes | Rank = A Tier (Profitable Startup)
44
+ Run # 8: Capital = $ 396.00 | Trust = 43% | Score = 10/20 Successes | Rank = A Tier (Profitable Startup)
45
+ Run # 9: Capital = $ 457.00 | Trust = 58% | Score = 13/20 Successes | Rank = A Tier (Profitable Startup)
46
+ Run #10: Capital = $ 427.00 | Trust = 58% | Score = 13/20 Successes | Rank = A Tier (Profitable Startup)
47
+ 📊 SUMMARY (Large Safe Strategy): Avg Profit = $411.90 | Avg Trust = 51.4% | Avg Wins = 11.8/20
48
+
49
+ --- Running 10 Playthroughs: Random Choice Strategy ---
50
+ Run # 1: Capital = $ 236.87 | Trust = 60% | Score = 12/20 Successes | Rank = A Tier (Profitable Startup)
51
+ Run # 2: Capital = $-540.00 | Trust = 49% | Score = 10/20 Successes | Rank = Bankrupt Founder
52
+ Run # 3: Capital = $-667.61 | Trust = 29% | Score = 8/20 Successes | Rank = Bankrupt Founder
53
+ Run # 4: Capital = $-843.27 | Trust = 43% | Score = 9/20 Successes | Rank = Bankrupt Founder
54
+ Run # 5: Capital = $-383.88 | Trust = 77% | Score = 13/20 Successes | Rank = Bankrupt Founder
55
+ Run # 6: Capital = $-387.66 | Trust = 27% | Score = 8/20 Successes | Rank = Bankrupt Founder
56
+ Run # 7: Capital = $-652.35 | Trust = 36% | Score = 9/20 Successes | Rank = Bankrupt Founder
57
+ Run # 8: Capital = $-833.83 | Trust = 34% | Score = 8/20 Successes | Rank = Bankrupt Founder
58
+ Run # 9: Capital = $-403.99 | Trust = 60% | Score = 12/20 Successes | Rank = Bankrupt Founder
59
+ Run #10: Capital = $-701.64 | Trust = 46% | Score = 10/20 Successes | Rank = Bankrupt Founder
60
+ 📊 SUMMARY (Random Choice Strategy): Avg Profit = $-517.74 | Avg Trust = 46.1% | Avg Wins = 9.9/20
61
+
62
+ ================================================================
63
+ 🔍 ANOMALY DETECTION REPORT
64
+ ================================================================
65
+
66
+ 1. High Failure Rate Bottlenecks (> 50% Failure across all runs):
67
+ ⚠️ [Req #Format JSON payload for API] Difficulty: Low | Failure Rate: 60.0% (24/40 runs failed)
68
+ ⚠️ [Req #Generate SQL query for analytics] Difficulty: Medium | Failure Rate: 55.0% (22/40 runs failed)
69
+ ⚠️ [Req #Extract receipt total from photo] Difficulty: Medium | Failure Rate: 62.5% (25/40 runs failed)
70
+ ⚠️ [Req #Debug React component hook crash] Difficulty: Medium | Failure Rate: 60.0% (24/40 runs failed)
71
+ ⚠️ [Req #Parse 50-page PDF legal contract] Difficulty: High | Failure Rate: 45.0% (18/40 runs failed)
72
+ ⚠️ [Req #Classify customer support ticket priority] Difficulty: Low | Failure Rate: 42.5% (17/40 runs failed)
73
+ ⚠️ [Req #Extract wireframe elements from napkin sketch] Difficulty: High | Failure Rate: 42.5% (17/40 runs failed)
74
+ ⚠️ [Req #Clean up CSV phone number formatting] Difficulty: Low | Failure Rate: 42.5% (17/40 runs failed)
75
+ ⚠️ [Req #Generate Regex pattern for credit card masking] Difficulty: Medium | Failure Rate: 55.0% (22/40 runs failed)
76
+ ⚠️ [Req #Enterprise API multi-service migration plan] Difficulty: High | Failure Rate: 42.5% (17/40 runs failed)
77
+
78
+ 2. Strategy ROI & Dominance Check:
79
+ - Optimal Strategy : Avg Final Capital = $628.10 (PROFITABLE)
80
+ - Nano Spam Strategy : Avg Final Capital = $-1107.00 (UNPROFITABLE)
81
+ - Large Safe Strategy : Avg Final Capital = $411.90 (PROFITABLE)
82
+ - Random Choice Strategy : Avg Final Capital = $-517.74 (UNPROFITABLE)
@@ -0,0 +1,17 @@
1
+ import { test, describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import * as cliModule from '../bin/cli.js';
4
+
5
+ describe('CLI Module Integrity', () => {
6
+ it('should export all required game components', () => {
7
+ assert.ok(Array.isArray(cliModule.MODELS), 'MODELS export missing');
8
+ assert.ok(Array.isArray(cliModule.REQUESTS), 'REQUESTS export missing');
9
+ assert.equal(typeof cliModule.evaluateChoice, 'function', 'evaluateChoice export missing');
10
+ });
11
+
12
+ it('should allow importing cli.js without auto-executing interactive CLI', () => {
13
+ // If importing cli.js auto-executed main(), it would block waiting for readline input.
14
+ // Reaching this assertion confirms top-level import behavior is clean.
15
+ assert.equal(cliModule.MODELS.length, 3);
16
+ });
17
+ });
@@ -0,0 +1,137 @@
1
+ import { test, describe, it, beforeEach, afterEach } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { evaluateChoice, MODELS, REQUESTS } from '../bin/cli.js';
4
+
5
+ describe('evaluateChoice Game Engine Evaluator', () => {
6
+ let originalRandom;
7
+
8
+ beforeEach(() => {
9
+ originalRandom = Math.random;
10
+ });
11
+
12
+ afterEach(() => {
13
+ Math.random = originalRandom;
14
+ });
15
+
16
+ const mockRandom = (value) => {
17
+ Math.random = () => value;
18
+ };
19
+
20
+ describe('SLA Enforcement & Latency Penalties', () => {
21
+ it('should trigger automatic rejection (0% success probability) when latency > 2x SLA', () => {
22
+ // Large model (latency 4.0s) on Request 3 (SLA 1.5s): latencyRatio = 4.0 / 1.5 = 2.67 > 2.0
23
+ const largeModel = MODELS.find(m => m.id === 'large');
24
+ const reqSla15 = REQUESTS.find(r => r.id === 3);
25
+
26
+ mockRandom(0.1); // Would normally pass if successProb > 0.1
27
+ const res = evaluateChoice(largeModel, reqSla15);
28
+
29
+ assert.equal(res.success, false);
30
+ assert.equal(res.trustDelta, -8);
31
+ assert.ok(res.headline.includes('SLA Violation! Automatic Rejection'));
32
+ assert.equal(res.profit, reqSla15.penalty - largeModel.cost);
33
+ });
34
+
35
+ it('should penalize success probability by 50% when SLA is exceeded but <= 2x SLA', () => {
36
+ // Large model (latency 4.0s) on Request 7 (SLA 2.5s): latencyRatio = 4.0 / 2.5 = 1.6 <= 2.0
37
+ const largeModel = MODELS.find(m => m.id === 'large');
38
+ const req = REQUESTS.find(r => r.id === 7); // diff: Medium, req caps max 4
39
+
40
+ // maxGap = 0 for Large model on req 7, so base successProb = 1.0 * 0.50 = 0.50
41
+ mockRandom(0.40); // 0.40 <= 0.50 -> success
42
+ const successRes = evaluateChoice(largeModel, req);
43
+ assert.equal(successRes.success, true);
44
+ assert.equal(successRes.headline, 'Barely met SLA.');
45
+ assert.equal(successRes.trustDelta, 1);
46
+
47
+ mockRandom(0.60); // 0.60 > 0.50 -> failure
48
+ const failRes = evaluateChoice(largeModel, req);
49
+ assert.equal(failRes.success, false);
50
+ assert.equal(failRes.headline, 'Fast but sloppy. (SLA Exceeded)');
51
+ assert.equal(failRes.trustDelta, -6);
52
+ });
53
+ });
54
+
55
+ describe('Capability Gap Penalties', () => {
56
+ it('should calculate capability gap penalties correctly on success and failure', () => {
57
+ // Nano model (cap quality 2) on Request 12 (req quality 4.5): gap = 2.5 > 2
58
+ const nanoModel = MODELS.find(m => m.id === 'nano');
59
+ const highReq = REQUESTS.find(r => r.id === 12); // SLA 4.5 (latencyRatio = 0.4 / 4.5 = 0.088)
60
+
61
+ // successProb = 1.0 * 0.05 = 0.05
62
+ mockRandom(0.04); // success
63
+ const successRes = evaluateChoice(nanoModel, highReq);
64
+ assert.equal(successRes.success, true);
65
+ assert.equal(successRes.headline, 'Excellent margin!'); // Nano on High diff
66
+ assert.equal(successRes.trustDelta, 4);
67
+
68
+ mockRandom(0.10); // failure
69
+ const failRes = evaluateChoice(nanoModel, highReq);
70
+ assert.equal(failRes.success, false);
71
+ assert.equal(failRes.headline, 'Inference costs eating profit. Underpowered model failed.');
72
+ assert.equal(failRes.trustDelta, -6);
73
+ });
74
+ });
75
+
76
+ describe('Over-engineering Penalty', () => {
77
+ it('should deduct $3.00 penalty and set trustDelta to -1 when using Large model on Low difficulty request', () => {
78
+ const largeModel = MODELS.find(m => m.id === 'large');
79
+ const lowReq = REQUESTS.find(r => r.diff === 'Low' && r.sla >= 4.0) || REQUESTS.find(r => r.diff === 'Low');
80
+
81
+ mockRandom(0.1);
82
+ const res = evaluateChoice(largeModel, lowReq);
83
+
84
+ if (largeModel.latency > lowReq.sla && (largeModel.latency / lowReq.sla > 2.0)) {
85
+ // If SLA violated, tested separately. Let's force lowReq with SLA check
86
+ return;
87
+ }
88
+
89
+ if (res.success) {
90
+ const expectedProfit = lowReq.reward - largeModel.cost - 3.00;
91
+ assert.equal(res.profit, expectedProfit);
92
+ assert.equal(res.trustDelta, -1);
93
+ assert.equal(res.headline, 'You burned money. (Over-engineered)');
94
+ }
95
+ });
96
+
97
+ it('should specifically test Large model on Request 1 (Low diff, SLA 2.0)', () => {
98
+ const largeModel = MODELS.find(m => m.id === 'large');
99
+ const req1 = REQUESTS.find(r => r.id === 1); // diff Low, SLA 2.0, latencyRatio = 4.0 / 2.0 = 2.0 (<= 2.0)
100
+
101
+ // latencyRatio = 2.0, !latencyMet -> successProb = 1.0 * 0.5 = 0.5
102
+ mockRandom(0.2); // success
103
+ const res = evaluateChoice(largeModel, req1);
104
+
105
+ assert.equal(res.success, true);
106
+ assert.equal(res.profit, req1.reward - largeModel.cost - 3.00);
107
+ assert.equal(res.trustDelta, -1);
108
+ assert.equal(res.headline, 'You burned money. (Over-engineered)');
109
+ });
110
+ });
111
+
112
+ describe('Financial Profit Formulas', () => {
113
+ it('should compute exact profit on success without over-engineering', () => {
114
+ const miniModel = MODELS.find(m => m.id === 'mini');
115
+ const req4 = REQUESTS.find(r => r.id === 4); // Medium diff, SLA 2.0 (latency 1.2 <= 2.0)
116
+
117
+ mockRandom(0.1);
118
+ const res = evaluateChoice(miniModel, req4);
119
+
120
+ assert.equal(res.success, true);
121
+ const expectedProfit = req4.reward - miniModel.cost;
122
+ assert.equal(res.profit, expectedProfit);
123
+ });
124
+
125
+ it('should compute exact profit (loss) on failure', () => {
126
+ const nanoModel = MODELS.find(m => m.id === 'nano');
127
+ const req4 = REQUESTS.find(r => r.id === 4);
128
+
129
+ mockRandom(0.99); // Force failure
130
+ const res = evaluateChoice(nanoModel, req4);
131
+
132
+ assert.equal(res.success, false);
133
+ const expectedProfit = req4.penalty - nanoModel.cost;
134
+ assert.equal(res.profit, expectedProfit);
135
+ });
136
+ });
137
+ });
@@ -0,0 +1,65 @@
1
+ import { test, describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { MODELS } from '../bin/cli.js';
4
+
5
+ describe('MODELS Configuration', () => {
6
+ it('should export an array with exactly 3 models', () => {
7
+ assert.ok(Array.isArray(MODELS));
8
+ assert.equal(MODELS.length, 3);
9
+ });
10
+
11
+ it('should have unique model IDs and names', () => {
12
+ const ids = MODELS.map(m => m.id);
13
+ const names = MODELS.map(m => m.name);
14
+
15
+ assert.equal(new Set(ids).size, ids.length, 'Model IDs must be unique');
16
+ assert.equal(new Set(names).size, names.length, 'Model names must be unique');
17
+ });
18
+
19
+ it('should contain expected models: Nano, Mini, Large', () => {
20
+ const ids = MODELS.map(m => m.id);
21
+ assert.deepEqual(ids, ['nano', 'mini', 'large']);
22
+ });
23
+
24
+ it('should have valid schema and values for each model', () => {
25
+ const requiredCapKeys = ['quality', 'coding', 'reasoning', 'vision', 'context'];
26
+
27
+ for (const model of MODELS) {
28
+ assert.equal(typeof model.id, 'string');
29
+ assert.ok(model.id.length > 0);
30
+
31
+ assert.equal(typeof model.name, 'string');
32
+ assert.ok(model.name.length > 0);
33
+
34
+ assert.equal(typeof model.cost, 'number');
35
+ assert.ok(model.cost > 0, `Cost for ${model.id} should be > 0`);
36
+
37
+ assert.equal(typeof model.latency, 'number');
38
+ assert.ok(model.latency > 0, `Latency for ${model.id} should be > 0`);
39
+
40
+ assert.equal(typeof model.desc, 'string');
41
+ assert.ok(model.desc.length > 0);
42
+
43
+ assert.equal(typeof model.cap, 'object');
44
+ assert.ok(model.cap !== null);
45
+
46
+ for (const capKey of requiredCapKeys) {
47
+ assert.ok(capKey in model.cap, `Capability '${capKey}' missing in ${model.id}`);
48
+ const capVal = model.cap[capKey];
49
+ assert.equal(typeof capVal, 'number');
50
+ assert.ok(capVal >= 0 && capVal <= 5, `Capability '${capKey}' (${capVal}) for ${model.id} must be between 0 and 5`);
51
+ }
52
+ }
53
+ });
54
+
55
+ it('should verify Nano, Mini, Large specific capability hierarchies', () => {
56
+ const nano = MODELS.find(m => m.id === 'nano');
57
+ const mini = MODELS.find(m => m.id === 'mini');
58
+ const large = MODELS.find(m => m.id === 'large');
59
+
60
+ assert.ok(nano.cost < mini.cost && mini.cost < large.cost, 'Cost hierarchy: Nano < Mini < Large');
61
+ assert.ok(nano.latency < mini.latency && mini.latency < large.latency, 'Latency hierarchy: Nano < Mini < Large');
62
+ assert.ok(nano.cap.quality < mini.cap.quality && mini.cap.quality < large.cap.quality, 'Quality capability hierarchy');
63
+ assert.ok(large.cap.quality === 5 && large.cap.coding === 5 && large.cap.reasoning === 5, 'Large model maxed stats');
64
+ });
65
+ });
@@ -0,0 +1,67 @@
1
+ import { test, describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { REQUESTS } from '../bin/cli.js';
4
+
5
+ describe('REQUESTS Dataset Configuration', () => {
6
+ it('should export an array with exactly 20 requests', () => {
7
+ assert.ok(Array.isArray(REQUESTS));
8
+ assert.equal(REQUESTS.length, 20);
9
+ });
10
+
11
+ it('should have sequential IDs from 1 to 20', () => {
12
+ const ids = REQUESTS.map(r => r.id);
13
+ const expectedIds = Array.from({ length: 20 }, (_, i) => i + 1);
14
+ assert.deepEqual(ids, expectedIds);
15
+ });
16
+
17
+ it('should have unique titles', () => {
18
+ const titles = REQUESTS.map(r => r.title);
19
+ assert.equal(new Set(titles).size, titles.length, 'Request titles should be unique');
20
+ });
21
+
22
+ it('should have valid schema and constraints for all 20 requests', () => {
23
+ const validDifficulties = ['Low', 'Medium', 'High'];
24
+ const requiredReqKeys = ['quality', 'coding', 'reasoning', 'vision', 'context'];
25
+
26
+ for (const reqItem of REQUESTS) {
27
+ assert.equal(typeof reqItem.id, 'number');
28
+
29
+ assert.equal(typeof reqItem.title, 'string');
30
+ assert.ok(reqItem.title.trim().length > 0);
31
+
32
+ assert.equal(typeof reqItem.desc, 'string');
33
+ assert.ok(reqItem.desc.trim().length > 0);
34
+
35
+ assert.equal(typeof reqItem.reward, 'number');
36
+ assert.ok(reqItem.reward > 0, `Reward for request ${reqItem.id} must be positive`);
37
+
38
+ assert.equal(typeof reqItem.penalty, 'number');
39
+ assert.ok(reqItem.penalty <= 0, `Penalty for request ${reqItem.id} must be negative or zero`);
40
+
41
+ assert.equal(typeof reqItem.sla, 'number');
42
+ assert.ok(reqItem.sla > 0, `SLA for request ${reqItem.id} must be > 0`);
43
+
44
+ assert.ok(
45
+ validDifficulties.includes(reqItem.diff),
46
+ `Difficulty for request ${reqItem.id} must be one of Low, Medium, High (got '${reqItem.diff}')`
47
+ );
48
+
49
+ assert.equal(typeof reqItem.req, 'object');
50
+ assert.ok(reqItem.req !== null);
51
+
52
+ for (const reqKey of requiredReqKeys) {
53
+ assert.ok(reqKey in reqItem.req, `Requirement key '${reqKey}' missing in request ${reqItem.id}`);
54
+ const reqVal = reqItem.req[reqKey];
55
+ assert.equal(typeof reqVal, 'number');
56
+ assert.ok(reqVal >= 0 && reqVal <= 5, `Requirement '${reqKey}' (${reqVal}) for request ${reqItem.id} must be between 0 and 5`);
57
+ }
58
+ }
59
+ });
60
+
61
+ it('should contain requests for Low, Medium, and High difficulties', () => {
62
+ const difficulties = new Set(REQUESTS.map(r => r.diff));
63
+ assert.ok(difficulties.has('Low'));
64
+ assert.ok(difficulties.has('Medium'));
65
+ assert.ok(difficulties.has('High'));
66
+ });
67
+ });
@@ -0,0 +1,85 @@
1
+ import { test, describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { evaluateChoice, MODELS, REQUESTS } from '../bin/cli.js';
4
+
5
+ function simulateGameSession(strategyFn) {
6
+ let capital = 100.00;
7
+ let trust = 85;
8
+ let totalRevenue = 0;
9
+ let totalCost = 0;
10
+ let successes = 0;
11
+ let failures = 0;
12
+
13
+ for (let i = 0; i < REQUESTS.length; i++) {
14
+ const req = REQUESTS[i];
15
+ const selectedModel = strategyFn(req, i);
16
+
17
+ const res = evaluateChoice(selectedModel, req);
18
+ capital += res.profit;
19
+ trust = Math.min(100, Math.max(0, trust + res.trustDelta));
20
+ totalCost += selectedModel.cost;
21
+
22
+ if (res.success) {
23
+ successes++;
24
+ totalRevenue += req.reward;
25
+ } else {
26
+ failures++;
27
+ totalRevenue += req.penalty;
28
+ }
29
+ }
30
+
31
+ let rank = 'C Tier (Break-even)';
32
+ if (capital > 250 && trust >= 90) rank = 'S Tier (Unicorn Founder)';
33
+ else if (capital > 150) rank = 'A Tier (Profitable Startup)';
34
+ else if (capital > 50) rank = 'B Tier (Sustainable Ops)';
35
+ else if (capital < 0) rank = 'Bankrupt Founder';
36
+
37
+ return { capital, trust, totalRevenue, totalCost, successes, failures, rank };
38
+ }
39
+
40
+ describe('Game Simulation Engine & State Logic', () => {
41
+ it('should enforce trust bounds between 0% and 100%', () => {
42
+ let trust = 85;
43
+
44
+ // Trust cannot exceed 100
45
+ trust = Math.min(100, Math.max(0, trust + 30));
46
+ assert.equal(trust, 100);
47
+
48
+ // Trust cannot drop below 0
49
+ trust = Math.min(100, Math.max(0, trust - 150));
50
+ assert.equal(trust, 0);
51
+ });
52
+
53
+ it('should correctly classify startup ranks', () => {
54
+ const getRank = (capital, trust) => {
55
+ if (capital > 250 && trust >= 90) return 'S Tier (Unicorn Founder)';
56
+ if (capital > 150) return 'A Tier (Profitable Startup)';
57
+ if (capital > 50) return 'B Tier (Sustainable Ops)';
58
+ if (capital < 0) return 'Bankrupt Founder';
59
+ return 'C Tier (Break-even)';
60
+ };
61
+
62
+ assert.equal(getRank(300, 95), 'S Tier (Unicorn Founder)');
63
+ assert.equal(getRank(300, 80), 'A Tier (Profitable Startup)');
64
+ assert.equal(getRank(200, 50), 'A Tier (Profitable Startup)');
65
+ assert.equal(getRank(100, 50), 'B Tier (Sustainable Ops)');
66
+ assert.equal(getRank(25, 50), 'C Tier (Break-even)');
67
+ assert.equal(getRank(-10, 50), 'Bankrupt Founder');
68
+ });
69
+
70
+ it('should simulate a full 20-request game session using optimal strategy', () => {
71
+ const optimalStrategy = (req) => {
72
+ if (req.diff === 'Low') return MODELS.find(m => m.id === 'nano');
73
+ if (req.diff === 'Medium') return MODELS.find(m => m.id === 'mini');
74
+ return MODELS.find(m => m.id === 'large');
75
+ };
76
+
77
+ const result = simulateGameSession(optimalStrategy);
78
+
79
+ assert.equal(result.successes + result.failures, 20);
80
+ assert.equal(typeof result.capital, 'number');
81
+ assert.ok(result.trust >= 0 && result.trust <= 100);
82
+ assert.ok(result.totalCost > 0);
83
+ assert.ok(typeof result.rank, 'string');
84
+ });
85
+ });
package/docs/PLAN.md DELETED
File without changes