@orangepro/orangepro-mcp 0.2.5 → 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**: what changed since last run, or "No changes identical graph, identical ranking." Every one of the ~N behaviors gets a plain-English description; every top risk gets a deterministic context line and a state-aware next step.
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 covered categories from real attached tests render normally; the rest render locked, meaning "warranted here, generated on the platform," never "hidden tests exist." Neither taxonomy changes evidence tiers.
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
  |----------|-----------------|
@@ -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 = 5;
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/mcp.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { z } from "zod";
4
+ import { ORANGEPRO_VERSION } from "./version.js";
4
5
  import { opAnalyze, opAiFlows, opAiLinks, opChanged, opCompare, opDoctor, opProofDoctor, opDynamicProof, opExplain, opExport, opGaps, opGenerate, opProveLoop, opRecordRun, opRtm, opScore, opStats, opStart, opStatus, opUpdate, resolveDiffTargets } from "./operations.js";
5
6
  import { runnableRunHintsFor, AGENT_RUN_WORKFLOW, GROUNDING_CONTRACT } from "./generate/runHints.js";
6
7
  import { preloadTreeSitter } from "./analyze/treeSitter/engine.js";
@@ -22,7 +23,7 @@ const DEFAULT_MARKDOWN_RTM_LIMIT = 500;
22
23
  * Codex, Claude, Copilot, …). First slice exposes NO upload or repo-write tools.
23
24
  */
24
25
  export function createLocalServer() {
25
- const server = new McpServer({ name: "orangepro-local", version: "0.2.0" });
26
+ const server = new McpServer({ name: "orangepro-local", version: ORANGEPRO_VERSION });
26
27
  const root = (ws) => ws || process.cwd();
27
28
  server.registerTool("orangepro_start", {
28
29
  title: "Start OrangePro",
@@ -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
- try {
1331
- behaviorCoveragePath = opBehaviorCoverageHtml(root, `${WORKSPACE_DIR}/behavior-coverage.html`).behavior_coverage_path;
1332
- }
1333
- catch {
1334
- // ponytail: swallow — refresh is advisory; DynamicProofResult carries no warnings channel.
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
- try {
1409
- behaviorCoveragePath = opBehaviorCoverageHtml(root, `${WORKSPACE_DIR}/behavior-coverage.html`).behavior_coverage_path;
1410
- }
1411
- catch (error) {
1412
- warnings.push(`behavior coverage view not written: ${error instanceof Error ? error.message : String(error)}`);
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
- const providerDeps = { ...deps, env: providerEnv };
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
- try {
2021
- writeFileSync(baselinePath, JSON.stringify(reportBaselineOf(data, new Date().toISOString())), "utf8");
2022
- }
2023
- catch {
2024
- // baseline write is advisory
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
  }
@@ -0,0 +1,6 @@
1
+ import { readFileSync } from "node:fs";
2
+ const manifest = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
3
+ if (typeof manifest.version !== "string" || manifest.version.length === 0) {
4
+ throw new Error("OrangePro package version is missing");
5
+ }
6
+ export const ORANGEPRO_VERSION = manifest.version;
@@ -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 coveredCategories = [...new Set([
740
+ const generatedCategories = [...new Set([
741
741
  ...generatedTests.map((t) => (t.bucket ? BUCKET_TO_CONCERN[t.bucket] : undefined)),
742
- // An integration/api/e2e-layer test covers the integration_flow
743
- // category by construction the chip and the pill must agree.
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
- coveredCategories,
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-shown{background:var(--gbg);border:1px solid var(--gbd);color:var(--green)}
234
- .cat-pill.cp-locked{background:transparent;border:1px dashed var(--bd2);color:var(--faint)}
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="\u2190 Back to system map";
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="\u2715 Clear filter \u201c"+svc+"\u201d";
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="\u201c"+h.words+"\u201d"+(h.owner?" on "+h.owner:"")+" — a behavior in "+(mod||"this repo")+".";
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 \u201c'+searchQ+'\u201d');
551
+ if(searchQ)parts.push('search '+searchQ+'');
553
552
  if(activeTier)parts.push('the selected evidence tier');
554
- if(activeGrp)parts.push('package \u201c'+activeGrp+'\u201d');
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 \u2014 clearing one usually brings results back.</p>');
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","\u2715 Clear search");
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 shownConcerns=new Set(r.coveredCategories||[]);
699
+ const draftedConcerns=new Set(r.generatedCategories||[]);
701
700
  const pills=r.applicableCategories.map(c=>{
702
- const shown=shownConcerns.has(c);
701
+ const drafted=draftedConcerns.has(c);
703
702
  const label=c.replace(/_/g,' ');
704
- return \`<span class="cat-pill \${shown?'cp-shown':'cp-locked'}">\${esc(label)}</span>\`;
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=>shownConcerns.has(c)).length;
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">Applicable testing categories for this flow (\${shownN} of \${totalN} covered)</div><div class="cat-pills">\${pills}</div></div>\`;
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.5",
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",