@orangepro/orangepro-mcp 0.2.31 → 0.2.32

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.
@@ -10,7 +10,7 @@ export function classifyGeneratedDraftBlocker(reason) {
10
10
  if (/no required module provides package|cannot find module|module not found|unresolved import|imports module-path package/i.test(text)) {
11
11
  return "unresolved_import";
12
12
  }
13
- if (/syntax check failed|undefined:|unknown field|redeclared in this block|expected (?:declaration|operand|'[^']+'|"[^"]+"|[^ ]+),? found|literal not terminated|cannot assign/i.test(text))
13
+ if (/syntax check failed|undefined:|unknown field|redeclared in this block|expected (?:declaration|operand|'[^']+'|"[^"]+"|[^ ]+),? found|literal not terminated|cannot assign|cannot use .+ as |missing ['",].+argument list/i.test(text))
14
14
  return "generated_code";
15
15
  return "unknown";
16
16
  }
@@ -1515,7 +1515,13 @@ function commandAvailable(command) {
1515
1515
  }
1516
1516
  }
1517
1517
  function shortStaticDiag(message) {
1518
- return message.replace(/\s+/g, " ").trim().slice(0, 240);
1518
+ const compact = message.replace(/\s+/g, " ").trim();
1519
+ if (compact.length <= 240)
1520
+ return compact;
1521
+ // Compiler output starts with package/build boilerplate and puts the useful
1522
+ // file:line diagnostic at the end. Preserve both ends so classification and
1523
+ // remediation do not degrade to "unknown" on long package names.
1524
+ return `${compact.slice(0, 72)} … ${compact.slice(-165)}`;
1519
1525
  }
1520
1526
  function escapeRegExp(value) {
1521
1527
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -2158,6 +2164,7 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
2158
2164
  const framework = pickFramework(graph, opts, targets, fileReader);
2159
2165
  const runSelection = targetsForFramework(graph, targets, framework);
2160
2166
  let runTargets = runSelection.targets;
2167
+ const explicitMulti = Boolean(opts.target_ids && opts.target_ids.length > 1);
2161
2168
  warnings.push(...runSelection.warnings);
2162
2169
  const systemPrompt = opts.systemPrompt ?? buildSystemPrompt();
2163
2170
  const promptVersion = inputMode === "graph_grounded" && opts.prompt_version === "v5" ? PROMPT_VERSION_V5 : PROMPT_VERSION;
@@ -2275,7 +2282,8 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
2275
2282
  }
2276
2283
  else if (opts.prompt_version === "v5") {
2277
2284
  const declaredDeps = readDeclaredDeps(graph.workspace.root);
2278
- for (const behavior of runTargets) {
2285
+ for (let targetIndex = 0; targetIndex < runTargets.length; targetIndex++) {
2286
+ const behavior = runTargets[targetIndex];
2279
2287
  if (generated.length >= limit)
2280
2288
  break;
2281
2289
  const gc = gatherContext(graph, behavior, framework, fileReader);
@@ -2378,7 +2386,13 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
2378
2386
  });
2379
2387
  continue;
2380
2388
  }
2381
- const selected = scenarios.slice(0, Math.max(1, limit - generated.length));
2389
+ const remainingSlots = Math.max(1, limit - generated.length);
2390
+ const remainingTargets = Math.max(1, runTargets.length - targetIndex);
2391
+ // In explicit multi-target mode, reserve a fair share for every remaining
2392
+ // target. Previously the first behavior could consume the entire batch
2393
+ // with several scenarios, leaving later high-risk behaviors untouched.
2394
+ const targetLimit = explicitMulti ? Math.max(1, Math.floor(remainingSlots / remainingTargets)) : remainingSlots;
2395
+ const selected = scenarios.slice(0, targetLimit);
2382
2396
  const completions = [];
2383
2397
  try {
2384
2398
  reportProgress(`Generating "${gc.ctx.behavior_title}" [v5 batch: ${selected.length}]…`);
@@ -2591,7 +2605,6 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
2591
2605
  }
2592
2606
  else {
2593
2607
  // Default: target-focused, bucket-diverse generation (one test per local bucket).
2594
- const explicitMulti = Boolean(opts.target_ids && opts.target_ids.length > 1);
2595
2608
  const plan = planGroundedBuckets(graph, runTargets, framework, fileReader, limit, explicitMulti, missing, warnings);
2596
2609
  // Repo dependency names (read once) — used by the runnable check to tell a missing
2597
2610
  // baseUrl-local import from a genuine external package the agent has installed.
@@ -19,7 +19,7 @@ import { enrichFromContent } from "./enrich/index.js";
19
19
  import { scoreGraph } from "./score/score.js";
20
20
  import { doctorGraph } from "./score/doctor.js";
21
21
  import { findGaps } from "./gaps/gaps.js";
22
- import { rankRiskGaps } from "./score/risk.js";
22
+ import { rankPriorityGaps, rankRiskGaps } from "./score/risk.js";
23
23
  import { generateTests } from "./generate/generator.js";
24
24
  import { classifyGeneratedDraftBlocker } from "./generate/draftGuidance.js";
25
25
  import { autoProve, NO_KEY_MESSAGE, isEligibleProvableTarget } from "./autoProve.js";
@@ -1631,7 +1631,7 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
1631
1631
  try {
1632
1632
  const graphForGeneration = loadGraph(workspacePaths(root).graphPath);
1633
1633
  const generatedTargets = new Set((graphForGeneration.generated_tests ?? []).map((t) => t.target_symbol_external_id).filter((id) => Boolean(id)));
1634
- const targetIds = rankRiskGaps(graphForGeneration, {
1634
+ const targetIds = rankPriorityGaps(graphForGeneration, {
1635
1635
  repoRoot: root,
1636
1636
  limit: generationLimit,
1637
1637
  provenIds: provenSymbolIds(graphForGeneration, loadLedger(root))
@@ -580,3 +580,11 @@ export function rankRiskGaps(graph, opts = {}) {
580
580
  .sort((a, b) => b.risk_score - a.risk_score || b.incoming_refs - a.incoming_refs || b.git_churn - a.git_churn || a.id.localeCompare(b.id))
581
581
  .slice(0, limit);
582
582
  }
583
+ /**
584
+ * Canonical priority-gap portfolio shown to a local user and used for automatic
585
+ * generation. Keeping this policy in one function prevents `opro start` from
586
+ * generating for a different "top N" than behavior-coverage.html displays.
587
+ */
588
+ export function rankPriorityGaps(graph, opts = {}) {
589
+ return rankRiskGaps(graph, { ...opts, maxPerFile: 3, maxPerTitle: 1 });
590
+ }
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import path from "node:path";
3
3
  import { buildRtm } from "../rtm.js";
4
- import { inspectRiskInputHealth, isEntryPoint, rankRiskGaps } from "../score/risk.js";
4
+ import { inspectRiskInputHealth, isEntryPoint, rankPriorityGaps } from "../score/risk.js";
5
5
  import { ORANGEPRO_VERSION } from "../version.js";
6
6
  import { PROOF_BLOCKER_GUIDE } from "../proofDoctor.js";
7
7
  import { classifyGeneratedDraftBlocker } from "../generate/draftGuidance.js";
@@ -892,7 +892,10 @@ export function buildBehaviorReportData(graph, ledger, opts = {}) {
892
892
  const flowIds = flowSymbolIds(graph);
893
893
  const summary = summaryFromRows(rows, flowIds);
894
894
  const repoRoot = opts.repoRoot ?? graph.workspace.root;
895
- const riskGaps = rankRiskGaps(graph, { repoRoot, limit: opts.riskLimit ?? 20, maxPerFile: 3, maxPerTitle: 1 });
895
+ const provenIds = new Set(rows
896
+ .filter((row) => row.evidence_tier === "proven" && Boolean(row.code_symbol))
897
+ .map((row) => row.code_symbol));
898
+ const riskGaps = rankPriorityGaps(graph, { repoRoot, limit: opts.riskLimit ?? 20, provenIds });
896
899
  const riskHealth = inspectRiskInputHealth(repoRoot);
897
900
  const churnAvailable = riskHealth.churnAvailable && riskGaps.every((risk) => risk.churn_available !== false);
898
901
  const provenance = {
@@ -911,16 +911,20 @@ if(D.viewMeta){
911
911
  if(rm&&rm.scored>rm.shown)$("#risk-cap-note").textContent="Showing the top "+rm.shown+" of "+rm.scored.toLocaleString()+" scored behaviors — every behavior is scored; only the highest-risk are surfaced here. Full ranking: opro gaps --limit N, or .orangepro/graph.json.";
912
912
  if(fm&&fm.shown>0&&fm.prunedByCaps>0)$("#flow-cap-note").textContent="Showing "+fm.shown.toLocaleString()+" flows, endpoint-anchored first. "+fm.prunedByCaps.toLocaleString()+" additional branch expansions were pruned by depth/branch/global rendering caps — pruning affects display only, not scoring.";
913
913
  }
914
- // Platform CTA: top banner in risk panel
915
- const riskTopBanner=el("div","platform-top-banner",
916
- \`<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>
917
- <a class="platform-footer-btn" href="https://orangepro.ai/get-started" target="_blank">Unlock Full Analysis &rarr;</a>\`);
918
- riskList.before(riskTopBanner);
919
914
  const generatedRiskCount=D.risks.filter(r=>r.generatedTests&&r.generatedTests.length).length;
920
915
  const generatedOutputCopy=[
921
916
  D.generatedRunnableTotal?\`\${D.generatedRunnableTotal} runnable generated test\${D.generatedRunnableTotal===1?"":"s"}\`:'',
922
917
  D.generatedDraftTotal?\`\${D.generatedDraftTotal} grounded draft\${D.generatedDraftTotal===1?"":"s"} with code withheld\`:'',
923
918
  ].filter(Boolean).join(' and ');
919
+ const generationSummary=D.generatedTotal
920
+ ?\` Generated output: <b>\${generatedOutputCopy}</b>; <b>\${D.shownCount}</b> shown inline across <b>\${generatedRiskCount} of \${D.risks.length}</b> priority flows.\`
921
+ :'';
922
+ // Platform CTA: top banner in risk panel. Keep generated-output totals beside
923
+ // the flow count so a user cannot mistake "5 flows" for "5 generated tests".
924
+ const riskTopBanner=el("div","platform-top-banner",
925
+ \`<span class="platform-top-banner-text">Local scan shows <b>\${D.risks.length}</b> priority gaps.\${generationSummary} Full ranked list, incident correlation, and CI merge gate on Platform.</span>
926
+ <a class="platform-footer-btn" href="https://orangepro.ai/get-started" target="_blank">Unlock Full Analysis &rarr;</a>\`);
927
+ riskList.before(riskTopBanner);
924
928
  let activeRiskFilter=generatedRiskCount?"generated":"all";
925
929
  function riskMatchesFilter(r){
926
930
  const hasGenerated=Boolean(r.generatedTests&&r.generatedTests.length);
@@ -931,8 +935,8 @@ function riskMatchesFilter(r){
931
935
  function renderRiskFilters(){
932
936
  const options=[
933
937
  ["all","All",D.risks.length],
934
- ["generated","Flows with generated output",generatedRiskCount],
935
- ["missing","No generated tests",D.risks.filter(r=>!(r.generatedTests&&r.generatedTests.length)).length]
938
+ ["generated","Flows with generated output",generatedRiskCount+"/"+D.risks.length],
939
+ ["missing","Flows without generated output",D.risks.filter(r=>!(r.generatedTests&&r.generatedTests.length)).length+"/"+D.risks.length]
936
940
  ];
937
941
  riskTools.innerHTML=options.map(([key,label,count])=>\`<button class="risk-filter" type="button" data-risk-filter="\${key}" aria-pressed="\${key===activeRiskFilter}">\${label} <span class="gc">\${count}</span></button>\`).join("");
938
942
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangepro/orangepro-mcp",
3
- "version": "0.2.31",
3
+ "version": "0.2.32",
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",