@xerg/cli 0.5.1 → 0.5.3
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 +11 -6
- package/dist/browser.js +3 -0
- package/dist/browser.js.map +1 -0
- package/dist/index.js +362 -444
- package/dist/index.js.map +1 -1
- package/package.json +9 -5
- package/skills/xerg/SKILL.md +15 -4
package/dist/index.js
CHANGED
|
@@ -122,23 +122,6 @@ import { closeSync, openSync, readSync } from "fs";
|
|
|
122
122
|
function sha1(input) {
|
|
123
123
|
return createHash("sha1").update(input).digest("hex");
|
|
124
124
|
}
|
|
125
|
-
function sha1File(path) {
|
|
126
|
-
const hash = createHash("sha1");
|
|
127
|
-
const fd = openSync(path, "r");
|
|
128
|
-
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
129
|
-
try {
|
|
130
|
-
let bytesRead = 0;
|
|
131
|
-
do {
|
|
132
|
-
bytesRead = readSync(fd, buffer, 0, buffer.length, null);
|
|
133
|
-
if (bytesRead > 0) {
|
|
134
|
-
hash.update(buffer.subarray(0, bytesRead));
|
|
135
|
-
}
|
|
136
|
-
} while (bytesRead > 0);
|
|
137
|
-
} finally {
|
|
138
|
-
closeSync(fd);
|
|
139
|
-
}
|
|
140
|
-
return hash.digest("hex");
|
|
141
|
-
}
|
|
142
125
|
|
|
143
126
|
// ../core/src/utils/time.ts
|
|
144
127
|
function parseSince(value) {
|
|
@@ -736,354 +719,80 @@ async function inspectCursorUsageCsv(options) {
|
|
|
736
719
|
}
|
|
737
720
|
|
|
738
721
|
// ../core/src/db/client.ts
|
|
739
|
-
import { mkdirSync } from "fs";
|
|
722
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
|
|
740
723
|
import { dirname } from "path";
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
kind TEXT NOT NULL,
|
|
750
|
-
file_hash TEXT NOT NULL,
|
|
751
|
-
mtime_ms INTEGER NOT NULL,
|
|
752
|
-
size_bytes INTEGER NOT NULL,
|
|
753
|
-
imported_at TEXT NOT NULL
|
|
754
|
-
);
|
|
755
|
-
|
|
756
|
-
CREATE TABLE IF NOT EXISTS runs (
|
|
757
|
-
id TEXT PRIMARY KEY,
|
|
758
|
-
source_path TEXT NOT NULL,
|
|
759
|
-
source_kind TEXT NOT NULL,
|
|
760
|
-
timestamp TEXT NOT NULL,
|
|
761
|
-
workflow TEXT NOT NULL,
|
|
762
|
-
environment TEXT NOT NULL,
|
|
763
|
-
tags_json TEXT NOT NULL,
|
|
764
|
-
total_cost_usd REAL NOT NULL,
|
|
765
|
-
total_tokens INTEGER NOT NULL,
|
|
766
|
-
observed_cost_usd REAL NOT NULL,
|
|
767
|
-
estimated_cost_usd REAL NOT NULL
|
|
768
|
-
);
|
|
769
|
-
|
|
770
|
-
CREATE TABLE IF NOT EXISTS calls (
|
|
771
|
-
id TEXT PRIMARY KEY,
|
|
772
|
-
run_id TEXT NOT NULL,
|
|
773
|
-
timestamp TEXT NOT NULL,
|
|
774
|
-
provider TEXT NOT NULL,
|
|
775
|
-
model TEXT NOT NULL,
|
|
776
|
-
input_tokens INTEGER NOT NULL,
|
|
777
|
-
output_tokens INTEGER NOT NULL,
|
|
778
|
-
cost_usd REAL NOT NULL,
|
|
779
|
-
cost_source TEXT NOT NULL,
|
|
780
|
-
latency_ms INTEGER,
|
|
781
|
-
tool_calls INTEGER NOT NULL,
|
|
782
|
-
retries INTEGER NOT NULL,
|
|
783
|
-
attempt INTEGER,
|
|
784
|
-
iteration INTEGER,
|
|
785
|
-
status TEXT,
|
|
786
|
-
task_class TEXT,
|
|
787
|
-
cache_hit INTEGER NOT NULL,
|
|
788
|
-
cache_cost_usd REAL,
|
|
789
|
-
metadata_json TEXT NOT NULL
|
|
790
|
-
);
|
|
791
|
-
|
|
792
|
-
CREATE TABLE IF NOT EXISTS findings (
|
|
793
|
-
id TEXT PRIMARY KEY,
|
|
794
|
-
audit_id TEXT NOT NULL,
|
|
795
|
-
classification TEXT NOT NULL,
|
|
796
|
-
confidence TEXT NOT NULL,
|
|
797
|
-
kind TEXT NOT NULL,
|
|
798
|
-
title TEXT NOT NULL,
|
|
799
|
-
summary TEXT NOT NULL,
|
|
800
|
-
scope TEXT NOT NULL,
|
|
801
|
-
scope_id TEXT NOT NULL,
|
|
802
|
-
cost_impact_usd REAL NOT NULL,
|
|
803
|
-
details_json TEXT NOT NULL
|
|
804
|
-
);
|
|
805
|
-
|
|
806
|
-
CREATE TABLE IF NOT EXISTS pricing_catalog (
|
|
807
|
-
id TEXT PRIMARY KEY,
|
|
808
|
-
provider TEXT NOT NULL,
|
|
809
|
-
model TEXT NOT NULL,
|
|
810
|
-
effective_date TEXT NOT NULL,
|
|
811
|
-
input_per_1m REAL NOT NULL,
|
|
812
|
-
output_per_1m REAL NOT NULL,
|
|
813
|
-
cached_input_per_1m REAL
|
|
814
|
-
);
|
|
815
|
-
|
|
816
|
-
CREATE TABLE IF NOT EXISTS audit_snapshots (
|
|
817
|
-
id TEXT PRIMARY KEY,
|
|
818
|
-
created_at TEXT NOT NULL,
|
|
819
|
-
summary_json TEXT NOT NULL
|
|
820
|
-
);
|
|
821
|
-
`;
|
|
822
|
-
|
|
823
|
-
// ../core/src/db/client.ts
|
|
824
|
-
function createDb(path) {
|
|
825
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
826
|
-
const sqlite = new Database(path);
|
|
827
|
-
const currentVersion = sqlite.pragma("user_version", { simple: true });
|
|
828
|
-
if (currentVersion > SCHEMA_VERSION) {
|
|
829
|
-
sqlite.close();
|
|
830
|
-
throw new Error(
|
|
831
|
-
`Unsupported Xerg database schema version ${currentVersion}. This build supports up to ${SCHEMA_VERSION}.`
|
|
724
|
+
var SNAPSHOT_STORE_VERSION = 1;
|
|
725
|
+
function readStoredSnapshotRows(path) {
|
|
726
|
+
if (!existsSync(path)) {
|
|
727
|
+
return [];
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
return normalizeStore(JSON.parse(readFileSync2(path, "utf8"))).auditSnapshots.sort(
|
|
731
|
+
(left, right) => right.createdAt.localeCompare(left.createdAt)
|
|
832
732
|
);
|
|
733
|
+
} catch (error) {
|
|
734
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
735
|
+
process.stderr.write(`Warning: skipping unreadable local snapshot store ${path}: ${message}
|
|
736
|
+
`);
|
|
737
|
+
return [];
|
|
833
738
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
739
|
+
}
|
|
740
|
+
function writeStoredSnapshotRows(path, rows) {
|
|
741
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
742
|
+
const store = {
|
|
743
|
+
version: SNAPSHOT_STORE_VERSION,
|
|
744
|
+
auditSnapshots: rows.sort((left, right) => right.createdAt.localeCompare(left.createdAt))
|
|
745
|
+
};
|
|
746
|
+
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
747
|
+
writeFileSync(tempPath, `${JSON.stringify(store, null, 2)}
|
|
748
|
+
`, "utf8");
|
|
749
|
+
renameSync(tempPath, path);
|
|
750
|
+
}
|
|
751
|
+
function upsertStoredAuditSnapshot(path, row) {
|
|
752
|
+
const rows = readStoredSnapshotRows(path);
|
|
753
|
+
if (rows.some((existing) => existing.id === row.id)) {
|
|
754
|
+
return;
|
|
837
755
|
}
|
|
838
|
-
|
|
756
|
+
writeStoredSnapshotRows(path, [...rows, row]);
|
|
839
757
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
const { sqlite } = createDb(dbPath);
|
|
844
|
-
const importedAt = isoNow();
|
|
845
|
-
const pricingRows = audit.pricingCatalog.map((entry) => ({
|
|
846
|
-
...entry,
|
|
847
|
-
cachedInputPer1m: entry.cachedInputPer1m ?? null
|
|
848
|
-
}));
|
|
849
|
-
const sourceFileRows = audit.summary.sourceFiles.map((file) => ({
|
|
850
|
-
id: sha1(`${file.path}:${file.mtimeMs}:${file.sizeBytes}`),
|
|
851
|
-
path: file.path,
|
|
852
|
-
kind: file.kind,
|
|
853
|
-
fileHash: sha1File(file.path),
|
|
854
|
-
mtimeMs: Math.trunc(file.mtimeMs),
|
|
855
|
-
sizeBytes: file.sizeBytes,
|
|
856
|
-
importedAt
|
|
857
|
-
}));
|
|
858
|
-
const runRows = audit.runs.map((run2) => ({
|
|
859
|
-
id: run2.id,
|
|
860
|
-
sourcePath: run2.sourcePath,
|
|
861
|
-
sourceKind: run2.sourceKind,
|
|
862
|
-
timestamp: run2.timestamp,
|
|
863
|
-
workflow: run2.workflow,
|
|
864
|
-
environment: run2.environment,
|
|
865
|
-
tagsJson: JSON.stringify(run2.tags),
|
|
866
|
-
totalCostUsd: run2.totalCostUsd,
|
|
867
|
-
totalTokens: run2.totalTokens,
|
|
868
|
-
observedCostUsd: run2.observedCostUsd,
|
|
869
|
-
estimatedCostUsd: run2.estimatedCostUsd
|
|
870
|
-
}));
|
|
871
|
-
const callRows = audit.runs.flatMap(
|
|
872
|
-
(run2) => run2.calls.map((call) => ({
|
|
873
|
-
id: call.id,
|
|
874
|
-
runId: call.runId,
|
|
875
|
-
timestamp: call.timestamp,
|
|
876
|
-
provider: call.provider,
|
|
877
|
-
model: call.model,
|
|
878
|
-
inputTokens: call.inputTokens,
|
|
879
|
-
outputTokens: call.outputTokens,
|
|
880
|
-
costUsd: call.costUsd,
|
|
881
|
-
costSource: call.costSource,
|
|
882
|
-
latencyMs: call.latencyMs,
|
|
883
|
-
toolCalls: call.toolCalls,
|
|
884
|
-
retries: call.retries,
|
|
885
|
-
attempt: call.attempt,
|
|
886
|
-
iteration: call.iteration,
|
|
887
|
-
status: call.status,
|
|
888
|
-
taskClass: call.taskClass,
|
|
889
|
-
cacheHit: call.cacheHit,
|
|
890
|
-
cacheCostUsd: call.cacheCostUsd,
|
|
891
|
-
metadataJson: JSON.stringify(call.metadata)
|
|
892
|
-
}))
|
|
893
|
-
);
|
|
894
|
-
const findingRows = audit.summary.findings.map((finding) => ({
|
|
895
|
-
id: finding.id,
|
|
896
|
-
auditId: audit.summary.auditId,
|
|
897
|
-
classification: finding.classification,
|
|
898
|
-
confidence: finding.confidence,
|
|
899
|
-
kind: finding.kind,
|
|
900
|
-
title: finding.title,
|
|
901
|
-
summary: finding.summary,
|
|
902
|
-
scope: finding.scope,
|
|
903
|
-
scopeId: finding.scopeId,
|
|
904
|
-
costImpactUsd: finding.costImpactUsd,
|
|
905
|
-
detailsJson: JSON.stringify(finding.details)
|
|
906
|
-
}));
|
|
907
|
-
const persistTransaction = sqlite.transaction(() => {
|
|
908
|
-
insertMany(
|
|
909
|
-
sqlite,
|
|
910
|
-
`
|
|
911
|
-
INSERT OR IGNORE INTO pricing_catalog (
|
|
912
|
-
id,
|
|
913
|
-
provider,
|
|
914
|
-
model,
|
|
915
|
-
effective_date,
|
|
916
|
-
input_per_1m,
|
|
917
|
-
output_per_1m,
|
|
918
|
-
cached_input_per_1m
|
|
919
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
920
|
-
`,
|
|
921
|
-
pricingRows.map((row) => [
|
|
922
|
-
row.id,
|
|
923
|
-
row.provider,
|
|
924
|
-
row.model,
|
|
925
|
-
row.effectiveDate,
|
|
926
|
-
row.inputPer1m,
|
|
927
|
-
row.outputPer1m,
|
|
928
|
-
row.cachedInputPer1m
|
|
929
|
-
])
|
|
930
|
-
);
|
|
931
|
-
insertMany(
|
|
932
|
-
sqlite,
|
|
933
|
-
`
|
|
934
|
-
INSERT OR IGNORE INTO source_files (
|
|
935
|
-
id,
|
|
936
|
-
path,
|
|
937
|
-
kind,
|
|
938
|
-
file_hash,
|
|
939
|
-
mtime_ms,
|
|
940
|
-
size_bytes,
|
|
941
|
-
imported_at
|
|
942
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
943
|
-
`,
|
|
944
|
-
sourceFileRows.map((row) => [
|
|
945
|
-
row.id,
|
|
946
|
-
row.path,
|
|
947
|
-
row.kind,
|
|
948
|
-
row.fileHash,
|
|
949
|
-
row.mtimeMs,
|
|
950
|
-
row.sizeBytes,
|
|
951
|
-
row.importedAt
|
|
952
|
-
])
|
|
953
|
-
);
|
|
954
|
-
insertMany(
|
|
955
|
-
sqlite,
|
|
956
|
-
`
|
|
957
|
-
INSERT OR IGNORE INTO runs (
|
|
958
|
-
id,
|
|
959
|
-
source_path,
|
|
960
|
-
source_kind,
|
|
961
|
-
timestamp,
|
|
962
|
-
workflow,
|
|
963
|
-
environment,
|
|
964
|
-
tags_json,
|
|
965
|
-
total_cost_usd,
|
|
966
|
-
total_tokens,
|
|
967
|
-
observed_cost_usd,
|
|
968
|
-
estimated_cost_usd
|
|
969
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
970
|
-
`,
|
|
971
|
-
runRows.map((row) => [
|
|
972
|
-
row.id,
|
|
973
|
-
row.sourcePath,
|
|
974
|
-
row.sourceKind,
|
|
975
|
-
row.timestamp,
|
|
976
|
-
row.workflow,
|
|
977
|
-
row.environment,
|
|
978
|
-
row.tagsJson,
|
|
979
|
-
row.totalCostUsd,
|
|
980
|
-
row.totalTokens,
|
|
981
|
-
row.observedCostUsd,
|
|
982
|
-
row.estimatedCostUsd
|
|
983
|
-
])
|
|
984
|
-
);
|
|
985
|
-
insertMany(
|
|
986
|
-
sqlite,
|
|
987
|
-
`
|
|
988
|
-
INSERT OR IGNORE INTO calls (
|
|
989
|
-
id,
|
|
990
|
-
run_id,
|
|
991
|
-
timestamp,
|
|
992
|
-
provider,
|
|
993
|
-
model,
|
|
994
|
-
input_tokens,
|
|
995
|
-
output_tokens,
|
|
996
|
-
cost_usd,
|
|
997
|
-
cost_source,
|
|
998
|
-
latency_ms,
|
|
999
|
-
tool_calls,
|
|
1000
|
-
retries,
|
|
1001
|
-
attempt,
|
|
1002
|
-
iteration,
|
|
1003
|
-
status,
|
|
1004
|
-
task_class,
|
|
1005
|
-
cache_hit,
|
|
1006
|
-
cache_cost_usd,
|
|
1007
|
-
metadata_json
|
|
1008
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1009
|
-
`,
|
|
1010
|
-
callRows.map((row) => [
|
|
1011
|
-
row.id,
|
|
1012
|
-
row.runId,
|
|
1013
|
-
row.timestamp,
|
|
1014
|
-
row.provider,
|
|
1015
|
-
row.model,
|
|
1016
|
-
row.inputTokens,
|
|
1017
|
-
row.outputTokens,
|
|
1018
|
-
row.costUsd,
|
|
1019
|
-
row.costSource,
|
|
1020
|
-
row.latencyMs ?? null,
|
|
1021
|
-
row.toolCalls,
|
|
1022
|
-
row.retries,
|
|
1023
|
-
row.attempt ?? null,
|
|
1024
|
-
row.iteration ?? null,
|
|
1025
|
-
row.status ?? null,
|
|
1026
|
-
row.taskClass ?? null,
|
|
1027
|
-
row.cacheHit ? 1 : 0,
|
|
1028
|
-
row.cacheCostUsd ?? null,
|
|
1029
|
-
row.metadataJson
|
|
1030
|
-
])
|
|
1031
|
-
);
|
|
1032
|
-
insertMany(
|
|
1033
|
-
sqlite,
|
|
1034
|
-
`
|
|
1035
|
-
INSERT OR IGNORE INTO findings (
|
|
1036
|
-
id,
|
|
1037
|
-
audit_id,
|
|
1038
|
-
classification,
|
|
1039
|
-
confidence,
|
|
1040
|
-
kind,
|
|
1041
|
-
title,
|
|
1042
|
-
summary,
|
|
1043
|
-
scope,
|
|
1044
|
-
scope_id,
|
|
1045
|
-
cost_impact_usd,
|
|
1046
|
-
details_json
|
|
1047
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1048
|
-
`,
|
|
1049
|
-
findingRows.map((row) => [
|
|
1050
|
-
row.id,
|
|
1051
|
-
row.auditId,
|
|
1052
|
-
row.classification,
|
|
1053
|
-
row.confidence,
|
|
1054
|
-
row.kind,
|
|
1055
|
-
row.title,
|
|
1056
|
-
row.summary,
|
|
1057
|
-
row.scope,
|
|
1058
|
-
row.scopeId,
|
|
1059
|
-
row.costImpactUsd,
|
|
1060
|
-
row.detailsJson
|
|
1061
|
-
])
|
|
1062
|
-
);
|
|
1063
|
-
sqlite.prepare(
|
|
1064
|
-
`
|
|
1065
|
-
INSERT OR IGNORE INTO audit_snapshots (
|
|
1066
|
-
id,
|
|
1067
|
-
created_at,
|
|
1068
|
-
summary_json
|
|
1069
|
-
) VALUES (?, ?, ?)
|
|
1070
|
-
`
|
|
1071
|
-
).run(audit.summary.auditId, audit.summary.generatedAt, JSON.stringify(audit.summary));
|
|
1072
|
-
});
|
|
1073
|
-
try {
|
|
1074
|
-
persistTransaction();
|
|
1075
|
-
} finally {
|
|
1076
|
-
sqlite.close();
|
|
758
|
+
function normalizeStore(input) {
|
|
759
|
+
if (!input || typeof input !== "object") {
|
|
760
|
+
throw new Error("Snapshot store is not an object.");
|
|
1077
761
|
}
|
|
762
|
+
const store = input;
|
|
763
|
+
if (store.version !== SNAPSHOT_STORE_VERSION) {
|
|
764
|
+
throw new Error(`Unsupported snapshot store version ${String(store.version)}.`);
|
|
765
|
+
}
|
|
766
|
+
if (!Array.isArray(store.auditSnapshots)) {
|
|
767
|
+
throw new Error("Snapshot store auditSnapshots field is missing or invalid.");
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
version: SNAPSHOT_STORE_VERSION,
|
|
771
|
+
auditSnapshots: store.auditSnapshots.map(normalizeSnapshotRow)
|
|
772
|
+
};
|
|
1078
773
|
}
|
|
1079
|
-
function
|
|
1080
|
-
if (
|
|
1081
|
-
|
|
774
|
+
function normalizeSnapshotRow(input) {
|
|
775
|
+
if (!input || typeof input !== "object") {
|
|
776
|
+
throw new Error("Snapshot row is not an object.");
|
|
1082
777
|
}
|
|
1083
|
-
const
|
|
1084
|
-
|
|
1085
|
-
|
|
778
|
+
const row = input;
|
|
779
|
+
if (typeof row.id !== "string" || typeof row.createdAt !== "string" || typeof row.summaryJson !== "string") {
|
|
780
|
+
throw new Error("Snapshot row is missing id, createdAt, or summaryJson.");
|
|
1086
781
|
}
|
|
782
|
+
return {
|
|
783
|
+
id: row.id,
|
|
784
|
+
createdAt: row.createdAt,
|
|
785
|
+
summaryJson: row.summaryJson
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// ../core/src/db/persist.ts
|
|
790
|
+
function persistAudit(audit, dbPath) {
|
|
791
|
+
upsertStoredAuditSnapshot(dbPath, {
|
|
792
|
+
id: audit.summary.auditId,
|
|
793
|
+
createdAt: audit.summary.generatedAt,
|
|
794
|
+
summaryJson: JSON.stringify(audit.summary)
|
|
795
|
+
});
|
|
1087
796
|
}
|
|
1088
797
|
|
|
1089
798
|
// ../core/src/report/comparison.ts
|
|
@@ -1165,6 +874,36 @@ function buildTaxonomyBuckets(findings, classification) {
|
|
|
1165
874
|
}
|
|
1166
875
|
return Array.from(buckets.values()).sort((left, right) => right.spendUsd - left.spendUsd);
|
|
1167
876
|
}
|
|
877
|
+
function buildWasteBySignalSource(findings) {
|
|
878
|
+
const rollup = {
|
|
879
|
+
observedUsd: 0,
|
|
880
|
+
inferredUsd: 0,
|
|
881
|
+
declaredUsd: 0,
|
|
882
|
+
unknownUsd: 0,
|
|
883
|
+
inferredShare: null
|
|
884
|
+
};
|
|
885
|
+
for (const finding of findings) {
|
|
886
|
+
if (finding.classification !== "waste") {
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
if (finding.signalSource === "observed") {
|
|
890
|
+
rollup.observedUsd = round2(rollup.observedUsd + finding.costImpactUsd);
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
if (finding.signalSource === "inferred") {
|
|
894
|
+
rollup.inferredUsd = round2(rollup.inferredUsd + finding.costImpactUsd);
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
if (finding.signalSource === "declared") {
|
|
898
|
+
rollup.declaredUsd = round2(rollup.declaredUsd + finding.costImpactUsd);
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
rollup.unknownUsd = round2(rollup.unknownUsd + finding.costImpactUsd);
|
|
902
|
+
}
|
|
903
|
+
const knownTotal = rollup.observedUsd + rollup.inferredUsd + rollup.declaredUsd;
|
|
904
|
+
rollup.inferredShare = rollup.unknownUsd > 0 ? null : Number((knownTotal === 0 ? 0 : rollup.inferredUsd / knownTotal).toFixed(4));
|
|
905
|
+
return rollup;
|
|
906
|
+
}
|
|
1168
907
|
function toSpendMap(rows) {
|
|
1169
908
|
return new Map(rows.map((row) => [row.key, row.spendUsd]));
|
|
1170
909
|
}
|
|
@@ -1280,6 +1019,7 @@ function hydrateAuditSummary(summary) {
|
|
|
1280
1019
|
opportunityByKind: summary.opportunityByKind?.length > 0 ? summary.opportunityByKind : buildTaxonomyBuckets(summary.findings, "opportunity"),
|
|
1281
1020
|
spendByDay: summary.spendByDay ?? [],
|
|
1282
1021
|
wasteByDay: summary.wasteByDay ?? [],
|
|
1022
|
+
wasteBySignalSource: summary.wasteBySignalSource ?? buildWasteBySignalSource(summary.findings),
|
|
1283
1023
|
recommendations: summary.recommendations ?? [],
|
|
1284
1024
|
notes: summary.notes ?? [],
|
|
1285
1025
|
pricingCoverage: summary.pricingCoverage ?? null,
|
|
@@ -1300,6 +1040,7 @@ function buildAuditComparison(current, baseline) {
|
|
|
1300
1040
|
baselineWasteSpendUsd: baseline.wasteSpendUsd,
|
|
1301
1041
|
baselineOpportunitySpendUsd: baseline.opportunitySpendUsd,
|
|
1302
1042
|
baselineStructuralWasteRate: baseline.structuralWasteRate,
|
|
1043
|
+
baselineWasteBySignalSource: baseline.wasteBySignalSource ?? buildWasteBySignalSource(baseline.findings),
|
|
1303
1044
|
deltaTotalSpendUsd: round2(current.totalSpendUsd - baseline.totalSpendUsd),
|
|
1304
1045
|
deltaObservedSpendUsd: round2(current.observedSpendUsd - baseline.observedSpendUsd),
|
|
1305
1046
|
deltaEstimatedSpendUsd: round2(current.estimatedSpendUsd - baseline.estimatedSpendUsd),
|
|
@@ -1326,19 +1067,7 @@ function parseAuditSummary(row) {
|
|
|
1326
1067
|
}
|
|
1327
1068
|
}
|
|
1328
1069
|
function listStoredAuditSummaries(dbPath) {
|
|
1329
|
-
|
|
1330
|
-
try {
|
|
1331
|
-
const rows = sqlite.prepare(
|
|
1332
|
-
`
|
|
1333
|
-
SELECT id, summary_json AS summaryJson
|
|
1334
|
-
FROM audit_snapshots
|
|
1335
|
-
ORDER BY created_at DESC
|
|
1336
|
-
`
|
|
1337
|
-
).all();
|
|
1338
|
-
return rows.map((row) => parseAuditSummary(row)).filter((summary) => summary !== null);
|
|
1339
|
-
} finally {
|
|
1340
|
-
sqlite.close();
|
|
1341
|
-
}
|
|
1070
|
+
return readStoredSnapshotRows(dbPath).map((row) => parseAuditSummary(row)).filter((summary) => summary !== null);
|
|
1342
1071
|
}
|
|
1343
1072
|
function readLatestComparableAuditSummary(input) {
|
|
1344
1073
|
return listStoredAuditSummaries(input.dbPath).find((summary) => {
|
|
@@ -1350,6 +1079,8 @@ function readLatestComparableAuditSummary(input) {
|
|
|
1350
1079
|
}
|
|
1351
1080
|
|
|
1352
1081
|
// ../core/src/findings/cursor.ts
|
|
1082
|
+
var CACHE_CARRYOVER_RULE_ID = "cursor_cache_ratio_v1";
|
|
1083
|
+
var MAX_MODE_CONCENTRATION_RULE_ID = "cursor_max_mode_concentration_v1";
|
|
1353
1084
|
function round3(value) {
|
|
1354
1085
|
return Number(value.toFixed(6));
|
|
1355
1086
|
}
|
|
@@ -1419,6 +1150,13 @@ function buildCursorUsageFindings(runs) {
|
|
|
1419
1150
|
scopeId: "all",
|
|
1420
1151
|
scopeLabel: "Cursor usage",
|
|
1421
1152
|
costImpactUsd: cacheImpactUsd,
|
|
1153
|
+
signalSource: "observed",
|
|
1154
|
+
ruleId: CACHE_CARRYOVER_RULE_ID,
|
|
1155
|
+
evidence: {
|
|
1156
|
+
callIds: cacheAwareCalls.map((call) => call.id).sort(),
|
|
1157
|
+
runIds: Array.from(new Set(cacheAwareCalls.map((call) => call.runId))).sort(),
|
|
1158
|
+
sourceKinds: ["cursor-usage-csv"]
|
|
1159
|
+
},
|
|
1422
1160
|
details: {
|
|
1423
1161
|
cacheReadShare: round3(cacheReadShare),
|
|
1424
1162
|
cacheCoverageShare: round3(cacheCoverageShare),
|
|
@@ -1447,6 +1185,13 @@ function buildCursorUsageFindings(runs) {
|
|
|
1447
1185
|
scopeId: "all",
|
|
1448
1186
|
scopeLabel: "Cursor usage",
|
|
1449
1187
|
costImpactUsd: round3(maxModeSpendUsd * 0.2),
|
|
1188
|
+
signalSource: "observed",
|
|
1189
|
+
ruleId: MAX_MODE_CONCENTRATION_RULE_ID,
|
|
1190
|
+
evidence: {
|
|
1191
|
+
callIds: maxModeCalls.map((call) => call.id).sort(),
|
|
1192
|
+
runIds: Array.from(new Set(maxModeCalls.map((call) => call.runId))).sort(),
|
|
1193
|
+
sourceKinds: ["cursor-usage-csv"]
|
|
1194
|
+
},
|
|
1450
1195
|
details: {
|
|
1451
1196
|
maxModeSpendUsd: round3(maxModeSpendUsd),
|
|
1452
1197
|
maxModeSpendShare: round3(maxModeSpendShare),
|
|
@@ -1468,55 +1213,178 @@ function buildCursorUsageFindings(runs) {
|
|
|
1468
1213
|
}
|
|
1469
1214
|
|
|
1470
1215
|
// ../core/src/findings/engine.ts
|
|
1216
|
+
var RETRY_OBSERVED_RULE_ID = "retry_explicit_failed_attempt_v1";
|
|
1217
|
+
var RETRY_INFERRED_RULE_ID = "retry_later_attempt_proxy_v1";
|
|
1218
|
+
var LOOP_RULE_ID = "loop_iteration_threshold_v1";
|
|
1219
|
+
var CONTEXT_OUTLIER_RULE_ID = "context_outlier_tokens_v1";
|
|
1220
|
+
var IDLE_SPEND_RULE_ID = "idle_workflow_name_v1";
|
|
1221
|
+
var CANDIDATE_DOWNGRADE_RULE_ID = "candidate_downgrade_task_model_v1";
|
|
1222
|
+
var LOOP_WASTE_START_ITERATION = 6;
|
|
1223
|
+
var LOOP_FINDING_MIN_ITERATION = 7;
|
|
1471
1224
|
function createFinding2(input) {
|
|
1472
1225
|
return {
|
|
1473
1226
|
...input,
|
|
1474
1227
|
id: sha1(
|
|
1475
|
-
`${input.kind}:${input.scope}:${input.scopeId}:${input.title}:${input.costImpactUsd}:${input.summary}`
|
|
1228
|
+
`${input.kind}:${input.scope}:${input.scopeId}:${input.title}:${input.costImpactUsd}:${input.summary}:${input.signalSource ?? "unknown"}:${input.ruleId ?? "none"}`
|
|
1476
1229
|
)
|
|
1477
1230
|
};
|
|
1478
1231
|
}
|
|
1479
1232
|
function round4(value) {
|
|
1480
1233
|
return Number(value.toFixed(6));
|
|
1481
1234
|
}
|
|
1235
|
+
function isFailedOrAborted(call) {
|
|
1236
|
+
const status = (call.status ?? "").toLowerCase();
|
|
1237
|
+
return status.includes("error") || status.includes("fail") || status.includes("abort");
|
|
1238
|
+
}
|
|
1239
|
+
function hasExplicitRetrySignal(call) {
|
|
1240
|
+
return (call.attempt ?? 1) > 1 || call.retries > 0;
|
|
1241
|
+
}
|
|
1242
|
+
function toTimestampMs(call) {
|
|
1243
|
+
const timestamp = new Date(call.timestamp).getTime();
|
|
1244
|
+
return Number.isFinite(timestamp) ? timestamp : Number.POSITIVE_INFINITY;
|
|
1245
|
+
}
|
|
1246
|
+
function sortCallsByTime(calls) {
|
|
1247
|
+
return calls.map((call, index) => ({ call, index })).sort((left, right) => {
|
|
1248
|
+
const delta = toTimestampMs(left.call) - toTimestampMs(right.call);
|
|
1249
|
+
return delta === 0 ? left.index - right.index : delta;
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
function canUseStructuralSignals(sourceKind) {
|
|
1253
|
+
return sourceKind === "gateway";
|
|
1254
|
+
}
|
|
1255
|
+
function hasLaterExplicitRetryAttempt(sortedCalls, currentIndex) {
|
|
1256
|
+
const current = sortedCalls[currentIndex]?.call;
|
|
1257
|
+
if (!current) {
|
|
1258
|
+
return false;
|
|
1259
|
+
}
|
|
1260
|
+
return sortedCalls.slice(currentIndex + 1).some(({ call }) => {
|
|
1261
|
+
if (!hasExplicitRetrySignal(call)) {
|
|
1262
|
+
return false;
|
|
1263
|
+
}
|
|
1264
|
+
if (current.attempt !== null && call.attempt !== null) {
|
|
1265
|
+
return call.attempt > current.attempt;
|
|
1266
|
+
}
|
|
1267
|
+
return true;
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
function uniqueSourceKinds(calls, runs) {
|
|
1271
|
+
const runById = new Map(runs.map((run2) => [run2.id, run2]));
|
|
1272
|
+
return Array.from(
|
|
1273
|
+
new Set(
|
|
1274
|
+
calls.map((call) => runById.get(call.runId)?.sourceKind).filter((sourceKind) => Boolean(sourceKind))
|
|
1275
|
+
)
|
|
1276
|
+
).sort();
|
|
1277
|
+
}
|
|
1278
|
+
function buildRetryFinding(input) {
|
|
1279
|
+
const retryCost = input.calls.reduce((sum, call) => sum + call.costUsd, 0);
|
|
1280
|
+
const observed = input.signalSource === "observed";
|
|
1281
|
+
return createFinding2({
|
|
1282
|
+
classification: "waste",
|
|
1283
|
+
confidence: observed ? "high" : "medium",
|
|
1284
|
+
kind: "retry-waste",
|
|
1285
|
+
title: observed ? "Retry waste is consuming measurable spend" : "Retry waste is likely present from later retry attempts",
|
|
1286
|
+
summary: observed ? `${input.calls.length} failed or aborted call${input.calls.length === 1 ? "" : "s"} were followed by explicit retry attempts, making their spend retry overhead.` : `${input.calls.length} later retry attempt${input.calls.length === 1 ? "" : "s"} were counted as proxy retry overhead because the earlier failed attempt was not separately countable.`,
|
|
1287
|
+
scope: "global",
|
|
1288
|
+
scopeId: "all",
|
|
1289
|
+
scopeLabel: "workspace",
|
|
1290
|
+
costImpactUsd: round4(retryCost),
|
|
1291
|
+
signalSource: input.signalSource,
|
|
1292
|
+
ruleId: input.ruleId,
|
|
1293
|
+
evidence: {
|
|
1294
|
+
callIds: input.calls.map((call) => call.id).sort(),
|
|
1295
|
+
runIds: Array.from(new Set(input.calls.map((call) => call.runId))).sort(),
|
|
1296
|
+
sourceKinds: uniqueSourceKinds(input.calls, input.runs)
|
|
1297
|
+
},
|
|
1298
|
+
details: {
|
|
1299
|
+
retryCallCount: input.calls.length
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1482
1303
|
function buildFindings(runs) {
|
|
1483
1304
|
const findings = [];
|
|
1484
1305
|
const wasteAttributions = [];
|
|
1485
|
-
const
|
|
1486
|
-
const
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1306
|
+
const observedRetryCalls = [];
|
|
1307
|
+
const inferredRetryCalls = [];
|
|
1308
|
+
const retryCoveredCallIds = /* @__PURE__ */ new Set();
|
|
1309
|
+
for (const run2 of runs.filter((candidate) => canUseStructuralSignals(candidate.sourceKind))) {
|
|
1310
|
+
const sortedCalls = sortCallsByTime(run2.calls);
|
|
1311
|
+
sortedCalls.forEach(({ call }, index) => {
|
|
1312
|
+
if (!isFailedOrAborted(call)) {
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
if (!hasExplicitRetrySignal(call) && !hasLaterExplicitRetryAttempt(sortedCalls, index)) {
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
if (!hasLaterExplicitRetryAttempt(sortedCalls, index)) {
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
observedRetryCalls.push(call);
|
|
1322
|
+
retryCoveredCallIds.add(call.id);
|
|
1323
|
+
const later = sortedCalls.slice(index + 1).find(({ call: laterCall }) => hasExplicitRetrySignal(laterCall));
|
|
1324
|
+
if (later) {
|
|
1325
|
+
retryCoveredCallIds.add(later.call.id);
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
for (const { call } of sortedCalls) {
|
|
1329
|
+
if (!hasExplicitRetrySignal(call) || retryCoveredCallIds.has(call.id)) {
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
const hasEarlierCountableFailure = sortedCalls.some(({ call: earlier }) => {
|
|
1333
|
+
if (earlier.id === call.id) {
|
|
1334
|
+
return false;
|
|
1335
|
+
}
|
|
1336
|
+
return toTimestampMs(earlier) < toTimestampMs(call) && isFailedOrAborted(earlier);
|
|
1337
|
+
});
|
|
1338
|
+
if (!hasEarlierCountableFailure) {
|
|
1339
|
+
inferredRetryCalls.push(call);
|
|
1340
|
+
retryCoveredCallIds.add(call.id);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
if (observedRetryCalls.length > 0) {
|
|
1492
1345
|
wasteAttributions.push(
|
|
1493
|
-
...
|
|
1346
|
+
...observedRetryCalls.map((call) => ({
|
|
1494
1347
|
kind: "retry-waste",
|
|
1495
1348
|
timestamp: call.timestamp,
|
|
1496
1349
|
wasteUsd: call.costUsd
|
|
1497
1350
|
}))
|
|
1498
1351
|
);
|
|
1499
1352
|
findings.push(
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1353
|
+
buildRetryFinding({
|
|
1354
|
+
calls: observedRetryCalls,
|
|
1355
|
+
runs,
|
|
1356
|
+
signalSource: "observed",
|
|
1357
|
+
ruleId: RETRY_OBSERVED_RULE_ID
|
|
1358
|
+
})
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
if (inferredRetryCalls.length > 0) {
|
|
1362
|
+
wasteAttributions.push(
|
|
1363
|
+
...inferredRetryCalls.map((call) => ({
|
|
1503
1364
|
kind: "retry-waste",
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1365
|
+
timestamp: call.timestamp,
|
|
1366
|
+
wasteUsd: call.costUsd
|
|
1367
|
+
}))
|
|
1368
|
+
);
|
|
1369
|
+
findings.push(
|
|
1370
|
+
buildRetryFinding({
|
|
1371
|
+
calls: inferredRetryCalls,
|
|
1372
|
+
runs,
|
|
1373
|
+
signalSource: "inferred",
|
|
1374
|
+
ruleId: RETRY_INFERRED_RULE_ID
|
|
1513
1375
|
})
|
|
1514
1376
|
);
|
|
1515
1377
|
}
|
|
1516
|
-
for (const run2 of runs) {
|
|
1517
|
-
const
|
|
1518
|
-
if (
|
|
1519
|
-
|
|
1378
|
+
for (const run2 of runs.filter((candidate) => canUseStructuralSignals(candidate.sourceKind))) {
|
|
1379
|
+
const iterations = run2.calls.map((call) => call.iteration).filter((iteration) => iteration !== null);
|
|
1380
|
+
if (iterations.length === 0) {
|
|
1381
|
+
continue;
|
|
1382
|
+
}
|
|
1383
|
+
const maxIteration = Math.max(...iterations);
|
|
1384
|
+
if (maxIteration >= LOOP_FINDING_MIN_ITERATION) {
|
|
1385
|
+
const loopCalls = run2.calls.filter(
|
|
1386
|
+
(call) => (call.iteration ?? 0) >= LOOP_WASTE_START_ITERATION
|
|
1387
|
+
);
|
|
1520
1388
|
const loopCost = loopCalls.reduce((sum, call) => sum + call.costUsd, 0);
|
|
1521
1389
|
wasteAttributions.push(
|
|
1522
1390
|
...loopCalls.map((call) => ({
|
|
@@ -1531,14 +1399,22 @@ function buildFindings(runs) {
|
|
|
1531
1399
|
confidence: "high",
|
|
1532
1400
|
kind: "loop-waste",
|
|
1533
1401
|
title: `Workflow "${run2.workflow}" ran beyond efficient loop bounds`,
|
|
1534
|
-
summary: `This run reached ${maxIteration} iterations. Xerg treats
|
|
1402
|
+
summary: `This run reached ${maxIteration} iterations. Xerg treats spend from iteration ${LOOP_WASTE_START_ITERATION} onward as loop waste.`,
|
|
1535
1403
|
scope: "run",
|
|
1536
1404
|
scopeId: run2.workflow,
|
|
1537
1405
|
scopeLabel: run2.workflow,
|
|
1538
1406
|
costImpactUsd: round4(loopCost),
|
|
1407
|
+
signalSource: "observed",
|
|
1408
|
+
ruleId: LOOP_RULE_ID,
|
|
1409
|
+
evidence: {
|
|
1410
|
+
callIds: loopCalls.map((call) => call.id).sort(),
|
|
1411
|
+
runIds: [run2.id],
|
|
1412
|
+
sourceKinds: [run2.sourceKind]
|
|
1413
|
+
},
|
|
1539
1414
|
details: {
|
|
1540
1415
|
workflow: run2.workflow,
|
|
1541
|
-
maxIteration
|
|
1416
|
+
maxIteration,
|
|
1417
|
+
thresholdIteration: LOOP_WASTE_START_ITERATION
|
|
1542
1418
|
}
|
|
1543
1419
|
})
|
|
1544
1420
|
);
|
|
@@ -1573,6 +1449,12 @@ function buildFindings(runs) {
|
|
|
1573
1449
|
scopeId: workflow,
|
|
1574
1450
|
scopeLabel: workflow,
|
|
1575
1451
|
costImpactUsd: round4(outlierCost),
|
|
1452
|
+
signalSource: "observed",
|
|
1453
|
+
ruleId: CONTEXT_OUTLIER_RULE_ID,
|
|
1454
|
+
evidence: {
|
|
1455
|
+
runIds: outlierRuns.map((run2) => run2.id).sort(),
|
|
1456
|
+
sourceKinds: Array.from(new Set(outlierRuns.map((run2) => run2.sourceKind))).sort()
|
|
1457
|
+
},
|
|
1576
1458
|
details: {
|
|
1577
1459
|
workflow,
|
|
1578
1460
|
averageInputTokens: round4(average),
|
|
@@ -1598,6 +1480,12 @@ function buildFindings(runs) {
|
|
|
1598
1480
|
scopeId: workflow,
|
|
1599
1481
|
scopeLabel: workflow,
|
|
1600
1482
|
costImpactUsd: round4(idleCost),
|
|
1483
|
+
signalSource: "observed",
|
|
1484
|
+
ruleId: IDLE_SPEND_RULE_ID,
|
|
1485
|
+
evidence: {
|
|
1486
|
+
runIds: idleRuns.map((run2) => run2.id).sort(),
|
|
1487
|
+
sourceKinds: Array.from(new Set(idleRuns.map((run2) => run2.sourceKind))).sort()
|
|
1488
|
+
},
|
|
1601
1489
|
details: {
|
|
1602
1490
|
workflow
|
|
1603
1491
|
}
|
|
@@ -1620,6 +1508,13 @@ function buildFindings(runs) {
|
|
|
1620
1508
|
scopeId: workflow,
|
|
1621
1509
|
scopeLabel: workflow,
|
|
1622
1510
|
costImpactUsd: round4(spend * 0.3),
|
|
1511
|
+
signalSource: "observed",
|
|
1512
|
+
ruleId: CANDIDATE_DOWNGRADE_RULE_ID,
|
|
1513
|
+
evidence: {
|
|
1514
|
+
callIds: downgradeCalls.map((call) => call.id).sort(),
|
|
1515
|
+
runIds: Array.from(new Set(downgradeCalls.map((call) => call.runId))).sort(),
|
|
1516
|
+
sourceKinds: uniqueSourceKinds(downgradeCalls, runs)
|
|
1517
|
+
},
|
|
1623
1518
|
details: {
|
|
1624
1519
|
workflow,
|
|
1625
1520
|
expensiveCallCount: downgradeCalls.length,
|
|
@@ -1781,7 +1676,7 @@ var templatesByKind = {
|
|
|
1781
1676
|
severity: "high",
|
|
1782
1677
|
effort: "low",
|
|
1783
1678
|
titleFn: (finding) => `Reduce retry waste in ${formatScopeLabel(finding)}`,
|
|
1784
|
-
summaryFn: (finding) => `${finding.summary} This is confirmed retry overhead, so it is a fix-now issue rather than an experiment.`,
|
|
1679
|
+
summaryFn: (finding) => finding.signalSource === "observed" ? `${finding.summary} This is confirmed retry overhead, so it is a fix-now issue rather than an experiment.` : `${finding.summary} Treat this as likely retry overhead and inspect the retry wrapper before classifying the full amount as proven waste.`,
|
|
1785
1680
|
whereToChangeFn: (finding) => `Reduce retries or add exponential backoff in the retry wrapper for ${formatScopeLabel(finding)}.`,
|
|
1786
1681
|
validationPlanFn: () => "Ship the change, then rerun `xerg audit --compare --push` against the same source. Retry waste should drop materially on the next audit.",
|
|
1787
1682
|
actionsFn: () => [
|
|
@@ -2128,6 +2023,7 @@ function buildAuditSummary(input) {
|
|
|
2128
2023
|
structuralWasteRate: Number(
|
|
2129
2024
|
(totalSpendUsd === 0 ? 0 : wasteSpendUsd / totalSpendUsd).toFixed(4)
|
|
2130
2025
|
),
|
|
2026
|
+
wasteBySignalSource: buildWasteBySignalSource(input.findings),
|
|
2131
2027
|
wasteByKind: buildTaxonomyBuckets(input.findings, "waste"),
|
|
2132
2028
|
opportunityByKind: buildTaxonomyBuckets(input.findings, "opportunity"),
|
|
2133
2029
|
spendByWorkflow: buildBreakdown(
|
|
@@ -2169,7 +2065,7 @@ import { homedir as homedir2 } from "os";
|
|
|
2169
2065
|
import { basename as basename2, join as join3 } from "path";
|
|
2170
2066
|
|
|
2171
2067
|
// ../core/src/normalize/hermes.ts
|
|
2172
|
-
import { readFileSync as
|
|
2068
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2173
2069
|
import { basename } from "path";
|
|
2174
2070
|
|
|
2175
2071
|
// ../core/src/utils/records.ts
|
|
@@ -2255,7 +2151,7 @@ function parseJsonLine(line) {
|
|
|
2255
2151
|
}
|
|
2256
2152
|
}
|
|
2257
2153
|
function parseJsonLines(path) {
|
|
2258
|
-
const content =
|
|
2154
|
+
const content = readFileSync3(path, "utf8");
|
|
2259
2155
|
const lines = content.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
2260
2156
|
const records = [];
|
|
2261
2157
|
for (const line of lines) {
|
|
@@ -2608,7 +2504,7 @@ function getAppPaths() {
|
|
|
2608
2504
|
};
|
|
2609
2505
|
}
|
|
2610
2506
|
function getDefaultDbPath() {
|
|
2611
|
-
return join(getAppPaths().data, "xerg.
|
|
2507
|
+
return join(getAppPaths().data, "xerg-snapshots.json");
|
|
2612
2508
|
}
|
|
2613
2509
|
function getDefaultOpenClawSessionsPattern() {
|
|
2614
2510
|
return join(getUserHome(), ".openclaw", "agents", "*", "sessions", "*.jsonl");
|
|
@@ -2799,10 +2695,10 @@ async function detectOpenClawSources(options) {
|
|
|
2799
2695
|
}
|
|
2800
2696
|
|
|
2801
2697
|
// ../core/src/normalize/openclaw.ts
|
|
2802
|
-
import { readFileSync as
|
|
2698
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
2803
2699
|
import { basename as basename3 } from "path";
|
|
2804
2700
|
function parseJsonLines2(path) {
|
|
2805
|
-
const content =
|
|
2701
|
+
const content = readFileSync4(path, "utf8");
|
|
2806
2702
|
const lines = content.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
2807
2703
|
const records = [];
|
|
2808
2704
|
for (const line of lines) {
|
|
@@ -3445,9 +3341,18 @@ function formatUsdDelta(value) {
|
|
|
3445
3341
|
const sign = value > 0 ? "+" : "";
|
|
3446
3342
|
return `${sign}${formatUsd(value)}`;
|
|
3447
3343
|
}
|
|
3344
|
+
function formatUsdRate(value) {
|
|
3345
|
+
return formatUsd(value);
|
|
3346
|
+
}
|
|
3448
3347
|
function isCursorUsageSummary(summary) {
|
|
3449
3348
|
return summary.sourceFiles.some((source) => source.kind === "cursor-usage-csv");
|
|
3450
3349
|
}
|
|
3350
|
+
function divideOrZero(numerator, denominator) {
|
|
3351
|
+
return denominator === 0 ? 0 : numerator / denominator;
|
|
3352
|
+
}
|
|
3353
|
+
function formatInferredShare(value) {
|
|
3354
|
+
return value === null || value === void 0 ? "unavailable" : formatPercent(value);
|
|
3355
|
+
}
|
|
3451
3356
|
function topRows(rows, limit = 5) {
|
|
3452
3357
|
return rows.slice(0, limit).map((row) => {
|
|
3453
3358
|
return `- ${row.key}: ${formatUsd(row.spendUsd)} (${formatPercent(row.observedShare)} observed)`;
|
|
@@ -3532,6 +3437,35 @@ function renderFindingChange(change, state) {
|
|
|
3532
3437
|
}
|
|
3533
3438
|
return `- New: ${change.title} (${formatUsd(change.currentCostImpactUsd ?? 0)})`;
|
|
3534
3439
|
}
|
|
3440
|
+
function renderCompareCoreRows(summary) {
|
|
3441
|
+
if (!summary.comparison) {
|
|
3442
|
+
return [];
|
|
3443
|
+
}
|
|
3444
|
+
const comparison = summary.comparison;
|
|
3445
|
+
const baselineWastePerRun = divideOrZero(
|
|
3446
|
+
comparison.baselineWasteSpendUsd,
|
|
3447
|
+
comparison.baselineRunCount
|
|
3448
|
+
);
|
|
3449
|
+
const currentWastePerRun = divideOrZero(summary.wasteSpendUsd, summary.runCount);
|
|
3450
|
+
const baselineWastePer1kCalls = divideOrZero(
|
|
3451
|
+
comparison.baselineWasteSpendUsd,
|
|
3452
|
+
comparison.baselineCallCount / 1e3
|
|
3453
|
+
);
|
|
3454
|
+
const currentWastePer1kCalls = divideOrZero(summary.wasteSpendUsd, summary.callCount / 1e3);
|
|
3455
|
+
return [
|
|
3456
|
+
"## Before / after",
|
|
3457
|
+
`Compared against ${comparison.baselineGeneratedAt}`,
|
|
3458
|
+
`- Waste rate: ${formatPercent(comparison.baselineStructuralWasteRate)} -> ${formatPercent(summary.structuralWasteRate)} (${formatPercentDelta(comparison.deltaStructuralWasteRate)})`,
|
|
3459
|
+
`- Waste per run: ${formatUsdRate(baselineWastePerRun)} -> ${formatUsdRate(currentWastePerRun)} (${formatUsdDelta(currentWastePerRun - baselineWastePerRun)})`,
|
|
3460
|
+
`- Waste per 1k calls: ${formatUsdRate(baselineWastePer1kCalls)} -> ${formatUsdRate(currentWastePer1kCalls)} (${formatUsdDelta(currentWastePer1kCalls - baselineWastePer1kCalls)})`,
|
|
3461
|
+
`- Inferred waste share: ${formatInferredShare(comparison.baselineWasteBySignalSource?.inferredShare)} -> ${formatInferredShare(summary.wasteBySignalSource?.inferredShare)}`,
|
|
3462
|
+
"- CPO: unavailable (no outcome signal)",
|
|
3463
|
+
`- Total spend (workload-dependent): ${formatUsd(comparison.baselineTotalSpendUsd)} -> ${formatUsd(summary.totalSpendUsd)} (${formatUsdDelta(comparison.deltaTotalSpendUsd)})`,
|
|
3464
|
+
`- Structural waste (workload-dependent): ${formatUsd(comparison.baselineWasteSpendUsd)} -> ${formatUsd(summary.wasteSpendUsd)} (${formatUsdDelta(comparison.deltaWasteSpendUsd)})`,
|
|
3465
|
+
`- Runs analyzed: ${comparison.baselineRunCount} -> ${summary.runCount} (${comparison.deltaRunCount > 0 ? "+" : ""}${comparison.deltaRunCount})`,
|
|
3466
|
+
`- Model calls: ${comparison.baselineCallCount} -> ${summary.callCount} (${comparison.deltaCallCount > 0 ? "+" : ""}${comparison.deltaCallCount})`
|
|
3467
|
+
];
|
|
3468
|
+
}
|
|
3535
3469
|
function renderCompareBlock(summary) {
|
|
3536
3470
|
if (!summary.comparison) {
|
|
3537
3471
|
return [];
|
|
@@ -3552,13 +3486,7 @@ function renderCompareBlock(summary) {
|
|
|
3552
3486
|
)
|
|
3553
3487
|
].slice(0, 5);
|
|
3554
3488
|
return [
|
|
3555
|
-
|
|
3556
|
-
`Compared against ${comparison.baselineGeneratedAt}`,
|
|
3557
|
-
`- Total spend: ${formatUsd(comparison.baselineTotalSpendUsd)} -> ${formatUsd(summary.totalSpendUsd)} (${formatUsdDelta(comparison.deltaTotalSpendUsd)})`,
|
|
3558
|
-
`- Structural waste: ${formatUsd(comparison.baselineWasteSpendUsd)} -> ${formatUsd(summary.wasteSpendUsd)} (${formatUsdDelta(comparison.deltaWasteSpendUsd)})`,
|
|
3559
|
-
`- Waste rate: ${formatPercent(comparison.baselineStructuralWasteRate)} -> ${formatPercent(summary.structuralWasteRate)} (${formatPercentDelta(comparison.deltaStructuralWasteRate)})`,
|
|
3560
|
-
`- Runs analyzed: ${comparison.baselineRunCount} -> ${summary.runCount} (${comparison.deltaRunCount > 0 ? "+" : ""}${comparison.deltaRunCount})`,
|
|
3561
|
-
`- Model calls: ${comparison.baselineCallCount} -> ${summary.callCount} (${comparison.deltaCallCount > 0 ? "+" : ""}${comparison.deltaCallCount})`,
|
|
3489
|
+
...renderCompareCoreRows(summary),
|
|
3562
3490
|
biggestImprovement ? `- Biggest improvement: ${describeSpendDelta(biggestImprovement)}` : "- Biggest improvement: none detected",
|
|
3563
3491
|
biggestRegression ? `- Biggest regression: ${describeSpendDelta(biggestRegression)}` : "- Biggest regression: none detected",
|
|
3564
3492
|
firstWorkflowToInspect ? `- First workflow to inspect now: ${firstWorkflowToInspect}` : "- First workflow to inspect now: no workflow delta available",
|
|
@@ -3682,10 +3610,7 @@ function renderCursorCompareBlock(summary) {
|
|
|
3682
3610
|
const modeSwing = comparison.workflowDeltas[0];
|
|
3683
3611
|
const modelSwing = comparison.modelDeltas[0];
|
|
3684
3612
|
return [
|
|
3685
|
-
|
|
3686
|
-
`Compared against ${comparison.baselineGeneratedAt}`,
|
|
3687
|
-
`- Total spend: ${formatUsd(comparison.baselineTotalSpendUsd)} -> ${formatUsd(summary.totalSpendUsd)} (${formatUsdDelta(comparison.deltaTotalSpendUsd)})`,
|
|
3688
|
-
`- Rows analyzed: ${formatCount(comparison.baselineRunCount)} -> ${formatCount(summary.runCount)} (${comparison.deltaRunCount > 0 ? "+" : ""}${comparison.deltaRunCount})`,
|
|
3613
|
+
...renderCompareCoreRows(summary),
|
|
3689
3614
|
`- Usage rows with pricing: ${formatCount(summary.pricingCoverage?.pricedCallCount ?? 0)}`,
|
|
3690
3615
|
modeSwing ? `- Mode swing to inspect: ${describeSpendDelta(modeSwing)}` : "- Mode swing to inspect: none",
|
|
3691
3616
|
modelSwing ? `- Model swing to inspect: ${describeSpendDelta(modelSwing)}` : "- Model swing to inspect: none"
|
|
@@ -3779,7 +3704,7 @@ function renderCursorMarkdownSummary(summary) {
|
|
|
3779
3704
|
"",
|
|
3780
3705
|
"## Findings",
|
|
3781
3706
|
...summary.findings.slice(0, 10).map((finding) => {
|
|
3782
|
-
return `- **${finding.title}** (${finding.classification}, ${finding.confidence})
|
|
3707
|
+
return `- **${finding.title}** (${finding.classification}, ${finding.confidence}). ${finding.summary} Estimated impact: ${formatUsd(finding.costImpactUsd)}.`;
|
|
3783
3708
|
}),
|
|
3784
3709
|
"",
|
|
3785
3710
|
...renderActionQueue(summary),
|
|
@@ -3862,21 +3787,13 @@ function renderMarkdownSummary(summary) {
|
|
|
3862
3787
|
"",
|
|
3863
3788
|
"## Findings",
|
|
3864
3789
|
...summary.findings.slice(0, 10).map((finding) => {
|
|
3865
|
-
return `- **${finding.title}** (${finding.classification}, ${finding.confidence})
|
|
3790
|
+
return `- **${finding.title}** (${finding.classification}, ${finding.confidence}). ${finding.summary} Estimated impact: ${formatUsd(finding.costImpactUsd)}.`;
|
|
3866
3791
|
}),
|
|
3867
3792
|
"",
|
|
3868
3793
|
...renderActionQueue(summary)
|
|
3869
3794
|
];
|
|
3870
3795
|
if (summary.comparison) {
|
|
3871
|
-
|
|
3872
|
-
lines.push(
|
|
3873
|
-
"",
|
|
3874
|
-
"## Before / after",
|
|
3875
|
-
`- Compared against: ${comparison.baselineGeneratedAt}`,
|
|
3876
|
-
`- Total spend: ${formatUsd(comparison.baselineTotalSpendUsd)} -> ${formatUsd(summary.totalSpendUsd)} (${formatUsdDelta(comparison.deltaTotalSpendUsd)})`,
|
|
3877
|
-
`- Structural waste: ${formatUsd(comparison.baselineWasteSpendUsd)} -> ${formatUsd(summary.wasteSpendUsd)} (${formatUsdDelta(comparison.deltaWasteSpendUsd)})`,
|
|
3878
|
-
`- Waste rate: ${formatPercent(comparison.baselineStructuralWasteRate)} -> ${formatPercent(summary.structuralWasteRate)} (${formatPercentDelta(comparison.deltaStructuralWasteRate)})`
|
|
3879
|
-
);
|
|
3796
|
+
lines.push("", ...renderCompareBlock(summary));
|
|
3880
3797
|
}
|
|
3881
3798
|
return lines.join("\n");
|
|
3882
3799
|
}
|
|
@@ -4006,12 +3923,12 @@ async function pushAudit(payload, config) {
|
|
|
4006
3923
|
}
|
|
4007
3924
|
|
|
4008
3925
|
// src/push/config.ts
|
|
4009
|
-
import { readFileSync as
|
|
3926
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
4010
3927
|
import { homedir as homedir4 } from "os";
|
|
4011
3928
|
import { join as join5 } from "path";
|
|
4012
3929
|
|
|
4013
3930
|
// src/auth/credentials.ts
|
|
4014
|
-
import { existsSync, mkdirSync as mkdirSync3, readFileSync as
|
|
3931
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
4015
3932
|
import { homedir as homedir3 } from "os";
|
|
4016
3933
|
import { dirname as dirname2, join as join4 } from "path";
|
|
4017
3934
|
function getCredentialsPath() {
|
|
@@ -4023,13 +3940,13 @@ function storeCredentials(token) {
|
|
|
4023
3940
|
const dir = dirname2(credPath);
|
|
4024
3941
|
mkdirSync3(dir, { recursive: true });
|
|
4025
3942
|
const data = { token, storedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4026
|
-
|
|
3943
|
+
writeFileSync2(credPath, JSON.stringify(data, null, 2), { mode: 384 });
|
|
4027
3944
|
}
|
|
4028
3945
|
function loadStoredCredentials() {
|
|
4029
3946
|
const credPath = getCredentialsPath();
|
|
4030
3947
|
try {
|
|
4031
|
-
if (!
|
|
4032
|
-
const raw =
|
|
3948
|
+
if (!existsSync2(credPath)) return null;
|
|
3949
|
+
const raw = readFileSync5(credPath, "utf8");
|
|
4033
3950
|
const parsed = JSON.parse(raw);
|
|
4034
3951
|
return parsed.token || null;
|
|
4035
3952
|
} catch {
|
|
@@ -4039,7 +3956,7 @@ function loadStoredCredentials() {
|
|
|
4039
3956
|
function clearCredentials() {
|
|
4040
3957
|
const credPath = getCredentialsPath();
|
|
4041
3958
|
try {
|
|
4042
|
-
if (!
|
|
3959
|
+
if (!existsSync2(credPath)) return false;
|
|
4043
3960
|
rmSync(credPath);
|
|
4044
3961
|
return true;
|
|
4045
3962
|
} catch {
|
|
@@ -4061,7 +3978,7 @@ function loadPushConfig() {
|
|
|
4061
3978
|
};
|
|
4062
3979
|
}
|
|
4063
3980
|
try {
|
|
4064
|
-
const raw =
|
|
3981
|
+
const raw = readFileSync6(CONFIG_PATH, "utf8");
|
|
4065
3982
|
const parsed = JSON.parse(raw);
|
|
4066
3983
|
if (parsed.apiKey) {
|
|
4067
3984
|
return {
|
|
@@ -4880,13 +4797,13 @@ function formatBytes2(bytes) {
|
|
|
4880
4797
|
}
|
|
4881
4798
|
|
|
4882
4799
|
// src/transport/config.ts
|
|
4883
|
-
import { readFileSync as
|
|
4800
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
4884
4801
|
import { resolve as resolve3 } from "path";
|
|
4885
4802
|
function loadRemoteConfig(configPath) {
|
|
4886
4803
|
const resolved = resolve3(configPath);
|
|
4887
4804
|
let raw;
|
|
4888
4805
|
try {
|
|
4889
|
-
raw =
|
|
4806
|
+
raw = readFileSync7(resolved, "utf8");
|
|
4890
4807
|
} catch {
|
|
4891
4808
|
throw new Error(`Cannot read remote config at ${resolved}`);
|
|
4892
4809
|
}
|
|
@@ -5033,11 +4950,11 @@ function resolveRemoteHost(target) {
|
|
|
5033
4950
|
}
|
|
5034
4951
|
|
|
5035
4952
|
// src/version.ts
|
|
5036
|
-
import { readFileSync as
|
|
4953
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
5037
4954
|
function getCliVersion() {
|
|
5038
4955
|
try {
|
|
5039
4956
|
const packageJsonPath = new URL("../package.json", import.meta.url);
|
|
5040
|
-
const packageJson = JSON.parse(
|
|
4957
|
+
const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf8"));
|
|
5041
4958
|
return packageJson.version ?? "0.0.0";
|
|
5042
4959
|
} catch {
|
|
5043
4960
|
return "0.0.0";
|
|
@@ -5573,7 +5490,8 @@ function renderMcpCredentialSourceMessage(config) {
|
|
|
5573
5490
|
}
|
|
5574
5491
|
|
|
5575
5492
|
// src/prompts.ts
|
|
5576
|
-
import
|
|
5493
|
+
import confirm from "@inquirer/confirm";
|
|
5494
|
+
import select from "@inquirer/select";
|
|
5577
5495
|
function hasPromptTty() {
|
|
5578
5496
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
5579
5497
|
}
|
|
@@ -5591,7 +5509,7 @@ async function promptSelect(message, choices) {
|
|
|
5591
5509
|
}
|
|
5592
5510
|
|
|
5593
5511
|
// src/commands/push.ts
|
|
5594
|
-
import { readFileSync as
|
|
5512
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
5595
5513
|
async function runPushCommand(options) {
|
|
5596
5514
|
const payload = options.file ? loadPayloadFromFile(options.file) : loadLatestCachedAuditPayload();
|
|
5597
5515
|
if (options.dryRun) {
|
|
@@ -5615,7 +5533,7 @@ async function runPushCommand(options) {
|
|
|
5615
5533
|
function loadPayloadFromFile(filePath) {
|
|
5616
5534
|
let raw;
|
|
5617
5535
|
try {
|
|
5618
|
-
raw =
|
|
5536
|
+
raw = readFileSync9(filePath, "utf8");
|
|
5619
5537
|
} catch {
|
|
5620
5538
|
throw new Error(`Cannot read file: ${filePath}`);
|
|
5621
5539
|
}
|
|
@@ -5986,7 +5904,7 @@ function renderRailwayDoctorReport(report) {
|
|
|
5986
5904
|
}
|
|
5987
5905
|
|
|
5988
5906
|
// src/commands/mcp-setup.ts
|
|
5989
|
-
import { existsSync as
|
|
5907
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "fs";
|
|
5990
5908
|
import { dirname as dirname3, join as join8 } from "path";
|
|
5991
5909
|
var HOSTED_MCP_URL = "https://mcp.xerg.ai/mcp";
|
|
5992
5910
|
var MCP_SERVER_NAME = "xerg";
|
|
@@ -6072,7 +5990,7 @@ async function runMcpSetupFlow() {
|
|
|
6072
5990
|
async function handleCursorSetup(snippet, config) {
|
|
6073
5991
|
const cursorDir = join8(process.cwd(), ".cursor");
|
|
6074
5992
|
const cursorConfigPath = join8(cursorDir, "mcp.json");
|
|
6075
|
-
if (
|
|
5993
|
+
if (existsSync3(cursorDir)) {
|
|
6076
5994
|
const shouldWrite = await promptConfirm(
|
|
6077
5995
|
"Write a project-scoped Cursor MCP config to .cursor/mcp.json?",
|
|
6078
5996
|
true
|
|
@@ -6119,9 +6037,9 @@ function tomlString(value) {
|
|
|
6119
6037
|
function writeCursorConfig(filePath, config) {
|
|
6120
6038
|
mkdirSync6(dirname3(filePath), { recursive: true });
|
|
6121
6039
|
let parsed = {};
|
|
6122
|
-
if (
|
|
6040
|
+
if (existsSync3(filePath)) {
|
|
6123
6041
|
try {
|
|
6124
|
-
parsed = JSON.parse(
|
|
6042
|
+
parsed = JSON.parse(readFileSync10(filePath, "utf8"));
|
|
6125
6043
|
} catch {
|
|
6126
6044
|
throw new Error(`Cursor config is not valid JSON: ${filePath}`);
|
|
6127
6045
|
}
|
|
@@ -6134,7 +6052,7 @@ function writeCursorConfig(filePath, config) {
|
|
|
6134
6052
|
...existingServers ?? {},
|
|
6135
6053
|
xerg: buildHostedMcpConfig(config).mcpServers.xerg
|
|
6136
6054
|
};
|
|
6137
|
-
|
|
6055
|
+
writeFileSync3(filePath, `${JSON.stringify(parsed, null, 2)}
|
|
6138
6056
|
`);
|
|
6139
6057
|
}
|
|
6140
6058
|
|
|
@@ -6346,7 +6264,7 @@ Options:
|
|
|
6346
6264
|
--compare Compare this audit to the newest compatible prior local snapshot
|
|
6347
6265
|
--json Render the report as JSON
|
|
6348
6266
|
--markdown Render the report as Markdown
|
|
6349
|
-
--db <path> Custom
|
|
6267
|
+
--db <path> Custom JSON snapshot path
|
|
6350
6268
|
--no-db Skip local persistence
|
|
6351
6269
|
|
|
6352
6270
|
Remote options (SSH, OpenClaw only):
|