@reconcrap/boss-recommend-mcp 1.3.23 → 1.3.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "1.3.23",
3
+ "version": "1.3.24",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -475,15 +475,6 @@ function toStringArray(value, maxItems = 8) {
475
475
  return normalized;
476
476
  }
477
477
 
478
- function hasEvidenceGateSignal(parsed, parsedEvidence = null) {
479
- if (!parsed || typeof parsed !== "object") return false;
480
- if (parsed?.evidenceGateEligible === true) return true;
481
- if (Number.isFinite(Number(parsed?.evidenceRawCount))) return true;
482
- if (Number.isFinite(Number(parsed?.evidenceMatchedCount))) return true;
483
- const normalizedEvidence = Array.isArray(parsedEvidence) ? parsedEvidence : toStringArray(parsed?.evidence);
484
- return normalizedEvidence.length > 0;
485
- }
486
-
487
478
  function toLowerSafe(text) {
488
479
  return String(text || "").toLowerCase();
489
480
  }
@@ -4969,26 +4960,17 @@ class RecommendScreenCli {
4969
4960
  const parsed = result && typeof result === "object" ? result : {};
4970
4961
  const rawPassed = parsed?.rawPassed === true || parsed?.passed === true;
4971
4962
  const parsedEvidence = toStringArray(parsed?.evidence);
4972
- const evidenceGateEligible = hasEvidenceGateSignal(parsed, parsedEvidence);
4973
- const evidenceRawCount = evidenceGateEligible
4974
- ? (Number.isFinite(Number(parsed?.evidenceRawCount))
4975
- ? Number(parsed.evidenceRawCount)
4976
- : parsedEvidence.length)
4977
- : null;
4978
- const evidenceMatchedCount = evidenceGateEligible
4979
- ? (Number.isFinite(Number(parsed?.evidenceMatchedCount))
4980
- ? Number(parsed.evidenceMatchedCount)
4981
- : parsedEvidence.length)
4982
- : null;
4983
- const evidenceGateDemoted = parsed?.evidenceGateDemoted === true
4984
- || (evidenceGateEligible && rawPassed && evidenceMatchedCount <= 0);
4963
+ const evidenceRawCount = Number.isFinite(Number(parsed?.evidenceRawCount))
4964
+ ? Number(parsed.evidenceRawCount)
4965
+ : parsedEvidence.length;
4966
+ const evidenceMatchedCount = Number.isFinite(Number(parsed?.evidenceMatchedCount))
4967
+ ? Number(parsed.evidenceMatchedCount)
4968
+ : parsedEvidence.length;
4985
4969
  const cot = normalizeText(parsed?.cot || parsed?.reason || "");
4986
4970
  const summary = normalizeText(parsed?.summary || cot);
4987
- const finalReason = evidenceGateDemoted
4988
- ? `模型未给出可在简历截图中引用的证据,按安全策略判为不通过。${cot ? ` 原始判断依据(CoT): ${cot}` : ""}`
4989
- : (cot || (rawPassed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。"));
4971
+ const finalReason = cot || (rawPassed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。");
4990
4972
  return {
4991
- passed: evidenceGateDemoted ? false : rawPassed,
4973
+ passed: rawPassed,
4992
4974
  rawPassed,
4993
4975
  cot: finalReason,
4994
4976
  reason: finalReason,
@@ -4996,7 +4978,7 @@ class RecommendScreenCli {
4996
4978
  evidence: parsedEvidence,
4997
4979
  evidenceRawCount,
4998
4980
  evidenceMatchedCount,
4999
- evidenceGateDemoted
4981
+ evidenceGateDemoted: false
5000
4982
  };
5001
4983
  }
5002
4984
 
@@ -5280,19 +5262,14 @@ class RecommendScreenCli {
5280
5262
  const reason = cot || (rawPassed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。");
5281
5263
  const summary = reason;
5282
5264
  const parsedEvidence = toStringArray(parsed?.evidence);
5283
- const evidenceGateEligible = hasEvidenceGateSignal(parsed, parsedEvidence);
5284
- const evidenceRawCount = evidenceGateEligible
5285
- ? (Number.isFinite(Number(parsed?.evidenceRawCount)) ? Number(parsed.evidenceRawCount) : parsedEvidence.length)
5286
- : null;
5287
- const evidenceMatchedCount = evidenceGateEligible
5288
- ? (Number.isFinite(Number(parsed?.evidenceMatchedCount)) ? Number(parsed.evidenceMatchedCount) : parsedEvidence.length)
5289
- : null;
5290
- const evidenceGateDemoted = evidenceGateEligible && rawPassed && (evidenceMatchedCount ?? 0) <= 0;
5291
- const finalReason = evidenceGateDemoted
5292
- ? `模型未给出可在简历截图中引用的证据,按安全策略判为不通过。${reason ? ` 原始判断依据(CoT): ${reason}` : ""}`
5293
- : reason;
5294
- const passed = evidenceGateDemoted ? false : rawPassed;
5295
- const enrichedReason = enrichReasonWithEvidence(finalReason, summary || finalReason, parsedEvidence, passed);
5265
+ const evidenceRawCount = Number.isFinite(Number(parsed?.evidenceRawCount))
5266
+ ? Number(parsed.evidenceRawCount)
5267
+ : parsedEvidence.length;
5268
+ const evidenceMatchedCount = Number.isFinite(Number(parsed?.evidenceMatchedCount))
5269
+ ? Number(parsed.evidenceMatchedCount)
5270
+ : parsedEvidence.length;
5271
+ const passed = rawPassed;
5272
+ const enrichedReason = enrichReasonWithEvidence(reason, summary || reason, parsedEvidence, passed);
5296
5273
  return {
5297
5274
  passed,
5298
5275
  rawPassed,
@@ -5302,8 +5279,8 @@ class RecommendScreenCli {
5302
5279
  evidence: parsedEvidence,
5303
5280
  evidenceRawCount,
5304
5281
  evidenceMatchedCount,
5305
- evidenceGateEligible,
5306
- evidenceGateDemoted
5282
+ evidenceGateEligible: false,
5283
+ evidenceGateDemoted: false
5307
5284
  };
5308
5285
  }
5309
5286
 
@@ -5445,17 +5422,11 @@ class RecommendScreenCli {
5445
5422
  const normalizedResume = normalizeText(safeResumeText);
5446
5423
  const normalizedResumeLower = toLowerSafe(normalizedResume);
5447
5424
  const parsedEvidence = toStringArray(parsed?.evidence);
5448
- const evidenceGateEligible = hasEvidenceGateSignal(parsed, parsedEvidence);
5449
5425
  const evidence = [];
5450
- const unmatchedEvidence = [];
5451
- if (evidenceGateEligible) {
5452
- for (const item of parsedEvidence) {
5453
- const matched = matchEvidenceAgainstResume(item, safeResumeText, normalizedResume, normalizedResumeLower);
5454
- if (matched.matched) {
5455
- evidence.push(item);
5456
- } else {
5457
- unmatchedEvidence.push(item);
5458
- }
5426
+ for (const item of parsedEvidence) {
5427
+ const matched = matchEvidenceAgainstResume(item, safeResumeText, normalizedResume, normalizedResumeLower);
5428
+ if (matched.matched) {
5429
+ evidence.push(item);
5459
5430
  }
5460
5431
  }
5461
5432
  const parsedPassed = parsePassedDecision(parsed?.passed);
@@ -5467,19 +5438,8 @@ class RecommendScreenCli {
5467
5438
  `Text model response missing boolean passed decision. content=${truncateText(content, 180)}`
5468
5439
  );
5469
5440
  }
5470
- let passed = rawPassed;
5471
- let finalReason = cot || (passed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。");
5472
- const evidenceGateDemoted = evidenceGateEligible && rawPassed && evidence.length <= 0;
5473
- if (evidenceGateDemoted) {
5474
- passed = false;
5475
- finalReason = `模型未给出可在简历原文中校验的证据,按安全策略判为不通过。${finalReason ? ` 原始判断依据(CoT): ${finalReason}` : ""}`;
5476
- if (unmatchedEvidence.length > 0) {
5477
- log(
5478
- `[EVIDENCE_GATE] passed=true 但证据未命中简历原文,已降级为不通过;` +
5479
- `chunk=${chunkIndex}/${chunkTotal}; unmatched=${unmatchedEvidence.slice(0, 3).join(" | ")}`
5480
- );
5481
- }
5482
- }
5441
+ const passed = rawPassed;
5442
+ const finalReason = cot || (passed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。");
5483
5443
  const summary = finalReason;
5484
5444
  const enrichedReason = enrichReasonWithEvidence(finalReason, summary || finalReason, evidence, passed);
5485
5445
  return {
@@ -5489,9 +5449,9 @@ class RecommendScreenCli {
5489
5449
  reason: enrichedReason,
5490
5450
  summary: summary || enrichedReason,
5491
5451
  evidence,
5492
- evidenceRawCount: evidenceGateEligible ? parsedEvidence.length : null,
5493
- evidenceMatchedCount: evidenceGateEligible ? evidence.length : null,
5494
- evidenceGateDemoted,
5452
+ evidenceRawCount: parsedEvidence.length,
5453
+ evidenceMatchedCount: evidence.length,
5454
+ evidenceGateDemoted: false,
5495
5455
  chunkIndex,
5496
5456
  chunkTotal
5497
5457
  };
@@ -1715,11 +1715,11 @@ async function testVisionEvidenceGateShouldKeepRawPassWithoutExplicitEvidencePro
1715
1715
  assert.equal(result.rawPassed, true);
1716
1716
  assert.equal(result.passed, true);
1717
1717
  assert.equal(result.evidenceGateDemoted, false);
1718
- assert.equal(result.evidenceRawCount, null);
1719
- assert.equal(result.evidenceMatchedCount, null);
1718
+ assert.equal(result.evidenceRawCount, 0);
1719
+ assert.equal(result.evidenceMatchedCount, 0);
1720
1720
  }
1721
1721
 
1722
- async function testVisionEvidenceGateShouldDemoteWhenExplicitlyArmedWithoutEvidence() {
1722
+ async function testVisionEvidenceGateShouldNotDemoteWhenExplicitlyArmedWithoutEvidence() {
1723
1723
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-evidence-gate-explicit-"));
1724
1724
  const cli = new RecommendScreenCli(createArgs(tempDir));
1725
1725
  cli.prepareVisionImageSegmentsForModel = async () => ({
@@ -1740,12 +1740,48 @@ async function testVisionEvidenceGateShouldDemoteWhenExplicitlyArmedWithoutEvide
1740
1740
  });
1741
1741
  const result = await cli.callVisionModel(path.join(tempDir, "fake.png"));
1742
1742
  assert.equal(result.rawPassed, true);
1743
- assert.equal(result.passed, false);
1744
- assert.equal(result.evidenceGateDemoted, true);
1743
+ assert.equal(result.passed, true);
1744
+ assert.equal(result.evidenceGateDemoted, false);
1745
1745
  assert.equal(result.evidenceRawCount, 0);
1746
1746
  assert.equal(result.evidenceMatchedCount, 0);
1747
1747
  }
1748
1748
 
1749
+ async function testTextModelShouldNotDemoteRawPassWhenEvidenceDoesNotMatchResume() {
1750
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-text-no-demote-"));
1751
+ const cli = new RecommendScreenCli(createArgs(tempDir));
1752
+ const originalFetch = global.fetch;
1753
+ global.fetch = async () => ({
1754
+ ok: true,
1755
+ status: 200,
1756
+ async json() {
1757
+ return {
1758
+ choices: [
1759
+ {
1760
+ message: {
1761
+ content: JSON.stringify({
1762
+ passed: true,
1763
+ reason: "matched",
1764
+ summary: "matched",
1765
+ evidence: ["完全不在简历里的证据"]
1766
+ })
1767
+ }
1768
+ }
1769
+ ]
1770
+ };
1771
+ }
1772
+ });
1773
+ try {
1774
+ const result = await cli.callTextModel("这是简历原文,没有那条证据");
1775
+ assert.equal(result.rawPassed, true);
1776
+ assert.equal(result.passed, true);
1777
+ assert.equal(result.evidenceGateDemoted, false);
1778
+ assert.equal(result.evidenceRawCount, 1);
1779
+ assert.equal(result.evidenceMatchedCount, 0);
1780
+ } finally {
1781
+ global.fetch = originalFetch;
1782
+ }
1783
+ }
1784
+
1749
1785
  async function testVisionModelShouldSendAllOrderedChunks() {
1750
1786
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-all-chunks-"));
1751
1787
  const chunkPaths = [];
@@ -1843,7 +1879,8 @@ async function main() {
1843
1879
  await testTextModelShouldSupportLowThinkingForVolcengine();
1844
1880
  await testPrepareVisionImageSegmentsShouldSplitLongImage();
1845
1881
  await testVisionEvidenceGateShouldKeepRawPassWithoutExplicitEvidenceProtocol();
1846
- await testVisionEvidenceGateShouldDemoteWhenExplicitlyArmedWithoutEvidence();
1882
+ await testVisionEvidenceGateShouldNotDemoteWhenExplicitlyArmedWithoutEvidence();
1883
+ await testTextModelShouldNotDemoteRawPassWhenEvidenceDoesNotMatchResume();
1847
1884
  await testVisionModelShouldSendAllOrderedChunks();
1848
1885
  testRecoverablePostActionErrorShouldTreatGreetContinueAndNoButtonAsRecoverable();
1849
1886
  await testRecoverableGreetContinueButtonShouldNotAbortWhenDetailCloseFails();