@orangepro/orangepro-mcp 0.2.26 → 0.2.27

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.
@@ -1494,6 +1494,7 @@ function writeStartStaticSnapshot(root, baseRef, warnings) {
1494
1494
  return behaviorCoveragePath ? { behaviorCoveragePath } : {};
1495
1495
  }
1496
1496
  export async function opStart(root, opts = {}, deps = defaultDeps()) {
1497
+ const startTime = Date.now();
1497
1498
  const providerOpts = startProviderOverride(root, opts);
1498
1499
  const scanRoot = opts.source ? resolve(opts.source) : resolve(root);
1499
1500
  const providerEnv = loadProviderEnv([root, scanRoot], deps.env);
@@ -1511,21 +1512,6 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
1511
1512
  suppressProgress: true
1512
1513
  }, deps);
1513
1514
  const warnings = [...analyze.warnings];
1514
- let dominantLang = "unknown";
1515
- try {
1516
- const g = loadGraph(root);
1517
- const lc = {};
1518
- for (const n of g.nodes) {
1519
- const lang = n.kind === "File" && typeof n.properties.language === "string" ? n.properties.language : null;
1520
- if (lang)
1521
- lc[lang] = (lc[lang] ?? 0) + 1;
1522
- }
1523
- const top = Object.entries(lc).sort((a, b) => b[1] - a[1]);
1524
- if (top.length > 0)
1525
- dominantLang = top[0][0];
1526
- }
1527
- catch { /* telemetry is best-effort */ }
1528
- pingTelemetry({ fileCount: scope.files, language: dominantLang });
1529
1515
  reportProgress("start: deterministic graph is ready", { current: 4, total: 8 });
1530
1516
  const staticSnapshot = writeStartStaticSnapshot(root, opts.baseRef, warnings);
1531
1517
  const providerConfigured = deps.aiProvider !== undefined || resolveProviderConfig(providerEnv, providerOpts) !== null;
@@ -1687,6 +1673,28 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
1687
1673
  const rtm = opRtm(root, { format: "md", baseRef: opts.baseRef, limit: START_RTM_LIMIT });
1688
1674
  const finalGraph = loadGraph(workspacePaths(root).graphPath);
1689
1675
  const aiLinked = summarizeAiLinks(finalGraph);
1676
+ // --- Telemetry (v2): fire after report is generated, graph is final ---
1677
+ try {
1678
+ const lc = {};
1679
+ for (const n of finalGraph.nodes) {
1680
+ if (n.kind === "File" && typeof n.properties.language === "string") {
1681
+ lc[n.properties.language] = (lc[n.properties.language] ?? 0) + 1;
1682
+ }
1683
+ }
1684
+ const topLang = Object.entries(lc).sort((a, b) => b[1] - a[1]);
1685
+ const dominantLang = topLang.length > 0 ? topLang[0][0] : "unknown";
1686
+ const behaviorCount = behaviorNodes(finalGraph).length;
1687
+ pingTelemetry({
1688
+ event: "scan_complete",
1689
+ fileCount: scope.files,
1690
+ language: dominantLang,
1691
+ behaviors: behaviorCount,
1692
+ hasByok: !!(providerEnv.ANTHROPIC_API_KEY || providerEnv.OPENAI_API_KEY || providerEnv.OLLAMA_BASE_URL),
1693
+ reportGenerated: !!coverageHtml,
1694
+ durationMs: Date.now() - startTime,
1695
+ });
1696
+ }
1697
+ catch { /* telemetry must never fail the run */ }
1690
1698
  const finalAnalyze = {
1691
1699
  ...analyze,
1692
1700
  analysis: finalGraph.analysis ?? analyze.analysis,
@@ -1,15 +1,20 @@
1
1
  /**
2
- * Anonymous usage telemetry for OrangePro MCP.
2
+ * Anonymous usage telemetry for OrangePro MCP (v2).
3
3
  *
4
- * Sends one lightweight ping per run with: version, detected language,
5
- * file count bucket, OS, and node version. No code, no file names,
6
- * no repo name, no identity, no IP stored.
4
+ * Sends one lightweight ping per scan with: version, detected language,
5
+ * file count bucket, behavior count bucket, whether BYOK is configured,
6
+ * whether the report was generated, scan duration, OS, and node version.
7
+ *
8
+ * No code, no file names, no repo name, no identity, no IP stored.
7
9
  *
8
10
  * Disable: set DO_NOT_TRACK=1 or ORANGEPRO_NO_TELEMETRY=1
9
11
  */
10
12
  import https from "https";
11
13
  import { readFileSync } from "fs";
12
- import { resolve } from "path";
14
+ import { dirname, resolve } from "path";
15
+ import { fileURLToPath } from "url";
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
13
18
  const ENDPOINT_HOST = "telemetry.orangepro.ai";
14
19
  const ENDPOINT_PATH = "/v1/ping";
15
20
  const TIMEOUT_MS = 3000;
@@ -22,20 +27,20 @@ function getVersion() {
22
27
  return "unknown";
23
28
  }
24
29
  }
30
+ function bucket(count, thresholds) {
31
+ for (let i = 0; i < thresholds.length; i++) {
32
+ if (count < thresholds[i]) {
33
+ const low = i === 0 ? 1 : thresholds[i - 1];
34
+ return `${low}-${thresholds[i] - 1}`;
35
+ }
36
+ }
37
+ return `${thresholds[thresholds.length - 1]}+`;
38
+ }
25
39
  function fileBucket(count) {
26
- if (count < 50)
27
- return "1-49";
28
- if (count < 200)
29
- return "50-199";
30
- if (count < 500)
31
- return "200-499";
32
- if (count < 1000)
33
- return "500-999";
34
- if (count < 5000)
35
- return "1000-4999";
36
- if (count < 10000)
37
- return "5000-9999";
38
- return "10000+";
40
+ return bucket(count, [50, 200, 500, 1000, 5000, 10000]);
41
+ }
42
+ function behaviorBucket(count) {
43
+ return bucket(count, [100, 500, 1000, 5000, 10000]);
39
44
  }
40
45
  export function pingTelemetry(payload) {
41
46
  // Respect DO_NOT_TRACK standard and custom env var
@@ -44,13 +49,16 @@ export function pingTelemetry(payload) {
44
49
  return;
45
50
  }
46
51
  const data = JSON.stringify({
47
- event: "run",
52
+ event: payload.event || "scan_complete",
48
53
  v: getVersion(),
49
54
  lang: payload.language,
50
55
  files: fileBucket(payload.fileCount),
56
+ behaviors: payload.behaviors != null ? behaviorBucket(payload.behaviors) : undefined,
57
+ has_byok: payload.hasByok ? 1 : 0,
58
+ report: payload.reportGenerated !== false ? 1 : 0,
59
+ duration_ms: payload.durationMs ?? undefined,
51
60
  os: process.platform,
52
61
  node: process.version,
53
- ts: new Date().toISOString(),
54
62
  });
55
63
  try {
56
64
  const req = https.request({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangepro/orangepro-mcp",
3
- "version": "0.2.26",
3
+ "version": "0.2.27",
4
4
  "private": false,
5
5
  "description": "OrangePro (`opro`) — a local-first, BYOK CLI + MCP server that builds an evidence graph from a local checkout, ingests runtime coverage, and generates grounded tests. Metadata-only exports; no source upload; generated tests stay local.",
6
6
  "license": "MIT",