@orangepro/orangepro-mcp 0.2.22 → 0.2.24

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.
@@ -13,7 +13,23 @@ const TEST_FRAMEWORK_DEPS = {
13
13
  pytest: { layer: "unit" },
14
14
  unittest: { layer: "unit" },
15
15
  rspec: { layer: "unit" },
16
- junit: { layer: "unit" }
16
+ junit: { layer: "unit" },
17
+ detox: { layer: "e2e" },
18
+ "@testing-library/react-native": { layer: "component" }
19
+ };
20
+ const RUNTIME_HINTS = {
21
+ "react-native": "React Native",
22
+ "expo": "Expo",
23
+ "next": "Next.js",
24
+ "nuxt": "Nuxt",
25
+ "express": "Express",
26
+ "fastify": "Fastify",
27
+ "@angular/core": "Angular",
28
+ "vue": "Vue",
29
+ "svelte": "Svelte",
30
+ "django": "Django",
31
+ "flask": "Flask",
32
+ "spring-boot": "Spring Boot",
17
33
  };
18
34
  const CONFIG_FRAMEWORK_HINTS = [
19
35
  { match: /^vitest\.config\.[tj]s$/, name: "vitest", layer: "unit" },
@@ -57,6 +73,13 @@ export function detectFromPackageJson(relPath, content) {
57
73
  frameworks.push({ name: dep.replace(/^@playwright\/test$/, "playwright"), category: "test", test_layer: hit.layer, evidence_ref: relPath });
58
74
  }
59
75
  }
76
+ // Runtime framework detection (for display when no test framework found)
77
+ for (const dep of depNames) {
78
+ const hint = RUNTIME_HINTS[dep];
79
+ if (hint) {
80
+ frameworks.push({ name: hint, category: "runtime", evidence_ref: relPath });
81
+ }
82
+ }
60
83
  return { pkg, frameworks };
61
84
  }
62
85
  /** Detect test frameworks from non-npm manifests (pyproject.toml, requirements). */
@@ -2,6 +2,7 @@ import { execFileSync, spawnSync } from "node:child_process";
2
2
  import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join, relative, resolve, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { pingTelemetry } from "./telemetry.js";
5
6
  import { LOCAL_GRAPH_SCHEMA_VERSION } from "./graph/ontology.js";
6
7
  import { systemClock } from "./util/time.js";
7
8
  import { redactSecrets } from "./util/redact.js";
@@ -1510,6 +1511,21 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
1510
1511
  suppressProgress: true
1511
1512
  }, deps);
1512
1513
  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 });
1513
1529
  reportProgress("start: deterministic graph is ready", { current: 4, total: 8 });
1514
1530
  const staticSnapshot = writeStartStaticSnapshot(root, opts.baseRef, warnings);
1515
1531
  const providerConfigured = deps.aiProvider !== undefined || resolveProviderConfig(providerEnv, providerOpts) !== null;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Anonymous usage telemetry for OrangePro MCP.
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.
7
+ *
8
+ * Disable: set DO_NOT_TRACK=1 or ORANGEPRO_NO_TELEMETRY=1
9
+ */
10
+ import https from "https";
11
+ import { readFileSync } from "fs";
12
+ import { resolve } from "path";
13
+ const ENDPOINT_HOST = "telemetry.orangepro.ai";
14
+ const ENDPOINT_PATH = "/v1/ping";
15
+ const TIMEOUT_MS = 3000;
16
+ function getVersion() {
17
+ try {
18
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, "../../package.json"), "utf8"));
19
+ return pkg.version || "unknown";
20
+ }
21
+ catch {
22
+ return "unknown";
23
+ }
24
+ }
25
+ 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+";
39
+ }
40
+ export function pingTelemetry(payload) {
41
+ // Respect DO_NOT_TRACK standard and custom env var
42
+ if (process.env.DO_NOT_TRACK === "1" ||
43
+ process.env.ORANGEPRO_NO_TELEMETRY === "1") {
44
+ return;
45
+ }
46
+ const data = JSON.stringify({
47
+ event: "run",
48
+ v: getVersion(),
49
+ lang: payload.language,
50
+ files: fileBucket(payload.fileCount),
51
+ os: process.platform,
52
+ node: process.version,
53
+ ts: new Date().toISOString(),
54
+ });
55
+ try {
56
+ const req = https.request({
57
+ hostname: ENDPOINT_HOST,
58
+ path: ENDPOINT_PATH,
59
+ method: "POST",
60
+ headers: {
61
+ "Content-Type": "application/json",
62
+ "Content-Length": Buffer.byteLength(data),
63
+ },
64
+ timeout: TIMEOUT_MS,
65
+ }, () => { } // ignore response
66
+ );
67
+ req.on("error", () => { }); // silent fail — never block the user
68
+ req.on("timeout", () => req.destroy());
69
+ req.write(data);
70
+ req.end();
71
+ }
72
+ catch {
73
+ // Never throw — telemetry must never affect the user experience
74
+ }
75
+ }
@@ -840,11 +840,18 @@ function riskRows(risks, graph) {
840
840
  });
