@orangepro/orangepro-mcp 0.2.41 → 0.2.43

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.
@@ -75,10 +75,14 @@ export function languageOf(relPath) {
75
75
  export function isTestFile(relPath) {
76
76
  const p = relPath.toLowerCase();
77
77
  const file = relPath.split("/").pop() || relPath;
78
+ // CLI frameworks commonly use `src/commands/<topic>/test/` as a PRODUCT
79
+ // command namespace (for example `agent test run`), not as a test suite.
80
+ // Explicit test filenames and real test roots remain authoritative.
81
+ const cliTestCommandNamespace = /(^|\/)src\/commands\/(?:[^/]+\/)*test\//.test(p);
78
82
  return (/\.(test|spec)\.[a-z]+$/.test(p) ||
79
83
  /(^|\/)test\.[cm]?[jt]sx?$/.test(p) ||
80
84
  /(^|\/)__tests__\//.test(p) ||
81
- /(^|\/)(tests?|e2e|cypress|spec)\//.test(p) ||
85
+ (!cliTestCommandNamespace && /(^|\/)(tests?|e2e|cypress|spec)\//.test(p)) ||
82
86
  /(^|\/)(unit|integration|functional|acceptance)tests?\//.test(p) ||
83
87
  /_test\.(py|go)$/.test(p) ||
84
88
  /test_.*\.py$/.test(p) ||
@@ -572,6 +572,21 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
572
572
  return out;
573
573
  }
574
574
  const implNorm = norm(implAbsPath);
575
+ const implSf = program.getSourceFile(implNorm);
576
+ const hasModifier = (node, kind) => ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((m) => m.kind === kind) ?? false);
577
+ const cliCommandClasses = new Set();
578
+ let defaultCliCommandClass = null;
579
+ for (const statement of implSf?.statements ?? []) {
580
+ if (!ts.isClassDeclaration(statement) || !statement.name)
581
+ continue;
582
+ const extendsCliCommand = statement.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword &&
583
+ clause.types.some((t) => /(?:^|\.)(?:Sf)?Command$/.test(t.expression.getText(implSf)))) ?? false;
584
+ if (!extendsCliCommand || !memberTargetsByClass.has(statement.name.text))
585
+ continue;
586
+ cliCommandClasses.add(statement.name.text);
587
+ if (hasModifier(statement, ts.SyntaxKind.DefaultKeyword))
588
+ defaultCliCommandClass = statement.name.text;
589
+ }
575
590
  // Resolve an identifier's binding to a terminal export name in implAbsPath, or
576
591
  // null. Follows aliases (renames, re-export barrels) to the defining file.
577
592
  const resolvedTargetName = (id) => {
@@ -918,6 +933,7 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
918
933
  const constructorBindings = new Map();
919
934
  const prototypeBindings = new Map();
920
935
  const mutatorAliases = new Map();
936
+ const loadedModuleBindings = new Map();
921
937
  const unwrapExpression = (expr) => {
922
938
  let current = expr;
923
939
  while (ts.isParenthesizedExpression(current) ||
@@ -930,6 +946,38 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
930
946
  }
931
947
  return current;
932
948
  };
949
+ const loadedModuleFromEsmock = (expr) => {
950
+ const value = unwrapExpression(expr);
951
+ if (!ts.isCallExpression(value) ||
952
+ !ts.isIdentifier(value.expression) ||
953
+ !importedFromPkg(value.expression, ["esmock"]) ||
954
+ value.arguments.length === 0) {
955
+ return null;
956
+ }
957
+ const spec = unwrapExpression(value.arguments[0]);
958
+ if (!ts.isStringLiteralLike(spec))
959
+ return null;
960
+ const resolved = resolveImport(spec.text, testAbsPath);
961
+ if (!resolved.resolvedFileName || norm(resolved.resolvedFileName) !== implNorm)
962
+ return null;
963
+ return {
964
+ src: { file: implNorm, importedName: "default", typeOnly: false },
965
+ defaultClassName: defaultCliCommandClass
966
+ };
967
+ };
968
+ const loadedDefaultClassBinding = (expr) => {
969
+ const value = unwrapExpression(expr);
970
+ if (!ts.isPropertyAccessExpression(value) || value.name.text !== "default")
971
+ return null;
972
+ const receiver = unwrapExpression(value.expression);
973
+ if (!ts.isIdentifier(receiver))
974
+ return null;
975
+ const sym = checker.getSymbolAtLocation(receiver);
976
+ const loaded = sym ? loadedModuleBindings.get(sym) : undefined;
977
+ if (!loaded?.defaultClassName)
978
+ return null;
979
+ return { className: loaded.defaultClassName, src: loaded.src, fromNest: false };
980
+ };
933
981
  const numericIndex = (expr) => {
934
982
  const index = unwrapExpression(expr);
935
983
  if (ts.isNumericLiteral(index))
@@ -1159,7 +1207,13 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
1159
1207
  if (ts.isIdentifier(receiver)) {
1160
1208
  const sym = checker.getSymbolAtLocation(receiver);
1161
1209
  const binding = sym ? instanceBindings.get(sym) : undefined;
1162
- return binding ?? null;
1210
+ if (binding)
1211
+ return binding;
1212
+ const directClass = classNameFromToken(receiver);
1213
+ if (directClass && cliCommandClasses.has(directClass)) {
1214
+ return { className: directClass, src: importSourceOf(receiver), fromNest: false };
1215
+ }
1216
+ return null;
1163
1217
  }
1164
1218
  if (ts.isConditionalExpression(receiver)) {
1165
1219
  return sameClassBinding([bindingFromNestInstanceExpr(receiver.whenTrue), bindingFromNestInstanceExpr(receiver.whenFalse)]);
@@ -1824,6 +1878,9 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
1824
1878
  };
1825
1879
  const classBindingFromExpr = (expr, requireNest) => {
1826
1880
  const value = unwrapExpression(expr);
1881
+ const loadedDefault = loadedDefaultClassBinding(value);
1882
+ if (loadedDefault)
1883
+ return loadedDefault;
1827
1884
  if (ts.isIdentifier(value)) {
1828
1885
  const sym = checker.getSymbolAtLocation(value);
1829
1886
  const binding = sym ? instanceBindings.get(sym) : undefined;
@@ -1856,6 +1913,22 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
1856
1913
  if (sym)
1857
1914
  instanceBindings.set(sym, binding);
1858
1915
  };
1916
+ const maybeBindLoadedModule = (name, expr) => {
1917
+ const loaded = loadedModuleFromEsmock(expr);
1918
+ if (!loaded)
1919
+ return;
1920
+ const sym = checker.getSymbolAtLocation(name);
1921
+ if (sym)
1922
+ loadedModuleBindings.set(sym, loaded);
1923
+ if (!loaded.defaultClassName)
1924
+ return;
1925
+ const targetName = `${loaded.defaultClassName}.run`;
1926
+ if (!nameSet.has(targetName))
1927
+ return;
1928
+ const st = stateFor(targetName);
1929
+ st.importedRuntime = true;
1930
+ st.importSources.push(loaded.src);
1931
+ };
1859
1932
  const maybeBindDerivedIdentity = (name, expr) => {
1860
1933
  bindMutatorAlias(name, expr);
1861
1934
  const sym = checker.getSymbolAtLocation(name);
@@ -2065,6 +2138,7 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
2065
2138
  markTupleMutationSeed(node.initializer);
2066
2139
  if (ts.isIdentifier(node.name)) {
2067
2140
  recordContainerBindings(node.name, node.initializer);
2141
+ maybeBindLoadedModule(node.name, node.initializer);
2068
2142
  maybeBindInstance(node.name, node.initializer);
2069
2143
  maybeBindDerivedIdentity(node.name, node.initializer);
2070
2144
  }
@@ -2395,7 +2469,10 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
2395
2469
  out.set(name, {
2396
2470
  verdict: "inferred",
2397
2471
  reason: "the behavior is exercised but no assertion consumes its result or observes its rendered effect via the real binding",
2398
- rejected_conjunct: 5
2472
+ rejected_conjunct: 5,
2473
+ ...(st.runtimeUses.some((u) => u.src != null && cleanBindingSource(u.src) && !srcMocked(u.src))
2474
+ ? { association_signal: "runtime_invocation" }
2475
+ : {})
2399
2476
  });
2400
2477
  }
2401
2478
  return out;
@@ -2546,10 +2623,11 @@ export function resolveSpecifier(spec, fromFile, repoRoot, wsPkgs) {
2546
2623
  export function runConfirmer(input) {
2547
2624
  const { candidates, symbolsByImpl, existingSymIds, anchorFile } = input;
2548
2625
  const confirmations = [];
2626
+ const associations = [];
2549
2627
  let attempted = 0;
2550
2628
  let capped_downgrades = 0;
2551
2629
  if (candidates.length === 0)
2552
- return { confirmations, attempted, capped_downgrades };
2630
+ return { confirmations, associations, attempted, capped_downgrades };
2553
2631
  const absFiles = new Set();
2554
2632
  for (const c of candidates) {
2555
2633
  absFiles.add(c.testAbs);
@@ -2564,7 +2642,9 @@ export function runConfirmer(input) {
2564
2642
  const verdicts = confirmBehaviors(ctx, c.testAbs, c.implAbs, names);
2565
2643
  for (const [name, v] of verdicts) {
2566
2644
  attempted++;
2567
- if (v.verdict !== "confirmed")
2645
+ const isConfirmed = v.verdict === "confirmed";
2646
+ const isAssociated = v.verdict === "inferred" && v.association_signal === "runtime_invocation";
2647
+ if (!isConfirmed && !isAssociated)
2568
2648
  continue;
2569
2649
  const symId = `sym:${c.implRel}#${name}`;
2570
2650
  if (!existingSymIds.has(symId)) {
@@ -2575,8 +2655,12 @@ export function runConfirmer(input) {
2575
2655
  if (seen.has(key))
2576
2656
  continue;
2577
2657
  seen.add(key);
2578
- confirmations.push({ testRel: c.testRel, implRel: c.implRel, behaviorName: name, symId, reason: v.reason });
2658
+ const edge = { testRel: c.testRel, implRel: c.implRel, behaviorName: name, symId, reason: v.reason };
2659
+ if (isConfirmed)
2660
+ confirmations.push(edge);
2661
+ else
2662
+ associations.push(edge);
2579
2663
  }
2580
2664
  }
2581
- return { confirmations, attempted, capped_downgrades };
2665
+ return { confirmations, associations, attempted, capped_downgrades };
2582
2666
  }
@@ -30,6 +30,9 @@ const RUNTIME_HINTS = {
30
30
  "django": "Django",
31
31
  "flask": "Flask",
32
32
  "spring-boot": "Spring Boot",
33
+ "@oclif/core": "oclif",
34
+ "@salesforce/sf-plugins-core": "Salesforce CLI",
35
+ "commander": "Commander",
33
36
  };
34
37
  const CONFIG_FRAMEWORK_HINTS = [
35
38
  { match: /^vitest\.config\.[tj]s$/, name: "vitest", layer: "unit" },