@4da/mcp-server 6.0.0 → 6.0.1
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/CHANGELOG.md +31 -0
- package/README.md +6 -6
- package/dist/db.d.ts +1 -57
- package/dist/db.js +0 -269
- package/dist/types.d.ts +0 -135
- package/package.json +10 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 6.0.1 — 2026-10-04
|
|
4
|
+
|
|
5
|
+
### The server has its own repository
|
|
6
|
+
|
|
7
|
+
`@4da/mcp-server` now lives at
|
|
8
|
+
[github.com/4DA-Systems/4da-mcp-server](https://github.com/4DA-Systems/4da-mcp-server),
|
|
9
|
+
with its full history, instead of a folder of the 4DA desktop app's monorepo.
|
|
10
|
+
Issues, releases and the Claude Code plugin marketplace move with it:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
claude plugin marketplace add 4DA-Systems/4da-mcp-server
|
|
14
|
+
claude plugin install 4da@4da
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The package, its name in the MCP Registry (`io.github.4DA-Systems/4da-mcp-server`)
|
|
18
|
+
and every tool are unchanged.
|
|
19
|
+
|
|
20
|
+
- **Published with provenance.** Releases are built and published from this
|
|
21
|
+
repository's `release.yml` through npm trusted publishing, so each version
|
|
22
|
+
carries a SLSA provenance attestation tying it to the commit it was built from.
|
|
23
|
+
- **App-schema contract.** The server reads the desktop app's database, which
|
|
24
|
+
is now maintained in a different repository. `pnpm run contract` checks every
|
|
25
|
+
statement the server issues, and every column it probes for, against the
|
|
26
|
+
schema the app's migrations produce (`contract/app-schema.sql`, generated in
|
|
27
|
+
the app's CI). The app's CI runs the same check before a migration merges.
|
|
28
|
+
- **Removed dead code.** `explainRelevance`, `getSourceItem`,
|
|
29
|
+
`recordAgentFeedback` and `getAgentFeedbackStats` had no callers since their
|
|
30
|
+
tools were removed; the standalone schema no longer creates the unused
|
|
31
|
+
`agent_feedback` table.
|
|
32
|
+
- The Claude Code plugin pins the exact server version it was released with.
|
|
33
|
+
|
|
3
34
|
## 6.0.0 — 2026-10-02
|
|
4
35
|
|
|
5
36
|
### New: `upgrade_impact`
|
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@4da/mcp-server)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](https://nodejs.org/)
|
|
6
|
+
[](https://github.com/4DA-Systems/4da-mcp-server/actions/workflows/ci.yml)
|
|
7
7
|
|
|
8
8
|
**Upgrade intelligence for AI coding agents.** Before your agent bumps a dependency it learns what changes between the version you run and the one you want, which of your files that touches, and which vulnerabilities the move fixes, from your own lockfiles, on your machine. Plus vulnerability scanning at osv-scanner parity, ranked upgrade plans and decision memory. Zero config, no account.
|
|
9
9
|
|
|
@@ -38,7 +38,7 @@ claude mcp add 4da -- npx @4da/mcp-server
|
|
|
38
38
|
**As a Claude Code plugin** (the MCP server plus a hook):
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
claude plugin marketplace add 4DA-Systems/
|
|
41
|
+
claude plugin marketplace add 4DA-Systems/4da-mcp-server
|
|
42
42
|
claude plugin install 4da@4da
|
|
43
43
|
```
|
|
44
44
|
|
|
@@ -268,11 +268,11 @@ Any tool that supports [MCP](https://modelcontextprotocol.io): Claude Code, Clau
|
|
|
268
268
|
## Build from Source
|
|
269
269
|
|
|
270
270
|
```bash
|
|
271
|
-
git clone https://github.com/4DA-Systems/
|
|
272
|
-
cd
|
|
271
|
+
git clone https://github.com/4DA-Systems/4da-mcp-server.git
|
|
272
|
+
cd 4da-mcp-server
|
|
273
273
|
pnpm install
|
|
274
274
|
pnpm build
|
|
275
|
-
pnpm test #
|
|
275
|
+
pnpm test # offline
|
|
276
276
|
```
|
|
277
277
|
|
|
278
278
|
## License
|
package/dist/db.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type BetterSqlite3 from "better-sqlite3";
|
|
8
8
|
/** Null when better-sqlite3 can open a database here; otherwise why not, with the fix. */
|
|
9
9
|
export declare function nativeBindingProblem(): string | null;
|
|
10
|
-
import type {
|
|
10
|
+
import type { RelevantItem, UserContext, FeedbackAction, FeedbackResult } from "./types.js";
|
|
11
11
|
import type { ProjectScanResult } from "./project-scanner.js";
|
|
12
12
|
/**
|
|
13
13
|
* Where the deployed desktop app keeps its database, mirroring
|
|
@@ -193,10 +193,6 @@ export declare class FourDADatabase {
|
|
|
193
193
|
* Fallback: TypeScript keyword scoring for standalone mode (no Rust pipeline).
|
|
194
194
|
*/
|
|
195
195
|
private getRelevantContentFallback;
|
|
196
|
-
/**
|
|
197
|
-
* Get a single source item by ID and type
|
|
198
|
-
*/
|
|
199
|
-
getSourceItem(itemId: number, sourceType: string): SourceItem | null;
|
|
200
196
|
/**
|
|
201
197
|
* Look up persisted necessity scores for an item (written by Rust analysis pipeline).
|
|
202
198
|
* Returns defaults (0.0/null) if no necessity data exists yet.
|
|
@@ -206,10 +202,6 @@ export declare class FourDADatabase {
|
|
|
206
202
|
* Get the user's context (what 4DA knows about them)
|
|
207
203
|
*/
|
|
208
204
|
getUserContext(includeAce?: boolean, includeLearned?: boolean): UserContext;
|
|
209
|
-
/**
|
|
210
|
-
* Explain why an item was considered relevant
|
|
211
|
-
*/
|
|
212
|
-
explainRelevance(itemId: number, sourceType: string): RelevanceExplanation | null;
|
|
213
205
|
/**
|
|
214
206
|
* Record user feedback on an item
|
|
215
207
|
*/
|
|
@@ -221,54 +213,6 @@ export declare class FourDADatabase {
|
|
|
221
213
|
* so against the desktop database this is a no-op.
|
|
222
214
|
*/
|
|
223
215
|
private ensureFeedbackTable;
|
|
224
|
-
/**
|
|
225
|
-
* Ensure the agent_feedback table exists.
|
|
226
|
-
* Called on first use so existing databases (created before this feature)
|
|
227
|
-
* get the table added without requiring a migration.
|
|
228
|
-
*/
|
|
229
|
-
private ensureAgentFeedbackTable;
|
|
230
|
-
/**
|
|
231
|
-
* Record agent feedback on recommended content items
|
|
232
|
-
*/
|
|
233
|
-
recordAgentFeedback(itemIds: string[], outcome: string, context?: string, sessionTask?: string): {
|
|
234
|
-
success: boolean;
|
|
235
|
-
message: string;
|
|
236
|
-
recorded_count: number;
|
|
237
|
-
};
|
|
238
|
-
/**
|
|
239
|
-
* Get agent feedback statistics for the given period
|
|
240
|
-
*/
|
|
241
|
-
getAgentFeedbackStats(days?: number): {
|
|
242
|
-
period_days: number;
|
|
243
|
-
outcomes: {
|
|
244
|
-
used: number;
|
|
245
|
-
rejected: number;
|
|
246
|
-
partially_used: number;
|
|
247
|
-
total: number;
|
|
248
|
-
};
|
|
249
|
-
by_source: Array<{
|
|
250
|
-
source_type: string;
|
|
251
|
-
used: number;
|
|
252
|
-
rejected: number;
|
|
253
|
-
partially_used: number;
|
|
254
|
-
total: number;
|
|
255
|
-
usefulness_rate: number;
|
|
256
|
-
}>;
|
|
257
|
-
top_used_items: Array<{
|
|
258
|
-
item_id: string;
|
|
259
|
-
title: string | null;
|
|
260
|
-
source_type: string | null;
|
|
261
|
-
used_count: number;
|
|
262
|
-
rejected_count: number;
|
|
263
|
-
}>;
|
|
264
|
-
recent_feedback: Array<{
|
|
265
|
-
item_id: string;
|
|
266
|
-
outcome: string;
|
|
267
|
-
context: string | null;
|
|
268
|
-
session_task: string | null;
|
|
269
|
-
recorded_at: string;
|
|
270
|
-
}>;
|
|
271
|
-
};
|
|
272
216
|
/**
|
|
273
217
|
* Compute relevance score for an item based on user context
|
|
274
218
|
*/
|
package/dist/db.js
CHANGED
|
@@ -668,17 +668,6 @@ export class FourDADatabase {
|
|
|
668
668
|
);
|
|
669
669
|
CREATE INDEX IF NOT EXISTS idx_advantage_period ON advantage_score(period, computed_at);
|
|
670
670
|
|
|
671
|
-
-- Agent feedback — queried by record_agent_feedback, get_agent_feedback_stats
|
|
672
|
-
CREATE TABLE IF NOT EXISTS agent_feedback (
|
|
673
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
674
|
-
item_id TEXT NOT NULL,
|
|
675
|
-
outcome TEXT NOT NULL,
|
|
676
|
-
context TEXT,
|
|
677
|
-
session_task TEXT,
|
|
678
|
-
recorded_at TEXT DEFAULT (datetime('now'))
|
|
679
|
-
);
|
|
680
|
-
CREATE INDEX IF NOT EXISTS idx_agent_feedback_item ON agent_feedback(item_id);
|
|
681
|
-
CREATE INDEX IF NOT EXISTS idx_agent_feedback_outcome ON agent_feedback(outcome);
|
|
682
671
|
`);
|
|
683
672
|
}
|
|
684
673
|
/**
|
|
@@ -1097,17 +1086,6 @@ export class FourDADatabase {
|
|
|
1097
1086
|
.sort((a, b) => b.relevance_score - a.relevance_score)
|
|
1098
1087
|
.slice(0, limit);
|
|
1099
1088
|
}
|
|
1100
|
-
/**
|
|
1101
|
-
* Get a single source item by ID and type
|
|
1102
|
-
*/
|
|
1103
|
-
getSourceItem(itemId, sourceType) {
|
|
1104
|
-
const stmt = this.db.prepare(`
|
|
1105
|
-
SELECT id, source_type, source_id, url, title, content, content_hash, created_at, last_seen
|
|
1106
|
-
FROM source_items
|
|
1107
|
-
WHERE id = ? AND source_type = ?
|
|
1108
|
-
`);
|
|
1109
|
-
return stmt.get(itemId, sourceType) || null;
|
|
1110
|
-
}
|
|
1111
1089
|
/**
|
|
1112
1090
|
* Look up persisted necessity scores for an item (written by Rust analysis pipeline).
|
|
1113
1091
|
* Returns defaults (0.0/null) if no necessity data exists yet.
|
|
@@ -1205,99 +1183,6 @@ export class FourDADatabase {
|
|
|
1205
1183
|
return context;
|
|
1206
1184
|
}
|
|
1207
1185
|
// ===========================================================================
|
|
1208
|
-
// Relevance Explanation
|
|
1209
|
-
// ===========================================================================
|
|
1210
|
-
/**
|
|
1211
|
-
* Explain why an item was considered relevant
|
|
1212
|
-
*/
|
|
1213
|
-
explainRelevance(itemId, sourceType) {
|
|
1214
|
-
const item = this.getSourceItem(itemId, sourceType);
|
|
1215
|
-
if (!item) {
|
|
1216
|
-
return null;
|
|
1217
|
-
}
|
|
1218
|
-
const context = this.getUserContext(true, true);
|
|
1219
|
-
const itemTopics = this.extractTopics(item.title + " " + item.content);
|
|
1220
|
-
// Compute matching elements
|
|
1221
|
-
const matchingInterests = context.interests
|
|
1222
|
-
.filter((i) => itemTopics.some((t) => this.topicMatches(t, i.topic)))
|
|
1223
|
-
.map((i) => i.topic);
|
|
1224
|
-
const matchingTech = context.tech_stack.filter((tech) => itemTopics.some((t) => this.topicMatches(t, tech)));
|
|
1225
|
-
const matchingTopics = context.ace?.active_topics
|
|
1226
|
-
.filter((at) => itemTopics.some((t) => this.topicMatches(t, at.topic)))
|
|
1227
|
-
.map((at) => at.topic) || [];
|
|
1228
|
-
const matchingAffinities = context.learned?.topic_affinities
|
|
1229
|
-
.filter((ta) => ta.affinity_score > 0 && itemTopics.some((t) => this.topicMatches(t, ta.topic)))
|
|
1230
|
-
.map((ta) => ta.topic) || [];
|
|
1231
|
-
// Compute score breakdown
|
|
1232
|
-
const staticScore = matchingInterests.length * 0.3 +
|
|
1233
|
-
matchingTech.length * 0.2 +
|
|
1234
|
-
(context.domains.some((d) => itemTopics.some((t) => this.topicMatches(t, d)))
|
|
1235
|
-
? 0.15
|
|
1236
|
-
: 0);
|
|
1237
|
-
const aceScore = context.ace
|
|
1238
|
-
? matchingTopics.length * 0.1 +
|
|
1239
|
-
context.ace.detected_tech.filter((dt) => itemTopics.some((t) => this.topicMatches(t, dt.name))).length *
|
|
1240
|
-
0.05
|
|
1241
|
-
: 0;
|
|
1242
|
-
// Learned affinity score DEMOTED in v19 (AD-029) — must mirror
|
|
1243
|
-
// computeRelevanceScore so the explanation matches what actually
|
|
1244
|
-
// scored (the two previously diverged on the anti-penalty weight too:
|
|
1245
|
-
// scorer used *0.3, this explanation used *0.5).
|
|
1246
|
-
const learnedScore = 0;
|
|
1247
|
-
// Anti-penalty from user-authored exclusions only (learned anti-topics
|
|
1248
|
-
// demoted with the rest of the behavioral stack, AD-029).
|
|
1249
|
-
let antiPenalty = 0;
|
|
1250
|
-
for (const exclusion of context.exclusions) {
|
|
1251
|
-
if (itemTopics.some((t) => this.topicMatches(t, exclusion))) {
|
|
1252
|
-
antiPenalty = 1.0; // Hard exclusion
|
|
1253
|
-
break;
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
const finalScore = Math.max(0, Math.min(1, staticScore + aceScore + learnedScore - antiPenalty));
|
|
1257
|
-
const scoreBreakdown = {
|
|
1258
|
-
embedding_similarity: null, // Not computing embedding similarity in MCP server
|
|
1259
|
-
static_match_score: Math.round(staticScore * 100) / 100,
|
|
1260
|
-
ace_match_score: Math.round(aceScore * 100) / 100,
|
|
1261
|
-
learned_affinity_score: Math.round(learnedScore * 100) / 100,
|
|
1262
|
-
anti_penalty: Math.round(antiPenalty * 100) / 100,
|
|
1263
|
-
final_score: Math.round(finalScore * 100) / 100,
|
|
1264
|
-
};
|
|
1265
|
-
const matchingContext = {
|
|
1266
|
-
matching_interests: matchingInterests,
|
|
1267
|
-
matching_tech: matchingTech,
|
|
1268
|
-
matching_topics: matchingTopics,
|
|
1269
|
-
matching_affinities: matchingAffinities,
|
|
1270
|
-
};
|
|
1271
|
-
// Generate human-readable explanation
|
|
1272
|
-
const explanationParts = [];
|
|
1273
|
-
if (matchingInterests.length > 0) {
|
|
1274
|
-
explanationParts.push(`Matches your interests: ${matchingInterests.slice(0, 3).join(", ")}`);
|
|
1275
|
-
}
|
|
1276
|
-
if (matchingTech.length > 0) {
|
|
1277
|
-
explanationParts.push(`Related to your tech stack: ${matchingTech.slice(0, 3).join(", ")}`);
|
|
1278
|
-
}
|
|
1279
|
-
if (matchingTopics.length > 0) {
|
|
1280
|
-
explanationParts.push(`Relevant to recent work: ${matchingTopics.slice(0, 3).join(", ")}`);
|
|
1281
|
-
}
|
|
1282
|
-
if (matchingAffinities.length > 0) {
|
|
1283
|
-
explanationParts.push(`You've shown interest in: ${matchingAffinities.slice(0, 3).join(", ")}`);
|
|
1284
|
-
}
|
|
1285
|
-
if (antiPenalty > 0) {
|
|
1286
|
-
explanationParts.push(`Score reduced due to excluded topics`);
|
|
1287
|
-
}
|
|
1288
|
-
const explanation = explanationParts.length > 0
|
|
1289
|
-
? explanationParts.join(". ") + "."
|
|
1290
|
-
: "No specific matches found - may be based on general relevance.";
|
|
1291
|
-
return {
|
|
1292
|
-
item_id: itemId,
|
|
1293
|
-
source_type: sourceType,
|
|
1294
|
-
title: item.title,
|
|
1295
|
-
score_breakdown: scoreBreakdown,
|
|
1296
|
-
matching_context: matchingContext,
|
|
1297
|
-
explanation,
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
// ===========================================================================
|
|
1301
1186
|
// Feedback
|
|
1302
1187
|
// ===========================================================================
|
|
1303
1188
|
/**
|
|
@@ -1392,160 +1277,6 @@ export class FourDADatabase {
|
|
|
1392
1277
|
`);
|
|
1393
1278
|
}
|
|
1394
1279
|
// ===========================================================================
|
|
1395
|
-
// Agent Feedback
|
|
1396
|
-
// ===========================================================================
|
|
1397
|
-
/**
|
|
1398
|
-
* Ensure the agent_feedback table exists.
|
|
1399
|
-
* Called on first use so existing databases (created before this feature)
|
|
1400
|
-
* get the table added without requiring a migration.
|
|
1401
|
-
*/
|
|
1402
|
-
ensureAgentFeedbackTable() {
|
|
1403
|
-
this.db.exec(`
|
|
1404
|
-
CREATE TABLE IF NOT EXISTS agent_feedback (
|
|
1405
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1406
|
-
item_id TEXT NOT NULL,
|
|
1407
|
-
outcome TEXT NOT NULL,
|
|
1408
|
-
context TEXT,
|
|
1409
|
-
session_task TEXT,
|
|
1410
|
-
recorded_at TEXT DEFAULT (datetime('now'))
|
|
1411
|
-
);
|
|
1412
|
-
CREATE INDEX IF NOT EXISTS idx_agent_feedback_item ON agent_feedback(item_id);
|
|
1413
|
-
CREATE INDEX IF NOT EXISTS idx_agent_feedback_outcome ON agent_feedback(outcome);
|
|
1414
|
-
`);
|
|
1415
|
-
}
|
|
1416
|
-
/**
|
|
1417
|
-
* Record agent feedback on recommended content items
|
|
1418
|
-
*/
|
|
1419
|
-
recordAgentFeedback(itemIds, outcome, context, sessionTask) {
|
|
1420
|
-
this.ensureAgentFeedbackTable();
|
|
1421
|
-
const validOutcomes = ["used", "rejected", "partially_used"];
|
|
1422
|
-
if (!validOutcomes.includes(outcome)) {
|
|
1423
|
-
return {
|
|
1424
|
-
success: false,
|
|
1425
|
-
message: `Invalid outcome: ${outcome}. Valid outcomes: ${validOutcomes.join(", ")}`,
|
|
1426
|
-
recorded_count: 0,
|
|
1427
|
-
};
|
|
1428
|
-
}
|
|
1429
|
-
if (!itemIds || itemIds.length === 0) {
|
|
1430
|
-
return {
|
|
1431
|
-
success: false,
|
|
1432
|
-
message: "item_ids must contain at least one item ID",
|
|
1433
|
-
recorded_count: 0,
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
|
-
try {
|
|
1437
|
-
const stmt = this.db.prepare(`
|
|
1438
|
-
INSERT INTO agent_feedback (item_id, outcome, context, session_task)
|
|
1439
|
-
VALUES (?, ?, ?, ?)
|
|
1440
|
-
`);
|
|
1441
|
-
const insertMany = this.db.transaction((ids) => {
|
|
1442
|
-
let count = 0;
|
|
1443
|
-
for (const id of ids) {
|
|
1444
|
-
stmt.run(id, outcome, context || null, sessionTask || null);
|
|
1445
|
-
count++;
|
|
1446
|
-
}
|
|
1447
|
-
return count;
|
|
1448
|
-
});
|
|
1449
|
-
const count = insertMany(itemIds);
|
|
1450
|
-
return {
|
|
1451
|
-
success: true,
|
|
1452
|
-
message: `Recorded ${outcome} feedback for ${count} item(s)`,
|
|
1453
|
-
recorded_count: count,
|
|
1454
|
-
};
|
|
1455
|
-
}
|
|
1456
|
-
catch (error) {
|
|
1457
|
-
return {
|
|
1458
|
-
success: false,
|
|
1459
|
-
message: `Failed to record agent feedback: ${error instanceof Error ? error.message : String(error)}`,
|
|
1460
|
-
recorded_count: 0,
|
|
1461
|
-
};
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
/**
|
|
1465
|
-
* Get agent feedback statistics for the given period
|
|
1466
|
-
*/
|
|
1467
|
-
getAgentFeedbackStats(days = 30) {
|
|
1468
|
-
this.ensureAgentFeedbackTable();
|
|
1469
|
-
const cutoff = `datetime('now', '-${Math.max(1, Math.min(365, days))} days')`;
|
|
1470
|
-
// Outcome totals
|
|
1471
|
-
const outcomeRows = this.db
|
|
1472
|
-
.prepare(`SELECT outcome, COUNT(*) as cnt
|
|
1473
|
-
FROM agent_feedback
|
|
1474
|
-
WHERE recorded_at >= ${cutoff}
|
|
1475
|
-
GROUP BY outcome`)
|
|
1476
|
-
.all();
|
|
1477
|
-
const outcomes = { used: 0, rejected: 0, partially_used: 0, total: 0 };
|
|
1478
|
-
for (const row of outcomeRows) {
|
|
1479
|
-
if (row.outcome === "used")
|
|
1480
|
-
outcomes.used = row.cnt;
|
|
1481
|
-
else if (row.outcome === "rejected")
|
|
1482
|
-
outcomes.rejected = row.cnt;
|
|
1483
|
-
else if (row.outcome === "partially_used")
|
|
1484
|
-
outcomes.partially_used = row.cnt;
|
|
1485
|
-
outcomes.total += row.cnt;
|
|
1486
|
-
}
|
|
1487
|
-
// By source type — join with source_items to get source_type
|
|
1488
|
-
const sourceRows = this.db
|
|
1489
|
-
.prepare(`SELECT COALESCE(si.source_type, 'unknown') as source_type, af.outcome, COUNT(*) as cnt
|
|
1490
|
-
FROM agent_feedback af
|
|
1491
|
-
LEFT JOIN source_items si ON CAST(af.item_id AS INTEGER) = si.id
|
|
1492
|
-
WHERE af.recorded_at >= ${cutoff}
|
|
1493
|
-
GROUP BY source_type, af.outcome
|
|
1494
|
-
ORDER BY cnt DESC`)
|
|
1495
|
-
.all();
|
|
1496
|
-
// Aggregate source rows into per-source stats
|
|
1497
|
-
const sourceMap = new Map();
|
|
1498
|
-
for (const row of sourceRows) {
|
|
1499
|
-
if (!sourceMap.has(row.source_type)) {
|
|
1500
|
-
sourceMap.set(row.source_type, { used: 0, rejected: 0, partially_used: 0, total: 0 });
|
|
1501
|
-
}
|
|
1502
|
-
const entry = sourceMap.get(row.source_type);
|
|
1503
|
-
if (row.outcome === "used")
|
|
1504
|
-
entry.used += row.cnt;
|
|
1505
|
-
else if (row.outcome === "rejected")
|
|
1506
|
-
entry.rejected += row.cnt;
|
|
1507
|
-
else if (row.outcome === "partially_used")
|
|
1508
|
-
entry.partially_used += row.cnt;
|
|
1509
|
-
entry.total += row.cnt;
|
|
1510
|
-
}
|
|
1511
|
-
const bySource = Array.from(sourceMap.entries()).map(([source_type, stats]) => ({
|
|
1512
|
-
source_type,
|
|
1513
|
-
...stats,
|
|
1514
|
-
usefulness_rate: stats.total > 0 ? (stats.used + stats.partially_used * 0.5) / stats.total : 0,
|
|
1515
|
-
}));
|
|
1516
|
-
// Top used items
|
|
1517
|
-
const topItems = this.db
|
|
1518
|
-
.prepare(`SELECT
|
|
1519
|
-
af.item_id,
|
|
1520
|
-
si.title,
|
|
1521
|
-
si.source_type,
|
|
1522
|
-
SUM(CASE WHEN af.outcome = 'used' THEN 1 ELSE 0 END) as used_count,
|
|
1523
|
-
SUM(CASE WHEN af.outcome = 'rejected' THEN 1 ELSE 0 END) as rejected_count
|
|
1524
|
-
FROM agent_feedback af
|
|
1525
|
-
LEFT JOIN source_items si ON CAST(af.item_id AS INTEGER) = si.id
|
|
1526
|
-
WHERE af.recorded_at >= ${cutoff}
|
|
1527
|
-
GROUP BY af.item_id
|
|
1528
|
-
HAVING used_count > 0
|
|
1529
|
-
ORDER BY used_count DESC
|
|
1530
|
-
LIMIT 10`)
|
|
1531
|
-
.all();
|
|
1532
|
-
// Recent feedback entries
|
|
1533
|
-
const recentFeedback = this.db
|
|
1534
|
-
.prepare(`SELECT item_id, outcome, context, session_task, recorded_at
|
|
1535
|
-
FROM agent_feedback
|
|
1536
|
-
WHERE recorded_at >= ${cutoff}
|
|
1537
|
-
ORDER BY recorded_at DESC
|
|
1538
|
-
LIMIT 20`)
|
|
1539
|
-
.all();
|
|
1540
|
-
return {
|
|
1541
|
-
period_days: days,
|
|
1542
|
-
outcomes,
|
|
1543
|
-
by_source: bySource,
|
|
1544
|
-
top_used_items: topItems,
|
|
1545
|
-
recent_feedback: recentFeedback,
|
|
1546
|
-
};
|
|
1547
|
-
}
|
|
1548
|
-
// ===========================================================================
|
|
1549
1280
|
// Helper Methods
|
|
1550
1281
|
// ===========================================================================
|
|
1551
1282
|
/**
|
package/dist/types.d.ts
CHANGED
|
@@ -119,37 +119,6 @@ export interface UserContext {
|
|
|
119
119
|
anti_topics: AntiTopic[];
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
|
-
/**
|
|
123
|
-
* Score breakdown for explain_relevance
|
|
124
|
-
*/
|
|
125
|
-
export interface ScoreBreakdown {
|
|
126
|
-
embedding_similarity: number | null;
|
|
127
|
-
static_match_score: number;
|
|
128
|
-
ace_match_score: number;
|
|
129
|
-
learned_affinity_score: number;
|
|
130
|
-
anti_penalty: number;
|
|
131
|
-
final_score: number;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Matching context elements
|
|
135
|
-
*/
|
|
136
|
-
export interface MatchingContext {
|
|
137
|
-
matching_interests: string[];
|
|
138
|
-
matching_tech: string[];
|
|
139
|
-
matching_topics: string[];
|
|
140
|
-
matching_affinities: string[];
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Full relevance explanation
|
|
144
|
-
*/
|
|
145
|
-
export interface RelevanceExplanation {
|
|
146
|
-
item_id: number;
|
|
147
|
-
source_type: string;
|
|
148
|
-
title: string;
|
|
149
|
-
score_breakdown: ScoreBreakdown;
|
|
150
|
-
matching_context: MatchingContext;
|
|
151
|
-
explanation: string;
|
|
152
|
-
}
|
|
153
122
|
/**
|
|
154
123
|
* Valid feedback action types
|
|
155
124
|
*/
|
|
@@ -293,107 +262,3 @@ export interface SignalEventRow {
|
|
|
293
262
|
data: string;
|
|
294
263
|
created_at: string;
|
|
295
264
|
}
|
|
296
|
-
/**
|
|
297
|
-
* Valid agent feedback outcome types
|
|
298
|
-
*/
|
|
299
|
-
export type AgentFeedbackOutcome = "used" | "rejected" | "partially_used";
|
|
300
|
-
/**
|
|
301
|
-
* Parameters for recording agent feedback
|
|
302
|
-
*/
|
|
303
|
-
export interface RecordAgentFeedbackParams {
|
|
304
|
-
item_ids: string[];
|
|
305
|
-
outcome: AgentFeedbackOutcome;
|
|
306
|
-
context?: string;
|
|
307
|
-
session_task?: string;
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Result from recording agent feedback
|
|
311
|
-
*/
|
|
312
|
-
export interface AgentFeedbackResult {
|
|
313
|
-
success: boolean;
|
|
314
|
-
message: string;
|
|
315
|
-
recorded_count: number;
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Parameters for getting agent feedback stats
|
|
319
|
-
*/
|
|
320
|
-
export interface GetAgentFeedbackStatsParams {
|
|
321
|
-
days?: number;
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Outcome breakdown in agent feedback stats
|
|
325
|
-
*/
|
|
326
|
-
export interface AgentFeedbackOutcomeStats {
|
|
327
|
-
used: number;
|
|
328
|
-
rejected: number;
|
|
329
|
-
partially_used: number;
|
|
330
|
-
total: number;
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Per-source feedback stats
|
|
334
|
-
*/
|
|
335
|
-
export interface AgentFeedbackSourceStats {
|
|
336
|
-
source_type: string;
|
|
337
|
-
used: number;
|
|
338
|
-
rejected: number;
|
|
339
|
-
partially_used: number;
|
|
340
|
-
total: number;
|
|
341
|
-
usefulness_rate: number;
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* A frequently used item
|
|
345
|
-
*/
|
|
346
|
-
export interface AgentFeedbackTopItem {
|
|
347
|
-
item_id: string;
|
|
348
|
-
title: string | null;
|
|
349
|
-
source_type: string | null;
|
|
350
|
-
used_count: number;
|
|
351
|
-
rejected_count: number;
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* A recent feedback entry
|
|
355
|
-
*/
|
|
356
|
-
export interface AgentFeedbackRecentEntry {
|
|
357
|
-
item_id: string;
|
|
358
|
-
outcome: string;
|
|
359
|
-
context: string | null;
|
|
360
|
-
session_task: string | null;
|
|
361
|
-
recorded_at: string;
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Full agent feedback stats response
|
|
365
|
-
*/
|
|
366
|
-
export interface AgentFeedbackStats {
|
|
367
|
-
period_days: number;
|
|
368
|
-
outcomes: AgentFeedbackOutcomeStats;
|
|
369
|
-
by_source: AgentFeedbackSourceStats[];
|
|
370
|
-
top_used_items: AgentFeedbackTopItem[];
|
|
371
|
-
recent_feedback: AgentFeedbackRecentEntry[];
|
|
372
|
-
}
|
|
373
|
-
/** Row from agent_feedback table */
|
|
374
|
-
export interface AgentFeedbackRow {
|
|
375
|
-
item_id: string;
|
|
376
|
-
outcome: string;
|
|
377
|
-
context: string | null;
|
|
378
|
-
session_task: string | null;
|
|
379
|
-
recorded_at: string;
|
|
380
|
-
}
|
|
381
|
-
/** Row from agent_feedback stats query — outcome counts */
|
|
382
|
-
export interface AgentFeedbackOutcomeRow {
|
|
383
|
-
outcome: string;
|
|
384
|
-
cnt: number;
|
|
385
|
-
}
|
|
386
|
-
/** Row from agent_feedback stats query — source breakdown */
|
|
387
|
-
export interface AgentFeedbackSourceRow {
|
|
388
|
-
source_type: string;
|
|
389
|
-
outcome: string;
|
|
390
|
-
cnt: number;
|
|
391
|
-
}
|
|
392
|
-
/** Row from agent_feedback stats query — top items */
|
|
393
|
-
export interface AgentFeedbackTopItemRow {
|
|
394
|
-
item_id: string;
|
|
395
|
-
title: string | null;
|
|
396
|
-
source_type: string | null;
|
|
397
|
-
used_count: number;
|
|
398
|
-
rejected_count: number;
|
|
399
|
-
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@4da/mcp-server",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"mcpName": "io.github.4DA-Systems/4da-mcp-server",
|
|
5
|
-
"description": "Upgrade intelligence for AI coding agents: what changes between your installed and target version (changelog, breaking changes, the files you import it from), a pre-install verdict on each bump, vulnerability scanning at osv-scanner parity across npm/pnpm/yarn, Cargo, Python and Go lockfiles, ranked upgrade plans, task-scoped briefings, decision memory. Local, no account
|
|
5
|
+
"description": "Upgrade intelligence for AI coding agents: what changes between your installed and target version (changelog, breaking changes, the files you import it from), a pre-install verdict on each bump, vulnerability scanning at osv-scanner parity across npm/pnpm/yarn, Cargo, Python and Go lockfiles, ranked upgrade plans, task-scoped briefings, decision memory. Local, no account; works standalone, and reads the 4DA desktop app's scored feed when it is installed.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"bin": {
|
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
"setup": "node dist/setup.js",
|
|
29
29
|
"test": "vitest run",
|
|
30
30
|
"test:watch": "vitest",
|
|
31
|
-
"prepublishOnly": "pnpm run build"
|
|
31
|
+
"prepublishOnly": "pnpm run build",
|
|
32
|
+
"check": "node scripts/check-repo.mjs",
|
|
33
|
+
"contract": "node scripts/app-schema-contract.mjs",
|
|
34
|
+
"contract:sync": "node scripts/sync-app-schema.mjs"
|
|
32
35
|
},
|
|
33
36
|
"keywords": [
|
|
34
37
|
"mcp",
|
|
@@ -70,12 +73,11 @@
|
|
|
70
73
|
"license": "Apache-2.0",
|
|
71
74
|
"repository": {
|
|
72
75
|
"type": "git",
|
|
73
|
-
"url": "git+https://github.com/4DA-Systems/
|
|
74
|
-
"directory": "mcp-4da-server"
|
|
76
|
+
"url": "git+https://github.com/4DA-Systems/4da-mcp-server.git"
|
|
75
77
|
},
|
|
76
|
-
"homepage": "https://4da.ai",
|
|
78
|
+
"homepage": "https://4da.ai/mcp/",
|
|
77
79
|
"bugs": {
|
|
78
|
-
"url": "https://github.com/4DA-Systems/
|
|
80
|
+
"url": "https://github.com/4DA-Systems/4da-mcp-server/issues"
|
|
79
81
|
},
|
|
80
82
|
"dependencies": {
|
|
81
83
|
"@modelcontextprotocol/node": "^2.0.0",
|
|
@@ -93,6 +95,7 @@
|
|
|
93
95
|
"engines": {
|
|
94
96
|
"node": ">=22.0.0"
|
|
95
97
|
},
|
|
98
|
+
"packageManager": "pnpm@10.34.6",
|
|
96
99
|
"pnpm": {
|
|
97
100
|
"onlyBuiltDependencies": [
|
|
98
101
|
"better-sqlite3",
|