841
841
  }
842
842
  function frameworkLabel(graph) {
843
- const frameworks = graph.nodes
843
+ const allFrameworks = graph.nodes
844
844
  .filter((n) => n.kind === "Framework")
845
845
  .map((n) => n.title || n.external_id.replace(/^framework:/, ""))
846
846
  .sort();
847
- return frameworks.length ? frameworks.slice(0, 4).join(", ") : "Unknown framework";
847
+ if (allFrameworks.length)
848
+ return allFrameworks.slice(0, 4).join(", ");
849
+ // Fallback: check for runtime framework nodes
850
+ const runtimeNodes = graph.nodes
851
+ .filter((n) => n.kind === "Package" && /react.native|expo|next|angular|vue|express/i.test(n.title || n.external_id))
852
+ .map((n) => n.title || n.external_id)
853
+ .sort();
854
+ return runtimeNodes.length ? runtimeNodes.slice(0, 3).join(", ") : "Unknown framework";
848
855
  }
849
856
  export function buildBehaviorReportData(graph, ledger, opts = {}) {
850
857
  const { rows } = buildRtm(graph, ledger);
@@ -866,7 +866,7 @@ if(cf&&cf.flows.length){
866
866
 
867
867
  // Platform CTA: after flows
868
868
  const flowCta=el("div","paywall",
869
- \`<div class="paywall-num">\\\${D.flows.length} flows mapped locally</div>
869
+ \`<div class="paywall-num">\${D.flows.length} flows mapped locally</div>
870
870
  <div class="paywall-txt">Track flow regressions across commits, correlate with production incidents, and set merge gates on OrangePro Platform.</div>
871
871
  <a class="paywall-btn" href="https://orangepro.ai/get-started" target="_blank">Unlock Full Analysis &rarr;</a>\`);
872
872
  fl.after(flowCta);
@@ -879,7 +879,7 @@ if(D.viewMeta){
879
879
  }
880
880
  // Platform CTA: top banner in risk panel
881
881
  const riskTopBanner=el("div","platform-top-banner",
882
- \`<span class="platform-top-banner-text">Local scan shows <b>\\\${D.risks.length}</b> priority gaps. Full ranked list, incident correlation, and CI merge gate on Platform.</span>
882
+ \`<span class="platform-top-banner-text">Local scan shows <b>\${D.risks.length}</b> priority gaps. Full ranked list, incident correlation, and CI merge gate on Platform.</span>
883
883
  <a class="platform-footer-btn" href="https://orangepro.ai/get-started" target="_blank">Unlock Full Analysis &rarr;</a>\`);
884
884
  riskList.before(riskTopBanner);
885
885
  const generatedRiskCount=D.risks.filter(r=>r.generatedTests&&r.generatedTests.length).length;
@@ -982,7 +982,7 @@ const pf=document.getElementById("platform-cta-footer");
982
982
  if(pf&&D.viewMeta&&D.viewMeta.risks){
983
983
  const rm=D.viewMeta.risks;
984
984
  pf.querySelector(".platform-footer-text").innerHTML=
985
- \`Showing top <b>\\\${rm.shown}</b> of <b>\\\${rm.scored.toLocaleString()}</b> scored behaviors. Full risk ranking + incident correlation + CI gate on Platform.\`;
985
+ \`Showing top <b>\${rm.shown}</b> of <b>\${rm.scored.toLocaleString()}</b> scored behaviors. Full risk ranking + incident correlation + CI gate on Platform.\`;
986
986
  }
987
987
  })();
988
988
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangepro/orangepro-mcp",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
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",