@xerg/cli 0.4.0 → 0.5.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 +15 -5
- package/dist/index.js +325 -131
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/xerg/SKILL.md +64 -9
package/dist/index.js
CHANGED
|
@@ -1280,6 +1280,7 @@ function hydrateAuditSummary(summary) {
|
|
|
1280
1280
|
opportunityByKind: summary.opportunityByKind?.length > 0 ? summary.opportunityByKind : buildTaxonomyBuckets(summary.findings, "opportunity"),
|
|
1281
1281
|
spendByDay: summary.spendByDay ?? [],
|
|
1282
1282
|
wasteByDay: summary.wasteByDay ?? [],
|
|
1283
|
+
recommendations: summary.recommendations ?? [],
|
|
1283
1284
|
notes: summary.notes ?? [],
|
|
1284
1285
|
pricingCoverage: summary.pricingCoverage ?? null,
|
|
1285
1286
|
cursorUsage: summary.cursorUsage ?? null
|
|
@@ -1416,6 +1417,7 @@ function buildCursorUsageFindings(runs) {
|
|
|
1416
1417
|
summary,
|
|
1417
1418
|
scope: "global",
|
|
1418
1419
|
scopeId: "all",
|
|
1420
|
+
scopeLabel: "Cursor usage",
|
|
1419
1421
|
costImpactUsd: cacheImpactUsd,
|
|
1420
1422
|
details: {
|
|
1421
1423
|
cacheReadShare: round3(cacheReadShare),
|
|
@@ -1443,6 +1445,7 @@ function buildCursorUsageFindings(runs) {
|
|
|
1443
1445
|
summary: `Max mode accounts for ${(maxModeSpendShare * 100).toFixed(0)}% of billed spend across ${maxModeCalls.length} paid row${maxModeCalls.length === 1 ? "" : "s"}. This is a strong candidate for splitting work between premium and standard passes.`,
|
|
1444
1446
|
scope: "global",
|
|
1445
1447
|
scopeId: "all",
|
|
1448
|
+
scopeLabel: "Cursor usage",
|
|
1446
1449
|
costImpactUsd: round3(maxModeSpendUsd * 0.2),
|
|
1447
1450
|
details: {
|
|
1448
1451
|
maxModeSpendUsd: round3(maxModeSpendUsd),
|
|
@@ -1502,6 +1505,7 @@ function buildFindings(runs) {
|
|
|
1502
1505
|
summary: `${retryCandidates.length} failed call${retryCandidates.length === 1 ? "" : "s"} were followed by additional work, making their spend pure retry overhead.`,
|
|
1503
1506
|
scope: "global",
|
|
1504
1507
|
scopeId: "all",
|
|
1508
|
+
scopeLabel: "workspace",
|
|
1505
1509
|
costImpactUsd: round4(retryCost),
|
|
1506
1510
|
details: {
|
|
1507
1511
|
failedCallCount: retryCandidates.length
|
|
@@ -1529,7 +1533,8 @@ function buildFindings(runs) {
|
|
|
1529
1533
|
title: `Workflow "${run2.workflow}" ran beyond efficient loop bounds`,
|
|
1530
1534
|
summary: `This run reached ${maxIteration} iterations. Xerg treats the spend after iteration 5 as likely loop waste.`,
|
|
1531
1535
|
scope: "run",
|
|
1532
|
-
scopeId: run2.
|
|
1536
|
+
scopeId: run2.workflow,
|
|
1537
|
+
scopeLabel: run2.workflow,
|
|
1533
1538
|
costImpactUsd: round4(loopCost),
|
|
1534
1539
|
details: {
|
|
1535
1540
|
workflow: run2.workflow,
|
|
@@ -1566,6 +1571,7 @@ function buildFindings(runs) {
|
|
|
1566
1571
|
summary: `Xerg found ${outlierRuns.length} run${outlierRuns.length === 1 ? "" : "s"} in this workflow with input token volume far above the workflow average.`,
|
|
1567
1572
|
scope: "workflow",
|
|
1568
1573
|
scopeId: workflow,
|
|
1574
|
+
scopeLabel: workflow,
|
|
1569
1575
|
costImpactUsd: round4(outlierCost),
|
|
1570
1576
|
details: {
|
|
1571
1577
|
workflow,
|
|
@@ -1590,6 +1596,7 @@ function buildFindings(runs) {
|
|
|
1590
1596
|
summary: "This workflow name looks like a recurring heartbeat or monitoring loop. Review whether the cadence and model tier are justified.",
|
|
1591
1597
|
scope: "workflow",
|
|
1592
1598
|
scopeId: workflow,
|
|
1599
|
+
scopeLabel: workflow,
|
|
1593
1600
|
costImpactUsd: round4(idleCost),
|
|
1594
1601
|
details: {
|
|
1595
1602
|
workflow
|
|
@@ -1611,6 +1618,7 @@ function buildFindings(runs) {
|
|
|
1611
1618
|
summary: "An expensive model is being used on a workflow that looks operationally simple. Treat this as an A/B test candidate, not proven waste.",
|
|
1612
1619
|
scope: "workflow",
|
|
1613
1620
|
scopeId: workflow,
|
|
1621
|
+
scopeLabel: workflow,
|
|
1614
1622
|
costImpactUsd: round4(spend * 0.3),
|
|
1615
1623
|
details: {
|
|
1616
1624
|
workflow,
|
|
@@ -1703,6 +1711,247 @@ function estimateCostUsd(provider, model, inputTokens, outputTokens) {
|
|
|
1703
1711
|
return Number((inputCost + outputCost).toFixed(8));
|
|
1704
1712
|
}
|
|
1705
1713
|
|
|
1714
|
+
// ../core/src/recommendations.ts
|
|
1715
|
+
var PRIORITY_RANK = {
|
|
1716
|
+
fix_now: 2,
|
|
1717
|
+
test_next: 1,
|
|
1718
|
+
watch: 0
|
|
1719
|
+
};
|
|
1720
|
+
var CONFIDENCE_RANK = {
|
|
1721
|
+
high: 2,
|
|
1722
|
+
medium: 1,
|
|
1723
|
+
low: 0
|
|
1724
|
+
};
|
|
1725
|
+
function roundCurrency(value) {
|
|
1726
|
+
return Number(value.toFixed(6));
|
|
1727
|
+
}
|
|
1728
|
+
function roundPct(value) {
|
|
1729
|
+
return Number(value.toFixed(4));
|
|
1730
|
+
}
|
|
1731
|
+
function formatScopeLabel(finding) {
|
|
1732
|
+
if (finding.scope === "global") {
|
|
1733
|
+
return "workspace";
|
|
1734
|
+
}
|
|
1735
|
+
return finding.scopeLabel?.trim() || finding.scopeId;
|
|
1736
|
+
}
|
|
1737
|
+
function normalizeRecommendationScope(finding) {
|
|
1738
|
+
if (finding.scope === "global") {
|
|
1739
|
+
return "workspace";
|
|
1740
|
+
}
|
|
1741
|
+
return "workflow";
|
|
1742
|
+
}
|
|
1743
|
+
function stableScopeKeyForFinding(finding) {
|
|
1744
|
+
if (finding.scope === "global") {
|
|
1745
|
+
return "workspace";
|
|
1746
|
+
}
|
|
1747
|
+
return finding.scopeId;
|
|
1748
|
+
}
|
|
1749
|
+
function resolveScopeId(finding, scope) {
|
|
1750
|
+
return scope === "workspace" ? "workspace" : stableScopeKeyForFinding(finding);
|
|
1751
|
+
}
|
|
1752
|
+
function dedupKeyForFinding(finding) {
|
|
1753
|
+
const scope = normalizeRecommendationScope(finding);
|
|
1754
|
+
return `${finding.kind}:${scope}:${stableScopeKeyForFinding(finding)}`;
|
|
1755
|
+
}
|
|
1756
|
+
function recommendationIdForFinding(finding, implementationSurface) {
|
|
1757
|
+
const scope = normalizeRecommendationScope(finding);
|
|
1758
|
+
return sha1(
|
|
1759
|
+
`rec:v2:${finding.kind}:${scope}:${stableScopeKeyForFinding(finding)}:${implementationSurface}`
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
function compareRecommendations(left, right) {
|
|
1763
|
+
const priorityDelta = PRIORITY_RANK[right.priorityBucket] - PRIORITY_RANK[left.priorityBucket];
|
|
1764
|
+
if (priorityDelta !== 0) {
|
|
1765
|
+
return priorityDelta;
|
|
1766
|
+
}
|
|
1767
|
+
if (right.estimatedSavingsUsd !== left.estimatedSavingsUsd) {
|
|
1768
|
+
return right.estimatedSavingsUsd - left.estimatedSavingsUsd;
|
|
1769
|
+
}
|
|
1770
|
+
const confidenceDelta = CONFIDENCE_RANK[right.confidence] - CONFIDENCE_RANK[left.confidence];
|
|
1771
|
+
if (confidenceDelta !== 0) {
|
|
1772
|
+
return confidenceDelta;
|
|
1773
|
+
}
|
|
1774
|
+
return left.title.localeCompare(right.title);
|
|
1775
|
+
}
|
|
1776
|
+
var templatesByKind = {
|
|
1777
|
+
"retry-waste": {
|
|
1778
|
+
priorityBucket: "fix_now",
|
|
1779
|
+
implementationSurface: "retry_policy",
|
|
1780
|
+
category: "structural_efficiency",
|
|
1781
|
+
severity: "high",
|
|
1782
|
+
effort: "low",
|
|
1783
|
+
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.`,
|
|
1785
|
+
whereToChangeFn: (finding) => `Reduce retries or add exponential backoff in the retry wrapper for ${formatScopeLabel(finding)}.`,
|
|
1786
|
+
validationPlanFn: () => "Ship the change, then rerun `xerg audit --compare --push` against the same source. Retry waste should drop materially on the next audit.",
|
|
1787
|
+
actionsFn: () => [
|
|
1788
|
+
"Lower max retry count.",
|
|
1789
|
+
"Add exponential backoff if none exists.",
|
|
1790
|
+
"Log or alert when retries hit the cap."
|
|
1791
|
+
]
|
|
1792
|
+
},
|
|
1793
|
+
"loop-waste": {
|
|
1794
|
+
priorityBucket: "fix_now",
|
|
1795
|
+
implementationSurface: "loop_guard",
|
|
1796
|
+
category: "structural_efficiency",
|
|
1797
|
+
severity: "high",
|
|
1798
|
+
effort: "medium",
|
|
1799
|
+
titleFn: (finding) => `Cap loop depth in ${formatScopeLabel(finding)}`,
|
|
1800
|
+
summaryFn: (finding) => `${finding.summary} This is confirmed loop waste and should be fixed before chasing lower-confidence opportunities.`,
|
|
1801
|
+
whereToChangeFn: (finding) => `Cap iteration depth or add an early-exit guard in the loop controller for ${formatScopeLabel(finding)}.`,
|
|
1802
|
+
validationPlanFn: () => "Ship the change, then rerun `xerg audit --compare --push`. Loop waste and call volume should fall on the next audit.",
|
|
1803
|
+
actionsFn: () => [
|
|
1804
|
+
"Add a hard iteration cap.",
|
|
1805
|
+
"Add a no-progress exit.",
|
|
1806
|
+
"Emit a warning when the cap is hit."
|
|
1807
|
+
]
|
|
1808
|
+
},
|
|
1809
|
+
"context-outlier": {
|
|
1810
|
+
priorityBucket: "test_next",
|
|
1811
|
+
implementationSurface: "prompt_builder",
|
|
1812
|
+
category: "context_hygiene",
|
|
1813
|
+
severity: "medium",
|
|
1814
|
+
effort: "medium",
|
|
1815
|
+
titleFn: (finding) => `Trim context in ${formatScopeLabel(finding)}`,
|
|
1816
|
+
summaryFn: (finding) => `${finding.summary} Treat this as a reversible optimization test rather than proven waste.`,
|
|
1817
|
+
whereToChangeFn: (finding) => `Trim input context in the prompt builder for ${formatScopeLabel(finding)}.`,
|
|
1818
|
+
validationPlanFn: () => "Ship the change, then rerun `xerg audit --compare --push`. Input tokens and context-related spend should fall.",
|
|
1819
|
+
actionsFn: () => [
|
|
1820
|
+
"Drop stale context blocks.",
|
|
1821
|
+
"Summarize long histories.",
|
|
1822
|
+
"Cap prompt size for this workflow."
|
|
1823
|
+
]
|
|
1824
|
+
},
|
|
1825
|
+
"idle-spend": {
|
|
1826
|
+
priorityBucket: "test_next",
|
|
1827
|
+
implementationSurface: "scheduler",
|
|
1828
|
+
category: "cadence_activity",
|
|
1829
|
+
severity: "medium",
|
|
1830
|
+
effort: "low",
|
|
1831
|
+
titleFn: (finding) => `Review cadence for ${formatScopeLabel(finding)}`,
|
|
1832
|
+
summaryFn: (finding) => `${finding.summary} This is usually a scheduling decision, so validate it by lowering or gating activity instead of rewriting the workflow.`,
|
|
1833
|
+
whereToChangeFn: (finding) => `Lower cadence or move to event-driven triggers for ${formatScopeLabel(finding)}.`,
|
|
1834
|
+
validationPlanFn: () => "Ship the change, then rerun `xerg audit --compare --push`. Idle spend per day should drop.",
|
|
1835
|
+
actionsFn: () => [
|
|
1836
|
+
"Reduce poll frequency.",
|
|
1837
|
+
"Gate runs behind a real trigger.",
|
|
1838
|
+
"Disable runs during dead windows."
|
|
1839
|
+
]
|
|
1840
|
+
},
|
|
1841
|
+
"candidate-downgrade": {
|
|
1842
|
+
priorityBucket: "test_next",
|
|
1843
|
+
implementationSurface: "model_routing",
|
|
1844
|
+
category: "model_fit",
|
|
1845
|
+
severity: "low",
|
|
1846
|
+
effort: "low",
|
|
1847
|
+
titleFn: (finding) => `Evaluate a cheaper model for ${formatScopeLabel(finding)}`,
|
|
1848
|
+
summaryFn: (finding) => `${finding.summary} This is an A/B candidate: spend may fall, but quality needs to be checked before rollout.`,
|
|
1849
|
+
whereToChangeFn: (finding) => `Re-map ${formatScopeLabel(finding)} to a cheaper model in the routing layer.`,
|
|
1850
|
+
validationPlanFn: () => "A/B the cheaper model, then rerun `xerg audit --compare --push`. Confirm spend drops without a quality regression.",
|
|
1851
|
+
actionsFn: () => [
|
|
1852
|
+
"Try a cheaper model on this workflow.",
|
|
1853
|
+
"Compare quality on a labeled sample.",
|
|
1854
|
+
"Roll out if acceptable."
|
|
1855
|
+
]
|
|
1856
|
+
},
|
|
1857
|
+
"cache-carryover": {
|
|
1858
|
+
priorityBucket: "test_next",
|
|
1859
|
+
implementationSurface: "user_behavior",
|
|
1860
|
+
category: "context_hygiene",
|
|
1861
|
+
severity: "medium",
|
|
1862
|
+
effort: "low",
|
|
1863
|
+
titleFn: () => "Reset or summarize long Cursor chats",
|
|
1864
|
+
summaryFn: (finding) => `${finding.summary} This is about session behavior rather than code structure, so the intervention is to reset context more aggressively.`,
|
|
1865
|
+
whereToChangeFn: () => "Reset or summarize long Cursor chats instead of continuing the same session.",
|
|
1866
|
+
validationPlanFn: () => "Change session behavior, then push a new Cursor usage audit with `--compare --push`. Cache-read share should fall.",
|
|
1867
|
+
actionsFn: () => [
|
|
1868
|
+
"Summarize context into a short recall note.",
|
|
1869
|
+
"Start a fresh chat.",
|
|
1870
|
+
"Carry forward only the recall note and necessary facts."
|
|
1871
|
+
]
|
|
1872
|
+
},
|
|
1873
|
+
"max-mode-concentration": {
|
|
1874
|
+
priorityBucket: "test_next",
|
|
1875
|
+
implementationSurface: "user_behavior",
|
|
1876
|
+
category: "model_fit",
|
|
1877
|
+
severity: "medium",
|
|
1878
|
+
effort: "low",
|
|
1879
|
+
titleFn: () => "Reserve max mode for the hardest Cursor turns",
|
|
1880
|
+
summaryFn: (finding) => `${finding.summary} The likely fix is changing mode habits or escalation rules, not modifying a prompt builder.`,
|
|
1881
|
+
whereToChangeFn: () => "Reserve max mode for the hardest Cursor turns; default to standard mode.",
|
|
1882
|
+
validationPlanFn: () => "Change your mode defaults, then push a new Cursor usage audit with `--compare --push`. Max-mode spend share should fall.",
|
|
1883
|
+
actionsFn: () => [
|
|
1884
|
+
"Start in standard mode.",
|
|
1885
|
+
"Escalate only when standard fails.",
|
|
1886
|
+
"Review any auto-escalation habit."
|
|
1887
|
+
]
|
|
1888
|
+
}
|
|
1889
|
+
};
|
|
1890
|
+
var unknownTemplate = {
|
|
1891
|
+
priorityBucket: "watch",
|
|
1892
|
+
implementationSurface: "other",
|
|
1893
|
+
category: "other",
|
|
1894
|
+
severity: "low",
|
|
1895
|
+
effort: "medium",
|
|
1896
|
+
titleFn: (finding) => `Review ${finding.title}`,
|
|
1897
|
+
summaryFn: (finding) => finding.summary,
|
|
1898
|
+
whereToChangeFn: (finding) => `Review ${formatScopeLabel(finding)} with your operator.`,
|
|
1899
|
+
validationPlanFn: () => "Ship any change, then rerun `xerg audit --compare --push` to confirm impact.",
|
|
1900
|
+
actionsFn: () => [
|
|
1901
|
+
"Investigate the finding details.",
|
|
1902
|
+
"Decide whether to act.",
|
|
1903
|
+
"Re-audit after the change."
|
|
1904
|
+
]
|
|
1905
|
+
};
|
|
1906
|
+
function buildSingleRecommendation(summary, finding) {
|
|
1907
|
+
const template = templatesByKind[finding.kind] ?? unknownTemplate;
|
|
1908
|
+
const scope = normalizeRecommendationScope(finding);
|
|
1909
|
+
const scopeId = resolveScopeId(finding, scope);
|
|
1910
|
+
const scopeLabel = formatScopeLabel(finding);
|
|
1911
|
+
const estimatedSavingsPct = summary.totalSpendUsd === 0 ? 0 : roundPct(Math.min(Math.max(finding.costImpactUsd / summary.totalSpendUsd, 0), 1));
|
|
1912
|
+
return {
|
|
1913
|
+
id: recommendationIdForFinding(finding, template.implementationSurface),
|
|
1914
|
+
findingId: finding.id,
|
|
1915
|
+
kind: finding.kind,
|
|
1916
|
+
title: template.titleFn(finding),
|
|
1917
|
+
summary: template.summaryFn(finding),
|
|
1918
|
+
priorityBucket: template.priorityBucket,
|
|
1919
|
+
recommendedOrder: 0,
|
|
1920
|
+
implementationSurface: template.implementationSurface,
|
|
1921
|
+
category: template.category,
|
|
1922
|
+
severity: template.severity,
|
|
1923
|
+
estimatedSavingsUsd: roundCurrency(finding.costImpactUsd),
|
|
1924
|
+
estimatedSavingsPct,
|
|
1925
|
+
confidence: finding.confidence,
|
|
1926
|
+
effort: template.effort,
|
|
1927
|
+
scope,
|
|
1928
|
+
scopeId,
|
|
1929
|
+
scopeLabel,
|
|
1930
|
+
whereToChange: template.whereToChangeFn(finding),
|
|
1931
|
+
validationPlan: template.validationPlanFn(finding),
|
|
1932
|
+
actions: template.actionsFn(finding)
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
function buildRecommendations(summary) {
|
|
1936
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
1937
|
+
for (const finding of summary.findings) {
|
|
1938
|
+
const recommendation = buildSingleRecommendation(summary, finding);
|
|
1939
|
+
const key = dedupKeyForFinding(finding);
|
|
1940
|
+
const existing = deduped.get(key);
|
|
1941
|
+
if (!existing) {
|
|
1942
|
+
deduped.set(key, recommendation);
|
|
1943
|
+
continue;
|
|
1944
|
+
}
|
|
1945
|
+
if (recommendation.estimatedSavingsUsd > existing.estimatedSavingsUsd || recommendation.estimatedSavingsUsd === existing.estimatedSavingsUsd && CONFIDENCE_RANK[recommendation.confidence] > CONFIDENCE_RANK[existing.confidence]) {
|
|
1946
|
+
deduped.set(key, recommendation);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
return [...deduped.values()].sort(compareRecommendations).map((recommendation, index) => ({
|
|
1950
|
+
...recommendation,
|
|
1951
|
+
recommendedOrder: index + 1
|
|
1952
|
+
}));
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1706
1955
|
// ../core/src/report/timeseries.ts
|
|
1707
1956
|
function round5(value) {
|
|
1708
1957
|
return Number(value.toFixed(6));
|
|
@@ -1856,7 +2105,7 @@ function buildAuditSummary(input) {
|
|
|
1856
2105
|
const generatedAt = isoNow();
|
|
1857
2106
|
const spendByDay = buildSpendByDay(input.runs);
|
|
1858
2107
|
const observedDays = buildObservedUtcDayRange(input.runs);
|
|
1859
|
-
|
|
2108
|
+
const summary = {
|
|
1860
2109
|
auditId: sha1(
|
|
1861
2110
|
`${generatedAt}:${input.runs.length}:${input.sources.map((source) => source.path).join("|")}`
|
|
1862
2111
|
),
|
|
@@ -1900,6 +2149,7 @@ function buildAuditSummary(input) {
|
|
|
1900
2149
|
spendByDay,
|
|
1901
2150
|
wasteByDay: buildWasteByDay(input.wasteAttributions, observedDays, wasteSpendUsd),
|
|
1902
2151
|
findings: input.findings,
|
|
2152
|
+
recommendations: [],
|
|
1903
2153
|
notes: [
|
|
1904
2154
|
"Cost per outcome is intentionally unavailable in v0. Xerg is measuring waste intelligence only.",
|
|
1905
2155
|
"Opportunity findings are directional recommendations, not proven waste."
|
|
@@ -1907,6 +2157,8 @@ function buildAuditSummary(input) {
|
|
|
1907
2157
|
sourceFiles: input.sources,
|
|
1908
2158
|
dbPath: input.dbPath
|
|
1909
2159
|
};
|
|
2160
|
+
summary.recommendations = buildRecommendations(summary);
|
|
2161
|
+
return summary;
|
|
1910
2162
|
}
|
|
1911
2163
|
|
|
1912
2164
|
// ../core/src/runtime.ts
|
|
@@ -3169,109 +3421,6 @@ async function auditCursorUsageCsv(options) {
|
|
|
3169
3421
|
return summary;
|
|
3170
3422
|
}
|
|
3171
3423
|
|
|
3172
|
-
// ../core/src/recommendations.ts
|
|
3173
|
-
var templatesByKind = {
|
|
3174
|
-
"retry-waste": {
|
|
3175
|
-
actionType: "other",
|
|
3176
|
-
titleFn: () => "Add retry backoff or reduce retry attempts",
|
|
3177
|
-
descriptionFn: (f) => `${f.summary} Consider adding exponential backoff or reducing the maximum retry count to eliminate this overhead.`,
|
|
3178
|
-
suggestedChangeFn: (f) => ({
|
|
3179
|
-
strategy: "exponential-backoff",
|
|
3180
|
-
maxRetries: 3,
|
|
3181
|
-
failedCallCount: f.details.failedCallCount
|
|
3182
|
-
})
|
|
3183
|
-
},
|
|
3184
|
-
"loop-waste": {
|
|
3185
|
-
actionType: "other",
|
|
3186
|
-
titleFn: (f) => `Cap iteration depth for ${extractWorkflow(f)}`,
|
|
3187
|
-
descriptionFn: (f) => `${f.summary} Adding an iteration limit or early-exit condition would prevent runaway loops from burning spend.`,
|
|
3188
|
-
suggestedChangeFn: (f) => ({
|
|
3189
|
-
strategy: "iteration-cap",
|
|
3190
|
-
suggestedMaxIterations: 5,
|
|
3191
|
-
observedMaxIteration: f.details.maxIteration
|
|
3192
|
-
})
|
|
3193
|
-
},
|
|
3194
|
-
"context-outlier": {
|
|
3195
|
-
actionType: "prompt-trim",
|
|
3196
|
-
titleFn: (f) => `Trim context for ${extractWorkflow(f)}`,
|
|
3197
|
-
descriptionFn: (f) => `${f.summary} Reducing input token volume to near the workflow baseline would lower cost proportionally.`,
|
|
3198
|
-
suggestedChangeFn: (f) => ({
|
|
3199
|
-
strategy: "context-reduction",
|
|
3200
|
-
averageInputTokens: f.details.averageInputTokens
|
|
3201
|
-
})
|
|
3202
|
-
},
|
|
3203
|
-
"candidate-downgrade": {
|
|
3204
|
-
actionType: "model-switch",
|
|
3205
|
-
titleFn: (f) => `Evaluate cheaper model for ${extractWorkflow(f)}`,
|
|
3206
|
-
descriptionFn: (f) => `${f.summary} This is an A/B test candidate \u2014 try a cheaper model on this workflow and compare quality.`,
|
|
3207
|
-
suggestedChangeFn: () => ({
|
|
3208
|
-
strategy: "model-downgrade",
|
|
3209
|
-
candidates: ["claude-3-haiku", "gpt-4o-mini"]
|
|
3210
|
-
})
|
|
3211
|
-
},
|
|
3212
|
-
"idle-spend": {
|
|
3213
|
-
actionType: "other",
|
|
3214
|
-
titleFn: (f) => `Review cadence for ${extractWorkflow(f)}`,
|
|
3215
|
-
descriptionFn: (f) => `${f.summary} Consider reducing polling frequency or switching to an event-driven approach.`,
|
|
3216
|
-
suggestedChangeFn: () => ({
|
|
3217
|
-
strategy: "cadence-review"
|
|
3218
|
-
})
|
|
3219
|
-
},
|
|
3220
|
-
"cache-carryover": {
|
|
3221
|
-
actionType: "prompt-trim",
|
|
3222
|
-
titleFn: () => "Summarize and reset long Cursor chats",
|
|
3223
|
-
descriptionFn: (f) => `${f.summary} Create a compact recall summary, start a fresh chat, and carry forward only the facts the model actually needs.`,
|
|
3224
|
-
suggestedChangeFn: (f) => ({
|
|
3225
|
-
strategy: "conversation-reset",
|
|
3226
|
-
cacheReadShare: f.details.cacheReadShare,
|
|
3227
|
-
totalCacheReadTokens: f.details.totalCacheReadTokens
|
|
3228
|
-
})
|
|
3229
|
-
},
|
|
3230
|
-
"max-mode-concentration": {
|
|
3231
|
-
actionType: "model-switch",
|
|
3232
|
-
titleFn: () => "Reserve max mode for the hardest Cursor turns",
|
|
3233
|
-
descriptionFn: (f) => `${f.summary} Try a two-pass workflow: standard mode first, then escalate only the prompts that truly need max mode.`,
|
|
3234
|
-
suggestedChangeFn: (f) => ({
|
|
3235
|
-
strategy: "tiered-routing",
|
|
3236
|
-
maxModeSpendShare: f.details.maxModeSpendShare,
|
|
3237
|
-
maxModeCallCount: f.details.maxModeCallCount
|
|
3238
|
-
})
|
|
3239
|
-
}
|
|
3240
|
-
};
|
|
3241
|
-
function extractWorkflow(finding) {
|
|
3242
|
-
const details = finding.details;
|
|
3243
|
-
return details.workflow || finding.scopeId || "this workflow";
|
|
3244
|
-
}
|
|
3245
|
-
function buildSingleRecommendation(finding) {
|
|
3246
|
-
const template = templatesByKind[finding.kind];
|
|
3247
|
-
if (template) {
|
|
3248
|
-
return {
|
|
3249
|
-
id: sha1(`rec:${finding.id}:${template.actionType}`),
|
|
3250
|
-
findingId: finding.id,
|
|
3251
|
-
kind: finding.kind,
|
|
3252
|
-
title: template.titleFn(finding),
|
|
3253
|
-
description: template.descriptionFn(finding),
|
|
3254
|
-
estimatedSavingsUsd: finding.costImpactUsd,
|
|
3255
|
-
confidence: finding.confidence,
|
|
3256
|
-
actionType: template.actionType,
|
|
3257
|
-
suggestedChange: template.suggestedChangeFn?.(finding)
|
|
3258
|
-
};
|
|
3259
|
-
}
|
|
3260
|
-
return {
|
|
3261
|
-
id: sha1(`rec:${finding.id}:other`),
|
|
3262
|
-
findingId: finding.id,
|
|
3263
|
-
kind: finding.kind,
|
|
3264
|
-
title: `Review: ${finding.title}`,
|
|
3265
|
-
description: finding.summary,
|
|
3266
|
-
estimatedSavingsUsd: finding.costImpactUsd,
|
|
3267
|
-
confidence: finding.confidence,
|
|
3268
|
-
actionType: "other"
|
|
3269
|
-
};
|
|
3270
|
-
}
|
|
3271
|
-
function buildRecommendations(summary) {
|
|
3272
|
-
return summary.findings.map(buildSingleRecommendation);
|
|
3273
|
-
}
|
|
3274
|
-
|
|
3275
3424
|
// ../core/src/report/render.ts
|
|
3276
3425
|
function formatUsd(value) {
|
|
3277
3426
|
return new Intl.NumberFormat("en-US", {
|
|
@@ -3330,16 +3479,6 @@ function renderTaxonomyBlock(summary) {
|
|
|
3330
3479
|
function topFinding(summary, classification) {
|
|
3331
3480
|
return summary.findings.filter((finding) => finding.classification === classification).sort((left, right) => right.costImpactUsd - left.costImpactUsd)[0];
|
|
3332
3481
|
}
|
|
3333
|
-
function topSavingsTest(summary) {
|
|
3334
|
-
return summary.findings.filter((finding) => finding.classification === "opportunity").sort((left, right) => {
|
|
3335
|
-
const leftPriority = left.kind === "candidate-downgrade" ? 1 : 0;
|
|
3336
|
-
const rightPriority = right.kind === "candidate-downgrade" ? 1 : 0;
|
|
3337
|
-
if (leftPriority !== rightPriority) {
|
|
3338
|
-
return rightPriority - leftPriority;
|
|
3339
|
-
}
|
|
3340
|
-
return right.costImpactUsd - left.costImpactUsd;
|
|
3341
|
-
})[0] ?? null;
|
|
3342
|
-
}
|
|
3343
3482
|
function renderFindingList(findings, emptyLabel) {
|
|
3344
3483
|
if (findings.length === 0) {
|
|
3345
3484
|
return [`- ${emptyLabel}`];
|
|
@@ -3348,6 +3487,33 @@ function renderFindingList(findings, emptyLabel) {
|
|
|
3348
3487
|
return `- ${finding.title}: ${formatUsd(finding.costImpactUsd)} (${finding.confidence})`;
|
|
3349
3488
|
});
|
|
3350
3489
|
}
|
|
3490
|
+
function renderActionQueueRow(label, recommendation) {
|
|
3491
|
+
if (!recommendation) {
|
|
3492
|
+
return [`${label}`, "- none"];
|
|
3493
|
+
}
|
|
3494
|
+
return [
|
|
3495
|
+
label,
|
|
3496
|
+
`- ${recommendation.title} [${recommendation.scopeLabel}]: ${formatUsd(recommendation.estimatedSavingsUsd)} (${recommendation.severity})`
|
|
3497
|
+
];
|
|
3498
|
+
}
|
|
3499
|
+
function renderActionQueue(summary) {
|
|
3500
|
+
const fixNow = summary.recommendations.find(
|
|
3501
|
+
(recommendation) => recommendation.priorityBucket === "fix_now"
|
|
3502
|
+
);
|
|
3503
|
+
const testNext = summary.recommendations.find(
|
|
3504
|
+
(recommendation) => recommendation.priorityBucket === "test_next"
|
|
3505
|
+
);
|
|
3506
|
+
const watch = summary.recommendations.find(
|
|
3507
|
+
(recommendation) => recommendation.priorityBucket === "watch"
|
|
3508
|
+
);
|
|
3509
|
+
return [
|
|
3510
|
+
"## Action queue",
|
|
3511
|
+
...renderActionQueueRow("Fix now", fixNow),
|
|
3512
|
+
...renderActionQueueRow("Test next", testNext),
|
|
3513
|
+
...renderActionQueueRow("Watch", watch),
|
|
3514
|
+
"How to validate: `xerg audit --compare --push`"
|
|
3515
|
+
];
|
|
3516
|
+
}
|
|
3351
3517
|
function describeSpendDelta(delta) {
|
|
3352
3518
|
return `${delta.key} (${formatUsdDelta(delta.deltaSpendUsd)})`;
|
|
3353
3519
|
}
|
|
@@ -3570,6 +3736,8 @@ function renderCursorTerminalSummary(summary) {
|
|
|
3570
3736
|
"## Findings",
|
|
3571
3737
|
...renderFindingList(summary.findings, "none detected"),
|
|
3572
3738
|
"",
|
|
3739
|
+
...renderActionQueue(summary),
|
|
3740
|
+
"",
|
|
3573
3741
|
...renderCursorCompareBlock(summary),
|
|
3574
3742
|
...summary.comparison ? [""] : [],
|
|
3575
3743
|
"## Notes",
|
|
@@ -3613,6 +3781,8 @@ function renderCursorMarkdownSummary(summary) {
|
|
|
3613
3781
|
...summary.findings.slice(0, 10).map((finding) => {
|
|
3614
3782
|
return `- **${finding.title}** (${finding.classification}, ${finding.confidence}) \u2014 ${finding.summary} Estimated impact: ${formatUsd(finding.costImpactUsd)}.`;
|
|
3615
3783
|
}),
|
|
3784
|
+
"",
|
|
3785
|
+
...renderActionQueue(summary),
|
|
3616
3786
|
...summary.comparison ? ["", ...renderCursorCompareBlock(summary)] : [],
|
|
3617
3787
|
"",
|
|
3618
3788
|
"## Notes",
|
|
@@ -3627,7 +3797,6 @@ function renderTerminalSummary(summary) {
|
|
|
3627
3797
|
const opportunityFindings = summary.findings.filter(
|
|
3628
3798
|
(finding) => finding.classification === "opportunity"
|
|
3629
3799
|
);
|
|
3630
|
-
const topSavings = topSavingsTest(summary);
|
|
3631
3800
|
const topWaste = topFinding(summary, "waste");
|
|
3632
3801
|
return [
|
|
3633
3802
|
"# Xerg audit",
|
|
@@ -3656,11 +3825,8 @@ function renderTerminalSummary(summary) {
|
|
|
3656
3825
|
"## Opportunities",
|
|
3657
3826
|
...renderFindingList(opportunityFindings, "none detected"),
|
|
3658
3827
|
"",
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
`- Start with ${topSavings.title}: ${formatUsd(topSavings.costImpactUsd)} of potential impact`,
|
|
3662
|
-
`- Why this test first: ${topSavings.summary}`
|
|
3663
|
-
] : ["- No savings test surfaced yet"],
|
|
3828
|
+
...renderActionQueue(summary),
|
|
3829
|
+
"",
|
|
3664
3830
|
...topWaste ? [`- Confirmed leak to close first: ${topWaste.title}`] : ["- Confirmed leak to close first: none"],
|
|
3665
3831
|
...summary.spendByWorkflow[0] ? [`- Workflow to inspect first: ${summary.spendByWorkflow[0].key}`] : ["- Workflow to inspect first: none"],
|
|
3666
3832
|
"",
|
|
@@ -3697,7 +3863,9 @@ function renderMarkdownSummary(summary) {
|
|
|
3697
3863
|
"## Findings",
|
|
3698
3864
|
...summary.findings.slice(0, 10).map((finding) => {
|
|
3699
3865
|
return `- **${finding.title}** (${finding.classification}, ${finding.confidence}) \u2014 ${finding.summary} Estimated impact: ${formatUsd(finding.costImpactUsd)}.`;
|
|
3700
|
-
})
|
|
3866
|
+
}),
|
|
3867
|
+
"",
|
|
3868
|
+
...renderActionQueue(summary)
|
|
3701
3869
|
];
|
|
3702
3870
|
if (summary.comparison) {
|
|
3703
3871
|
const comparison = summary.comparison;
|
|
@@ -3714,7 +3882,7 @@ function renderMarkdownSummary(summary) {
|
|
|
3714
3882
|
}
|
|
3715
3883
|
|
|
3716
3884
|
// ../schemas/dist/index.js
|
|
3717
|
-
var AUDIT_PUSH_PAYLOAD_VERSION =
|
|
3885
|
+
var AUDIT_PUSH_PAYLOAD_VERSION = 2;
|
|
3718
3886
|
|
|
3719
3887
|
// ../core/src/wire.ts
|
|
3720
3888
|
function toWireFinding(finding) {
|
|
@@ -3766,6 +3934,7 @@ function toWirePayload(summary, meta) {
|
|
|
3766
3934
|
spendByDay: summary.spendByDay,
|
|
3767
3935
|
wasteByDay: summary.wasteByDay,
|
|
3768
3936
|
findings: summary.findings.map(toWireFinding),
|
|
3937
|
+
recommendations: summary.recommendations,
|
|
3769
3938
|
notes: summary.notes,
|
|
3770
3939
|
comparison: summary.comparison ? toWireComparison(summary.comparison) : null
|
|
3771
3940
|
},
|
|
@@ -5134,11 +5303,10 @@ ${errorMessages}`);
|
|
|
5134
5303
|
summaries.push({ name: source.name, source, summary });
|
|
5135
5304
|
}
|
|
5136
5305
|
if (options.json) {
|
|
5137
|
-
const output = summaries.length === 1 ?
|
|
5306
|
+
const output = summaries.length === 1 ? summaries[0].summary : {
|
|
5138
5307
|
sources: summaries.map((s) => ({
|
|
5139
5308
|
name: s.name,
|
|
5140
|
-
...s.summary
|
|
5141
|
-
recommendations: buildRecommendations(s.summary)
|
|
5309
|
+
...s.summary
|
|
5142
5310
|
}))
|
|
5143
5311
|
};
|
|
5144
5312
|
process.stdout.write(`${JSON.stringify(output, null, 2)}
|
|
@@ -5216,9 +5384,7 @@ function renderOutput(summary, options) {
|
|
|
5216
5384
|
return;
|
|
5217
5385
|
}
|
|
5218
5386
|
if (options.json) {
|
|
5219
|
-
|
|
5220
|
-
const output = { ...summary, recommendations };
|
|
5221
|
-
process.stdout.write(`${JSON.stringify(output, null, 2)}
|
|
5387
|
+
process.stdout.write(`${JSON.stringify(summary, null, 2)}
|
|
5222
5388
|
`);
|
|
5223
5389
|
return;
|
|
5224
5390
|
}
|
|
@@ -5823,6 +5989,7 @@ function renderRailwayDoctorReport(report) {
|
|
|
5823
5989
|
import { existsSync as existsSync2, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync2 } from "fs";
|
|
5824
5990
|
import { dirname as dirname3, join as join8 } from "path";
|
|
5825
5991
|
var HOSTED_MCP_URL = "https://mcp.xerg.ai/mcp";
|
|
5992
|
+
var MCP_SERVER_NAME = "xerg";
|
|
5826
5993
|
async function runMcpSetupCommand() {
|
|
5827
5994
|
await runMcpSetupFlow();
|
|
5828
5995
|
}
|
|
@@ -5865,6 +6032,11 @@ async function runMcpSetupFlow() {
|
|
|
5865
6032
|
value: "claude-code",
|
|
5866
6033
|
description: "Project-scoped Claude Code MCP config"
|
|
5867
6034
|
},
|
|
6035
|
+
{
|
|
6036
|
+
name: "Codex",
|
|
6037
|
+
value: "codex",
|
|
6038
|
+
description: "Codex config.toml snippet"
|
|
6039
|
+
},
|
|
5868
6040
|
{
|
|
5869
6041
|
name: "Other",
|
|
5870
6042
|
value: "other",
|
|
@@ -5876,6 +6048,14 @@ async function runMcpSetupFlow() {
|
|
|
5876
6048
|
await handleCursorSetup(snippet, config);
|
|
5877
6049
|
return;
|
|
5878
6050
|
}
|
|
6051
|
+
if (client === "codex") {
|
|
6052
|
+
process.stdout.write(`${buildCodexMcpConfig(config)}
|
|
6053
|
+
`);
|
|
6054
|
+
process.stderr.write(
|
|
6055
|
+
"Add this to `~/.codex/config.toml`, then restart Codex so it loads the Xerg MCP tools.\n"
|
|
6056
|
+
);
|
|
6057
|
+
return;
|
|
6058
|
+
}
|
|
5879
6059
|
process.stdout.write(`${snippet}
|
|
5880
6060
|
`);
|
|
5881
6061
|
if (client === "claude-code") {
|
|
@@ -5913,7 +6093,7 @@ async function handleCursorSetup(snippet, config) {
|
|
|
5913
6093
|
function buildHostedMcpConfig(config) {
|
|
5914
6094
|
return {
|
|
5915
6095
|
mcpServers: {
|
|
5916
|
-
|
|
6096
|
+
[MCP_SERVER_NAME]: {
|
|
5917
6097
|
type: "http",
|
|
5918
6098
|
url: HOSTED_MCP_URL,
|
|
5919
6099
|
headers: {
|
|
@@ -5923,6 +6103,19 @@ function buildHostedMcpConfig(config) {
|
|
|
5923
6103
|
}
|
|
5924
6104
|
};
|
|
5925
6105
|
}
|
|
6106
|
+
function buildCodexMcpConfig(config) {
|
|
6107
|
+
return [
|
|
6108
|
+
`[mcp_servers.${MCP_SERVER_NAME}]`,
|
|
6109
|
+
"enabled = true",
|
|
6110
|
+
`url = ${tomlString(HOSTED_MCP_URL)}`,
|
|
6111
|
+
"",
|
|
6112
|
+
`[mcp_servers.${MCP_SERVER_NAME}.http_headers]`,
|
|
6113
|
+
`Authorization = ${tomlString(`Bearer ${config.apiKey}`)}`
|
|
6114
|
+
].join("\n");
|
|
6115
|
+
}
|
|
6116
|
+
function tomlString(value) {
|
|
6117
|
+
return JSON.stringify(value);
|
|
6118
|
+
}
|
|
5926
6119
|
function writeCursorConfig(filePath, config) {
|
|
5927
6120
|
mkdirSync6(dirname3(filePath), { recursive: true });
|
|
5928
6121
|
let parsed = {};
|
|
@@ -6259,7 +6452,7 @@ Notes:
|
|
|
6259
6452
|
function renderMcpSetupHelp(commandPrefix) {
|
|
6260
6453
|
return `${formatCommand("mcp-setup", commandPrefix)}
|
|
6261
6454
|
|
|
6262
|
-
Generate hosted MCP client configuration for Cursor, Claude Code, or another MCP client.
|
|
6455
|
+
Generate hosted MCP client configuration for Cursor, Claude Code, Codex, or another MCP client.
|
|
6263
6456
|
|
|
6264
6457
|
Usage:
|
|
6265
6458
|
${formatCommand("mcp-setup", commandPrefix)}
|
|
@@ -6268,6 +6461,7 @@ Notes:
|
|
|
6268
6461
|
- Interactive in v1 because client selection is prompt-driven
|
|
6269
6462
|
- Uses the hosted MCP endpoint at https://mcp.xerg.ai/mcp
|
|
6270
6463
|
- Can write a project-scoped Cursor config when .cursor/ already exists
|
|
6464
|
+
- Prints a Codex config.toml snippet when Codex is selected
|
|
6271
6465
|
- Local audits and compare stay available even if you skip hosted MCP setup
|
|
6272
6466
|
|
|
6273
6467
|
-h, --help Show help
|