@orangepro/orangepro-mcp 0.2.6 → 0.2.7
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
CHANGED
|
@@ -32,7 +32,7 @@ The command writes:
|
|
|
32
32
|
orangepro_generated/ ← contained generated tests; existing source files are untouched
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
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 rerun shows a **delta banner
|
|
35
|
+
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
36
|
|
|
37
37
|
Run `opro export` when you want a machine-readable evidence pack.
|
|
38
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" />
|
|
@@ -189,7 +189,7 @@ If the environment can't run tests yet (dependencies not installed, runner uncon
|
|
|
189
189
|
|
|
190
190
|
## Test categories
|
|
191
191
|
|
|
192
|
-
Generation is evidence-gated. A category is produced only when the graph has supporting evidence — never padded with generic filler. These are the local generation buckets. The report additionally shows each risk's **applicable testing categories** (contract, boundary limits, integration flow, state lifecycle, failure recovery, …), derived deterministically from graph facts
|
|
192
|
+
Generation is evidence-gated. A category is produced only when the graph has supporting evidence — never padded with generic filler. These are the local generation buckets. The report additionally shows each risk's **applicable testing categories** (contract, boundary limits, integration flow, state lifecycle, failure recovery, …), derived deterministically from graph facts. Categories with generated drafts are highlighted as drafts; they are not coverage or proof, and remaining applicable categories stay outlined. Neither taxonomy changes evidence tiers.
|
|
193
193
|
|
|
194
194
|
| Category | What it targets |
|
|
195
195
|
|----------|-----------------|
|
package/dist/local/autoProve.js
CHANGED
|
@@ -794,7 +794,8 @@ export async function autoProve(root, opts, deps) {
|
|
|
794
794
|
dynamicProofRunner: deps.dynamicProofRunner,
|
|
795
795
|
coverageRunner: deps.coverageRunner,
|
|
796
796
|
analyze: deps.analyze,
|
|
797
|
-
aiProvider: deps.aiProvider
|
|
797
|
+
aiProvider: deps.aiProvider,
|
|
798
|
+
suppressBehaviorReportRefresh: deps.suppressBehaviorReportRefresh
|
|
798
799
|
};
|
|
799
800
|
const graph = loadGraph(workspacePaths(root).graphPath);
|
|
800
801
|
const sourceRoot = resolve(graph.workspace.root);
|
|
@@ -3,7 +3,7 @@ import { LOCAL_GRAPH_SCHEMA_VERSION } from "../graph/ontology.js";
|
|
|
3
3
|
import { rankRiskGaps } from "../score/risk.js";
|
|
4
4
|
import { stableId } from "../util/ids.js";
|
|
5
5
|
const DEFAULT_MAX_DEPTH = 8;
|
|
6
|
-
const DEFAULT_MAX_FLOWS_PER_ENTRY =
|
|
6
|
+
const DEFAULT_MAX_FLOWS_PER_ENTRY = 20;
|
|
7
7
|
const DEFAULT_GLOBAL_CAP = 500;
|
|
8
8
|
const HIGH_ROUTE_RE = /payment|refund|checkout|cart|order|auth|login|token|customer|user|tax|fulfillment|ship/i;
|
|
9
9
|
const MUTATION_METHOD_RE = /^(POST|PUT|PATCH|DELETE)\b/i;
|
package/dist/local/operations.js
CHANGED
|
@@ -1327,11 +1327,13 @@ export function opProveLoop(root, opts, deps = defaultDeps()) {
|
|
|
1327
1327
|
// Best-effort report refresh (exactly like ai-flows --apply): a render failure
|
|
1328
1328
|
// must never fail a completed proof — the ledger/cert is already written.
|
|
1329
1329
|
let behaviorCoveragePath;
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1330
|
+
if (!deps.suppressBehaviorReportRefresh) {
|
|
1331
|
+
try {
|
|
1332
|
+
behaviorCoveragePath = opBehaviorCoverageHtml(root, `${WORKSPACE_DIR}/behavior-coverage.html`).behavior_coverage_path;
|
|
1333
|
+
}
|
|
1334
|
+
catch {
|
|
1335
|
+
// ponytail: swallow — refresh is advisory; DynamicProofResult carries no warnings channel.
|
|
1336
|
+
}
|
|
1335
1337
|
}
|
|
1336
1338
|
return { ...proof, ...(behaviorCoveragePath ? { behavior_coverage_path: behaviorCoveragePath } : {}) };
|
|
1337
1339
|
}
|
|
@@ -1405,11 +1407,13 @@ export async function opAiFlows(root, opts = {}, deps = defaultDeps()) {
|
|
|
1405
1407
|
// Best-effort: a render failure must never fail apply (graph is saved).
|
|
1406
1408
|
const warnings = [...result.warnings];
|
|
1407
1409
|
let behaviorCoveragePath;
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1410
|
+
if (!deps.suppressBehaviorReportRefresh) {
|
|
1411
|
+
try {
|
|
1412
|
+
behaviorCoveragePath = opBehaviorCoverageHtml(root, `${WORKSPACE_DIR}/behavior-coverage.html`).behavior_coverage_path;
|
|
1413
|
+
}
|
|
1414
|
+
catch (error) {
|
|
1415
|
+
warnings.push(`behavior coverage view not written: ${error instanceof Error ? error.message : String(error)}`);
|
|
1416
|
+
}
|
|
1413
1417
|
}
|
|
1414
1418
|
return { ...result, warnings, ...(behaviorCoveragePath ? { behavior_coverage_path: behaviorCoveragePath } : {}) };
|
|
1415
1419
|
}
|
|
@@ -1471,11 +1475,7 @@ function writeStartStaticSnapshot(root, baseRef, warnings) {
|
|
|
1471
1475
|
let behaviorCoveragePath;
|
|
1472
1476
|
try {
|
|
1473
1477
|
reportProgress("artifacts: writing static behavior view (proof still running)", { current: 4, total: 8 });
|
|
1474
|
-
behaviorCoveragePath = opBehaviorCoverageHtml(root, `${WORKSPACE_DIR}/behavior-coverage.html`, {
|
|
1475
|
-
attempted: 0,
|
|
1476
|
-
proven: 0,
|
|
1477
|
-
needsSetup: []
|
|
1478
|
-
}).behavior_coverage_path;
|
|
1478
|
+
behaviorCoveragePath = opBehaviorCoverageHtml(root, `${WORKSPACE_DIR}/behavior-coverage.html`, { attempted: 0, proven: 0, needsSetup: [] }, { persistBaseline: false }).behavior_coverage_path;
|
|
1479
1479
|
}
|
|
1480
1480
|
catch (error) {
|
|
1481
1481
|
warnings.push(`static behavior view not written: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1493,7 +1493,9 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
|
|
|
1493
1493
|
const providerOpts = startProviderOverride(root, opts);
|
|
1494
1494
|
const scanRoot = opts.source ? resolve(opts.source) : resolve(root);
|
|
1495
1495
|
const providerEnv = loadProviderEnv([root, scanRoot], deps.env);
|
|
1496
|
-
|
|
1496
|
+
// `start` writes advisory/intermediate views before the final report. Keep
|
|
1497
|
+
// those helpers from advancing report-baseline.json mid-run.
|
|
1498
|
+
const providerDeps = { ...deps, env: providerEnv, suppressBehaviorReportRefresh: true };
|
|
1497
1499
|
const scope = summarizeCorpusScope(scanRoot);
|
|
1498
1500
|
reportProgress(`start: preflight found ${scope.files.toLocaleString()} source/doc file(s)`, { current: 1, total: 8 });
|
|
1499
1501
|
reportProgress("start: running deterministic analysis", { current: 2, total: 8 });
|
|
@@ -1995,7 +1997,7 @@ export function opGraphHtml(root, outputPath = "orangepro-graph.html") {
|
|
|
1995
1997
|
return { graph_html_path: htmlPath };
|
|
1996
1998
|
}
|
|
1997
1999
|
/** Write the self-contained offline behavior-coverage view (deterministic, metadata only). */
|
|
1998
|
-
export function opBehaviorCoverageHtml(root, outputPath = "orangepro-behavior-coverage.html", dynamicProof) {
|
|
2000
|
+
export function opBehaviorCoverageHtml(root, outputPath = "orangepro-behavior-coverage.html", dynamicProof, options = {}) {
|
|
1999
2001
|
const graph = loadGraph(workspacePaths(root).graphPath);
|
|
2000
2002
|
// Standalone regens have no this-run outcome: fall back to the persisted
|
|
2001
2003
|
// proof-attempts sidecar ONLY when it anchors to the current graph+commit
|
|
@@ -2017,11 +2019,13 @@ export function opBehaviorCoverageHtml(root, outputPath = "orangepro-behavior-co
|
|
|
2017
2019
|
const html = renderBehaviorReport(data);
|
|
2018
2020
|
const htmlPath = resolve(root, outputPath);
|
|
2019
2021
|
writeFileSync(htmlPath, html, "utf8");
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2022
|
+
if (options.persistBaseline !== false) {
|
|
2023
|
+
try {
|
|
2024
|
+
writeFileSync(baselinePath, JSON.stringify(reportBaselineOf(data, new Date().toISOString())), "utf8");
|
|
2025
|
+
}
|
|
2026
|
+
catch {
|
|
2027
|
+
// baseline write is advisory
|
|
2028
|
+
}
|
|
2025
2029
|
}
|
|
2026
2030
|
return { behavior_coverage_path: htmlPath };
|
|
2027
2031
|
}
|
|
@@ -737,16 +737,16 @@ function riskRows(risks, graph) {
|
|
|
737
737
|
const generatedTests = riskGeneratedTests(graph, risk, riskIds, firstRowForFile.get(risk.file) === risk.id);
|
|
738
738
|
const verb = methodMatch?.[1]?.toUpperCase() ?? "BEHAVIOR";
|
|
739
739
|
const path = methodMatch?.[2] ?? risk.title;
|
|
740
|
-
const
|
|
740
|
+
const generatedCategories = [...new Set([
|
|
741
741
|
...generatedTests.map((t) => (t.bucket ? BUCKET_TO_CONCERN[t.bucket] : undefined)),
|
|
742
|
-
// An integration/api/e2e-layer
|
|
743
|
-
//
|
|
742
|
+
// An integration/api/e2e-layer draft targets integration_flow. This
|
|
743
|
+
// remains generation metadata until dynamic proof closes.
|
|
744
744
|
...generatedTests.map((t) => (t.concern === "integration" || t.concern === "api" || t.concern === "e2e" ? "integration_flow" : undefined))
|
|
745
745
|
].filter((c) => Boolean(c)))];
|
|
746
746
|
return {
|
|
747
747
|
generatedTests,
|
|
748
748
|
applicableCategories: riskApplicableConcerns(risk, verb),
|
|
749
|
-
|
|
749
|
+
generatedCategories,
|
|
750
750
|
verb,
|
|
751
751
|
path,
|
|
752
752
|
todo: riskTodo(risk, verb, path, generatedTests)
|
|
@@ -230,9 +230,8 @@ nav.tabs{display:flex;gap:2px;margin:18px 0 0;border-bottom:1px solid var(--bd)}
|
|
|
230
230
|
.cat-strip-lbl{font-size:9.5px;color:var(--muted);font-weight:600;letter-spacing:.04em;text-transform:uppercase;margin-bottom:6px}
|
|
231
231
|
.cat-pills{display:flex;flex-wrap:wrap;gap:5px}
|
|
232
232
|
.cat-pill{font-size:10px;padding:3px 8px;border-radius:4px;font-weight:600;letter-spacing:.01em;display:inline-flex;align-items:center;gap:4px}
|
|
233
|
-
.cat-pill.cp-
|
|
234
|
-
.cat-pill.cp-
|
|
235
|
-
.cat-pill.cp-locked::after{content:"\\1F512";font-size:8px}
|
|
233
|
+
.cat-pill.cp-drafted{background:var(--obg);border:1px solid var(--obd);color:var(--orange)}
|
|
234
|
+
.cat-pill.cp-open{background:transparent;border:1px dashed var(--bd2);color:var(--faint)}
|
|
236
235
|
/* PAYWALL CTA */
|
|
237
236
|
.paywall{background:linear-gradient(135deg,rgba(240,136,62,.08),rgba(240,136,62,.02));border:1px dashed var(--obd);border-radius:9px;padding:14px 16px;margin-top:14px;text-align:center}
|
|
238
237
|
.paywall-num{font-size:18px;font-weight:750;color:var(--orange);font-variant-numeric:tabular-nums}
|
|
@@ -443,7 +442,7 @@ const D=window.DATA,$=(s)=>document.querySelector(s);
|
|
|
443
442
|
bc.innerHTML='';
|
|
444
443
|
var backBtn=document.createElement("button");
|
|
445
444
|
backBtn.className="sm-crumb-btn";
|
|
446
|
-
backBtn.textContent="
|
|
445
|
+
backBtn.textContent="← Back to system map";
|
|
447
446
|
backBtn.addEventListener("click",function(){
|
|
448
447
|
if(search){search.value="";search.dispatchEvent(new Event("input"));}
|
|
449
448
|
bc.remove();
|
|
@@ -453,7 +452,7 @@ const D=window.DATA,$=(s)=>document.querySelector(s);
|
|
|
453
452
|
});
|
|
454
453
|
var clearBtn=document.createElement("button");
|
|
455
454
|
clearBtn.className="sm-crumb-btn";
|
|
456
|
-
clearBtn.textContent="
|
|
455
|
+
clearBtn.textContent="✕ Clear filter “"+svc+"”";
|
|
457
456
|
clearBtn.addEventListener("click",function(){
|
|
458
457
|
if(search){search.value="";search.dispatchEvent(new Event("input"));}
|
|
459
458
|
bc.remove();
|
|
@@ -523,7 +522,7 @@ function describeBehavior(b){
|
|
|
523
522
|
:b.tier==="assoc"?"A real test imports and calls it, but no mutation proof exists yet."
|
|
524
523
|
:b.tier==="candidate"?"Only a lexically similar test file was found — an unconfirmed lead, not evidence."
|
|
525
524
|
:"No test signal of any kind was found for it.";
|
|
526
|
-
const s1="
|
|
525
|
+
const s1="“"+h.words+"”"+(h.owner?" on "+h.owner:"")+" — a behavior in "+(mod||"this repo")+".";
|
|
527
526
|
const s2=reach.length?"A user or system reaches it via "+reach.join(" and ")+(reach.length>=2?" (and possibly more)":"")+".":(b.reachable?"It appears inside at least one statically traced call chain.":"No entry-anchored call chain reaches it in the static flow set.");
|
|
528
527
|
return s1+" "+s2+" "+tierTxt;
|
|
529
528
|
}
|
|
@@ -549,14 +548,14 @@ function renderBeh(){
|
|
|
549
548
|
const rows=D.behaviors.filter(b=>(!activeGrp||b.group===activeGrp)&&(!activeTier||tierOf(b).cls===activeTier)&&(!searchQ||(b.sig+" "+b.file).toLowerCase().includes(searchQ)));
|
|
550
549
|
if(rows.length===0){
|
|
551
550
|
const parts=[];
|
|
552
|
-
if(searchQ)parts.push('search
|
|
551
|
+
if(searchQ)parts.push('search “'+searchQ+'”');
|
|
553
552
|
if(activeTier)parts.push('the selected evidence tier');
|
|
554
|
-
if(activeGrp)parts.push('package
|
|
553
|
+
if(activeGrp)parts.push('package “'+activeGrp+'”');
|
|
555
554
|
const why=parts.length?parts.join(' + '):'the current filters';
|
|
556
555
|
const box=el("div","beh-empty",
|
|
557
|
-
'<p>No behaviors match '+esc(why)+'. Filters combine
|
|
556
|
+
'<p>No behaviors match '+esc(why)+'. Filters combine — clearing one usually brings results back.</p>');
|
|
558
557
|
if(searchQ){
|
|
559
|
-
const b1=el("button","sm-crumb-btn","
|
|
558
|
+
const b1=el("button","sm-crumb-btn","✕ Clear search");
|
|
560
559
|
b1.onclick=function(){searchEl.value="";searchQ="";renderBeh();};
|
|
561
560
|
box.append(b1);
|
|
562
561
|
}
|
|
@@ -697,15 +696,16 @@ function riskCardHtml(r){
|
|
|
697
696
|
// category strip: show which concern categories apply to this flow
|
|
698
697
|
let catHtml='';
|
|
699
698
|
if(r.applicableCategories&&r.applicableCategories.length){
|
|
700
|
-
const
|
|
699
|
+
const draftedConcerns=new Set(r.generatedCategories||[]);
|
|
701
700
|
const pills=r.applicableCategories.map(c=>{
|
|
702
|
-
const
|
|
701
|
+
const drafted=draftedConcerns.has(c);
|
|
703
702
|
const label=c.replace(/_/g,' ');
|
|
704
|
-
|
|
703
|
+
const hint=drafted?'Generated draft attached; not coverage or proof':'Applicable category without a generated draft';
|
|
704
|
+
return \`<span class="cat-pill \${drafted?'cp-drafted':'cp-open'}" title="\${hint}">\${esc(label)}</span>\`;
|
|
705
705
|
}).join('');
|
|
706
|
-
const shownN=r.applicableCategories.filter(c=>
|
|
706
|
+
const shownN=r.applicableCategories.filter(c=>draftedConcerns.has(c)).length;
|
|
707
707
|
const totalN=r.applicableCategories.length;
|
|
708
|
-
catHtml=\`<div class="cat-strip"><div class="cat-strip-lbl">
|
|
708
|
+
catHtml=\`<div class="cat-strip"><div class="cat-strip-lbl">Generated drafts: \${shownN} of \${totalN} applicable testing categories</div><div class="cat-pills">\${pills}</div></div>\`;
|
|
709
709
|
}
|
|
710
710
|
let testsHtml='';
|
|
711
711
|
if(r.generatedTests&&r.generatedTests.length){
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangepro/orangepro-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
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",
|