@orangepro/orangepro-mcp 0.2.38 → 0.2.40

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/dist/local/cli.js CHANGED
@@ -692,6 +692,8 @@ async function main() {
692
692
  printJson(res);
693
693
  else {
694
694
  out(`Test gaps (${res.gaps.length} of ${res.total_behaviors} behaviors):`);
695
+ for (const w of res.warnings ?? [])
696
+ out(` warning: ${w}`);
695
697
  if (res.guidance)
696
698
  out(` ${res.guidance}`);
697
699
  for (const g of res.gaps) {
@@ -656,7 +656,9 @@ export function opAnalyze(root, opts = {}, deps = defaultDeps()) {
656
656
  const coverageSuccess = runtimeCoveragePrepare?.generated
657
657
  .filter((g) => g.ok && g.artifact_path)
658
658
  .map((g) => `coverage artifact generated for ${g.language} module ${g.module_dir}: ${g.artifact_path}`) ?? [];
659
- const warnings = [...coverageSuccess, ...coverageWarnings, ...(runtimeCoveragePrepare?.warnings ?? []), ...analyzeFragment.warnings, ...enrichFragments.flatMap((f) => f.warnings)];
659
+ // Risk-config problems must surface on EVERY entry point that ranks, not just `start`.
660
+ const riskConfigWarnings = loadRiskConfig(scanRoot).warnings;
661
+ const warnings = [...coverageSuccess, ...coverageWarnings, ...(runtimeCoveragePrepare?.warnings ?? []), ...analyzeFragment.warnings, ...enrichFragments.flatMap((f) => f.warnings), ...riskConfigWarnings];
660
662
  return {
661
663
  graph_path: paths.graphPath,
662
664
  sources_count: graph.sources.length,
@@ -876,8 +878,11 @@ export function opGaps(root, opts = {}) {
876
878
  is_new_code: gap.is_new_code,
877
879
  integration_signal: gap.integration_signal
878
880
  }));
881
+ // Same root the ranking itself uses (the analyzed source), not the workspace cwd.
882
+ const riskConfigWarnings = loadRiskConfig(graph.workspace.root).warnings;
879
883
  return {
880
884
  ...gaps,
885
+ ...(riskConfigWarnings.length ? { warnings: riskConfigWarnings } : {}),
881
886
  top_risk_gaps: topRiskGaps,
882
887
  risk_model: {
883
888
  formula: "OrangePro Risk Score = Probability(1-10) × Impact(1-10) × DetectionDifficulty(1|5|10); P = normalize(git_churn*0.35 + fan_out*0.30 + new_code*15 + complexity*0.20); I = normalize(fan_in*0.30 + route_weight*0.30 + flow_position*0.20 + data_sensitivity*0.20); D = {proven:1, associated:5, none:10}",
@@ -1556,7 +1561,7 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
1556
1561
  reportProgress("start: deterministic graph is ready", { current: 4, total: 8 });
1557
1562
  // A per-repo/user risk config that fails to parse must announce itself here, not
1558
1563
  // silently revert to defaults (round-three review finding #1).
1559
- for (const w of loadRiskConfig(root).warnings)
1564
+ for (const w of loadRiskConfig(scanRoot).warnings)
1560
1565
  warnings.push(w);
1561
1566
  const staticSnapshot = writeStartStaticSnapshot(root, opts.baseRef, warnings);
1562
1567
  const aiProviderConfigured = deps.aiProvider !== undefined || resolveProviderConfig(providerEnv, providerOpts) !== null;
@@ -737,6 +737,10 @@ export function configDisclosureFor(graph, repoRoot) {
737
737
  if (n.external_id === o.symbol || re.test(n.external_id))
738
738
  suppressed.push({ symbol: n.title || n.external_id, reason: o.reason });
739
739
  }
740
+ const ignoreRes = cfg.classification.sensitivity_ignore.map(globToRegExp);
741
+ const sensitivityIgnored = ignoreRes.length
742
+ ? symbols.filter((n) => ignoreRes.some((re) => re.test(n.title || "") || re.test(n.external_id))).map((n) => n.title || n.external_id).slice(0, 50)
743
+ : [];
740
744
  return {
741
745
  hash: loaded.hash,
742
746
  warnings: loaded.warnings,
@@ -744,6 +748,13 @@ export function configDisclosureFor(graph, repoRoot) {
744
748
  suppressed,
745
749
  rankExcludePaths: cfg.classification.rank_exclude_paths,
746
750
  floor: cfg.tuning.irreversibility_floor,
747
- silence: cfg.tuning.silence_multiplier
751
+ silence: cfg.tuning.silence_multiplier,
752
+ classification: {
753
+ test_support_paths: cfg.classification.test_support_paths,
754
+ scheduled_entry_paths: cfg.classification.scheduled_entry_paths,
755
+ destructive_sinks: cfg.classification.destructive_sinks,
756
+ sensitivity_ignore: cfg.classification.sensitivity_ignore
757
+ },
758
+ sensitivityIgnored
748
759
  };
749
760
  }
@@ -1034,12 +1034,16 @@ renderRisks();
1034
1034
  const C=D.configDisclosure, host=$("#config-disclosure");
1035
1035
  if(!C||!host)return;
1036
1036
  const warn=(C.warnings||[]).length>0;
1037
- const tuned=C.overridesActive>0||(C.rankExcludePaths||[]).length>0||!C.floor||!C.silence;
1037
+ const K=C.classification||{};
1038
+ const clsSet=["test_support_paths","scheduled_entry_paths","destructive_sinks","sensitivity_ignore"].filter(k=>(K[k]||[]).length>0);
1039
+ const tuned=C.overridesActive>0||(C.rankExcludePaths||[]).length>0||!C.floor||!C.silence||clsSet.length>0;
1038
1040
  const bits=["config <b>"+esc(C.hash)+"</b>","overrides active <b>"+C.overridesActive+"</b>","suppressed <b>"+(C.suppressed||[]).length+"</b>"];
1039
1041
  if((C.rankExcludePaths||[]).length)bits.push("ranking excludes <b>"+esc(C.rankExcludePaths.join(", "))+"</b>");
1040
1042
  if(!C.floor)bits.push("<b>irreversibility floor OFF</b>");
1041
1043
  if(!C.silence)bits.push("<b>silence multiplier OFF</b>");
1042
1044
  let html="<div>"+bits.join(" · ")+"</div>";
1045
+ clsSet.forEach(k=>{html+='<span class="cfg-row">'+esc(k)+': <b>'+esc(K[k].join(", "))+'</b></span>';});
1046
+ if((C.sensitivityIgnored||[]).length)html+='<span class="cfg-row">sensitivity ignored by config (score changed): <b>'+esc(C.sensitivityIgnored.join(", "))+'</b></span>';
1043
1047
  (C.suppressed||[]).forEach(s=>{html+='<span class="cfg-row">suppressed by config: <b>'+esc(s.symbol)+'</b> — '+esc(s.reason)+'</span>';});
1044
1048
  (C.warnings||[]).forEach(w=>{html+='<span class="cfg-row">⚠ '+esc(w)+'</span>';});
1045
1049
  host.className="cfg"+(warn?" cfg-warn":tuned?" cfg-tuned":"");
@@ -92,7 +92,16 @@ export function loadConfig(paths) {
92
92
  privacy: defaultPrivacySettings()
93
93
  };
94
94
  }
95
- return JSON.parse(readFileSync(paths.configPath, "utf8"));
95
+ const raw = readFileSync(paths.configPath, "utf8");
96
+ try {
97
+ return JSON.parse(raw);
98
+ }
99
+ catch (err) {
100
+ // Fail loud AND clear: this file also carries the per-repo risk config, so a
101
+ // silent default here would be exactly the failure a verification layer must not have.
102
+ const hint = /\/\//.test(raw) ? " JSON does not allow // comments." : "";
103
+ throw new Error(`Unreadable ${paths.configPath}: ${err.message}.${hint} Fix the JSON, or delete the file to regenerate defaults.`);
104
+ }
96
105
  }
97
106
  export function saveConfig(paths, config) {
98
107
  writeJson(paths.configPath, config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangepro/orangepro-mcp",
3
- "version": "0.2.38",
3
+ "version": "0.2.40",
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",