@4da/mcp-server 4.0.0 → 4.1.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/LICENSE +1 -1
- package/README.md +160 -85
- package/dist/auth.d.ts +36 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +82 -0
- package/dist/auth.js.map +1 -0
- package/dist/db.d.ts +73 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +607 -4
- package/dist/db.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +33 -7
- package/dist/doctor.js.map +1 -1
- package/dist/http-transport.d.ts.map +1 -1
- package/dist/http-transport.js +19 -2
- package/dist/http-transport.js.map +1 -1
- package/dist/index.d.ts +15 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +150 -325
- package/dist/index.js.map +1 -1
- package/dist/live/cache.d.ts +16 -0
- package/dist/live/cache.d.ts.map +1 -0
- package/dist/live/cache.js +61 -0
- package/dist/live/cache.js.map +1 -0
- package/dist/live/hn-fetcher.d.ts +19 -0
- package/dist/live/hn-fetcher.d.ts.map +1 -0
- package/dist/live/hn-fetcher.js +123 -0
- package/dist/live/hn-fetcher.js.map +1 -0
- package/dist/live/index.d.ts +55 -0
- package/dist/live/index.d.ts.map +1 -0
- package/dist/live/index.js +131 -0
- package/dist/live/index.js.map +1 -0
- package/dist/live/osv-scanner.d.ts +19 -0
- package/dist/live/osv-scanner.d.ts.map +1 -0
- package/dist/live/osv-scanner.js +197 -0
- package/dist/live/osv-scanner.js.map +1 -0
- package/dist/live/rate-limiter.d.ts +19 -0
- package/dist/live/rate-limiter.d.ts.map +1 -0
- package/dist/live/rate-limiter.js +52 -0
- package/dist/live/rate-limiter.js.map +1 -0
- package/dist/live/types.d.ts +89 -0
- package/dist/live/types.d.ts.map +1 -0
- package/dist/live/types.js +3 -0
- package/dist/live/types.js.map +1 -0
- package/dist/live/version-resolver.d.ts +11 -0
- package/dist/live/version-resolver.d.ts.map +1 -0
- package/dist/live/version-resolver.js +321 -0
- package/dist/live/version-resolver.js.map +1 -0
- package/dist/live-singleton.d.ts +9 -0
- package/dist/live-singleton.d.ts.map +1 -0
- package/dist/live-singleton.js +14 -0
- package/dist/live-singleton.js.map +1 -0
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js +3 -2
- package/dist/llm.js.map +1 -1
- package/dist/output-manager.d.ts.map +1 -1
- package/dist/output-manager.js +1 -0
- package/dist/output-manager.js.map +1 -1
- package/dist/project-scanner.d.ts +27 -0
- package/dist/project-scanner.d.ts.map +1 -0
- package/dist/project-scanner.js +366 -0
- package/dist/project-scanner.js.map +1 -0
- package/dist/schema-registry.d.ts +31 -13
- package/dist/schema-registry.d.ts.map +1 -1
- package/dist/schema-registry.js +164 -32
- package/dist/schema-registry.js.map +1 -1
- package/dist/schemas/agent-feedback-stats.json +14 -0
- package/dist/schemas/preemption-feed.json +18 -0
- package/dist/schemas/record-agent-feedback.json +28 -0
- package/dist/schemas/trust-summary.json +13 -0
- package/dist/schemas/vulnerability-scan.json +26 -0
- package/dist/schemas/what-should-i-know.json +19 -0
- package/dist/setup.d.ts +2 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +38 -4
- package/dist/setup.js.map +1 -1
- package/dist/tool-dispatch.d.ts +33 -0
- package/dist/tool-dispatch.d.ts.map +1 -0
- package/dist/tool-dispatch.js +87 -0
- package/dist/tool-dispatch.js.map +1 -0
- package/dist/tools/agent-feedback-stats.d.ts +30 -0
- package/dist/tools/agent-feedback-stats.d.ts.map +1 -0
- package/dist/tools/agent-feedback-stats.js +41 -0
- package/dist/tools/agent-feedback-stats.js.map +1 -0
- package/dist/tools/agent-memory.d.ts.map +1 -1
- package/dist/tools/agent-memory.js +33 -0
- package/dist/tools/agent-memory.js.map +1 -1
- package/dist/tools/agent-session-brief.d.ts.map +1 -1
- package/dist/tools/agent-session-brief.js +1 -0
- package/dist/tools/agent-session-brief.js.map +1 -1
- package/dist/tools/attention-report.d.ts.map +1 -1
- package/dist/tools/attention-report.js +1 -0
- package/dist/tools/attention-report.js.map +1 -1
- package/dist/tools/autophagy-status.d.ts.map +1 -1
- package/dist/tools/autophagy-status.js.map +1 -1
- package/dist/tools/compound-advantage.d.ts +3 -0
- package/dist/tools/compound-advantage.d.ts.map +1 -1
- package/dist/tools/compound-advantage.js +67 -10
- package/dist/tools/compound-advantage.js.map +1 -1
- package/dist/tools/config-validator.d.ts.map +1 -1
- package/dist/tools/config-validator.js +46 -4
- package/dist/tools/config-validator.js.map +1 -1
- package/dist/tools/context-analysis.d.ts.map +1 -1
- package/dist/tools/context-analysis.js +1 -0
- package/dist/tools/context-analysis.js.map +1 -1
- package/dist/tools/daily-briefing.d.ts.map +1 -1
- package/dist/tools/daily-briefing.js +17 -1
- package/dist/tools/daily-briefing.js.map +1 -1
- package/dist/tools/decision-enforcement.d.ts.map +1 -1
- package/dist/tools/decision-enforcement.js +1 -0
- package/dist/tools/decision-enforcement.js.map +1 -1
- package/dist/tools/decision-memory.d.ts +2 -0
- package/dist/tools/decision-memory.d.ts.map +1 -1
- package/dist/tools/decision-memory.js +80 -0
- package/dist/tools/decision-memory.js.map +1 -1
- package/dist/tools/decision-windows.d.ts.map +1 -1
- package/dist/tools/decision-windows.js.map +1 -1
- package/dist/tools/delegation-score.d.ts.map +1 -1
- package/dist/tools/delegation-score.js +10 -0
- package/dist/tools/delegation-score.js.map +1 -1
- package/dist/tools/developer-dna.d.ts.map +1 -1
- package/dist/tools/developer-dna.js +31 -1
- package/dist/tools/developer-dna.js.map +1 -1
- package/dist/tools/explain-relevance.d.ts.map +1 -1
- package/dist/tools/explain-relevance.js +1 -0
- package/dist/tools/explain-relevance.js.map +1 -1
- package/dist/tools/export-context.d.ts +9 -0
- package/dist/tools/export-context.d.ts.map +1 -1
- package/dist/tools/export-context.js +70 -7
- package/dist/tools/export-context.js.map +1 -1
- package/dist/tools/get-actionable-signals.d.ts.map +1 -1
- package/dist/tools/get-actionable-signals.js +75 -10
- package/dist/tools/get-actionable-signals.js.map +1 -1
- package/dist/tools/get-context.d.ts.map +1 -1
- package/dist/tools/get-context.js +1 -0
- package/dist/tools/get-context.js.map +1 -1
- package/dist/tools/get-relevant-content.d.ts.map +1 -1
- package/dist/tools/get-relevant-content.js +12 -1
- package/dist/tools/get-relevant-content.js.map +1 -1
- package/dist/tools/index.d.ts +16 -16
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +21 -16
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/knowledge-gaps.d.ts +8 -0
- package/dist/tools/knowledge-gaps.d.ts.map +1 -1
- package/dist/tools/knowledge-gaps.js +23 -7
- package/dist/tools/knowledge-gaps.js.map +1 -1
- package/dist/tools/llm-status.d.ts.map +1 -1
- package/dist/tools/llm-status.js +1 -0
- package/dist/tools/llm-status.js.map +1 -1
- package/dist/tools/muse-context.d.ts +195 -0
- package/dist/tools/muse-context.d.ts.map +1 -0
- package/dist/tools/muse-context.js +284 -0
- package/dist/tools/muse-context.js.map +1 -0
- package/dist/tools/preemption-feed.d.ts +55 -0
- package/dist/tools/preemption-feed.d.ts.map +1 -0
- package/dist/tools/preemption-feed.js +124 -0
- package/dist/tools/preemption-feed.js.map +1 -0
- package/dist/tools/project-health.d.ts +12 -0
- package/dist/tools/project-health.d.ts.map +1 -1
- package/dist/tools/project-health.js +62 -11
- package/dist/tools/project-health.js.map +1 -1
- package/dist/tools/record-agent-feedback.d.ts +46 -0
- package/dist/tools/record-agent-feedback.d.ts.map +1 -0
- package/dist/tools/record-agent-feedback.js +71 -0
- package/dist/tools/record-agent-feedback.js.map +1 -0
- package/dist/tools/record-feedback.d.ts.map +1 -1
- package/dist/tools/record-feedback.js +1 -0
- package/dist/tools/record-feedback.js.map +1 -1
- package/dist/tools/reverse-mentions.d.ts.map +1 -1
- package/dist/tools/reverse-mentions.js +31 -13
- package/dist/tools/reverse-mentions.js.map +1 -1
- package/dist/tools/score-autopsy.d.ts.map +1 -1
- package/dist/tools/score-autopsy.js +9 -2
- package/dist/tools/score-autopsy.js.map +1 -1
- package/dist/tools/semantic-shifts.d.ts.map +1 -1
- package/dist/tools/semantic-shifts.js +1 -0
- package/dist/tools/semantic-shifts.js.map +1 -1
- package/dist/tools/signal-chains.d.ts.map +1 -1
- package/dist/tools/signal-chains.js +5 -2
- package/dist/tools/signal-chains.js.map +1 -1
- package/dist/tools/source-health.d.ts.map +1 -1
- package/dist/tools/source-health.js +1 -0
- package/dist/tools/source-health.js.map +1 -1
- package/dist/tools/tech-radar.d.ts.map +1 -1
- package/dist/tools/tech-radar.js +1 -0
- package/dist/tools/tech-radar.js.map +1 -1
- package/dist/tools/topic-connections.d.ts.map +1 -1
- package/dist/tools/topic-connections.js +1 -0
- package/dist/tools/topic-connections.js.map +1 -1
- package/dist/tools/trend-analysis.d.ts.map +1 -1
- package/dist/tools/trend-analysis.js +1 -0
- package/dist/tools/trend-analysis.js.map +1 -1
- package/dist/tools/trust-summary.d.ts +38 -0
- package/dist/tools/trust-summary.d.ts.map +1 -0
- package/dist/tools/trust-summary.js +89 -0
- package/dist/tools/trust-summary.js.map +1 -0
- package/dist/tools/vulnerability-scan.d.ts +89 -0
- package/dist/tools/vulnerability-scan.d.ts.map +1 -0
- package/dist/tools/vulnerability-scan.js +127 -0
- package/dist/tools/vulnerability-scan.js.map +1 -0
- package/dist/tools/what-should-i-know.d.ts +79 -0
- package/dist/tools/what-should-i-know.d.ts.map +1 -0
- package/dist/tools/what-should-i-know.js +380 -0
- package/dist/tools/what-should-i-know.js.map +1 -0
- package/dist/types.d.ts +112 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +35 -18
package/dist/db.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: FSL-1.1-Apache-2.0
|
|
1
2
|
/**
|
|
2
3
|
* Database module for 4DA MCP Server
|
|
3
4
|
*
|
|
@@ -73,12 +74,21 @@ function getDefaultDbPath() {
|
|
|
73
74
|
*/
|
|
74
75
|
export class FourDADatabase {
|
|
75
76
|
db;
|
|
77
|
+
_isStandalone = false;
|
|
76
78
|
constructor(dbPath) {
|
|
77
79
|
const resolvedPath = dbPath || getDefaultDbPath();
|
|
78
80
|
// Resolve path - if relative, resolve from cwd
|
|
79
81
|
const absolutePath = path.isAbsolute(resolvedPath)
|
|
80
82
|
? resolvedPath
|
|
81
83
|
: path.resolve(process.cwd(), resolvedPath);
|
|
84
|
+
const isNew = !fs.existsSync(absolutePath);
|
|
85
|
+
// Ensure parent directory exists (standalone mode may need to create it)
|
|
86
|
+
if (isNew) {
|
|
87
|
+
const dir = path.dirname(absolutePath);
|
|
88
|
+
if (!fs.existsSync(dir)) {
|
|
89
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
82
92
|
try {
|
|
83
93
|
this.db = new Database(absolutePath, { readonly: false }); // Need write for feedback
|
|
84
94
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -86,6 +96,18 @@ export class FourDADatabase {
|
|
|
86
96
|
catch (error) {
|
|
87
97
|
throw new Error(`Failed to open 4DA database at ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
88
98
|
}
|
|
99
|
+
// Standalone mode: create schema for a brand-new database
|
|
100
|
+
if (isNew) {
|
|
101
|
+
this.createMinimalSchema();
|
|
102
|
+
this._isStandalone = true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Whether this database was freshly created in standalone mode
|
|
107
|
+
* (no pre-existing 4DA desktop app database found).
|
|
108
|
+
*/
|
|
109
|
+
get isStandalone() {
|
|
110
|
+
return this._isStandalone;
|
|
89
111
|
}
|
|
90
112
|
/**
|
|
91
113
|
* Validate that a database file exists, is readable, and passes integrity checks.
|
|
@@ -100,13 +122,12 @@ export class FourDADatabase {
|
|
|
100
122
|
const absolutePath = path.isAbsolute(resolvedPath)
|
|
101
123
|
? resolvedPath
|
|
102
124
|
: path.resolve(process.cwd(), resolvedPath);
|
|
103
|
-
// Check if file exists
|
|
125
|
+
// Check if file exists — if not, standalone mode will create it
|
|
104
126
|
if (!fs.existsSync(absolutePath)) {
|
|
105
127
|
return {
|
|
106
128
|
valid: false,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
+ "to the correct location.",
|
|
129
|
+
standalone: true,
|
|
130
|
+
error: `No existing database at ${absolutePath}. Standalone mode will create one on startup.`,
|
|
110
131
|
};
|
|
111
132
|
}
|
|
112
133
|
// Try to open and run integrity check
|
|
@@ -160,6 +181,405 @@ export class FourDADatabase {
|
|
|
160
181
|
close() {
|
|
161
182
|
this.db.close();
|
|
162
183
|
}
|
|
184
|
+
// ===========================================================================
|
|
185
|
+
// Standalone Mode — Schema & Population
|
|
186
|
+
// ===========================================================================
|
|
187
|
+
/**
|
|
188
|
+
* Create the minimal schema required for MCP tools to function.
|
|
189
|
+
* Mirrors the desktop app's tables but only the subset that MCP tools query.
|
|
190
|
+
* Called once when creating a brand-new standalone database.
|
|
191
|
+
*/
|
|
192
|
+
createMinimalSchema() {
|
|
193
|
+
this.db.exec(`
|
|
194
|
+
-- Schema version tracking
|
|
195
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
196
|
+
version INTEGER PRIMARY KEY
|
|
197
|
+
);
|
|
198
|
+
INSERT INTO schema_version (version) VALUES (1);
|
|
199
|
+
|
|
200
|
+
-- Source items (content feed) — queried by get_relevant_content, source_health, daily_briefing, etc.
|
|
201
|
+
CREATE TABLE IF NOT EXISTS source_items (
|
|
202
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
203
|
+
source_type TEXT NOT NULL,
|
|
204
|
+
source_id TEXT NOT NULL,
|
|
205
|
+
url TEXT,
|
|
206
|
+
title TEXT NOT NULL,
|
|
207
|
+
content TEXT NOT NULL DEFAULT '',
|
|
208
|
+
content_hash TEXT NOT NULL DEFAULT '',
|
|
209
|
+
embedding BLOB NOT NULL DEFAULT x'00',
|
|
210
|
+
signal_type TEXT,
|
|
211
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
212
|
+
last_seen TEXT NOT NULL DEFAULT (datetime('now')),
|
|
213
|
+
UNIQUE(source_type, source_id)
|
|
214
|
+
);
|
|
215
|
+
CREATE INDEX IF NOT EXISTS idx_source_type ON source_items(source_type);
|
|
216
|
+
CREATE INDEX IF NOT EXISTS idx_source_type_created ON source_items(source_type, created_at);
|
|
217
|
+
|
|
218
|
+
-- User identity — queried by get_context
|
|
219
|
+
CREATE TABLE IF NOT EXISTS user_identity (
|
|
220
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
221
|
+
role TEXT,
|
|
222
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
223
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
-- Tech stack (user-declared) — queried by get_context, tech_radar, developer_dna
|
|
227
|
+
CREATE TABLE IF NOT EXISTS tech_stack (
|
|
228
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
229
|
+
technology TEXT NOT NULL UNIQUE,
|
|
230
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
-- Domains of interest — queried by get_context
|
|
234
|
+
CREATE TABLE IF NOT EXISTS domains (
|
|
235
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
236
|
+
domain TEXT NOT NULL UNIQUE,
|
|
237
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
-- Explicit interests — queried by get_context, developer_dna
|
|
241
|
+
CREATE TABLE IF NOT EXISTS explicit_interests (
|
|
242
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
243
|
+
topic TEXT NOT NULL UNIQUE,
|
|
244
|
+
weight REAL DEFAULT 1.0,
|
|
245
|
+
embedding BLOB,
|
|
246
|
+
source TEXT DEFAULT 'explicit',
|
|
247
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
248
|
+
);
|
|
249
|
+
CREATE INDEX IF NOT EXISTS idx_interests_topic ON explicit_interests(topic);
|
|
250
|
+
|
|
251
|
+
-- Exclusions — queried by get_context
|
|
252
|
+
CREATE TABLE IF NOT EXISTS exclusions (
|
|
253
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
254
|
+
topic TEXT NOT NULL UNIQUE,
|
|
255
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
-- ACE detected tech — queried by get_context, tech_radar, developer_dna
|
|
259
|
+
CREATE TABLE IF NOT EXISTS detected_tech (
|
|
260
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
261
|
+
name TEXT NOT NULL UNIQUE,
|
|
262
|
+
category TEXT NOT NULL,
|
|
263
|
+
confidence REAL DEFAULT 0.5,
|
|
264
|
+
source TEXT NOT NULL DEFAULT 'project_scan',
|
|
265
|
+
evidence TEXT,
|
|
266
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
267
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
268
|
+
);
|
|
269
|
+
CREATE INDEX IF NOT EXISTS idx_detected_tech_name ON detected_tech(name);
|
|
270
|
+
CREATE INDEX IF NOT EXISTS idx_detected_tech_confidence ON detected_tech(confidence);
|
|
271
|
+
|
|
272
|
+
-- Active topics — queried by get_context
|
|
273
|
+
CREATE TABLE IF NOT EXISTS active_topics (
|
|
274
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
275
|
+
topic TEXT NOT NULL UNIQUE,
|
|
276
|
+
weight REAL DEFAULT 0.5,
|
|
277
|
+
confidence REAL DEFAULT 0.5,
|
|
278
|
+
embedding BLOB,
|
|
279
|
+
source TEXT NOT NULL DEFAULT 'project_scan',
|
|
280
|
+
last_seen TEXT DEFAULT (datetime('now')),
|
|
281
|
+
decay_applied INTEGER DEFAULT 0,
|
|
282
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
283
|
+
);
|
|
284
|
+
CREATE INDEX IF NOT EXISTS idx_active_topics_topic ON active_topics(topic);
|
|
285
|
+
|
|
286
|
+
-- Topic affinities (learned) — queried by get_context, developer_dna, attention_report
|
|
287
|
+
CREATE TABLE IF NOT EXISTS topic_affinities (
|
|
288
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
289
|
+
topic TEXT NOT NULL UNIQUE,
|
|
290
|
+
embedding BLOB,
|
|
291
|
+
positive_signals INTEGER DEFAULT 0,
|
|
292
|
+
negative_signals INTEGER DEFAULT 0,
|
|
293
|
+
total_exposures INTEGER DEFAULT 0,
|
|
294
|
+
affinity_score REAL DEFAULT 0.0,
|
|
295
|
+
confidence REAL DEFAULT 0.0,
|
|
296
|
+
last_interaction TEXT DEFAULT (datetime('now')),
|
|
297
|
+
decay_applied INTEGER DEFAULT 0,
|
|
298
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
299
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
-- Anti-topics (learned exclusions) — queried by get_context
|
|
303
|
+
CREATE TABLE IF NOT EXISTS anti_topics (
|
|
304
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
305
|
+
topic TEXT NOT NULL UNIQUE,
|
|
306
|
+
rejection_count INTEGER DEFAULT 0,
|
|
307
|
+
confidence REAL DEFAULT 0.0,
|
|
308
|
+
auto_detected INTEGER DEFAULT 1,
|
|
309
|
+
user_confirmed INTEGER DEFAULT 0,
|
|
310
|
+
first_rejection TEXT DEFAULT (datetime('now')),
|
|
311
|
+
last_rejection TEXT DEFAULT (datetime('now')),
|
|
312
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
313
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
-- Interactions — queried by record_feedback, developer_dna, knowledge_gaps
|
|
317
|
+
CREATE TABLE IF NOT EXISTS interactions (
|
|
318
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
319
|
+
source_item_id INTEGER,
|
|
320
|
+
item_id INTEGER,
|
|
321
|
+
action TEXT,
|
|
322
|
+
action_type TEXT,
|
|
323
|
+
action_data TEXT,
|
|
324
|
+
item_topics TEXT,
|
|
325
|
+
item_source TEXT,
|
|
326
|
+
signal_strength REAL DEFAULT 0.5,
|
|
327
|
+
timestamp TEXT DEFAULT (datetime('now'))
|
|
328
|
+
);
|
|
329
|
+
CREATE INDEX IF NOT EXISTS idx_interactions_timestamp ON interactions(timestamp);
|
|
330
|
+
CREATE INDEX IF NOT EXISTS idx_interactions_item ON interactions(source_item_id);
|
|
331
|
+
|
|
332
|
+
-- Project dependencies — queried by project_health, tech_radar, developer_dna, knowledge_gaps
|
|
333
|
+
CREATE TABLE IF NOT EXISTS project_dependencies (
|
|
334
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
335
|
+
project_path TEXT NOT NULL,
|
|
336
|
+
manifest_type TEXT NOT NULL,
|
|
337
|
+
package_name TEXT NOT NULL,
|
|
338
|
+
version TEXT,
|
|
339
|
+
is_dev INTEGER DEFAULT 0,
|
|
340
|
+
language TEXT NOT NULL,
|
|
341
|
+
last_scanned TEXT NOT NULL DEFAULT (datetime('now')),
|
|
342
|
+
UNIQUE(project_path, package_name)
|
|
343
|
+
);
|
|
344
|
+
CREATE INDEX IF NOT EXISTS idx_deps_package ON project_dependencies(package_name);
|
|
345
|
+
CREATE INDEX IF NOT EXISTS idx_deps_project ON project_dependencies(project_path);
|
|
346
|
+
|
|
347
|
+
-- Developer decisions — queried by tech_radar, decision_memory
|
|
348
|
+
CREATE TABLE IF NOT EXISTS developer_decisions (
|
|
349
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
350
|
+
decision_type TEXT NOT NULL,
|
|
351
|
+
subject TEXT NOT NULL,
|
|
352
|
+
decision TEXT NOT NULL,
|
|
353
|
+
rationale TEXT,
|
|
354
|
+
alternatives_rejected TEXT DEFAULT '[]',
|
|
355
|
+
context_tags TEXT DEFAULT '[]',
|
|
356
|
+
confidence REAL NOT NULL DEFAULT 0.8,
|
|
357
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
358
|
+
superseded_by INTEGER,
|
|
359
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
360
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
361
|
+
);
|
|
362
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_type ON developer_decisions(decision_type);
|
|
363
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_subject ON developer_decisions(subject);
|
|
364
|
+
|
|
365
|
+
-- Temporal events — queried by signal_chains, semantic_shifts, trend_analysis
|
|
366
|
+
CREATE TABLE IF NOT EXISTS temporal_events (
|
|
367
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
368
|
+
event_type TEXT NOT NULL,
|
|
369
|
+
subject TEXT NOT NULL,
|
|
370
|
+
data JSON NOT NULL,
|
|
371
|
+
embedding BLOB,
|
|
372
|
+
source_item_id INTEGER,
|
|
373
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
374
|
+
expires_at TEXT
|
|
375
|
+
);
|
|
376
|
+
CREATE INDEX IF NOT EXISTS idx_temporal_type_time ON temporal_events(event_type, created_at);
|
|
377
|
+
|
|
378
|
+
-- Item relationships — queried by reverse_mentions, topic_connections
|
|
379
|
+
CREATE TABLE IF NOT EXISTS item_relationships (
|
|
380
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
381
|
+
source_item_id INTEGER NOT NULL,
|
|
382
|
+
related_item_id INTEGER NOT NULL,
|
|
383
|
+
relationship_type TEXT NOT NULL,
|
|
384
|
+
strength REAL DEFAULT 1.0,
|
|
385
|
+
metadata JSON,
|
|
386
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
387
|
+
UNIQUE(source_item_id, related_item_id, relationship_type)
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
-- Agent memory — queried by agent_memory, agent_session_brief
|
|
391
|
+
CREATE TABLE IF NOT EXISTS agent_memory (
|
|
392
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
393
|
+
session_id TEXT NOT NULL,
|
|
394
|
+
agent_type TEXT NOT NULL,
|
|
395
|
+
memory_type TEXT NOT NULL,
|
|
396
|
+
subject TEXT NOT NULL,
|
|
397
|
+
content TEXT NOT NULL,
|
|
398
|
+
context_tags TEXT DEFAULT '[]',
|
|
399
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
400
|
+
expires_at TEXT,
|
|
401
|
+
promoted_to_decision_id INTEGER
|
|
402
|
+
);
|
|
403
|
+
CREATE INDEX IF NOT EXISTS idx_agent_memory_type ON agent_memory(memory_type);
|
|
404
|
+
CREATE INDEX IF NOT EXISTS idx_agent_memory_session ON agent_memory(session_id);
|
|
405
|
+
|
|
406
|
+
-- Source health — queried by source_health diagnostic
|
|
407
|
+
CREATE TABLE IF NOT EXISTS source_health (
|
|
408
|
+
source_type TEXT PRIMARY KEY,
|
|
409
|
+
status TEXT NOT NULL DEFAULT 'unknown',
|
|
410
|
+
last_success TEXT,
|
|
411
|
+
last_error TEXT,
|
|
412
|
+
error_count INTEGER NOT NULL DEFAULT 0,
|
|
413
|
+
consecutive_failures INTEGER NOT NULL DEFAULT 0,
|
|
414
|
+
items_fetched INTEGER NOT NULL DEFAULT 0,
|
|
415
|
+
response_time_ms INTEGER NOT NULL DEFAULT 0,
|
|
416
|
+
checked_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
-- Sources registry
|
|
420
|
+
CREATE TABLE IF NOT EXISTS sources (
|
|
421
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
422
|
+
source_type TEXT NOT NULL UNIQUE,
|
|
423
|
+
name TEXT NOT NULL,
|
|
424
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
425
|
+
config TEXT,
|
|
426
|
+
last_fetch TEXT,
|
|
427
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
-- Briefings — queried by agent_session_brief, daily_briefing
|
|
431
|
+
CREATE TABLE IF NOT EXISTS briefings (
|
|
432
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
433
|
+
content TEXT NOT NULL,
|
|
434
|
+
model TEXT,
|
|
435
|
+
item_count INTEGER NOT NULL DEFAULT 0,
|
|
436
|
+
tokens_used INTEGER,
|
|
437
|
+
latency_ms INTEGER,
|
|
438
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
-- Autophagy cycles — queried by autophagy_status
|
|
442
|
+
CREATE TABLE IF NOT EXISTS autophagy_cycles (
|
|
443
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
444
|
+
items_analyzed INTEGER NOT NULL DEFAULT 0,
|
|
445
|
+
items_pruned INTEGER NOT NULL DEFAULT 0,
|
|
446
|
+
calibrations_produced INTEGER NOT NULL DEFAULT 0,
|
|
447
|
+
topic_decay_rates_updated INTEGER NOT NULL DEFAULT 0,
|
|
448
|
+
source_autopsies_produced INTEGER NOT NULL DEFAULT 0,
|
|
449
|
+
anti_patterns_detected INTEGER NOT NULL DEFAULT 0,
|
|
450
|
+
db_size_before_bytes INTEGER NOT NULL DEFAULT 0,
|
|
451
|
+
db_size_after_bytes INTEGER NOT NULL DEFAULT 0,
|
|
452
|
+
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
453
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
-- Digested intelligence — queried by autophagy_status
|
|
457
|
+
CREATE TABLE IF NOT EXISTS digested_intelligence (
|
|
458
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
459
|
+
digest_type TEXT NOT NULL,
|
|
460
|
+
subject TEXT NOT NULL,
|
|
461
|
+
data TEXT NOT NULL,
|
|
462
|
+
confidence REAL NOT NULL DEFAULT 0.5,
|
|
463
|
+
sample_size INTEGER NOT NULL DEFAULT 0,
|
|
464
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
465
|
+
expires_at TEXT,
|
|
466
|
+
superseded_by INTEGER,
|
|
467
|
+
FOREIGN KEY (superseded_by) REFERENCES digested_intelligence(id)
|
|
468
|
+
);
|
|
469
|
+
CREATE INDEX IF NOT EXISTS idx_digest_type_subject ON digested_intelligence(digest_type, subject);
|
|
470
|
+
|
|
471
|
+
-- Decision windows — queried by decision_windows, compound_advantage
|
|
472
|
+
CREATE TABLE IF NOT EXISTS decision_windows (
|
|
473
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
474
|
+
window_type TEXT NOT NULL,
|
|
475
|
+
title TEXT NOT NULL,
|
|
476
|
+
description TEXT NOT NULL DEFAULT '',
|
|
477
|
+
urgency REAL NOT NULL DEFAULT 0.5,
|
|
478
|
+
relevance REAL NOT NULL DEFAULT 0.5,
|
|
479
|
+
source_item_ids TEXT NOT NULL DEFAULT '[]',
|
|
480
|
+
signal_chain_id INTEGER,
|
|
481
|
+
dependency TEXT,
|
|
482
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
483
|
+
opened_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
484
|
+
expires_at TEXT,
|
|
485
|
+
acted_at TEXT,
|
|
486
|
+
closed_at TEXT,
|
|
487
|
+
outcome TEXT,
|
|
488
|
+
lead_time_hours REAL,
|
|
489
|
+
streets_engine TEXT
|
|
490
|
+
);
|
|
491
|
+
CREATE INDEX IF NOT EXISTS idx_dw_status ON decision_windows(status);
|
|
492
|
+
|
|
493
|
+
-- Advantage score — queried by compound_advantage
|
|
494
|
+
CREATE TABLE IF NOT EXISTS advantage_score (
|
|
495
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
496
|
+
period TEXT NOT NULL,
|
|
497
|
+
score REAL NOT NULL DEFAULT 0.0,
|
|
498
|
+
items_surfaced INTEGER NOT NULL DEFAULT 0,
|
|
499
|
+
avg_lead_time_hours REAL NOT NULL DEFAULT 0.0,
|
|
500
|
+
windows_opened INTEGER NOT NULL DEFAULT 0,
|
|
501
|
+
windows_acted INTEGER NOT NULL DEFAULT 0,
|
|
502
|
+
windows_expired INTEGER NOT NULL DEFAULT 0,
|
|
503
|
+
knowledge_gaps_closed INTEGER NOT NULL DEFAULT 0,
|
|
504
|
+
calibration_accuracy REAL NOT NULL DEFAULT 0.0,
|
|
505
|
+
response_rate REAL,
|
|
506
|
+
computed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
507
|
+
);
|
|
508
|
+
CREATE INDEX IF NOT EXISTS idx_advantage_period ON advantage_score(period, computed_at);
|
|
509
|
+
|
|
510
|
+
-- Agent feedback — queried by record_agent_feedback, get_agent_feedback_stats
|
|
511
|
+
CREATE TABLE IF NOT EXISTS agent_feedback (
|
|
512
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
513
|
+
item_id TEXT NOT NULL,
|
|
514
|
+
outcome TEXT NOT NULL,
|
|
515
|
+
context TEXT,
|
|
516
|
+
session_task TEXT,
|
|
517
|
+
recorded_at TEXT DEFAULT (datetime('now'))
|
|
518
|
+
);
|
|
519
|
+
CREATE INDEX IF NOT EXISTS idx_agent_feedback_item ON agent_feedback(item_id);
|
|
520
|
+
CREATE INDEX IF NOT EXISTS idx_agent_feedback_outcome ON agent_feedback(outcome);
|
|
521
|
+
`);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Populate the standalone database from a project scan result.
|
|
525
|
+
* Inserts detected tech, dependencies, and topics so MCP tools return useful data.
|
|
526
|
+
*/
|
|
527
|
+
populateFromScan(scan) {
|
|
528
|
+
const insertTech = this.db.prepare("INSERT OR IGNORE INTO detected_tech (name, category, confidence, source) VALUES (?, ?, ?, ?)");
|
|
529
|
+
const insertTopic = this.db.prepare("INSERT OR IGNORE INTO active_topics (topic, weight, confidence, source) VALUES (?, ?, ?, ?)");
|
|
530
|
+
const insertDep = this.db.prepare("INSERT OR IGNORE INTO project_dependencies (project_path, manifest_type, package_name, version, is_dev, language) VALUES (?, ?, ?, ?, ?, ?)");
|
|
531
|
+
const insertInterest = this.db.prepare("INSERT OR IGNORE INTO explicit_interests (topic, weight, source) VALUES (?, ?, ?)");
|
|
532
|
+
const insertTechStack = this.db.prepare("INSERT OR IGNORE INTO tech_stack (technology) VALUES (?)");
|
|
533
|
+
// Run all inserts in a single transaction for speed
|
|
534
|
+
const populate = this.db.transaction(() => {
|
|
535
|
+
// Languages -> detected_tech + tech_stack + active_topics
|
|
536
|
+
for (const lang of scan.languages) {
|
|
537
|
+
insertTech.run(lang, "language", 0.95, "project_scan");
|
|
538
|
+
insertTechStack.run(lang);
|
|
539
|
+
insertTopic.run(lang, 0.8, 0.9, "project_scan");
|
|
540
|
+
}
|
|
541
|
+
// Frameworks -> detected_tech + tech_stack + active_topics
|
|
542
|
+
for (const fw of scan.frameworks) {
|
|
543
|
+
insertTech.run(fw, "framework", 0.85, "project_scan");
|
|
544
|
+
insertTechStack.run(fw);
|
|
545
|
+
insertTopic.run(fw, 0.7, 0.85, "project_scan");
|
|
546
|
+
}
|
|
547
|
+
// Determine primary ecosystem for dependency labeling.
|
|
548
|
+
// For multi-language projects, we use the primary ecosystem;
|
|
549
|
+
// this is a simplification — the desktop app's ACE engine handles
|
|
550
|
+
// per-manifest tracking more precisely.
|
|
551
|
+
const ecosystem = scan.languages.includes("typescript") || scan.languages.includes("javascript")
|
|
552
|
+
? "npm"
|
|
553
|
+
: scan.languages.includes("rust")
|
|
554
|
+
? "rust"
|
|
555
|
+
: scan.languages.includes("python")
|
|
556
|
+
? "python"
|
|
557
|
+
: scan.languages.includes("go")
|
|
558
|
+
? "go"
|
|
559
|
+
: "unknown";
|
|
560
|
+
const manifestType = ecosystem === "rust"
|
|
561
|
+
? "Cargo.toml"
|
|
562
|
+
: ecosystem === "python"
|
|
563
|
+
? "pyproject.toml"
|
|
564
|
+
: ecosystem === "go"
|
|
565
|
+
? "go.mod"
|
|
566
|
+
: "package.json";
|
|
567
|
+
// Production dependencies
|
|
568
|
+
for (const dep of scan.dependencies) {
|
|
569
|
+
insertDep.run(scan.projectPath, manifestType, dep, null, 0, ecosystem);
|
|
570
|
+
}
|
|
571
|
+
// Dev dependencies
|
|
572
|
+
for (const dep of scan.devDependencies) {
|
|
573
|
+
insertDep.run(scan.projectPath, manifestType, dep, null, 1, ecosystem);
|
|
574
|
+
}
|
|
575
|
+
// Topics -> active_topics + explicit_interests (so get_context returns them)
|
|
576
|
+
for (const topic of scan.topics) {
|
|
577
|
+
insertTopic.run(topic, 0.7, 0.8, "project_scan");
|
|
578
|
+
insertInterest.run(topic, 0.8, "project_scan");
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
populate();
|
|
582
|
+
}
|
|
163
583
|
/**
|
|
164
584
|
* Execute a database operation with retry logic for SQLITE_BUSY / SQLITE_LOCKED.
|
|
165
585
|
*
|
|
@@ -228,6 +648,8 @@ export class FourDADatabase {
|
|
|
228
648
|
: hoursAgo < 24
|
|
229
649
|
? `${hoursAgo} hours ago`
|
|
230
650
|
: `${Math.round(hoursAgo / 24)} days ago`;
|
|
651
|
+
// Look up persisted necessity scores from Rust analysis pipeline
|
|
652
|
+
const necessity = this.getNecessityForItem(item.id);
|
|
231
653
|
scoredItems.push({
|
|
232
654
|
id: item.id,
|
|
233
655
|
source_type: item.source_type,
|
|
@@ -238,6 +660,10 @@ export class FourDADatabase {
|
|
|
238
660
|
relevance_score: Math.round(score * 100) / 100,
|
|
239
661
|
created_at: item.created_at,
|
|
240
662
|
discovered_ago: discoveredAgo,
|
|
663
|
+
necessity_score: necessity.score,
|
|
664
|
+
necessity_reason: necessity.reason,
|
|
665
|
+
necessity_category: necessity.category,
|
|
666
|
+
necessity_urgency: necessity.urgency,
|
|
241
667
|
});
|
|
242
668
|
}
|
|
243
669
|
}
|
|
@@ -257,6 +683,29 @@ export class FourDADatabase {
|
|
|
257
683
|
`);
|
|
258
684
|
return stmt.get(itemId, sourceType) || null;
|
|
259
685
|
}
|
|
686
|
+
/**
|
|
687
|
+
* Look up persisted necessity scores for an item (written by Rust analysis pipeline).
|
|
688
|
+
* Returns defaults (0.0/null) if no necessity data exists yet.
|
|
689
|
+
*/
|
|
690
|
+
getNecessityForItem(itemId) {
|
|
691
|
+
try {
|
|
692
|
+
const stmt = this.db.prepare(`SELECT necessity_score, necessity_reason, necessity_category, necessity_urgency
|
|
693
|
+
FROM item_necessity WHERE source_item_id = ?`);
|
|
694
|
+
const row = stmt.get(itemId);
|
|
695
|
+
if (row) {
|
|
696
|
+
return {
|
|
697
|
+
score: row.necessity_score,
|
|
698
|
+
reason: row.necessity_reason,
|
|
699
|
+
category: row.necessity_category,
|
|
700
|
+
urgency: row.necessity_urgency,
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
catch {
|
|
705
|
+
// Table may not exist yet (pre-migration) — graceful fallback
|
|
706
|
+
}
|
|
707
|
+
return { score: 0.0, reason: null, category: null, urgency: null };
|
|
708
|
+
}
|
|
260
709
|
// ===========================================================================
|
|
261
710
|
// User Context
|
|
262
711
|
// ===========================================================================
|
|
@@ -495,6 +944,160 @@ export class FourDADatabase {
|
|
|
495
944
|
}
|
|
496
945
|
}
|
|
497
946
|
// ===========================================================================
|
|
947
|
+
// Agent Feedback
|
|
948
|
+
// ===========================================================================
|
|
949
|
+
/**
|
|
950
|
+
* Ensure the agent_feedback table exists.
|
|
951
|
+
* Called on first use so existing databases (created before this feature)
|
|
952
|
+
* get the table added without requiring a migration.
|
|
953
|
+
*/
|
|
954
|
+
ensureAgentFeedbackTable() {
|
|
955
|
+
this.db.exec(`
|
|
956
|
+
CREATE TABLE IF NOT EXISTS agent_feedback (
|
|
957
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
958
|
+
item_id TEXT NOT NULL,
|
|
959
|
+
outcome TEXT NOT NULL,
|
|
960
|
+
context TEXT,
|
|
961
|
+
session_task TEXT,
|
|
962
|
+
recorded_at TEXT DEFAULT (datetime('now'))
|
|
963
|
+
);
|
|
964
|
+
CREATE INDEX IF NOT EXISTS idx_agent_feedback_item ON agent_feedback(item_id);
|
|
965
|
+
CREATE INDEX IF NOT EXISTS idx_agent_feedback_outcome ON agent_feedback(outcome);
|
|
966
|
+
`);
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Record agent feedback on recommended content items
|
|
970
|
+
*/
|
|
971
|
+
recordAgentFeedback(itemIds, outcome, context, sessionTask) {
|
|
972
|
+
this.ensureAgentFeedbackTable();
|
|
973
|
+
const validOutcomes = ["used", "rejected", "partially_used"];
|
|
974
|
+
if (!validOutcomes.includes(outcome)) {
|
|
975
|
+
return {
|
|
976
|
+
success: false,
|
|
977
|
+
message: `Invalid outcome: ${outcome}. Valid outcomes: ${validOutcomes.join(", ")}`,
|
|
978
|
+
recorded_count: 0,
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
if (!itemIds || itemIds.length === 0) {
|
|
982
|
+
return {
|
|
983
|
+
success: false,
|
|
984
|
+
message: "item_ids must contain at least one item ID",
|
|
985
|
+
recorded_count: 0,
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
try {
|
|
989
|
+
const stmt = this.db.prepare(`
|
|
990
|
+
INSERT INTO agent_feedback (item_id, outcome, context, session_task)
|
|
991
|
+
VALUES (?, ?, ?, ?)
|
|
992
|
+
`);
|
|
993
|
+
const insertMany = this.db.transaction((ids) => {
|
|
994
|
+
let count = 0;
|
|
995
|
+
for (const id of ids) {
|
|
996
|
+
stmt.run(id, outcome, context || null, sessionTask || null);
|
|
997
|
+
count++;
|
|
998
|
+
}
|
|
999
|
+
return count;
|
|
1000
|
+
});
|
|
1001
|
+
const count = insertMany(itemIds);
|
|
1002
|
+
return {
|
|
1003
|
+
success: true,
|
|
1004
|
+
message: `Recorded ${outcome} feedback for ${count} item(s)`,
|
|
1005
|
+
recorded_count: count,
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
catch (error) {
|
|
1009
|
+
return {
|
|
1010
|
+
success: false,
|
|
1011
|
+
message: `Failed to record agent feedback: ${error instanceof Error ? error.message : String(error)}`,
|
|
1012
|
+
recorded_count: 0,
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Get agent feedback statistics for the given period
|
|
1018
|
+
*/
|
|
1019
|
+
getAgentFeedbackStats(days = 30) {
|
|
1020
|
+
this.ensureAgentFeedbackTable();
|
|
1021
|
+
const cutoff = `datetime('now', '-${Math.max(1, Math.min(365, days))} days')`;
|
|
1022
|
+
// Outcome totals
|
|
1023
|
+
const outcomeRows = this.db
|
|
1024
|
+
.prepare(`SELECT outcome, COUNT(*) as cnt
|
|
1025
|
+
FROM agent_feedback
|
|
1026
|
+
WHERE recorded_at >= ${cutoff}
|
|
1027
|
+
GROUP BY outcome`)
|
|
1028
|
+
.all();
|
|
1029
|
+
const outcomes = { used: 0, rejected: 0, partially_used: 0, total: 0 };
|
|
1030
|
+
for (const row of outcomeRows) {
|
|
1031
|
+
if (row.outcome === "used")
|
|
1032
|
+
outcomes.used = row.cnt;
|
|
1033
|
+
else if (row.outcome === "rejected")
|
|
1034
|
+
outcomes.rejected = row.cnt;
|
|
1035
|
+
else if (row.outcome === "partially_used")
|
|
1036
|
+
outcomes.partially_used = row.cnt;
|
|
1037
|
+
outcomes.total += row.cnt;
|
|
1038
|
+
}
|
|
1039
|
+
// By source type — join with source_items to get source_type
|
|
1040
|
+
const sourceRows = this.db
|
|
1041
|
+
.prepare(`SELECT COALESCE(si.source_type, 'unknown') as source_type, af.outcome, COUNT(*) as cnt
|
|
1042
|
+
FROM agent_feedback af
|
|
1043
|
+
LEFT JOIN source_items si ON CAST(af.item_id AS INTEGER) = si.id
|
|
1044
|
+
WHERE af.recorded_at >= ${cutoff}
|
|
1045
|
+
GROUP BY source_type, af.outcome
|
|
1046
|
+
ORDER BY cnt DESC`)
|
|
1047
|
+
.all();
|
|
1048
|
+
// Aggregate source rows into per-source stats
|
|
1049
|
+
const sourceMap = new Map();
|
|
1050
|
+
for (const row of sourceRows) {
|
|
1051
|
+
if (!sourceMap.has(row.source_type)) {
|
|
1052
|
+
sourceMap.set(row.source_type, { used: 0, rejected: 0, partially_used: 0, total: 0 });
|
|
1053
|
+
}
|
|
1054
|
+
const entry = sourceMap.get(row.source_type);
|
|
1055
|
+
if (row.outcome === "used")
|
|
1056
|
+
entry.used += row.cnt;
|
|
1057
|
+
else if (row.outcome === "rejected")
|
|
1058
|
+
entry.rejected += row.cnt;
|
|
1059
|
+
else if (row.outcome === "partially_used")
|
|
1060
|
+
entry.partially_used += row.cnt;
|
|
1061
|
+
entry.total += row.cnt;
|
|
1062
|
+
}
|
|
1063
|
+
const bySource = Array.from(sourceMap.entries()).map(([source_type, stats]) => ({
|
|
1064
|
+
source_type,
|
|
1065
|
+
...stats,
|
|
1066
|
+
usefulness_rate: stats.total > 0 ? (stats.used + stats.partially_used * 0.5) / stats.total : 0,
|
|
1067
|
+
}));
|
|
1068
|
+
// Top used items
|
|
1069
|
+
const topItems = this.db
|
|
1070
|
+
.prepare(`SELECT
|
|
1071
|
+
af.item_id,
|
|
1072
|
+
si.title,
|
|
1073
|
+
si.source_type,
|
|
1074
|
+
SUM(CASE WHEN af.outcome = 'used' THEN 1 ELSE 0 END) as used_count,
|
|
1075
|
+
SUM(CASE WHEN af.outcome = 'rejected' THEN 1 ELSE 0 END) as rejected_count
|
|
1076
|
+
FROM agent_feedback af
|
|
1077
|
+
LEFT JOIN source_items si ON CAST(af.item_id AS INTEGER) = si.id
|
|
1078
|
+
WHERE af.recorded_at >= ${cutoff}
|
|
1079
|
+
GROUP BY af.item_id
|
|
1080
|
+
HAVING used_count > 0
|
|
1081
|
+
ORDER BY used_count DESC
|
|
1082
|
+
LIMIT 10`)
|
|
1083
|
+
.all();
|
|
1084
|
+
// Recent feedback entries
|
|
1085
|
+
const recentFeedback = this.db
|
|
1086
|
+
.prepare(`SELECT item_id, outcome, context, session_task, recorded_at
|
|
1087
|
+
FROM agent_feedback
|
|
1088
|
+
WHERE recorded_at >= ${cutoff}
|
|
1089
|
+
ORDER BY recorded_at DESC
|
|
1090
|
+
LIMIT 20`)
|
|
1091
|
+
.all();
|
|
1092
|
+
return {
|
|
1093
|
+
period_days: days,
|
|
1094
|
+
outcomes,
|
|
1095
|
+
by_source: bySource,
|
|
1096
|
+
top_used_items: topItems,
|
|
1097
|
+
recent_feedback: recentFeedback,
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
// ===========================================================================
|
|
498
1101
|
// Helper Methods
|
|
499
1102
|
// ===========================================================================
|
|
500
1103
|
/**
|