@4da/mcp-server 5.0.2 → 5.0.4
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/README.md +3 -3
- package/dist/auth-context.d.ts +0 -1
- package/dist/auth-context.js +0 -1
- package/dist/auth.d.ts +0 -1
- package/dist/auth.js +0 -1
- package/dist/db.d.ts +17 -2
- package/dist/db.js +104 -67
- package/dist/doctor.d.ts +0 -1
- package/dist/doctor.js +17 -15
- package/dist/embeddings.d.ts +0 -1
- package/dist/embeddings.js +0 -1
- package/dist/http-transport.d.ts +0 -1
- package/dist/http-transport.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +17 -5
- package/dist/live/cache.d.ts +0 -1
- package/dist/live/cache.js +0 -1
- package/dist/live/crates-registry.d.ts +0 -1
- package/dist/live/crates-registry.js +5 -8
- package/dist/live/cvss.d.ts +0 -1
- package/dist/live/cvss.js +0 -1
- package/dist/live/go-registry.d.ts +0 -1
- package/dist/live/go-registry.js +4 -9
- package/dist/live/hn-fetcher.d.ts +0 -1
- package/dist/live/hn-fetcher.js +23 -11
- package/dist/live/http-utils.d.ts +0 -1
- package/dist/live/http-utils.js +0 -1
- package/dist/live/index.d.ts +0 -1
- package/dist/live/index.js +0 -1
- package/dist/live/npm-registry.d.ts +0 -1
- package/dist/live/npm-registry.js +4 -8
- package/dist/live/osv-scanner.d.ts +20 -2
- package/dist/live/osv-scanner.js +82 -9
- package/dist/live/platform.d.ts +0 -1
- package/dist/live/platform.js +0 -1
- package/dist/live/pypi-registry.d.ts +0 -1
- package/dist/live/pypi-registry.js +7 -10
- package/dist/live/rate-limiter.d.ts +0 -1
- package/dist/live/rate-limiter.js +0 -1
- package/dist/live/semver-utils.d.ts +11 -1
- package/dist/live/semver-utils.js +14 -1
- package/dist/live/types.d.ts +0 -1
- package/dist/live/types.js +0 -1
- package/dist/live/version-resolver.d.ts +0 -1
- package/dist/live/version-resolver.js +0 -1
- package/dist/live-singleton.d.ts +0 -1
- package/dist/live-singleton.js +0 -1
- package/dist/project-scanner.d.ts +0 -1
- package/dist/project-scanner.js +0 -1
- package/dist/schema-registry.d.ts +17 -8
- package/dist/schema-registry.js +54 -9
- package/dist/schemas/developer-dna.json +1 -1
- package/dist/schemas/get-context.json +2 -2
- package/dist/schemas/get-relevant-content.json +1 -1
- package/dist/schemas/record-feedback.json +1 -1
- package/dist/setup.d.ts +0 -1
- package/dist/setup.js +0 -1
- package/dist/tool-dispatch.d.ts +0 -1
- package/dist/tool-dispatch.js +21 -2
- package/dist/tools/agent-memory.d.ts +0 -1
- package/dist/tools/agent-memory.js +0 -1
- package/dist/tools/decision-enforcement.d.ts +0 -1
- package/dist/tools/decision-enforcement.js +0 -1
- package/dist/tools/decision-memory.d.ts +0 -1
- package/dist/tools/decision-memory.js +0 -1
- package/dist/tools/decision-recall.d.ts +0 -1
- package/dist/tools/decision-recall.js +0 -1
- package/dist/tools/dependency-health.d.ts +0 -1
- package/dist/tools/dependency-health.js +31 -5
- package/dist/tools/developer-dna.d.ts +3 -3
- package/dist/tools/developer-dna.js +63 -47
- package/dist/tools/ecosystem-pulse.d.ts +7 -2
- package/dist/tools/ecosystem-pulse.js +39 -3
- package/dist/tools/get-actionable-signals.d.ts +0 -1
- package/dist/tools/get-actionable-signals.js +0 -1
- package/dist/tools/get-context.d.ts +0 -1
- package/dist/tools/get-context.js +2 -3
- package/dist/tools/get-relevant-content.d.ts +0 -1
- package/dist/tools/get-relevant-content.js +1 -3
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -1
- package/dist/tools/knowledge-gaps.d.ts +0 -1
- package/dist/tools/knowledge-gaps.js +79 -17
- package/dist/tools/recall.d.ts +0 -1
- package/dist/tools/recall.js +0 -1
- package/dist/tools/record-feedback.d.ts +1 -2
- package/dist/tools/record-feedback.js +7 -8
- package/dist/tools/upgrade-planner.d.ts +0 -1
- package/dist/tools/upgrade-planner.js +15 -3
- package/dist/tools/vulnerability-scan.d.ts +0 -1
- package/dist/tools/vulnerability-scan.js +0 -1
- package/dist/tools/what-should-i-know.d.ts +0 -1
- package/dist/tools/what-should-i-know.js +6 -3
- package/dist/types.d.ts +2 -3
- package/dist/types.js +0 -1
- package/package.json +37 -11
|
@@ -210,4 +210,3 @@ export function executeCheckDecisionAlignment(db, params) {
|
|
|
210
210
|
// Provider configured -> hybrid retrieval (returns a Promise; dispatch awaits).
|
|
211
211
|
return hybridDecisionRecall(db, query, candidates, 50, config).then(({ ranked, semanticById, recall_mode }) => assembleAlignment(params, ranked, semanticById, recall_mode));
|
|
212
212
|
}
|
|
213
|
-
//# sourceMappingURL=decision-enforcement.js.map
|
|
@@ -56,4 +56,3 @@ export declare function decisionEmbedText(row: DecisionLike): string;
|
|
|
56
56
|
* be embedded.
|
|
57
57
|
*/
|
|
58
58
|
export declare function hybridDecisionRecall<T extends DecisionLike>(db: FourDADatabase, query: string, rows: T[], limit: number, config: EmbeddingConfig): Promise<DecisionRecallResult<T>>;
|
|
59
|
-
//# sourceMappingURL=decision-recall.d.ts.map
|
|
@@ -42,4 +42,3 @@ export declare const dependencyHealthTool: {
|
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
44
|
export declare function executeDependencyHealth(_db: FourDADatabase, params: DependencyHealthParams, liveIntel: LiveIntelligence | null): Promise<DependencyHealthResult>;
|
|
45
|
-
//# sourceMappingURL=dependency-health.d.ts.map
|
|
@@ -112,11 +112,38 @@ export async function executeDependencyHealth(_db, params, liveIntel) {
|
|
|
112
112
|
});
|
|
113
113
|
const limit = params.limit ?? 50;
|
|
114
114
|
const limited = sorted.slice(0, limit);
|
|
115
|
-
// Health score: 100 = perfect
|
|
115
|
+
// Health score: 100 = perfect. Proportional and severity-weighted — the old
|
|
116
|
+
// absolute-count formula (15 points per vulnerable package, 2 per outdated,
|
|
117
|
+
// regardless of severity, degree, or project size) pinned any real-sized
|
|
118
|
+
// project at 0/100: a patch-behind dep cost the same as a critical CVE and
|
|
119
|
+
// the number informed nothing.
|
|
116
120
|
const total = registryData.length || 1;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
// Vulnerabilities: severity-weighted; transitive findings at one-third
|
|
122
|
+
// weight (their fix arrives via a parent bump — the direct surface is what
|
|
123
|
+
// the user controls directly).
|
|
124
|
+
const sevWeight = { critical: 20, high: 10, medium: 4, low: 1 };
|
|
125
|
+
let vulnPenaltyRaw = 0;
|
|
126
|
+
if (vulnResult) {
|
|
127
|
+
for (const v of vulnResult.vulnerabilities) {
|
|
128
|
+
const w = sevWeight[v.severity] ?? 2; // "unknown" (mostly unmaintained-notices) counts low
|
|
129
|
+
vulnPenaltyRaw += v.isDirect ? w : w / 3;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const vulnPenalty = Math.min(Math.round(vulnPenaltyRaw), 55);
|
|
133
|
+
// Staleness: degree-weighted and proportional to the tracked set — a major
|
|
134
|
+
// behind is a real liability, a patch behind is routine drift.
|
|
135
|
+
let stalenessWeight = 0;
|
|
136
|
+
for (const dep of registryData) {
|
|
137
|
+
const label = dep.versionsBehind?.label;
|
|
138
|
+
if (label === "major")
|
|
139
|
+
stalenessWeight += 1;
|
|
140
|
+
else if (label === "minor")
|
|
141
|
+
stalenessWeight += 0.35;
|
|
142
|
+
else if (label === "patch")
|
|
143
|
+
stalenessWeight += 0.1;
|
|
144
|
+
}
|
|
145
|
+
const outdatedPenalty = Math.min(30, Math.round((stalenessWeight / total) * 60));
|
|
146
|
+
const deprecatedPenalty = Math.min(deprecated * 8, 15);
|
|
120
147
|
const healthScore = Math.max(0, 100 - vulnPenalty - deprecatedPenalty - outdatedPenalty);
|
|
121
148
|
const vulnSummary = vulnResult ? {
|
|
122
149
|
critical: vulnResult.bySeverity.critical,
|
|
@@ -151,4 +178,3 @@ export async function executeDependencyHealth(_db, params, liveIntel) {
|
|
|
151
178
|
cached: false,
|
|
152
179
|
};
|
|
153
180
|
}
|
|
154
|
-
//# sourceMappingURL=dependency-health.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* developer_dna tool
|
|
3
3
|
*
|
|
4
4
|
* Export a comprehensive Developer DNA profile built from project scans,
|
|
5
|
-
*
|
|
5
|
+
* explicit interests, and local interaction history.
|
|
6
6
|
*/
|
|
7
7
|
import type { FourDADatabase } from "../db.js";
|
|
8
8
|
export interface DeveloperDnaParams {
|
|
@@ -46,7 +46,8 @@ interface EngagedTopic {
|
|
|
46
46
|
interface BlindSpotEntry {
|
|
47
47
|
dependency: string;
|
|
48
48
|
severity: string;
|
|
49
|
-
|
|
49
|
+
/** Days since last engagement with content about this dep; null = never engaged. */
|
|
50
|
+
days_stale: number | null;
|
|
50
51
|
}
|
|
51
52
|
interface SourceEngagement {
|
|
52
53
|
source_type: string;
|
|
@@ -76,4 +77,3 @@ interface DeveloperDna {
|
|
|
76
77
|
}
|
|
77
78
|
export declare function executeDeveloperDna(db: FourDADatabase, params: DeveloperDnaParams): DeveloperDna;
|
|
78
79
|
export {};
|
|
79
|
-
//# sourceMappingURL=developer-dna.d.ts.map
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* developer_dna tool
|
|
4
4
|
*
|
|
5
5
|
* Export a comprehensive Developer DNA profile built from project scans,
|
|
6
|
-
*
|
|
6
|
+
* explicit interests, and local interaction history.
|
|
7
7
|
*/
|
|
8
8
|
// =============================================================================
|
|
9
9
|
// Tool Definition
|
|
10
10
|
// =============================================================================
|
|
11
11
|
export const developerDnaTool = {
|
|
12
12
|
name: "developer_dna",
|
|
13
|
-
description: `Export your Developer DNA — a comprehensive profile of your tech identity built from project scans,
|
|
13
|
+
description: `Export your Developer DNA — a comprehensive profile of your tech identity built from project scans, explicit interests, and local interaction history. Includes primary stack, adjacent tech, top dependencies, engaged topics, blind spots, source engagement rates, and aggregate stats.`,
|
|
14
14
|
inputSchema: {
|
|
15
15
|
type: "object",
|
|
16
16
|
properties: {
|
|
@@ -135,24 +135,40 @@ export function executeDeveloperDna(db, params) {
|
|
|
135
135
|
// table may not exist
|
|
136
136
|
}
|
|
137
137
|
// -------------------------------------------------------------------------
|
|
138
|
-
// 5. Top Engaged Topics — from topic_affinities
|
|
138
|
+
// 5. Top Engaged Topics — from explicit interactions (topic_affinities was
|
|
139
|
+
// dropped with the implicit-capture system in desktop schema 105; querying
|
|
140
|
+
// it returned empty forever).
|
|
139
141
|
// -------------------------------------------------------------------------
|
|
140
142
|
let topEngagedTopics = [];
|
|
141
143
|
try {
|
|
142
|
-
const
|
|
143
|
-
.prepare(`SELECT
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
144
|
+
const interactionRows = rawDb
|
|
145
|
+
.prepare(`SELECT item_topics FROM interactions
|
|
146
|
+
WHERE action_type IN ('click', 'save') AND item_topics IS NOT NULL`)
|
|
147
|
+
.all();
|
|
148
|
+
const topicCounts = new Map();
|
|
149
|
+
for (const row of interactionRows) {
|
|
150
|
+
try {
|
|
151
|
+
const topics = JSON.parse(row.item_topics);
|
|
152
|
+
if (!Array.isArray(topics))
|
|
153
|
+
continue;
|
|
154
|
+
for (const t of topics) {
|
|
155
|
+
if (typeof t === "string" && t.length > 0) {
|
|
156
|
+
topicCounts.set(t, (topicCounts.get(t) || 0) + 1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Malformed topic JSON on one row — skip it
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const totalPositive = [...topicCounts.values()].reduce((a, b) => a + b, 0);
|
|
165
|
+
topEngagedTopics = [...topicCounts.entries()]
|
|
166
|
+
.sort((a, b) => b[1] - a[1])
|
|
167
|
+
.slice(0, maxTopics)
|
|
168
|
+
.map(([topic, count]) => ({
|
|
169
|
+
topic,
|
|
170
|
+
interactions: count,
|
|
171
|
+
percent_of_total: totalPositive > 0 ? Math.round((count / totalPositive) * 10000) / 100 : 0,
|
|
156
172
|
}));
|
|
157
173
|
}
|
|
158
174
|
catch {
|
|
@@ -175,25 +191,9 @@ export function executeDeveloperDna(db, params) {
|
|
|
175
191
|
const now = Date.now();
|
|
176
192
|
for (const dep of topDependencies) {
|
|
177
193
|
const depLower = dep.name.toLowerCase();
|
|
178
|
-
// Check if any interaction item's source mentions this dependency
|
|
179
|
-
// Use a simpler heuristic: check if the dependency name appears in
|
|
180
|
-
// topic_affinities with positive signals
|
|
181
|
-
let lastEngagement = null;
|
|
182
|
-
try {
|
|
183
|
-
const affinityRow = rawDb
|
|
184
|
-
.prepare(`SELECT topic, positive_signals
|
|
185
|
-
FROM topic_affinities
|
|
186
|
-
WHERE LOWER(topic) = ?
|
|
187
|
-
AND positive_signals > 0`)
|
|
188
|
-
.get(depLower);
|
|
189
|
-
if (affinityRow) {
|
|
190
|
-
continue; // Has engagement, not a blind spot
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
// Ignore
|
|
195
|
-
}
|
|
196
194
|
// Check for any source_items mentioning this dep that have interactions
|
|
195
|
+
// (the old topic_affinities shortcut queried a table dropped in schema
|
|
196
|
+
// 105 — it never fired).
|
|
197
197
|
try {
|
|
198
198
|
// Escape LIKE wildcards so a name containing % or _ matches literally.
|
|
199
199
|
const likeDep = `%${depLower.replace(/[\\%_]/g, (c) => "\\" + c)}%`;
|
|
@@ -218,11 +218,12 @@ export function executeDeveloperDna(db, params) {
|
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
else {
|
|
221
|
-
// Never interacted with content about this dependency
|
|
221
|
+
// Never interacted with content about this dependency — null, not
|
|
222
|
+
// a -1 sentinel leaking into user-facing output.
|
|
222
223
|
blindSpots.push({
|
|
223
224
|
dependency: dep.name,
|
|
224
225
|
severity: "low",
|
|
225
|
-
days_stale:
|
|
226
|
+
days_stale: null,
|
|
226
227
|
});
|
|
227
228
|
}
|
|
228
229
|
}
|
|
@@ -294,14 +295,28 @@ export function executeDeveloperDna(db, params) {
|
|
|
294
295
|
catch {
|
|
295
296
|
// table may not exist
|
|
296
297
|
}
|
|
298
|
+
// total_relevant / rejection_rate describe the PIPELINE's judgment, so they
|
|
299
|
+
// must come from its persisted verdicts — the old interaction-based count
|
|
300
|
+
// reported total_relevant: 0 / rejection_rate: 1.0 for any passive user,
|
|
301
|
+
// contradicting a corpus with hundreds of feed-relevant items.
|
|
302
|
+
let judgedCount = 0;
|
|
297
303
|
try {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
.
|
|
304
|
-
|
|
304
|
+
if (db.hasColumn("source_items", "feed_relevant")) {
|
|
305
|
+
const row = rawDb
|
|
306
|
+
.prepare(`SELECT COUNT(*) as judged, SUM(CASE WHEN feed_relevant = 1 THEN 1 ELSE 0 END) as relevant
|
|
307
|
+
FROM source_items WHERE feed_relevant IS NOT NULL`)
|
|
308
|
+
.get();
|
|
309
|
+
judgedCount = row.judged;
|
|
310
|
+
totalRelevant = row.relevant ?? 0;
|
|
311
|
+
}
|
|
312
|
+
else if (db.hasColumn("source_items", "relevance_score")) {
|
|
313
|
+
const row = rawDb
|
|
314
|
+
.prepare(`SELECT COUNT(*) as judged, SUM(CASE WHEN relevance_score >= 0.4 THEN 1 ELSE 0 END) as relevant
|
|
315
|
+
FROM source_items WHERE relevance_score IS NOT NULL`)
|
|
316
|
+
.get();
|
|
317
|
+
judgedCount = row.judged;
|
|
318
|
+
totalRelevant = row.relevant ?? 0;
|
|
319
|
+
}
|
|
305
320
|
}
|
|
306
321
|
catch {
|
|
307
322
|
// table may not exist
|
|
@@ -336,8 +351,10 @@ export function executeDeveloperDna(db, params) {
|
|
|
336
351
|
catch {
|
|
337
352
|
// table may not exist
|
|
338
353
|
}
|
|
339
|
-
|
|
340
|
-
|
|
354
|
+
// Rejection rate over the JUDGED set — unjudged items are "not yet curated",
|
|
355
|
+
// not rejected.
|
|
356
|
+
const rejectionRate = judgedCount > 0
|
|
357
|
+
? Math.round(((judgedCount - totalRelevant) / judgedCount) * 10000) / 10000
|
|
341
358
|
: 0;
|
|
342
359
|
const stats = {
|
|
343
360
|
total_items_processed: totalItemsProcessed,
|
|
@@ -406,4 +423,3 @@ function buildIdentitySummary(primaryStack, adjacentTech, interests, engagedTopi
|
|
|
406
423
|
}
|
|
407
424
|
return parts.join(". ") + ".";
|
|
408
425
|
}
|
|
409
|
-
//# sourceMappingURL=developer-dna.js.map
|
|
@@ -43,6 +43,11 @@ export declare const ecosystemPulseTool: {
|
|
|
43
43
|
};
|
|
44
44
|
};
|
|
45
45
|
};
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Derive the tech stack used to filter HN headlines from the database:
|
|
48
|
+
* explicit tech_stack entries plus the languages of tracked dependencies.
|
|
49
|
+
* Works on both the desktop DB and the standalone minimal schema.
|
|
50
|
+
*/
|
|
51
|
+
export declare function deriveTechStackForHeadlines(db: FourDADatabase): string[];
|
|
52
|
+
export declare function executeEcosystemPulse(db: FourDADatabase, params: EcosystemPulseParams, liveIntel: LiveIntelligence | null): Promise<EcosystemPulseResult>;
|
|
47
53
|
export {};
|
|
48
|
-
//# sourceMappingURL=ecosystem-pulse.d.ts.map
|
|
@@ -23,7 +23,35 @@ export const ecosystemPulseTool = {
|
|
|
23
23
|
},
|
|
24
24
|
},
|
|
25
25
|
};
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Derive the tech stack used to filter HN headlines from the database:
|
|
28
|
+
* explicit tech_stack entries plus the languages of tracked dependencies.
|
|
29
|
+
* Works on both the desktop DB and the standalone minimal schema.
|
|
30
|
+
*/
|
|
31
|
+
export function deriveTechStackForHeadlines(db) {
|
|
32
|
+
const stack = new Set();
|
|
33
|
+
const rawDb = db.getRawDb();
|
|
34
|
+
try {
|
|
35
|
+
for (const row of rawDb.prepare("SELECT technology FROM tech_stack").all()) {
|
|
36
|
+
if (row.technology)
|
|
37
|
+
stack.add(row.technology.toLowerCase());
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// tech_stack may not exist on exotic DBs — languages below still apply.
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
for (const row of rawDb.prepare("SELECT DISTINCT language FROM project_dependencies").all()) {
|
|
45
|
+
if (row.language)
|
|
46
|
+
stack.add(row.language.toLowerCase());
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// project_dependencies may not exist — an empty stack yields an honest empty result.
|
|
51
|
+
}
|
|
52
|
+
return [...stack];
|
|
53
|
+
}
|
|
54
|
+
export async function executeEcosystemPulse(db, params, liveIntel) {
|
|
27
55
|
if (!liveIntel) {
|
|
28
56
|
return {
|
|
29
57
|
headlines: [],
|
|
@@ -32,7 +60,16 @@ export function executeEcosystemPulse(_db, params, liveIntel) {
|
|
|
32
60
|
note: "Live intelligence not available. Set FOURDA_OFFLINE=false and restart.",
|
|
33
61
|
};
|
|
34
62
|
}
|
|
35
|
-
|
|
63
|
+
// The startup prefetch only ever ran in standalone mode, so full-DB servers
|
|
64
|
+
// returned an empty cache forever. Fetch on demand when the cache is empty —
|
|
65
|
+
// the cache still serves warm repeat calls.
|
|
66
|
+
let headlines = liveIntel.getHeadlines();
|
|
67
|
+
if (headlines.length === 0 && liveIntel.isEnabled()) {
|
|
68
|
+
const techStack = deriveTechStackForHeadlines(db);
|
|
69
|
+
if (techStack.length > 0) {
|
|
70
|
+
headlines = await liveIntel.fetchHeadlines(techStack);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
36
73
|
const minPoints = params.min_points ?? 0;
|
|
37
74
|
const limit = params.limit ?? 15;
|
|
38
75
|
const filtered = headlines
|
|
@@ -57,4 +94,3 @@ export function executeEcosystemPulse(_db, params, liveIntel) {
|
|
|
57
94
|
: `${filtered.length} headline${filtered.length !== 1 ? "s" : ""} relevant to your tech stack.`,
|
|
58
95
|
};
|
|
59
96
|
}
|
|
60
|
-
//# sourceMappingURL=ecosystem-pulse.js.map
|
|
@@ -359,4 +359,3 @@ export function executeGetActionableSignals(db, params) {
|
|
|
359
359
|
...(signals.length === 0 && items.length === 0 ? { note: "No source items in the database. Run the 4DA desktop app to fetch content, or use vulnerability_scan to check dependencies directly." } : {}),
|
|
360
360
|
};
|
|
361
361
|
}
|
|
362
|
-
//# sourceMappingURL=get-actionable-signals.js.map
|
|
@@ -15,7 +15,7 @@ export const getContextTool = {
|
|
|
15
15
|
Returns information about:
|
|
16
16
|
- Static Identity: User-declared role, tech stack, domains, interests, and exclusions
|
|
17
17
|
- ACE Context (optional): Auto-detected technologies and active topics from recent activity
|
|
18
|
-
- Learned Preferences (optional):
|
|
18
|
+
- Learned Preferences (optional): retained compatibility field; implicit preference learning no longer contributes to scoring
|
|
19
19
|
|
|
20
20
|
Use this to understand what the user is working on and interested in.`,
|
|
21
21
|
inputSchema: {
|
|
@@ -28,7 +28,7 @@ Use this to understand what the user is working on and interested in.`,
|
|
|
28
28
|
},
|
|
29
29
|
include_learned: {
|
|
30
30
|
type: "boolean",
|
|
31
|
-
description: "Include learned
|
|
31
|
+
description: "Include the retained learned-preferences compatibility field. Default: true",
|
|
32
32
|
default: true,
|
|
33
33
|
},
|
|
34
34
|
},
|
|
@@ -58,4 +58,3 @@ export function executeGetContext(db, params) {
|
|
|
58
58
|
}
|
|
59
59
|
return context;
|
|
60
60
|
}
|
|
61
|
-
//# sourceMappingURL=get-context.js.map
|
|
@@ -41,4 +41,3 @@ export declare const getRelevantContentTool: {
|
|
|
41
41
|
* Execute the get_relevant_content tool
|
|
42
42
|
*/
|
|
43
43
|
export declare function executeGetRelevantContent(db: FourDADatabase, params: GetRelevantContentParams): RelevantItem[];
|
|
44
|
-
//# sourceMappingURL=get-relevant-content.d.ts.map
|
|
@@ -11,12 +11,11 @@ export const getRelevantContentTool = {
|
|
|
11
11
|
name: "get_relevant_content",
|
|
12
12
|
description: `Get filtered relevant content from 4DA's personalized feed.
|
|
13
13
|
|
|
14
|
-
Returns content items that match the user's interests, tech stack, and
|
|
14
|
+
Returns content items that match the user's explicit interests, tech stack, and ACE-detected context.
|
|
15
15
|
Items are scored based on:
|
|
16
16
|
- Explicit interests declared by the user
|
|
17
17
|
- Tech stack and domains
|
|
18
18
|
- ACE-detected context (recent files, projects, git activity)
|
|
19
|
-
- Learned preferences from past interactions
|
|
20
19
|
|
|
21
20
|
Each item includes necessity fields (necessity_score, necessity_reason, necessity_category, necessity_urgency) indicating how critical the item is — e.g., security vulnerabilities affecting your deps, breaking changes in your stack, or deprecation notices. These are populated from the full PASIFA analysis pipeline when available.
|
|
22
21
|
|
|
@@ -67,4 +66,3 @@ export function executeGetRelevantContent(db, params) {
|
|
|
67
66
|
}
|
|
68
67
|
return items;
|
|
69
68
|
}
|
|
70
|
-
//# sourceMappingURL=get-relevant-content.js.map
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -21,4 +21,3 @@ export { decisionMemoryTool, executeDecisionMemory, } from "./decision-memory.js
|
|
|
21
21
|
export { checkDecisionAlignmentTool, executeCheckDecisionAlignment, } from "./decision-enforcement.js";
|
|
22
22
|
export { agentMemoryTool, executeAgentMemory, } from "./agent-memory.js";
|
|
23
23
|
export { developerDnaTool, executeDeveloperDna, } from "./developer-dna.js";
|
|
24
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/tools/index.js
CHANGED
|
@@ -27,4 +27,3 @@ export { checkDecisionAlignmentTool, executeCheckDecisionAlignment, } from "./de
|
|
|
27
27
|
export { agentMemoryTool, executeAgentMemory, } from "./agent-memory.js";
|
|
28
28
|
// Identity
|
|
29
29
|
export { developerDnaTool, executeDeveloperDna, } from "./developer-dna.js";
|
|
30
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -6,7 +6,18 @@
|
|
|
6
6
|
*/
|
|
7
7
|
// Word-boundary matching prevents "cve" matching inside "achieve", "receiver", etc.
|
|
8
8
|
function hasWordBoundary(text, term) {
|
|
9
|
-
const regex = new RegExp(`\\b${term}\\b`, 'i');
|
|
9
|
+
const regex = new RegExp(`\\b${escapeRegExp(term)}\\b`, 'i');
|
|
10
|
+
return regex.test(text);
|
|
11
|
+
}
|
|
12
|
+
// Package names can contain regex metacharacters (@scope/name, c++, next.js).
|
|
13
|
+
function escapeRegExp(s) {
|
|
14
|
+
return s.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
15
|
+
}
|
|
16
|
+
// True when `text` mentions the package name as a whole word. Word characters
|
|
17
|
+
// for this purpose include - and _ (so dep "hono" does NOT match "hono-shim",
|
|
18
|
+
// and never matches "in HONOr of"). @scope/name matches as the full literal.
|
|
19
|
+
function mentionsPackage(text, pkg) {
|
|
20
|
+
const regex = new RegExp(`(^|[^A-Za-z0-9_-])${escapeRegExp(pkg)}($|[^A-Za-z0-9_-])`, "i");
|
|
10
21
|
return regex.test(text);
|
|
11
22
|
}
|
|
12
23
|
// Escape SQL LIKE wildcards so a package name containing % or _ (npm names may
|
|
@@ -36,9 +47,16 @@ export const knowledgeGapsTool = {
|
|
|
36
47
|
};
|
|
37
48
|
export function executeKnowledgeGaps(db, params) {
|
|
38
49
|
const rawDb = db.getRawDb();
|
|
39
|
-
//
|
|
50
|
+
// Feature-detect optional columns: a pure-standalone database has no scoring
|
|
51
|
+
// pipeline (no relevance_score) and may predate content_type — the tool must
|
|
52
|
+
// degrade to word-boundary + recency grounding there, never throw.
|
|
53
|
+
const hasRelevance = db.hasColumn("source_items", "relevance_score");
|
|
54
|
+
const hasContentType = db.hasColumn("source_items", "content_type");
|
|
55
|
+
const hasIsDirect = db.hasColumn("project_dependencies", "is_direct");
|
|
56
|
+
// Direct dependencies only — a transitive dep's news is not the user's
|
|
57
|
+
// reading backlog. Dev deps stay in: a vitest or eslint advisory is real.
|
|
40
58
|
const deps = rawDb
|
|
41
|
-
.prepare(
|
|
59
|
+
.prepare(`SELECT package_name, version, project_path, language FROM project_dependencies ${hasIsDirect ? "WHERE is_direct = 1 " : ""}LIMIT 100`)
|
|
42
60
|
.all();
|
|
43
61
|
if (deps.length === 0) {
|
|
44
62
|
return {
|
|
@@ -47,41 +65,86 @@ export function executeKnowledgeGaps(db, params) {
|
|
|
47
65
|
};
|
|
48
66
|
}
|
|
49
67
|
const gaps = [];
|
|
68
|
+
const seenPackages = new Set();
|
|
50
69
|
for (const dep of deps) {
|
|
51
70
|
// Names shorter than 3 chars (e.g. "c", "go", "ws") match too many unrelated
|
|
52
71
|
// items via substring LIKE to be a trustworthy "mention" signal — skip them.
|
|
53
72
|
if (!dep.package_name || dep.package_name.length < 3)
|
|
54
73
|
continue;
|
|
74
|
+
// One gap per package: the same dep declared by several projects would
|
|
75
|
+
// otherwise repeat identical missed_items once per project_path.
|
|
76
|
+
const pkgKey = dep.package_name.toLowerCase();
|
|
77
|
+
if (seenPackages.has(pkgKey))
|
|
78
|
+
continue;
|
|
79
|
+
seenPackages.add(pkgKey);
|
|
55
80
|
// Find source items mentioning this dependency. Engagement is recorded by the
|
|
56
81
|
// app in interactions.item_id / .action_type (the canonical columns; the older
|
|
57
82
|
// source_item_id / action columns are unused), so the NOT-IN suppression must
|
|
58
83
|
// read those or it silently never fires.
|
|
84
|
+
//
|
|
85
|
+
// Grounding (each guard killed an observed false-positive class):
|
|
86
|
+
// - relevance_score >= 0.2: the scoring pipeline's above-noise band — drops
|
|
87
|
+
// off-topic chatter that merely contains the string (chocolate-bar class).
|
|
88
|
+
// - 30-day window: a "gap" is something you MISSED, not archaeology — a
|
|
89
|
+
// 2014 StackOverflow post is not missed intelligence.
|
|
90
|
+
// - LIKE is only a cheap candidate pre-filter; the real test is the
|
|
91
|
+
// word-boundary check below ("invite" must never evidence a vite gap).
|
|
92
|
+
// - feed_relevant is deliberately NOT filtered here: an item the feed gate
|
|
93
|
+
// rejected can still be a legitimate unread dep mention — surfacing those
|
|
94
|
+
// is this tool's niche. The relevance floor already excludes noise.
|
|
59
95
|
const pattern = `%${escapeLike(dep.package_name)}%`;
|
|
60
|
-
const
|
|
61
|
-
.prepare(`SELECT si.id, si.title, si.url, si.source_type, si.created_at
|
|
96
|
+
const candidates = rawDb
|
|
97
|
+
.prepare(`SELECT si.id, si.title, si.url, si.source_type, ${hasContentType ? "si.content_type" : "NULL AS content_type"}, si.created_at,
|
|
98
|
+
${hasRelevance ? "si.relevance_score" : "NULL AS relevance_score"}, substr(COALESCE(si.content, ''), 1, 2000) AS content_head
|
|
62
99
|
FROM source_items si
|
|
63
100
|
WHERE (si.title LIKE ? ESCAPE '\\' OR si.content LIKE ? ESCAPE '\\')
|
|
101
|
+
${hasRelevance ? "AND si.relevance_score IS NOT NULL AND si.relevance_score >= 0.2" : ""}
|
|
102
|
+
AND si.created_at >= datetime('now', '-30 days')
|
|
64
103
|
AND si.id NOT IN (SELECT item_id FROM interactions WHERE action_type IN ('click', 'save'))
|
|
65
|
-
ORDER BY si.created_at DESC LIMIT
|
|
104
|
+
ORDER BY si.created_at DESC LIMIT 25`)
|
|
66
105
|
.all(pattern, pattern);
|
|
106
|
+
// Word-boundary verification: the mention must be the package name as a
|
|
107
|
+
// whole word in the title or the content head, not a substring.
|
|
108
|
+
const mentionedItems = candidates
|
|
109
|
+
.filter((item) => mentionsPackage(item.title || "", dep.package_name) ||
|
|
110
|
+
mentionsPackage(item.content_head || "", dep.package_name))
|
|
111
|
+
.slice(0, 5);
|
|
67
112
|
if (mentionedItems.length > 0) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
113
|
+
const isAdvisory = (item) => item.source_type === "cve" ||
|
|
114
|
+
item.source_type === "osv" ||
|
|
115
|
+
item.content_type === "security_advisory";
|
|
116
|
+
const securityKeywords = (title) => hasWordBoundary(title, "cve") ||
|
|
117
|
+
hasWordBoundary(title, "security") ||
|
|
118
|
+
hasWordBoundary(title, "vulnerability");
|
|
119
|
+
// Graded honestly:
|
|
120
|
+
// - critical: an actual advisory whose TITLE names this dependency — the
|
|
121
|
+
// advisory is about the dep, not merely co-mentioning it in a body.
|
|
122
|
+
// - high: a security-keyword item whose title names the dep.
|
|
123
|
+
// - medium: 3+ recent unread mentions; low: 1-2.
|
|
124
|
+
const advisoryAboutDep = mentionedItems.some((item) => isAdvisory(item) && mentionsPackage(item.title || "", dep.package_name));
|
|
125
|
+
const securityTitleMention = mentionedItems.some((item) => securityKeywords(item.title || "") &&
|
|
126
|
+
mentionsPackage(item.title || "", dep.package_name));
|
|
127
|
+
const severity = advisoryAboutDep
|
|
73
128
|
? "critical"
|
|
74
|
-
:
|
|
129
|
+
: securityTitleMention
|
|
75
130
|
? "high"
|
|
76
|
-
:
|
|
131
|
+
: mentionedItems.length >= 3
|
|
132
|
+
? "medium"
|
|
133
|
+
: "low";
|
|
77
134
|
gaps.push({
|
|
78
135
|
dependency: dep.package_name,
|
|
79
136
|
version: dep.version,
|
|
80
137
|
project_path: dep.project_path,
|
|
81
138
|
language: dep.language,
|
|
82
|
-
missed_items: mentionedItems.map(item => ({
|
|
83
|
-
|
|
84
|
-
title: item.title && item.title.length > 120
|
|
139
|
+
missed_items: mentionedItems.map((item) => ({
|
|
140
|
+
id: item.id,
|
|
141
|
+
title: item.title && item.title.length > 120
|
|
142
|
+
? item.title.substring(0, 120) + "..."
|
|
143
|
+
: item.title,
|
|
144
|
+
url: item.url,
|
|
145
|
+
source_type: item.source_type,
|
|
146
|
+
created_at: item.created_at,
|
|
147
|
+
relevance_score: item.relevance_score,
|
|
85
148
|
})),
|
|
86
149
|
gap_severity: severity,
|
|
87
150
|
missed_count: mentionedItems.length,
|
|
@@ -101,4 +164,3 @@ export function executeKnowledgeGaps(db, params) {
|
|
|
101
164
|
summary: `${filtered.length} knowledge gaps across ${deps.length} tracked dependencies (showing top ${Math.min(filtered.length, maxGaps)})`,
|
|
102
165
|
};
|
|
103
166
|
}
|
|
104
|
-
//# sourceMappingURL=knowledge-gaps.js.map
|
package/dist/tools/recall.d.ts
CHANGED
package/dist/tools/recall.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* record_feedback tool
|
|
3
3
|
*
|
|
4
|
-
* Record user
|
|
4
|
+
* Record explicit user interaction history for an item.
|
|
5
5
|
*/
|
|
6
6
|
import type { FourDADatabase } from "../db.js";
|
|
7
7
|
import type { RecordFeedbackParams, FeedbackResult } from "../types.js";
|
|
@@ -36,4 +36,3 @@ export declare const recordFeedbackTool: {
|
|
|
36
36
|
* Execute the record_feedback tool
|
|
37
37
|
*/
|
|
38
38
|
export declare function executeRecordFeedback(db: FourDADatabase, params: RecordFeedbackParams): FeedbackResult;
|
|
39
|
-
//# sourceMappingURL=record-feedback.d.ts.map
|