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/README.md +5 -0
- package/dist/cli.js +613 -122
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
391
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
463
|
-
|
|
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
|
|
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
|
-
|
|
472
|
-
|
|
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.
|
|
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
|
-
|
|
700
|
+
import_fs5.default.mkdirSync(p, { recursive: true });
|
|
526
701
|
}
|
|
527
702
|
function writeFile(filePath, content, force) {
|
|
528
|
-
if (!force &&
|
|
529
|
-
ensureDir2(
|
|
530
|
-
|
|
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 =
|
|
536
|
-
const policiesDir =
|
|
537
|
-
const outDir =
|
|
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(
|
|
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(
|
|
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(
|
|
635
|
-
const p2 = writeFile(
|
|
636
|
-
const p3 = writeFile(
|
|
637
|
-
const p4 = writeFile(
|
|
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 =
|
|
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 =
|
|
797
|
-
if (force || !
|
|
798
|
-
|
|
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
|
-
|
|
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
|
|
1000
|
+
var import_fs5, import_path6;
|
|
826
1001
|
var init_install = __esm({
|
|
827
1002
|
"src/install.ts"() {
|
|
828
1003
|
"use strict";
|
|
829
|
-
|
|
830
|
-
|
|
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
|
-
|
|
842
|
-
const p =
|
|
843
|
-
|
|
844
|
-
|
|
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 =
|
|
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 =
|
|
861
|
-
const policiesDir =
|
|
862
|
-
const outDir =
|
|
863
|
-
const usagePath =
|
|
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:
|
|
866
|
-
checks.push({ name: "aiopt/policies exists", ok:
|
|
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:
|
|
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
|
|
1081
|
+
var import_fs6, import_path7;
|
|
907
1082
|
var init_doctor = __esm({
|
|
908
1083
|
"src/doctor.ts"() {
|
|
909
1084
|
"use strict";
|
|
910
|
-
|
|
911
|
-
|
|
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
|
|
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
|
-
|
|
976
|
-
|
|
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(
|
|
1154
|
+
return JSON.parse(import_fs7.default.readFileSync(p, "utf8"));
|
|
980
1155
|
}
|
|
981
|
-
var import_crypto,
|
|
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
|
-
|
|
987
|
-
|
|
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 =
|
|
1225
|
-
const file = (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 (!
|
|
1229
|
-
return
|
|
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) => !
|
|
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,
|
|
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
|
-
|
|
1567
|
-
|
|
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 =
|
|
2030
|
+
let cur = import_path12.default.resolve(startCwd);
|
|
1578
2031
|
while (true) {
|
|
1579
|
-
const outDir =
|
|
1580
|
-
if (
|
|
2032
|
+
const outDir = import_path12.default.join(cur, "aiopt-output");
|
|
2033
|
+
if (import_fs11.default.existsSync(outDir)) {
|
|
1581
2034
|
try {
|
|
1582
|
-
if (
|
|
2035
|
+
if (import_fs11.default.statSync(outDir).isDirectory()) return { cwd: cur, outDir };
|
|
1583
2036
|
} catch {
|
|
1584
2037
|
}
|
|
1585
2038
|
}
|
|
1586
|
-
const parent =
|
|
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 =
|
|
1592
|
-
const children =
|
|
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 =
|
|
1595
|
-
if (
|
|
2047
|
+
const outDir = import_path12.default.join(child, "aiopt-output");
|
|
2048
|
+
if (import_fs11.default.existsSync(outDir)) {
|
|
1596
2049
|
try {
|
|
1597
|
-
if (
|
|
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:
|
|
2057
|
+
return { cwd: import_path12.default.resolve(startCwd), outDir: import_path12.default.join(import_path12.default.resolve(startCwd), "aiopt-output") };
|
|
1605
2058
|
}
|
|
1606
|
-
var
|
|
2059
|
+
var import_fs11, import_path12;
|
|
1607
2060
|
var init_find_output = __esm({
|
|
1608
2061
|
"src/find-output.ts"() {
|
|
1609
2062
|
"use strict";
|
|
1610
|
-
|
|
1611
|
-
|
|
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 =
|
|
1618
|
-
return JSON.parse(
|
|
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
|
|
2073
|
+
var import_fs12, import_path13;
|
|
1621
2074
|
var init_rates_util = __esm({
|
|
1622
2075
|
"src/rates-util.ts"() {
|
|
1623
2076
|
"use strict";
|
|
1624
|
-
|
|
1625
|
-
|
|
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
|
-
|
|
1637
|
-
const p =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1665
|
-
|
|
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
|
-
|
|
1686
|
-
|
|
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
|
|
2150
|
+
var import_fs13, import_path14;
|
|
1698
2151
|
var init_quickstart = __esm({
|
|
1699
2152
|
"src/quickstart.ts"() {
|
|
1700
2153
|
"use strict";
|
|
1701
|
-
|
|
1702
|
-
|
|
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
|
|
1711
|
-
var
|
|
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 =
|
|
1720
|
-
return JSON.parse(
|
|
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 =
|
|
1727
|
-
const dst =
|
|
1728
|
-
if (!
|
|
1729
|
-
|
|
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 (!
|
|
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: ${
|
|
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
|
-
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 && !
|
|
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 =
|
|
1871
|
-
|
|
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
|
-
|
|
1874
|
-
|
|
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
|
-
|
|
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
|
|
1905
|
-
const
|
|
1906
|
-
|
|
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
|
-
|
|
1909
|
-
|
|
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
|
-
|
|
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")
|
|
1923
|
-
else if (process.platform === "win32")
|
|
1924
|
-
else
|
|
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
|
}
|