aiopt 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -230,6 +230,169 @@ var init_solutions = __esm({
230
230
  }
231
231
  });
232
232
 
233
+ // src/code-scan.ts
234
+ function isTextLike(p) {
235
+ const ext = import_path3.default.extname(p).toLowerCase();
236
+ return [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".java", ".kt", ".rb", ".php"].includes(ext);
237
+ }
238
+ function walk(root, out) {
239
+ const items = import_fs3.default.readdirSync(root, { withFileTypes: true });
240
+ for (const it of items) {
241
+ if (DEFAULT_EXCLUDES.has(it.name)) continue;
242
+ const full = import_path3.default.join(root, it.name);
243
+ if (it.isDirectory()) walk(full, out);
244
+ else out.push(full);
245
+ }
246
+ }
247
+ function findAllLines(content, re) {
248
+ const lines = [];
249
+ const parts = content.split(/\r?\n/);
250
+ for (let i = 0; i < parts.length; i++) {
251
+ const line = parts[i];
252
+ if (re.test(line)) lines.push(i + 1);
253
+ }
254
+ return lines;
255
+ }
256
+ function runCodeScan(rootDir) {
257
+ const files = [];
258
+ walk(rootDir, files);
259
+ const findings = [];
260
+ for (const file of files) {
261
+ if (!isTextLike(file)) continue;
262
+ let st;
263
+ try {
264
+ st = import_fs3.default.statSync(file);
265
+ } catch {
266
+ continue;
267
+ }
268
+ if (!st.isFile()) continue;
269
+ if (st.size > 1024 * 1024) continue;
270
+ let content = "";
271
+ try {
272
+ content = import_fs3.default.readFileSync(file, "utf8");
273
+ } catch {
274
+ continue;
275
+ }
276
+ for (const ln of findAllLines(content, /\b(maxRetries|maximumRetries|retries|max_attempts|attempts)\s*[:=]\s*(\d{2,}|[6-9])\b/i)) {
277
+ findings.push({
278
+ ruleId: "AIOPT.RETRY.EXPLOSION_RISK",
279
+ level: "warning",
280
+ message: "High retry/attempt count detected. Consider capping retries to prevent cost explosions.",
281
+ file,
282
+ line: ln,
283
+ help: "Cap retries (e.g. 2-3), add backoff, and fail fast on non-retriable errors."
284
+ });
285
+ break;
286
+ }
287
+ for (const ln of findAllLines(content, /(gpt-5\.?2|gpt-4\.?|o1-|o3-|claude-3|sonnet|opus)/i)) {
288
+ findings.push({
289
+ ruleId: "AIOPT.MODEL.ROUTING.EXPENSIVE_DEFAULT",
290
+ level: "note",
291
+ message: "Possible expensive model hard-coded. Consider cheap default + explicit override for critical paths.",
292
+ file,
293
+ line: ln,
294
+ help: "Route cheap by default; allow overrides via env/config for high-impact tasks."
295
+ });
296
+ break;
297
+ }
298
+ const hasOpenAI = /\bopenai\b|\bOpenAI\b|responses\.create|chat\.completions\.create/i.test(content);
299
+ const hasTimeout = /\btimeout\b|\brequestTimeout\b|\bsignal\b/i.test(content);
300
+ if (hasOpenAI && !hasTimeout) {
301
+ findings.push({
302
+ ruleId: "AIOPT.TIMEOUT.MISSING",
303
+ level: "note",
304
+ message: "OpenAI/LLM call detected without obvious timeout. Add a timeout to reduce hanging retries and cost waste.",
305
+ file,
306
+ line: 1,
307
+ help: "Add request timeout / AbortSignal and handle retryable errors explicitly."
308
+ });
309
+ }
310
+ }
311
+ return findings.slice(0, 200);
312
+ }
313
+ var import_fs3, import_path3, DEFAULT_EXCLUDES;
314
+ var init_code_scan = __esm({
315
+ "src/code-scan.ts"() {
316
+ "use strict";
317
+ import_fs3 = __toESM(require("fs"));
318
+ import_path3 = __toESM(require("path"));
319
+ DEFAULT_EXCLUDES = /* @__PURE__ */ new Set([
320
+ ".git",
321
+ "node_modules",
322
+ "dist",
323
+ "aiopt-output",
324
+ ".next",
325
+ "build",
326
+ "coverage"
327
+ ]);
328
+ }
329
+ });
330
+
331
+ // src/sarif.ts
332
+ function toUri(p) {
333
+ const abs = import_path4.default.resolve(p);
334
+ const u = abs.replace(/\\/g, "/");
335
+ return u.match(/^[A-Za-z]:\//) ? `file:///${u}` : `file://${u}`;
336
+ }
337
+ function buildSarif(toolName, toolVersion, findings) {
338
+ const rulesMap = /* @__PURE__ */ new Map();
339
+ for (const f of findings) {
340
+ if (!rulesMap.has(f.ruleId)) {
341
+ rulesMap.set(f.ruleId, {
342
+ id: f.ruleId,
343
+ shortDescription: f.ruleId,
344
+ help: f.help
345
+ });
346
+ }
347
+ }
348
+ const rules = [...rulesMap.values()].map((r) => ({
349
+ id: r.id,
350
+ shortDescription: { text: r.shortDescription },
351
+ help: r.help ? { text: r.help } : void 0
352
+ })).map((x) => {
353
+ const y = { ...x };
354
+ if (!y.help) delete y.help;
355
+ return y;
356
+ });
357
+ const results = findings.map((f) => ({
358
+ ruleId: f.ruleId,
359
+ level: f.level,
360
+ message: { text: f.message },
361
+ locations: [
362
+ {
363
+ physicalLocation: {
364
+ artifactLocation: { uri: toUri(f.file) },
365
+ region: { startLine: Math.max(1, Math.floor(f.line || 1)) }
366
+ }
367
+ }
368
+ ]
369
+ }));
370
+ return {
371
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
372
+ version: "2.1.0",
373
+ runs: [
374
+ {
375
+ tool: {
376
+ driver: {
377
+ name: toolName,
378
+ version: toolVersion,
379
+ informationUri: "https://www.npmjs.com/package/aiopt",
380
+ rules
381
+ }
382
+ },
383
+ results
384
+ }
385
+ ]
386
+ };
387
+ }
388
+ var import_path4;
389
+ var init_sarif = __esm({
390
+ "src/sarif.ts"() {
391
+ "use strict";
392
+ import_path4 = __toESM(require("path"));
393
+ }
394
+ });
395
+
233
396
  // src/scan.ts
234
397
  function topN(map, n) {
235
398
  return [...map.entries()].map(([key, v]) => ({ key, cost: v.cost, events: v.events })).sort((a, b) => b.cost - a.cost).slice(0, n);
@@ -387,8 +550,8 @@ function round22(n) {
387
550
  }
388
551
  function writeOutputs(outDir, analysis, savings, policy, meta) {
389
552
  const mode = meta?.mode || "legacy";
390
- import_fs3.default.mkdirSync(outDir, { recursive: true });
391
- import_fs3.default.writeFileSync(import_path3.default.join(outDir, "analysis.json"), JSON.stringify(analysis, null, 2));
553
+ import_fs4.default.mkdirSync(outDir, { recursive: true });
554
+ import_fs4.default.writeFileSync(import_path5.default.join(outDir, "analysis.json"), JSON.stringify(analysis, null, 2));
392
555
  const unknownCount = analysis.unknown_models?.length || 0;
393
556
  const confidence = unknownCount === 0 ? "HIGH" : unknownCount <= 3 ? "MED" : "LOW";
394
557
  const ratio = analysis.total_cost > 0 ? savings.estimated_savings_total / analysis.total_cost : 0;
@@ -420,7 +583,7 @@ function writeOutputs(outDir, analysis, savings, policy, meta) {
420
583
  unknown_models: analysis.unknown_models,
421
584
  notes: savings.notes
422
585
  };
423
- import_fs3.default.writeFileSync(import_path3.default.join(outDir, "report.json"), JSON.stringify(reportJson, null, 2));
586
+ import_fs4.default.writeFileSync(import_path5.default.join(outDir, "report.json"), JSON.stringify(reportJson, null, 2));
424
587
  const ratioMd = analysis.total_cost > 0 ? savings.estimated_savings_total / analysis.total_cost : 0;
425
588
  const warningsMd = [];
426
589
  if (ratioMd >= 0.9) warningsMd.push("WARNING: estimated savings >= 90% \u2014 check overlap/missing rate table");
@@ -449,7 +612,7 @@ function writeOutputs(outDir, analysis, savings, policy, meta) {
449
612
  "- `aiopt-output/patches/*`",
450
613
  ""
451
614
  ].join("\n");
452
- import_fs3.default.writeFileSync(import_path3.default.join(outDir, "report.md"), reportMd);
615
+ import_fs4.default.writeFileSync(import_path5.default.join(outDir, "report.md"), reportMd);
453
616
  const reportTxt = [
454
617
  `\uCD1D\uBE44\uC6A9: $${analysis.total_cost}`,
455
618
  `\uC808\uAC10 \uAC00\uB2A5 \uAE08\uC561(Estimated): $${savings.estimated_savings_total}`,
@@ -459,19 +622,31 @@ function writeOutputs(outDir, analysis, savings, policy, meta) {
459
622
  savings.notes[2],
460
623
  ""
461
624
  ].join("\n");
462
- import_fs3.default.writeFileSync(import_path3.default.join(outDir, "report.txt"), reportTxt);
463
- import_fs3.default.writeFileSync(import_path3.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
625
+ import_fs4.default.writeFileSync(import_path5.default.join(outDir, "report.txt"), reportTxt);
626
+ import_fs4.default.writeFileSync(import_path5.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
464
627
  const fixes = buildTopFixes(analysis, savings);
465
628
  writePatches(outDir, fixes);
629
+ try {
630
+ const cwd = meta?.cwd;
631
+ const cliVersion = meta?.cliVersion || "unknown";
632
+ if (cwd && import_fs4.default.existsSync(cwd)) {
633
+ const findings = runCodeScan(cwd);
634
+ const sarif = buildSarif("aiopt", cliVersion, findings);
635
+ import_fs4.default.writeFileSync(import_path5.default.join(outDir, "aiopt.sarif"), JSON.stringify(sarif, null, 2));
636
+ }
637
+ } catch {
638
+ }
466
639
  }
467
- var import_fs3, import_path3, ROUTE_TO_CHEAP_FEATURES;
640
+ var import_fs4, import_path5, ROUTE_TO_CHEAP_FEATURES;
468
641
  var init_scan = __esm({
469
642
  "src/scan.ts"() {
470
643
  "use strict";
471
- import_fs3 = __toESM(require("fs"));
472
- import_path3 = __toESM(require("path"));
644
+ import_fs4 = __toESM(require("fs"));
645
+ import_path5 = __toESM(require("path"));
473
646
  init_cost();
474
647
  init_solutions();
648
+ init_code_scan();
649
+ init_sarif();
475
650
  ROUTE_TO_CHEAP_FEATURES = /* @__PURE__ */ new Set(["summarize", "classify", "translate"]);
476
651
  }
477
652
  });
@@ -481,7 +656,7 @@ var require_package = __commonJS({
481
656
  "package.json"(exports2, module2) {
482
657
  module2.exports = {
483
658
  name: "aiopt",
484
- version: "0.3.0",
659
+ version: "0.3.1",
485
660
  description: "Pre-deploy LLM cost accident guardrail (serverless local CLI)",
486
661
  bin: {
487
662
  aiopt: "dist/cli.js"
@@ -522,19 +697,19 @@ __export(install_exports, {
522
697
  runInstall: () => runInstall
523
698
  });
524
699
  function ensureDir2(p) {
525
- import_fs4.default.mkdirSync(p, { recursive: true });
700
+ import_fs5.default.mkdirSync(p, { recursive: true });
526
701
  }
527
702
  function writeFile(filePath, content, force) {
528
- if (!force && import_fs4.default.existsSync(filePath)) return { wrote: false, reason: "exists" };
529
- ensureDir2(import_path4.default.dirname(filePath));
530
- import_fs4.default.writeFileSync(filePath, content);
703
+ if (!force && import_fs5.default.existsSync(filePath)) return { wrote: false, reason: "exists" };
704
+ ensureDir2(import_path6.default.dirname(filePath));
705
+ import_fs5.default.writeFileSync(filePath, content);
531
706
  return { wrote: true };
532
707
  }
533
708
  function runInstall(cwd, opts) {
534
709
  const force = Boolean(opts.force);
535
- const aioptDir = import_path4.default.join(cwd, "aiopt");
536
- const policiesDir = import_path4.default.join(aioptDir, "policies");
537
- const outDir = import_path4.default.join(cwd, "aiopt-output");
710
+ const aioptDir = import_path6.default.join(cwd, "aiopt");
711
+ const policiesDir = import_path6.default.join(aioptDir, "policies");
712
+ const outDir = import_path6.default.join(cwd, "aiopt-output");
538
713
  ensureDir2(aioptDir);
539
714
  ensureDir2(policiesDir);
540
715
  ensureDir2(outDir);
@@ -590,7 +765,7 @@ return {
590
765
  };
591
766
  \`\`\`
592
767
  `;
593
- const r1 = writeFile(import_path4.default.join(aioptDir, "README.md"), readme, force);
768
+ const r1 = writeFile(import_path6.default.join(aioptDir, "README.md"), readme, force);
594
769
  created.push({ path: "aiopt/README.md", status: r1.wrote ? "created" : "skipped" });
595
770
  const config = {
596
771
  version: 1,
@@ -600,7 +775,7 @@ return {
600
775
  policies_dir: "./aiopt/policies",
601
776
  rate_table: { path: "./rates/rate_table.json" }
602
777
  };
603
- const r2 = writeFile(import_path4.default.join(aioptDir, "aiopt.config.json"), JSON.stringify(config, null, 2) + "\n", force);
778
+ const r2 = writeFile(import_path6.default.join(aioptDir, "aiopt.config.json"), JSON.stringify(config, null, 2) + "\n", force);
604
779
  created.push({ path: "aiopt/aiopt.config.json", status: r2.wrote ? "created" : "skipped" });
605
780
  const routing = {
606
781
  version: 1,
@@ -631,15 +806,15 @@ return {
631
806
  reduce_top_percentile: 0.2,
632
807
  assumed_reduction_ratio: 0.25
633
808
  };
634
- const p1 = writeFile(import_path4.default.join(policiesDir, "routing.json"), JSON.stringify(routing, null, 2) + "\n", force);
635
- const p2 = writeFile(import_path4.default.join(policiesDir, "retry.json"), JSON.stringify(retry, null, 2) + "\n", force);
636
- const p3 = writeFile(import_path4.default.join(policiesDir, "output.json"), JSON.stringify(output, null, 2) + "\n", force);
637
- const p4 = writeFile(import_path4.default.join(policiesDir, "context.json"), JSON.stringify(context, null, 2) + "\n", force);
809
+ const p1 = writeFile(import_path6.default.join(policiesDir, "routing.json"), JSON.stringify(routing, null, 2) + "\n", force);
810
+ const p2 = writeFile(import_path6.default.join(policiesDir, "retry.json"), JSON.stringify(retry, null, 2) + "\n", force);
811
+ const p3 = writeFile(import_path6.default.join(policiesDir, "output.json"), JSON.stringify(output, null, 2) + "\n", force);
812
+ const p4 = writeFile(import_path6.default.join(policiesDir, "context.json"), JSON.stringify(context, null, 2) + "\n", force);
638
813
  created.push({ path: "aiopt/policies/routing.json", status: p1.wrote ? "created" : "skipped" });
639
814
  created.push({ path: "aiopt/policies/retry.json", status: p2.wrote ? "created" : "skipped" });
640
815
  created.push({ path: "aiopt/policies/output.json", status: p3.wrote ? "created" : "skipped" });
641
816
  created.push({ path: "aiopt/policies/context.json", status: p4.wrote ? "created" : "skipped" });
642
- const wrapperPath = import_path4.default.join(aioptDir, "aiopt-wrapper.js");
817
+ const wrapperPath = import_path6.default.join(aioptDir, "aiopt-wrapper.js");
643
818
  const wrapper = `// AIOpt Wrapper (guardrails) \u2014 local-only (CommonJS)
644
819
 
645
820
  const fs = require('fs');
@@ -793,9 +968,9 @@ module.exports = { guardedCall };
793
968
  ;
794
969
  const w = writeFile(wrapperPath, wrapper, force);
795
970
  created.push({ path: "aiopt/aiopt-wrapper.js", status: w.wrote ? "created" : "skipped" });
796
- const usagePath = import_path4.default.join(outDir, "usage.jsonl");
797
- if (force || !import_fs4.default.existsSync(usagePath)) {
798
- import_fs4.default.writeFileSync(usagePath, "");
971
+ const usagePath = import_path6.default.join(outDir, "usage.jsonl");
972
+ if (force || !import_fs5.default.existsSync(usagePath)) {
973
+ import_fs5.default.writeFileSync(usagePath, "");
799
974
  created.push({ path: "aiopt-output/usage.jsonl", status: "created" });
800
975
  if (opts.seedSample) {
801
976
  const sample = {
@@ -815,19 +990,19 @@ module.exports = { guardedCall };
815
990
  latency_ms: 1,
816
991
  meta: { feature_tag: "demo", routed_from: null, policy_hits: ["install-sample"] }
817
992
  };
818
- import_fs4.default.appendFileSync(usagePath, JSON.stringify(sample) + "\n");
993
+ import_fs5.default.appendFileSync(usagePath, JSON.stringify(sample) + "\n");
819
994
  }
820
995
  } else {
821
996
  created.push({ path: "aiopt-output/usage.jsonl", status: "skipped" });
822
997
  }
823
998
  return { created };
824
999
  }
825
- var import_fs4, import_path4;
1000
+ var import_fs5, import_path6;
826
1001
  var init_install = __esm({
827
1002
  "src/install.ts"() {
828
1003
  "use strict";
829
- import_fs4 = __toESM(require("fs"));
830
- import_path4 = __toESM(require("path"));
1004
+ import_fs5 = __toESM(require("fs"));
1005
+ import_path6 = __toESM(require("path"));
831
1006
  }
832
1007
  });
833
1008
 
@@ -838,10 +1013,10 @@ __export(doctor_exports, {
838
1013
  });
839
1014
  function canWrite(dir) {
840
1015
  try {
841
- import_fs5.default.mkdirSync(dir, { recursive: true });
842
- const p = import_path5.default.join(dir, `.aiopt-write-test-${Date.now()}`);
843
- import_fs5.default.writeFileSync(p, "ok");
844
- import_fs5.default.unlinkSync(p);
1016
+ import_fs6.default.mkdirSync(dir, { recursive: true });
1017
+ const p = import_path7.default.join(dir, `.aiopt-write-test-${Date.now()}`);
1018
+ import_fs6.default.writeFileSync(p, "ok");
1019
+ import_fs6.default.unlinkSync(p);
845
1020
  return true;
846
1021
  } catch {
847
1022
  return false;
@@ -849,7 +1024,7 @@ function canWrite(dir) {
849
1024
  }
850
1025
  function tailLines(filePath, n) {
851
1026
  try {
852
- const raw = import_fs5.default.readFileSync(filePath, "utf8");
1027
+ const raw = import_fs6.default.readFileSync(filePath, "utf8");
853
1028
  const lines = raw.split(/\r?\n/).filter((l) => l.trim().length > 0);
854
1029
  return lines.slice(Math.max(0, lines.length - n));
855
1030
  } catch {
@@ -857,15 +1032,15 @@ function tailLines(filePath, n) {
857
1032
  }
858
1033
  }
859
1034
  function runDoctor(cwd) {
860
- const aioptDir = import_path5.default.join(cwd, "aiopt");
861
- const policiesDir = import_path5.default.join(aioptDir, "policies");
862
- const outDir = import_path5.default.join(cwd, "aiopt-output");
863
- const usagePath = import_path5.default.join(outDir, "usage.jsonl");
1035
+ const aioptDir = import_path7.default.join(cwd, "aiopt");
1036
+ const policiesDir = import_path7.default.join(aioptDir, "policies");
1037
+ const outDir = import_path7.default.join(cwd, "aiopt-output");
1038
+ const usagePath = import_path7.default.join(outDir, "usage.jsonl");
864
1039
  const checks = [];
865
- checks.push({ name: "aiopt/ exists", ok: import_fs5.default.existsSync(aioptDir) });
866
- checks.push({ name: "aiopt/policies exists", ok: import_fs5.default.existsSync(policiesDir) });
1040
+ checks.push({ name: "aiopt/ exists", ok: import_fs6.default.existsSync(aioptDir) });
1041
+ checks.push({ name: "aiopt/policies exists", ok: import_fs6.default.existsSync(policiesDir) });
867
1042
  checks.push({ name: "aiopt-output/ writable", ok: canWrite(outDir) });
868
- checks.push({ name: "usage.jsonl exists", ok: import_fs5.default.existsSync(usagePath), detail: usagePath });
1043
+ checks.push({ name: "usage.jsonl exists", ok: import_fs6.default.existsSync(usagePath), detail: usagePath });
869
1044
  const last5raw = tailLines(usagePath, 5);
870
1045
  const last5 = last5raw.length === 0 ? [{ status: "(empty usage.jsonl)" }] : last5raw.map((l) => {
871
1046
  try {
@@ -903,12 +1078,12 @@ function runDoctor(cwd) {
903
1078
  const ok = checks.every((c) => c.ok);
904
1079
  return { ok, checks, last5 };
905
1080
  }
906
- var import_fs5, import_path5;
1081
+ var import_fs6, import_path7;
907
1082
  var init_doctor = __esm({
908
1083
  "src/doctor.ts"() {
909
1084
  "use strict";
910
- import_fs5 = __toESM(require("fs"));
911
- import_path5 = __toESM(require("path"));
1085
+ import_fs6 = __toESM(require("fs"));
1086
+ import_path7 = __toESM(require("path"));
912
1087
  }
913
1088
  });
914
1089
 
@@ -963,7 +1138,7 @@ function verifyLicenseKey(key, publicKeyPem) {
963
1138
  }
964
1139
  }
965
1140
  function defaultLicensePath(cwd) {
966
- return import_path6.default.join(cwd, "aiopt", "license.json");
1141
+ return import_path8.default.join(cwd, "aiopt", "license.json");
967
1142
  }
968
1143
  function writeLicenseFile(p, key, payload, verified) {
969
1144
  const out = {
@@ -972,19 +1147,19 @@ function writeLicenseFile(p, key, payload, verified) {
972
1147
  verified,
973
1148
  verified_at: (/* @__PURE__ */ new Date()).toISOString()
974
1149
  };
975
- import_fs6.default.mkdirSync(import_path6.default.dirname(p), { recursive: true });
976
- import_fs6.default.writeFileSync(p, JSON.stringify(out, null, 2));
1150
+ import_fs7.default.mkdirSync(import_path8.default.dirname(p), { recursive: true });
1151
+ import_fs7.default.writeFileSync(p, JSON.stringify(out, null, 2));
977
1152
  }
978
1153
  function readLicenseFile(p) {
979
- return JSON.parse(import_fs6.default.readFileSync(p, "utf8"));
1154
+ return JSON.parse(import_fs7.default.readFileSync(p, "utf8"));
980
1155
  }
981
- var import_crypto, import_fs6, import_path6, DEFAULT_PUBLIC_KEY_PEM;
1156
+ var import_crypto, import_fs7, import_path8, DEFAULT_PUBLIC_KEY_PEM;
982
1157
  var init_license = __esm({
983
1158
  "src/license.ts"() {
984
1159
  "use strict";
985
1160
  import_crypto = __toESM(require("crypto"));
986
- import_fs6 = __toESM(require("fs"));
987
- import_path6 = __toESM(require("path"));
1161
+ import_fs7 = __toESM(require("fs"));
1162
+ import_path8 = __toESM(require("path"));
988
1163
  DEFAULT_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
989
1164
  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz1LLE/pXIx5TloDa0LAf
990
1165
  jg9NSIW6STWhsAFP2ZzXgpWoQ3cCmW6xcB/4QNEmPpGlfMWhyRfkxsdKuhnjUMTg
@@ -997,6 +1172,284 @@ DwIDAQAB
997
1172
  }
998
1173
  });
999
1174
 
1175
+ // src/gate.ts
1176
+ var gate_exports = {};
1177
+ __export(gate_exports, {
1178
+ formatGateStdout: () => formatGateStdout,
1179
+ runGate: () => runGate
1180
+ });
1181
+ function parseFileUri(uri) {
1182
+ try {
1183
+ if (!uri) return "";
1184
+ const u = uri.replace(/^file:\/\//, "");
1185
+ const norm = u.replace(/^\/+/, "").replace(/%20/g, " ");
1186
+ return norm;
1187
+ } catch {
1188
+ return uri;
1189
+ }
1190
+ }
1191
+ function runGate(outDir, cwd) {
1192
+ const sarifPath = import_path9.default.join(outDir, "aiopt.sarif");
1193
+ if (!import_fs8.default.existsSync(sarifPath)) {
1194
+ return { violations: 0, top3: [] };
1195
+ }
1196
+ let sarif;
1197
+ try {
1198
+ sarif = JSON.parse(import_fs8.default.readFileSync(sarifPath, "utf8"));
1199
+ } catch {
1200
+ return { violations: 0, top3: [] };
1201
+ }
1202
+ const results = sarif.runs?.[0]?.results || [];
1203
+ const viol = results.filter((r) => {
1204
+ const lvl = String(r.level || "").toLowerCase();
1205
+ return lvl === "warning" || lvl === "error";
1206
+ });
1207
+ const locs = [];
1208
+ for (const r of viol) {
1209
+ const loc = r.locations?.[0]?.physicalLocation;
1210
+ const uri = loc?.artifactLocation?.uri || "";
1211
+ const line = Number(loc?.region?.startLine || 1);
1212
+ let file = parseFileUri(uri);
1213
+ try {
1214
+ const abs = import_path9.default.isAbsolute(file) ? file : import_path9.default.resolve(file);
1215
+ file = import_path9.default.relative(cwd, abs) || file;
1216
+ } catch {
1217
+ }
1218
+ locs.push({ file, line: Number.isFinite(line) && line > 0 ? line : 1 });
1219
+ }
1220
+ const top3 = locs.slice(0, 3);
1221
+ return { violations: viol.length, top3 };
1222
+ }
1223
+ function formatGateStdout(r, outDir) {
1224
+ const lines = [];
1225
+ if (r.violations <= 0) {
1226
+ lines.push("OK: no policy violations");
1227
+ lines.push(`Artifacts: ${import_path9.default.join(outDir, "report.md")} | ${import_path9.default.join(outDir, "aiopt.sarif")}`);
1228
+ return { text: lines.join("\n"), exitCode: 0 };
1229
+ }
1230
+ lines.push(`FAIL: policy violations=${r.violations}`);
1231
+ lines.push("Top3:");
1232
+ for (const x of r.top3) lines.push(`- ${x.file}:${x.line}`);
1233
+ lines.push("See artifacts: aiopt-output/report.md | aiopt-output/aiopt.sarif | aiopt-output/patches/");
1234
+ const text = lines.slice(0, 10).join("\n");
1235
+ return { text, exitCode: 1 };
1236
+ }
1237
+ var import_fs8, import_path9;
1238
+ var init_gate = __esm({
1239
+ "src/gate.ts"() {
1240
+ "use strict";
1241
+ import_fs8 = __toESM(require("fs"));
1242
+ import_path9 = __toESM(require("path"));
1243
+ }
1244
+ });
1245
+
1246
+ // src/fix.ts
1247
+ var fix_exports = {};
1248
+ __export(fix_exports, {
1249
+ runFix: () => runFix
1250
+ });
1251
+ function isTextLike2(p) {
1252
+ const ext = import_path10.default.extname(p).toLowerCase();
1253
+ return [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext);
1254
+ }
1255
+ function walk2(root, out) {
1256
+ const items = import_fs9.default.readdirSync(root, { withFileTypes: true });
1257
+ for (const it of items) {
1258
+ if (it.name.startsWith(".")) continue;
1259
+ if (DEFAULT_EXCLUDES2.has(it.name)) continue;
1260
+ const full = import_path10.default.join(root, it.name);
1261
+ if (it.isDirectory()) walk2(full, out);
1262
+ else out.push(full);
1263
+ }
1264
+ }
1265
+ function insertModelConst(content) {
1266
+ if (/\bAIOPT_MODEL\b/.test(content)) return { changed: false, next: content };
1267
+ if (!/\bprocess\.env\.AIOPT_MODEL\b/.test(content)) {
1268
+ }
1269
+ const line = "const AIOPT_MODEL = process.env.AIOPT_MODEL || 'gpt-5.2-mini';";
1270
+ const lines = content.split(/\r?\n/);
1271
+ let i = 0;
1272
+ if (lines[0] && lines[0].startsWith("#!")) i++;
1273
+ while (i < lines.length) {
1274
+ const t = lines[i].trim();
1275
+ if (t.startsWith("import ")) {
1276
+ i++;
1277
+ continue;
1278
+ }
1279
+ if (t.startsWith("const ") && t.includes("require(")) {
1280
+ i++;
1281
+ continue;
1282
+ }
1283
+ if (t === "" || t.startsWith("//") || t.startsWith("/*")) {
1284
+ i++;
1285
+ continue;
1286
+ }
1287
+ break;
1288
+ }
1289
+ lines.splice(i, 0, line, "");
1290
+ return { changed: true, next: lines.join("\n") };
1291
+ }
1292
+ function applyRetryCap(content) {
1293
+ let changed = false;
1294
+ let next = content;
1295
+ next = next.replace(/\b(maxRetries|maximumRetries|max_attempts|attempts)\s*:\s*(\d+)\b/gi, (m, key, num) => {
1296
+ const n = Number(num);
1297
+ if (!Number.isFinite(n) || n < 4) return m;
1298
+ changed = true;
1299
+ return `${key}: 3`;
1300
+ });
1301
+ next = next.replace(/\b(retries)\s*:\s*(\d+)\b/gi, (m, key, num) => {
1302
+ const n = Number(num);
1303
+ if (!Number.isFinite(n) || n < 4) return m;
1304
+ changed = true;
1305
+ return `${key}: 3`;
1306
+ });
1307
+ return { changed, next };
1308
+ }
1309
+ function applyModelRouting(content) {
1310
+ let changed = false;
1311
+ let next = content;
1312
+ const re = /(\bmodel\s*[:=]\s*)(['"])gpt-5\.2\2/g;
1313
+ if (re.test(next)) {
1314
+ const ins = insertModelConst(next);
1315
+ next = ins.next;
1316
+ next = next.replace(re, (_m, prefix) => {
1317
+ changed = true;
1318
+ return `${prefix}AIOPT_MODEL`;
1319
+ });
1320
+ const re2 = /(\bmodel\s*[:=]\s*)(['"])openai-codex\/gpt-5\.2\2/g;
1321
+ next = next.replace(re2, (_m, prefix) => {
1322
+ changed = true;
1323
+ return `${prefix}(process.env.AIOPT_MODEL_FULL || 'openai-codex/gpt-5.2-mini')`;
1324
+ });
1325
+ }
1326
+ return { changed, next };
1327
+ }
1328
+ function tmpFilePath(original) {
1329
+ const base = import_path10.default.basename(original);
1330
+ const rand = Math.random().toString(16).slice(2);
1331
+ return import_path10.default.join(import_os.default.tmpdir(), `aiopt-fix-${base}-${rand}`);
1332
+ }
1333
+ function diffNoIndex(oldPath, newPath) {
1334
+ try {
1335
+ return (0, import_child_process.execSync)(`git diff --no-index -- ${JSON.stringify(oldPath)} ${JSON.stringify(newPath)}`, {
1336
+ encoding: "utf8",
1337
+ stdio: ["ignore", "pipe", "pipe"]
1338
+ });
1339
+ } catch (e) {
1340
+ const out = String(e?.stdout || "");
1341
+ return out;
1342
+ }
1343
+ }
1344
+ function normalizePatchPaths(diffText, rel) {
1345
+ const raw = diffText.split(/\r?\n/);
1346
+ const lines = [];
1347
+ for (let i = 0; i < raw.length; i++) {
1348
+ const line = raw[i];
1349
+ if (line.startsWith("old mode ") || line.startsWith("new mode ")) continue;
1350
+ if (line.startsWith("diff --git ")) {
1351
+ lines.push(`diff --git a/${rel} b/${rel}`);
1352
+ continue;
1353
+ }
1354
+ if (line.startsWith("--- ")) {
1355
+ lines.push(`--- a/${rel}`);
1356
+ continue;
1357
+ }
1358
+ if (line.startsWith("+++ ")) {
1359
+ lines.push(`+++ b/${rel}`);
1360
+ continue;
1361
+ }
1362
+ lines.push(line);
1363
+ }
1364
+ return lines.join("\n");
1365
+ }
1366
+ function runFix(cwd, opts) {
1367
+ const files = [];
1368
+ walk2(cwd, files);
1369
+ const patches = [];
1370
+ const changedFiles = [];
1371
+ for (const file of files) {
1372
+ if (!isTextLike2(file)) continue;
1373
+ let st;
1374
+ try {
1375
+ st = import_fs9.default.statSync(file);
1376
+ } catch {
1377
+ continue;
1378
+ }
1379
+ if (!st.isFile()) continue;
1380
+ if (st.size > 1024 * 1024) continue;
1381
+ let content = "";
1382
+ try {
1383
+ content = import_fs9.default.readFileSync(file, "utf8");
1384
+ } catch {
1385
+ continue;
1386
+ }
1387
+ const r1 = applyRetryCap(content);
1388
+ const r2 = applyModelRouting(r1.next);
1389
+ const next = r2.next;
1390
+ if (next === content) continue;
1391
+ const tmp = tmpFilePath(file);
1392
+ import_fs9.default.writeFileSync(tmp, next);
1393
+ const rel = import_path10.default.relative(cwd, file).replace(/\\/g, "/");
1394
+ const d0 = diffNoIndex(file, tmp);
1395
+ const d = normalizePatchPaths(d0, rel);
1396
+ if (d && d.trim().length > 0) {
1397
+ patches.push(d);
1398
+ changedFiles.push(rel);
1399
+ }
1400
+ try {
1401
+ import_fs9.default.unlinkSync(tmp);
1402
+ } catch {
1403
+ }
1404
+ if (patches.join("\n").length > 5e5) break;
1405
+ }
1406
+ import_fs9.default.mkdirSync(opts.outDir, { recursive: true });
1407
+ const patchPath = import_path10.default.join(opts.outDir, "aiopt.patch");
1408
+ const header = [
1409
+ "# AIOpt patch (generated)",
1410
+ "# - retry cap: reduce high retry/attempt counts to 3",
1411
+ "# - model routing: cheap default via AIOPT_MODEL env override",
1412
+ ""
1413
+ ].join("\n");
1414
+ import_fs9.default.writeFileSync(patchPath, header + patches.join("\n"));
1415
+ if (opts.apply) {
1416
+ try {
1417
+ const inside = (0, import_child_process.execSync)("git rev-parse --is-inside-work-tree", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
1418
+ if (inside !== "true") {
1419
+ return { ok: false, applied: false, patchPath, changedFiles, hint: "not inside a git work tree (run from your repo root, or omit --apply and apply manually)" };
1420
+ }
1421
+ } catch {
1422
+ return { ok: false, applied: false, patchPath, changedFiles, hint: "git not available or not a git repo (omit --apply and apply manually)" };
1423
+ }
1424
+ try {
1425
+ (0, import_child_process.execSync)(`git apply ${JSON.stringify(patchPath)}`, { stdio: "inherit" });
1426
+ return { ok: true, applied: true, patchPath, changedFiles };
1427
+ } catch {
1428
+ return { ok: false, applied: false, patchPath, changedFiles, hint: "git apply failed. Ensure a clean working tree, then re-run; or open aiopt.patch and apply manually." };
1429
+ }
1430
+ }
1431
+ return { ok: true, applied: false, patchPath, changedFiles };
1432
+ }
1433
+ var import_fs9, import_path10, import_os, import_child_process, DEFAULT_EXCLUDES2;
1434
+ var init_fix = __esm({
1435
+ "src/fix.ts"() {
1436
+ "use strict";
1437
+ import_fs9 = __toESM(require("fs"));
1438
+ import_path10 = __toESM(require("path"));
1439
+ import_os = __toESM(require("os"));
1440
+ import_child_process = require("child_process");
1441
+ DEFAULT_EXCLUDES2 = /* @__PURE__ */ new Set([
1442
+ ".git",
1443
+ "node_modules",
1444
+ "dist",
1445
+ "aiopt-output",
1446
+ ".next",
1447
+ "build",
1448
+ "coverage"
1449
+ ]);
1450
+ }
1451
+ });
1452
+
1000
1453
  // src/guard.ts
1001
1454
  var guard_exports = {};
1002
1455
  __export(guard_exports, {
@@ -1221,12 +1674,12 @@ __export(dashboard_exports, {
1221
1674
  async function startDashboard(cwd, opts) {
1222
1675
  const host = "127.0.0.1";
1223
1676
  const port = opts.port || 3010;
1224
- const outDir = import_path7.default.join(cwd, "aiopt-output");
1225
- const file = (name) => import_path7.default.join(outDir, name);
1677
+ const outDir = import_path11.default.join(cwd, "aiopt-output");
1678
+ const file = (name) => import_path11.default.join(outDir, name);
1226
1679
  function readOrNull(p) {
1227
1680
  try {
1228
- if (!import_fs7.default.existsSync(p)) return null;
1229
- return import_fs7.default.readFileSync(p, "utf8");
1681
+ if (!import_fs10.default.existsSync(p)) return null;
1682
+ return import_fs10.default.readFileSync(p, "utf8");
1230
1683
  } catch {
1231
1684
  return null;
1232
1685
  }
@@ -1523,7 +1976,7 @@ load();
1523
1976
  const name = url.replace("/api/", "");
1524
1977
  if (name === "_meta") {
1525
1978
  const expected = ["guard-last.txt", "guard-last.json", "report.json", "report.md", "usage.jsonl", "guard-history.jsonl"];
1526
- const missing = expected.filter((f) => !import_fs7.default.existsSync(file(f)));
1979
+ const missing = expected.filter((f) => !import_fs10.default.existsSync(file(f)));
1527
1980
  res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
1528
1981
  res.end(JSON.stringify({ baseDir: cwd, outDir, missing }, null, 2));
1529
1982
  return;
@@ -1558,13 +2011,13 @@ load();
1558
2011
  await new Promise(() => {
1559
2012
  });
1560
2013
  }
1561
- var import_http, import_fs7, import_path7;
2014
+ var import_http, import_fs10, import_path11;
1562
2015
  var init_dashboard = __esm({
1563
2016
  "src/dashboard.ts"() {
1564
2017
  "use strict";
1565
2018
  import_http = __toESM(require("http"));
1566
- import_fs7 = __toESM(require("fs"));
1567
- import_path7 = __toESM(require("path"));
2019
+ import_fs10 = __toESM(require("fs"));
2020
+ import_path11 = __toESM(require("path"));
1568
2021
  }
1569
2022
  });
1570
2023
 
@@ -1574,55 +2027,55 @@ __export(find_output_exports, {
1574
2027
  findAioptOutputDir: () => findAioptOutputDir
1575
2028
  });
1576
2029
  function findAioptOutputDir(startCwd) {
1577
- let cur = import_path8.default.resolve(startCwd);
2030
+ let cur = import_path12.default.resolve(startCwd);
1578
2031
  while (true) {
1579
- const outDir = import_path8.default.join(cur, "aiopt-output");
1580
- if (import_fs8.default.existsSync(outDir)) {
2032
+ const outDir = import_path12.default.join(cur, "aiopt-output");
2033
+ if (import_fs11.default.existsSync(outDir)) {
1581
2034
  try {
1582
- if (import_fs8.default.statSync(outDir).isDirectory()) return { cwd: cur, outDir };
2035
+ if (import_fs11.default.statSync(outDir).isDirectory()) return { cwd: cur, outDir };
1583
2036
  } catch {
1584
2037
  }
1585
2038
  }
1586
- const parent = import_path8.default.dirname(cur);
2039
+ const parent = import_path12.default.dirname(cur);
1587
2040
  if (parent === cur) break;
1588
2041
  cur = parent;
1589
2042
  }
1590
2043
  try {
1591
- const base = import_path8.default.resolve(startCwd);
1592
- const children = import_fs8.default.readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => import_path8.default.join(base, d.name));
2044
+ const base = import_path12.default.resolve(startCwd);
2045
+ const children = import_fs11.default.readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => import_path12.default.join(base, d.name));
1593
2046
  for (const child of children) {
1594
- const outDir = import_path8.default.join(child, "aiopt-output");
1595
- if (import_fs8.default.existsSync(outDir)) {
2047
+ const outDir = import_path12.default.join(child, "aiopt-output");
2048
+ if (import_fs11.default.existsSync(outDir)) {
1596
2049
  try {
1597
- if (import_fs8.default.statSync(outDir).isDirectory()) return { cwd: child, outDir };
2050
+ if (import_fs11.default.statSync(outDir).isDirectory()) return { cwd: child, outDir };
1598
2051
  } catch {
1599
2052
  }
1600
2053
  }
1601
2054
  }
1602
2055
  } catch {
1603
2056
  }
1604
- return { cwd: import_path8.default.resolve(startCwd), outDir: import_path8.default.join(import_path8.default.resolve(startCwd), "aiopt-output") };
2057
+ return { cwd: import_path12.default.resolve(startCwd), outDir: import_path12.default.join(import_path12.default.resolve(startCwd), "aiopt-output") };
1605
2058
  }
1606
- var import_fs8, import_path8;
2059
+ var import_fs11, import_path12;
1607
2060
  var init_find_output = __esm({
1608
2061
  "src/find-output.ts"() {
1609
2062
  "use strict";
1610
- import_fs8 = __toESM(require("fs"));
1611
- import_path8 = __toESM(require("path"));
2063
+ import_fs11 = __toESM(require("fs"));
2064
+ import_path12 = __toESM(require("path"));
1612
2065
  }
1613
2066
  });
1614
2067
 
1615
2068
  // src/rates-util.ts
1616
2069
  function loadRateTableFromDistPath() {
1617
- const p = import_path9.default.join(__dirname, "..", "rates", "rate_table.json");
1618
- return JSON.parse(import_fs9.default.readFileSync(p, "utf8"));
2070
+ const p = import_path13.default.join(__dirname, "..", "rates", "rate_table.json");
2071
+ return JSON.parse(import_fs12.default.readFileSync(p, "utf8"));
1619
2072
  }
1620
- var import_fs9, import_path9;
2073
+ var import_fs12, import_path13;
1621
2074
  var init_rates_util = __esm({
1622
2075
  "src/rates-util.ts"() {
1623
2076
  "use strict";
1624
- import_fs9 = __toESM(require("fs"));
1625
- import_path9 = __toESM(require("path"));
2077
+ import_fs12 = __toESM(require("fs"));
2078
+ import_path13 = __toESM(require("path"));
1626
2079
  }
1627
2080
  });
1628
2081
 
@@ -1633,8 +2086,8 @@ __export(quickstart_exports, {
1633
2086
  seedDemoUsage: () => seedDemoUsage
1634
2087
  });
1635
2088
  function seedDemoUsage(outDir) {
1636
- import_fs10.default.mkdirSync(outDir, { recursive: true });
1637
- const p = import_path10.default.join(outDir, "usage.jsonl");
2089
+ import_fs13.default.mkdirSync(outDir, { recursive: true });
2090
+ const p = import_path14.default.join(outDir, "usage.jsonl");
1638
2091
  const now = Date.now();
1639
2092
  const lines = [];
1640
2093
  for (let i = 0; i < 60; i++) {
@@ -1651,18 +2104,18 @@ function seedDemoUsage(outDir) {
1651
2104
  meta: { feature_tag: i % 2 ? "summarize" : "coding" }
1652
2105
  });
1653
2106
  }
1654
- import_fs10.default.writeFileSync(p, lines.map((x) => JSON.stringify(x)).join("\n") + "\n");
2107
+ import_fs13.default.writeFileSync(p, lines.map((x) => JSON.stringify(x)).join("\n") + "\n");
1655
2108
  return p;
1656
2109
  }
1657
2110
  function runQuickstart(cwd, opts) {
1658
- const outDir = import_path10.default.join(cwd, "aiopt-output");
2111
+ const outDir = import_path14.default.join(cwd, "aiopt-output");
1659
2112
  const usagePath = seedDemoUsage(outDir);
1660
2113
  const rt = loadRateTableFromDistPath();
1661
2114
  const { readJsonl: readJsonl2 } = (init_io(), __toCommonJS(io_exports));
1662
2115
  const events = readJsonl2(usagePath);
1663
2116
  const { analysis, savings, policy, meta } = analyze(rt, events);
1664
- import_fs10.default.writeFileSync(import_path10.default.join(outDir, "analysis.json"), JSON.stringify(analysis, null, 2));
1665
- import_fs10.default.writeFileSync(import_path10.default.join(outDir, "report.json"), JSON.stringify({
2117
+ import_fs13.default.writeFileSync(import_path14.default.join(outDir, "analysis.json"), JSON.stringify(analysis, null, 2));
2118
+ import_fs13.default.writeFileSync(import_path14.default.join(outDir, "report.json"), JSON.stringify({
1666
2119
  version: 3,
1667
2120
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
1668
2121
  confidence: analysis.unknown_models?.length ? "MEDIUM" : "HIGH",
@@ -1682,8 +2135,8 @@ function runQuickstart(cwd, opts) {
1682
2135
  unknown_models: analysis.unknown_models || [],
1683
2136
  notes: []
1684
2137
  }, null, 2));
1685
- import_fs10.default.writeFileSync(import_path10.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
1686
- import_fs10.default.writeFileSync(import_path10.default.join(outDir, "report.md"), "# AIOpt quickstart demo\n\nThis is a demo report generated by `aiopt quickstart --demo`.\n");
2138
+ import_fs13.default.writeFileSync(import_path14.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
2139
+ import_fs13.default.writeFileSync(import_path14.default.join(outDir, "report.md"), "# AIOpt quickstart demo\n\nThis is a demo report generated by `aiopt quickstart --demo`.\n");
1687
2140
  const r = runGuard(rt, {
1688
2141
  baselineEvents: events,
1689
2142
  candidate: {
@@ -1694,12 +2147,12 @@ function runQuickstart(cwd, opts) {
1694
2147
  });
1695
2148
  return { usagePath, outDir, guard: r, port: opts.port };
1696
2149
  }
1697
- var import_fs10, import_path10;
2150
+ var import_fs13, import_path14;
1698
2151
  var init_quickstart = __esm({
1699
2152
  "src/quickstart.ts"() {
1700
2153
  "use strict";
1701
- import_fs10 = __toESM(require("fs"));
1702
- import_path10 = __toESM(require("path"));
2154
+ import_fs13 = __toESM(require("fs"));
2155
+ import_path14 = __toESM(require("path"));
1703
2156
  init_scan();
1704
2157
  init_rates_util();
1705
2158
  init_guard();
@@ -1707,8 +2160,8 @@ var init_quickstart = __esm({
1707
2160
  });
1708
2161
 
1709
2162
  // src/cli.ts
1710
- var import_fs11 = __toESM(require("fs"));
1711
- var import_path11 = __toESM(require("path"));
2163
+ var import_fs14 = __toESM(require("fs"));
2164
+ var import_path15 = __toESM(require("path"));
1712
2165
  var import_commander = require("commander");
1713
2166
  init_io();
1714
2167
  init_scan();
@@ -1716,17 +2169,17 @@ var program = new import_commander.Command();
1716
2169
  var DEFAULT_INPUT = "./aiopt-output/usage.jsonl";
1717
2170
  var DEFAULT_OUTPUT_DIR = "./aiopt-output";
1718
2171
  function loadRateTable() {
1719
- const p = import_path11.default.join(__dirname, "..", "rates", "rate_table.json");
1720
- return JSON.parse(import_fs11.default.readFileSync(p, "utf8"));
2172
+ const p = import_path15.default.join(__dirname, "..", "rates", "rate_table.json");
2173
+ return JSON.parse(import_fs14.default.readFileSync(p, "utf8"));
1721
2174
  }
1722
2175
  program.name("aiopt").description("AI \uBE44\uC6A9 \uC790\uB3D9 \uC808\uAC10 \uC778\uD504\uB77C \u2014 \uC11C\uBC84 \uC5C6\uB294 \uB85C\uCEEC CLI MVP").version(require_package().version);
1723
2176
  program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.jsonl, aiopt-output/ \uC0DD\uC131").action(() => {
1724
2177
  ensureDir("./aiopt-input");
1725
2178
  ensureDir("./aiopt-output");
1726
- const sampleSrc = import_path11.default.join(__dirname, "..", "samples", "sample_usage.jsonl");
1727
- const dst = import_path11.default.join("./aiopt-input", "usage.jsonl");
1728
- if (!import_fs11.default.existsSync(dst)) {
1729
- import_fs11.default.copyFileSync(sampleSrc, dst);
2179
+ const sampleSrc = import_path15.default.join(__dirname, "..", "samples", "sample_usage.jsonl");
2180
+ const dst = import_path15.default.join("./aiopt-input", "usage.jsonl");
2181
+ if (!import_fs14.default.existsSync(dst)) {
2182
+ import_fs14.default.copyFileSync(sampleSrc, dst);
1730
2183
  console.log("Created ./aiopt-input/usage.jsonl (sample)");
1731
2184
  } else {
1732
2185
  console.log("Exists ./aiopt-input/usage.jsonl (skip)");
@@ -1736,7 +2189,7 @@ program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.json
1736
2189
  program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C \uBD84\uC11D\uD558\uACE0 report.md/report.json + patches\uAE4C\uC9C0 \uC0DD\uC131").option("--input <path>", "input file path (default: ./aiopt-output/usage.jsonl)", DEFAULT_INPUT).option("--out <dir>", "output dir (default: ./aiopt-output)", DEFAULT_OUTPUT_DIR).action(async (opts) => {
1737
2190
  const inputPath = String(opts.input);
1738
2191
  const outDir = String(opts.out);
1739
- if (!import_fs11.default.existsSync(inputPath)) {
2192
+ if (!import_fs14.default.existsSync(inputPath)) {
1740
2193
  console.error(`Input not found: ${inputPath}`);
1741
2194
  process.exit(1);
1742
2195
  }
@@ -1744,7 +2197,7 @@ program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C
1744
2197
  const events = isCsvPath(inputPath) ? readCsv(inputPath) : readJsonl(inputPath);
1745
2198
  const { analysis, savings, policy, meta } = analyze(rt, events);
1746
2199
  policy.generated_from.input = inputPath;
1747
- writeOutputs(outDir, analysis, savings, policy, meta);
2200
+ writeOutputs(outDir, analysis, savings, policy, { ...meta, cwd: process.cwd(), cliVersion: program.version() });
1748
2201
  const { buildTopFixes: buildTopFixes2 } = await Promise.resolve().then(() => (init_solutions(), solutions_exports));
1749
2202
  const fixes = buildTopFixes2(analysis, savings).slice(0, 3);
1750
2203
  console.log("Top Fix 3:");
@@ -1752,7 +2205,7 @@ program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C
1752
2205
  const tag = f.status === "no-issue" ? "(no issue detected)" : `($${Math.round(f.impact_usd * 100) / 100})`;
1753
2206
  console.log(`${i + 1}) ${f.title} ${tag}`);
1754
2207
  });
1755
- console.log(`Report: ${import_path11.default.join(outDir, "report.md")}`);
2208
+ console.log(`Report: ${import_path15.default.join(outDir, "report.md")}`);
1756
2209
  });
1757
2210
  program.command("policy").description("\uB9C8\uC9C0\uB9C9 scan \uACB0\uACFC \uAE30\uBC18\uC73C\uB85C cost-policy.json\uB9CC \uC7AC\uC0DD\uC131 (MVP: scan\uACFC \uB3D9\uC77C \uB85C\uC9C1)").option("--input <path>", "input file path (default: ./aiopt-input/usage.jsonl)", DEFAULT_INPUT).option("--out <dir>", "output dir (default: ./aiopt-output)", DEFAULT_OUTPUT_DIR).action((opts) => {
1758
2211
  const inputPath = String(opts.input);
@@ -1762,7 +2215,7 @@ program.command("policy").description("\uB9C8\uC9C0\uB9C9 scan \uACB0\uACFC \uAE
1762
2215
  const { policy } = analyze(rt, events);
1763
2216
  policy.generated_from.input = inputPath;
1764
2217
  ensureDir(outDir);
1765
- import_fs11.default.writeFileSync(import_path11.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
2218
+ import_fs14.default.writeFileSync(import_path15.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
1766
2219
  console.log(`OK: ${outDir}/cost-policy.json`);
1767
2220
  });
1768
2221
  program.command("install").description("Install AIOpt guardrails: create aiopt/ + policies + usage.jsonl").option("--force", "overwrite existing files").option("--seed-sample", "seed 1 sample line into aiopt-output/usage.jsonl").action(async (opts) => {
@@ -1802,7 +2255,7 @@ licenseCmd.command("verify").option("--path <path>", "license.json path (default
1802
2255
  const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
1803
2256
  const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
1804
2257
  const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
1805
- if (!import_fs11.default.existsSync(p)) {
2258
+ if (!import_fs14.default.existsSync(p)) {
1806
2259
  console.error(`FAIL: license file not found: ${p}`);
1807
2260
  process.exit(3);
1808
2261
  }
@@ -1819,7 +2272,7 @@ licenseCmd.command("status").option("--path <path>", "license.json path (default
1819
2272
  const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
1820
2273
  const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
1821
2274
  const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
1822
- if (!import_fs11.default.existsSync(p)) {
2275
+ if (!import_fs14.default.existsSync(p)) {
1823
2276
  console.log("NO_LICENSE");
1824
2277
  process.exit(2);
1825
2278
  }
@@ -1832,6 +2285,44 @@ licenseCmd.command("status").option("--path <path>", "license.json path (default
1832
2285
  console.log(`INVALID: ${v.reason || "unknown"}`);
1833
2286
  process.exit(3);
1834
2287
  });
2288
+ program.command("gate").description("Merge gate (CI-friendly): fail (exit 1) when policy violations are detected; prints <=10 lines").option("--input <path>", "input usage jsonl/csv (default: ./aiopt-output/usage.jsonl)", DEFAULT_INPUT).option("--out <dir>", "output dir (default: ./aiopt-output)", DEFAULT_OUTPUT_DIR).action(async (opts) => {
2289
+ const inputPath = String(opts.input);
2290
+ const outDir = String(opts.out);
2291
+ if (!import_fs14.default.existsSync(inputPath)) {
2292
+ console.error(`FAIL: input not found: ${inputPath}`);
2293
+ process.exit(1);
2294
+ }
2295
+ const rt = loadRateTable();
2296
+ const events = isCsvPath(inputPath) ? readCsv(inputPath) : readJsonl(inputPath);
2297
+ const { analysis, savings, policy, meta } = analyze(rt, events);
2298
+ policy.generated_from.input = inputPath;
2299
+ writeOutputs(outDir, analysis, savings, policy, { ...meta, cwd: process.cwd(), cliVersion: program.version() });
2300
+ const { runGate: runGate2, formatGateStdout: formatGateStdout2 } = await Promise.resolve().then(() => (init_gate(), gate_exports));
2301
+ const r = runGate2(outDir, process.cwd());
2302
+ const out = formatGateStdout2(r, outDir);
2303
+ console.log(out.text);
2304
+ process.exit(out.exitCode);
2305
+ });
2306
+ program.command("fix").description("Auto-fix suggestions: generate aiopt.patch (and optionally apply it via git apply)").option("--out <dir>", "output dir (default: ./aiopt-output)", DEFAULT_OUTPUT_DIR).option("--apply", "apply the generated patch via git apply").action(async (opts) => {
2307
+ const outDir = String(opts.out);
2308
+ const { runFix: runFix2 } = await Promise.resolve().then(() => (init_fix(), fix_exports));
2309
+ const r = runFix2(process.cwd(), { outDir, apply: Boolean(opts.apply) });
2310
+ console.log(`Patch: ${r.patchPath}`);
2311
+ if (r.changedFiles.length) {
2312
+ console.log(`Files: ${r.changedFiles.slice(0, 10).join(", ")}${r.changedFiles.length > 10 ? " ..." : ""}`);
2313
+ } else {
2314
+ console.log("No changes suggested.");
2315
+ }
2316
+ if (r.applied) {
2317
+ console.log("OK: patch applied");
2318
+ process.exit(0);
2319
+ }
2320
+ if (!r.ok) {
2321
+ console.error(`FAIL: could not apply patch${r.hint ? ` (${r.hint})` : ""}`);
2322
+ process.exit(1);
2323
+ }
2324
+ process.exit(0);
2325
+ });
1835
2326
  program.command("guard").description("Pre-deploy guardrail: compare baseline usage vs candidate change (or diff two log sets) and print warnings (exit codes 0/2/3)").option("--input <path>", "baseline usage jsonl/csv (legacy alias for --baseline; default: ./aiopt-output/usage.jsonl)", DEFAULT_INPUT).option("--baseline <path>", "baseline usage jsonl/csv (diff mode when used with --candidate)").option("--candidate <path>", "candidate usage jsonl/csv (diff mode: compare two real log sets)").option("--provider <provider>", "candidate provider override (transform mode only)").option("--model <model>", "candidate model override (transform mode only)").option("--context-mult <n>", "multiply input_tokens by n (transform mode only)", (v) => Number(v)).option("--output-mult <n>", "multiply output_tokens by n (transform mode only)", (v) => Number(v)).option("--retries-delta <n>", "add n to retries (transform mode only)", (v) => Number(v)).option("--call-mult <n>", "multiply call volume by n (traffic spike)", (v) => Number(v)).option("--budget-monthly <usd>", "fail if estimated candidate monthly cost exceeds this budget", (v) => Number(v)).action(async (opts) => {
1836
2327
  const rt = loadRateTable();
1837
2328
  const baselinePath = String(opts.baseline || opts.input);
@@ -1841,11 +2332,11 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
1841
2332
  console.error("FAIL: diff mode requires both --baseline and --candidate");
1842
2333
  process.exit(3);
1843
2334
  }
1844
- if (!import_fs11.default.existsSync(baselinePath)) {
2335
+ if (!import_fs14.default.existsSync(baselinePath)) {
1845
2336
  console.error(`FAIL: baseline not found: ${baselinePath}`);
1846
2337
  process.exit(3);
1847
2338
  }
1848
- if (candidatePath && !import_fs11.default.existsSync(candidatePath)) {
2339
+ if (candidatePath && !import_fs14.default.existsSync(candidatePath)) {
1849
2340
  console.error(`FAIL: candidate not found: ${candidatePath}`);
1850
2341
  process.exit(3);
1851
2342
  }
@@ -1867,13 +2358,13 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
1867
2358
  });
1868
2359
  console.log(r.message);
1869
2360
  try {
1870
- const outDir = import_path11.default.resolve(DEFAULT_OUTPUT_DIR);
1871
- import_fs11.default.mkdirSync(outDir, { recursive: true });
2361
+ const outDir = import_path15.default.resolve(DEFAULT_OUTPUT_DIR);
2362
+ import_fs14.default.mkdirSync(outDir, { recursive: true });
1872
2363
  const ts = (/* @__PURE__ */ new Date()).toISOString();
1873
- import_fs11.default.writeFileSync(import_path11.default.join(outDir, "guard-last.txt"), r.message);
1874
- import_fs11.default.writeFileSync(import_path11.default.join(outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.exitCode }, null, 2));
2364
+ import_fs14.default.writeFileSync(import_path15.default.join(outDir, "guard-last.txt"), r.message);
2365
+ import_fs14.default.writeFileSync(import_path15.default.join(outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.exitCode }, null, 2));
1875
2366
  const histLine = JSON.stringify({ ts, exitCode: r.exitCode, mode: candidateEvents ? "diff" : "transform", baseline: baselinePath, candidate: candidatePath }) + "\n";
1876
- import_fs11.default.appendFileSync(import_path11.default.join(outDir, "guard-history.jsonl"), histLine);
2367
+ import_fs14.default.appendFileSync(import_path15.default.join(outDir, "guard-history.jsonl"), histLine);
1877
2368
  } catch {
1878
2369
  }
1879
2370
  process.exit(r.exitCode);
@@ -1901,14 +2392,14 @@ program.command("quickstart").description("1-minute demo: generate sample usage,
1901
2392
  console.log("--- guard ---");
1902
2393
  console.log(r.guard.message);
1903
2394
  try {
1904
- const fs12 = await import("fs");
1905
- const path12 = await import("path");
1906
- fs12.mkdirSync(r.outDir, { recursive: true });
2395
+ const fs15 = await import("fs");
2396
+ const path16 = await import("path");
2397
+ fs15.mkdirSync(r.outDir, { recursive: true });
1907
2398
  const ts = (/* @__PURE__ */ new Date()).toISOString();
1908
- fs12.writeFileSync(path12.join(r.outDir, "guard-last.txt"), r.guard.message);
1909
- fs12.writeFileSync(path12.join(r.outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.guard.exitCode }, null, 2));
2399
+ fs15.writeFileSync(path16.join(r.outDir, "guard-last.txt"), r.guard.message);
2400
+ fs15.writeFileSync(path16.join(r.outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.guard.exitCode }, null, 2));
1910
2401
  const histLine = JSON.stringify({ ts, exitCode: r.guard.exitCode, mode: "quickstart", baseline: r.usagePath, candidate: null }) + "\n";
1911
- fs12.appendFileSync(path12.join(r.outDir, "guard-history.jsonl"), histLine);
2402
+ fs15.appendFileSync(path16.join(r.outDir, "guard-history.jsonl"), histLine);
1912
2403
  } catch {
1913
2404
  }
1914
2405
  console.log("--- next ---");
@@ -1917,11 +2408,11 @@ program.command("quickstart").description("1-minute demo: generate sample usage,
1917
2408
  const { startDashboard: startDashboard2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
1918
2409
  if (opts.open) {
1919
2410
  try {
1920
- const { execSync } = await import("child_process");
2411
+ const { execSync: execSync2 } = await import("child_process");
1921
2412
  const url = `http://127.0.0.1:${port}/`;
1922
- if (process.platform === "darwin") execSync(`open "${url}"`);
1923
- else if (process.platform === "win32") execSync(`cmd.exe /c start "" "${url}"`);
1924
- else execSync(`xdg-open "${url}"`);
2413
+ if (process.platform === "darwin") execSync2(`open "${url}"`);
2414
+ else if (process.platform === "win32") execSync2(`cmd.exe /c start "" "${url}"`);
2415
+ else execSync2(`xdg-open "${url}"`);
1925
2416
  } catch {
1926
2417
  }
1927
2418
  }