@orangepro/orangepro-mcp 0.2.23 → 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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangepro/orangepro-mcp",
3
- "version": "0.2.23",
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",