@orangepro/orangepro-mcp 0.2.11 → 0.2.13
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 +19 -2
- package/dist/local/generate/generator.js +55 -5
- package/dist/local/operations.js +25 -6
- package/dist/local/rtm.js +15 -0
- package/dist/local/score/risk.js +40 -8
- package/dist/local/viz/behaviorReportHtml.js +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
|
-
# OrangePro
|
|
1
|
+
# OrangePro MCP
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@orangepro/mcp-server )
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/@orangepro/mcp-server )
|
|
6
|
+
[](https://glama.ai/mcp/servers/OrangeproAI/orangepro-mcp )
|
|
2
7
|
|
|
3
8
|
**Find the behaviors your tests miss. Generate grounded tests that actually run.**
|
|
4
9
|
|
|
5
10
|
`opro` builds a knowledge graph from your local checkout, maps every behavior in your code, shows which ones are tested and which aren't, and generates integration-level tests grounded in real symbols — not hallucinated imports. It runs as a CLI and a local stdio MCP server.
|
|
6
11
|
|
|
12
|
+
Once you run the mcp server against a repo, you can get behavior-coverage/html
|
|
13
|
+
**<a href="https://orangeproai.github.io/orangepro-mcp/twenty-crm-behavior-coverage.html" target="_blank">→ Live example: Twenty CRM behavior coverage report</a>**
|
|
14
|
+
|
|
15
|
+
<img width="895" alt="OrangePro system map — entry lanes, services, evidence tiers" src="https://github.com/user-attachments/assets/1ceba779-e0ec-4ec1-99ce-001bc3589b42" />
|
|
16
|
+
|
|
17
|
+
*Fig 1: System map — entry lanes (GraphQL, HTTP, Jobs) flowing into services, sized by traffic, colored by evidence tier, red-ringed by risk.*
|
|
18
|
+
|
|
19
|
+
<img width="818" height="1284" alt="Screenshot 2026-07-30 at 4 38 35 PM" src="https://github.com/user-attachments/assets/1c41475d-be2a-4c6d-ace6-c6546573bf97" />
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
*Fig 2: Priority gaps — top 20 unproven behaviors ranked by blast radius, with generated test drafts and applicable testing categories.*
|
|
23
|
+
|
|
24
|
+
---
|
|
7
25
|
Install the target repository's dependencies first, then run OrangePro from that repository:
|
|
8
26
|
|
|
9
27
|
```bash
|
|
@@ -35,7 +53,6 @@ orangepro_generated/ ← contained generated tests; existing source file
|
|
|
35
53
|
The report opens on a **system map** of your repo — entry lanes (GraphQL/HTTP/Jobs) flowing into the services they reach, sized by traffic, colored by evidence tier, risk-ringed — identical on every run. Each completed rerun shows a **delta banner** against the previous completed run. The report discloses when bounded path enumeration prunes additional branch expansions. Every behavior gets a plain-English description; every top risk gets a deterministic context line and a state-aware next step.
|
|
36
54
|
|
|
37
55
|
Run `opro export` when you want a machine-readable evidence pack.
|
|
38
|
-
<img width="895" height="960" alt="Screenshot 2026-07-08 at 1 18 01 AM" src="https://github.com/user-attachments/assets/73b8a812-2eab-43a1-8aed-4289545e630b" />
|
|
39
56
|
|
|
40
57
|
---
|
|
41
58
|
|
|
@@ -11,6 +11,7 @@ import { GENERATED_DIR } from "./runHints.js";
|
|
|
11
11
|
import { behaviorNodes, findNode, nodesByKind, priorityRank } from "../graph/factories.js";
|
|
12
12
|
import { structurallyUnconfirmable } from "../graph/confirmable.js";
|
|
13
13
|
import { shortHash } from "../util/hash.js";
|
|
14
|
+
import { targetFingerprint } from "../ledger.js";
|
|
14
15
|
import { reportProgress } from "../util/progress.js";
|
|
15
16
|
import { redactSecrets, redactSecretsPreservingLineCount } from "../util/redact.js";
|
|
16
17
|
import { systemClock } from "../util/time.js";
|
|
@@ -2042,6 +2043,25 @@ function planGroundedBuckets(graph, targets, framework, fileReader, limit, expli
|
|
|
2042
2043
|
}
|
|
2043
2044
|
return plan;
|
|
2044
2045
|
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Previously generated drafts that may be reused verbatim for `behavior`: the
|
|
2048
|
+
* newest run's RUNNABLE, non-stale drafts recorded against the target's CURRENT
|
|
2049
|
+
* fingerprint. A missing fingerprint (unanalyzed file, unparseable id) pins
|
|
2050
|
+
* nothing — the conservative direction is to regenerate, never to serve a draft
|
|
2051
|
+
* whose target we cannot prove is unchanged.
|
|
2052
|
+
*/
|
|
2053
|
+
function pinnedDraftsFor(graph, behavior, fingerprint) {
|
|
2054
|
+
if (!fingerprint || behavior.kind !== "CodeSymbol")
|
|
2055
|
+
return [];
|
|
2056
|
+
const matches = (graph.generated_tests ?? []).filter((t) => t.target_symbol_external_id === behavior.external_id &&
|
|
2057
|
+
t.target_fingerprint === fingerprint &&
|
|
2058
|
+
t.runnable !== false &&
|
|
2059
|
+
t.stale !== true);
|
|
2060
|
+
if (matches.length === 0)
|
|
2061
|
+
return [];
|
|
2062
|
+
const latestRunId = matches[matches.length - 1].run_id;
|
|
2063
|
+
return matches.filter((t) => t.run_id === latestRunId);
|
|
2064
|
+
}
|
|
2045
2065
|
/**
|
|
2046
2066
|
* Select the same target behaviors + gathered context for the A/B comparison, so
|
|
2047
2067
|
* both arms target identical behaviors and differ ONLY in whether the KG evidence
|
|
@@ -2067,7 +2087,7 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2067
2087
|
const { targets, warnings } = selectTargets(graph, opts);
|
|
2068
2088
|
const framework = pickFramework(graph, opts, targets, fileReader);
|
|
2069
2089
|
const runSelection = targetsForFramework(graph, targets, framework);
|
|
2070
|
-
|
|
2090
|
+
let runTargets = runSelection.targets;
|
|
2071
2091
|
warnings.push(...runSelection.warnings);
|
|
2072
2092
|
const systemPrompt = opts.systemPrompt ?? buildSystemPrompt();
|
|
2073
2093
|
const promptVersion = inputMode === "graph_grounded" && opts.prompt_version === "v5" ? PROMPT_VERSION_V5 : PROMPT_VERSION;
|
|
@@ -2076,6 +2096,24 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2076
2096
|
const run_id = `local-gen-${runSeed}`;
|
|
2077
2097
|
const generated = [];
|
|
2078
2098
|
const missing = [];
|
|
2099
|
+
// Pin: a target whose code is byte-identical to what a previously RUNNABLE draft
|
|
2100
|
+
// was written against gets that draft back verbatim — no model call, no new
|
|
2101
|
+
// persisted record, no drift in what the agent is told to run. Regeneration
|
|
2102
|
+
// resumes the moment the target's fingerprint changes.
|
|
2103
|
+
const fingerprintOf = (behavior) => behavior.kind === "CodeSymbol" ? targetFingerprint(graph, behavior.external_id) : undefined;
|
|
2104
|
+
if (opts.pin_unchanged && inputMode === "graph_grounded") {
|
|
2105
|
+
const remaining = [];
|
|
2106
|
+
for (const behavior of runTargets) {
|
|
2107
|
+
const pinned = pinnedDraftsFor(graph, behavior, fingerprintOf(behavior));
|
|
2108
|
+
if (pinned.length === 0 || generated.length >= limit) {
|
|
2109
|
+
remaining.push(behavior);
|
|
2110
|
+
continue;
|
|
2111
|
+
}
|
|
2112
|
+
generated.push(...pinned.slice(0, limit - generated.length).map((t) => ({ ...t, pinned: true })));
|
|
2113
|
+
warnings.push(`Reused ${pinned.length} pinned draft(s) for "${behavior.title || behavior.external_id}" — the target is unchanged since they were generated (no model call).`);
|
|
2114
|
+
}
|
|
2115
|
+
runTargets = remaining;
|
|
2116
|
+
}
|
|
2079
2117
|
if (inputMode === "raw_prompt") {
|
|
2080
2118
|
// Internal baseline only: broad sampling, one raw test per target (no buckets).
|
|
2081
2119
|
// Cap ATTEMPTS (not successes): with empty completions skipped below, capping
|
|
@@ -2439,6 +2477,7 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2439
2477
|
grounding: { entity_ids: gc.entityIds, source_refs: [], weak_relationships_used: [] },
|
|
2440
2478
|
weak_evidence_used: false,
|
|
2441
2479
|
target_symbol_external_id: behavior.external_id,
|
|
2480
|
+
...(fingerprintOf(behavior) ? { target_fingerprint: fingerprintOf(behavior) } : {}),
|
|
2442
2481
|
runnable: false,
|
|
2443
2482
|
unresolved_reason: reason
|
|
2444
2483
|
});
|
|
@@ -2460,7 +2499,9 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2460
2499
|
import_provenance
|
|
2461
2500
|
},
|
|
2462
2501
|
weak_evidence_used: gc.weakUsed.length > 0,
|
|
2463
|
-
...(behavior.kind === "CodeSymbol"
|
|
2502
|
+
...(behavior.kind === "CodeSymbol"
|
|
2503
|
+
? { target_symbol_external_id: behavior.external_id, ...(fingerprintOf(behavior) ? { target_fingerprint: fingerprintOf(behavior) } : {}) }
|
|
2504
|
+
: {}),
|
|
2464
2505
|
runnable: true
|
|
2465
2506
|
});
|
|
2466
2507
|
}
|
|
@@ -2631,7 +2672,12 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2631
2672
|
import_provenance
|
|
2632
2673
|
},
|
|
2633
2674
|
weak_evidence_used: item.weakUsed.length > 0,
|
|
2634
|
-
...(item.behavior.kind === "CodeSymbol"
|
|
2675
|
+
...(item.behavior.kind === "CodeSymbol"
|
|
2676
|
+
? {
|
|
2677
|
+
target_symbol_external_id: item.behavior.external_id,
|
|
2678
|
+
...(fingerprintOf(item.behavior) ? { target_fingerprint: fingerprintOf(item.behavior) } : {})
|
|
2679
|
+
}
|
|
2680
|
+
: {}),
|
|
2635
2681
|
runnable,
|
|
2636
2682
|
...(unresolved_reason ? { unresolved_reason } : {})
|
|
2637
2683
|
});
|
|
@@ -2640,7 +2686,11 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2640
2686
|
if (targets.length === 0) {
|
|
2641
2687
|
warnings.push("No behavior anchors available to target. Add requirements/templates or analyze a path with tests.");
|
|
2642
2688
|
}
|
|
2643
|
-
|
|
2689
|
+
// A pinned draft belongs to the run that FIRST produced it: it must not be
|
|
2690
|
+
// re-listed under this run_id (nor re-persisted), or every pin-only generation
|
|
2691
|
+
// would duplicate the same draft in the graph.
|
|
2692
|
+
const freshTests = generated.filter((t) => !t.pinned);
|
|
2693
|
+
const run = freshTests.length
|
|
2644
2694
|
? {
|
|
2645
2695
|
run_id,
|
|
2646
2696
|
model_provider: provider.providerName,
|
|
@@ -2648,7 +2698,7 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2648
2698
|
input_mode: inputMode,
|
|
2649
2699
|
prompt_version: promptVersion,
|
|
2650
2700
|
created_at,
|
|
2651
|
-
generated_test_ids:
|
|
2701
|
+
generated_test_ids: freshTests.map((t) => t.id)
|
|
2652
2702
|
}
|
|
2653
2703
|
: null;
|
|
2654
2704
|
return { run, generated_tests: generated, missing_evidence: missing, warnings };
|
package/dist/local/operations.js
CHANGED
|
@@ -41,7 +41,7 @@ import { renderCoverageReport } from "./pack/coverageReport.js";
|
|
|
41
41
|
import { confirmedCoverageByLayer } from "./score/coverage.js";
|
|
42
42
|
import { prepareRuntimeCoverage } from "./analyze/coverageArtifacts.js";
|
|
43
43
|
import { appendLedgerRecord, canReproveLanguage, loadLedger, ledgerStats, proofEdgesFor, reproveTarget, resolveTargetSymbol, targetFingerprint, targetLanguage } from "./ledger.js";
|
|
44
|
-
import { buildRtm, renderRtmCsv, renderRtmMarkdown } from "./rtm.js";
|
|
44
|
+
import { buildRtm, provenSymbolIds, renderRtmCsv, renderRtmMarkdown } from "./rtm.js";
|
|
45
45
|
import { buildProofDoctor, distillProofAttempts, loadProofAttempts, proofAttemptsFresh, writeProofAttempts } from "./proofDoctor.js";
|
|
46
46
|
import { tryScopedReprove } from "./reprove/scoped.js";
|
|
47
47
|
import { resolveContained, toWorkspaceRel } from "./reprove/paths.js";
|
|
@@ -811,7 +811,10 @@ export function opProofDoctor(root) {
|
|
|
811
811
|
export function opGaps(root, opts = {}) {
|
|
812
812
|
const graph = loadGraph(workspacePaths(root).graphPath);
|
|
813
813
|
const gaps = findGaps(graph, opts);
|
|
814
|
-
|
|
814
|
+
// Ledger proofs are invisible to the graph's hard edges; pass them so a type
|
|
815
|
+
// whose every method is Dynamically Proven is suppressed instead of ranked.
|
|
816
|
+
const provenIds = provenSymbolIds(graph, loadLedger(root));
|
|
817
|
+
const topRiskGaps = rankRiskGaps(graph, { limit: opts.limit ?? 10, repoRoot: root, provenIds }).map((gap) => ({
|
|
815
818
|
external_id: gap.id,
|
|
816
819
|
title: gap.title,
|
|
817
820
|
file: gap.file,
|
|
@@ -1598,7 +1601,11 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
|
|
|
1598
1601
|
try {
|
|
1599
1602
|
const graphForGeneration = loadGraph(workspacePaths(root).graphPath);
|
|
1600
1603
|
const generatedTargets = new Set((graphForGeneration.generated_tests ?? []).map((t) => t.target_symbol_external_id).filter((id) => Boolean(id)));
|
|
1601
|
-
const targetIds = rankRiskGaps(graphForGeneration, {
|
|
1604
|
+
const targetIds = rankRiskGaps(graphForGeneration, {
|
|
1605
|
+
repoRoot: root,
|
|
1606
|
+
limit: START_GENERATE_RISK_LIMIT,
|
|
1607
|
+
provenIds: provenSymbolIds(graphForGeneration, loadLedger(root))
|
|
1608
|
+
})
|
|
1602
1609
|
.map((gap) => gap.id)
|
|
1603
1610
|
.filter((id) => !generatedTargets.has(id));
|
|
1604
1611
|
if (targetIds.length) {
|
|
@@ -1792,12 +1799,24 @@ export async function opGenerate(root, opts = {}, deps = defaultDeps()) {
|
|
|
1792
1799
|
};
|
|
1793
1800
|
}
|
|
1794
1801
|
const reader = fileReaderFor(graph.workspace.root);
|
|
1795
|
-
const result = await generateTests(graph, {
|
|
1796
|
-
|
|
1802
|
+
const result = await generateTests(graph, {
|
|
1803
|
+
target_ids: opts.target_ids,
|
|
1804
|
+
framework: opts.framework,
|
|
1805
|
+
limit: opts.limit,
|
|
1806
|
+
input_mode: opts.input_mode,
|
|
1807
|
+
prompt_version: opts.prompt_version,
|
|
1808
|
+
// Persisting lane: a runnable draft for an unchanged target is reused as-is
|
|
1809
|
+
// rather than re-bought from the model on every run.
|
|
1810
|
+
pin_unchanged: opts.pin_unchanged ?? true
|
|
1811
|
+
}, provider, reader, deps.clock);
|
|
1812
|
+
// Pinned drafts are already IN graph.generated_tests — appending them again
|
|
1813
|
+
// would duplicate a draft on every generation of an unchanged target.
|
|
1814
|
+
const freshTests = result.generated_tests.filter((t) => !t.pinned);
|
|
1815
|
+
if (result.run && freshTests.length) {
|
|
1797
1816
|
const next = {
|
|
1798
1817
|
...graph,
|
|
1799
1818
|
generation_runs: [...graph.generation_runs, result.run],
|
|
1800
|
-
generated_tests: [...graph.generated_tests, ...
|
|
1819
|
+
generated_tests: [...graph.generated_tests, ...freshTests],
|
|
1801
1820
|
updated_at: deps.clock()
|
|
1802
1821
|
};
|
|
1803
1822
|
saveGraph(paths.graphPath, next);
|
package/dist/local/rtm.js
CHANGED
|
@@ -47,6 +47,21 @@ export function buildRtm(graph, ledger, opts = {}) {
|
|
|
47
47
|
const rows = opts.limit && opts.limit > 0 ? filteredRows.slice(0, opts.limit) : filteredRows;
|
|
48
48
|
return { summary: summarizeRows(baseRows, unionRows), rows, ...(opts.scope ? { scope: opts.scope } : {}) };
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* CodeSymbol ids the RTM would call Dynamically Proven — the SAME selection
|
|
52
|
+
* buildRtm uses (`selectLedgerBySymbol(...).proven`), so proof judgment has one
|
|
53
|
+
* definition. Consumers outside the RTM (risk ranking's container suppression)
|
|
54
|
+
* need it because a proof leaves no graph edge and its target frequently sits
|
|
55
|
+
* outside the deterministic denominator.
|
|
56
|
+
*/
|
|
57
|
+
export function provenSymbolIds(graph, ledger) {
|
|
58
|
+
const out = new Set();
|
|
59
|
+
for (const [symbol, selected] of selectLedgerBySymbol(ledger, graph)) {
|
|
60
|
+
if (selected.proven)
|
|
61
|
+
out.add(symbol);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
50
65
|
function inScope(node, targetSet, fileSet) {
|
|
51
66
|
if (!targetSet && !fileSet)
|
|
52
67
|
return true;
|
package/dist/local/score/risk.js
CHANGED
|
@@ -11,7 +11,37 @@ function symbolFile(n) {
|
|
|
11
11
|
function symbolTitle(n) {
|
|
12
12
|
return n.title ?? n.external_id.split("#")[1] ?? n.external_id;
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Structural container → member children, read from the analyzer's own
|
|
16
|
+
* `properties.member_of` (the same signal src/local/reprove/scoped.ts uses).
|
|
17
|
+
* Id-prefix matching cannot do this job: `sym:f.ts#a.b` is a child of
|
|
18
|
+
* `sym:f.ts#a` only by string luck, and a dotted symbol name with no container
|
|
19
|
+
* node would be mis-parented. A container is resolved inside the child's OWN
|
|
20
|
+
* file: a same-named symbol elsewhere is a different thing, and mis-parenting
|
|
21
|
+
* here would suppress a genuine gap.
|
|
22
|
+
*/
|
|
23
|
+
function containerChildren(graph) {
|
|
24
|
+
const symbols = graph.nodes.filter((n) => n.kind === "CodeSymbol");
|
|
25
|
+
const idByFileTitle = new Map();
|
|
26
|
+
for (const n of symbols)
|
|
27
|
+
idByFileTitle.set(`${symbolFile(n)}#${symbolTitle(n)}`, n.external_id);
|
|
28
|
+
const out = new Map();
|
|
29
|
+
for (const n of symbols) {
|
|
30
|
+
const memberOf = n.properties.member_of;
|
|
31
|
+
if (typeof memberOf !== "string" || memberOf === "")
|
|
32
|
+
continue;
|
|
33
|
+
const containerId = idByFileTitle.get(`${symbolFile(n)}#${memberOf}`);
|
|
34
|
+
if (!containerId || containerId === n.external_id)
|
|
35
|
+
continue;
|
|
36
|
+
const list = out.get(containerId);
|
|
37
|
+
if (list)
|
|
38
|
+
list.push(n.external_id);
|
|
39
|
+
else
|
|
40
|
+
out.set(containerId, [n.external_id]);
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
function confirmedBehaviorIds(graph, provenIds) {
|
|
15
45
|
const ids = new Set();
|
|
16
46
|
const nodeKinds = new Map(graph.nodes.map((n) => [n.external_id, n.kind]));
|
|
17
47
|
for (const e of graph.edges) {
|
|
@@ -27,13 +57,15 @@ function confirmedBehaviorIds(graph) {
|
|
|
27
57
|
// A container type whose every method child is confirmed has no distinct
|
|
28
58
|
// untested surface left — suppress it from the gap ranking rather than
|
|
29
59
|
// listing it as an unlinked candidate above its own proven methods.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
60
|
+
// "Confirmed" here means a hard static link OR a current ledger proof: the
|
|
61
|
+
// proof lane leaves no graph edge and usually sits outside the denominator,
|
|
62
|
+
// so a hard-edge-only check never fires for a dynamically proven type.
|
|
63
|
+
const eligible = new Set(graph.nodes.filter((n) => n.kind === "CodeSymbol" && n.denominator_eligible === true).map((n) => n.external_id));
|
|
64
|
+
for (const [containerId, children] of containerChildren(graph)) {
|
|
65
|
+
if (ids.has(containerId) || !eligible.has(containerId))
|
|
33
66
|
continue;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
ids.add(id);
|
|
67
|
+
if (children.every((c) => ids.has(c) || provenIds?.has(c) === true))
|
|
68
|
+
ids.add(containerId);
|
|
37
69
|
}
|
|
38
70
|
return ids;
|
|
39
71
|
}
|
|
@@ -325,7 +357,7 @@ function candidateSignalIds(graph, candidateIds) {
|
|
|
325
357
|
}
|
|
326
358
|
export function rankRiskGaps(graph, opts = {}) {
|
|
327
359
|
const limit = opts.limit ?? 20;
|
|
328
|
-
const confirmed = confirmedBehaviorIds(graph);
|
|
360
|
+
const confirmed = confirmedBehaviorIds(graph, opts.provenIds);
|
|
329
361
|
const symbols = graph.nodes.filter((n) => n.kind === "CodeSymbol" && n.denominator_eligible === true && !n.stale && !confirmed.has(n.external_id));
|
|
330
362
|
const symbolIds = new Set(symbols.map((s) => s.external_id));
|
|
331
363
|
const symbolsByFile = new Map();
|
|
@@ -758,11 +758,11 @@ function renderRisks(){
|
|
|
758
758
|
hiddenGeneratedFlows
|
|
759
759
|
? \`<div class="paywall-num">\${hiddenGeneratedFlows} more flows with tests</div>
|
|
760
760
|
<div class="paywall-txt">OrangePro accepted \${D.generatedTotal} runnable generated test\${D.generatedTotal===1?"":"s"} across \${generatedRiskCount} high-risk flow\${generatedRiskCount===1?"":"s"}. Use the “Flows with tests” filter to review them first.</div>
|
|
761
|
-
<a class="paywall-btn" href="https://
|
|
761
|
+
<a class="paywall-btn" href="https://platform.orangepro.ai/" target="_blank">View all on OrangePro Platform →</a>\`
|
|
762
762
|
: remainingRiskFlows
|
|
763
763
|
? \`<div class="paywall-num">\${remainingRiskFlows} high-risk flows left</div>
|
|
764
764
|
<div class="paywall-txt">The local MCP accepted \${D.generatedTotal} runnable generated test\${D.generatedTotal===1?"":"s"} across \${generatedRiskCount} high-risk flow\${generatedRiskCount===1?"":"s"}. Generate the remaining high-risk flow tests on OrangePro Platform.</div>
|
|
765
|
-
<a class="paywall-btn" href="https://
|
|
765
|
+
<a class="paywall-btn" href="https://platform.orangepro.ai/" target="_blank">Generate remaining tests on Platform →</a>\`
|
|
766
766
|
: \`<div class="paywall-num">All generated tests are shown</div>
|
|
767
767
|
<div class="paywall-txt">OrangePro generated tests for every high-risk flow in this report, and every generated test is visible here.</div>\`));
|
|
768
768
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangepro/orangepro-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
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",
|
|
@@ -131,4 +131,4 @@
|
|
|
131
131
|
"unplugin-swc": "^1.5.9",
|
|
132
132
|
"vitest": "^3.2.0"
|
|
133
133
|
}
|
|
134
|
-
}
|
|
134
|
+
}
|