freshcontext-mcp 0.3.16 → 0.3.18
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/.env.example +3 -0
- package/LICENSE +21 -0
- package/NOTICE.md +17 -0
- package/README.md +395 -296
- package/SECURITY.md +34 -0
- package/TRADEMARKS.md +9 -0
- package/dist/adapters/arxiv.js +92 -48
- package/dist/adapters/finance.js +87 -101
- package/dist/adapters/gdelt.js +1 -1
- package/dist/adapters/gebiz.js +1 -1
- package/dist/adapters/hackernews.js +59 -29
- package/dist/adapters/productHunt.js +8 -4
- package/dist/adapters/registry.js +232 -0
- package/dist/adapters/repoSearch.js +1 -1
- package/dist/adapters/secFilings.js +1 -1
- package/dist/core/decay.js +61 -0
- package/dist/core/decision.js +176 -0
- package/dist/core/envelope.js +59 -0
- package/dist/core/explain.js +28 -0
- package/dist/core/guards.js +17 -0
- package/dist/core/index.js +11 -0
- package/dist/core/pipeline.js +101 -0
- package/dist/core/provenance.js +73 -0
- package/dist/core/rank.js +84 -0
- package/dist/core/signal.js +101 -0
- package/dist/core/sourceProfiles.js +126 -0
- package/dist/core/types.js +1 -0
- package/dist/core/utility.js +90 -0
- package/dist/rest/handler.js +126 -0
- package/dist/security.js +1 -1
- package/dist/server.js +10 -10
- package/dist/tools/freshnessStamp.js +1 -117
- package/dist/types.js +0 -1
- package/docs/API_DESIGN.md +434 -0
- package/docs/CODEX_MCP_USAGE.md +116 -0
- package/docs/CORE_API.md +224 -0
- package/docs/DEPENDENCY_DILIGENCE.md +63 -0
- package/docs/HA_PRI_V2_DESIGN.md +279 -0
- package/docs/OPERATIONAL_DEMO_RUNBOOK.md +458 -0
- package/docs/RELEASE_INTEGRITY.md +53 -0
- package/docs/RELEASE_NOTES.md +38 -0
- package/docs/SIGNAL_CONTRACT.md +89 -0
- package/docs/SOURCE_PROFILES.md +427 -0
- package/freshcontext.schema.json +103 -103
- package/package-script-guard.mjs +140 -0
- package/package.json +92 -52
- package/server.json +27 -28
- package/.github/workflows/publish.yml +0 -32
- package/RESEARCH.md +0 -487
- package/RISKS.md +0 -137
- package/cleanup.ps1 +0 -99
- package/demo/README.md +0 -70
- package/demo/data.json +0 -88
- package/demo/generate.mjs +0 -199
- package/demo/index.html +0 -513
- package/demo/logo-export.html +0 -61
- package/demo/logo.svg +0 -23
- package/dist/apify.js +0 -133
- package/freshcontext-validate.js +0 -196
- package/time-check.ps1 +0 -46
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { LAMBDA, calculateFreshnessScore } from "./decay.js";
|
|
2
|
+
import { formatForLLM, toStructuredJSON } from "./envelope.js";
|
|
3
|
+
import { calculateHaPriV2 } from "./provenance.js";
|
|
4
|
+
import { rankSignal } from "./rank.js";
|
|
5
|
+
import { normalizeSignal } from "./signal.js";
|
|
6
|
+
import { calculateContextUtility } from "./utility.js";
|
|
7
|
+
function ageHours(signal) {
|
|
8
|
+
if (!signal.published_at)
|
|
9
|
+
return 0;
|
|
10
|
+
const published = new Date(signal.published_at).getTime();
|
|
11
|
+
const retrieved = new Date(signal.retrieved_at).getTime();
|
|
12
|
+
if (isNaN(published) || isNaN(retrieved))
|
|
13
|
+
return 0;
|
|
14
|
+
return Math.max(0, (retrieved - published) / (1000 * 60 * 60));
|
|
15
|
+
}
|
|
16
|
+
function envelopeConfidence(signal) {
|
|
17
|
+
if (signal.status === "failed" || signal.date_confidence === "unknown")
|
|
18
|
+
return "low";
|
|
19
|
+
return signal.date_confidence;
|
|
20
|
+
}
|
|
21
|
+
function createEnvelope(signal, freshnessScore, options) {
|
|
22
|
+
if (!options.includeEnvelope || signal.content === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
const ctx = {
|
|
25
|
+
content: signal.content.slice(0, options.envelopeMaxLength ?? 8000),
|
|
26
|
+
source_url: signal.source,
|
|
27
|
+
content_date: signal.published_at,
|
|
28
|
+
retrieved_at: signal.retrieved_at,
|
|
29
|
+
freshness_confidence: envelopeConfidence(signal),
|
|
30
|
+
freshness_score: freshnessScore,
|
|
31
|
+
adapter: signal.source_type,
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
context: ctx,
|
|
35
|
+
text: formatForLLM(ctx, options.envelopeFormat),
|
|
36
|
+
structured: toStructuredJSON(ctx),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function createProvenance(signal, options, reasons) {
|
|
40
|
+
if (!options.includeProvenance)
|
|
41
|
+
return undefined;
|
|
42
|
+
const resultId = options.provenance?.resultId ?? signal.id;
|
|
43
|
+
const engineVersion = options.provenance?.engineVersion;
|
|
44
|
+
if (!signal.content) {
|
|
45
|
+
reasons.push("provenance was requested but content was missing");
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
if (!resultId) {
|
|
49
|
+
reasons.push("provenance was requested but resultId was missing");
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
if (!engineVersion) {
|
|
53
|
+
reasons.push("provenance was requested but engineVersion was missing");
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return calculateHaPriV2({
|
|
57
|
+
resultId,
|
|
58
|
+
rawContent: signal.content,
|
|
59
|
+
semanticFingerprint: options.provenance?.semanticFingerprint ?? null,
|
|
60
|
+
adapter: signal.source_type,
|
|
61
|
+
publishedAt: signal.published_at,
|
|
62
|
+
retrievedAt: signal.retrieved_at,
|
|
63
|
+
engineVersion,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export function evaluateSignal(input, options = {}) {
|
|
67
|
+
const signal = normalizeSignal(input, options);
|
|
68
|
+
const freshness_score = signal.status === "failed" || signal.date_confidence === "unknown"
|
|
69
|
+
? null
|
|
70
|
+
: calculateFreshnessScore(signal.published_at, signal.retrieved_at, signal.source_type);
|
|
71
|
+
const utility = calculateContextUtility({
|
|
72
|
+
contextualRelevance: signal.semantic_score * 100,
|
|
73
|
+
lambda: LAMBDA[signal.source_type] ?? LAMBDA.default,
|
|
74
|
+
ageHours: ageHours(signal),
|
|
75
|
+
dateConfidence: signal.date_confidence,
|
|
76
|
+
status: signal.status,
|
|
77
|
+
});
|
|
78
|
+
const ranked = rankSignal(signal, options);
|
|
79
|
+
const reasons = [...signal.reasons, ...utility.reasons];
|
|
80
|
+
const envelope = createEnvelope(signal, freshness_score, options);
|
|
81
|
+
const provenance = createProvenance(signal, options, reasons);
|
|
82
|
+
return {
|
|
83
|
+
signal,
|
|
84
|
+
freshness_score,
|
|
85
|
+
utility,
|
|
86
|
+
ranked,
|
|
87
|
+
explanation: ranked.reason,
|
|
88
|
+
envelope,
|
|
89
|
+
provenance,
|
|
90
|
+
reasons,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export function evaluateSignals(inputs, options = {}) {
|
|
94
|
+
return inputs
|
|
95
|
+
.map((input, index) => ({ evaluation: evaluateSignal(input, options), index }))
|
|
96
|
+
.sort((a, b) => {
|
|
97
|
+
const scoreDiff = b.evaluation.ranked.final_score - a.evaluation.ranked.final_score;
|
|
98
|
+
return scoreDiff !== 0 ? scoreDiff : a.index - b.index;
|
|
99
|
+
})
|
|
100
|
+
.map(({ evaluation }) => evaluation);
|
|
101
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
const HA_PRI_V2_VERSION = "FRESHCONTEXT_HA_PRI_V2";
|
|
3
|
+
const NULL_SENTINEL = "null";
|
|
4
|
+
function fieldValue(value) {
|
|
5
|
+
return value ?? NULL_SENTINEL;
|
|
6
|
+
}
|
|
7
|
+
export function canonicalizeHaPriContent(input) {
|
|
8
|
+
return input
|
|
9
|
+
.replace(/\r\n/g, "\n")
|
|
10
|
+
.replace(/\r/g, "\n")
|
|
11
|
+
.split("\n")
|
|
12
|
+
.map((line) => line.replace(/[ \t]+$/g, ""))
|
|
13
|
+
.join("\n");
|
|
14
|
+
}
|
|
15
|
+
export function sha256Hex(input) {
|
|
16
|
+
return createHash("sha256").update(input, "utf8").digest("hex");
|
|
17
|
+
}
|
|
18
|
+
export function calculateHaPriV2(input) {
|
|
19
|
+
const canonicalContentSha256 = sha256Hex(canonicalizeHaPriContent(input.rawContent));
|
|
20
|
+
const semanticFingerprintSha256 = sha256Hex(fieldValue(input.semanticFingerprint));
|
|
21
|
+
const resultId = fieldValue(input.resultId);
|
|
22
|
+
const adapter = fieldValue(input.adapter);
|
|
23
|
+
const publishedAt = fieldValue(input.publishedAt);
|
|
24
|
+
const retrievedAt = fieldValue(input.retrievedAt);
|
|
25
|
+
const engineVersion = fieldValue(input.engineVersion);
|
|
26
|
+
const signingPayload = [
|
|
27
|
+
HA_PRI_V2_VERSION,
|
|
28
|
+
`result_id=${resultId}`,
|
|
29
|
+
`canonical_content_sha256=${canonicalContentSha256}`,
|
|
30
|
+
`semantic_fingerprint_sha256=${semanticFingerprintSha256}`,
|
|
31
|
+
`adapter=${adapter}`,
|
|
32
|
+
`published_at=${publishedAt}`,
|
|
33
|
+
`retrieved_at=${retrievedAt}`,
|
|
34
|
+
`engine_version=${engineVersion}`,
|
|
35
|
+
].join("\n");
|
|
36
|
+
return {
|
|
37
|
+
version: HA_PRI_V2_VERSION,
|
|
38
|
+
resultId,
|
|
39
|
+
canonicalContentSha256,
|
|
40
|
+
semanticFingerprintSha256,
|
|
41
|
+
adapter,
|
|
42
|
+
publishedAt,
|
|
43
|
+
retrievedAt,
|
|
44
|
+
engineVersion,
|
|
45
|
+
signingPayload,
|
|
46
|
+
haPriSigV2: sha256Hex(signingPayload),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function verifyHaPriV2(input, actualSig) {
|
|
50
|
+
if (actualSig === null || actualSig === undefined || actualSig.trim() === "") {
|
|
51
|
+
return {
|
|
52
|
+
status: "unknown",
|
|
53
|
+
expected: null,
|
|
54
|
+
actual: actualSig ?? null,
|
|
55
|
+
reasons: ["missing ha_pri_sig_v2; verification status unknown"],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const expected = calculateHaPriV2(input).haPriSigV2;
|
|
59
|
+
if (actualSig === expected) {
|
|
60
|
+
return {
|
|
61
|
+
status: "valid",
|
|
62
|
+
expected,
|
|
63
|
+
actual: actualSig,
|
|
64
|
+
reasons: [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
status: "invalid",
|
|
69
|
+
expected,
|
|
70
|
+
actual: actualSig,
|
|
71
|
+
reasons: ["stored ha_pri_sig_v2 did not match recomputed signature"],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { calculateFreshnessScore } from "./decay.js";
|
|
2
|
+
import { explainSignal } from "./explain.js";
|
|
3
|
+
import { looksLikeFailedAdapterContent } from "./guards.js";
|
|
4
|
+
const DEFAULT_SEMANTIC_WEIGHT = 0.7;
|
|
5
|
+
const DEFAULT_FRESHNESS_WEIGHT = 0.3;
|
|
6
|
+
export function clampScore(value) {
|
|
7
|
+
if (!Number.isFinite(value))
|
|
8
|
+
return 0;
|
|
9
|
+
return Math.min(1, Math.max(0, value));
|
|
10
|
+
}
|
|
11
|
+
function positiveNumber(value) {
|
|
12
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
13
|
+
}
|
|
14
|
+
function resolveWeights(options) {
|
|
15
|
+
let semantic = positiveNumber(options.semanticWeight);
|
|
16
|
+
let freshness = positiveNumber(options.freshnessWeight);
|
|
17
|
+
if (semantic === 0 && freshness === 0) {
|
|
18
|
+
semantic = DEFAULT_SEMANTIC_WEIGHT;
|
|
19
|
+
freshness = DEFAULT_FRESHNESS_WEIGHT;
|
|
20
|
+
}
|
|
21
|
+
const total = semantic + freshness;
|
|
22
|
+
return {
|
|
23
|
+
semantic: semantic / total,
|
|
24
|
+
freshness: freshness / total,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function resolveRetrievedAt(signal, options) {
|
|
28
|
+
if (signal.retrieved_at)
|
|
29
|
+
return signal.retrieved_at;
|
|
30
|
+
if (options.now instanceof Date)
|
|
31
|
+
return options.now.toISOString();
|
|
32
|
+
if (typeof options.now === "string")
|
|
33
|
+
return options.now;
|
|
34
|
+
return new Date().toISOString();
|
|
35
|
+
}
|
|
36
|
+
function resolveSourceType(signal, options) {
|
|
37
|
+
return signal.source_type ?? options.defaultSourceType ?? signal.source ?? "default";
|
|
38
|
+
}
|
|
39
|
+
function isFailedSignal(signal) {
|
|
40
|
+
return signal.status === "failed"
|
|
41
|
+
|| (signal.content !== undefined && looksLikeFailedAdapterContent(signal.content));
|
|
42
|
+
}
|
|
43
|
+
function confidenceFor(signal, semanticScore, freshnessScore) {
|
|
44
|
+
if (isFailedSignal(signal)) {
|
|
45
|
+
return "low";
|
|
46
|
+
}
|
|
47
|
+
if (freshnessScore !== null && semanticScore >= 0.7) {
|
|
48
|
+
return "high";
|
|
49
|
+
}
|
|
50
|
+
if (freshnessScore !== null || semanticScore >= 0.5) {
|
|
51
|
+
return "medium";
|
|
52
|
+
}
|
|
53
|
+
return "low";
|
|
54
|
+
}
|
|
55
|
+
export function rankSignal(signal, options = {}) {
|
|
56
|
+
const weights = resolveWeights(options);
|
|
57
|
+
const semantic_score = clampScore(signal.semantic_score);
|
|
58
|
+
const freshness_score = isFailedSignal(signal) || signal.date_confidence === "unknown"
|
|
59
|
+
? null
|
|
60
|
+
: calculateFreshnessScore(signal.published_at ?? signal.content_date ?? null, resolveRetrievedAt(signal, options), resolveSourceType(signal, options));
|
|
61
|
+
const freshnessComponent = freshness_score === null ? 0 : clampScore(freshness_score / 100);
|
|
62
|
+
const final_score = clampScore(semantic_score * weights.semantic + freshnessComponent * weights.freshness);
|
|
63
|
+
const confidence = confidenceFor(signal, semantic_score, freshness_score);
|
|
64
|
+
const ranked = {
|
|
65
|
+
...signal,
|
|
66
|
+
semantic_score,
|
|
67
|
+
freshness_score,
|
|
68
|
+
final_score,
|
|
69
|
+
confidence,
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
...ranked,
|
|
73
|
+
reason: explainSignal(ranked),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function rankSignals(signals, options = {}) {
|
|
77
|
+
return signals
|
|
78
|
+
.map((signal, index) => ({ ranked: rankSignal(signal, options), index }))
|
|
79
|
+
.sort((a, b) => {
|
|
80
|
+
const scoreDiff = b.ranked.final_score - a.ranked.final_score;
|
|
81
|
+
return scoreDiff !== 0 ? scoreDiff : a.index - b.index;
|
|
82
|
+
})
|
|
83
|
+
.map(({ ranked }) => ranked);
|
|
84
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { isMeaningfullyFutureDate } from "./decay.js";
|
|
2
|
+
import { looksLikeFailedAdapterContent } from "./guards.js";
|
|
3
|
+
export const SIGNAL_CONTRACT_VERSION = "freshcontext.signal.v1";
|
|
4
|
+
const DATE_CONFIDENCE_VALUES = new Set(["high", "medium", "low", "unknown"]);
|
|
5
|
+
const STATUS_VALUES = new Set(["success", "partial", "stale", "failed", "unknown"]);
|
|
6
|
+
function isSignalDateConfidence(value) {
|
|
7
|
+
return typeof value === "string" && DATE_CONFIDENCE_VALUES.has(value);
|
|
8
|
+
}
|
|
9
|
+
function isContextUtilityStatus(value) {
|
|
10
|
+
return typeof value === "string" && STATUS_VALUES.has(value);
|
|
11
|
+
}
|
|
12
|
+
function normalizeDate(value) {
|
|
13
|
+
if (!value)
|
|
14
|
+
return null;
|
|
15
|
+
const timestamp = new Date(value).getTime();
|
|
16
|
+
if (isNaN(timestamp))
|
|
17
|
+
return null;
|
|
18
|
+
return new Date(timestamp).toISOString();
|
|
19
|
+
}
|
|
20
|
+
function resolveRetrievedAt(input, options, reasons) {
|
|
21
|
+
const retrievedAt = normalizeDate(input.retrieved_at);
|
|
22
|
+
if (retrievedAt)
|
|
23
|
+
return retrievedAt;
|
|
24
|
+
if (input.retrieved_at) {
|
|
25
|
+
reasons.push("retrieved_at was invalid; used normalization time");
|
|
26
|
+
}
|
|
27
|
+
const optionNow = options.now instanceof Date
|
|
28
|
+
? (isNaN(options.now.getTime()) ? null : options.now.toISOString())
|
|
29
|
+
: normalizeDate(options.now);
|
|
30
|
+
return optionNow ?? new Date().toISOString();
|
|
31
|
+
}
|
|
32
|
+
function normalizeSemanticScore(value, reasons) {
|
|
33
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
34
|
+
reasons.push("semantic_score was missing or invalid; clamped to 0");
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
if (value < 0) {
|
|
38
|
+
reasons.push("semantic_score was below 0; clamped to 0");
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
if (value > 1) {
|
|
42
|
+
reasons.push("semantic_score exceeded 1; clamped to 1");
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
function resolveDateConfidence(input, hasTrustedDate) {
|
|
48
|
+
if (!hasTrustedDate)
|
|
49
|
+
return "unknown";
|
|
50
|
+
if (isSignalDateConfidence(input.date_confidence))
|
|
51
|
+
return input.date_confidence;
|
|
52
|
+
if (input.freshness_confidence)
|
|
53
|
+
return input.freshness_confidence;
|
|
54
|
+
return "medium";
|
|
55
|
+
}
|
|
56
|
+
function resolveStatus(input, failedContent) {
|
|
57
|
+
if (failedContent)
|
|
58
|
+
return "failed";
|
|
59
|
+
if (isContextUtilityStatus(input.status))
|
|
60
|
+
return input.status;
|
|
61
|
+
return "success";
|
|
62
|
+
}
|
|
63
|
+
export function normalizeSignal(input, options = {}) {
|
|
64
|
+
const reasons = [];
|
|
65
|
+
const retrieved_at = resolveRetrievedAt(input, options, reasons);
|
|
66
|
+
const rawPublishedAt = input.published_at ?? input.content_date ?? null;
|
|
67
|
+
let published_at = normalizeDate(rawPublishedAt);
|
|
68
|
+
if (!input.published_at && input.content_date) {
|
|
69
|
+
reasons.push("content_date alias was normalized to published_at");
|
|
70
|
+
}
|
|
71
|
+
if (rawPublishedAt && !published_at) {
|
|
72
|
+
reasons.push("published_at/content_date was invalid; cleared");
|
|
73
|
+
}
|
|
74
|
+
if (published_at && isMeaningfullyFutureDate(published_at, retrieved_at)) {
|
|
75
|
+
published_at = null;
|
|
76
|
+
reasons.push("published_at/content_date was meaningfully future-dated; cleared");
|
|
77
|
+
}
|
|
78
|
+
const failedContent = input.content !== undefined && looksLikeFailedAdapterContent(input.content);
|
|
79
|
+
if (failedContent) {
|
|
80
|
+
reasons.push("content looked like failed adapter output; status set to failed");
|
|
81
|
+
}
|
|
82
|
+
const source_type = input.source_type ?? options.defaultSourceType ?? "default";
|
|
83
|
+
if (!input.source_type && !options.defaultSourceType) {
|
|
84
|
+
reasons.push("source_type was missing; defaulted to default");
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
contract_version: SIGNAL_CONTRACT_VERSION,
|
|
88
|
+
id: input.id,
|
|
89
|
+
source: input.source,
|
|
90
|
+
source_type,
|
|
91
|
+
title: input.title,
|
|
92
|
+
content: input.content,
|
|
93
|
+
published_at,
|
|
94
|
+
retrieved_at,
|
|
95
|
+
semantic_score: normalizeSemanticScore(input.semantic_score, reasons),
|
|
96
|
+
date_confidence: resolveDateConfidence(input, published_at !== null),
|
|
97
|
+
status: resolveStatus(input, failedContent),
|
|
98
|
+
metadata: input.metadata ? { ...input.metadata } : {},
|
|
99
|
+
reasons,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { LAMBDA } from "./decay.js";
|
|
2
|
+
function halfLifeHours(lambda) {
|
|
3
|
+
return Number((Math.log(2) / lambda).toFixed(2));
|
|
4
|
+
}
|
|
5
|
+
function profile(input) {
|
|
6
|
+
return {
|
|
7
|
+
...input,
|
|
8
|
+
half_life_hours: halfLifeHours(input.default_decay_lambda),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function copyProfile(profile) {
|
|
12
|
+
return {
|
|
13
|
+
...profile,
|
|
14
|
+
source_types: [...profile.source_types],
|
|
15
|
+
recommended_surfaces: [...profile.recommended_surfaces],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export const BUILT_IN_SOURCE_PROFILES = Object.freeze({
|
|
19
|
+
official_docs: profile({
|
|
20
|
+
profile_id: "official_docs",
|
|
21
|
+
source_types: ["official_docs", "changelog", "packagetrends"],
|
|
22
|
+
purpose: "Official product docs, API docs, standards, changelogs, and canonical source material.",
|
|
23
|
+
default_decay_lambda: LAMBDA.github,
|
|
24
|
+
authority_hint: "high",
|
|
25
|
+
date_policy: "balanced",
|
|
26
|
+
failure_policy: "warn",
|
|
27
|
+
recommended_surfaces: ["rest", "sdk", "cli", "operator"],
|
|
28
|
+
}),
|
|
29
|
+
code_activity: profile({
|
|
30
|
+
profile_id: "code_activity",
|
|
31
|
+
source_types: ["github", "reposearch", "changelog", "packagetrends"],
|
|
32
|
+
purpose: "Repository activity, release cadence, dependency health, and implementation evidence.",
|
|
33
|
+
default_decay_lambda: LAMBDA.github,
|
|
34
|
+
authority_hint: "medium",
|
|
35
|
+
date_policy: "balanced",
|
|
36
|
+
failure_policy: "downgrade",
|
|
37
|
+
recommended_surfaces: ["mcp", "rest", "sdk", "cli", "operator"],
|
|
38
|
+
}),
|
|
39
|
+
social_pulse: profile({
|
|
40
|
+
profile_id: "social_pulse",
|
|
41
|
+
source_types: ["hackernews", "reddit", "producthunt"],
|
|
42
|
+
purpose: "Community awareness, social proof, launch momentum, and early-market signal.",
|
|
43
|
+
default_decay_lambda: LAMBDA.hackernews,
|
|
44
|
+
authority_hint: "medium",
|
|
45
|
+
date_policy: "strict",
|
|
46
|
+
failure_policy: "downgrade",
|
|
47
|
+
recommended_surfaces: ["mcp", "rest", "sdk", "operator"],
|
|
48
|
+
}),
|
|
49
|
+
academic_research: profile({
|
|
50
|
+
profile_id: "academic_research",
|
|
51
|
+
source_types: ["google_scholar", "arxiv"],
|
|
52
|
+
purpose: "Scholarly material, papers, research abstracts, and citation-oriented context.",
|
|
53
|
+
default_decay_lambda: LAMBDA.arxiv,
|
|
54
|
+
authority_hint: "high",
|
|
55
|
+
date_policy: "lenient",
|
|
56
|
+
failure_policy: "warn",
|
|
57
|
+
recommended_surfaces: ["mcp", "rest", "sdk", "cli", "operator"],
|
|
58
|
+
}),
|
|
59
|
+
market_finance: profile({
|
|
60
|
+
profile_id: "market_finance",
|
|
61
|
+
source_types: ["finance", "finance_landscape"],
|
|
62
|
+
purpose: "Market prices, quotes, financial movement, and finance-specific situational awareness.",
|
|
63
|
+
default_decay_lambda: LAMBDA.finance,
|
|
64
|
+
authority_hint: "medium",
|
|
65
|
+
date_policy: "strict",
|
|
66
|
+
failure_policy: "exclude",
|
|
67
|
+
recommended_surfaces: ["mcp", "rest", "sdk", "operator"],
|
|
68
|
+
}),
|
|
69
|
+
jobs_opportunities: profile({
|
|
70
|
+
profile_id: "jobs_opportunities",
|
|
71
|
+
source_types: ["jobs"],
|
|
72
|
+
purpose: "Job listings, openings, hiring signals, and opportunity windows.",
|
|
73
|
+
default_decay_lambda: LAMBDA.jobs,
|
|
74
|
+
authority_hint: "medium",
|
|
75
|
+
date_policy: "strict",
|
|
76
|
+
failure_policy: "downgrade",
|
|
77
|
+
recommended_surfaces: ["mcp", "rest", "sdk", "cli", "operator"],
|
|
78
|
+
}),
|
|
79
|
+
government_regulatory: profile({
|
|
80
|
+
profile_id: "government_regulatory",
|
|
81
|
+
source_types: ["govcontracts", "sec_filings", "gebiz", "gdelt", "gov_landscape"],
|
|
82
|
+
purpose: "Public-sector contracts, official filings, tenders, regulatory disclosures, and global news intelligence.",
|
|
83
|
+
default_decay_lambda: LAMBDA.govcontracts,
|
|
84
|
+
authority_hint: "high",
|
|
85
|
+
date_policy: "strict",
|
|
86
|
+
failure_policy: "warn",
|
|
87
|
+
recommended_surfaces: ["mcp", "rest", "sdk", "operator"],
|
|
88
|
+
}),
|
|
89
|
+
company_intel: profile({
|
|
90
|
+
profile_id: "company_intel",
|
|
91
|
+
source_types: ["yc", "company_landscape"],
|
|
92
|
+
purpose: "Company research, product velocity, ecosystem activity, and competitive context.",
|
|
93
|
+
default_decay_lambda: LAMBDA.company_landscape,
|
|
94
|
+
authority_hint: "medium",
|
|
95
|
+
date_policy: "balanced",
|
|
96
|
+
failure_policy: "downgrade",
|
|
97
|
+
recommended_surfaces: ["mcp", "rest", "sdk", "operator"],
|
|
98
|
+
}),
|
|
99
|
+
composite_landscape: profile({
|
|
100
|
+
profile_id: "composite_landscape",
|
|
101
|
+
source_types: ["landscape", "idea_landscape", "gov_landscape", "finance_landscape", "company_landscape"],
|
|
102
|
+
purpose: "Multi-source validation and idea, company, market, government, or finance landscape checks.",
|
|
103
|
+
default_decay_lambda: LAMBDA.landscape,
|
|
104
|
+
authority_hint: "medium",
|
|
105
|
+
date_policy: "balanced",
|
|
106
|
+
failure_policy: "warn",
|
|
107
|
+
recommended_surfaces: ["mcp", "rest", "sdk", "operator"],
|
|
108
|
+
}),
|
|
109
|
+
local_custom: profile({
|
|
110
|
+
profile_id: "local_custom",
|
|
111
|
+
source_types: ["local_custom", "user_provided", "custom"],
|
|
112
|
+
purpose: "User-provided content and custom signals supplied explicitly by a host or caller.",
|
|
113
|
+
default_decay_lambda: LAMBDA.default,
|
|
114
|
+
authority_hint: "medium",
|
|
115
|
+
date_policy: "balanced",
|
|
116
|
+
failure_policy: "warn",
|
|
117
|
+
recommended_surfaces: ["rest", "sdk", "cli", "operator"],
|
|
118
|
+
}),
|
|
119
|
+
});
|
|
120
|
+
export function listSourceProfiles() {
|
|
121
|
+
return Object.values(BUILT_IN_SOURCE_PROFILES).map(copyProfile);
|
|
122
|
+
}
|
|
123
|
+
export function getSourceProfile(profileId) {
|
|
124
|
+
const profile = BUILT_IN_SOURCE_PROFILES[profileId];
|
|
125
|
+
return profile ? copyProfile(profile) : undefined;
|
|
126
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const DATE_CONFIDENCE_FACTORS = {
|
|
2
|
+
high: 1.0,
|
|
3
|
+
medium: 0.75,
|
|
4
|
+
low: 0.4,
|
|
5
|
+
unknown: 0.0,
|
|
6
|
+
};
|
|
7
|
+
const STATUS_FACTORS = {
|
|
8
|
+
success: 1.0,
|
|
9
|
+
partial: 0.65,
|
|
10
|
+
stale: 0.4,
|
|
11
|
+
failed: 0.0,
|
|
12
|
+
unknown: 0.5,
|
|
13
|
+
};
|
|
14
|
+
function clampRelevance(value, reasons) {
|
|
15
|
+
if (!Number.isFinite(value)) {
|
|
16
|
+
reasons.push("contextual relevance was not finite; clamped to 0");
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
if (value > 100) {
|
|
20
|
+
reasons.push("contextual relevance exceeded 100; clamped to 100");
|
|
21
|
+
return 100;
|
|
22
|
+
}
|
|
23
|
+
if (value < 0) {
|
|
24
|
+
reasons.push("contextual relevance was below 0; clamped to 0");
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function safeLambda(value, reasons) {
|
|
30
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
31
|
+
reasons.push("lambda was invalid; clamped to 0");
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function safeAgeHours(value, reasons) {
|
|
37
|
+
if (!Number.isFinite(value)) {
|
|
38
|
+
reasons.push("ageHours was not finite; clamped to 0");
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
if (value < 0) {
|
|
42
|
+
reasons.push("ageHours was negative; clamped to 0");
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
export function calculateContextUtility(input) {
|
|
48
|
+
const reasons = [];
|
|
49
|
+
const contextualRelevance = clampRelevance(input.contextualRelevance, reasons);
|
|
50
|
+
const lambda = safeLambda(input.lambda, reasons);
|
|
51
|
+
const ageHours = safeAgeHours(input.ageHours, reasons);
|
|
52
|
+
const dateConfidence = input.dateConfidence ?? "unknown";
|
|
53
|
+
const status = input.status ?? "unknown";
|
|
54
|
+
const decayFactor = Math.exp(-lambda * ageHours);
|
|
55
|
+
const dateConfidenceFactor = DATE_CONFIDENCE_FACTORS[dateConfidence];
|
|
56
|
+
const statusFactor = STATUS_FACTORS[status];
|
|
57
|
+
if (dateConfidence === "medium") {
|
|
58
|
+
reasons.push("timestamp confidence is medium; utility reduced");
|
|
59
|
+
}
|
|
60
|
+
else if (dateConfidence === "low") {
|
|
61
|
+
reasons.push("timestamp confidence is low; utility reduced");
|
|
62
|
+
}
|
|
63
|
+
else if (dateConfidence === "unknown") {
|
|
64
|
+
reasons.push("timestamp confidence is unknown; utility reduced to zero");
|
|
65
|
+
}
|
|
66
|
+
if (status === "partial") {
|
|
67
|
+
reasons.push("signal status is partial; utility reduced");
|
|
68
|
+
}
|
|
69
|
+
else if (status === "stale") {
|
|
70
|
+
reasons.push("signal status is stale; utility reduced");
|
|
71
|
+
}
|
|
72
|
+
else if (status === "failed") {
|
|
73
|
+
reasons.push("signal status is failed; utility reduced to zero");
|
|
74
|
+
}
|
|
75
|
+
else if (status === "unknown") {
|
|
76
|
+
reasons.push("signal status is unknown; utility reduced");
|
|
77
|
+
}
|
|
78
|
+
const score = Math.min(100, Math.max(0, contextualRelevance * decayFactor * dateConfidenceFactor * statusFactor));
|
|
79
|
+
return {
|
|
80
|
+
score,
|
|
81
|
+
contextualRelevance,
|
|
82
|
+
decayFactor,
|
|
83
|
+
dateConfidenceFactor,
|
|
84
|
+
statusFactor,
|
|
85
|
+
lambda,
|
|
86
|
+
ageHours,
|
|
87
|
+
status,
|
|
88
|
+
reasons,
|
|
89
|
+
};
|
|
90
|
+
}
|